Developing custom indicators using CCanvas class

Alexander Fedosov | 19 July, 2017

Contents

Introduction

Custom indicators form an integral part of modern MetaTrader 5 trading. They are used both in automated trading systems (as part of the algorithms) and in manual trading. By now, it has been possible to set a drawing style and apply 18 types of graphical plotting when developing an indicator. But the platform features broader graphical capabilities. The CCanvas custom graphics library allows developing custom non-standard indicators with infinite visualization capabilities. This article introduces readers to the library features and provides examples of its application. A separate library of custom indicators is developed as well.


Developing a base custom graphics class

To develop a base class, we need to write a set of methods creating the foundation of any custom graphics object and including a common set of properties. To do this, create the CanvasBase.mqh file in the CustomGUI folder of the <data folder>\MQL5\Include directory. This file will contain the CCanvasBase base class for all future types of custom graphics.

//+------------------------------------------------------------------+
//|                                                  CCanvasBase.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Base class for custom graphics development                       |
//+------------------------------------------------------------------+
class CCanvasBase
  {
private:
   //--- Canvas name
   string            m_canvas_name;
   //--- Canvas coordinates
   int               m_x;
   int               m_y;
   //--- Canvas size
   int               m_x_size;
   int               m_y_size;
protected:
   CCanvas           m_canvas;
   //--- Create the graphical resource for the object
   bool              CreateCanvas(void);
   //--- Delete the graphical resource
   bool              DeleteCanvas(void);
public:
                     CCanvasBase(void);
                    ~CCanvasBase(void);
   //--- Set and get coordinates
   void              X(const int x)                         { m_x=x;                      }
   void              Y(const int y)                         { m_y=y;                      }
   int               X(void)                                { return(m_x);                }
   int               Y(void)                                { return(m_y);                }
   //--- Set and get size
   void              XSize(const int x_size)                { m_x_size=x_size;            }
   void              YSize(const int y_size)                { m_y_size=y_size;            }
   int               XSize(void)                            { return(m_x_size);           }
   int               YSize(void)                            { return(m_y_size);           }
   //--- Set the indicator name when creating
   void              Name(const string canvas_name) { m_canvas_name=canvas_name;  }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(void) : m_x(0),
                                 m_y(0),
                                 m_x_size(200),
                                 m_y_size(200)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
  }
