Graphics in DoEasy library (Part 75): Methods of handling primitives and text in the basic graphical element

Artyom Trishkin | 6 July, 2021

Contents


Concept

I continue the development of the basic graphical element object class used as a basis for creating more complex library graphical objects. In the previous article, I produced the concept of constructing the base graphical object, created the graphical element and endowed it with basic properties that can already be set, changed and received.
Since the CCanvas class is meant for drawing "on the canvas", it features the methods for working with graphical primitives and a text. In the current article, I will create the element object methods allowing us to access and handle the CCanvas class methods for drawing. These methods are to be simple. They will be used to create advanced drawing methods in the descendant objects of the element object class.

In addition to creating the methods for working with primitives, I will create the methods for working with files. Our graphical objects, which are the GUI elements of custom programs, should "remember" their properties, status and location on the chart, for example when switching to another timeframe. To achieve this, I will save object properties to a file. When constructing an object, the properties will be read from it.
However, since files should be handled in the graphical object collection class and I have not developed it yet, I will simply add the methods for saving and uploading graphical object properties. While creating the graphical object collection class, we will use the methods for saving and uploading the properties I am going to develop here for the graphical element object.

Also, I will need the class for working with color. I am going to add it to the library as well.
The class will be taken from the MQL5.com code library developed by Dmitry Fedoseev and submitted to the community.

The final result will be a graphical element ready for further use as a basis for library graphical objects.


Improving library classes

If we need to clear a CCanvas class object that has transparency, we should use the Erase() method that receives zero by default:

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

In our case, this is an incorrect solution. While clearing the canvas using zero, we lose sight of its alpha channel (color transparency channel), which eventually leads to artifacts when drawing on the canvas with the alpha channel cleared in this way.
To clear the canvas with the alpha channel, use 0x00FFFFFF instead of zero.
This is a completely transparent black color in the ARGB format (Alpha = 0, Red = 255, Green = 255, Blue = 255).

In \MQL5\Include\DoEasy\Defines.mqh, add the macro substitution for specifying such a color:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel
//+------------------------------------------------------------------+

When displaying a text on the canvas using the TextOut() method, we can set the anchor angle (text alignment point) for the text message — the bounding rectangle, relative to which the message is to be located. The anchor points are set using six flags — combinations of two flags, all of which are listed below:

Horizontal text alignment flags:

Vertical text alignment flags:

The possible combinations of flags and anchoring methods set by them are shown below:


To avoid a confusion about which flag comes first, simply set a custom enumeration specifying all possible flag combinations for aligning a text relative to its anchor point:

//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of anchoring methods                                        |
//| (horizontal and vertical text alignment)                         |
//+------------------------------------------------------------------+
enum ENUM_TEXT_ANCHOR
  {
   TEXT_ANCHOR_LEFT_TOP       =  0,                   // Text anchor point at the upper left corner of the bounding rectangle
   TEXT_ANCHOR_CENTER_TOP     =  1,                   // Text anchor point at the top center side of the bounding rectangle
   TEXT_ANCHOR_RIGHT_TOP      =  2,                   // Text anchor point at the upper right corner of the bounding rectangle
   TEXT_ANCHOR_LEFT_CENTER    =  4,                   // Text anchor point at the left center side of the bounding rectangle
   TEXT_ANCHOR_CENTER         =  5,                   // Text anchor point at the center of the bounding rectangle
   TEXT_ANCHOR_RIGHT_CENTER   =  6,                   // Text anchor point at the right center side of the bounding rectangle
   TEXT_ANCHOR_LEFT_BOTTOM    =  8,                   // Text anchor point at the bottom left corner of the bounding rectangle
   TEXT_ANCHOR_CENTER_BOTTOM  =  9,                   // Text anchor point at the bottom center side of the bounding rectangle
   TEXT_ANCHOR_RIGHT_BOTTOM   =  10,                  // Text anchor point at the bottom right corner of the bounding rectangle
  };
//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+

Here we have set three flags for each text anchoring level:

  1. Top vertical anchor point (TA_TOP) — 0:
    • left horizontal anchor point (TA_LEFT) — 0,
    • center horizontal anchor point (TA_CENTER) — 1,
    • right horizontal anchor point (TA_RIGHT) — 2.
  2. Center vertical anchor point (TA_VCENTER) — 4:
    • left horizontal anchor point (TA_LEFT) — 0,
    • center horizontal anchor point (TA_CENTER) — 1,
    • right horizontal anchor point (TA_RIGHT) — 2.
  3. Bottom vertical anchor point (TA_BOTTOM) — 8:
    • left horizontal anchor point (TA_LEFT) — 0,
    • center horizontal anchor point (TA_CENTER) — 1,
    • right horizontal anchor point (TA_RIGHT) — 2.

Each of the ENUM_TEXT_ANCHOR enumeration values corresponds to the combination of correctly set flags described above:

I will use this enumeration further on to specify the text alignment relative to its anchor point.

Since I am currently developing the graphical part of the library, I will need various methods for working with color.
The library of MQL5.com source codes features the remarkable library of functions for working with colors kindly provided by Dmitry Fedoseev for general use.

Let's slightly improve the CColors class — make it static in order not to set the class object, but rather to directly access its methods using the context resolution operator (::), for example:

class_name::variable

Thus, the CColors class included into the library allows us to access the class methods anywhere in the code (including a custom program), for example to mix two colors — blue with the opacity of 128 and red with the opacity of 64:

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

Save the class file in the library directory \MQL5\Include\DoEasy\Services\ in Colors.mqh.

//+------------------------------------------------------------------+
//|                                                       Colors.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/integer"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Class for working with color                                     |
//+------------------------------------------------------------------+
class CColors
  {
private:
   static double     Arctan2(const double x,const double y);
   static double     Hue_To_RGB(double v1,double v2,double vH);
public:
//+--------------------------------------------------------------------+
//| The list of functions from 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);
//+------------------------------------------------------------------+
//| Other functions for working with color                           |
//+------------------------------------------------------------------+
   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);
  };
//+------------------------------------------------------------------+
//| Class methods                                                    |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of XYZ into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of XYZ into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of Yxy into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of XYZ into 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));
  }
//+------------------------------------------------------------------+
//| Conversion of HunterLab into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of XYZ into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of CIELab into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of CIELab into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of CIELCH into CIELab                                 |
//+------------------------------------------------------------------+
void CColors::CIELCHtoCIELab(const double aCIEL,const double aCIEC,const double aCIEH,double &oCIEL,double &oCIEa,double &oCIEb)
  {
//--- Arguments from 0 to 360°
   oCIEL =aCIEL;
   oCIEa =::cos(M_PI*aCIEH/180.0)*aCIEC;
   oCIEb =::sin(M_PI*aCIEH/180)*aCIEC;
  }
//+------------------------------------------------------------------+
//| Conversion of XYZ into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of CIELuv into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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;
     }
  }
