Possibilities of Canvas. - page 6

 
alighasemi1993 #:

In fact, for a custom object, all the steps are the same as the steps in this example, and only the length and width of the object should be applied instead of the length and width of the chart?

I don't understand your end goal.  Try to say what you want in other words without vague questions.
And don't forget that the canvas object is being discussed here, not other objects, which are very limited in their capabilities.
 

@Nikolai Semko: You seem to do wonders with canvas in this thread - nice work! Let me challenge you with a question which I have not managed to get answered anywhere in the Web so far. Hope you'll know what is going on:

Whenever I setup my canvas with a call to CreateBitmapLabel the created, invisible graphical object covers my MT5 chart area in a way that I stop getting OHLC tooltips while hovering mouse over bars/candles. Setting up BitmapLabel to cover the whole chart (= what I intend to do) effectively prevents any candle tooltips to be displayed. Tried setting z-order of the bitmap object to negative values, setting its tooltip to "\n", sending the whole bitmap object to background, chart to foreground and/or making the object not selectable. None of this (in any combination) allowed me to get the tooltips back. Do you (or anyone reading this) know a way around it?

Would be so cool to use canvas in my indicator, yet I know my target users use the tooltips very often and won't accept them not working. Am I forced to use regular chart objects because of that?

// Sets up volume profile's canvas i.e. additional, visual layer on top of the entire, active chart.
void SetupCanvas() {
    ProfileCanvas.CreateBitmapLabel(IndicatorPrefix + " canvas", 
                                    0, 0, 
                                    int(ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS)), 
                                    int(ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS)),
                                    COLOR_FORMAT_ARGB_NORMALIZE);
    const string name = ProfileCanvas.ChartObjectName();
    ObjectSetInteger(ChartID(), name, OBJPROP_SELECTABLE, false);
    ObjectSetInteger(ChartID(), name, OBJPROP_SELECTED, false);
    ObjectSetString(0, name, OBJPROP_TOOLTIP, "\n");
    ObjectSetInteger(0, name, OBJPROP_ZORDER, -1);
    ChartRedraw(ChartID());
}
 
Astralius #:

@Nikolai Semko: You seem to do wonders with canvas in this thread - nice work! Let me challenge you with a question which I have not managed to get answered anywhere in the Web so far. Hope you'll know what is going on:

Whenever I setup my canvas with a call to CreateBitmapLabel the created, invisible graphical object covers my MT5 chart area in a way that I stop getting OHLC tooltips while hovering mouse over bars/candles. Setting up BitmapLabel to cover the whole chart (= what I intend to do) effectively prevents any candle tooltips to be displayed. Tried setting z-order of the bitmap object to negative values, setting its tooltip to "\n", sending the whole bitmap object to background, chart to foreground and/or making the object not selectable. None of this (in any combination) allowed me to get the tooltips back. Do you (or anyone reading this) know a way around it?

Would be so cool to use canvas in my indicator, yet I know my target users use the tooltips very often and won't accept them not working. Am I forced to use regular chart objects because of that?

It's an MT5 bug. MetaQuotes is already aware about it.

 
Astralius #:

@Nikolai Semko: You seem to do wonders with canvas in this thread - nice work! Let me challenge you with a question which I have not managed to get answered anywhere in the Web so far. Hope you'll know what is going on:

Whenever I setup my canvas with a call to CreateBitmapLabel the created, invisible graphical object covers my MT5 chart area in a way that I stop getting OHLC tooltips while hovering mouse over bars/candles. Setting up BitmapLabel to cover the whole chart (= what I intend to do) effectively prevents any candle tooltips to be displayed. Tried setting z-order of the bitmap object to negative values, setting its tooltip to "\n", sending the whole bitmap object to background, chart to foreground and/or making the object not selectable. None of this (in any combination) allowed me to get the tooltips back. Do you (or anyone reading this) know a way around it?

Would be so cool to use canvas in my indicator, yet I know my target users use the tooltips very often and won't accept them not working. Am I forced to use regular chart objects because of that?

If you just need the OHLC tooltips is an easy task. You can draw it in your canvas, or separate small canvas. If you need it for for all the rest of the objects and indicators, it is impossible at this point.

 
Astralius #:

@Nikolai Semko: You seem to do wonders with canvas in this thread - nice work! Let me challenge you with a question which I have not managed to get answered anywhere in the Web so far. Hope you'll know what is going on:

Whenever I setup my canvas with a call to CreateBitmapLabel the created, invisible graphical object covers my MT5 chart area in a way that I stop getting OHLC tooltips while hovering mouse over bars/candles. Setting up BitmapLabel to cover the whole chart (= what I intend to do) effectively prevents any candle tooltips to be displayed. Tried setting z-order of the bitmap object to negative values, setting its tooltip to "\n", sending the whole bitmap object to background, chart to foreground and/or making the object not selectable. None of this (in any combination) allowed me to get the tooltips back. Do you (or anyone reading this) know a way around it?

Would be so cool to use canvas in my indicator, yet I know my target users use the tooltips very often and won't accept them not working. Am I forced to use regular chart objects because of that?

Yes, unfortunately Alain is right.

I also agree with Samuel that in case of an urgent need to show tooltips, you can use the same or another canvas. In any case, I would do so if I needed it.
In this case, I would recommend my library iCanvas , which would greatly simplify such an implementation, since it automatically fills an instance of the Window (W) structure, which has parameters such as MouseBar and MousePrice.
Moreover, the iCanvas class significantly increases performance due to its own functions for translating the XY system into price-time and back.


Just in case, I am attaching the latest version of the iCanvas library

Easy Canvas (iCanvas)
Easy Canvas (iCanvas)
  • www.mql5.com
The library and iCanvas class simplify writing programs using Canvas.
Files:
iCanvas_CB.mqh  73 kb
 

@Alain Verleyen@Samuel Manoel De Souza@Nikolai Semko: Thanks for valuable input. Putting it all together, I managed to work around the tooltips problem like below.

Basically, what is done here:

  1. Mouse coordinates are captured to detect the candle/bar (if any) under the pointer,
  2. If a bar is detected, a temporary OBJ_RECTANGLE is drawn around that candle's body (excluding wick/tail). If no bar is detected (mouse moved away), that object is destroyed.
  3. A custom tooltip is assigned to the temporary rectangle, which is displayed normally, mimicking the original behavior (with Volume/TickVolume as a bonus).
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
	// Handle OHLCV tooltips (can't rely standard tooltips as chart is covered by the ProfileCanvas).
    	if(id == CHARTEVENT_MOUSE_MOVE) {    
                const int mouseX = (int)lparam;
                const int mouseY = (int)dparam;        
                int barShift = GetBarShiftAtPosition(mouseX, mouseY);        
                if (barShift == -1) {
                 ObjectDelete(ChartID(), TooltipObjectName);
                return; // not hovering over any candle
        	}
        	DrawCustomBarTooltip(barShift);
    	}
}

/*
Computes bar shift of the candle under the specified screen coordinates.
Returns -1 if there is no candle at these coordinates.
*/
int GetBarShiftAtPosition(const int x, const int y) {
    int result = -1;    // not found
    datetime time;
    double price;
    int subwindow;

    // Detect the exact time/price under the provided xy coordinates:
    if (!ChartXYToTimePrice(ChartID(), x, y, subwindow, time, price) || subwindow > 0) return result;  // Do not continue if x/y are outside of the main chart window
    if (time >= iTime(_Symbol, PERIOD_CURRENT, 0)) return result;  // Do not continue if x time exceeds current bar's open time (= ignore current bar)
    const int barShift = iBarShift(_Symbol, PERIOD_CURRENT, time);
    if (barShift == -1) {
        return result;
    }

    // Get bar's time/price coordinates (only the candle's real body i.e. excluding wick/tail is considered):
    // NOTE: open/close times are returned from candle's center line. Need to adjust.
    const datetime barTimeOpen = iTime(_Symbol, PERIOD_CURRENT, barShift) - PeriodSeconds(PERIOD_CURRENT)/3;
    const datetime barTimeClose = barTimeOpen + 2*PeriodSeconds(PERIOD_CURRENT)/3;
    const double barPriceOpen = iOpen(_Symbol, PERIOD_CURRENT, barShift);
    const double barPriceClose = iClose(_Symbol, PERIOD_CURRENT, barShift);
    const double barPriceBottom = barPriceClose > barPriceOpen ? barPriceOpen : barPriceClose;
    const double barPriceTop = barPriceClose > barPriceOpen ? barPriceClose : barPriceOpen;

    // Translate to bar's xy coordinates:
    int barX1 = 0, barY1 = 0; // lower left corner of the candle's real body
    if (!ChartTimePriceToXY(ChartID(), 0, barTimeOpen, barPriceBottom, barX1, barY1)) return result;
    int barX2 = 0, barY2 = 0; // upper right corner of the candle's real body
    if (!ChartTimePriceToXY(ChartID(), 0, barTimeClose, barPriceTop, barX2, barY2)) return result;

    // Evaluate whether xy coordinates are within the bar's xy bounds (NOTE: y starts on top):
    if (barX1 <= x && x <= barX2 && barY2 <= y && y <= barY1) result = barShift;

    return result;
}