//+------------------------------------------------------------------+
//| Create the graphical resource for an object                      |
//+------------------------------------------------------------------+
bool CCanvasBase::CreateCanvas(void)
  {
   ObjectDelete(0,m_canvas_name);
   if(!m_canvas.CreateBitmapLabel(m_canvas_name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_ANCHOR,ANCHOR_CENTER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_BACK,false);
   return(true);
  }
//+------------------------------------------------------------------+
//| Delete the graphical resource                                    |
//+------------------------------------------------------------------+
bool CCanvasBase::DeleteCanvas()
  {
   return(ObjectDelete(0,m_canvas_name)?true:false);
  }
//+------------------------------------------------------------------+

As we can see, the initial object coordinates, name and size are complemented with the graphical resource creation and deletion methods that form the foundation (canvas) the custom graphics elements are plotted on. Further on, CreateCanvas() and DeleteCanvas() methods are to be applied during the initial construction of any graphical object at all times. Therefore, the variables used in these methods should be initialized, like it is done in the constructor.


CCircleSimple class

Let's use the "simple-to-complex basis" in order to grasp the principles of developing custom graphics. First, we are going to develop a simple circular indicator featuring a frame, numerical value and description. The Fig. 1 shows the structure of the basic elements.

  • Frame (border). An outlined edging.
  • Background. The space the text elements are located in.
  • Value. A text element displaying a numerical value.
  • Description. Indicator description text (name, period and other distinctive information).


Fig. 1. Basic structure of the simple circular indicator

Let's create yet another folder and name it Indicator in the previously created CustomGUI one. The folder is to contain all classes of future indicators. Create the first one named CircleSimple.mqh. First, we need to include the previously created CanvaseBase.mqh file to the base class for all types of graphical objects and make the CCanvaseBase class basic for the current one, so that all methods of the class are available for the current CCircleSimple.

//+------------------------------------------------------------------+
//|                                                 CircleSimple.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Circular indicator with numerical value and description          |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase

In order not to enter graphical objects manually every time we need them in EA and indicator files, let's create CustomGUI.mqh file in the CustomGUI folder. The file is to contain all inclusions of classes from the Indicators folder. Thus, we need to include only this file to access all library classes. Now, let's include the current one:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"

When implementing the simple circular indicator class, it is also necessary to carefully consider the set of properties and methods providing the ability to set this seemingly simple graphical indicator pattern. The listing below contains the set:

//+------------------------------------------------------------------+
//| Circular indicator with numerical value and description          |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase
  {
private:
   //--- Background color 
   color             m_bg_color;
   //--- Frame color
   color             m_border_color;
   //--- Value text color
   color             m_font_color;
   //--- Label text color
   color             m_label_color;
   //--- Transparency
   uchar             m_transparency;
   //--- Frame width
   int               m_border;
   //--- Indicator size
   int               m_radius;
   //--- Value font size
   int               m_font_size;
   //--- Label font size
   int               m_label_font_size;
   //---
   int               m_digits;
   //--- Label
   string            m_label;
public:
                     CCircleSimple(void);
                    ~CCircleSimple(void);
   //--- Set and get background color
   color             Color(void)                      { return(m_bg_color);            }
   void              Color(const color clr)           { m_bg_color=clr;                }
   //--- Set and get size
   int               Radius(void)                     { return(m_radius);              }
   void              Radius(const int r)              { m_radius=r;                    }
   //--- Set and get value font size
   int               FontSize(void)                   { return(m_font_size);           }
   void              FontSize(const int fontsize)     { m_font_size=fontsize;          }
   //--- Set and get label font size
   int               LabelSize(void)                  { return(m_label_font_size);    }
   void              LabelSize(const int fontsize)    { m_label_font_size=fontsize;   }
   //--- Set and get value font color
   color             FontColor(void)                  { return(m_font_color);          }
   void              FontColor(const color fontcolor) { m_font_color=fontcolor;        }
   //--- Set and get label font color
   color             LabelColor(void)                 { return(m_label_color);         }
   void              LabelColor(const color fontcolor){ m_label_color=fontcolor;       }
   //--- Set frame color and width
   void              BorderColor(const color clr)     { m_border_color=clr;            }
   void              BorderSize(const int border)     { m_border=border;               }
   //--- Set and get transparency
   uchar             Alpha(void)                      { return(m_transparency);        }
   void              Alpha(const uchar alpha)         { m_transparency=alpha;          }
   //--- Set and get label value
   string            Label(void)                      { return(m_label);               }
   void              Label(const string label)        { m_label=label;                 }
   //--- Create the indicator
   void              Create(string name,int x,int y);
   //--- Remove the indicator
   void              Delete(string name);
   //--- Set and get the indicator value
   void              NewValue(int value);
   void              NewValue(double value);
  };

The purpose of the variables and methods they are used in can be seen from the description. Let's consider in details the methods implementing the plotting of the indicator in the form, in which it is presented in Fig. 1. The first method we are going to consider is CreateCanvas(). As we can see in the listing, it has only three arguments. I found them the most important. Providing additional arguments is redundant, since this complicates the method implementation. Therefore, all other properties are put into separate methods. Consequently, all variables are initialized in the class constructor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCircleSimple::CCircleSimple(void) : m_bg_color(clrAliceBlue),
                                     m_border_color(clrRoyalBlue),
                                     m_font_color(clrBlack),
                                     m_label_color(clrBlack),
                                     m_transparency(255),
                                     m_border(5),
                                     m_radius(40),
                                     m_font_size(17),
                                     m_label_font_size(20),
                                     m_digits(2),
                                     m_label("label")
  {
  }

This is convenient because when you create an indicator of that type, you just need to create an instance of its class and use the CreateCanvas () method only. Before creating, you can specify only the properties you want to change. When constructing complex graphical objects using the CCanvas library, keep in mind that methods implementing primitives are drawn sequentially and in layers. This has much in common with an actual canvas. First, artists usually draw a background, then they portray objects which in turn are followed by details, etc. 

//+------------------------------------------------------------------+
//| Create the indicator                                             |
//+------------------------------------------------------------------+
void CCircleSimple::Create(string name,int x,int y)
  {
   int r=m_radius;
//--- Modify the indicator location relative to the radius
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
   if(m_border>0)
      m_canvas.FillCircle(r,r,r,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

We will not dwell on the method implementation details. Let's highlight some basics only:

  • First, adjust the location of the graphical resource relative to the actual indicator size, so that it does not go beyond the main chart.
  • Then, use the setting and work with the name, size and coordinate methods. The foundation is created from the CCanvasBase base class.
  • When implementing the frame, we used the superimposing principle described above. In particular, we have created the two circles: the first one (filled) and the second one with its radius lesser than the first circle's one by the value of the frame width.
  • Numerical value and description elements are created above these objects.

Next, let's consider the NewValue() method that implements the display of the indicator's numerical value updating in real time:

//+------------------------------------------------------------------+
//| Set and update the indicator value                               |
//+------------------------------------------------------------------+
void CCircleSimple::NewValue(int value)
  {
   int r=m_radius;
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,IntegerToString(value),ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

In order for the indicator's numerical value to update correctly, we need to re-draw the three objects (background and text elements) in the same order as when creating them. Therefore, the background is re-drawn in the NewValue() method followed by value and description text elements.

Now, it is time to check the implementation of the circular indicator in the MetaTrader 5 terminal. To do this, let's create an empty indicator, include the CustomGUI.mqh file in it and create two instances of the CCircleSimple class:

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSimple ind1,ind2;

Then, in the indicator initialization, take the standard indicators having the values that are to be used in circular indicators:

//---
int InpInd_Handle,InpInd_Handle1;
double adx[],rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---- get the indicator handle
   InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,10);
   InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
   ArraySetAsSeries(adx,true);
   ArraySetAsSeries(rsi,true);

Let's define some properties of circular indicators for illustrative purposes and create them:

//---
   ind1.Radius(60);
   ind1.Label("ADX");
   ind1.Color(clrWhiteSmoke);
   ind1.LabelColor(clrBlueViolet);
   ind1.Create("adx",100,100);
//---
   ind2.Radius(55);
   ind2.BorderSize(8);
   ind2.FontSize(22);
   ind2.LabelColor(clrCrimson);
   ind2.BorderColor(clrFireBrick);
   ind2.Label("RSI");
   ind2.Create("rsi",250,100);

Now, we only need to enter the current numerical values ​​of standard indicators into the created ones in the indicator's calculation part:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,2,adx)<=0 || 
      CopyBuffer(InpInd_Handle1,0,0,2,rsi)<=0
      )
      return(0);
   ind1.NewValue(adx[0]);
   ind2.NewValue(rsi[0]);
//--- return value of prev_calculated for next call
   return(rates_total);
  }

The only thing left to do is writing the methods for deleting graphical resources in the deinitialization, so that graphical objects are deleted correctly when the indicator is removed.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind1.Delete();
   ind2.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

The results are presented in Fig. 2. As we can see, the indicators differ significantly from each other in many parameters.

Fig. 2. Sample circular indicators

CCircleArc indicator class

We have considered a class providing the simplest form of implementing a circular indicator. Let's slightly complicate the task and create a class having an additional graphical display element besides the numerical one – an arc. The structure of the basic elements of the indicator with an arc indication is provided in Fig. 3.


Fig. 3. Basic structure of the circular indicator with the arc indication

As we can see, this type features the Arc indicator element. To implement the arc indication, we can use the method that draws the filled ellipse sector and is called Pie(). Of all the overloads of this method, I decided to use the following:

void  Pie( 
   int         x,       // X coordinate of the ellipse center 
   int         y,       // Y coordinate of the ellipse center 
   int         rx,      // ellipse radius X coordinate 
   int         ry,      // ellipse radius Y coordinate 
   int         fi3,     // ray angle from the ellipse center setting the first arc border 
   int         fi4,     // ray angle from the ellipse center setting the second arc border 
   const uint  clr,     // line color 
   const uint  fill_clr // filling color 
   );

fi3 sets the first arc border and is used as the beginning of the arc scale, while fi4 can dynamically change depending on the numerical value passed to our indicator. Create the CircleArc.mqh file in the Indicators folder and add the following string in the CustomGUI.mqh file:

#include "Indicators\CircleArc.mqh"

Thus, we add the future class to the overall list of inclusions of all indicators. Define the set of common properties and methods. They are not different from the previous class methods, however it is worth considering the ENUM_ORIENTATION enumeration. It has two values (VERTICAL and HORIZONTAL) and defines the location of the first arc border of the arc scale. If the value is vertical, the arc starts at 90 degrees and if it is horizontal — at zero. The indication is counted counter-clockwise.

//+------------------------------------------------------------------+
//|                                                    CircleArc.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Circular indicator with numerical value and arc indication       |
//+------------------------------------------------------------------+
class CCircleArc : public CCanvasBase
  {
private:
   //--- Indicator background color
   color             m_bg_color;
   //--- Indicator arc color
   color             m_arc_color;
   //--- Indication area color
   color             m_area_color;
   //--- Indicator description color 
   color             m_label_color;
   //--- Indicator value color
   color             m_value_color;
   //--- Indicator transparency
   uchar             m_transparency;
   //--- Indicator size
   int               m_radius;
   //--- Indicator scale width 
   int               m_scale_width;
   //--- Description font size
   int               m_label_font_size;
   //--- Value font size
   int               m_value_font_size;
   //--- Indicator description
   string            m_label_value;
   //--- Indicator scale orientation type
   ENUM_ORIENTATION  m_orientation;
public:
                     CCircleArc(void);
                    ~CCircleArc(void);
   //--- Set and get the indicator background color
   color             BgColor(void)                                   { return(m_bg_color);            }
   void              BgColor(const color clr)                        { m_bg_color=clr;                }
   //--- Set and get the indicator arc color
   color             ArcColor(void)                                  { return(m_arc_color);           }
   void              ArcColor(const color clr)                       { m_arc_color=clr;               }
   //--- Set and get the indication area color
   color             AreaColor(void)                                 { return(m_area_color);          }
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   //--- Set and get the indicator description color 
   color             LabelColor(void)                                { return(m_label_color);         }
   void              LabelColor(const color clr)                     { m_label_color=clr;             }
   //--- Set and get the indicator value color
   color             ValueColor(void)                                { return(m_value_color);         }
   void              ValueColor(const color clr)                     { m_value_color=clr;             }
   //--- Set and get the indicator transparency 
   uchar             Alpha(void)                                     { return(m_transparency);        }
   void              Alpha(const uchar trn)                          { m_transparency=trn;            }
   //--- Set and get the indicator size
   int               Radius(void)                                    { return(m_radius);              }
   void              Radius(const int r)                             { m_radius=r;                    }
   //--- Set and get the indicator scale width
   int               Width(void)                                     { return(m_scale_width);         }
   void              Width(const int w)                              { m_scale_width=w;               }
   //--- Set and get the description font size
   int               LabelSize(void)                                 { return(m_label_font_size);     }
   void              LabelSize(const int sz)                         { m_label_font_size=sz;          }
   //--- Set and get the value font size
   int               ValueSize(void)                                 { return(m_value_font_size);     }
   void              ValueSize(const int sz)                         { m_value_font_size=sz;          }
   //--- Set and get the indicator description
   string            LabelValue(void)                                { return(m_label_value);         }
   void              LabelValue(const string str)                    { m_label_value=str;             }
   //--- Set and get the scale orientation type
   void              Orientation(const ENUM_ORIENTATION orietation)  { m_orientation=orietation;      }
   ENUM_ORIENTATION  Orientation(void)                               { return(m_orientation);         }
   //--- Create the indicator
   void              Create(string name,int x,int y);
   //--- Remove the indicator
   void              Delete(void);
   //--- Set and update the indicator value
   void              NewValue(double value,double maxvalue,int digits);
  };

Let's have a closer look at the Create() and NewValue() methods, since their implementation is not provided. Keep in mind that created objects are superimposed on each other in layers, so the sequence of creating them is as follows:

  1. Arc indicator background. As a filled circle.
  2. Arc indicator. As a filled ellipse sector.
  3. Text background. As a filled circle.
  4. Indicator numerical value and its description.

Implementation following the established sequence is displayed below:

//+------------------------------------------------------------------+
//| Create the indicator                                             |
//+------------------------------------------------------------------+
void CCircleArc::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Set the initial indicator location
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
   b+=90*M_PI/180;
//--- Correct the indicator location relative to the radius
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

While setting the initial values of the first and second arc borders (i.e., the indicator's start and its current value), we consider its initial orientation and the fact that corners for the Pie() method are set in radians. As an example, the default current value corresponding to the b variable is set to 90, then it is converted to radians, otherwise, the display is incorrect. The NewValue() method is implemented using the same principles:

//+------------------------------------------------------------------+
//| Set and update the indicator value                               |
//+------------------------------------------------------------------+
void CCircleArc::NewValue(double value,double maxvalue,int digits=2)
  {
   int r=m_radius;
   double a,b,result;
//--- Checks for out of range
   value=(value>maxvalue)?maxvalue:value;
   value=(value<0)?0:value;
//--- Set the initial indicator location
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
//---
   result=value*(360.0/maxvalue);
   b+=result*M_PI/180;
   if(b>=2*M_PI)
      b=2*M_PI-0.02;
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

After checking and setting the initial positions, the angle is re-calculated in radians of the second arc border. After that, all indicator elements are redrawn with new values. As an application example, we have developed a simple spread indicator, which changes the indication arc scale color when reaching the threshold value.

//+------------------------------------------------------------------+
//|                                                                  |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//+------------------------------------------------------------------+
//|  Indicator inputs                                                |
//+------------------------------------------------------------------+
input double         maxspr=12;                 // Maximum value
input int            stepval=8;                 // Threshold value
input color          stepcolor=clrCrimson;      // Threshold value color
//---
CCircleArc arc;
double spr;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   arc.Orientation(HORIZONTAL);
   arc.LabelValue("Spread");
   arc.Create("spread_ind",100,100);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   spr=double(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD));
   if(spr>=stepval)
      arc.ArcColor(stepcolor);
   else
      arc.ArcColor(clrForestGreen);
//---
   arc.NewValue(spr,maxspr,0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   arc.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

As we can see from the listing, the implementation using the CCircleArc class is quite simple to create and configure. Keep in mind that the properties should be changed and configured only before the indicator is created (Create() method) or before using the NewValue() method. Since these are the methods, in which the indicator elements are re-drawn and changed properties are applied. Spread indicator is shown in Fig. 4.

Fig. 4. Sample circular indicator with the arc indication


CCircleSection indicator class

Unlike a simple arc indication, the sectional one looks as if it has labels separating equal intervals. When creating a layout of the indicator of this type, I have decided to make ten sections and add a new element – the inner frame. The basic structure with the arc sectional indication is presented in Fig. 5.


Fig. 5. Basic structure of the circular indicator with the arc sectional indication

Displaying the arc indication with sections is completely different from the previous class, which is marked with one-time use of the Pie() method of the CCanvas library. In this method, the location of the second ellipse sector arc is modified when the value changes. Here, this method is applied ten times, and the arc locations remain static. Simply put, the indicator features 10 filled ellipse sectors following each other in a circle. Certain sections changing their colors serve as a visual indication.

Create the CircleSection.mqh file in the Indicators folder and include it in the CustomUI.mqh file like all previous ones. Now, its list looks as follows:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"

Since the indicator classes are presented in the order of complicating their construction and functionality, I will nor provide their common properties and methods. Instead, let's focus our attention on the ones that add new functionality and have a different implementation. Therefore, in the current class, most methods are similar to the previous one with the following additions:

//+------------------------------------------------------------------+
//|                                                CircleRounded.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+--------------------------------------------------------------------------+
//| Circular indicator with numerical value and circular sectional indication|
//+--------------------------------------------------------------------------+
class CCircleSection : public CCanvasBase
  {
private:
   //--- Color of the scale active/inactive division
   color             m_scale_color_on;
   color             m_scale_color_off;
public:
   //--- Set the color of the active/inactive scale division
   void              ScaleColorOn(const color clr)                   { m_scale_color_on=clr;          }
   void              ScaleColorOff(const color clr)                  { m_scale_color_off=clr;         }
   //--- Create the indicator
   void              Create(string name,int x,int y);
   //--- Set and update the indicator value
   void              NewValue(double value,double maxvalue,int digits);
  };

The Create() and NewValue() methods are left since their implementation here is different from the previous ones. As we can see from the listing below, correction of the location relative to the radius is followed by the block for creating ten sections using a cycle. The starting point is considered: horizontal – from zero degrees, vertical – from 90 degrees.

//+------------------------------------------------------------------+
//| Create the indicator                                             |
//+------------------------------------------------------------------+
void CCircleSection::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Modify the indicator location relative to the radius
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Indicator sections
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(m_scale_color_off,m_transparency));
     }
//--- Frame and background
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//--- Description
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//--- Numerical value
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

As we have already said, in addition to changing the numerical value, the visual indication is represented in the form of a change in the sections color. The change should be sequential and based on the current and highest specified value. For example, if the numerical value is 20 and the maximum one is 100, two sections will change the color. But if the maximum one is 200, only one section changes its color. Let us consider in more detail how this is done in the NewValue() method:

//+------------------------------------------------------------------+
//| Set and update the indicator value                               |
//+------------------------------------------------------------------+
void CCircleSection::NewValue(double value,double maxvalue=100,int digits=2)
  {
//---
   int r=m_radius;
   double a,b;
   color clr;
//---
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      clr=(maxvalue/10*(i+1)<=value)?m_scale_color_on:m_scale_color_off;
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(clr,m_transparency));
     }
