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);
|
|
}
|
|
//+------------------------------------------------------------------+
|