mql-for-begginers/Include/Graphics/Axis.mqh
2025-07-22 18:30:17 +03:00

260 lines
12 KiB
MQL5

//+------------------------------------------------------------------+
//| 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)<m_zero_lever)
m_min=0;
//--- repeat the zero-lever test for cases where the m_max is less than zero
if(m_max<0 && MathAbs(m_max/(m_max-m_min))<m_zero_lever)
m_max=0;
//--- calculate the new m_step size
double target_step=(m_default_step!=0) ? m_default_step : m_max-m_min;
//--- Calculate the m_step size based on target steps
m_step=CalcStepSize(m_max-m_min,target_step);
double labels=MathCeil((m_max-m_min)/m_step);
if(m_max_labels<labels && m_max_labels>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);
}
//+------------------------------------------------------------------+