//---
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

As we can see in the listing above, the implementation looks quite simple. The current value is checked relative to the maximum one. If it exceeds or becomes equal to the maximum value divided by the number of sections and multiplied by the current cycle iteration, the section color is changed from non-active to active.

As an example of using the class, I have implemented the drawdown indicator that displays the ratio of the equity to the current account balance. Thus, we can visually manage the drawdown on the account instead of tracking the numbers in the platform. The listing of the implementation of such an indicator is presented below.

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSection ind;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ind.Radius(70);
   ind.LabelValue("Drawdown");
   ind.Create("drawdown",150,150);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ind.NewValue(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE));
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Do not forget to use the Delete() method in deinitialization to delete the indicator from the chart correctly.


CLineGraph linear graph class

To create a linear graph using custom graphics, you should first determine the elements of the graph itself, namely:

  • Graph backdrop. It is to have two features: size (which also the size of the object itself) and color.
  • Graph frame. Only color. The element consists of the two filled rectangles. The coordinates of the last one (which also serves as the graph background) are shifted by one providing the frame effect.
  • Graph background. Only color. 
  • Graph axes. Only color. Implemented the same way as the graph frame by overlaying two rectangles, where the upper one shifts the coordinates by one.
  • Axis scale. Only color. Set of lines implemented using the LineHorizontal() methods for Y axis and LineVertical() for Х axis.
  • Axis value. Color and font size properties. Set of text objects using the TextOut() method.
  • Grid. Only color. The LineAA() method is selected for implementing the grid, since it allows to set the line style.
  • Graph. Only color. The element consists of line and filled circle — the FillCircle() method. 