//+------------------------------------------------------------------+
//| Conversion of HSL into 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));
     }
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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;
     }
  }
//+------------------------------------------------------------------+
//| Conversion of HSV into 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;
     }
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of CMY into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of CMY into 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;
  }
//+------------------------------------------------------------------+
//| Conversion of CMYK into 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);
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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);
  }
//+------------------------------------------------------------------+
//| Getting values of the RGB components                             |
//+------------------------------------------------------------------+
void CColors::ColorToRGB(const color aColor,double &aR,double &aG,double &aB)
  {
   aR =GetR(aColor);
   aG =GetG(aColor);
   aB =GetB(aColor);
  }
//+------------------------------------------------------------------+
//| Getting the R component value                                    |
//+------------------------------------------------------------------+
double CColors::GetR(const color aColor)
  {
   return(aColor&0xff);
  }
//+------------------------------------------------------------------+
//| Getting the G component value                                    |
//+------------------------------------------------------------------+
double CColors::GetG(const color aColor)
  {
   return((aColor>>8)&0xff);
  }
//+------------------------------------------------------------------+
//| Getting the B component value                                    |
//+------------------------------------------------------------------+
double CColors::GetB(const color aColor)
  {
   return((aColor>>16)&0xff);
  }
//+------------------------------------------------------------------+
//| Getting the A component value                                    |
//+------------------------------------------------------------------+
double CColors::GetA(const color aColor)
  {
   return(double(uchar((aColor)>>24)));
  }
//+------------------------------------------------------------------+
//| Conversion of RGB into 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);
  }
//+------------------------------------------------------------------+
//| Getting the value of the intermediary color between two colors   |
//+------------------------------------------------------------------+
color CColors::MixColors(const color aCol1,const color aCol2,const double aK)
  {
//--- aK - from 0 to 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));
  }
//+------------------------------------------------------------------+
//| Blending two colors considering the transparency of color on top |
//+------------------------------------------------------------------+
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;
//--- Convert the colors in ARGB format
   uint pixel_color=::ColorToARGB(upper_color);
//--- Get the components of the lower and upper colors
   ColorToRGB(lower_color,r1,g1,b1);
   ColorToRGB(pixel_color,r2,g2,b2);
//--- Get the transparency percentage from 0.00 to 1.00
   alpha=GetA(upper_color)/255.0;
//--- If there is transparency
   if(alpha<1.0)
     {
      //--- Blend the components taking the alpha channel into account
      r3=(r1*(1-alpha))+(r2*alpha);
      g3=(g1*(1-alpha))+(g2*alpha);
      b3=(b1*(1-alpha))+(b2*alpha);
      //--- Adjustment of the obtained values
      r3=(r3>255)? 255 : r3;
      g3=(g3>255)? 255 : g3;
      b3=(b3>255)? 255 : b3;
     }
   else
     {
      r3=r2;
      g3=g2;
      b3=b2;
     }
//--- Combine the obtained components and return the color
   return(RGBToColor(r3,g3,b3));
  }
//+------------------------------------------------------------------+
//| Getting an array of the specified size with a color gradient     |
//+------------------------------------------------------------------+
void CColors::Gradient(color &aColors[],   // List of colors
                       color &aOut[],      // Return array
                       int   aOutCount,    // Set the size of the return array
                       bool  aCycle=false) // Closed-loop cycle. Return array ends with the same color as it starts with
  {
   ::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;
     }
  }
//+------------------------------------------------------------------+
//| One more variant of conversion of RGB into XYZ and               |
//| corresponding conversion of XYZ into 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;
  }
//+------------------------------------------------------------------+
//| Negative color                                                   |
//+------------------------------------------------------------------+
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));
  }
//+------------------------------------------------------------------+
//| Search for the most similar color                                |
//| in the set of standard colors of the terminal                    |
//+------------------------------------------------------------------+
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]);
  }
//+------------------------------------------------------------------+
//| Conversion into gray color                                       |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Simple conversion into gray color                                |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

All made changes boil down to setting the 'static' modifier to each of the methods, as well as some purely cosmetic changes (apart from variable names in the method arguments) that suit my coding style. Besides, I have added the RGBtoLab() method for converting the RGB color model into Lab. The method simply converts the RGB model into XYZ, which in turn is converted into the Lab color model. This matter was once considered by Anatoli Kazharski in his article "Graphical Interfaces IX: The Color Picker Control (Chapter 1)":

There is no suitable method in the CColors class for converting from the RGB format to Lab. Therefore, where the conversion RGB->Lab is required, double correction through color master model XYZ will need to be applied: RGB->XYZ->Lab.

I have simply followed the author's advice.

To make the CColors class visible to the entire library and programs based on it, include the class file to the file of the library service functions in \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property strict  // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
#include "Pause.mqh"
#include "Colors.mqh"
//+------------------------------------------------------------------+
//| Service functions                                                |
//+------------------------------------------------------------------+

I will not need the class here but I will start using it later when creating the graphical element object descendant classes.

Each graphical object features at least its size and the coordinates of its location on the chart. In addition, our objects are endowed with many properties that can be changed while the program is running. But if we relaunch the program or switch the timeframe, all changes made to graphical objects while the program is running are reset. To let each object remember the state of its properties, we need to save them outside. In this case, after restarting the program, all graphical objects constructed and changed during its operation read their appropriate properties (relevant as of the reset moment) from the file and restore them. To achieve this, we need to add two methods to the graphical element object class — the method for writing the object properties to the file and the one for reading object properties from the file.

In order to read and write the object properties, I will save the object properties to the structure, while the structure can be both saved to the file and read from it using the StructToCharArray() and CharArrayToStruct() standard functions.

Each graphical object is to feature the methods for saving properties to the file and reading them from the file since each graphical object based on the canvas is to be a descendant from the graphical element object, in which the methods are to be set. Thus, if the object is composite (i.e. it consists of other objects based on the graphical element), we can restore the states of all of its subordinate objects one by one according to the object index in the list of subordinate objects (the index is stored in the CANV_ELEMENT_PROP_NUM constant of the ENUM_CANV_ELEMENT_PROP_INTEGER enumeration of the element object properties).

In this article, I will not save the properties to the file/read them from the file since this should be done from the graphical object collection class. I will consider it later — after creating the graphical element. Anyway, the writing and reading methods will be added here.

Since the graphical element is a descendant of the base object of all CGBaseObj library graphical objects, first let's set the virtual method for creating the structure out of the object properties and the virtual method for restoring the object properties from the structure in the protected section of the object class file (\MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh):

protected:
   string            m_name_prefix;                      // Object name prefix
   string            m_name;                             // Object name
   long              m_chart_id;                         // Chart ID
   int               m_subwindow;                        // Subwindow index
   int               m_shift_y;                          // Subwindow Y coordinate shift
   int               m_type;                             // Object type

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

public:

These methods do nothing here — they should be redefined in the class descendants. The closest descendant of the class is the class of the graphical element object in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh. Declare the same virtual methods in its protected section:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
//--- Return the cursor position relative to the (1) entire element and (2) the element active area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);

private:

In the private section, declare the structure for storing all object properties, the object with the structure type and the object structure array:

private:
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type
      int            number;                                   // Element index in the list
      long           chart_id;                                 // Chart ID
      int            subwindow;                                // Chart subwindow index
      int            coord_x;                                  // Form's X coordinate on the chart
      int            coord_y;                                  // Form's Y coordinate on the chart
      int            width;                                    // Element width
      int            height;                                   // Element height
      int            edge_right;                               // Element right border
      int            edge_bottom;                              // Element bottom border
      int            act_shift_left;                           // Active area offset from the left edge of the element
      int            act_shift_top;                            // Active area offset from the top edge of the element
      int            act_shift_right;                          // Active area offset from the right edge of the element
      int            act_shift_bottom;                         // Active area offset from the bottom edge of the element
      uchar          opacity;                                  // Element opacity
      color          color_bg;                                 // Element background color
      bool           movable;                                  // Element moveability flag
      bool           active;                                   // Element activity flag
      int            coord_act_x;                              // X coordinate of the element active area
      int            coord_act_y;                              // Y coordinate of the element active area
      int            coord_act_right;                          // Right border of the element active area
      int            coord_act_bottom;                         // Bottom border of the element active area
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
     };
   SData             m_struct_obj;                             // Object structure
   uchar             m_uchar_array[];                          // uchar array of the object structure
   
   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Integer properties
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Real properties
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // String properties

In the public section of the class, declare the methods of writing and reading object properties from the file:

public:
//--- Set object (1) integer, (2) real and (3) string properties
   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; }
//--- Return object (1) integer, (2) real and (3) string property from the properties array
   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)];}

//--- Return the flag of the object supporting this 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; }

//--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CGCnvElement objects with each other by all properties (to search equal objects)
   bool              IsEqual(CGCnvElement* compared_obj) const;

//--- (1) Save the object to file and (2) upload the object from the file
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);

//--- Create the element

Since the object has no real properties, the virtual method returning the flag of supporting real properties by the object should return false.

Implement declared methods outside the class body.

The method creating the object structure from its properties:

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                            // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                        // Graphical element type
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                       // Eleemnt ID in the list
   this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                     // Chart ID
   this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);                // Chart subwindow index
   this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);                  // Form's X coordinate on the chart
   this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);                  // Form's Y coordinate on the chart
   this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                      // Element width
   this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);                    // Element height
   this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT);                 // Element right edge
   this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM);               // Element bottom edge
   this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    // Active area offset from the left edge of the element
   this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);      // Active area offset from the top edge of the element
   this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);  // Active area offset from the right edge of the element
   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element
   this.m_struct_obj.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY);                // Element opacity
   this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG);              // Element background color
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Element moveability flag
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Element activity flag
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X coordinate of the element active area
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y coordinate of the element active area
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);        // Right border of the element active area
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Bottom border of the element active area
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name
   //--- Save the structure to the uchar array
   ::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;
  }
//+------------------------------------------------------------------+

Here all is simple: each integer structure field receives the appropriate object property, while object string properties are saved in the appropriate uchar array of the structure. Next, simply save the newly created object property structure to the uchar array using StructToCharArray().
If failed to save the structure to the array, inform of the error and return false. As a result, return true.

The method restoring the object properties from the structure:

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                 // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                             // Graphical element type
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                            // Element index in the list
   this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id);                     // Chart ID
   this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow);                     // Chart subwindow index
   this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x);                       // Form's X coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y);                       // Form's Y coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width);                           // Element width
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height);                         // Element height
   this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right);                      // Element right edge
   this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom);                    // Element bottom edge
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left);         // Active area offset from the left edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top);           // Active area offset from the upper edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right);       // Active area offset from the right edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom);     // Active area offset from the bottom edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_OPACITY,this.m_struct_obj.opacity);                       // Element opacity
   this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,this.m_struct_obj.color_bg);                     // Element background color
   this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable);                       // Element moveability flag
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Element activity flag
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);             // Right border of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Bottom border of the element active area
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name
  }
//+------------------------------------------------------------------+

Here, each integer object property receives the value from the appropriate structure field, while the contents of the appropriate uchar array structure is read to the object string properties using CharArrayToString().

The method saving the object to the file:

//+------------------------------------------------------------------+
//| Save the object to the file                                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

The method receives the handle of the file the object properties should be saved to. The object properties are then saved in the structure using the ObjectToStruct() method considered above. The uchar array created when constructing the structure is written to the file using FileWriteArray() and true is returned. In case of a failure, the method displays the error message in the journal and returns false.

The method uploading the object properties from the file:

//+------------------------------------------------------------------+
//| Upload the object from the file                                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

The method receives the file handle featuring saved object properties. Next, the object properties from the file are uploaded to the uchar array using FileReadArray(). The uploaded properties are copied to the structure using CharArrayToStruct(). The structure filled in from the file is set in the object properties using the above mentioned StructToObject() method and true is returned. If reading from the file or copying the obtained array to the structure ends with an error, the method informs of that and returns false.

The block of methods for a simplified access to object properties receives the methods for returning the right and bottom element edge, the methods for setting and returning the element background color, as well as the methods for returning the element ID and its index in the list of elements in the composite object:

//+------------------------------------------------------------------+
//| The methods of simplified access to object properties            |
//+------------------------------------------------------------------+
//--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge,
   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());         }
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element,
//--- (5) all shifts of the active area edges relative to the element, (6) the element background color and (7) the element opacity
   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);
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   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);     }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   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());           }
//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge
   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();                          }
//--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,
   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);               }
//--- Return the element (1) moveability and (2) activity flag
   bool              Movable(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);             }
   bool              Active(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);              }
//--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index
   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);              }
//--- Return (1) the element ID and (2) index in the list
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }

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

All these methods simply return the appropriate element object property.


Methods of working with primitives

The CCanvas class provides ample opportunities for drawing various graphical primitives on the canvas. We are able to either read the color of each pixel or set the required color and transparency to it. In addition to simply setting a color to a pixel, the class provides the tools for drawing different figures either pixel by pixel (without smoothing) or with the help of various smoothing methods.

The graphical element object class is to provide users the access to the CCanvas class drawing methods. Our methods will only slightly simplify calling the CCanvas class methods. The simplification is in the fact that the color is set in the usual way — by specifying the necessary color in the color format and setting the color opacity degree (0 — transparent, 255 — completely opaque), whereas the CCanvas class methods "ask" to specify the color immediately in the uint ARGB format, which is just a number. Not everyone is comfortable with specifying the desired color in this format (semi-transparent gray: 0x7F7F7F7F). In subsequent classes that are to be inherited from the graphical element object, I will expand the range of drawing features by adding the convenient functionality inherent in each created class. In the same class, which is basic for creating the remaining graphical objects, the drawing methods should be simple and straightforward.

