English 中文 Español Deutsch 日本語 Português
Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе

Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе

MetaTrader 5Примеры | 11 июня 2021, 14:05
2 794 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Продолжаем развивать класс базового объекта-графического элемента, который является основой для создания более сложных графических объектов библиотеки. В прошлой статье мы выработали концепцию построения базового графического объекта, создали графический элемент и наделили его основными свойствами, которые уже можно устанавливать, изменять и получать.
Так как класс CCanvas предназначен для рисования "на холсте", то в нём имеются методы для работы с графическими примитивами и текстом. Сегодня мы напишем методы класса объекта-элемента, позволяющие обращаться к методам класса CCanvas для рисования и работать с ними. Это будут простые методы, на основе которых в объектах-наследниках класса объекта-элемента мы создадим более продвинутые методы для рисования.

Помимо создания методов для работы с примитивами мы создадим методы для работы с файлами — наши графические объекты, которые будут являться элементами GUI пользовательских программ, должны "помнить" свои свойства, состояние и расположение на графике, например, при смене таймфрейма. Для этого мы будем сохранять все свойства объекта в файл, а при построении объекта — читать свойства из файла при наличии такового.
Но так как работа с файлами у нас должна быть организована в классе-коллекции графических объектов, а до него мы ещё не дошли, то сегодня просто напишем методы для сохранения и загрузки свойств графического объекта. При создании же класса коллекции графических объектов мы будем использовать эти методы для сохранения и загрузки свойств, которые напишем для объекта-графического элемента сегодня.

Также в дальнейшем нам потребуется класс для работы с цветом. Сегодня мы его тоже добавим в библиотеку.
Класс мы возьмём из Библиотеки кодов MQL5.com, который был написан Дмитрием Федосеевым и любезно предоставлен сообществу.

В итоге мы сегодня будем иметь практически подготовленный для дальнейшего использования графический элемент, на основе которого далее будем создавать графические объекты библиотеки.


Доработка классов библиотеки

При работе с канвасом, если необходимо очистить объект класса CCanvas, имеющий прозрачность, мы должны использовать метод Erase(), в который по умолчанию передаётся ноль:

   //--- clear/fill color
   void              Erase(const uint clr=0);

Для нашего случая это некорректное решение, так как очищая канвас нулём, мы упускаем из вида его альфа-канал (канал прозрачности цвета), что в итоге приведёт к артефактам при рисовании на канвасе с альфа-каналом, очищенным таким образом.
Для очистки канваса с альфа-каналом вместо нуля необходимо использовать значение 0x00FFFFFF.
Это полностью прозрачный чёрный цвет в формате ARGB (Alpha = 0, Red = 255, Green = 255, Blue = 255).

В файле \MQL5\Include\DoEasy\Defines.mqh пропишем макроподстановку для указания такого цвета:

//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
#define NULL_COLOR                     (0x00FFFFFF)               // Ноль для канваса с альфа-каналом
//+------------------------------------------------------------------+

При выводе текста на канвас при помощи метода TextOut(), текстовому сообщению можно задать угол привязки — это точка центрирования текста — его ограничивающего прямоугольника, относительно которой будет располагаться само сообщение. Точки привязки задаются шестью флагами — комбинациями из двух флагов, все из которых перечислены ниже:

Флаги выравнивания текста по горизонтали:

  • TA_LEFT — точка привязки на левой стороне ограничивающего прямоугольника
  • TA_CENTER  точка привязки по горизонтали находится в середине ограничивающего прямоугольника
  • TA_RIGHT  точка привязки на правой стороне ограничивающего прямоугольника

Флаги выравнивания текста по вертикали:

  • TA_TOP  точка привязки на верхней стороне ограничивающего прямоугольника
  • TA_VCENTER  точка привязки по вертикали находится в середине ограничивающего прямоугольника
  • TA_BOTTOM  точка привязки на нижней стороне ограничивающего прямоугольника

Возможные комбинации флагов и задаваемые ими способы привязки показаны на рисунке:


Чтобы не путаться, какой флаг писать первым, какой вторым, просто зададим своё перечисление, в котором будут указаны все возможные комбинации флагов для выравнивания текста относительно его точки привязки:

//+------------------------------------------------------------------+
//| Данные для работы с графическими элементами                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список способов привязки                                         |
//| (выравнивание текста по горизонтали и вертикали)                 |
//+------------------------------------------------------------------+
enum ENUM_TEXT_ANCHOR
  {
   TEXT_ANCHOR_LEFT_TOP       =  0,                   // Точка привязки текста в левом верхнем углу ограничивающего прямоугольника
   TEXT_ANCHOR_CENTER_TOP     =  1,                   // Точка привязки текста по центру верхней стороны ограничивающего прямоугольника
   TEXT_ANCHOR_RIGHT_TOP      =  2,                   // Точка привязки текста в правом верхнем углу ограничивающего прямоугольника
   TEXT_ANCHOR_LEFT_CENTER    =  4,                   // Точка привязки текста в по центру левой стороны ограничивающего прямоугольника
   TEXT_ANCHOR_CENTER         =  5,                   // Точка привязки текста по центру ограничивающего прямоугольника
   TEXT_ANCHOR_RIGHT_CENTER   =  6,                   // Точка привязки текста в по центру правой стороны ограничивающего прямоугольника
   TEXT_ANCHOR_LEFT_BOTTOM    =  8,                   // Точка привязки текста в в левом нижнем углу ограничивающего прямоугольника
   TEXT_ANCHOR_CENTER_BOTTOM  =  9,                   // Точка привязки текста по центру нижней стороны ограничивающего прямоугольника
   TEXT_ANCHOR_RIGHT_BOTTOM   =  10,                  // Точка привязки текста в правом нижнем углу ограничивающего прямоугольника
  };
//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+

Здесь мы указали по три флага для каждого уровня привязки текста:

  1. Вертикальная точка привязки сверху (TA_TOP) — значение 0:
    • горизонтальная точка привязки слева (TA_LEFT) — значение 0,
    • горизонтальная точка привязки по центру (TA_CENTER) — значение 1,
    • горизонтальная точка привязки справа (TA_RIGHT) — значение 2.
  2. Вертикальная точка привязки по центру (TA_VCENTER) — значение 4:
    • горизонтальная точка привязки слева (TA_LEFT) — значение 0,
    • горизонтальная точка привязки по центру (TA_CENTER) — значение 1,
    • горизонтальная точка привязки справа (TA_RIGHT) — значение 2.
  3. Вертикальная точка привязки снизу (TA_BOTTOM) — значение 8:
    • горизонтальная точка привязки слева (TA_LEFT) — значение 0,
    • горизонтальная точка привязки по центру (TA_CENTER) — значение 1,
    • горизонтальная точка привязки справа (TA_RIGHT) — значение 2.

Каждое из значений перечисления ENUM_TEXT_ANCHOR соответствует значению комбинации правильно выставленных вышеперечисленных флагов:

  • TEXT_ANCHOR_LEFT_TOP = (TA_LEFT | TA_TOP) = 0,
  • TEXT_ANCHOR_CENTER_TOP = (TA_CENTER | TA_TOP) = 1,
  • TEXT_ANCHOR_RIGHT_TOP = (TA_RIGHT | TA_TOP) = 2,
  • TEXT_ANCHOR_LEFT_CENTER = (TA_LEFT | TA_VCENTER) = 4,
  • TEXT_ANCHOR_CENTER = (TA_CENTER | TA_VCENTER) = 5,
  • TEXT_ANCHOR_RIGHT_CENTER = (TA_RIGHT | TA_VCENTER) = 6,
  • TEXT_ANCHOR_LEFT_BOTTOM = (TA_LEFT | TA_BOTTOM) = 8,
  • TEXT_ANCHOR_CENTER_BOTTOM = (TA_CENTER | TA_BOTTOM) = 9,
  • TEXT_ANCHOR_RIGHT_BOTTOM = (TA_RIGHT | TA_BOTTOM) = 10.

Далее для указания выравнивания текста относительно его точки привязки будем использовать это перечисление.

Так как мы занялись созданием графической части библиотеки, нам в дальнейшем потребуются различные методы работы с цветом.
В Библиотеке исходных кодов MQL5.com есть замечательная библиотека функций для работы с цветом — её любезно предоставил Дмитрий Федосеев для общего пользования.

Возьмём его класс CColors и немного подкорректируем его — сделаем его статическим — чтобы не задавать объект класса, а напрямую обращаться к его методам при помощи оператора разрешения контекста (::), как пример:

class_name::variable

Таким образом, имея подключенный к библиотеке файл класса CColors, мы сможем в любом месте нашего кода (в том числе и в пользовательской программе) обращаться к методам класса, например, для смешивания двух цветов — голубого с непрозрачностью 128 и красного с непрозрачностью 64:

CColors::BlendColors(ColorToARGB(clrBlue,128),ColorToARGB(clrRed,64));

Файл класса сохраним в каталоге библиотеки \MQL5\Include\DoEasy\Services\ в файле Colors.mqh.

//+------------------------------------------------------------------+
//|                                                       Colors.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/integer"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Класс для работы с цветом                                        |
//+------------------------------------------------------------------+
class CColors
  {
private:
   static double     Arctan2(const double x,const double y);
   static double     Hue_To_RGB(double v1,double v2,double vH);
public:
//+------------------------------------------------------------------+
//| Список функций с сайта http://www.easyrgb.com/index.php?X=MATH   |
//+------------------------------------------------------------------+
   static void       RGBtoXYZ(const double aR,const double aG,const double aB,double &oX,double &oY,double &oZ);
   static void       XYZtoRGB(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB);
   static void       XYZtoYxy(const double aX,const double aY,const double aZ,double &oY,double &ox,double &oy);
   static void       YxyToXYZ(const double aY,const double ax,const double ay,double &oX,double &oY,double &oZ);
   static void       XYZtoHunterLab(const double aX,const double aY,const double aZ,double &oL,double &oa,double &ob);
   static void       HunterLabToXYZ(const double aL,const double aa,const double ab,double &oX,double &oY,double &oZ);
   static void       XYZtoCIELab(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEa,double &oCIEb);
   static void       CIELabToXYZ(const double aCIEL,const double aCIEa,const double aCIEb,double &oX,double &oY,double &oZ);
   static void       CIELabToCIELCH(const double aCIEL,const double aCIEa,const double aCIEb,double &oCIEL,double &oCIEC,double &oCIEH);
   static void       CIELCHtoCIELab(const double aCIEL,const double aCIEC,const double aCIEH,double &oCIEL,double &oCIEa,double &oCIEb);
   static void       XYZtoCIELuv(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEu,double &oCIEv);
   static void       CIELuvToXYZ(const double aCIEL,const double aCIEu,const double aCIEv,double &oX,double &oY,double &oZ);
   static void       RGBtoHSL(const double aR,const double aG,const double aB,double &oH,double &oS,double &oL);
   static void       HSLtoRGB(const double aH,const double aS,const double aL,double &oR,double &oG,double &oB);
   static void       RGBtoHSV(const double aR,const double aG,const double aB,double &oH,double &oS,double &oV);
   static void       HSVtoRGB(const double aH,const double aS,const double aV,double &oR,double &oG,double &oB);
   static void       RGBtoCMY(const double aR,const double aG,const double aB,double &oC,double &oM,double &oY);
   static void       CMYtoRGB(const double aC,const double aM,const double aY,double &oR,double &oG,double &oB);
   static void       CMYtoCMYK(const double aC,const double aM,const double aY,double &oC,double &oM,double &oY,double &oK);
   static void       CMYKtoCMY(const double aC,const double aM,const double aY,const double aK,double &oC,double &oM,double &oY);
   static void       RGBtoLab(const double aR,const double aG,const double aB,double &oL,double &oa,double &ob);
//+------------------------------------------------------------------+
//| Другие функции работы с цветом                                   |
//+------------------------------------------------------------------+
   static void       ColorToRGB(const color aColor,double &aR,double &aG,double &aB);
   static double     GetR(const color aColor);
   static double     GetG(const color aColor);
   static double     GetB(const color aColor);
   static double     GetA(const color aColor);
   static color      RGBToColor(const double aR,const double aG,const double aB);
   static color      MixColors(const color aCol1,const color aCol2,const double aK);
   static color      BlendColors(const uint lower_color,const uint upper_color);
   static void       Gradient(color &aColors[],color &aOut[],int aOutCount,bool aCycle=false);
   static void       RGBtoXYZsimple(double aR,double aG,double aB,double &oX,double &oY,double &oZ);
   static void       XYZtoRGBsimple(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB);
   static color      Negative(const color aColor);
   static color      StandardColor(const color aColor,int &aIndex);
   static double     RGBtoGray(double aR,double aG,double aB);
   static double     RGBtoGraySimple(double aR,double aG,double aB);
  };