Only customizable properties are specified in the elements description. Fig. 6 displays the graph elements structure. 


Fig. 6. Basic linear graph structure

First, create the LineGraph.mqh file in the Indicators folder and include it in the CustomGUI.mqh file:

//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"
#include "Indicators\LineGraph.mqh"
//+------------------------------------------------------------------+

Then, create the CLineGraph class in the LineGraph.mqh file and make CCanvasBase the basic one for it, like the previous ones. Define all properties and methods described above in the basic structure of the linear graph. 

//+------------------------------------------------------------------+
//|                                                    LineGraph.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Linear graph                                                     |
//+------------------------------------------------------------------+
class CLineGraph : public CCanvasBase
  {
private:
   //--- Graph backdrop color
   color             m_bg_color;
   //--- Graph background color
   color             m_bg_graph_color;
   //--- Graph frame color
   color             m_border_color;
   //--- Graph axes values color
   color             m_axis_color;
   //--- Graph grid color
   color             m_grid_color;
   //--- Graph axes scale color
   color             m_scale_color;
   //--- Graph line color
   color             m_graph_color;
   //--- Graph size
   int               m_x_size;
   int               m_y_size;
   //--- Graph indent from the backdrop edge
   int               m_gap;
   //--- Value font size on graph axes
   int               m_font_size;
   //--- Array of Y axis values
   int               m_x[];
   //--- Minimum and maximum Y axis value
   double            m_y_min;
   double            m_y_max;
   //--- Number of Y axis scales
   int               m_num_grid;
   //--- Graph transparency
   uchar             m_transparency;
public:
                     CLineGraph(void);
                    ~CLineGraph(void);
   //--- Set and get the graph backdrop color
   color             BgColor(void)                                   { return(m_bg_color);                  }
   void              BgColor(const color clr)                        { m_bg_color=clr;                      }
   //--- Set and get the graph background color
   color             BgGraphColor(void)                              { return(m_bg_graph_color);            }
   void              BgGraphColor(const color clr)                   { m_bg_graph_color=clr;                }
   //--- Set and get the graph frame color
   color             BorderColor(void)                               { return(m_border_color);              }
   void              BorderColor(const color clr)                    { m_border_color=clr;                  }
   //--- Set and get the color of the graph axes values
   color             AxisColor(void)                                 { return(m_axis_color);                }
   void              AxisColor(const color clr)                      { m_axis_color=clr;                    }
   //--- Set and get the graph grid color
   color             GridColor(void)                                 { return(m_grid_color);                }
   void              GridColor(const color clr)                      { m_grid_color=clr;                    }
   //--- Set and get the graph axes scale color
   color             ScaleColor(void)                                { return(m_scale_color);               }
   void              ScaleColor(const color clr)                     { m_scale_color=clr;                   }
   //--- Set and get the graph line color
   color             GraphColor(void)                                { return(m_graph_color);               }
   void              GraphColor(const color clr)                     { m_graph_color=clr;                   }
   //--- Set and get the graph size
   int               XGraphSize(void)                                { return(m_x_size);                    }
   void              XGraphSize(const int x_size)                    { m_x_size=x_size;                     }
   int               YGraphSize(void)                                { return(m_y_size);                    }
   void              YGraphSize(const int y_size)                    { m_y_size=y_size;                     }
   //--- Set and get the value font size on the graph axes
   int               FontSize(void)                                  { return(m_font_size);                 }
   void              FontSize(const int fontzise)                    { m_font_size=fontzise;                }
   //--- Set and get the raph indent from the backdrop edge
   int               Gap(void)                                       { return(m_gap);                       }
   void              Gap(const int g)                                { m_gap=g;                             }
   //--- Set and get the minimum and maximum Y axis value
   double            YMin(void)                                      { return(m_y_min);                     }
   void              YMin(const double ymin)                         { m_y_min=ymin;                        }
   double            YMax(void)                                      { return(m_y_max);                     }
   void              YMax(const double ymax)                         { m_y_max=ymax;                        }
   //--- Set and get the number of Y axis scales
   int               NumGrid(void)                                   { return(m_num_grid);                  }
   void              NumGrid(const int num)                          { m_num_grid=num;                      }
   //--- Create the graph
   void              Create(string name,int x,int y);
   //--- Delete the graph 
   void              Delete(void);
   //--- Set the inputs array
   void              SetArrayValue(double &data[]);
private:
   //---
   void              VerticalScale(double min,double max,int num_grid);
   void              HorizontalScale(int num_grid);
  };

