260 lines
12 KiB
MQL5
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);
| |||
}
| |||
//+------------------------------------------------------------------+
|