//+------------------------------------------------------------------+
//| Методы класса                                                    |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Arctan2                                                          |
//+------------------------------------------------------------------+
double CColors::Arctan2(const double x,const double y)
  {
   if(y==0)
      return(x<0 ? M_PI : 0);
   else
     {
      if(x>0)
         return(::atan(y/x));
      if(x<0)
         return(y>0 ? atan(y/x)+M_PI : atan(y/x)-M_PI);
      else
         return(y<0 ? -M_PI_2 : M_PI_2);
     }
  }
//+------------------------------------------------------------------+
//| Hue_To_RGB                                                       |
//+------------------------------------------------------------------+
double CColors::Hue_To_RGB(double v1,double v2,double vH)
  {
   if(vH<0)
      vH+=1.0;
   if(vH>1.0)
      vH-=1;
   if((6.0*vH)<1.0)
      return(v1+(v2-v1)*6.0*vH);
   if((2.0*vH)<1.0)
      return(v2);
   if((3.0*vH)<2.0)
      return(v1+(v2-v1)*((2.0/3.0)-vH)*6.0);
//---
   return(v1);
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в XYZ                                         |
//+------------------------------------------------------------------+
void CColors::RGBtoXYZ(const double aR,const double aG,const double aB,double &oX,double &oY,double &oZ)
  {
   double var_R=aR/255;
   double var_G=aG/255;
   double var_B=aB/255;
//---
   if(var_R>0.04045)
      var_R=::pow((var_R+0.055)/1.055,2.4);
   else
      var_R=var_R/12.92;
//---
   if(var_G>0.04045)
      var_G=::pow((var_G+0.055)/1.055,2.4);
   else
      var_G=var_G/12.92;
//---
   if(var_B>0.04045)
      var_B=::pow((var_B+0.055)/1.055,2.4);
   else
      var_B=var_B/12.92;
//---
   var_R =var_R*100.0;
   var_G =var_G*100.0;
   var_B =var_B*100.0;
   oX    =var_R*0.4124+var_G*0.3576+var_B*0.1805;
   oY    =var_R*0.2126+var_G*0.7152+var_B*0.0722;
   oZ    =var_R*0.0193+var_G*0.1192+var_B*0.9505;
  }
//+------------------------------------------------------------------+
//| Преобразование XYZ в RGB                                         |
//+------------------------------------------------------------------+
void CColors::XYZtoRGB(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB)
  {
   double var_X =aX/100;
   double var_Y =aY/100;
   double var_Z =aZ/100;
   double var_R =var_X*3.2406+var_Y*-1.5372+var_Z*-0.4986;
   double var_G =var_X*(-0.9689)+var_Y*1.8758+var_Z*0.0415;
   double var_B =var_X*0.0557+var_Y*(-0.2040)+var_Z*1.0570;
//---
   if(var_R>0.0031308)
      var_R=1.055*(::pow(var_R,1.0/2.4))-0.055;
   else
      var_R=12.92*var_R;
//---
   if(var_G>0.0031308)
      var_G=1.055*(::pow(var_G,1.0/2.4))-0.055;
   else
      var_G=12.92*var_G;
//---
   if(var_B>0.0031308)
      var_B=1.055*(::pow(var_B,1.0/2.4))-0.055;
   else
      var_B=12.92*var_B;
//---
   oR =var_R*255.0;
   oG =var_G*255.0;
   oB =var_B*255.0;
  }
//+------------------------------------------------------------------+
//| Преобразование XYZ в Yxy                                         |
//+------------------------------------------------------------------+
void CColors::XYZtoYxy(const double aX,const double aY,const double aZ,double &oY,double &ox,double &oy)
  {
   oY =aY;
   ox =aX/(aX+aY+aZ);
   oy =aY/(aX+aY+aZ);
  }
//+------------------------------------------------------------------+
//| Преобразование Yxy в XYZ                                         |
//+------------------------------------------------------------------+
void CColors::YxyToXYZ(const double aY,const double ax,const double ay,double &oX,double &oY,double &oZ)
  {
   oX =ax*(aY/ay);
   oY =aY;
   oZ =(1.0-ax-ay)*(aY/ay);
  }
//+------------------------------------------------------------------+
//| Преобразование XYZ в HunterLab                                   |
//+------------------------------------------------------------------+
void CColors::XYZtoHunterLab(const double aX,const double aY,const double aZ,double &oL,double &oa,double &ob)
  {
   oL =10.0*::sqrt(aY);
   oa =17.5*(((1.02*aX)-aY)/::sqrt(aY));
   ob =7.0*((aY-(0.847*aZ))/::sqrt(aY));
  }
//+------------------------------------------------------------------+
//| Преобразование HunterLab в XYZ                                   |
//+------------------------------------------------------------------+
void CColors::HunterLabToXYZ(const double aL,const double aa,const double ab,double &oX,double &oY,double  &oZ)
  {
   double var_Y =aL/10.0;
   double var_X =aa/17.5*aL/10.0;
   double var_Z =ab/7.0*aL/10.0;
//---
   oY =::pow(var_Y,2);
   oX =(var_X+oY)/1.02;
   oZ =-(var_Z-oY)/0.847;
  }
//+------------------------------------------------------------------+
//| Преобразование XYZ в CIELab                                      |
//+------------------------------------------------------------------+
void CColors::XYZtoCIELab(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEa,double &oCIEb)
  {
   double ref_X =95.047;
   double ref_Y =100.0;
   double ref_Z =108.883;
   double var_X =aX/ref_X;
   double var_Y =aY/ref_Y;
   double var_Z =aZ/ref_Z;
//---
   if(var_X>0.008856)
      var_X=::pow(var_X,1.0/3.0);
   else
      var_X=(7.787*var_X)+(16.0/116.0);
//---
   if(var_Y>0.008856)
      var_Y=::pow(var_Y,1.0/3.0);
   else
      var_Y=(7.787*var_Y)+(16.0/116.0);
//---
   if(var_Z>0.008856)
      var_Z=::pow(var_Z,1.0/3.0);
   else
      var_Z=(7.787*var_Z)+(16.0/116.0);
//---
   oCIEL =(116.0*var_Y)-16.0;
   oCIEa =500.0*(var_X-var_Y);
   oCIEb =200*(var_Y-var_Z);
  }
//+------------------------------------------------------------------+
//| Преобразование CIELab в ToXYZ                                    |
//+------------------------------------------------------------------+
void CColors::CIELabToXYZ(const double aCIEL,const double aCIEa,const double aCIEb,double &oX,double &oY,double &oZ)
  {
   double var_Y =(aCIEL+16.0)/116.0;
   double var_X =aCIEa/500.0+var_Y;
   double var_Z =var_Y-aCIEb/200.0;
//---
   if(::pow(var_Y,3)>0.008856)
      var_Y=::pow(var_Y,3);
   else
      var_Y=(var_Y-16.0/116.0)/7.787;
//---
   if(::pow(var_X,3)>0.008856)
      var_X=::pow(var_X,3);
   else
      var_X=(var_X-16.0/116.0)/7.787;
//---
   if(::pow(var_Z,3)>0.008856)
      var_Z=::pow(var_Z,3);
   else
      var_Z=(var_Z-16.0/116.0)/7.787;
//---
   double ref_X =95.047;
   double ref_Y =100.0;
   double ref_Z =108.883;
//---
   oX =ref_X*var_X;
   oY =ref_Y*var_Y;
   oZ =ref_Z*var_Z;
  }
//+------------------------------------------------------------------+
//| Преобразование CIELab в CIELCH                                   |
//+------------------------------------------------------------------+
void CColors::CIELabToCIELCH(const double aCIEL,const double aCIEa,const double aCIEb,double &oCIEL,double &oCIEC,double &oCIEH)
  {
   double var_H=Arctan2(aCIEb,aCIEa);
//---
   if(var_H>0)
      var_H=(var_H/M_PI)*180.0;
   else
      var_H=360.0-(::fabs(var_H)/M_PI)*180.0;
//---
   oCIEL =aCIEL;
   oCIEC =::sqrt(::pow(aCIEa,2)+::pow(aCIEb,2));
   oCIEH =var_H;
  }
//+------------------------------------------------------------------+
//| Преобразование CIELCH в CIELab                                   |
//+------------------------------------------------------------------+
void CColors::CIELCHtoCIELab(const double aCIEL,const double aCIEC,const double aCIEH,double &oCIEL,double &oCIEa,double &oCIEb)
  {
//--- Аргументы from 0 to 360°
   oCIEL =aCIEL;
   oCIEa =::cos(M_PI*aCIEH/180.0)*aCIEC;
   oCIEb =::sin(M_PI*aCIEH/180)*aCIEC;
  }
//+------------------------------------------------------------------+
//| Преобразование XYZ в CIELuv                                      |
//+------------------------------------------------------------------+
void CColors::XYZtoCIELuv(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEu,double &oCIEv)
  {
   double var_U =(4.0*aX)/(aX+(15.0*aY)+(3.0*aZ));
   double var_V =(9.0*aY)/(aX+(15.0*aY)+(3.0*aZ));
   double var_Y =aY/100.0;
//---
   if(var_Y>0.008856)
      var_Y=::pow(var_Y,1.0/3.0);
   else
      var_Y=(7.787*var_Y)+(16.0/116.0);
//---
   double ref_X =95.047;
   double ref_Y =100.000;
   double ref_Z =108.883;
   double ref_U =(4.0*ref_X)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z));
   double ref_V =(9.0*ref_Y)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z));
//---
   oCIEL =(116.0*var_Y)-16.0;
   oCIEu =13.0*oCIEL*(var_U-ref_U);
   oCIEv =13.0*oCIEL*(var_V-ref_V);
  }
//+------------------------------------------------------------------+
//| Преобразование CIELuv в XYZ                                      |
//+------------------------------------------------------------------+
void CColors::CIELuvToXYZ(const double aCIEL,const double aCIEu,const double aCIEv,double &oX,double &oY,double &oZ)
  {
   double var_Y=(aCIEL+16.0)/116.0;
//---
   if(::pow(var_Y,3)>0.008856)
      var_Y=::pow(var_Y,3);
   else
      var_Y=(var_Y-16.0/116.0)/7.787;
//---
   double ref_X =95.047;
   double ref_Y =100.000;
   double ref_Z =108.883;
   double ref_U =(4.0*ref_X)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z));
   double ref_V =(9.0*ref_Y)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z));
   double var_U =aCIEu/(13.0*aCIEL)+ref_U;
   double var_V =aCIEv/(13.0*aCIEL)+ref_V;
//---
   oY=var_Y*100.0;
   oX=-(9.0*oY*var_U)/((var_U-4.0)*var_V-var_U*var_V);
   oZ=(9.0*oY-(15.0*var_V*oY)-(var_V*oX))/(3.0*var_V);
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в HSL                                         |
//+------------------------------------------------------------------+
void CColors::RGBtoHSL(const double aR,const double aG,const double aB,double &oH,double &oS,double &oL)
  {
   double var_R   =(aR/255);
   double var_G   =(aG/255);
   double var_B   =(aB/255);
   double var_Min =::fmin(var_R,::fmin(var_G,var_B));
   double var_Max =::fmax(var_R,::fmax(var_G,var_B));
   double del_Max =var_Max-var_Min;
//---
   oL=(var_Max+var_Min)/2;
//---
   if(del_Max==0)
     {
      oH=0;
      oS=0;
     }
   else
     {
      if(oL<0.5)
         oS=del_Max/(var_Max+var_Min);
      else
         oS=del_Max/(2.0-var_Max-var_Min);
      //---
      double del_R =(((var_Max-var_R)/6.0)+(del_Max/2.0))/del_Max;
      double del_G =(((var_Max-var_G)/6.0)+(del_Max/2.0))/del_Max;
      double del_B =(((var_Max-var_B)/6.0)+(del_Max/2.0))/del_Max;
      //---
      if(var_R==var_Max)
         oH=del_B-del_G;
      else if(var_G==var_Max)
         oH=(1.0/3.0)+del_R-del_B;
      else if(var_B==var_Max)
         oH=(2.0/3.0)+del_G-del_R;
      //---
      if(oH<0)
         oH+=1.0;
      //---
      if(oH>1)
         oH-=1.0;
     }
  }