The block of methods for simplified access to object properties is followed by new code blocks. I tried to distribute them according to their purpose.
Data receiving methods starts with "Get", while data setting methods begin with "Set".

The method receiving the color of the point with specified coordinates:

//+------------------------------------------------------------------+
//| The methods of receiving raster data                             |
//+------------------------------------------------------------------+
//--- Get a color of the dot with the specified coordinates
   uint              GetPixel(const int x,const int y)   const { return this.m_canvas.PixelGet(x,y);                                   }

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

The result of calling the PixelGet() method of the CCanvas class is returned here. The method returns the color in ARGB format.

The methods of filling, clearing and updating raster data:

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- Clear the element filling it with color and opacity
   void              Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element completely
   void              Erase(const bool redraw=false);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }
   
//+------------------------------------------------------------------+

The Update() method simply updates the object and chart using the Update() method of the CCanvas class.

The Erase() methods are implemented outside the class body:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const bool redraw=false)
  {
   this.m_canvas.Erase(NULL_COLOR);
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+

These are two overloaded methods.
In the first one, we pass the required color and opacity used to fill the entire element with the help of the Erase() method of the CCanvas class. Please note that the method applies the color and opacity level passed to the Erase() method of the CCanvas class using conversion of their values to ARGB format by the ColorToARGB() function. I will do that in all drawing methods.
In the second method, we simply fill the entire background with a completely transparent black color. Its value was previously defined using the NULL_COLOR macro substitution.
Each of the methods receives the flag indicating the necessity to redraw the chart. If the flag is set, the chart is re-drawn.

Next is the block of methods for drawing primitives without smoothing. All methods are identical and call the corresponding methods of the CCanvas class. These methods receive the parameters specified in the method arguments and the color converted into ARGB format from the color and transparency values passed to the methods:

//+------------------------------------------------------------------+
//| The methods of drawing primitives without smoothing              |
//+------------------------------------------------------------------+
//--- Set the color of the dot with the specified coordinates
   void              SetPixel(const int x,const int y,const color clr,const uchar opacity=255)
                       { this.m_canvas.PixelSet(x,y,::ColorToARGB(clr,opacity));                                                       }
                       
//--- Draw a segment of a vertical line
   void              DrawLineVertical(const int x,                // X coordinate of the segment
                                      const int y1,               // Y coordinate of the segment first point
                                      const int y2,               // Y coordinate of the segment second point
                                      const color clr,            // Color
                                      const uchar opacity=255)    // Opacity
                       { this.m_canvas.LineVertical(x,y1,y2,::ColorToARGB(clr,opacity));                                               }
                       
//--- Draw a segment of a horizontal line
   void              DrawLineHorizontal(const int x1,             // X coordinate of the segment first point
                                        const int x2,             // X coordinate of the segment second point
                                        const int y,              // Segment Y coordinate
                                        const color clr,          // Color
                                        const uchar opacity=255)  // Opacity
                       { this.m_canvas.LineHorizontal(x1,x2,y,::ColorToARGB(clr,opacity));                                             }
                       
//--- Draw a segment of a freehand line
   void              DrawLine(const int x1,                       // X coordinate of the segment first point
                              const int y1,                       // Y coordinate of the segment first point
                              const int x2,                       // X coordinate of the segment second point
                              const int y2,                       // Y coordinate of the segment second point
                              const color clr,                    // Color
                              const uchar opacity=255)            // Opacity
                       { this.m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                                   }
                       
//--- Draw a polyline
   void              DrawPolyline(int &array_x[],                 // Array with the X coordinates of polyline points
                                  int & array_y[],                // Array with the Y coordinates of polyline points
                                  const color clr,                // Color
                                  const uchar opacity=255)        // Opacity
                       { this.m_canvas.Polyline(array_x,array_y,::ColorToARGB(clr,opacity));                                           }
                       
//--- Draw a polygon
   void              DrawPolygon(int &array_x[],                  // Array with the X coordinates of polygon points
                                 int &array_y[],                  // Array with the Y coordinates of polygon points
                                 const color clr,                 // Color
                                 const uchar opacity=255)         // Opacity
                       { this.m_canvas.Polygon(array_x,array_y,::ColorToARGB(clr,opacity));                                            }
                       
//--- Draw a rectangle using two points
   void              DrawRectangle(const int x1,                  // X coordinate of the first point defining the rectangle
                                   const int y1,                  // Y coordinate of the first point defining the rectangle
                                   const int x2,                  // X coordinate of the second point defining the rectangle
                                   const int y2,                  // Y coordinate of the second point defining the rectangle
                                   const color clr,               // color
                                   const uchar opacity=255)       // Opacity
                       { this.m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                              }
                       
//--- Draw a circle
   void              DrawCircle(const int x,                      // X coordinate of the circle center
                                const int y,                      // Y coordinate of the circle center
                                const int r,                      // Circle radius
                                const color clr,                  // Color
                                const uchar opacity=255)          // Opacity
                       { this.m_canvas.Circle(x,y,r,::ColorToARGB(clr,opacity));                                                       }
                       
//--- Draw a triangle
   void              DrawTriangle(const int x1,                   // X coordinate of the triangle first vertex
                                  const int y1,                   // Y coordinate of the triangle first vertex
                                  const int x2,                   // X coordinate of the triangle second vertex
                                  const int y2,                   // Y coordinate of the triangle second vertex
                                  const int x3,                   // X coordinate of the triangle third vertex
                                  const int y3,                   // Y coordinate of the triangle third vertex
                                  const color clr,                // Color
                                  const uchar opacity=255)        // Opacity
                       { m_canvas.Triangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity));                                              }
                       
//--- Draw an ellipse using two points
   void              DrawEllipse(const int x1,                    // X coordinate of the first point defining the ellipse
                                 const int y1,                    // Y coordinate of the first point defining the ellipse
                                 const int x2,                    // X coordinate of the second point defining the ellipse
                                 const int y2,                    // Y coordinate of the second point defining the ellipse
                                 const color clr,                 // Color
                                 const uchar opacity=255)         // Opacity
                       { this.m_canvas.Ellipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                                }
                       
//--- Draw an arc of an ellipse inscribed in a rectangle with corners at (x1,y1) and (x2,y2).
//--- The arc boundaries are clipped by lines from the center of the ellipse, which extend to two points with coordinates (x3,y3) and (x4,y4)
   void              DrawArc(const int x1,                        // X coordinate of the top left corner forming the rectangle
                             const int y1,                        // Y coordinate of the top left corner forming the rectangle
                             const int x2,                        // X coordinate of the bottom right corner forming the rectangle
                             const int y2,                        // Y coordinate of the bottom right corner forming the rectangle
                             const int x3,                        // X coordinate of the first point, to which a line from the rectangle center is drawn in order to obtain the arc boundary
                             const int y3,                        // Y coordinate of the first point, to which a line from the rectangle center is drawn in order to obtain the arc boundary
                             const int x4,                        // X coordinate of the second point, to which a line from the rectangle center is drawn in order to obtain the arc boundary
                             const int y4,                        // Y coordinate of the second point, to which a line from the rectangle center is drawn in order to obtain the arc boundary
                             const color clr,                     // Color
                             const uchar opacity=255)             // Opacity
                       { m_canvas.Arc(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity));                                             }
                       