Let's have a closer look at the implementation of the methods that are not specified in the above listing. The Create() method is used to create a graphical resource, which is then placed on a symbol chart. The two private methods for setting the vertical and horizontal scale are used here as well: either based on initialized settings in the class constructor or using the class methods before creation.

//+------------------------------------------------------------------+
//| Create the graph                                                 |
//+------------------------------------------------------------------+
void CLineGraph::Create(string name,int x,int y)
  {
//--- Modify the indicator location relative to
   x=(x<m_x_size/2)?m_x_size/2:x;
   y=(y<m_y_size/2)?m_y_size/2:y;
   Name(name);
   X(x);
   Y(y);
   XSize(m_x_size);
   YSize(m_y_size);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Create the graph frame and backdrop
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Create axes and graph background
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Create axis scales and their values
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(5);
   m_canvas.Update();
  }

In order to display a linear chart, it is reasonable to use a data array as a basis, since MetaTrader often applies arrays for copying values from indicator buffers. When implementing the SetArrayValue() graph display method, the data array is used as a basis (method arguments), where the values corresponding to the array size are set on the X axis and the array values — on the Y axis. 

//+------------------------------------------------------------------+
//| Set the input array                                              |
//+------------------------------------------------------------------+
void CLineGraph::SetArrayValue(double &data[])
  {
   int y0,y1;
//---  Create the graph frame and backdrop
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Create the axes and the graph background
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- If the maximum data array value exceeds the upper Y border, the axis is scaled.
   if(data[ArrayMaximum(data)]>m_y_max)
      m_y_max=data[ArrayMaximum(data)];
//--- Create the axis scales and their values
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(ArraySize(data));
//--- Create the graph by the data array
   for(int i=0;i<ArraySize(data)-1;i++)
     {
      y0=int((YSize()-2*m_gap)*(1-data[i]/m_y_max));
      y0=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]);
      y0=(y0<m_gap)?m_gap:y0;
      y1=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i+1]);
      y1=(y1<m_gap)?m_gap:y1;
      m_canvas.LineAA(m_x[i+1],y0,m_x[i+2],y1,ColorToARGB(m_graph_color,m_transparency),STYLE_SOLID);
      m_canvas.FillCircle(m_x[i+1],y0,2,ColorToARGB(m_graph_color,m_transparency));
      m_canvas.FillCircle(m_x[i+2],y1,2,ColorToARGB(m_graph_color,m_transparency));
     }
   m_canvas.Update();
  }

