//+------------------------------------------------------------------+ //| Axis.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ typedef string(*DoubleToStringFunction)(double,void*); //--- Various axis types enum ENUM_AXIS_TYPE { AXIS_TYPE_DOUBLE, AXIS_TYPE_DATETIME, AXIS_TYPE_CUSTOM, }; //+------------------------------------------------------------------+ //| Class CAxis | //| Usage: class for create axes on a two-dimensional graphics | //+------------------------------------------------------------------+ class CAxis { private: double m_min; double m_max; double m_step; uint m_clr; string m_name; int m_name_size; int m_values_size; int m_values_width; string m_values_format; string m_values_fontname; uint m_values_fontflags; uint m_values_fontangle; bool m_auto_scale; double m_zero_lever; // this number is used to determine when an axis scale range should be extended to include the zero value. double m_default_step; // length of the default step double m_max_labels; // the maximum number of marks double m_min_grace; // "grace" value applied to the minimum data range double m_max_grace; // "grace" value applied to the maximum data range int m_values_dt_mode; DoubleToStringFunction m_values_func; void *m_values_cbdata; ENUM_AXIS_TYPE m_type; public: CAxis(void); ~CAxis(void); //--- properties double Step(void) const { return(m_step); } double Min(void) const { return(m_min); } void Min(const double min) { m_min=min; } double Max(void) const { return(m_max); } void Max(const double max) { m_max=max; } string Name(void) const { return(m_name); } void Name(const string name) { m_name=name; } ENUM_AXIS_TYPE Type(void) const { return(m_type); } void Type(ENUM_AXIS_TYPE type) { m_type=type; } //--- default properties uint Color(void) const { return(m_clr); } void Color(const uint clr) { m_clr=clr; } bool AutoScale(void) const { return(m_auto_scale); } void AutoScale(const bool auto) { m_auto_scale=auto; } int ValuesSize(void) const { return(m_values_size); } void ValuesSize(const int size) { m_values_size=size; } int ValuesWidth(void) const { return(m_values_width); } void ValuesWidth(const int width) { m_values_width=width; } string ValuesFormat(void) const { return(m_values_format); } void ValuesFormat(const string format) { m_values_format=format; } int ValuesDateTimeMode(void) const { return(m_values_dt_mode); } void ValuesDateTimeMode(const int mode) { m_values_dt_mode=mode; } DoubleToStringFunction ValuesFunctionFormat(void) const { return(m_values_func); } void ValuesFunctionFormat(DoubleToStringFunction func) { m_values_func=func; } void *ValuesFunctionFormatCBData(void) const { return(m_values_cbdata); } void ValuesFunctionFormatCBData(void *cbdata) { m_values_cbdata=cbdata; } string ValuesFontName(void) const { return(m_values_fontname); } void ValuesFontName(const string fontname) { m_values_fontname=fontname; } uint ValuesFontAngle(void) const { return(m_values_fontangle); } void ValuesFontAngle(const uint fontangle) { m_values_fontangle=fontangle; } uint ValuesFontFlags(void) const { return(m_values_fontflags); } void ValuesFontFlags(const uint fontflags) { m_values_fontflags=fontflags; } int NameSize(void) const { return(m_name_size); } void NameSize(const int size) { m_name_size=size; } double ZeroLever(void) const { return(m_zero_lever); } void ZeroLever(const double value) { m_zero_lever=value; } double DefaultStep(void) const { return(m_default_step); } void DefaultStep(const double value) { m_default_step=value; } double MaxLabels(void) const { return(m_max_labels); } void MaxLabels(const double value) { m_max_labels=value; } double MinGrace(void) const { return(m_min_grace); } void MinGrace(const double value) { m_min_grace=value; } double MaxGrace(void) const { return(m_max_grace); } void MaxGrace(const double value) { m_max_grace=value; } //--- method for auto scaling void SelectAxisScale(void); private: //--- additional methods for axis autoscaling void ExtensionBoundaries(void); double CalcStepSize(const double range,const double steps); double Mod(const double x,const double y); double CalcBoundedStepSize(const double range,const double max_steps); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAxis::CAxis(void) : m_auto_scale(true), m_zero_lever(0.25), m_default_step(25.0), m_max_labels(15), m_min_grace(0.01), m_max_grace(0.01), m_clr(clrBlack), m_name_size(0), m_values_size(12), m_values_fontname("arial"), m_values_fontflags(0), m_values_fontangle(0), m_values_func(NULL), m_values_cbdata(NULL), m_values_dt_mode(TIME_MINUTES), m_type(AXIS_TYPE_DOUBLE), m_values_width(30) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAxis::~CAxis(void) { } //+------------------------------------------------------------------+ //| Rounds a maximum, minimum values and defines step size. | //+------------------------------------------------------------------+ void CAxis::SelectAxisScale(void) { if(m_min>m_max) m_max=m_min; if(!m_auto_scale) { if(m_max<=m_min) ExtensionBoundaries(); m_step=((m_max-m_min)>m_default_step && m_default_step>0) ? m_default_step : m_max-m_min; return; } ExtensionBoundaries(); //--- test for trivial condition of range = 0 and pick a suitable default if(m_max-m_min<1.0e-30) { m_max=m_max+0.2 *(m_max==0 ? 1.0 : MathAbs(m_max)); m_min=m_min-0.2 *(m_min==0 ? 1.0 : MathAbs(m_min)); } //--- this is the zero-lever test. If m_min is within the zero lever fraction of the data range,then use zero. if(m_min>0 && m_min/(m_max-m_min)0) m_step=CalcBoundedStepSize(m_max-m_min,m_max_labels); else m_step=CalcBoundedStepSize(m_max-m_min,labels); //--- calculate the scale minimum m_min=m_min-Mod(m_min,m_step); //--- calculate the scale maximum m_max=Mod(m_max,m_step)==0.0 ? m_max : m_max+m_step-Mod(m_max,m_step); } //+------------------------------------------------------------------+ //| Expands the boundaries to the left and right. | //+------------------------------------------------------------------+ void CAxis::ExtensionBoundaries(void) { double range=m_max-m_min; //--- do not let the grace value extend the axis below zero when all the values were positive if(m_min<0 || m_min-m_min_grace*range>=0.0) m_min=m_min-m_min_grace*range; //--- do not let the grace value extend the axis above zero when all the values were negative if(m_max>0 || m_max+m_max_grace*range<=0.0) m_max=m_max+m_max_grace*range; //--- calculate new min and max values if they equal if(m_max==m_min) { if(MathAbs(m_max)>1e-100) { m_max *= (m_min < 0 ? 0.95 : 1.05 ); m_min *= (m_min < 0 ? 1.05 : 0.95 ); } else { m_max = 1.0; m_min = -1.0; } } //--- } //+------------------------------------------------------------------+ //| Calculate a m_step size based on a data range. | //+------------------------------------------------------------------+ double CAxis::CalcStepSize(const double range,const double steps) { //--- calculate an initial guess at m_step size double temp=range/steps; //--- get the magnitude of the m_step size double mag=MathFloor(MathLog10(temp)); double magPow=MathPow(10.0,mag); //--- calculate most significant digit of the new m_step size double magMsd=NormalizeDouble(temp/magPow+.5,0); //--- promote the MSD to either 1, 2, or 5 if(magMsd>5.0) magMsd=10.0; else if(magMsd>2.0) magMsd=5.0; else if(magMsd>1.0) magMsd=2.0; //--- return step return(magMsd * magPow); } //+------------------------------------------------------------------+ //| Calculate the modulus (remainder) in a safe manner so that divide| //| by zero errors are avoided | //+------------------------------------------------------------------+ double CAxis::Mod(const double x,const double y) { //--- check if(y==0) return(0); //--- calculate modulus return (x>0)? MathMod(x,y): MathMod(x,y)+y; } //+------------------------------------------------------------------+ //| Calculate a m_step size based on a data range, limited to a max | //| number of steps. | //+------------------------------------------------------------------+ double CAxis::CalcBoundedStepSize(const double range,const double max_steps) { //--- calculate an initial guess at m_step size double temp=range/max_steps; //--- get the magnitude of the m_step size double mag=MathFloor(MathLog10(temp)); double magPow=MathPow((double) 10.0,mag); //--- calculate most significant digit of the new m_step size double magMsd=MathCeil(temp/magPow); //--- promote the MSD to either 1, 2, or 5 if(magMsd>5.0) magMsd=10.0; else if(magMsd>2.0) magMsd=5.0; else if(magMsd>1.0) magMsd=2.0; //--- return step return(magMsd * magPow); } //+------------------------------------------------------------------+