//+------------------------------------------------------------------+
//| Преобразование HSL в RGB                                         |
//+------------------------------------------------------------------+
void CColors::HSLtoRGB(const double aH,const double aS,const double aL,double &oR,double &oG,double &oB)
  {
   if(aS==0)
     {
      oR=aL*255;
      oG=aL*255;
      oB=aL*255;
     }
   else
     {
      double var_2=0.0;
      //---
      if(aL<0.5)
         var_2=aL*(1.0+aS);
      else
         var_2=(aL+aS)-(aS*aL);
      //---
      double var_1=2.0*aL-var_2;
      oR =255.0*Hue_To_RGB(var_1,var_2,aH+(1.0/3.0));
      oG =255.0*Hue_To_RGB(var_1,var_2,aH);
      oB =255.0*Hue_To_RGB(var_1,var_2,aH-(1.0/3.0));
     }
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в HSV                                         |
//+------------------------------------------------------------------+
void CColors::RGBtoHSV(const double aR,const double aG,const double aB,double &oH,double &oS,double &oV)
  {
   const double var_R   =(aR/255.0);
   const double var_G   =(aG/255.0);
   const double var_B   =(aB/255.0);

   const double var_Min =::fmin(var_R,::fmin(var_G, var_B));
   const double var_Max =::fmax(var_R,::fmax(var_G,var_B));
   const double del_Max =var_Max-var_Min;
//---
   oV=var_Max;
//---
   if(del_Max==0)
     {
      oH=0;
      oS=0;
     }
   else
     {
      oS=del_Max/var_Max;
      const double del_R =(((var_Max-var_R)/6.0)+(del_Max/2))/del_Max;
      const double del_G =(((var_Max-var_G)/6.0)+(del_Max/2))/del_Max;
      const double del_B =(((var_Max-var_B)/6.0)+(del_Max/2))/del_Max;
      //---
      if(var_R==var_Max)
         oH=del_B-del_G;
      else if(var_G==var_Max)
         oH=(1.0/3.0)+del_R-del_B;
      else if(var_B==var_Max)
         oH=(2.0/3.0)+del_G-del_R;
      //---
      if(oH<0)
         oH+=1.0;
      //---
      if(oH>1.0)
         oH-=1.0;
     }
  }
//+------------------------------------------------------------------+
//| Преобразование HSV в RGB                                         |
//+------------------------------------------------------------------+
void CColors::HSVtoRGB(const double aH,const double aS,const double aV,double &oR,double &oG,double &oB)
  {
   if(aS==0)
     {
      oR =aV*255.0;
      oG =aV*255.0;
      oB =aV*255.0;
     }
   else
     {
      double var_h=aH*6.0;
      //---
      if(var_h==6)
         var_h=0;
      //---
      int    var_i =int(var_h);
      double var_1 =aV*(1.0-aS);
      double var_2 =aV*(1.0-aS*(var_h-var_i));
      double var_3 =aV*(1.0-aS*(1.0-(var_h-var_i)));
      double var_r =0.0;
      double var_g =0.0;
      double var_b =0.0;
      //---
      if(var_i==0)
        {
         var_r =aV;
         var_g =var_3;
         var_b =var_1;
        }
      else if(var_i==1.0)
        {
         var_r=var_2;
         var_g=aV;
         var_b=var_1;
        }
      else if(var_i==2.0)
        {
         var_r=var_1;
         var_g=aV;
         var_b=var_3;
        }
      else if(var_i==3)
        {
         var_r=var_1;
         var_g=var_2;
         var_b=aV;
        }
      else if(var_i==4)
        {
         var_r=var_3;
         var_g=var_1;
         var_b=aV;
        }
      else
        {
         var_r=aV;
         var_g=var_1;
         var_b=var_2;
        }
      //---
      oR =var_r*255.0;
      oG =var_g*255.0;
      oB =var_b*255.0;
     }
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в CMY                                         |
//+------------------------------------------------------------------+
void CColors::RGBtoCMY(const double aR,const double aG,const double aB,double &oC,double &oM,double &oY)
  {
   oC =1.0-(aR/255.0);
   oM =1.0-(aG/255.0);
   oY =1.0-(aB/255.0);
  }
//+------------------------------------------------------------------+
//| Преобразование CMY в RGB                                         |
//+------------------------------------------------------------------+
void CColors::CMYtoRGB(const double aC,const double aM,const double aY,double &oR,double &oG,double &oB)
  {
   oR =(1.0-aC)*255.0;
   oG =(1.0-aM)*255.0;
   oB =(1.0-aY)*255.0;
  }
//+------------------------------------------------------------------+
//| Преобразование CMY в CMYK                                        |
//+------------------------------------------------------------------+
void CColors::CMYtoCMYK(const double aC,const double aM,const double aY,double &oC,double &oM,double &oY,double &oK)
  {
   double var_K=1;
//---
   if(aC<var_K)
      var_K=aC;
   if(aM<var_K)
      var_K=aM;
   if(aY<var_K)
      var_K=aY;
//---
   if(var_K==1.0)
     {
      oC =0;
      oM =0;
      oY =0;
     }
   else
     {
      oC =(aC-var_K)/(1.0-var_K);
      oM =(aM-var_K)/(1.0-var_K);
      oY =(aY-var_K)/(1.0-var_K);
     }
//---
   oK=var_K;
  }
//+------------------------------------------------------------------+
//| Преобразование CMYK в CMY                                        |
//+------------------------------------------------------------------+
void CColors::CMYKtoCMY(const double aC,const double aM,const double aY,const double aK,double &oC,double &oM,double &oY)
  {
   oC =(aC*(1.0-aK)+aK);
   oM =(aM*(1.0-aK)+aK);
   oY =(aY*(1.0-aK)+aK);
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в Lab                                         |
//+------------------------------------------------------------------+
void CColors::RGBtoLab(const double aR,const double aG,const double aB,double &oL,double &oa,double &ob)
  {
   double X=0,Y=0,Z=0;
   RGBtoXYZ(aR,aG,aB,X,Y,Z);
   XYZtoHunterLab(X,Y,Z,oL,oa,ob);
  }
//+------------------------------------------------------------------+
//| Получение значений компонентов RGB                               |
//+------------------------------------------------------------------+
void CColors::ColorToRGB(const color aColor,double &aR,double &aG,double &aB)
  {
   aR =GetR(aColor);
   aG =GetG(aColor);
   aB =GetB(aColor);
  }
//+------------------------------------------------------------------+
//| Получение значения компонента R                                  |
//+------------------------------------------------------------------+
double CColors::GetR(const color aColor)
  {
   return(aColor&0xff);
  }
//+------------------------------------------------------------------+
//| Получение значения компонента G                                  |
//+------------------------------------------------------------------+
double CColors::GetG(const color aColor)
  {
   return((aColor>>8)&0xff);
  }
//+------------------------------------------------------------------+
//| Получение значения компонента B                                  |
//+------------------------------------------------------------------+
double CColors::GetB(const color aColor)
  {
   return((aColor>>16)&0xff);
  }
//+------------------------------------------------------------------+
//| Получение значения компонента A                                  |
//+------------------------------------------------------------------+
double CColors::GetA(const color aColor)
  {
   return(double(uchar((aColor)>>24)));
  }
//+------------------------------------------------------------------+
//| Преобразование RGB в const color                                 |
//+------------------------------------------------------------------+
color CColors::RGBToColor(const double aR,const double aG,const double aB)
  {
   int int_r =(int)::round(aR);
   int int_g =(int)::round(aG);
   int int_b =(int)::round(aB);
   int Color =0;
//---
   Color=int_b;
   Color<<=8;
   Color|=int_g;
   Color<<=8;
   Color|=int_r;
//---
   return((color)Color);
  }
//+------------------------------------------------------------------+
//| Получение значени промежуточного цвета между двух цветов         |
//+------------------------------------------------------------------+
color CColors::MixColors(const color aCol1,const color aCol2,const double aK)
  {
//--- aK - от 0 до 1
   double R1=0.0,G1=0.0,B1=0.0,R2=0.0,G2=0.0,B2=0.0;
//---
   ColorToRGB(aCol1,R1,G1,B1);
   ColorToRGB(aCol2,R2,G2,B2);
//---
   R1+=(int)::round(aK*(R2-R1));
   G1+=(int)::round(aK*(G2-G1));
   B1+=(int)::round(aK*(B2-B1));
//---
   return(RGBToColor(R1,G1,B1));
  }
//+------------------------------------------------------------------+
//| Смешивание двух цветов с учётом прозрачности верхнего            |
//+------------------------------------------------------------------+
color CColors::BlendColors(const uint lower_color,const uint upper_color)
  {
   double r1=0,g1=0,b1=0;
   double r2=0,g2=0,b2=0,alpha=0;
   double r3=0,g3=0,b3=0;
//--- Конвертация цвета в формат ARGB
   uint pixel_color=::ColorToARGB(upper_color);
//--- Получим компоненты нижнего и верхнего цветов
   ColorToRGB(lower_color,r1,g1,b1);
   ColorToRGB(pixel_color,r2,g2,b2);
//--- Получим процент прозрачности от 0.00 до 1.00
   alpha=GetA(upper_color)/255.0;
//--- Если есть прозрачность
   if(alpha<1.0)
     {
      //--- Смешиваем компоненты с учётом альфа-канала
      r3=(r1*(1-alpha))+(r2*alpha);
      g3=(g1*(1-alpha))+(g2*alpha);
      b3=(b1*(1-alpha))+(b2*alpha);
      //--- Коррекция полученных значений
      r3=(r3>255)? 255 : r3;
      g3=(g3>255)? 255 : g3;
      b3=(b3>255)? 255 : b3;
     }
   else
     {
      r3=r2;
      g3=g2;
      b3=b2;
     }
//--- Соединить полученные компоненты и вернуть цвет
   return(RGBToColor(r3,g3,b3));
  }
//+------------------------------------------------------------------+
//| Получение массива заданного размера с цветовым градиентом        |
//+------------------------------------------------------------------+
void CColors::Gradient(color &aColors[],   // Список цветов
                       color &aOut[],      // Возвращаемый массив
                       int   aOutCount,    // Установка размера возвращаемого массива
                       bool  aCycle=false) // Замкнутый цикл. Возвращаемый массив заканчивается таким же цветом, с которого начинается
  {
   ::ArrayResize(aOut,aOutCount);
//---
   int    InCount =::ArraySize(aColors)+aCycle;
   int    PrevJ   =0;
   int    nci     =0;
   double K       =0.0;
//---
   for(int i=1; i<InCount; i++)
     {
      int J=(aOutCount-1)*i/(InCount-1);
      //---
      for(int j=PrevJ; j<=J; j++)
        {
         if(aCycle && i==InCount-1)
           {
            nci =0;
            K   =1.0*(j-PrevJ)/(J-PrevJ+1);
           }
         else
           {
            nci =i;
            K   =1.0*(j-PrevJ)/(J-PrevJ);
           }
         aOut[j]=MixColors(aColors[i-1],aColors[nci],K);
        }
      PrevJ=J;
     }
  }
//+------------------------------------------------------------------+
//| Еще один вариант преобразования RGB в XYZ и                      |
//| соответствующее ему преобразование XYZ в RGB                     |
//+------------------------------------------------------------------+
void CColors::RGBtoXYZsimple(double aR,double aG,double aB,double &oX,double &oY,double &oZ)
  {
   aR/=255;
   aG/=255;
   aB/=255;
   aR*=100;
   aG*=100;
   aB*=100;
//---
   oX=0.431*aR+0.342*aG+0.178*aB;
   oY=0.222*aR+0.707*aG+0.071*aB;
   oZ=0.020*aR+0.130*aG+0.939*aB;
  }
//+------------------------------------------------------------------+
//| XYZtoRGBsimple                                                   |
//+------------------------------------------------------------------+
void CColors::XYZtoRGBsimple(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB)
  {
   oR=3.063*aX-1.393*aY-0.476*aZ;
   oG=-0.969*aX+1.876*aY+0.042*aZ;
   oB=0.068*aX-0.229*aY+1.069*aZ;
  }
//+------------------------------------------------------------------+
//| Негативный цвет                                                  |
//+------------------------------------------------------------------+
color CColors::Negative(const color aColor)
  {
   double R=0.0,G=0.0,B=0.0;
   ColorToRGB(aColor,R,G,B);
//---
   return(RGBToColor(255-R,255-G,255-B));
  }
//+------------------------------------------------------------------+
//| Поиск наиболее похожего цвета                                    |
//| в наборе стандартных цветов терминала                            |
//+------------------------------------------------------------------+
color CColors::StandardColor(const color aColor,int &aIndex)
  {
   color m_c[]=
     {
      clrBlack,clrDarkGreen,clrDarkSlateGray,clrOlive,clrGreen,clrTeal,clrNavy,clrPurple,clrMaroon,clrIndigo,
      clrMidnightBlue,clrDarkBlue,clrDarkOliveGreen,clrSaddleBrown,clrForestGreen,clrOliveDrab,clrSeaGreen,
      clrDarkGoldenrod,clrDarkSlateBlue,clrSienna,clrMediumBlue,clrBrown,clrDarkTurquoise,clrDimGray,
      clrLightSeaGreen,clrDarkViolet,clrFireBrick,clrMediumVioletRed,clrMediumSeaGreen,clrChocolate,clrCrimson,
      clrSteelBlue,clrGoldenrod,clrMediumSpringGreen,clrLawnGreen,clrCadetBlue,clrDarkOrchid,clrYellowGreen,
      clrLimeGreen,clrOrangeRed,clrDarkOrange,clrOrange,clrGold,clrYellow,clrChartreuse,clrLime,clrSpringGreen,
      clrAqua,clrDeepSkyBlue,clrBlue,clrFuchsia,clrRed,clrGray,clrSlateGray,clrPeru,clrBlueViolet,clrLightSlateGray,
      clrDeepPink,clrMediumTurquoise,clrDodgerBlue,clrTurquoise,clrRoyalBlue,clrSlateBlue,clrDarkKhaki,clrIndianRed,
      clrMediumOrchid,clrGreenYellow,clrMediumAquamarine,clrDarkSeaGreen,clrTomato,clrRosyBrown,clrOrchid,
      clrMediumPurple,clrPaleVioletRed,clrCoral,clrCornflowerBlue,clrDarkGray,clrSandyBrown,clrMediumSlateBlue,
      clrTan,clrDarkSalmon,clrBurlyWood,clrHotPink,clrSalmon,clrViolet,clrLightCoral,clrSkyBlue,clrLightSalmon,
      clrPlum,clrKhaki,clrLightGreen,clrAquamarine,clrSilver,clrLightSkyBlue,clrLightSteelBlue,clrLightBlue,
      clrPaleGreen,clrThistle,clrPowderBlue,clrPaleGoldenrod,clrPaleTurquoise,clrLightGray,clrWheat,clrNavajoWhite,
      clrMoccasin,clrLightPink,clrGainsboro,clrPeachPuff,clrPink,clrBisque,clrLightGoldenrod,clrBlanchedAlmond,
      clrLemonChiffon,clrBeige,clrAntiqueWhite,clrPapayaWhip,clrCornsilk,clrLightYellow,clrLightCyan,clrLinen,
      clrLavender,clrMistyRose,clrOldLace,clrWhiteSmoke,clrSeashell,clrIvory,clrHoneydew,clrAliceBlue,clrLavenderBlush,
      clrMintCream,clrSnow,clrWhite,clrDarkCyan,clrDarkRed,clrDarkMagenta,clrAzure,clrGhostWhite,clrFloralWhite
     };
//---
   double m_rv=0.0,m_gv=0.0,m_bv=0.0;
//---
   ColorToRGB(aColor,m_rv,m_gv,m_bv);
//---
   double m_md=0.3*::pow(255,2)+0.59*::pow(255,2)+0.11*::pow(255,2)+1;
   aIndex=0;
//---
   for(int i=0; i<138; i++)
     {
      double m_d=0.3*::pow(GetR(m_c[i])-m_rv,2)+0.59*::pow(GetG(m_c[i])-m_gv,2)+0.11*::pow(GetB(m_c[i])-m_bv,2);
      //---
      if(m_d<m_md)
        {
         m_md   =m_d;
         aIndex =i;
        }
     }
//---
   return(m_c[aIndex]);
  }
//+------------------------------------------------------------------+
//| Преобразование в серый цвет                                      |
//+------------------------------------------------------------------+
double CColors::RGBtoGray(double aR,double aG,double aB)
  {
   aR/=255;
   aG/=255;
   aB/=255;
//---
   aR=::pow(aR,2.2);
   aG=::pow(aG,2.2);
   aB=::pow(aB,2.2);
//---
   double rY=0.21*aR+0.72*aG+0.07*aB;
   rY=::pow(rY,1.0/2.2);
//---
   return(rY);
  }
//+------------------------------------------------------------------+
//| Простое преобразование в серый цвет                              |
//+------------------------------------------------------------------+
double CColors::RGBtoGraySimple(double aR,double aG,double aB)
  {
   aR/=255;
   aG/=255;
   aB/=255;
   double rY=0.3*aR+0.59*aG+0.11*aB;
//---
   return(rY);
  }
//+------------------------------------------------------------------+

Все изменения, которые были сделаны, — это задание каждому из методов модификатора static, и некоторые чисто оформительские изменения (кроме названия переменных в аргументах методов), более подходящие к моему стилю написания кода. Помимо этого, был добавлен метод RGBtoLab() для преобразования цветовой модели RGB в Lab. Метод просто преобразует модель RGB в модель XYZ, а уже из неё в цветовую модель Lab. Об этом в своё время говорил Анатолий Кажарский в своей статье "Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1)":

Для конвертации из формата RGB в формат Lab в классе CColors нет подходящего метода. Поэтому, там где нужна конвертация RGB->Lab, будет использоваться двойное преобразование через цветовую мастер-модель XYZ, то есть: RGB->XYZ->Lab.

Мы просто последовали его совету.

Для того, чтобы класс CColors был виден всей библиотеке и программам, созданным на её основе, подключим файл класса к файлу сервисных функций библиотеки в файле \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property strict  // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
#include "Pause.mqh"
#include "Colors.mqh"
//+------------------------------------------------------------------+
//| Сервисные функции                                                |
//+------------------------------------------------------------------+

В данной статье нам этот класс не понадобится, но мы его начнём использовать далее — при создании классов-наследников объекта-графического элемента.

Каждый графический объект имеет как минимум координаты своего расположения на графике и размеры. Помимо этого, наши объекты наделены множеством свойств, которые могут быть изменены во время работы программы. Но если мы перезапустим программу или поменяем таймфрейм, то все сделанные во время работы программы изменения графических объектов будут сброшены. Чтобы каждый объект помнил о состоянии своих свойств, нам эти свойства необходимо сохранять извне. Тогда после перезапуска программы все графические объекты, построенные и изменённые во время её работы, считают из соответствующего файла принадлежащие им актуальные на момент перезапуска свойства и восстановят их. Для этого нам необходимо добавить в класс объекта-графического элемента два метода — для записи свойств объекта в файл и для чтения свойств объекта из файла.

Для записи и чтения свойств объекта мы воспользуемся сохранением свойств объекта в структуру, а уже эту структуру мы можем как сохранять в файл, так и читать из файла стандартными функциями StructToCharArray() и CharArrayToStruct().

В каждом графическом объекте будут находиться методы для сохранения свойств в файл и чтения свойств из файла, так как каждый графический объект на основе канваса будет унаследован от объекта-графического элемента, в котором мы пропишем эти методы. Соответственно, если объект составной, т.е. имеет в своём составе другие объекты на основе графического элемента, то мы сможем поочерёдно восстановить состояния всех его подчинённых объектов — по очереди в соответствии с номером объекта в списке подчинённых объектов (номер хранится в константе CANV_ELEMENT_PROP_NUM перечисления ENUM_CANV_ELEMENT_PROP_INTEGER свойств объекта-элемента).

Сегодня мы не будем заниматься сохранением свойств в файл и их чтением из файла, так как это нужно делать из класса-коллекции графических объектов. Его мы будем рассматривать далее — после создания графического элемента. Но методы записи и чтения мы добавим уже сегодня.

Так как графический элемент является наследником базового объекта всех графических объектов библиотеки CGBaseObj, то сначала пропишем в файле класса этого объекта (\MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh) в защищённой секции виртуальный метод для создания структуры из свойств объекта и виртуальный метод для восстановления свойств объекта из структуры:

protected:
   string            m_name_prefix;                      // Префикс имени объекта
   string            m_name;                             // Имя объекта
   long              m_chart_id;                         // Идентификатор графика
   int               m_subwindow;                        // Номер подокна
   int               m_shift_y;                          // Смещение координаты Y для подокна
   int               m_type;                             // Тип объекта

//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

public:

Здесь эти методы ничего не делают — их необходимо переопределять в наследниках класса. Ближайшим наследником этого класса является класс объекта-графического элемента в файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh. Объявим в его защищённой секции такие же виртуальные методы:

//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // Объект класса CCanvas
   CPause            m_pause;                                  // Объект класса "Пауза"
//--- Возвращает положение курсора относительно (1) всего элемента, (2) активной зоны элемента
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);

private:

А в приватной секции объявим структуру для хранения всех свойств объекта, объект с типом этой структуры и массив структуры объекта:

private:
   struct SData
     {
      //--- Целочисленные свойства объекта
      int            id;                                       // Идентификатор элемента
      int            type;                                     // Тип графического элемента
      int            number;                                   // Номер элемента в списке
      long           chart_id;                                 // Идентификатор графика
      int            subwindow;                                // Номер подокна графика
      int            coord_x;                                  // X-координата формы на графике
      int            coord_y;                                  // Y-координата формы на графике
      int            width;                                    // Ширина элемента
      int            height;                                   // Высота элемента
      int            edge_right;                               // Правая граница элемента
      int            edge_bottom;                              // Нижняя граница элемента
      int            act_shift_left;                           // Отступ активной зоны от левого края элемента
      int            act_shift_top;                            // Отступ активной зоны от верхнего края элемента
      int            act_shift_right;                          // Отступ активной зоны от правого края элемента
      int            act_shift_bottom;                         // Отступ активной зоны от нижнего края элемента
      uchar          opacity;                                  // Непрозрачность элемента
      color          color_bg;                                 // Цвет фона элемента
      bool           movable;                                  // Флаг перемещаемости элемента
      bool           active;                                   // Флаг активности элемента
      int            coord_act_x;                              // X-координата активной зоны элемента
      int            coord_act_y;                              // Y-координата активной зоны элемента
      int            coord_act_right;                          // Правая граница активной зоны элемента
      int            coord_act_bottom;                         // Нижняя граница активной зоны элемента
      //--- Вещественные свойства объекта

      //--- Строковые свойства объекта
      uchar          name_obj[64];                             // Имя объекта-графического элемента
      uchar          name_res[64];                             // Имя графического ресурса
     };
   SData             m_struct_obj;                             // Структура объекта
   uchar             m_uchar_array[];                          // uchar-массив структуры объекта
   
   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Целочисленные свойства
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Вещественные свойства
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // Строковые свойства

В публичной секции класса объявим методы записи и чтения свойств объекта из файла:

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                   }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value; }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value; }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)        const { return this.m_long_prop[property];                  }
   double            GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];}
   string            GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];}