void DrawCustomBarTooltip(int barShift) {
    // Get bar data:
    datetime barTimeOpen = iTime(_Symbol, PERIOD_CURRENT, barShift);
    double barPriceOpen = iOpen(_Symbol, PERIOD_CURRENT, barShift);
    double barPriceHigh = iHigh(_Symbol, PERIOD_CURRENT, barShift);
    double barPriceLow = iLow(_Symbol, PERIOD_CURRENT, barShift);
    double barPriceClose = iClose(_Symbol, PERIOD_CURRENT, barShift);
    long barPriceVolume = iVolume(_Symbol, PERIOD_CURRENT, barShift);
    long barPriceTickVolume = iTickVolume(_Symbol, PERIOD_CURRENT, barShift);

    // Prepare tooltip container:
    // NOTE: open time is returned for candle's center line. Need to adjust to the left.
    long currentPeriodSeconds = PeriodSeconds(PERIOD_CURRENT);
    datetime startTime = datetime(barTimeOpen - long(currentPeriodSeconds/3));
    datetime stopTime = datetime(startTime + long(2*currentPeriodSeconds/3));
    ObjectCreate(ChartID(), TooltipObjectName, OBJ_RECTANGLE, 0, startTime, barPriceOpen, stopTime, barPriceClose);
    ObjectSetInteger(ChartID(), TooltipObjectName, OBJPROP_COLOR, 
                     ChartGetInteger(ChartID(), barPriceClose > barPriceOpen ? CHART_COLOR_CHART_UP : CHART_COLOR_CHART_DOWN));
    ObjectSetInteger(ChartID(), TooltipObjectName, OBJPROP_HIDDEN, true);
    ObjectSetInteger(ChartID(), TooltipObjectName, OBJPROP_WIDTH, 2);
    ObjectSetInteger(ChartID(), TooltipObjectName, OBJPROP_READONLY, true);
    ObjectSetInteger(ChartID(), TooltipObjectName, OBJPROP_SELECTABLE, false);
    ObjectSetString(ChartID(), TooltipObjectName, OBJPROP_TEXT, NULL);

    // Prepare tooltip text:
    string tooltip = "";
    const string time = TimeToString(barTimeOpen);
    const string open = "Open: " + DoubleToString(barPriceOpen, _Digits);
    const string high = "High: " + DoubleToString(barPriceHigh, _Digits);
    const string low = "Low: " + DoubleToString(barPriceLow, _Digits);
    const string close = "Close: " + DoubleToString(barPriceClose, _Digits);
    const string volume = "Volume: " + IntegerToString(barPriceVolume > 0 ? barPriceVolume : barPriceTickVolume);
    StringConcatenate(tooltip, time, "\n", open, "\n", high, "\n", low, "\n", close, "\n", volume);
    ObjectSetString(ChartID(), TooltipObjectName, OBJPROP_TOOLTIP, tooltip);

    ChartRedraw();
}

Results attached. Sharing it here if someone has a similar problem to mine when using canvas. 

