//+------------------------------------------------------------------+ //| FlameCanvas.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "Canvas.mqh" #include //+------------------------------------------------------------------+ //| Gradient descriptors | //+------------------------------------------------------------------+ struct GRADIENT_COLOR { uint clr; // color in ARGB format uint pos; // position of color in percentage of gradient range }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct GRADIENT_SIZE { uint size; // width of gradient fill in percentage of base fill uint pos; // position of color in percentage of gradient length }; //+------------------------------------------------------------------+ //| Class CFlameCanvas | //| Usage: generates flame | //+------------------------------------------------------------------+ class CFlameCanvas : public CCanvas { private: //--- parameters uint m_bar_gap; uint m_bar_width; uint m_chart_scale; double m_chart_price_min; double m_chart_price_max; ENUM_TIMEFRAMES m_timeframe; string m_symbol; int m_future_bars; int m_back_bars; int m_rates_total; uint m_palette[256]; // flame palette uchar m_flame[]; // buffer for calculation of flame uint m_time_redraw; uint m_delay; // bool m_resize_flag; // int m_tick_cnt; //--- flame parameters datetime m_tb1; double m_pb1; datetime m_te1; double m_pe1; datetime m_tb2; double m_pb2; datetime m_te2; double m_pe2; //--- equation parameters for flame int m_cloud_axis[100]; double m_a1; double m_b1; double m_a2; double m_b2; int m_xb1; int m_yb1; int m_xe1; int m_ye1; int m_xb2; int m_yb2; int m_xe2; int m_ye2; public: CFlameCanvas(void); ~CFlameCanvas(void); //--- create bool FlameCreate(const string name,const datetime time,const int future_bars,const int back_bars=0); void RatesTotal(const int value); //--- setting void PaletteSet(uint clr=0xFF0000); //--- draw void FlameDraw(const double &prices[],const int width,const int lenght); void FlameSet(datetime xb1,double yb1,datetime xe1,double ye1,datetime xb2,double yb2,datetime xe2,double ye2); //--- event handler void ChartEventHandler(const int id,const long &lparam,const double &dparam,const string &sparam); protected: bool Resize(void); void ChartScale(void); void FlameSet(void); void CloudDraw(const double &prices[],const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[],const uchar t_level=255,const bool custom_gradient=true); void FlameDraw(const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[]); void GradientVertical(const int xb,const int xe,const int yb1,const int ye1,const int yb2,const int ye2,const GRADIENT_COLOR &gradient[]); void GradientVerticalLine(int x,int y1,int y2,const GRADIENT_COLOR &gradient[]); void GradientVerticalLineMonochrome(int x,int y1,int y2,uint clr1,uint clr2); void FlameCreate(void); void FlameCalculate(void); void Delay(const uint value); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CFlameCanvas::CFlameCanvas(void) : m_bar_gap(16), m_bar_width(8), m_chart_scale(1), m_chart_price_min(0.0), m_chart_price_max(0.0), m_timeframe(PERIOD_CURRENT), m_symbol(NULL), m_future_bars(0), m_back_bars(0), m_rates_total(0), m_time_redraw(0), m_delay(50), m_tb1(0), m_pb1(0), m_te1(0), m_pe1(0), m_tb2(0), m_pb2(0), m_te2(0), m_pe2(0) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CFlameCanvas::~CFlameCanvas(void) { Destroy(); } //+------------------------------------------------------------------+ //| Creates dynamic resource with object | //+------------------------------------------------------------------+ bool CFlameCanvas::FlameCreate(const string name,const datetime time,const int future_bars,const int back_bars) { //--- get chart parameters ChartScale(); //--- create int width =(int)m_bar_gap*(future_bars+back_bars); int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS); if(!CreateBitmap(0,0,name,time-back_bars*PeriodSeconds(),m_chart_price_max,width,height,COLOR_FORMAT_ARGB_NORMALIZE)) return(false); ArrayResize(m_flame,width*height); //--- save parameters m_future_bars=future_bars; m_back_bars =back_bars; //--- settings PaletteSet(); m_timeframe =Period(); m_symbol =Symbol(); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Resize | //+------------------------------------------------------------------+ bool CFlameCanvas::Resize(void) { int x,y; //--- get limits double min=ChartGetDouble(0,CHART_PRICE_MIN); double max=ChartGetDouble(0,CHART_PRICE_MAX); if(m_chart_price_max!=max) { //--- move object ObjectSetDouble(0,m_objname,OBJPROP_PRICE,0,max); } //--- check if(m_chart_price_min==min && m_chart_price_max==max) return(false); m_chart_price_min=min; m_chart_price_max=max; //--- grt size ChartTimePriceToXY(0,0,m_tb1,min,x,y); int width =(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS)-x; int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS); //--- resize if(width80) g+=dg; if(i>160) b+=db; } } //+------------------------------------------------------------------+ //| Draws the flame | //+------------------------------------------------------------------+ void CFlameCanvas::FlameDraw(const double &prices[],const int width,const int lenght) { static GRADIENT_SIZE sword[]={{100,0},{150,70},{0,100}}; static GRADIENT_COLOR flame[]={{0x00,0},{0x7F7F7F,12},{0xCCCCCC,30},{0xFFFFFF,45},{0xFFFFFF,55},{0xCCCCCC,70},{0x7F7F7F,88},{0x00,100}}; //--- draw CloudDraw(prices,width,lenght,sword,flame); //--- copy flame buffer FlameCalculate(); //--- start timer EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFlameCanvas::FlameDraw(const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[]) { //--- check int total=ArraySize(m_cloud_axis); if(total<2) return; if(total>lenght) total=lenght; //--- draw int xb,xe; // coordinates of the segment int ybm,yem; // coordinates of the center line int yb1,ye1; // coordinates of the first line int yb2,ye2; // coordinates of the second line //--- for implementation of variable width int w_total=ArraySize(size); if(w_total<2) return; int w_i =0; int w_is=(int)size[w_i].pos*total/100; int w_ie=(int)size[w_i+1].pos*total/100; double w =size[w_i].size*width/100; double dw =(size[w_i+1].size*width/100-w)/(w_ie-w_is); //--- draw from left to right xb=0; ybm=m_cloud_axis[0]; yb1=ybm-(int)(w/2); yb2=ybm+(int)(w/2); //--- draw for(int i=1;i=m_width) break; yb1=ye1; yb2=ye2; while(i>=w_ie-1 && i!=total-1) { w_i++; w_is=(int)size[w_i].pos*total/100; w_ie=(int)size[w_i+1].pos*total/100; w =size[w_i].size*width/100; if(w_ie==w_is) { //--- for "instant" resize dw=size[w_i+1].size*width/100-w; w+=dw; ye1=yem-(int)(w/2); ye2=yem+(int)(w/2); //--- draw the segment of 'cloud' GradientVertical(xb,xe,yb1,ye1,yb2,ye2,gradient); yb1=ye1; yb2=ye2; } else { dw=(size[w_i+1].size*width/100-w)/(w_ie-w_is); break; } } } //--- copy flame buffer FlameCalculate(); } //+------------------------------------------------------------------+ //| Sets parameters of the flame and starts to draw | //+------------------------------------------------------------------+ void CFlameCanvas::FlameSet(void) { m_a1=m_bar_gap*((m_ye1-m_yb1)/((double)m_xe1-m_xb1)); m_a2=m_bar_gap*((m_ye2-m_yb2)/((double)m_xe2-m_xb2)); } //+------------------------------------------------------------------+ //| Sets parameters of the flame and starts to draw | //+------------------------------------------------------------------+ void CFlameCanvas::FlameSet(datetime tb1,double pb1, datetime te1,double pe1, datetime tb2,double pb2, datetime te2,double pe2) { datetime obj_time =(datetime)ObjectGetInteger(0,m_objname,OBJPROP_TIME); double obj_price=ObjectGetDouble(0,m_objname,OBJPROP_PRICE); int dx,dy; //--- save parameters m_tb1=tb1; m_pb1=pb1; m_te1=te1; m_pe1=pe1; m_tb2=tb2; m_pb2=pb2; m_te2=te2; m_pe2=pe2; //--- resize Resize(); //--- convert if(ChartTimePriceToXY(0,0,obj_time,obj_price,dx,dy)) { dy=m_yb1; if(ChartTimePriceToXY(0,0,tb1,pb1,m_xb1,m_yb1)) if(ChartTimePriceToXY(0,0,te1,pe1,m_xe1,m_ye1)) if(ChartTimePriceToXY(0,0,tb2,pb2,m_xb2,m_yb2)) if(ChartTimePriceToXY(0,0,te2,pe2,m_xe2,m_ye2)) { //--- convert to canvas coordinates m_xb1-=dx; m_xe1-=dx; m_xb2-=dx; m_xe2-=dx; //--- FlameSet(); } } //--- start timer EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); } //+------------------------------------------------------------------+ //| Generate array that describes the body of flame | //+------------------------------------------------------------------+ void CFlameCanvas::FlameCreate(void) { static GRADIENT_SIZE sword[]={{100,0},{150,70},{0,100}}; static GRADIENT_COLOR flame[]={{0x00,0},{0x7F7F7F,12},{0xCCCCCC,30},{0xFFFFFF,45},{0xFFFFFF,55},{0xCCCCCC,70},{0x7F7F7F,88},{0x00,100}}; //--- double a=rand(); // parameter of line a*x+b double b=rand(); // parameter of line a*x+b double c=rand(); // parameter of sine c*Sin(d*x) double d=rand(); // parameter of sine c*Sin(d*x) int w=rand(); // width at the base int l=rand(); // length //--- normalize a=fmod(a,(m_a2-m_a1))+m_a1; b=(m_yb1+m_yb2)/2; c=fmod(c,20); d=fmod(d,3*M_PI)+M_PI; //--- shape w%=150; if(w<10) w=10; // but no less than 10 sword[1].size=w; w=rand(); l%=50; sword[1].pos=l+30; l=rand(); //--- sizes w=(m_yb2-m_yb1!=0) ? w%(m_yb2-m_yb1) : 10; // proportional to the starting width if(w<10) w=10; // but no less than 10 l=l%((m_xe1-m_xb1)/(int)m_bar_gap-20)+20; // proportional to length //--- create int total=ArraySize(m_cloud_axis); for(int i=0;ilenght) total=lenght; //--- draw int xb,xe; // coordinates of the segment int ybm,yem; // coordinates of the center line int yb1,ye1; // coordinates of the first line int yb2,ye2; // coordinates of the second line int xx; //--- for implementation of variable width int w_total=ArraySize(size); if(w_total<2) return; int w_i =0; int w_is=(int)size[w_i].pos*total/100; int w_ie=(int)size[w_i+1].pos*total/100; double w =size[w_i].size*width/100; double dw =(size[w_i+1].size*width/100-w)/(w_ie-w_is); //--- draw from left to right xb=0; ChartTimePriceToXY(0,0,0,prices[0],xx,ybm); yb1=ybm-(int)(w/2); yb2=ybm+(int)(w/2); //--- draw for(int i=1;i=m_width) break; yb1=ye1; yb2=ye2; while(i>=w_ie-1 && i!=total-1) { w_i++; w_is=(int)size[w_i].pos*total/100; w_ie=(int)size[w_i+1].pos*total/100; w =size[w_i].size*width/100; if(w_ie==w_is) { //--- for "instant" resize dw=size[w_i+1].size*width/100-w; w+=dw; ye1=yem-(int)(w/2); ye2=yem+(int)(w/2); //--- draw the segment of 'cloud' GradientVertical(xb,xe,yb1,ye1,yb2,ye2,gradient); yb1=ye1; yb2=ye2; } else { dw=(size[w_i+1].size*width/100-w)/(w_ie-w_is); break; } } } } //+------------------------------------------------------------------+ //| Draws area with vertical fill using specified gradient | //+------------------------------------------------------------------+ void CFlameCanvas::GradientVertical(const int xb,const int xe,const int yb1,const int ye1,const int yb2,const int ye2,const GRADIENT_COLOR &gradient[]) { //--- it is assumed that the colors array has sufficient size and positions are already sorted in ascending order //--- get length by X and Y int x1 =xb; int y1 =yb1; int x2 =xb; int y2 =yb2; int dx =(xe>xb)? xe-xb : xb-xe; int dy1=(ye1>yb1)? ye1-yb1 : yb1-ye1; int dy2=(ye2>yb2)? ye2-yb2 : yb2-ye2; //--- get direction by X and Y int sx =(xb-dy1) { //--- try to change X coordinate of the first line //--- draw the second line while(x2!=xe || y2!=ye2) { //--- calculate coordinates of next pixel of the second line if((er2<<1)>-dy2) { //--- try to change X coordinate of the second line //--- gradient fill GradientVerticalLine(x1,y1,y2,gradient); er2-=dy2; if(x2!=xe) x2+=sx; } if((er2<<1)0) { dd=dy; dy=1; } else { dd=-dy; dy=-1; } //--- increments for the color components dc=(double)((uchar)clr2-clr)/dd; //--- draw for(int i=0;y1!=y2;i++,y1+=dy) { int idx=y1*m_width+x; //--- check range if(idx<0 || idx>=ArraySize(m_flame)) continue; if(x>=0 && x=0 && y1m_time_redraw) { //--- add the body of flame FlameCreate(); //--- draw frame FlameCalculate(); Update(); //--- calculate time for the next frame m_time_redraw=GetTickCount()+m_delay; } //--- generate next event for custom timer EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); break; } } //+------------------------------------------------------------------+ //| Delay | //+------------------------------------------------------------------+ void CFlameCanvas::Delay(const uint value) { //--- too small if(value<10) return; //--- start delay uint cnt=GetTickCount()+value; //--- delay while(cnt>=GetTickCount()); } //+------------------------------------------------------------------+