//--- Возвращает флаг поддержания объектом данного свойства
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)          { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)           { return false;}
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)           { return true; }

//--- Сравнивает объекты CGCnvElement между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CGCnvElement между собой по всем свойствам (для поиска равных объектов)
   bool              IsEqual(CGCnvElement* compared_obj) const;

//--- (1) Сохраняет объект в файл, (2), загружает объект из файла
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);

//--- Создаёт элемент

Так как у объекта нет вещественных свойств, то виртуальный метод, возвращающий флаг поддержания объектом вещественных свойств, у нас должен возвращать false, что мы и сделали.

За пределами тела класса напишем реализацию объявленных методов.

Метод, создающий структуру объекта из его свойств:

//+------------------------------------------------------------------+
//| Создаёт структуру объекта                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                            // Идентификатор элемента
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                        // Тип графического элемента
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                       // Номер элемента в списке
   this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                     // Идентификатор графика
   this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);                // Номер подокна графика
   this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);                  // X-координата формы на графике
   this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);                  // Y-координата формы на графике
   this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                      // Ширина элемента
   this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);                    // Высота элемента
   this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT);                 // Правая граница элемента
   this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM);               // Нижняя граница элемента
   this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    // Отступ активной зоны от левого края элемента
   this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);      // Отступ активной зоны от верхнего края элемента
   this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);  // Отступ активной зоны от правого края элемента
   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Отступ активной зоны от нижнего края элемента
   this.m_struct_obj.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY);                // Непрозрачность элемента
   this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG);              // Цвет фона элемента
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Флаг перемещаемости элемента
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Флаг активности элемента
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X-координата активной зоны элемента
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y-координата активной зоны элемента
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);        // Правая граница активной зоны элемента
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Нижняя граница активной зоны элемента
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Имя объекта-графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Имя графического ресурса
   //--- Сохранение структуры в uchar-массив
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Здесь всё просто: в каждое целочисленное поле структуры вписываем соответствующее свойство объекта, а строковые свойства объекта сохраняем в соответствующий uchar-массив структуры. Затем просто сохраняем созданную структуру свойств объекта в uchar-массив при помощи StructToCharArray().
Если структуру сохранить в массив не удалось, сообщаем об ошибке и возвращаем false. В итоге — возвращаем true.