//--- Draw a filled sector of an ellipse inscribed in a rectangle with corners at (x1,y1) and (x2,y2).
//--- The sector boundaries are clipped by lines from the center of the ellipse, which extend to two points with coordinates (x3,y3) and (x4,y4)
   void              DrawPie(const int x1,                        // X coordinate of the upper left corner of the rectangle
                             const int y1,                        // Y coordinate of the upper left corner of the rectangle
                             const int x2,                        // X coordinate of the bottom right corner of the rectangle
                             const int y2,                        // Y coordinate of the bottom right corner of the rectangle
                             const int x3,                        // X coordinate of the first point to find the arc boundaries
                             const int y3,                        // Y coordinate of the first point to find the arc boundaries
                             const int x4,                        // X coordinate of the second point to find the arc boundaries
                             const int y4,                        // Y coordinate of the second point to find the arc boundaries
                             const color clr,                     // Line color
                             const color fill_clr,                // Fill color
                             const uchar opacity=255)             // Opacity
                       { this.m_canvas.Pie(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity),ColorToARGB(fill_clr,opacity));          }
                       
//+------------------------------------------------------------------+


The block of methods for drawing filled primitives without smoothing:

//+------------------------------------------------------------------+
//| The methods of drawing filled primitives without smoothing       |
//+------------------------------------------------------------------+
//--- Fill in the area
   void              Fill(const int x,                            // X coordinate of the filling start point
                          const int y,                            // Y coordinate of the filling start point
                          const color clr,                        // Color
                          const uchar opacity=255,                // Opacity
                          const uint threshould=0)                // Threshold
                       { this.m_canvas.Fill(x,y,::ColorToARGB(clr,opacity),threshould);                                                }
                       
//--- Draw a filled rectangle
   void              DrawRectangleFill(const int x1,              // X coordinate of the first point defining the rectangle
                                       const int y1,              // Y coordinate of the first point defining the rectangle
                                       const int x2,              // X coordinate of the second point defining the rectangle
                                       const int y2,              // Y coordinate of the second point defining the rectangle
                                       const color clr,           // Color
                                       const uchar opacity=255)   // Opacity
                       { this.m_canvas.FillRectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                          }

//--- Draw a filled circle
   void              DrawCircleFill(const int x,                  // X coordinate of the circle center
                                    const int y,                  // Y coordinate of the circle center
                                    const int r,                  // Circle radius
                                    const color clr,              // Color
                                    const uchar opacity=255)      // Opacity
                       { this.m_canvas.FillCircle(x,y,r,::ColorToARGB(clr,opacity));                                                   }
                       
//--- Draw a filled triangle
   void              DrawTriangleFill(const int         x1,      // X coordinate of the triangle first vertex
                                      const int         y1,      // Y coordinate of the triangle first vertex
                                      const int         x2,      // X coordinate of the triangle second vertex
                                      const int         y2,      // Y coordinate of the triangle second vertex
                                      const int         x3,      // X coordinate of the triangle third vertex
                                      const int         y3,      // Y coordinate of the triangle third vertex
                                      const color clr,           // Color
                                      const uchar opacity=255)   // Opacity
                       { this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity));                                     }
                       
//--- Draw a filled polygon
   void              DrawPolygonFill(int &array_x[],              // Array with the X coordinates of polygon points
                                     int &array_y[],              // Array with the Y coordinates of polygon points
                                     const color clr,             // Color
                                     const uchar opacity=255)     // Opacity
                       { this.m_canvas.FillPolygon(array_x,array_y,::ColorToARGB(clr,opacity));                                        }
                       
//--- Draw a filled ellipse inscribed in a rectangle with the specified coordinates
   void              DrawEllipseFill(const int x1,                // X coordinate of the top left corner forming the rectangle
                                     const int y1,                // Y coordinate of the top left corner forming the rectangle
                                     const int x2,                // X coordinate of the bottom right corner forming the rectangle
                                     const int y2,                // Y coordinate of the bottom right corner forming the rectangle
                                     const color clr,             // Color
                                     const uchar opacity=255)     // Opacity
                       { this.m_canvas.FillEllipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity));                                            }
                       
//+------------------------------------------------------------------+


The methods of drawing primitives using smoothing:

//+------------------------------------------------------------------+
//| The methods of drawing primitives using smoothing                |
//+------------------------------------------------------------------+
//--- Draw a point using AntiAliasing algorithm
   void              SetPixelAA(const double x,                   // Point X coordinate
                                const double y,                   // Point Y coordinate
                                const color clr,                  // Color
                                const uchar opacity=255)          // Opacity
                       { this.m_canvas.PixelSetAA(x,y,::ColorToARGB(clr,opacity));                                                     }
                       
//--- Draw a segment of a freehand line using AntiAliasing algorithm
   void              DrawLineAA(const int   x1,                   // X coordinate of the segment first point
                                const int   y1,                   // Y coordinate of the segment first point
                                const int   x2,                   // X coordinate of the segment second point
                                const int   y2,                   // Y coordinate of the segment second point
                                const color clr,                  // Color
                                const uchar opacity=255,          // Opacity
                                const uint  style=UINT_MAX)       // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.LineAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                           }
                       
//--- Draw a segment of a freehand line using Wu algorithm
   void              DrawLineWu(const int   x1,                   // X coordinate of the segment first point
                                const int   y1,                   // Y coordinate of the segment first point
                                const int   x2,                   // X coordinate of the segment second point
                                const int   y2,                   // Y coordinate of the segment second point
                                const color clr,                  // Color
                                const uchar opacity=255,          // Opacity
                                const uint  style=UINT_MAX)       // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.LineWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                           }
                       
//--- Draws a segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration
   void              DrawLineThick(const int   x1,                // X coordinate of the segment first point
                                   const int   y1,                // Y coordinate of the segment first point
                                   const int   x2,                // X coordinate of the segment second point
                                   const int   y2,                // Y coordinate of the segment second point
                                   const int   size,              // Line width
                                   const color clr,               // Color
                                   const uchar opacity=255,       // Opacity
                                   const uint  style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                   ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.LineThick(x1,y1,x2,y2,::ColorToARGB(clr,opacity),size,style,end_style);                         }
 