As an example of the class application, I have developed a graph based on the selected oscillator values and compared with the original display. The standard RSI has been used for that purpose. The listing below shows implementing of construction based on the buffer values using the ClineGraph class.

//+------------------------------------------------------------------+
//|                                                            4.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CLineGraph ind;
int InpInd_Handle;
double rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---- get the indicator handle
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
//---
   ind.NumGrid(10);
   ind.YMax(100);
   ind.Create("rsi",350,250);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,10,rsi)<=0)
      return(0);
   ind.SetArrayValue(rsi);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Now, set the original indicator with the same parameters and compare the obtained results in Fig. 7:

Fig. 7. Comparing the implementation of constructions using the CLineGraph class and original RSI

Note that the array the data is written to using the CopyBuffer method should have the original form. Unlike time series, no indexation change is required.


Conclusion

In this article, we have dealt with step-by-step creation of custom indicators of an arbitrary form with the help of the CСanvas custom graphics library. We have also analyzed examples of various complexity – from a simple circular indicator consisting of only four elements up to a circular one with sectional indication. Besides, we have considered the implementation of the already known indicator types in a new way. As we have seen, the options are not limited by a rigidly defined set of properties. The implementation examples have been structured as classes, i.e. they are a supplement or extension of the already complete CCanvas class. 

The attached archive contains all the listed files, which are located in the appropriate folders. For a correct operation, you should save the MQL5 folder to the terminal's root directory. 

Programs used in the article:

#
 Name
Type
Description
1
CanvasBase.mqh Library  Base custom graphics class
2
CustomGUI.mqh Library  File of all the library classes include list 
3 CircleArc.mqh Library  Contains the CCircleArc indicator class
4 CircleSection.mqh Library  Contains the CCircleSection indicator class
5 CircleSimple.mqh Library  Contains the CCircleSimle indicator class
LineGraph.mqh Library  Contains the CLineGraph linear graph class
7 1.mq5 Indicator  Sample implementation of the CCirleSimple class
8 2.mq5 Indicator  Sample implementation of the CCirleArc class
9 3.mq5 Indicator  Sample implementation of the CCirleSection class
 10 4.mq5 Indicator  Sample implementation of the CLineGraph class