Метод, восстанавливающий свойства объекта из структуры:

//+------------------------------------------------------------------+
//| Создаёт объект из структуры                                      |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                 // Идентификатор элемента
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                             // Тип графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                            // Номер элемента в списке
   this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id);                     // Идентификатор графика
   this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow);                     // Номер подокна графика
   this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x);                       // X-координата формы на графике
   this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y);                       // Y-координата формы на графике
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width);                           // Ширина элемента
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height);                         // Высота элемента
   this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right);                      // Правая граница элемента
   this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom);                    // Нижняя граница элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left);         // Отступ активной зоны от левого края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top);           // Отступ активной зоны от верхнего края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right);       // Отступ активной зоны от правого края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom);     // Отступ активной зоны от нижнего края элемента
   this.SetProperty(CANV_ELEMENT_PROP_OPACITY,this.m_struct_obj.opacity);                       // Непрозрачность элемента
   this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,this.m_struct_obj.color_bg);                     // Цвет фона элемента
   this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable);                       // Флаг перемещаемости элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Флаг активности элемента
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X-координата активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y-координата активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);             // Правая граница активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Нижняя граница активной зоны элемента
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Имя объекта-графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Имя графического ресурса
  }
//+------------------------------------------------------------------+

Здесь: в каждое целочисленное свойство объекта вписываем значение из соответствующего поля структуры, а в строковые свойства объекта считываем содержимое соответствующего uchar-массива структуры при помощи CharArrayToString().

Метод, сохраняющий объект в файл:

//+------------------------------------------------------------------+
//| Сохраняет объект в файл                                          |
//+------------------------------------------------------------------+
bool CGCnvElement::Save(const int file_handle)
  {
   if(!this.ObjectToStruct())
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT));
      return false;
     }
   if(::FileWriteArray(file_handle,this.m_uchar_array)==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE));
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

В метод передаётся хэндл файла, в который должны быть сохранены свойства объекта. Затем сохраняются свойства объекта в структуре при помощи вышерассмотренного метода ObjectToStruct(), в файл записывается созданный при создании структуры uchar-массив при помощи FileWriteArray() и возвращается true. При неудаче метод выводит в журнал сообщение об ошибке и возвращает false.

Метод, загружающий свойства объекта из файла:

//+------------------------------------------------------------------+
//| Загружает объект из файла                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::Load(const int file_handle)
  {
   if(::FileReadArray(file_handle,this.m_uchar_array)==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE));
      return false;
     }
   if(!::CharArrayToStruct(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY));
      return false;
     }
   this.StructToObject();
   return true;
  }
//+------------------------------------------------------------------+

В метод передаётся хэндл файла, в котором сохранены свойства объекта. Затем при помощи FileReadArray() в uchar-массив загружаются свойства объекта из файла и загруженные в массив свойства копируются в структуру при помощи CharArrayToStruct(). В итоге при помощи вышерассмотренного метода StructToObject() заполненную из файла структуру записываем в свойства объекта и возвращаем true. При ошибках чтения из файла или копирования полученного из файла массива в структуру, метод сообщает об ошибке и возвращает false.

В блок методов для упрощённого доступа к свойствам объекта добавим методы для возврата правого и нижнего края элемента, методы для установки и возврата цвета фона элемента и методы для возврата идентификатора элемента и его номера в списке элементов в составном объекте:

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правый, (6) нижний край элемента,
   bool              SetCoordX(const int coord_x);
   bool              SetCoordY(const int coord_y);
   bool              SetWidth(const int width);
   bool              SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }
//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента,
//--- (5) все смещения краёв активной зоны относительно элемента, (6) цвет фона элемента, (7) непрозрачность
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetColorBG(const color colour)            { this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour);                  }
   void              SetOpacity(const uchar value,const bool redraw=false);
   
//--- Возвращает смещение (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента
   int               ActiveAreaLeftShift(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);       }
   int               ActiveAreaRightShift(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);      }
   int               ActiveAreaTopShift(void)            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);        }
   int               ActiveAreaBottomShift(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);     }
//--- Возвращает координату (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }
//--- Возвращает (1) цвет фона, (2) непрозрачность, координату (3) правого, (4) нижнего края элемента
   color             ColorBG(void)                       const { return (color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG);           }
   uchar             Opacity(void)                       const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY);            }
   int               RightEdge(void)                     const { return this.CoordX()+this.m_canvas.Width();                           }
   int               BottomEdge(void)                    const { return this.CoordY()+this.m_canvas.Height();                          }
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента,
   int               CoordX(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);              }
   int               CoordY(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);              }
   int               Width(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                }
   int               Height(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);               }
//--- Возвращает флаг (1) перемещаемости, (2) активности элемента
   bool              Movable(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);             }
   bool              Active(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);              }
//--- Возвращает (1) имя объекта, (2) имя графического ресурса, (3) идентификатор графика, (4) номер подокна графика
   string            NameObj(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ);                  }
   string            NameRes(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_RES);                  }
   long              ChartID(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                  }
   int               WindowNum(void)                     const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);              }
//--- Возвращает (1) идентификатор элемента, (2) номер элемента в списке
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }

//+------------------------------------------------------------------+

Все эти методы просто возвращают соответствующее свойство объекта-элемента.


Методы работы с примитивами

Класс CCanvas предоставляет широкие возможности для рисования различных графических примитивов на холсте. Мы можем как прочитать цвет каждого пикселя, так и установить ему требуемый цвет и прозрачность. Помимо простой установки цвета пикселю класс предоставляет инструменты рисования различных фигур либо попиксельно — без сглаживания, либо с применением различных методов сглаживания.

В нашем классе объекта-графического элемента мы дадим пользователю доступ к методам рисования класса CCanvas. Наши методы будут лишь слегка упрощать обращение к методам класса CCanvas. Упрощение будет в том, что цвет мы будем задавать привычным образом — при помощи указания нужного цвета в формате color и указания степени непрозрачности цвета (0 — прозрачный, 255 — полностью непрозрачный), тогда как методы класса CCanvas "просят" указывать цвет сразу в формате uint ARGB, а это просто число. Не каждому удобно задавать нужный цвет в таком формате (полупрозрачный серый: 0x7F7F7F7F). В последующих классах, которые будут наследоваться от объекта-графического элемента, мы расширим диапазон возможностей рисования, добавив к методам рисования удобный функционал, присущий каждому создаваемому классу. В этом же классе, являющимся базовым для создания остальных графических объектов, методы рисования должны быть простыми и понятными.

Следом за блоком методов упрощённого доступа к свойствам объекта начнём вписывать новые блоки кода. Я их постарался распределить по назначению.
Методы получения данных начинаются с префикса "Get", а методы установки данных — с префикса "Set".

Метод, получающий цвет точки с указанными координатами:

//+------------------------------------------------------------------+
//| Методы получения растровых данных                                |
//+------------------------------------------------------------------+
//--- Получает цвет точки с указанными координатами
   uint              GetPixel(const int x,const int y)   const { return this.m_canvas.PixelGet(x,y);                                   }

//+------------------------------------------------------------------+

Здесь просто возвращается результат вызова метода PixelGet() класса CCanvas. Метод возвращает цвет в формате ARGB.

Методы заполнения, очистки и обновления растровых данных:

//+------------------------------------------------------------------+
//| Методы заполнения, очистки и обновления растровых данных         |
//+------------------------------------------------------------------+
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   void              Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Полностью очищает элемент
   void              Erase(const bool redraw=false);
//--- Обновляет элемент
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }
   
//+------------------------------------------------------------------+

Метод Update() просто обновляет объект и график при помощи метода Update() класса CCanvas.

Методы Erase() реализованы за пределами тела класса:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| Полностью очищает элемент                                        |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const bool redraw=false)
  {
   this.m_canvas.Erase(NULL_COLOR);
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+

Это два перегруженных метода.
В первом мы передаём требуемые цвет и непрозрачность, которым будет заполнен весь элемент при помощи метода Erase() класса CCanvas. Обратите внимание, что в нашем методе мы используем цвет и уровень непрозрачности, которые передаём в метод Erase() класса CCanvas при помощи преобразования их значений в формат ARGB функцией ColorToARGB(). Именно так мы и будем делать во всех наших методах рисования.
Во втором методе мы просто заполняем весь фон полностью прозрачным чёрным цветом, значение которого определено нами ранее макроподстановкой NULL_COLOR.
В каждый из методов передаётся флаг необходимости перерисовки графика и, если он установлен, то график перерисовывается.

Далее у нас расположен блок методов для рисования примитивов без сглаживания. Все методы идентичны и вызывают соответствующие методы класса CCanvas, в которые передаются указанные в аргументах методов параметры и цвет, преобразованный в формат ARGB из переданных в методы значений цвета и прозрачности:

//+------------------------------------------------------------------+
//| Методы рисования примитивов без сглаживания                      |
//+------------------------------------------------------------------+
//--- Устанавливает цвет точки с указанными координатами
   void              SetPixel(const int x,const int y,const color clr,const uchar opacity=255)
                       { this.m_canvas.PixelSet(x,y,::ColorToARGB(clr,opacity));                                                       }
                       
//--- Рисует отрезок вертикальной линии
   void              DrawLineVertical(const int x,                // Координата X отрезка
                                      const int y1,               // Координата Y первой точки отрезка
                                      const int y2,               // Координата Y второй точки отрезка
                                      const color clr,            // Цвет
                                      const uchar opacity=255)    // Непрозрачность
                       { this.m_canvas.LineVertical(x,y1,y2,::ColorToARGB(clr,opacity));                                               }
                       
//--- Рисует отрезок горизонтальной линии
   void              DrawLineHorizontal(const int x1,             // Координата X первой точки отрезка
                                        const int x2,             // Координата X второй точки отрезка
                                        const int y,              // Координата Y отрезка
                                        const color clr,          // Цвет
                                        const uchar opacity=255)  // Непрозрачность
                       { this.m_canvas.LineHorizontal(x1,x2,y,::ColorToARGB(clr,opacity));                                             }
                       
//--- Рисует отрезок произвольной линии
   void              DrawLine(const int x1,                       // Координата X первой точки отрезка
                              const int y1,                       // Координата Y первой точки отрезка
                              const int x2,                       // Координата X второй точки отрезка
                              const int y2,                       // Координата Y второй точки отрезка
                              const color clr,                    // Цвет
                              const uchar opacity=255)            // Непрозрачность
                       { this.m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                                   }
                       
//--- Рисует ломаную линию
   void              DrawPolyline(int &array_x[],                 // Массив координат X точек ломаной линии
                                  int & array_y[],                // Массив координат Y точек ломаной линии
                                  const color clr,                // Цвет
                                  const uchar opacity=255)        // Непрозрачность
                       { this.m_canvas.Polyline(array_x,array_y,::ColorToARGB(clr,opacity));                                           }
                       
//--- Рисует многоугольник
   void              DrawPolygon(int &array_x[],                  // Массив координат X точек многоугольника
                                 int &array_y[],                  // Массив координат Y точек многоугольника
                                 const color clr,                 // Цвет
                                 const uchar opacity=255)         // Непрозрачность
                       { this.m_canvas.Polygon(array_x,array_y,::ColorToARGB(clr,opacity));                                            }
                       
//--- Рисует прямоугольник по двум точкам
   void              DrawRectangle(const int x1,                  // Координата X первой точки, определяющей прямоугольник
                                   const int y1,                  // Координата Y первой точки, определяющей прямоугольник
                                   const int x2,                  // Координата X второй точки, определяющей прямоугольник
                                   const int y2,                  // Координата Y второй точки, определяющей прямоугольник
                                   const color clr,               // цвет
                                   const uchar opacity=255)       // Непрозрачность
                       { this.m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                              }
                       
//--- Рисует окружность
   void              DrawCircle(const int x,                      // Координата X центра окружности
                                const int y,                      // Координата Y центра окружности
                                const int r,                      // Радиус окружности
                                const color clr,                  // Цвет
                                const uchar opacity=255)          // Непрозрачность
                       { this.m_canvas.Circle(x,y,r,::ColorToARGB(clr,opacity));                                                       }
                       
//--- Рисует треугольник
   void              DrawTriangle(const int x1,                   // Координата X первой вершины треугольника
                                  const int y1,                   // Координата Y первой вершины треугольника
                                  const int x2,                   // Координата X второй вершины треугольника
                                  const int y2,                   // Координата Y второй вершины треугольника
                                  const int x3,                   // Координата X третьей вершины треугольника
                                  const int y3,                   // Координата Y третьей вершины треугольника
                                  const color clr,                // Цвет
                                  const uchar opacity=255)        // Непрозрачность
                       { m_canvas.Triangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity));                                              }
                       