//--- Draw a vertical segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration
   void              DrawLineThickVertical(const int   x,         // X coordinate of the segment
                                           const int   y1,        // Y coordinate of the segment first point
                                           const int   y2,        // Y coordinate of the segment second point
                                           const int   size,      // Line width
                                           const color clr,       // Color
                                           const uchar opacity=255,// Opacity
                                           const uint  style=STYLE_SOLID,  // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                           const ENUM_LINE_END end_style=LINE_END_ROUND)  // Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.LineThickVertical(x,y1,y2,::ColorToARGB(clr,opacity),size,style,end_style);                     }
                       
//--- Draw a horizontal segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration
   void              DrawLineThickHorizontal(const int   x1,      // X coordinate of the segment first point
                                             const int   x2,      // X coordinate of the segment second point
                                             const int   y,       // Segment Y coordinate
                                             const int   size,    // Line width
                                             const color clr,     // Color
                                             const uchar opacity=255,// Opacity
                                             const uint  style=STYLE_SOLID,  // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                             const ENUM_LINE_END end_style=LINE_END_ROUND)  // Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.LineThickHorizontal(x1,x2,y,::ColorToARGB(clr,opacity),size,style,end_style);                   }

//--- Draws a polyline using AntiAliasing algorithm
   void              DrawPolylineAA(int        &array_x[],        // Array with the X coordinates of polyline points
                                    int        &array_y[],        // Array with the Y coordinates of polyline points
                                    const color clr,              // Color
                                    const uchar opacity=255,      // Opacity
                                    const uint  style=UINT_MAX)   // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.PolylineAA(array_x,array_y,::ColorToARGB(clr,opacity),style);                                   }
                       
//--- Draws a polyline using Wu algorithm
   void              DrawPolylineWu(int        &array_x[],        // Array with the X coordinates of polyline points
                                    int        &array_y[],        // Array with the Y coordinates of polyline points
                                    const color clr,              // Color
                                    const uchar opacity=255,      // Opacity
                                    const uint  style=UINT_MAX)   // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.PolylineWu(array_x,array_y,::ColorToARGB(clr,opacity),style);                                   }
                       
//--- Draw a polyline with a specified width consecutively using two antialiasing algorithms.
//--- First, individual line segments are smoothed based on Bezier curves.
//--- Then, the raster antialiasing algorithm is applied to the polyline built from these segments to improve the rendering quality
   void              DrawPolylineSmooth(const int   &array_x[],   // Array with the X coordinates of polyline points
                                        const int   &array_y[],   // Array with the Y coordinates of polyline points
                                        const int    size,        // Line width
                                        const color  clr,         // Color
                                        const uchar  opacity=255, // Opacity
                                        const double tension=0.5, // Smoothing parameter value
                                        const double step=10,     // Approximation step
                                        const ENUM_LINE_STYLE style=STYLE_SOLID,// Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                        const ENUM_LINE_END   end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.PolylineSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step);   }
                       
//--- Draw a polyline having a specified width using smoothing algorithm with the preliminary filtration
   void              DrawPolylineThick(const int     &array_x[],  // Array with the X coordinates of polyline points
                                       const int     &array_y[],  // Array with the Y coordinates of polyline points
                                       const int      size,       // Line width
                                       const color    clr,        // Color
                                       const uchar    opacity=255,// Opacity
                                       const uint     style=STYLE_SOLID,         // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                       ENUM_LINE_END  end_style=LINE_END_ROUND)  // Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.PolylineThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style);                 }
                       
//--- Draw a polygon using AntiAliasing algorithm
   void              DrawPolygonAA(int        &array_x[],         // Array with the X coordinates of polygon points
                                   int        &array_y[],         // Array with the Y coordinates of polygon points
                                   const color clr,               // Color
                                   const uchar opacity=255,       // Opacity
                                   const uint  style=UINT_MAX)    // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.PolygonAA(array_x,array_y,::ColorToARGB(clr,opacity),style);                                    }
                       
//--- Draw a polygon using Wu algorithm
   void              DrawPolygonWu(int        &array_x[],         // Array with the X coordinates of polygon points
                                   int        &array_y[],         // Array with the Y coordinates of polygon points
                                   const color clr,               // Color
                                   const uchar opacity=255,       // Opacity
                                   const uint  style=UINT_MAX)    // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.PolygonWu(array_x,array_y,::ColorToARGB(clr,opacity),style);                                    }
                       
//--- Draw a polygon with a specified width consecutively using two smoothing algorithms.
//--- First, individual segments are smoothed based on Bezier curves.
//--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. 
   void              DrawPolygonSmooth(int         &array_x[],    // Array with the X coordinates of polyline points
                                       int         &array_y[],    // Array with the Y coordinates of polyline points
                                       const int    size,         // Line width
                                       const color  clr,          // Color
                                       const uchar  opacity=255,  // Opacity
                                       const double tension=0.5,  // Smoothing parameter value
                                       const double step=10,      // Approximation step
                                       const ENUM_LINE_STYLE style=STYLE_SOLID,// Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                                       const ENUM_LINE_END   end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration values
                       { this.m_canvas.PolygonSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step);    }
                       
//--- Draw a polygon having a specified width using smoothing algorithm with the preliminary filtration
   void              DrawPolygonThick(const int  &array_x[],      // array with the X coordinates of polygon points
                                      const int  &array_y[],      // array with the Y coordinates of polygon points
                                      const int   size,           // line width
                                      const color clr,            // Color
                                      const uchar opacity=255,    // Opacity
                                      const uint  style=STYLE_SOLID,// line style
                                      ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style
                       { this.m_canvas.PolygonThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style);                  }
                       
//--- Draw a triangle using AntiAliasing algorithm
   void              DrawTriangleAA(const int   x1,               // X coordinate of the triangle first vertex
                                    const int   y1,               // Y coordinate of the triangle first vertex
                                    const int   x2,               // X coordinate of the triangle second vertex
                                    const int   y2,               // Y coordinate of the triangle second vertex
                                    const int   x3,               // X coordinate of the triangle third vertex
                                    const int   y3,               // Y coordinate of the triangle third vertex
                                    const color clr,              // Color
                                    const uchar opacity=255,      // Opacity
                                    const uint  style=UINT_MAX)   // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.TriangleAA(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style);                                 }
                       
//--- Draw a triangle using Wu algorithm
   void              DrawTriangleWu(const int   x1,               // X coordinate of the triangle first vertex
                                    const int   y1,               // Y coordinate of the triangle first vertex
                                    const int   x2,               // X coordinate of the triangle second vertex
                                    const int   y2,               // Y coordinate of the triangle second vertex
                                    const int   x3,               // X coordinate of the triangle third vertex
                                    const int   y3,               // Y coordinate of the triangle third vertex
                                    const color clr,              // Color
                                    const uchar opacity=255,      // Opacity
                                    const uint  style=UINT_MAX)   // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.TriangleWu(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style);                                 }
                       
//--- Draw a circle using AntiAliasing algorithm
   void              DrawCircleAA(const int    x,                 // X coordinate of the circle center
                                  const int    y,                 // Y coordinate of the circle center
                                  const double r,                 // Circle radius
                                  const color  clr,               // Color
                                  const uchar opacity=255,        // Opacity
                                  const uint  style=UINT_MAX)     // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.CircleAA(x,y,r,::ColorToARGB(clr,opacity),style);                                               }
                       
//--- Draw a circle using Wu algorithm
   void              DrawCircleWu(const int    x,                 // X coordinate of the circle center
                                  const int    y,                 // Y coordinate of the circle center
                                  const double r,                 // Circle radius
                                  const color  clr,               // Color
                                  const uchar opacity=255,        // Opacity
                                  const uint  style=UINT_MAX)     // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.CircleWu(x,y,r,::ColorToARGB(clr,opacity),style);                                               }
                       
//--- Draw an ellipse by two points using AntiAliasing algorithm
   void              DrawEllipseAA(const double x1,               // X coordinate of the first point defining the ellipse
                                   const double y1,               // Y coordinate of the first point defining the ellipse
                                   const double x2,               // X coordinate of the second point defining the ellipse
                                   const double y2,               // Y coordinate of the second point defining the ellipse
                                   const color  clr,              // Color
                                   const uchar opacity=255,       // Opacity
                                   const uint  style=UINT_MAX)    // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.EllipseAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                        }
                       
//--- Draw an ellipse by two points using Wu algorithm
   void              DrawEllipseWu(const int   x1,                // X coordinate of the first point defining the ellipse
                                   const int   y1,                // Y coordinate of the first point defining the ellipse
                                   const int   x2,                // X coordinate of the second point defining the ellipse
                                   const int   y2,                // Y coordinate of the second point defining the ellipse
                                   const color clr,               // Color
                                   const uchar opacity=255,       // Opacity
                                   const uint  style=UINT_MAX)    // Line style is one of the ENUM_LINE_STYLE enumeration values or a custom value
                       { this.m_canvas.EllipseWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style);                                        }

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

The logic of all added methods is completely transparent. All parameters passed to the methods are signed. The purpose of each method is stated in the comments. So, I believe all is clear here. In any case, you are welcome to use the comments section.


Methods of working with a text

The CCanvas class remembers the settings of the last displayed text, including its font, color, transparency, etc. To find out the text size, we are able to use the TextSize() method applying the current font settings to measure the width and height of the text-bounding rectangle. We may need this on several occasions, like when overwriting the previous text using the background color and writing it using new coordinates — text offset. In this case, we need not only text coordinates but also text anchor points (left-top, center-top, right-top etc.). We need to know exactly which anchor angle of the bounding rectangle is given to the text, otherwise the coordinates of the eraser rectangle will be set incorrectly. To achieve this, we need to add the class member variable storing the last specified anchor point.

Declare the variable in the private section of the class:

   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Integer properties
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Real properties
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // String properties
   
   ENUM_TEXT_ANCHOR  m_text_anchor;                            // Current text alignment

//--- Return the index of the array the order (1) double and (2) string properties are located at
   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;  }

At the very start of the parametric class constructor, initialize the values of the object type and the text anchor point:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
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))
     {

In the m_type parent class variable returned by its Type() virtual method, add the object type passed in the constructor parameters, while the m_text_anchor variable is initialized using the default value — the upper left corner of the bounding rectangle.

At the very end of the class body (namely, after the code block for working with primitives), add the code block for working with text:

//+------------------------------------------------------------------+
//| The methods of working with text                                 |
//+------------------------------------------------------------------+
//--- Return text the alignment type (anchor method)
   ENUM_TEXT_ANCHOR  TextAnchor(void)                       const { return this.m_text_anchor;                                         }

//--- Set the current font
   bool              SetFont(const string name,                   // Font name. For example, "Arial"
                             const int    size,                   // Font size
                             const uint   flags=0,                // Font creation flags
                             const uint   angle=0,                // Font slope angle in tenths of a degree
                             const bool   relative=true)          // Relative font size flag
                       { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle);                                  }