One downside (or feature? ;p) is that the bar is highlighted (=rectangle's border) when hovered over: a side effect of me not finding any chart object that'd nicely cover candles' body (for tooltip's hit test) while being invisible to user.

If you see any redundant parts or something that could've been done better/more efficient: all comments are welcome :)
Files:
 
Astralius #:

@Alain Verleyen@Samuel Manoel De Souza@Nikolai Semko: Thanks for valuable input. Putting it all together, I managed to work around the tooltips problem like below.

Basically, what is done here:

  1. Mouse coordinates are captured to detect the candle/bar (if any) under the pointer,
  2. If a bar is detected, a temporary OBJ_RECTANGLE is drawn around that candle's body (excluding wick/tail). If no bar is detected (mouse moved away), that object is destroyed.
  3. A custom tooltip is assigned to the temporary rectangle, which is displayed normally, mimicking the original behavior (with Volume/TickVolume as a bonus).

Results attached. Sharing it here if someone has a similar problem to mine when using canvas. 

One downside (or feature? ;p) is that the bar is highlighted (=rectangle's border) when hovered over: a side effect of me not finding any chart object that'd nicely cover candles' body (for tooltip's hit test) while being invisible to user.

If you see any redundant parts or something that could've been done better/more efficient: all comments are welcome :)

Solution without creating objects on canvas background

#property indicator_chart_window
#include <Canvas\iCanvas_CB.mqh> // https://www.mql5.com/en/code/23840
//+------------------------------------------------------------------+
int OnInit() {
   ChartSetInteger(0,CHART_SHOW_OBJECT_DESCR,true);
   Canvas.SetBack(true);
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[]) {
   return(rates_total);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam) {
   static int last_bar = -100;
   if (id == CHARTEVENT_MOUSE_MOVE) {
      int cur_bar = Round(W.MouseBar)-1;
      double high = iHigh(NULL,0,cur_bar);
      double low = iLow(NULL,0,cur_bar);
      for (int y =0; y<W.Height;y++) ArrayFill(Canvas.m_pixels,y*W.Width,W.Width,Canvas.Grad(double(W.MouseY)/W.Height*double(y)/W.Height)); //   change the canvas color
      Canvas.Update();
      if (W.MousePrice>=low && W.MousePrice<=high) {
         if (cur_bar != last_bar)  {
            last_bar = cur_bar;
            string tooltip = TimeToString(iTime(NULL,0,cur_bar))+"\n";
            tooltip += "Open: " + DoubleToString(iOpen(NULL,0,cur_bar), _Digits)+"\n";
            tooltip += "High: " + DoubleToString(high, _Digits)+"\n";
            tooltip += "Low: " + DoubleToString(low, _Digits)+"\n";
            tooltip += "Close: " + DoubleToString(iClose(NULL,0,cur_bar), _Digits)+"\n";
            tooltip += "Volume: " + IntegerToString(iVolume(NULL,0,cur_bar));
            ObjectSetString(Canvas.m_chart_id,Canvas.m_objname, OBJPROP_TOOLTIP, tooltip);
         }
      } else {
         last_bar = -100;
         ObjectSetString(Canvas.m_chart_id,Canvas.m_objname,OBJPROP_TOOLTIP,"\n");
      }

   }
}
//+------------------------------------------------------------------+
Easy Canvas (iCanvas)
Easy Canvas (iCanvas)
  • www.mql5.com
The library and iCanvas class simplify writing programs using Canvas.
Files:
iCanvas_CB.mqh  73 kb
 
Nikolai Semko #:

Solution without creating objects on canvas background

Thanks a lot! While I didn't use your solution as-is, I improved upon it and managed to simplify mine down to 50 LOC (2 functions), without using the iCanvas (btw. iCanvas is great, but using it solely for tooltips in my solution felt like employing a space shuttle to deliver pizza :)). Case closed.
 
Astralius #:
Thanks a lot! While I didn't use your solution as-is, I improved upon it and managed to simplify mine down to 50 LOC (2 functions), without using the iCanvas (btw. iCanvas is great, but using it solely for tooltips in my solution felt like employing a space shuttle to deliver pizza :)). Case closed.

😁👍