//--- Рисует эллипс по двум точкам
   void              DrawEllipse(const int x1,                    // Координата X первой точки, определяющей эллипс
                                 const int y1,                    // Координата Y первой точки, определяющей эллипс
                                 const int x2,                    // Координата X второй точки, определяющей эллипс
                                 const int y2,                    // Координата Y второй точки, определяющей эллипс
                                 const color clr,                 // Цвет
                                 const uchar opacity=255)         // Непрозрачность
                       { this.m_canvas.Ellipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                                }
                       
//--- Рисует дугу эллипса, вписанного в прямоугольник с углами в (x1,y1) и (x2,y2).
//--- Границы дуги отсекаются линиями из центра эллипса, идущими к двум точкам с координатами (x3,y3) и (x4,y4)
   void              DrawArc(const int x1,                        // Координата X левого верхнего угла, определяющего прямоугольник
                             const int y1,                        // Координата Y левого верхнего угла, определяющего прямоугольник
                             const int x2,                        // Координата X правого нижнего угла, определяющего прямоугольник
                             const int y2,                        // Координата Y правого нижнего угла, определяющего прямоугольник
                             const int x3,                        // Координата X первой точки, к которой проведена линия из центра прямоугольника для получения границы дуги
                             const int y3,                        // Координата Y первой точки, к которой проведена линия из центра прямоугольника для получения границы дуги
                             const int x4,                        // Координата X второй точки, к которой проведена линия из центра прямоугольника для получения границы дуги
                             const int y4,                        // Координата Y второй точки, к которой проведена линия из центра прямоугольника для получения границы дуги
                             const color clr,                     // Цвет
                             const uchar opacity=255)             // Непрозрачность
                       { m_canvas.Arc(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity));                                             }
                       
//--- Рисует закрашенный сектор эллипса, вписанного в прямоугольник с углами в (x1,y1) и (x2,y2).
//--- Границы сектора отсекаются линиями из центра эллипса, идущими к двум точкам с координатами (x3,y3) и (x4,y4)
   void              DrawPie(const int x1,                        // координата X верхнего левого угла прямоугольника
                             const int y1,                        // координата Y верхнего левого угла прямоугольника
                             const int x2,                        // координата X нижнего правого угла прямоугольника
                             const int y2,                        // координата Y нижнего правого угла прямоугольника
                             const int x3,                        // координата X первой точки для нахождения границы дуги
                             const int y3,                        // координата Y первой точки для нахождения границы дуги
                             const int x4,                        // координата X второй точки для нахождения границы дуги
                             const int y4,                        // координата Y второй точки для нахождения границы дуги
                             const color clr,                     // Цвет линии
                             const color fill_clr,                // Цвет заливки
                             const uchar opacity=255)             // Непрозрачность
                       { this.m_canvas.Pie(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity),ColorToARGB(fill_clr,opacity));          }
                       
//+------------------------------------------------------------------+


Блок методов рисования закрашенных примитивов без сглаживания:

//+------------------------------------------------------------------+
//| Методы рисования закрашенных примитивов без сглаживания          |
//+------------------------------------------------------------------+
//--- Закрашивает область
   void              Fill(const int x,                            // Координата X точки начала закрашивания
                          const int y,                            // Координата Y точки начала закрашивания
                          const color clr,                        // Цвет
                          const uchar opacity=255,                // Непрозрачность
                          const uint threshould=0)                // Порог
                       { this.m_canvas.Fill(x,y,::ColorToARGB(clr,opacity),threshould);                                                }
                       
//--- Рисует закрашенный прямоугольник
   void              DrawRectangleFill(const int x1,              // Координата X первой точки, определяющей прямоугольник
                                       const int y1,              // Координата Y первой точки, определяющей прямоугольник
                                       const int x2,              // Координата X второй точки, определяющей прямоугольник
                                       const int y2,              // Координата Y второй точки, определяющей прямоугольник
                                       const color clr,           // Цвет
                                       const uchar opacity=255)   // Непрозрачность
                       { this.m_canvas.FillRectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                          }

//--- Рисует закрашенный круг
   void              DrawCircleFill(const int x,                  // Координата X центра круга
                                    const int y,                  // Координата Y центра круга
                                    const int r,                  // Радиус круга
                                    const color clr,              // Цвет
                                    const uchar opacity=255)      // Непрозрачность
                       { this.m_canvas.FillCircle(x,y,r,::ColorToARGB(clr,opacity));                                                   }
                       
//--- Рисует закрашенный треугольник
   void              DrawTriangleFill(const int         x1,      // Координата X первой вершины треугольника
                                      const int         y1,      // Координата Y первой вершины треугольника
                                      const int         x2,      // Координата X второй вершины треугольника
                                      const int         y2,      // Координата Y второй вершины треугольника
                                      const int         x3,      // Координата X третьей вершины треугольника
                                      const int         y3,      // Координата Y третьей вершины треугольника
                                      const color clr,           // Цвет
                                      const uchar opacity=255)   // Непрозрачность
                       { this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity));                                     }
                       
//--- Рисует закрашенный многоугольник
   void              DrawPolygonFill(int &array_x[],              // Массив, содержащий координаты X точек многоугольника
                                     int &array_y[],              // Массив, содержащий координаты Y точек многоугольника
                                     const color clr,             // Цвет
                                     const uchar opacity=255)     // Непрозрачность
                       { this.m_canvas.FillPolygon(array_x,array_y,::ColorToARGB(clr,opacity));                                        }
                       
//--- Рисует закрашенный эллипс, вписанный в прямоугольник с заданными координатами
   void              DrawEllipseFill(const int x1,                // Координата X левого верхнего угла, определяющего прямоугольник
                                     const int y1,                // Координата Y левого верхнего угла, определяющего прямоугольник
                                     const int x2,                // Координата X правого нижнего угла, определяющего прямоугольник
                                     const int y2,                // Координата Y правого нижнего угла, определяющего прямоугольник
                                     const color clr,             // Цвет
                                     const uchar opacity=255)     // Непрозрачность
                       { this.m_canvas.FillEllipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                            }
                       
//+------------------------------------------------------------------+


Методы рисования примитивов с использованием сглаживания:

//+------------------------------------------------------------------+
//| Методы рисования примитивов с использованием сглаживания         |
//+------------------------------------------------------------------+
//--- Рисует точку с использованием алгоритма сглаживания AntiAliasing
   void              SetPixelAA(const double x,                   // Координата X точки
                                const double y,                   // Координата Y точки
                                const color clr,                  // Цвет
                                const uchar opacity=255)          // Непрозрачность
                       { this.m_canvas.PixelSetAA(x,y,::ColorToARGB(clr,opacity));                                                     }
                       
//--- Рисует отрезок произвольной линии с использованием алгоритма сглаживания AntiAliasing
   void              DrawLineAA(const int   x1,                   // Координата X первой точки отрезка
                                const int   y1,                   // Координата Y первой точки отрезка
                                const int   x2,                   // Координата X второй точки отрезка
                                const int   y2,                   // Координата Y второй точки отрезка
                                const color clr,                  // Цвет
                                const uchar opacity=255,          // Непрозрачность
                                const uint  style=UINT_MAX)       // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.LineAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                           }
                       
//--- Рисует отрезок произвольной линии с использованием алгоритма сглаживания Wu
   void              DrawLineWu(const int   x1,                   // Координата X первой точки отрезка
                                const int   y1,                   // Координата Y первой точки отрезка
                                const int   x2,                   // Координата X второй точки отрезка
                                const int   y2,                   // Координата Y второй точки отрезка
                                const color clr,                  // Цвет
                                const uchar opacity=255,          // Непрозрачность
                                const uint  style=UINT_MAX)       // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.LineWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                           }
                       
//--- Рисует отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   void              DrawLineThick(const int   x1,                // Координата X первой точки отрезка
                                   const int   y1,                // Координата Y первой точки отрезка
                                   const int   x2,                // Координата X второй точки отрезка
                                   const int   y2,                // Координата Y второй точки отрезка
                                   const int   size,              // Толщина линии
                                   const color clr,               // Цвет
                                   const uchar opacity=255,       // Непрозрачность
                                   const uint  style=STYLE_SOLID, // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                   ENUM_LINE_END end_style=LINE_END_ROUND) // Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.LineThick(x1,y1,x2,y2,::ColorToARGB(clr,opacity),size,style,end_style);                         }
 
//--- Рисует вертикальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   void              DrawLineThickVertical(const int   x,         // Координата X отрезка
                                           const int   y1,        // Координата Y первой точки отрезка
                                           const int   y2,        // Координата Y второй точки отрезка
                                           const int   size,      // толщина линии
                                           const color clr,       // Цвет
                                           const uchar opacity=255,// Непрозрачность
                                           const uint  style=STYLE_SOLID,  // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                           const ENUM_LINE_END end_style=LINE_END_ROUND)  // Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.LineThickVertical(x,y1,y2,::ColorToARGB(clr,opacity),size,style,end_style);                     }
                       
//--- Рисует горизонтальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   void              DrawLineThickHorizontal(const int   x1,      // Координата X первой точки отрезка
                                             const int   x2,      // Координата X второй точки отрезка
                                             const int   y,       // Координата Y отрезка
                                             const int   size,    // толщина линии
                                             const color clr,     // Цвет
                                             const uchar opacity=255,// Непрозрачность
                                             const uint  style=STYLE_SOLID,  // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                             const ENUM_LINE_END end_style=LINE_END_ROUND)  // Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.LineThickHorizontal(x1,x2,y,::ColorToARGB(clr,opacity),size,style,end_style);                   }

//--- Рисует ломаную линию с использованием алгоритма сглаживания AntiAliasing
   void              DrawPolylineAA(int        &array_x[],        // Массив координат X точек ломаной линии
                                    int        &array_y[],        // Массив координат Y точек ломаной линии
                                    const color clr,              // Цвет
                                    const uchar opacity=255,      // Непрозрачность
                                    const uint  style=UINT_MAX)   // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.PolylineAA(array_x,array_y,::ColorToARGB(clr,opacity),style);                                   }
                       
//--- Рисует ломаную линию с использованием алгоритма сглаживания Wu
   void              DrawPolylineWu(int        &array_x[],        // Массив координат X точек ломаной линии
                                    int        &array_y[],        // Массив координат Y точек ломаной линии
                                    const color clr,              // Цвет
                                    const uchar opacity=255,      // Непрозрачность
                                    const uint  style=UINT_MAX)   // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.PolylineWu(array_x,array_y,::ColorToARGB(clr,opacity),style);                                   }
                       