//--- Set a font name
   bool              SetFontName(const string name)               // Font name. For example, "Arial"
                       { return this.m_canvas.FontNameSet(name);                                                                       }

//--- Set a font size
   bool              SetFontSize(const int size,                  // Font size
                                 const bool relative=true)        // Relative font size flag
                       { return this.m_canvas.FontSizeSet(relative ? size*-10 : size);                                                 }

//--- Set font flags
//--- FONT_ITALIC - Italic, FONT_UNDERLINE - Underline, FONT_STRIKEOUT - Strikeout
   bool              SetFontFlags(const uint flags)               // Font creation flags
                       { return this.m_canvas.FontFlagsSet(flags);                                                                     }

//--- Set a font slope angle
   bool              SetFontAngle(const float angle)              // Font slope angle in tenths of a degree
                       { return this.m_canvas.FontAngleSet(uint(angle*10));                                                            }

//--- Set the font anchor angle (alignment type)
   void              SetTextAnchor(const uint flags=0)      { this.m_text_anchor=(ENUM_TEXT_ANCHOR)flags;                              }

//--- Gets the current font parameters and write them to variables
   void              GetFont(string &name,                        // The reference to the variable for returning a font name
                             int    &size,                        // Reference to the variable for returning a font size
                             uint   &flags,                       // Reference to the variable for returning font flags
                             uint   &angle)                       // Reference to the variable for returning a font slope angle
                       { this.m_canvas.FontGet(name,size,flags,angle);                                                                 }

//--- Return (1) the font name, (2) size, (3) flags and (4) slope angle
   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();                               }

//--- Return the text (1) width, (2) height and (3) all sizes (the current font is used to measure the text)
   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,                  // Text for measurement
                              int         &width,                 // Reference to the variable for returning a text width
                              int         &height)                // Reference to the variable for returning a text height
                       { this.m_canvas.TextSize(text,width,height);                                                                    }

//--- Display the text in the current font
   void              Text(int         x,                          // X coordinate of the text anchor point
                          int         y,                          // Y coordinate of the text anchor point
                          string      text,                       // Display text
                          const color clr,                        // Color
                          const uchar opacity=255,                // Opacity
                          uint        alignment=0)                // Text anchoring method
                       { 
                        this.m_text_anchor=(ENUM_TEXT_ANCHOR)alignment;
                        this.m_canvas.TextOut(x,y,text,::ColorToARGB(clr,opacity),alignment);
                       }

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

Here all is similar to handling primitives. All methods feature comments disclosing their purpose, inputs and outputs.
I would like to comment on the method of setting the current font parameters:

//--- Set the current font
   bool              SetFont(const string name,                   // Font name. For example, "Arial"
                             const int    size,                   // Font size
                             const uint   flags=0,                // Font creation flags
                             const uint   angle=0,                // Font slope angle in tenths of a degree
                             const bool   relative=true)          // Relative font size flag
                       { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle);  