//--- Рисует ломаную линию заданной толщины с использованием двух алгоритмов сглаживания последовательно.
//--- Сначала на основе кривых Безье сглаживаются отдельные отрезки линии.
//--- Затем для повышения качества отрисовки к построенной из этих отрезков ломаной линии применяется растровый алгоритм сглаживания
   void              DrawPolylineSmooth(const int   &array_x[],   // Массив координат X точек ломаной линии
                                        const int   &array_y[],   // Массив координат Y точек ломаной линии
                                        const int    size,        // Толщина линии
                                        const color  clr,         // Цвет
                                        const uchar  opacity=255, // Непрозрачность
                                        const double tension=0.5, // Значение параметра сглаживания
                                        const double step=10,     // Шаг аппроксимации
                                        const ENUM_LINE_STYLE style=STYLE_SOLID,// Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                        const ENUM_LINE_END   end_style=LINE_END_ROUND)// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.PolylineSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step);   }
                       
//--- Рисует ломаную линию заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   void              DrawPolylineThick(const int     &array_x[],  // Массив координат X точек ломаной линии
                                       const int     &array_y[],  // Массив координат Y точек ломаной линии
                                       const int      size,       // Толщина линии
                                       const color    clr,        // Цвет
                                       const uchar    opacity=255,// Непрозрачность
                                       const uint     style=STYLE_SOLID,         // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                       ENUM_LINE_END  end_style=LINE_END_ROUND)  // Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.PolylineThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style);                 }
                       
//--- Рисует многоугольник с использованием алгоритма сглаживания AntiAliasing
   void              DrawPolygonAA(int        &array_x[],         // Массив координат X точек многоугольника
                                   int        &array_y[],         // Массив координат Y точек многоугольника
                                   const color clr,               // Цвет
                                   const uchar opacity=255,       // Непрозрачность
                                   const uint  style=UINT_MAX)    // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.PolygonAA(array_x,array_y,::ColorToARGB(clr,opacity),style);                                    }
                       
//--- Рисует многоугольник с использованием алгоритма сглаживания Wu
   void              DrawPolygonWu(int        &array_x[],         // Массив координат X точек многоугольника
                                   int        &array_y[],         // Массив координат Y точек многоугольника
                                   const color clr,               // Цвет
                                   const uchar opacity=255,       // Непрозрачность
                                   const uint  style=UINT_MAX)    // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.PolygonWu(array_x,array_y,::ColorToARGB(clr,opacity),style);                                    }
                       
//--- Рисует  многоугольник заданной толщины с использованием двух алгоритмов сглаживания последовательно.
//--- Сначала на основе кривых Безье сглаживаются отдельные отрезки.
//--- Затем для повышения качества отрисовки к построенному из этих отрезков многоугольнику применяется растровый алгоритм сглаживания. 
   void              DrawPolygonSmooth(int         &array_x[],    // Массив координат X точек ломаной линии
                                       int         &array_y[],    // Массив координат Y точек ломаной линии
                                       const int    size,         // Толщина линии
                                       const color  clr,          // Цвет
                                       const uchar  opacity=255,  // Непрозрачность
                                       const double tension=0.5,  // Значение параметра сглаживания
                                       const double step=10,      // Шаг аппроксимации
                                       const ENUM_LINE_STYLE style=STYLE_SOLID,// Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                       const ENUM_LINE_END   end_style=LINE_END_ROUND)// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { this.m_canvas.PolygonSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step);    }
                       
//--- Рисует многоугольник заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   void              DrawPolygonThick(const int  &array_x[],      // массив координат X точек многоугольника
                                      const int  &array_y[],      // массив координат Y точек многоугольника
                                      const int   size,           // толщина линии
                                      const color clr,            // Цвет
                                      const uchar opacity=255,    // Непрозрачность
                                      const uint  style=STYLE_SOLID,// стиль линии
                                      ENUM_LINE_END end_style=LINE_END_ROUND) // стиль концов линии
                       { this.m_canvas.PolygonThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style);                  }
                       
//--- Рисует треугольник с использованием алгоритма сглаживания AntiAliasing
   void              DrawTriangleAA(const int   x1,               // Координата X первой вершины треугольника
                                    const int   y1,               // Координата Y первой вершины треугольника
                                    const int   x2,               // Координата X второй вершины треугольника
                                    const int   y2,               // Координата Y второй вершины треугольника
                                    const int   x3,               // Координата X третьей вершины треугольника
                                    const int   y3,               // Координата Y третьей вершины треугольника
                                    const color clr,              // Цвет
                                    const uchar opacity=255,      // Непрозрачность
                                    const uint  style=UINT_MAX)   // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.TriangleAA(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style);                                 }
                       
//--- Рисует треугольник с использованием алгоритма сглаживания Wu
   void              DrawTriangleWu(const int   x1,               // Координата X первой вершины треугольника
                                    const int   y1,               // Координата Y первой вершины треугольника
                                    const int   x2,               // Координата X второй вершины треугольника
                                    const int   y2,               // Координата Y второй вершины треугольника
                                    const int   x3,               // Координата X третьей вершины треугольника
                                    const int   y3,               // Координата Y третьей вершины треугольника
                                    const color clr,              // Цвет
                                    const uchar opacity=255,      // Непрозрачность
                                    const uint  style=UINT_MAX)   // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.TriangleWu(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style);                                 }
                       
//--- Рисует окружность с использованием алгоритма сглаживания AntiAliasing
   void              DrawCircleAA(const int    x,                 // Координата X центра окружности
                                  const int    y,                 // Координата Y центра окружности
                                  const double r,                 // Радиус окружности
                                  const color  clr,               // Цвет
                                  const uchar opacity=255,        // Непрозрачность
                                  const uint  style=UINT_MAX)     // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.CircleAA(x,y,r,::ColorToARGB(clr,opacity),style);                                               }
                       
//--- Рисует окружность с использованием алгоритма сглаживания Wu
   void              DrawCircleWu(const int    x,                 // Координата X центра окружности
                                  const int    y,                 // Координата Y центра окружности
                                  const double r,                 // Радиус окружности
                                  const color  clr,               // Цвет
                                  const uchar opacity=255,        // Непрозрачность
                                  const uint  style=UINT_MAX)     // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.CircleWu(x,y,r,::ColorToARGB(clr,opacity),style);                                               }
                       
//--- Рисует эллипс по двум точкам с использованием алгоритма сглаживания AntiAliasing
   void              DrawEllipseAA(const double x1,               // Координата X первой точки, определяющей эллипс
                                   const double y1,               // Координата Y первой точки, определяющей эллипс
                                   const double x2,               // Координата X второй точки, определяющей эллипс
                                   const double y2,               // Координата Y второй точки, определяющей эллипс
                                   const color  clr,              // Цвет
                                   const uchar opacity=255,       // Непрозрачность
                                   const uint  style=UINT_MAX)    // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.EllipseAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                        }
                       
//--- Рисует эллипс по двум точкам с использованием алгоритма сглаживания Wu
   void              DrawEllipseWu(const int   x1,                // Координата X первой точки, определяющей эллипс
                                   const int   y1,                // Координата Y первой точки, определяющей эллипс
                                   const int   x2,                // Координата X второй точки, определяющей эллипс
                                   const int   y2,                // Координата Y второй точки, определяющей эллипс
                                   const color clr,               // Цвет
                                   const uchar opacity=255,       // Непрозрачность
                                   const uint  style=UINT_MAX)    // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { this.m_canvas.EllipseWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                        }

//+------------------------------------------------------------------+

Логика всех добавленных методов абсолютно прозрачна, все параметры, передаваемые в методы подписаны, назначение каждого метода прокомментировано и, надеюсь, коды методов вопросов не вызовут. В любом случае всё можно обсудить в комментариях к статье.


Методы работы с текстом

Класс CCanvas так устроен, что помнит настройки последнего выведенного текста — параметры его шрифта, цвет, прозрачность и т.д. Чтобы узнать размер текста, мы можем использовать метод TextSize(), который использует для измерения ширины и высоты ограничивающего текст прямоугольника текущие настройки шрифта.  Зачем нам это нужно? Ну вот хотя бы для того, чтобы затереть нарисованный ранее текст на холсте цветом фона и нарисовать этот же текст в новых координатах — смещение текста. Но здесь важны не только координаты текста, но ещё и точка привязки текста (лево-верх, центр-верх, право-верх, и т.д.). Мы точно должны знать, какой угол привязки ограничивающего прямоугольника задан тексту, иначе координаты стирающего прямоугольника будут заданы неправильно. Для этого нам нужно добавить переменную-член класса, в которой будет храниться последняя заданная точка привязки.

В приватной секции класса объявим эту переменную:

   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Целочисленные свойства
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Вещественные свойства
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // Строковые свойства
   
   ENUM_TEXT_ANCHOR  m_text_anchor;                            // Текущее выравнивание текста

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство ордера
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL;                                 }
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_STRING property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL;  }

В параметрическом конструкторе класса, в самом его начале, инициализируем значения типа объекта и точки привязки текста:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   name,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false)
                                          
  {
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.m_text_anchor=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

В переменную родительского класса m_type, возвращаемую его виртуальным методом Type(), впишем тип объекта, переданный в параметрах конструктора, а переменную m_text_anchor инициализируем значением по умолчанию — левый верхний угол ограничивающего прямоугольника.

В самом конце тела класса, после блока кода для работы с примитивами, впишем блок кода для работы с текстом:

//+------------------------------------------------------------------+
//| Методы работы с текстом                                          |
//+------------------------------------------------------------------+
//--- Возвращает тип выравнивания (способ привязки) текста
   ENUM_TEXT_ANCHOR  TextAnchor(void)                       const { return this.m_text_anchor;                                         }

//--- Устанавливает текущий шрифт
   bool              SetFont(const string name,                   // Имя шрифта. Например "Arial"
                             const int    size,                   // Размер шрифта
                             const uint   flags=0,                // Флаги создания шрифта
                             const uint   angle=0,                // Угол наклона шрифта в десятых долях градуса
                             const bool   relative=true)          // Флаг относительного размера шрифта
                       { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle);                                  }

//--- Устанавливает имя шрифта
   bool              SetFontName(const string name)               // Имя шрифта. Например, "Arial"
                       { return this.m_canvas.FontNameSet(name);                                                                       }

//--- Устанавливает размер шрифта
   bool              SetFontSize(const int size,                  // Размер шрифта
                                 const bool relative=true)        // Флаг относительного размера шрифта
                       { return this.m_canvas.FontSizeSet(relative ? size*-10 : size);                                                 }

//--- Устанавливает флаги шрифта
//--- FONT_ITALIC - Курсив, FONT_UNDERLINE - Подчёркивание, FONT_STRIKEOUT - Перечёркивание
   bool              SetFontFlags(const uint flags)               // Флаги создания шрифта
                       { return this.m_canvas.FontFlagsSet(flags);                                                                     }

//--- Устанавливает угол наклона шрифта
   bool              SetFontAngle(const float angle)              // Угол наклона шрифта в градусах с десятыми долями
                       { return this.m_canvas.FontAngleSet(uint(angle*10));                                                            }

//--- Устанавливает угол привязки (тип выравнивания) шрифта
   void              SetTextAnchor(const uint flags=0)      { this.m_text_anchor=(ENUM_TEXT_ANCHOR)flags;                              }

//--- Получает параметры текущего шрифта и записывает их в переменные
   void              GetFont(string &name,                        // Ссылка на переменную для возврата имени шрифта
                             int    &size,                        // Ссылка на переменную для возврата размера шрифта
                             uint   &flags,                       // Ссылка на переменную для возврата флагов шрифта
                             uint   &angle)                       // Ссылка на переменную для возврата угла наклона шрифта
                       { this.m_canvas.FontGet(name,size,flags,angle);                                                                 }

//--- Возвращает (1) имя шрифта, (2) размер шрифта, (3)флаги шрифта, (4)угол наклона шрифта
   string            FontName(void)                         const { return this.m_canvas.FontNameGet();                                }
   int               FontSize(void)                         const { return this.m_canvas.FontSizeGet();                                }
   int               FontSizeRelative(void)                 const { return(this.FontSize()<0 ? -this.FontSize()/10 : this.FontSize()); }
   uint              FontFlags(void)                        const { return this.m_canvas.FontFlagsGet();                               }
   uint              FontAngle(void)                        const { return this.m_canvas.FontAngleGet();                               }

//--- Возвращает (1) ширину, (2) высоту, (3) все размеры текста (Для измерения текста используется текущий шрифт)
   int               TextWidth(const string text)                 { return this.m_canvas.TextWidth(text);                              }
   int               TextHeight(const string text)                { return this.m_canvas.TextHeight(text);                             }
   void              TextSize(const string text,                  // Текст для измерения
                              int         &width,                 // Ссылка на переменную для возврата ширины текста
                              int         &height)                // Ссылка на переменную для возврата высоты текста
                       { this.m_canvas.TextSize(text,width,height);                                                                    }

//--- Выводит текст текущим шрифтом
   void              Text(int         x,                          // Координата X точки привязки текста
                          int         y,                          // Координата Y точки привязки текста
                          string      text,                       // Текст для отображения
                          const color clr,                        // Цвет
                          const uchar opacity=255,                // Непрозрачность
                          uint        alignment=0)                // Способ привязки текста
                       { 
                        this.m_text_anchor=(ENUM_TEXT_ANCHOR)alignment;
                        this.m_canvas.TextOut(x,y,text,::ColorToARGB(clr,opacity),alignment);
                       }

  };
//+------------------------------------------------------------------+

Итак, здесь всё так же, как и в методах работы с примитивами, — все методы прокомментированы, их назначение, их входные и выходные переменные.
Хочется прокомментировать метод установки параметров текущего шрифта:

//--- Устанавливает текущий шрифт
   bool              SetFont(const string name,                   // Имя шрифта. Например "Arial"
                             const int    size,                   // Размер шрифта
                             const uint   flags=0,                // Флаги создания шрифта
                             const uint   angle=0,                // Угол наклона шрифта в десятых долях градуса
                             const bool   relative=true)          // Флаг относительного размера шрифта
                       { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle);  

Здесь параметр size, указывающий на размер шрифта, всегда должен задаваться в соответствии с размером шрифта, который бы мы задали при выводе текста обычным объектом-текстовой меткой OBJ_LABEL — в нём размеры задаются положительными целыми значениями. Тогда как при рисовании текста на канвасе, размеры шрифта указываются так же, как в функции TextSetFont():

Размер шрифта задается положительными или отрицательными значениями, знак определяет зависимость размера текста от настроек операционной системы (масштаба шрифта).

  • Если размер задается положительным числом, то при отображении логического шрифта в физический происходит преобразование размера в физические единицы измерения устройства (пиксели) и этот размер соответствует высоте ячеек символов из доступных шрифтов. Не рекомендуется в тех случаях, когда предполагается совместное использование на графике текстов, выведенных функцией TextOut(), и текстов, отображаемых с помощью графического объекта OBJ_LABEL ("Текстовая метка").
  • Если размер задается отрицательным числом, то указанный размер предполагается заданным в десятых долях логического пункта (значение -350 равно 35 логическим пунктам) и делится на 10, а затем полученное значение преобразуется в физические единицы измерения устройства (пиксели) и соответствует абсолютному значению высоты символа из доступных шрифтов. Чтобы получить на экране текст такого же размера, как и в объекте OBJ_LABEL, возьмите указанный в свойствах объекта размер шрифта и умножьте на -10.

В методе проверяется флаг относительного размера шрифта, и если он установлен (по умолчанию), то указанный в параметре size размер умножается на -10 для того, чтобы шрифт был указан верным значением для передачи в метод FontSet() класса CCanvas.

В параметрическом конструкторе класса добавим инициализацию шрифта (установим его название и размер по умолчанию):

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   name,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false)
                                          
  {
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

На сегодня это все необходимые доработки. Протестируем, что у нас получилось.


Тестирование

Что и как будем тестировать?
У нас есть советник из прошлой статьи, выводящий на график два объекта-графических элемента. Возьмём этот же советник, сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part75\ под новым именем TestDoEasyPart75.mq5 и сделаем так:

При щелчке по первому (верхнему) объекту будем поочерёдно рисовать на нём прямоугольник и окружность. С каждым новым щелчком по объекту размеры прямоугольника будут уменьшаться на 2 пикселя с каждой стороны и радиус окружности тоже будет уменьшаться на 2 пикселя. Прямоугольник рисуем обычным способом, а окружность нарисуем при помощи сглаживания. Также, с каждым щелчком непрозрачность объекта будет увеличиваться по кругу от 0 до 255.

При щелчке по второму (нижнему) объекту выведем на него текст, поочерёдно меняя его точку привязки, а в самом тексте будем писать название точки привязки. Прозрачность объекта менять не будем.

Укажем количество создаваемых элементов:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart75.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\GCnvElement.mqh>
//--- defines
#define        ELEMENTS_TOTAL (2)   // Количество создаваемых графических элементов
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
CArrayObj      list_elements;
//+------------------------------------------------------------------+

Чтобы не создавалось каждый раз лишних одинаковых объектов при смене таймфрейма, в обработчике OnInit() перед созданием новых объектов очистим список уже созданных:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка разрешений на отсылку событий перемещения курсора и прокрутки колёсика мышки
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Установка глобальных переменных советника

//--- Создадим заданное количество графических элементов на канвасе
   list_elements.Clear();
   int total=ELEMENTS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- При создании объекта передаём в него все требуемые параметры
      CGCnvElement *element=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,i,0,ChartID(),0,"Element_0"+(string)(i+1),300,40+(i*80),100,70,clrSilver,200,InpMovable,true,true);
      if(element==NULL)
         continue;
      //--- Добавим объекты в список
      if(!list_elements.Add(element))
        {
         delete element;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Так как у нас пока нет класса-коллекции графических объектов, в котором будет проверяться необходимость создания нового объекта с указанным именем, то здесь мы эти объекты будем просто пересоздавать заново, предварительно очищая список уже ранее созданных объектов.

В обработчик OnChartEvent() впишем обработку щелчков мышки по двум созданным объектам:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Если щелчок по объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Получим в новый список объект-элемент с именем, соответствующим значению строкового параметра sparam обработчика OnChartEvent()
      CArrayObj *obj_list=CSelect::ByGraphCanvElementProperty(GetPointer(list_elements),CANV_ELEMENT_PROP_NAME_OBJ,sparam,EQUAL);
      //--- Если объект получен из списка
      if(obj_list!=NULL && obj_list.Total()>0)
        {
         static uchar try0=0, try1=0;
         //--- Получим указатель на объект в списке
         CGCnvElement *obj=obj_list.At(0);
         //--- Если это первый графический элемент
         if(obj.ID()==0)
           {
            //--- Зададим для объекта новый уровень непрозрачности
            uchar opasity=obj.Opacity();
            if((opasity+5)>255)
               opasity=0;
            else 
               opasity+=5;
            //--- Установим новую непрозрачность объекту
            obj.SetOpacity(opasity);
            //--- Установим координаты прямоугольника и окружности
            int x1=2,x2=obj.Width()-3;
            int y1=2,y2=obj.Height()-3;
            int xC=(x1+x2)/2;
            int yC=(y1+y2)/2;
            int R=yC-y1;
            //--- Каждый первый щелчок выводим прямоугольник
            if(try0%2==0)
               obj.DrawRectangle(x1+try0,y1+try0,x2-try0,y2-try0,clrDodgerBlue,obj.Opacity());
            //--- Каждый второй щелчок выводим сглаженную методом AntiAliasing окружность
            else
               obj.DrawCircleAA(xC,yC,R-try0,clrGreen,obj.Opacity());
            //--- Если щелчков по объекту более 30
            if(try0>30)
              {
               //--- Очистим объект с установкой его текущего цвета и текущей прозрачности
               obj.Erase(obj.ColorBG(),obj.Opacity());
               //--- Начнём отсчёт щелчков заново
               try0=0;
              }
            //--- Обновим объект и чарт и выведем нанего комментарий со значениями цвета и непрозрачности объекта
            obj.Update(true); // Здесь можно без true, так как следующая команда Comment и так перерисовывает график
            Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG());
            //--- Увеличим счётчик щелчков мышки по объекту
            try0++;
           }
         //--- Если это второй объект
         else if(obj.ID()==1)
           {
            //--- Установим для него параметры фонта ("Calibri" размером 8)
            obj.SetFont("Calibri",8);
            //--- Установим угол привязки текста, соответствующий счётчику щелчка по объекту
            obj.SetTextAnchor((ENUM_TEXT_ANCHOR)try1);
            //--- Создадим текст из наименования угла привязки
            string text=StringSubstr(EnumToString(obj.TextAnchor()),12);
            //--- Установим координаты текста относительно верхнего левого угла графического элемента
            int xT=2,yT=2;
            //--- В зависимости от угла привязки зададим новые координаты выводимого текста
            //--- LEFT_TOP
            if(try1==0)       { xT=2; yT=2;                                   }
            //--- CENTER_TOP
            else if(try1==1)  { xT=obj.Width()/2; yT=2;                       }
            //--- RIGHT_TOP
            //--- так как нет значения 3 в перечислении ENUM_TEXT_ANCHOR, то увеличим на 1 счётчик щелчков по объекту
            else if(try1==2)  { xT=obj.Width()-2; yT=2; try1++;               }
            //--- LEFT_CENTER
            else if(try1==4)  { xT=2; yT=obj.Height()/2;                      }
            //--- CENTER
            else if(try1==5)  { xT=obj.Width()/2; yT=obj.Height()/2;          }
            //--- RIGHT_CENTER
            //--- так как нет значения 7 в перечислении ENUM_TEXT_ANCHOR, то увеличим на 1 счётчик щелчков по объекту
            else if(try1==6)  { xT=obj.Width()-2; yT=obj.Height()/2; try1++;  }
            //--- LEFT_BOTTOM
            else if(try1==8)  { xT=2; yT=obj.Height()-2;                      }
            //--- CENTER_BOTTOM
            else if(try1==9)  { xT=obj.Width()/2; yT=obj.Height()-2;          }
            //--- RIGHT_BOTTOM
            else if(try1==10)    { xT=obj.Width()-2; yT=obj.Height()-2;       }
            //--- Очистим графический элемент с заливкой его текущим цветом и текущей прозрачностью
            obj.Erase(obj.ColorBG(),obj.Opacity());
            //--- Выведем текст с рассчитанными координатами в очищенный элемент
            obj.Text(xT,yT,text,clrDodgerBlue,255,obj.TextAnchor());
            //--- Обновим объект и график
            obj.Update(true); // Здесь можно без true, так как следующая команда Comment и так перерисовывает график
            Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG());
            //--- Увеличим счётчик щелчков по объекту
            try1++;
            if(try1>10)
               try1=0;
           }
        }
     }
  }
//+------------------------------------------------------------------+

Код обработчика полностью весь прокомментирован. Надеюсь, его логика понятна. Но любые вопросы по сегодняшнему занятию всегда можно задать в обсуждении к статье.

Скомпилируем советник и запустим его на графике. Пощёлкаем мышкой по объектам:


Хм... В итоге мы случайно получили на верхнем объекте забавную картинку, напоминающую CD-диск :)

Что дальше

В следующей статье начнём разработку объектов-наследников созданного сегодня объекта-графического элемента.

Ниже прикреплены все файлы текущей версии библиотеки и файл тестового советника для MQL5. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas

Прикрепленные файлы |
MQL5.zip (4018.58 KB)
Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы
В статье опишем концепцию построения различных тем оформления GUI в библиотеке, создадим объект "Форма", являющийся потомком объекта класса графического элемента, подготовим данные для создания теней графических объектов библиотеки и дальнейшего развития функционала.
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas
Переработаем концепцию построения графических объектов из прошлой статьи и подготовим базовый класс-основу всех графических объектов библиотеки, создаваемых на базе класса CCanvas Стандартной библиотеки.
Комбинаторика и теория вероятностей для трейдинга (Часть II): Универсальный фрактал Комбинаторика и теория вероятностей для трейдинга (Часть II): Универсальный фрактал
В данной статье я продолжаю изучать фракталы и очень большое внимание будет уделено обобщению всего материала. А именно, я постараюсь свести все наработок в нечто более компактное и понятное для практического применения в трейдинге.
Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы
В данной серии статей будем искать практическое применение теории вероятностей для описания процесса торговли и ценообразования. В первой статье мы познакомимся с основами комбинаторики и теории вероятностей, и разберем первый пример применения фракталов в рамках теории вероятности.