Here size specifies the font size and is always set according to the font size we would set when displaying a text with the help of the ordinary OBJ_LABEL text label object. The size is set as a positive integer value in it. Contrary to that, when drawing the text on the canvas, the font size is set the same way as in the TextSetFont() function:

The font size is set using positive or negative values. The sign defines whether the text size depends on the operating system settings (font scale).

  • If the size is positive, it is converted into device physical units (pixels) when displaying the logical font as a physical one. The size corresponds to the height of symbol cells from available fonts. It is not recommended in cases of shared usage of texts displayed using the TextOut() function and texts displayed using the OBJ_LABEL ("Text label") graphical object.
  • If the size is negative, it is assumed to be set in tenths of a logical point (the value -350 is equal to 35 logical points) and is divided by 10. The resulting value is converted into physical units of the device (pixels) and corresponds to the absolute value of the character height from available fonts. To obtain a text of the OBJ_LABEL object size on the screen, multiply the font size specified in the object properties by -10.

The font relative size flag is checked in the method. If it is set (by default), the size value is multiplied by -10 so that the font is specified using a correct value for passing the CCanvas class to the FontSet() method.

In the parametric class constructor, add font initialization (set its default name and size):

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
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))
     {

These are all the improvements I have planned for the current article. Let's test the results.


Test


We have the EA from the previous article displaying two graphical element objects on the chart. Let's take the same EA, save it in the new folder \MQL5\Experts\TestDoEasy\Part75\ as TestDoEasyPart75.mq5 and do the following:

When clicking on the first (top) object, we will alternately draw a rectangle and a circle on it. With each new click on the object, the size of the rectangle decreases by 2 pixels on each side and the radius of the circle also decreases by 2 pixels. The rectangle is drawn in the usual way, while the circle is drawn using smoothing. With each new click, the object opacity is increased from 0 to 255 in a loop.

When clicking on the second (bottom) object, display a text on it alternately changing its anchor point. Write the anchor point name in the text itself. The object transparency remains unchanged.

Specify the number of created elements:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart75.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/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)   // Number of created graphical elements
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
CArrayObj      list_elements;
//+------------------------------------------------------------------+

To avoid creating identical objects each time the timeframe changes, clear the list of the already created objects in the OnInit() handler before creating new ones:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables

//--- Create the specified number of graphical elements on the canvas
   list_elements.Clear();
   int total=ELEMENTS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      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;
      //--- Add objects to the list
      if(!list_elements.Add(element))
        {
         delete element;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Since I do not have the graphical object collection class yet to check the need for creating a new object with a specified name, here I will simply recreate these objects anew clearing the list of the previously created objects beforehand.

The OnChartEvent() handler receives handling mouse clicks on two created objects:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If clicking on an object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- In the new list, get the element object with the name corresponding to the sparam string parameter value of the OnChartEvent() handler
      CArrayObj *obj_list=CSelect::ByGraphCanvElementProperty(GetPointer(list_elements),CANV_ELEMENT_PROP_NAME_OBJ,sparam,EQUAL);
      //--- If the object is received from the list
      if(obj_list!=NULL && obj_list.Total()>0)
        {
         static uchar try0=0, try1=0;
         //--- Get the pointer to the object in the list
         CGCnvElement *obj=obj_list.At(0);
         //--- If this is the first graphical element
         if(obj.ID()==0)
           {
            //--- Set a new opacity level for the object
            uchar opasity=obj.Opacity();
            if((opasity+5)>255)
               opasity=0;
            else 
               opasity+=5;
            //--- Set a new opacity to the object
            obj.SetOpacity(opasity);
            //--- Set rectangle and circle coordinates
            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;
            //--- Draw a rectangle at each first click
            if(try0%2==0)
               obj.DrawRectangle(x1+try0,y1+try0,x2-try0,y2-try0,clrDodgerBlue,obj.Opacity());
            //--- Display the circle smoothed using AntiAliasing at each second click
            else
               obj.DrawCircleAA(xC,yC,R-try0,clrGreen,obj.Opacity());
            //--- If the number of clicks on the object exceeds 30
            if(try0>30)
              {
               //--- Clear the object setting its current color and transparency
               obj.Erase(obj.ColorBG(),obj.Opacity());
               //--- Re-start the click number countdown
               try0=0;
              }
            //--- Update the chart and the object, and display the comment featuring color values and object opacity
            obj.Update(true); // 'true' is not needed here since the next Comment command redraws the chart anyway
            Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG());
            //--- Increase the counter of mouse clicks by object
            try0++;
           }
         //--- If this is the second object
         else if(obj.ID()==1)
           {
            //--- Set the font parameters for it ("Calibri" size 8)
            obj.SetFont("Calibri",8);
            //--- Set the text anchor angle corresponding to the click counter by object
            obj.SetTextAnchor((ENUM_TEXT_ANCHOR)try1);
            //--- Create the text out of the anchor angle name
            string text=StringSubstr(EnumToString(obj.TextAnchor()),12);
            //--- Set the text coordinates relative to the upper left corner of the graphical element
            int xT=2,yT=2;
            //--- Depending on the anchor angle, set the new coordinates of the displayed text
            //--- LEFT_TOP
            if(try1==0)       { xT=2; yT=2;                                   }
            //--- CENTER_TOP
            else if(try1==1)  { xT=obj.Width()/2; yT=2;                       }
            //--- RIGHT_TOP
            //--- since the ENUM_TEXT_ANCHOR enumeration features no 3, increase the counter of object clicks by 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
            //--- since the ENUM_TEXT_ANCHOR enumeration features no 7, increase the counter of object clicks by 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;       }
            //--- Clear the graphical element filling it with the current color and transparency
            obj.Erase(obj.ColorBG(),obj.Opacity());
            //--- Display the text with the calculated coordinates in the cleared element
            obj.Text(xT,yT,text,clrDodgerBlue,255,obj.TextAnchor());
            //--- Update the object and chart
            obj.Update(true); // 'true' is not needed here since the next Comment command redraws the chart anyway
            Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG());
            //--- Increase the counter of object clicks
            try1++;
            if(try1>10)
               try1=0;
           }
        }
     }
  }
//+------------------------------------------------------------------+

The handler code features detailed comments. I believe, its logic is clear. If you have any questions related to the current article, feel free to ask them in the comments below.

Compile the EA and launch it on the chart. Click on the objects:


As a result, I accidentally obtained a funny image on the upper object resembling a CD :)

What's next?

In the next article, I will start the development of the objects that are descendants of the graphical element object created here.

All files of the current version of the library are attached below together with the test EA file for MQL5 for you to test and download.
Leave your questions and suggestions in the comments.

Back to contents

*Previous articles within the series:

Graphics in DoEasy library (Part 73): Form object of a graphical element
Graphics in DoEasy library (Part 74): Basic graphical element powered by the CCanvas class