English Русский
preview
MQL5-Handelswerkzeuge (Teil 13): Entwicklung eines Canvas-basierten Kurs-Dashboards mit Chart- und Statistik-Panels

MQL5-Handelswerkzeuge (Teil 13): Entwicklung eines Canvas-basierten Kurs-Dashboards mit Chart- und Statistik-Panels

MetaTrader 5Handelssysteme |
28 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel (Teil 12) haben wir das Dashboard für eine Korrelationsmatrix in MetaQuotes Language 5 (MQL5) mit interaktiven Funktionen für eine bessere Nutzerfreundlichkeit erweitert. In Teil 13 stellen wir Ihnen ein canvasbasiertes Dashboard mit Grafiken und Statistikpanel vor, das Händlern klare, praxisnahe Einblicke in Kursbewegungen und Kontometriken bietet. Mithilfe der Klasse CCanvas bietet dieses Dashboard verschiebbare und in der Größe veränderbare Panels, visualisiert die jüngsten Kursverläufe und zeigt wichtige Statistiken an, einschließlich Kontostand, Equity und die Eröffnungs-, Höchst-, Tiefst- und Schlusskurse der aktuellen Bar. Unsere Handelsentscheidungen werden durch anpassbare Hintergründe, das Umschalten zwischen Designs und Echtzeit-Updates zusätzlich unterstützt. Mit diesen Instrumenten können Sie Marktveränderungen effizienter überwachen und darauf reagieren. Wir behandeln folgende Themen:

  1. Grundlagen des Canvas-basierten Kurs-Dashboard-Rahmens
  2. Implementierung in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende verfügen Sie über ein funktionales, auf MQL5-Canvas basierendes Dashboard zur Überwachung von Kursen und Metriken, das zur Anpassung bereit ist – beginnen wir!


Grundlagen des Canvas-basierten Kurs-Dashboard-Rahmens

Das canvasbasierte Kurs-Dashboard-Framework nutzt die Klasse CCanvas von MQL5, um nutzerdefinierte grafische Panels für die Anzeige von Echtzeit-Kurs- und Kontodaten zu erstellen und bietet eine kompakte, interaktive Alternative zu Standard-Chartindikatoren für Nutzer, die schnelle visuelle Übersichten benötigen, ohne das Hauptchart zu überladen. Es besteht aus einem Haupt-Grafikpanel, das die Schlusskurse der letzten Bar als Linie mit gefüllten Bereichen und Nebeleffekten für die Tiefe anzeigt, einem optionalen Statistikpanel, das Kontodetails wie Saldo/Equity und die OHLC-Werte der aktuellen Bar anzeigt, beide unterstützt von Hintergrundbildern mit Deckkraftüberblendung, Farbverlauf oder Volltonfüllungen und doppelten Rändern für die Ästhetik.

Zu den Verbesserungen gehören mausgesteuertes Ziehen für die Neupositionierung, Größenanpassung über Rahmen-Hover und Griffe mit Icons für Feedback, Schaltflächen zum Minimieren und Maximieren der Panels, Umschalten zwischen dunklen/hellen Modi für Farbanpassungen und Echtzeit-Updates für neue Bars, um die neuesten Kurse und Statistiken wiederzugeben.

Die Umsetzung basiert auf Mausereignissen, auf bikubischer Skalierung zur hochwertigen Bildanpassung, auf Alpha-Blending für Überlagerungen wie Nebel sowie ARGB-Farbverwaltung für Transparenzeffekte. Dadurch bleibt das Dashboard reaktionsschnell und flexibel anpassbar, ohne auf native MQL5-Objekte zurückzugreifen. Genau das war diesmal unser Ziel, da wir native Objekte bereits mehrfach verwendet haben; diesmal wechseln wir bewusst den Ansatz und nutzen die Canvas-Funktionen umfassend.

Geplant ist, zunächst die Canvas-Bibliothek einzubinden, Eingabeparameter für Position, Größe, Farben, Deckkraft und Modi zu definieren, anschließend eine Hintergrundbild-Ressource zu laden und zu skalieren sowie getrennte Canvas-Elemente für Header, Graph und Statistik mit entsprechenden Prüfungen zu erstellen. Anschließend sollen die Zeichenfunktionen für Header mit Icons/Tooltips/Rahmen implementiert werden, wie auch die Charts mit Kursplotting/Füllung/Zeitetiketten/Symbolen mit Größenänderung und Statistiken mit dem Design von Text/Gradienten/verdunkelten Rändern. Hinzugefügt werden Hilfsfunktionen für Farbinterpolation/Verdunkelung/Vermischung/ARGB-Extraktion und Handhabung von Chart-Ereignissen für Mouseover/Ziehen/Größenänderung/Schaltfunktionen mit Begrenzungen und Mindestgrößen, Aktualisierung bei neuen Tick-Daten. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

CANVAS DASHBOARD FRAMEWORK


Implementierung in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner „Experts“, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                       Canvas Dashboard PART1.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas canvasGraph;                                             //--- Declare canvas for graph
CCanvas canvasStats;                                             //--- Declare canvas for stats
CCanvas canvasHeader;                                            //--- Declare canvas for header

string canvasGraphName = "GraphCanvas";                          //--- Set graph canvas name
string canvasStatsName = "StatsCanvas";                          //--- Set stats canvas name
string canvasHeaderName = "HeaderCanvas";                        //--- Set header canvas name

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int graphBars                   = 50;                      // Number of recent bars to plot in the graph
input color borderColor               = clrBlack;                // Border color (change for white chart background)
input color borderHoverColor          = clrRed;                  // Border color on hover for resize indication

input int CanvasX                     = 30;                      // Main canvas X position
input int CanvasY                     = 50;                      // Main canvas Y position
input int CanvasWidth                 = 400;                     // Main canvas width
input int CanvasHeight                = 300;                     // Main canvas height

input bool EnableStatsPanel           = true;                    // Enable second stats panel
input int PanelGap                    = 10;                      // Gap between panels in pixels

input bool UseBackground              = true;                    // Enable background image
input double FogOpacity               = 0.5;                     // Fog opacity (0.0 = no fog/fully transparent, 1.0 = fully opaque)
input bool BlendFog                   = true;                    // Blend fog with image (true: image visible under fog; false: original fog hides image)

input int StatsFontSize               = 12;                      // Font size for stats panel text
input color StatsLabelColor           = clrDodgerBlue;           // Color for stats labels
input color StatsValueColor           = clrWhite;                // Color for stats values
input color StatsHeaderColor          = clrDodgerBlue;           // Color for stats headers
input int StatsHeaderFontSize         = 14;                      // Font size for stats headers

input double BorderOpacityPercentReduction = 20.0;               // Percent reduction for border opacity (0-100)
input double BorderDarkenPercent      = 30.0;                    // Percent to darken borders (0-100)

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_BACKGROUND_MODE
{
   NoColor          = 0,         // No color fill
   SingleColor      = 1,         // Single color fill
   GradientTwoColors = 2         // Gradient with two colors
};

input ENUM_BACKGROUND_MODE StatsBackgroundMode = GradientTwoColors; // Stats background mode
input color TopColor                   = clrBlack;                  // Top color for gradient or single fill
input color BottomColor                = clrRed;                    // Bottom color for gradient
input double BackgroundOpacity         = 0.7;                       // Opacity for stats background fill (0.0 to 1.0)

enum ENUM_RESIZE_MODE
{
   NONE,
   BOTTOM,
   RIGHT,
   BOTTOM_RIGHT
};

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource "1. Transparent MT5 bmp image.bmp"                     // Hardcoded background image resource

//+------------------------------------------------------------------+
//| Global Variables Continued                                       |
//+------------------------------------------------------------------+
uint original_bg_pixels[];                                       //--- Declare original unscaled background
uint orig_w = 0, orig_h = 0;                                     //--- Initialize original dimensions
uint bg_pixels_graph[];                                          //--- Declare scaled background for graph
uint bg_pixels_stats[];                                          //--- Declare scaled background for stats

int currentCanvasX = CanvasX;                                    //--- Set current X position
int currentCanvasY = CanvasY;                                    //--- Set current Y position
int currentWidth = CanvasWidth;                                  //--- Set current width
int currentHeight = CanvasHeight;                                //--- Set current height
bool panel_dragging = false;                                     //--- Set dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Initialize drag start mouse coordinates
int panel_start_x = 0, panel_start_y = 0;                        //--- Initialize drag start panel coordinates
bool resizing = false;                                           //--- Set resizing flag
ENUM_RESIZE_MODE resize_mode = NONE;                             //--- Set resize mode
ENUM_RESIZE_MODE hover_mode = NONE;                              //--- Set hover mode for resize
int resize_start_x = 0, resize_start_y = 0;                      //--- Initialize resize start coordinates
int start_width = 0, start_height = 0;                           //--- Initialize start dimensions
const int resize_thickness = 5;                                  //--- Set resize border thickness
const int min_width = 200;                                       //--- Set minimum width
const int min_height = 150;                                      //--- Set minimum height
int hover_mouse_local_x = 0;                                     //--- Set local mouse x for icon
int hover_mouse_local_y = 0;                                     //--- Set local mouse y for icon
bool header_hovered = false;                                     //--- Set header hover flag
bool minimize_hovered = false;                                   //--- Set minimize hover flag
bool close_hovered = false;                                      //--- Set close hover flag
bool theme_hovered = false;                                      //--- Set theme hover flag
bool resize_hovered = false;                                     //--- Set resize hover flag
int prev_mouse_state = 0;                                        //--- Initialize previous mouse state
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Initialize last mouse position
int header_height = 27;                                          //--- Set header height
int gap_y = 7;                                                   //--- Set y gap
int button_size = 25;                                            //--- Set button size
int theme_x_offset = -75;                                        //--- Set theme offset relative to header right
int minimize_x_offset = -50;                                     //--- Set minimize offset relative to header right
int close_x_offset = -25;                                        //--- Set close offset relative to header right
bool panels_minimized = false;                                   //--- Set minimized flag
color HeaderColor = C'60,60,60';                                 //--- Set header color
color HeaderHoverColor = clrRed;                                 //--- Set header hover color
color HeaderDragColor = clrMediumBlue;                           //--- Set header drag color

bool is_dark_theme = true;                                       //--- Set dark theme flag

color LightHeaderColor = clrSilver;                              //--- Set light header color
color LightHeaderTextColor = clrBlack;                           //--- Set light header text color
color LightStatsLabelColor = clrBlue;                            //--- Set light stats label color
color LightStatsValueColor = clrBlack;                           //--- Set light stats value color
color LightStatsHeaderColor = clrBlue;                           //--- Set light stats header color
color LightBorderColor = clrBlack;                               //--- Set light border color
color LightTopColor = clrGreen;                                  //--- Set light top color
color LightBottomColor = clrGold;                                //--- Set light bottom color
color LightHeaderHoverColor = clrRed;                            //--- Set light header hover color
color LightHeaderDragColor = clrMediumBlue;                      //--- Set light header drag color

bool graphCreated = false;                                       //--- Set graph created flag
bool statsCreated = false;                                       //--- Set stats created flag

Wir beginnen die Implementierung, indem wir die Canvas-Bibliothek mit „#include <Canvas/Canvas.mqh>“ einbinden, die die Klasse CCanvas für die Erstellung und Verwaltung von Bitmap-basierten grafischen Feldern im Chart bereitstellt. Anschließend deklarieren wir drei „CCanvas“-Objekte: „canvasGraph“ für das Kurs-Grafikpanel, „canvasStats“ für die Statistik und „canvasHeader“ für die Kopfzeile. Wir setzen String-Konstanten für ihre Namen als „GraphCanvas“, „StatsCanvas“ und „HeaderCanvas“, um sie eindeutig zu identifizieren.

Als Nächstes definieren wir Eingabeparameter, um das Dashboard anzupassen: „graphBars“ auf fünfzig für die Anzahl der darzustellenden Bars, „borderColor“ auf schwarz und „borderHoverColor“ auf rot für Rahmen und Mouseover-Anzeigen, Positionen und Abmessungen wie „CanvasX“ auf dreißig, „CanvasY“ auf fünfzig, „CanvasWidth“ auf vierhundert, „CanvasHeight“ auf dreihundert, ein boolescher Wert „EnableStatsPanel“ true für die Anzeige des Statistikpanels mit „PanelGap“ auf zehn Pixel, „UseBackground“ true für Bildhintergründe mit „FogOpacity“ auf 0.5 und „BlendFog“ true für die Überblendung, Schriftgrößen und -farben für Statistiken wie „StatsFontSize“ auf 12, „StatsLabelColor“ auf Dodger-Blau und Prozentsätze für Randanpassungen wie „BorderOpacityPercentReduction“ auf 20,0 und „BorderDarkenPercent“ auf 30,0.

Wir erstellen die Enumeration „ENUM_BACKGROUND_MODE“ mit den Optionen „NoColor“ für keine Füllung, „SingleColor“ für einheitliche Farbe und „GradientTwoColors“ für zweifarbige Farbverläufe, wobei die Eingabe „StatsBackgroundMode“ standardmäßig auf Farbverlauf und die Farben „TopColor“ auf Schwarz, „BottomColor“ auf Rot sowie „BackgroundOpacity“ auf 0.7 eingestellt sind. Dann fügen wir die Enumeration „ENUM_RESIZE_MODE“ für die Größenänderungen hinzu: „NONE“, „BOTTOM“, „RIGHT“ und „BOTTOM_RIGHT“.

Wir binden eine Ressource mit der Direktive #resource ein; „1. Transparentes MetaTrader 5 bmp image.bmp“ für ein fest kodiertes Hintergrundbild. Das Bild, das angehängt werden soll, muss eine reine Bitmap-Datei sein. Das lässt sich einfach umsetzen; unsere Bilddatei sieht nun wie folgt aus. Wir haben sie der Einfachheit halber dorthin importiert, wo sich die Programmdatei befindet. Schauen Sie sich unten an, was wir erreicht haben.

BITMAP-BILDDATEI

Nachdem die Bilddatei fertig ist, fahren wir mit den globalen Variablen fort: Arrays „original_bg_pixels“, „bg_pixels_graph“ und „bg_pixels_stats“ für Bildpixel, Dimensionen „orig_w“ und „orig_h“ auf Null, aktuelle Positionen und Größen aus Eingaben initialisiert, Booleans wie „panel_dragging“ false, „resizing“ false, Aufzählungen „resize_mode“ und „hover_mode“ auf „NONE“, Integerwerte für die Startwerte für die Größenänderung und Mindestmaße wie „resize_thickness“ auf fünf, „min_width“ auf zweihundert, Mouseover wie „header_hovered“ false, „prev_mouse_state“ auf null, Layout-Konstanten wie „header_height“ auf siebenundzwanzig, „gap_y“ auf sieben, „button_size“ auf fünfundzwanzig, Offsets für Buttons, „panels_minimized“ false, Farben wie „HeaderColor“ auf mittelgrau, „HeaderHoverColor“ auf rot, Design-Flag „is_dark_theme“ true, Light-Mode-Farben wie „LightHeaderColor“ auf silber und Flags „graphCreated“ und „statsCreated“ false.

Als Nächstes müssen wir eine Funktion erstellen, die das Bild dynamisch skaliert, damit es den neuen Abmessungen entspricht. Dies ist nützlich, wenn wir die Größe des Hintergrundbildes in den Panels ändern wollen, wenn dies erlaubt ist. Hier ist die Logik, mit der wir das erreicht haben.

//+------------------------------------------------------------------+
//| Scale image                                                      |
//+------------------------------------------------------------------+
void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) {
   uint scaled_pixels[];                                         //--- Declare scaled array
   ArrayResize(scaled_pixels, new_width * new_height);           //--- Resize scaled
   for (int y = 0; y < new_height; y++)                          //--- Loop new rows
   {
      for (int x = 0; x < new_width; x++)                        //--- Loop new columns
      {
         double original_x = (double)x * original_width / new_width; //--- Compute original x
         double original_y = (double)y * original_height / new_height; //--- Compute original y
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel
         scaled_pixels[y * new_width + x] = pixel;               //--- Set scaled pixel
      }
   }
   ArrayResize(pixels, new_width * new_height);                  //--- Resize original
   ArrayCopy(pixels, scaled_pixels);                             //--- Copy scaled
}

//+------------------------------------------------------------------+
//| Bicubic interpolate pixel                                        |
//+------------------------------------------------------------------+
uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) {
   int x0 = (int)x;                                              //--- Get integer x
   int y0 = (int)y;                                              //--- Get integer y
   double fractional_x = x - x0;                                 //--- Get fractional x
   double fractional_y = y - y0;                                 //--- Get fractional y
   int x_indices[4], y_indices[4];                               //--- Declare indices
   for (int i = -1; i <= 2; i++)                                 //--- Loop offsets
   {
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Clamp x index
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Clamp y index
   }
   uint neighborhood_pixels[16];                                 //--- Declare neighborhood
   for (int j = 0; j < 4; j++)                                   //--- Loop y indices
   {
      for (int i = 0; i < 4; i++)                                //--- Loop x indices
      {
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Get pixel
      }
   }
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare components
   for (int i = 0; i < 16; i++)                                  //--- Loop pixels
   {
      GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Get ARGB
   }
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue
   return (((uint)alpha_out) << 24) | (((uint)red_out) << 16) | (((uint)green_out) << 8) | ((uint)blue_out); //--- Return interpolated
}

//+------------------------------------------------------------------+
//| Bicubic interpolate component                                    |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   double weights_x[4];                                          //--- Declare x weights
   double t = fractional_x;                                      //--- Set t x
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t);          //--- Compute weight 0
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1);           //--- Compute weight 1
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);      //--- Compute weight 2
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t);               //--- Compute weight 3
   double y_values[4];                                           //--- Declare y values
   for (int j = 0; j < 4; j++)                                   //--- Loop y
   {
      y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] + weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Compute y value
   }
   double weights_y[4];                                          //--- Declare y weights
   t = fractional_y;                                             //--- Set t y
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t);          //--- Compute weight 0
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1);           //--- Compute weight 1
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);      //--- Compute weight 2
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t);               //--- Compute weight 3
   double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] + weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Compute result
   return MathMax(0, MathMin(255, result));                      //--- Clamp result
}

//+------------------------------------------------------------------+
//| Get ARGB components                                              |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF);                        //--- Get alpha
   red = (uchar)((pixel >> 16) & 0xFF);                          //--- Get red
   green = (uchar)((pixel >> 8) & 0xFF);                         //--- Get green
   blue = (uchar)(pixel & 0xFF);                                 //--- Get blue
}

Zunächst implementieren wir die Funktion „ScaleImage“, um die Größe eines Bildfeldes aus Pixeln von den ursprünglichen Abmessungen auf die neue Breite und Höhe zu ändern, wobei wir die bikubische Interpolation für eine gleichmäßige Skalierung verwenden. Sie nimmt einen Verweis auf das Pixel-Array, die ursprüngliche Breite und Höhe und die neuen Abmessungen. Wir deklarieren ein temporäres Array „scaled_pixels“ und passen die Größe an die neue Größe an, dann verschachteln wir Schleifen über die neue Höhe und Breite, um die entsprechenden Originalkoordinaten durch proportionales Mapping zu berechnen. Für jedes neue Pixel rufen wir „BicubicInterpolate“ mit dem ursprünglichen Array und den gebrochenen Koordinaten auf, um den interpolierten Wert zu erhalten und ihn in „scaled_pixels“ zu speichern. Schließlich wird die Größe des Arrays Eingangs-Pixel-Arrays auf die neue Größe angepasst und „scaled_pixels“ mit der Funktion ArrayCopy hineinkopiert.

Als Nächstes folgt die Funktion „BicubicInterpolate“ zur Berechnung eines einzelnen Pixelwerts durch bikubische Interpolation bei gegebenen Bruchteilen von x und y im Bild. Die Funktion verwendet das Pixel-Array, Breite, Höhe und die Gleitkommawerte x und y als Parameter. Wir erhalten die ganzzahligen Teile x0 und y0, die gebrochenen Teile und erstellen Index-Arrays für eine 4x4-Nachbarschaft, indem wir die Offsets von -1 bis 2 mit den Funktionen MathMin und MathMax an die Bildgrenzen anpassen. Wir extrahieren sechzehn benachbarte Pixel in ein Array, deklarieren dann Komponenten-Arrays für Alpha, Rot, Grün und Blau und füllen sie in einer Schleife mit „GetArgb“ auf. Wir interpolieren jede Komponente mit „BicubicInterpolateComponent“ und die Brüche, Casting zu uchar, und kombinieren in einem uint ARGB-Wert mit Bit-Shifts.

Anschließend erstellen wir die Funktion „BicubicInterpolateComponent“, um eine bikubische Interpolation auf dem 4x4-Komponenten-Array eines einzelnen Farbkanals unter Verwendung von gebrochenen x- und y-Werten durchzuführen. Es berechnet vier x-Gewichtungen mit der bikubischen Kernelformel auf der Grundlage von t als gebrochenes x und berechnet dann vier y-Zwischenwerte durch gewichtete Summen von Zeilen im Komponenten-Array. In ähnlicher Weise werden y-Gewichtungen mit einem Bruchteil von y als t berechnet, und das Endergebnis wird als gewichtete Summe dieser y-Werte abgeleitet, wobei mit „MathMax“ und „MathMin“ zwischen Null und 255 geklemmt wird. Schließlich implementieren wir die Funktion „GetArgb“, um ARGB-Komponenten aus einem uint-Pixelwert in uchar-Referenzen für Alpha, Rot, Grün und Blau zu extrahieren, wobei Bitverschiebungen nach rechts um 24/16/8/0 und Maskierung mit 0xFF verwendet werden.

Es ist wichtig zu verstehen, dass es kein Muss ist, einen bikubischen Ansatz zu verwenden. Sie können einen linearen oder bilinearen Ansatz verwenden, aber das ergibt ein zerklüfteteres Bild als das, was wir eigentlich wollen. Wir wählen also den besten Ansatz für eine möglichst visuell saubere Bildskalierung. Nachfolgend finden Sie verschiedene Ansätze, die Sie in Betracht ziehen könnten.

VERSCHIEDENE INTERPOLATIONSANSÄTZE

Es zeigt sich, dass der Ansatz der bikubischen Interpolation im Vergleich zu den anderen Ansätzen die glatteste Bildansicht liefert. Als Nächstes werden wir die Funktionen verwenden, um die Größe unseres Ressourcenbildes zu ändern, damit es dynamisch passt. Unsere Bilddatei ist größer als die Canvas, auf der sie gerendert werden soll, also müssen wir ihre Größe ändern. Lassen Sie uns das im Initialisierungs-Ereignishandler tun.

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit()
{
   currentWidth = CanvasWidth;                                   //--- Set initial width
   currentHeight = CanvasHeight;                                 //--- Set initial height
   currentCanvasX = CanvasX;                                     //--- Set initial X
   currentCanvasY = CanvasY;                                     //--- Set initial Y
   
   if (UseBackground)                                            //--- Check if background enabled
   {
      if (ResourceReadImage("::1. Transparent MT5 bmp image.bmp", original_bg_pixels, orig_w, orig_h) && orig_w > 0 && orig_h > 0) //--- Load image if valid
      {
         ArrayCopy(bg_pixels_graph, original_bg_pixels);         //--- Copy to graph background
         ScaleImage(bg_pixels_graph, (int)orig_w, (int)orig_h, currentWidth, currentHeight); //--- Scale graph background
         if (EnableStatsPanel)                                   //--- Check stats panel
         {
            ArrayCopy(bg_pixels_stats, original_bg_pixels);      //--- Copy to stats background
            ScaleImage(bg_pixels_stats, (int)orig_w, (int)orig_h, currentWidth / 2, currentHeight); //--- Scale stats background
         }
      }
      else                                                       //--- Handle load failure
      {
         Print("Failed to load background image from ::1. Transparent MT5 bmp image.bmp"); //--- Print error
      }
   }
   
   int header_width = currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0); //--- Compute header width
   if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, header_width, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create header canvas
   {
      Print("Failed to create Header Canvas");                   //--- Print error
      return(INIT_FAILED);                                       //--- Return failure
   }
   
   if (!canvasGraph.CreateBitmapLabel(0, 0, canvasGraphName, currentCanvasX, currentCanvasY + header_height + gap_y, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create graph canvas
   {
      Print("Failed to create Graph Canvas");                    //--- Print error
      return(INIT_FAILED);                                       //--- Return failure
   }
   graphCreated = true;                                          //--- Set graph created
   
   if (EnableStatsPanel)                                         //--- Check stats panel
   {
      int statsX = currentCanvasX + currentWidth + PanelGap;     //--- Compute stats X
      if (!canvasStats.CreateBitmapLabel(0, 0, canvasStatsName, statsX, currentCanvasY + header_height + gap_y, currentWidth / 2, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create stats canvas
      {
         Print("Failed to create Stats Canvas");                 //--- Print error
      }
      statsCreated = true;                                       //--- Set stats created
   }
   
   ChartRedraw();                                                //--- Redraw chart
   return(INIT_SUCCEEDED);                                       //--- Return success
}

Im OnInit-Ereignishandler richten wir den Ausgangszustand ein und erstellen die Canvas-Panels für das Dashboard. Wir initialisieren die aktuellen Abmessungen und Positionen durch Eingaben wie „currentWidth“ zu „CanvasWidth“, „currentHeight“ zu „CanvasHeight“, „currentCanvasX“ zu „CanvasX“ und „currentCanvasY“ zu „CanvasY“. Wenn „UseBackground“ wahr ist, laden wir das Ressourcenbild mit ResourceReadImage in „original_bg_pixels“ und erhalten seine ursprüngliche Breite und Höhe. Im Erfolgsfall kopieren wir das Bild in „bg_pixels_graph“ und skalieren es mit „ScaleImage“ auf die aktuellen Abmessungen. Für das Statistikpanel, wenn „EnableStatsPanel“ aktiviert ist, kopieren wir zu „bg_pixels_stats“ und skalieren auf die Hälfte der Breite mit voller Höhe. Bei einem Fehlschlag wird eine Fehlermeldung ausgegeben.

Wir berechnen die Breite der Kopfzeile aus der Breite des Grafik-Panels zuzüglich – falls aktiviert – der Breite des Statistik-Panels und des Abstands dazwischen und erstellen dann die Canvas für die Kopfzeile mit „CreateBitmapLabel“ im Unterfenster Null, Name „canvasHeaderName“, Position, Breite und „header_height“ unter Verwendung von COLOR_FORMAT_ARGB_NORMALIZE, wobei der Fehler ausgegeben und „INIT_FAILED“ zurückgegeben wird, wenn dies nicht erfolgreich war. In ähnlicher Weise erstellen wir den Grafik-Canvas unterhalb der Kopfzeile plus „gap_y“, wobei „graphCreated“ bei Erfolg auf true gesetzt wird oder bei einem Fehler INIT_FAILED zurückgibt. Wenn die Statistik aktiviert ist, berechnen wir ihre x-Position nach dem Diagramm plus „PanelGap“, erstellen die Statistik-Canvas und setzen „statsCreated“ auf true. Schließlich wird das Chart mit „ChartRedraw“ neu gezeichnet und INIT_SUCCEEDED zurückgegeben. So entstehen die Zeichenflächen, aber noch keine eigentliche Zeichnung. Zur Veranschaulichung sehen Sie hier ein Beispiel dafür, was bei der Kompilierung in den Tooltips zu sehen ist.

CANVAS BEREICHE

Wir sehen, dass die Canvas-Bereiche jetzt sichtbar sind. Im nächsten Schritt zeichnen wir die eigentlichen Inhalte dieser Canvas-Objekte. Wir werden mit der Kopfzeile beginnen, aber da wir themenabhängig sein müssen, werden wir zuerst einige Hilfsfunktionen für das Design definieren, die es uns ermöglichen, entweder ein dunkles oder ein helles Design zu erstellen, wo dies möglich ist.

//+------------------------------------------------------------------+
//| Get theme-aware colors                                           |
//+------------------------------------------------------------------+
color GetHeaderColor() { return is_dark_theme ? HeaderColor : LightHeaderColor; }                //--- Return header color
color GetHeaderHoverColor() { return is_dark_theme ? HeaderHoverColor : LightHeaderHoverColor; } //--- Return hover color
color GetHeaderDragColor() { return is_dark_theme ? HeaderDragColor : LightHeaderDragColor; }    //--- Return drag color
color GetStatsLabelColor() { return is_dark_theme ? StatsLabelColor : LightStatsLabelColor; }    //--- Return label color
color GetStatsValueColor() { return is_dark_theme ? StatsValueColor : LightStatsValueColor; }    //--- Return value color
color GetStatsHeaderColor() { return is_dark_theme ? StatsHeaderColor : LightStatsHeaderColor; } //--- Return header color
color GetBorderColor() { return is_dark_theme ? borderColor : LightBorderColor; }                //--- Return border color
color GetTopColor() { return is_dark_theme ? TopColor : LightTopColor; }                         //--- Return top color
color GetBottomColor() { return is_dark_theme ? BottomColor : LightBottomColor; }                //--- Return bottom color
color GetHeaderTextColor() { return is_dark_theme ? clrWhite : LightHeaderTextColor; }           //--- Return text color
color GetIconColor(bool is_drag) { return is_drag ? GetHeaderDragColor() : GetHeaderHoverColor(); } //--- Return icon color

Hier implementieren wir mehrere Getter-Funktionen, um themenabhängige Farben auf der Grundlage des aktuellen „is_dark_theme“-Flags abzurufen und so eine konsistente Darstellung in dunklen und hellen Modi zu gewährleisten, ohne dass an anderer Stelle redundante Prüfungen durchgeführt werden. Die Funktion „GetHeaderColor“ liefert „HeaderColor“ im dunklen Design oder „LightHeaderColor“ im hellen Design für den Hintergrund der Kopfzeile. Wir verwenden dieselbe Logik für alle anderen Funktionen. Mit diesen Hilfsfunktionen können wir nun die Header-Canvas-Objekte erstellen.

//+------------------------------------------------------------------+
//| Draw header on header canvas                                     |
//+------------------------------------------------------------------+
void DrawHeaderOnCanvas()
{
   canvasHeader.Erase(0);                                        //--- Clear canvas
   
   color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Set background
   uint argb_bg = ColorToARGB(header_bg, 255);                   //--- Convert to ARGB
   canvasHeader.FillRectangle(0, 0, canvasHeader.Width() - 1, header_height - 1, argb_bg); //--- Fill background
   
   uint argbBorder = ColorToARGB(GetBorderColor(), 255);         //--- Convert border to ARGB
   canvasHeader.Line(0, 0, canvasHeader.Width() - 1, 0, argbBorder); //--- Draw top border
   canvasHeader.Line(canvasHeader.Width() - 1, 0, canvasHeader.Width() - 1, header_height - 1, argbBorder); //--- Draw right border
   canvasHeader.Line(canvasHeader.Width() - 1, header_height - 1, 0, header_height - 1, argbBorder); //--- Draw bottom border
   canvasHeader.Line(0, header_height - 1, 0, 0, argbBorder);    //--- Draw left border
   
   canvasHeader.FontSet("Arial Bold", 15);                       //--- Set font
   uint argbText = ColorToARGB(GetHeaderTextColor(), 255);       //--- Convert text to ARGB
   canvasHeader.TextOut(10, (header_height - 15) / 2, "Price Dashboard", argbText, TA_LEFT); //--- Draw title
   
   int theme_x = canvasHeader.Width() + theme_x_offset;          //--- Compute theme x
   string theme_symbol = CharToString((uchar)91);                //--- Set theme symbol
   color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Set theme color
   canvasHeader.FontSet("Wingdings", 22);                        //--- Set font
   uint argb_theme = ColorToARGB(theme_color, 255);              //--- Convert to ARGB
   canvasHeader.TextOut(theme_x, (header_height - 22) / 2, theme_symbol, argb_theme, TA_CENTER); //--- Draw theme icon
   
   int min_x = canvasHeader.Width() + minimize_x_offset;         //--- Compute minimize x
   string min_symbol = panels_minimized ? CharToString((uchar)111) : CharToString((uchar)114); //--- Set minimize symbol
   color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Set minimize color
   canvasHeader.FontSet("Wingdings", 22);                        //--- Set font
   uint argb_min = ColorToARGB(min_color, 255);                  //--- Convert to ARGB
   canvasHeader.TextOut(min_x, (header_height - 22) / 2, min_symbol, argb_min, TA_CENTER); //--- Draw minimize icon
   
   int close_x = canvasHeader.Width() + close_x_offset;          //--- Compute close x
   string close_symbol = CharToString((uchar)114);               //--- Set close symbol
   color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Set close color
   canvasHeader.FontSet("Webdings", 22);                         //--- Set font
   uint argb_close = ColorToARGB(close_color, 255);              //--- Convert to ARGB
   canvasHeader.TextOut(close_x, (header_height - 22) / 2, close_symbol, argb_close, TA_CENTER); //--- Draw close icon
   
   canvasHeader.Update();                                        //--- Update canvas
}

Hier implementieren wir die Funktion „DrawHeaderOnCanvas“, um die Kopfzeile auf dem „canvasHeader“-Objekt darzustellen und einen Titel und interaktive Symbole mit dynamischen Farben basierend auf Zuständen wie Ziehen oder Mouseover bereitzustellen. Wir beginnen damit, die Canvas mit der Methode „Erase“ zu löschen, die auf Null gesetzt ist. Wir legen die Hintergrundfarbe nach bestimmten Kriterien fest: wenn „panel_dragging“ wahr ist, verwenden wir „GetHeaderDragColor“; wenn „header_hovered“, verwenden wir „GetHeaderHoverColor“; andernfalls „GetHeaderColor“. Wir konvertieren sie in ARGB mit ColorToARGB bei voller Deckkraft 255 und füllen ein Rechteck von (0,0) bis Breite minus eins und „header_height“ minus eins mit der Methode FillRectangle.

Für die Umrandung konvertieren wir die Farbe der Umrandung von „GetBorderColor“ in ARGB bei 255 und zeichnen Linien mit Line für den oberen, rechten, unteren und linken Rand der Kopfzeile. Wir stellen die Schriftart Arial Bold in Größe fünfzehn mit FontSet ein, konvertieren die Farbe des Kopfzeilentextes von „GetHeaderTextColor“ in ARGB und zeichnen den Titel „Price Dashboard“ bei x zehn vertikal zentriert mit TextOut mit linker Ausrichtung.

Für das Designsymbol berechnen wir sein x als Canvas-Breite plus „theme_x_offset“, setzen das Symbol auf Zeichen 91 aus dem uchar-Cast, die Farbe auf gelb, wenn „theme_hovered“, sonst auf die Textfarbe der Kopfzeile, ändern die Schriftart auf Wingdings bei zweiundzwanzig, konvertieren in ARGB und zeichnen es horizontal und vertikal zentriert mit „TextOut“ und zentrierter Ausrichtung. In ähnlicher Weise wird für das Minimieren-Symbol x mit „minimize_x_offset“ berechnet, das Symbol bedingt auf das Zeichen 111 gesetzt, wenn „panels_minimized“, oder andernfalls auf 114, die Farbe auf gelb bei Mouseover, sonst auf Textfarbe, die Schrift Wingdings verwendet, konvertiert und zentriert gezeichnet. Für das Schließen-Symbol berechnen wir x mit „close_x_offset“, setzen das Symbol auf 114, die Farbe auf rot bei „close_hovered“, sonst Textfarbe, wechseln die Schriftart zu Webdings bei zweiundzwanzig, konvertieren und zeichnen zentriert. Schließlich aktualisieren wir die Canvas mit „Update“, um die Änderungen anzuzeigen. Wenn wir diese Funktion bei der Initialisierung aufrufen, erhalten wir das folgende Ergebnis.

CANVAS-HEADER-RENDERING

Auf dem Bild ist zu erkennen, dass die Kopfzeile der Canvas korrekt beschriftet ist. Als Nächstes zeichnen wir den Inhalt im Grafikpanel, in dem wir die Kurse analysieren und ihr Liniendiagramm erstellen sollen. Im Wesentlichen zeichnen wir also unser eigenes einfaches Kurschart. Beachten Sie jedoch, dass dies nur unser Ansatz ist; es ist der einfachste, den wir für diese Demonstration dachten. Sie können Ihre eigenen komplexen Berechnungen anstellen, wenn Sie dies für richtig halten. Hier ist der Ansatz, mit dem wir das erreichen.

//+------------------------------------------------------------------+
//| Update the price graph on the main Canvas                        |
//+------------------------------------------------------------------+
void UpdateGraphOnCanvas()
{
   canvasGraph.Erase(0);                                         //--- Clear canvas
   
   if (UseBackground && ArraySize(bg_pixels_graph) == currentWidth * currentHeight) //--- Check background
   {
      for (int y = 0; y < currentHeight; y++)                    //--- Loop rows
      {
         for (int x = 0; x < currentWidth; x++)                  //--- Loop columns
         {
            canvasGraph.PixelSet(x, y, bg_pixels_graph[y * currentWidth + x]); //--- Set pixel
         }
      }
   }
   
   uint argbBorder = ColorToARGB(GetBorderColor(), 255);         //--- Convert border to ARGB
   canvasGraph.Line(0, 0, currentWidth - 1, 0, argbBorder);      //--- Draw top outer
   canvasGraph.Line(currentWidth - 1, 0, currentWidth - 1, currentHeight - 1, argbBorder); //--- Draw right outer
   canvasGraph.Line(currentWidth - 1, currentHeight - 1, 0, currentHeight - 1, argbBorder); //--- Draw bottom outer
   canvasGraph.Line(0, currentHeight - 1, 0, 0, argbBorder);     //--- Draw left outer
   canvasGraph.Line(1, 1, currentWidth - 2, 1, argbBorder);      //--- Draw top inner
   canvasGraph.Line(currentWidth - 2, 1, currentWidth - 2, currentHeight - 2, argbBorder); //--- Draw right inner
   canvasGraph.Line(currentWidth - 2, currentHeight - 2, 1, currentHeight - 2, argbBorder); //--- Draw bottom inner
   canvasGraph.Line(1, currentHeight - 2, 1, 1, argbBorder);     //--- Draw left inner
   
   double closePrices[];                                         //--- Declare close array
   ArrayResize(closePrices, graphBars);                          //--- Resize close
   if (CopyClose(_Symbol, _Period, 0, graphBars, closePrices) != graphBars) //--- Copy closes
   {
      Print("Failed to copy close prices");                      //--- Print error
      return;                                                    //--- Exit
   }
   
   datetime timeArr[];                                           //--- Declare time array
   ArrayResize(timeArr, graphBars);                              //--- Resize time
   if (CopyTime(_Symbol, _Period, 0, graphBars, timeArr) != graphBars) //--- Copy times
   {
      Print("Failed to copy times");                             //--- Print error
      return;                                                    //--- Exit
   }
   
   double minPrice = closePrices[0];                             //--- Set initial min
   double maxPrice = closePrices[0];                             //--- Set initial max
   for (int i = 1; i < graphBars; i++)                           //--- Loop prices
   {
      if (closePrices[i] < minPrice) minPrice = closePrices[i];  //--- Update min
      if (closePrices[i] > maxPrice) maxPrice = closePrices[i];  //--- Update max
   }
   double priceRange = maxPrice - minPrice;                      //--- Compute range
   if (priceRange == 0) priceRange = _Point;                     //--- Avoid zero
   
   int graphLeft = 2;                                            //--- Set left margin
   int graphRight = currentWidth - 3;                            //--- Set right margin
   double graphWidth_d = graphRight - graphLeft;                 //--- Compute width
   int graphHeight = currentHeight - 4;                          //--- Compute height
   int bottomY = 2 + graphHeight;                                //--- Set bottom y
   
   int x_pos[];                                                  //--- Declare x positions
   int y_pos[];                                                  //--- Declare y positions
   ArrayResize(x_pos, graphBars);                                //--- Resize x
   ArrayResize(y_pos, graphBars);                                //--- Resize y
   for (int i = 0; i < graphBars; i++)                           //--- Loop bars
   {
      double norm = (graphBars > 1) ? (double)i / (graphBars - 1) : 0.0; //--- Normalize
      x_pos[i] = graphLeft + (int)(norm * graphWidth_d + 0.5);   //--- Set x
      double price = closePrices[graphBars - 1 - i];             //--- Get price (flipped)
      y_pos[i] = 2 + (int)(graphHeight * (maxPrice - price) / priceRange + 0.5); //--- Set y
   }
   
   color lineColor = clrBlue;                                    //--- Set line color
   uint argbLine = ColorToARGB(lineColor, 255);                  //--- Convert to ARGB
   for (int i = 0; i < graphBars - 1; i++)                       //--- Loop segments
   {
      int x1 = (currentWidth - 1) - x_pos[i];                    //--- Set x1 (flipped)
      int y1 = y_pos[i];                                         //--- Set y1
      int x2 = (currentWidth - 1) - x_pos[i + 1];                //--- Set x2 (flipped)
      int y2 = y_pos[i + 1];                                     //--- Set y2
      canvasGraph.LineAA(x1, y1, x2, y2, argbLine);              //--- Draw line
   }
   
   int min_flipped_x = (currentWidth - 1) - graphRight;          //--- Set min flipped x
   int max_flipped_x = (currentWidth - 1) - graphLeft;           //--- Set max flipped x
   for (int colX = min_flipped_x; colX <= max_flipped_x; colX++) //--- Loop columns
   {
      int logical_colX = (currentWidth - 1) - colX;              //--- Get logical x
      int seg = -1;                                              //--- Initialize segment
      for (int j = 0; j < graphBars - 1; j++)                    //--- Loop segments
      {
         if (x_pos[j] <= logical_colX && logical_colX <= x_pos[j + 1]) //--- Check segment
         {
            seg = j;                                             //--- Set segment
            break;                                               //--- Exit
         }
      }
      if (seg == -1) continue;                                   //--- Skip if no segment
      
      double dx = x_pos[seg + 1] - x_pos[seg];                   //--- Compute dx
      double t = (dx > 0) ? (logical_colX - x_pos[seg]) / dx : 0.0; //--- Compute t
      double interpY = y_pos[seg] + t * (y_pos[seg + 1] - y_pos[seg]); //--- Interpolate y
      int topY = (int)(interpY + 0.5);                           //--- Round top y
      
      for (int fillY = topY; fillY < bottomY; fillY++)           //--- Loop fill
      {
         double fadeFactor = (double)(bottomY - fillY) / (bottomY - topY); //--- Compute fade
         uchar alpha = (uchar)(255 * fadeFactor * FogOpacity);   //--- Compute alpha
         uint argbFill = ColorToARGB(lineColor, alpha);          //--- Convert fill
         if (BlendFog)                                           //--- Check blend
         {
            uint currentPixel = canvasGraph.PixelGet(colX, fillY); //--- Get pixel
            uint blendedPixel = BlendPixels(currentPixel, argbFill); //--- Blend pixels
            canvasGraph.PixelSet(colX, fillY, blendedPixel);     //--- Set blended
         }
         else                                                    //--- Handle no blend
         {
            canvasGraph.PixelSet(colX, fillY, argbFill);         //--- Set fill
         }
      }
   }
   
   canvasGraph.FontSet("Arial", 12);                             //--- Set font
   uint argbText = ColorToARGB(is_dark_theme ? clrBlack : clrGray, 255); //--- Convert text
   canvasGraph.TextOut(currentWidth / 2, 10, "Price Graph (" + _Symbol + ")", argbText, TA_CENTER); //--- Draw title
   
   canvasGraph.FontSet("Arial", 12);                             //--- Set font
   
   string newTime = TimeToString(timeArr[0], TIME_DATE | TIME_MINUTES); //--- Get new time
   string oldTime = TimeToString(timeArr[graphBars - 1], TIME_DATE | TIME_MINUTES); //--- Get old time
   canvasGraph.TextOut(10, currentHeight - 15, newTime, argbText, TA_LEFT); //--- Draw new time
   canvasGraph.TextOut(currentWidth - 10, currentHeight - 15, oldTime, argbText, TA_RIGHT); //--- Draw old time
   
   if (resize_hovered || resizing)                               //--- Check resize state
   {
      ENUM_RESIZE_MODE active_mode = resizing ? resize_mode : hover_mode; //--- Get active mode
      if (active_mode == NONE)                                       //--- Check none
      {
         canvasGraph.Update();                                   //--- Update canvas
         return;                                                 //--- Exit
      }
      
      string icon_font = "Wingdings 3";                          //--- Set icon font
      int icon_size = 25;                                        //--- Set icon size
      uchar icon_code;                                           //--- Declare code
      int angle = 0;                                             //--- Set angle
      switch (active_mode)                                       //--- Switch mode
      {
         case BOTTOM: 
            icon_code = (uchar)'2';                              //--- Set bottom code
            angle = 0;                                           //--- Set angle
            break;
         case RIGHT: 
            icon_code = (uchar)'1';                              //--- Set right code
            angle = 0;                                           //--- Set angle
            break;
         case BOTTOM_RIGHT: 
            icon_code = (uchar)'2';                              //--- Set corner code
            angle = 450;                                         //--- Set angle
            break;
         default: canvasGraph.Update(); return;
      }
      string icon_symbol = CharToString(icon_code);              //--- Set symbol
      
      color icon_color = GetIconColor(resizing);                 //--- Get icon color
      uint argb_icon = ColorToARGB(icon_color, 255);             //--- Convert to ARGB
      
      canvasGraph.FontSet(icon_font, icon_size);                 //--- Set font
      canvasGraph.FontAngleSet(angle);                           //--- Set angle
      
      int icon_x = 0;                                            //--- Initialize x
      int icon_y = 0;                                            //--- Initialize y
      
      switch (active_mode)                                       //--- Switch for position
      {
         case BOTTOM:
            icon_x = MathMax(0, MathMin(hover_mouse_local_x - (icon_size / 2), currentWidth - icon_size)); //--- Set x
            icon_y = currentHeight - icon_size - 2;              //--- Set y
            break;
         case RIGHT:
            icon_y = MathMax(0, MathMin(hover_mouse_local_y - (icon_size / 2), currentHeight - icon_size)); //--- Set y
            icon_x = currentWidth - icon_size - 2;               //--- Set x
            break;
         case BOTTOM_RIGHT:
            icon_x = currentWidth - icon_size - 10;              //--- Set x
            icon_y = currentHeight - icon_size;                  //--- Set y
            break;
         default: break;
      }
      
      canvasGraph.TextOut(icon_x, icon_y, icon_symbol, argb_icon, TA_LEFT | TA_TOP); //--- Draw icon
      canvasGraph.FontAngleSet(0);                               //--- Reset angle
   }
   
   canvasGraph.Update();                                         //--- Update canvas
}

Hier implementieren wir die Funktion „UpdateGraphOnCanvas“, um das Kurschart auf dem „canvasGraph“-Objekt darzustellen, das die Schlusskurse der jüngsten Bars als Liniendiagramm mit gefüllten Bereichen, Beschriftungen und optionalen Größenänderungsindikatoren darstellt. Wir beginnen damit, die Canvas zu löschen, wobei Erase auf Null gesetzt wird. Wenn „UseBackground“ wahr ist und „bg_pixels_graph“ mit den aktuellen Abmessungen übereinstimmt, durchlaufen wir Höhe und Breite in einer Schleife, um jedes Pixel aus dem skalierten Hintergrund-Array mit der PixelSet-Methode zu setzen. Wir konvertieren die Farbe des Rahmens von „GetBorderColor“ in ARGB bei voller Deckkraft mit ColorToARGB und zeichnen äußere und innere Ränder mit „Line“ für den oberen, rechten, unteren und linken Rand, wodurch ein doppelter Rahmeneffekt entsteht.

Wir deklarieren ein Double-Array „closePrices“ und passen die Größe an „graphBars“ an, kopieren die Schlusskurse mit CopyClose für das aktuelle Symbol und den aktuellen Zeitraum, beginnend bei Bar Null, geben einen Fehler aus und beenden die Anwendung, wenn sie unvollständig ist. In ähnlicher Weise holen wir Zeiten in ein „timeArr“-Datetime-Array mit CopyTime und behandeln Fehler. Wir finden die Minimal- und MaximalKurse, indem wir auf den ersten Schlusskurs initialisieren und in einer Schleife aktualisieren, den Bereich berechnen und bei Null auf _Point zurückgreifen, um Divisionsprobleme zu vermeiden.

Wir setzen die Ränder wie „graphLeft“ auf zwei, „graphRight“ auf Breite minus drei, berechnen die effektive Breite und Höhe und unten y auf zwei plus Höhe. Wir passen die Größe der Integer-Arrays „x_pos“ und „y_pos“ an „graphBars“ an und führen dann eine Schleife durch, um die Positionen zu normalisieren: x als links plus normalisierter Bruchteil der Breite gerundet, y als zwei plus skaliert (max minus Kurs) über den Bereich gerundet, wobei der Index des Kurs-Arrays für den letzten Wert auf der linken Seite gespiegelt wird. Wir setzen die Linienfarbe auf Blau, konvertieren in ARGB und durchlaufen in einer Schleife über die Segmente, um mit LineAA Anti-Aliasing-Linien zu zeichnen, wobei die X-Positionen für die Ausrichtung von rechts nach links gespiegelt werden.

Zum Ausfüllen berechnen wir den minimalen und maximalen umgedrehten x-Wert, durchlaufen die Spalten von min bis max umgedreht (von rechts nach links), leiten den logischen x-Wert ab, suchen das Segment, das ihn enthält, indem wir die Positionen überprüfen, und überspringen es, falls keines vorhanden ist. Wir berechnen den Interpolationsfaktor t, interpolieren y und runden auf das TopY. Anschließend iterieren wir von topY bis bottomY, berechnen Fade-Faktor von unten, Alpha als 255 mal Fade mal „FogOpacity“, konvertieren Füllung zu ARGB mit diesem Alpha. Wenn „BlendFog“ wahr ist, wird das aktuelle Pixel mit „PixelGet“ ermittelt, mit „BlendPixels“ überblendet und gesetzt; andernfalls wird es direkt mit „PixelSet“ gesetzt. Für das Überblenden der Pixel verwenden wir eine nutzerdefinierte Hilfsfunktion, deren Codeschnipsel wie folgt aussieht.

//+------------------------------------------------------------------+
//| Alpha blending function for two ARGB colors                      |
//+------------------------------------------------------------------+
uint BlendPixels(uint bg, uint fg)
{
   uchar bgA = (uchar)((bg >> 24) & 0xFF);                       //--- Get bg alpha
   uchar bgR = (uchar)((bg >> 16) & 0xFF);                       //--- Get bg red
   uchar bgG = (uchar)((bg >> 8) & 0xFF);                        //--- Get bg green
   uchar bgB = (uchar)(bg & 0xFF);                               //--- Get bg blue
   
   uchar fgA = (uchar)((fg >> 24) & 0xFF);                       //--- Get fg alpha
   uchar fgR = (uchar)((fg >> 16) & 0xFF);                       //--- Get fg red
   uchar fgG = (uchar)((fg >> 8) & 0xFF);                        //--- Get fg green
   uchar fgB = (uchar)(fg & 0xFF);                               //--- Get fg blue
   
   if (fgA == 0) return bg;                                      //--- Return bg if transparent
   if (fgA == 255) return fg;                                    //--- Return fg if opaque
   
   double alphaFg = fgA / 255.0;                                 //--- Compute fg alpha
   double alphaBg = 1.0 - alphaFg;                               //--- Compute bg alpha
   
   uchar outR = (uchar)(fgR * alphaFg + bgR * alphaBg);          //--- Blend red
   uchar outG = (uchar)(fgG * alphaFg + bgG * alphaBg);          //--- Blend green
   uchar outB = (uchar)(fgB * alphaFg + bgB * alphaBg);          //--- Blend blue
   uchar outA = (uchar)(fgA + bgA * alphaBg);                    //--- Blend alpha
   
   return (((uint)outA) << 24) | (((uint)outR) << 16) | (((uint)outG) << 8) | ((uint)outB); //--- Return blended
}

Für die Funktion verwenden wir einen ähnlichen Ansatz wie bei der ARGB-Funktion, indem wir bitweise Operationen verwenden. Wir haben der Klarheit halber Kommentare hinzugefügt. Wir stellen die Schriftart Arial auf zwölf ein, konvertieren die Textfarbe (schwarz in dunkel, grau in hell) in ARGB und zeichnen einen zentrierten Titel „Kurschart“ mit einem Symbol am oberen Rand. Wir formatieren die neuesten und ältesten Zeiten mit TimeToString unter Verwendung von Datum und Minuten, zeichnen linksbündig unten links und rechtsbündig unten rechts.

Wenn „resize_hovered“ oder „resizing“ wahr ist, erhalten wir das aktive Design von „resize_mode“ oder „hover_mode“ und beenden den Vorgang vorzeitig, wenn es keinen gibt. Wir setzen die Schriftart des Symbols auf Wingdings 3 bei fünfundzwanzig, bestimmen Code und Winkel auf der Grundlage des Designs (unten/rechts als '2'/'1' bei Null, Ecke '2' bei 450) und konvertieren das Symbol mit der Funktion CharToString. Was die Symbole angeht, können Sie die besten auswählen, die zu Ihrem Stil passen. Wir haben uns für diese entschieden, da MQL5 noch keine eingebauten Cursor-Änderungen hat, also mussten wir kreativ sein. Hier finden Sie eine Visualisierung der Schriftartsymbole, die Sie verwenden können.

SYMBOL-FONTS

Als Nächstes holen wir uns die Farbe des Symbols aus „GetIconColor“ mit dem Flag für die Größenänderung, konvertieren in ARGB, setzen Schriftart und Winkel mit FontSet und FontAngleSet, berechnen die Position basierend auf dem Design mit „MathMax“/“MathMin“ für die Begrenzung und schweben lokale Koordinaten oder feste Offsets, zeichnen mit TextOut links oben ausgerichtet, und setzen den Winkel auf Null. Schließlich aktualisieren wir die Canvas mit Update, um die Grafik anzuzeigen. Wenn wir die Funktion im Initialisierungs-Ereignishandler aufrufen, erhalten wir das folgende Ergebnis.

GRAFIK-CANVAS

Nachdem das Grafik-Canvas gerendert wurde, müssen wir nun das andere Statistik-Panel auf der rechten Seite des Grafik-Canvas erstellen. In diesem Fall wollen wir etwas weiter gehen und zwei Farben für den Hintergrund mischen, wo sie sich treffen, indem wir lineare Interpolation verwenden, da dies eine einfache Sache ist, die wir haben wollen, und auch die Randfarben basierend auf den ausgewählten Hintergrundfarben verdunkeln, anstatt der statischen Randfarben, die wir bisher für die Kopfzeile und das Grafik-Canvas hatten. Um dies mit Leichtigkeit zu erreichen, benötigen wir einige Hilfsfunktionen.

//+------------------------------------------------------------------+
//| Linear interpolation between two colors                          |
//+------------------------------------------------------------------+
color InterpolateColor(color start, color end, double factor)
{
   uchar r1 = (uchar)((start >> 16) & 0xFF);                     //--- Get start red
   uchar g1 = (uchar)((start >> 8) & 0xFF);                      //--- Get start green
   uchar b1 = (uchar)(start & 0xFF);                             //--- Get start blue
   
   uchar r2 = (uchar)((end >> 16) & 0xFF);                       //--- Get end red
   uchar g2 = (uchar)((end >> 8) & 0xFF);                        //--- Get end green
   uchar b2 = (uchar)(end & 0xFF);                               //--- Get end blue
   
   uchar r = (uchar)(r1 + factor * (r2 - r1));                   //--- Interpolate red
   uchar g = (uchar)(g1 + factor * (g2 - g1));                   //--- Interpolate green
   uchar b = (uchar)(b1 + factor * (b2 - b1));                   //--- Interpolate blue
   
   return (r << 16) | (g << 8) | b;                              //--- Return color
}

//+------------------------------------------------------------------+
//| Darken a color by a factor (0.0 to 1.0)                         |
//+------------------------------------------------------------------+
color DarkenColor(color colorValue, double factor)
{
   int blue = int((colorValue & 0xFF) * factor);                 //--- Darken blue
   int green = int(((colorValue >> 8) & 0xFF) * factor);         //--- Darken green
   int red = int(((colorValue >> 16) & 0xFF) * factor);          //--- Darken red
   return (color)(blue | (green << 8) | (red << 16));            //--- Return darkened
}

Zunächst implementieren wir die Funktion „InterpolateColor“, die eine lineare Überblendung zwischen zwei Farben auf der Grundlage eines Faktors von null bis eins ermöglicht und die interpolierte Farbe für Effekte wie Farbverläufe zurückgibt. Er benötigt die Farbparameter start und end sowie einen Doppelfaktor. Wir extrahieren die Rot-, Grün- und Blau-Komponenten aus dem Anfang, indem wir die Bits um sechzehn/acht/null nach rechts verschieben und mit 0xFF nach uchar maskieren, ebenso für das Ende, genau wie bei den anderen farbbasierten Funktionen. Wir interpolieren jeden Kanal als einen uchar von Startwert plus Faktor mal die Differenz, dann kombinieren wir mit rot verschoben links sechzehn, grün acht und blau Null mit Bit-Verschiebungen und OR.

Als Nächstes erstellen wir die Funktion „DarkenColor“, um die Helligkeit einer Farbe um einen Faktor von null bis eins zu verringern, wobei der Wert eins die Farbe unverändert lässt und die niedrigeren Werte sie abdunkeln und die angepasste Farbe zurückgeben. Die Funktion nimmt den Farbwert „colorValue“ und einen Faktor vom Typ „double“ entgegen, dunkelt Blau um den Faktor des Blauanteils der Maske 0xFF ab, Grün um den Faktor der um acht verschobenen Maske und Rot um den Faktor der um sechzehn verschobenen Maske. Als Ergebnis wird die Kombination aus Blau ODER Grün (um acht verschoben) ODER Rot (um sechzehn verschoben) zurückgegeben, die in den Typ „color“ umgewandelt wird. Wir können diese Funktionen nun bei der Erstellung des Statistikpanels verwenden.

//+------------------------------------------------------------------+
//| Update the stats on the second Canvas                            |
//+------------------------------------------------------------------+
void UpdateStatsOnCanvas()
{
   canvasStats.Erase(0);                                         //--- Clear canvas
   
   int statsWidth = currentWidth / 2;                            //--- Compute width
   if (UseBackground && ArraySize(bg_pixels_stats) == statsWidth * currentHeight) //--- Check background
   {
      for (int y = 0; y < currentHeight; y++)                    //--- Loop rows
      {
         for (int x = 0; x < statsWidth; x++)                    //--- Loop columns
         {
            canvasStats.PixelSet(x, y, bg_pixels_stats[y * statsWidth + x]); //--- Set pixel
         }
      }
   }
   
   if (StatsBackgroundMode != NoColor)                           //--- Check mode
   {
      for (int y = 0; y < currentHeight; y++)                    //--- Loop rows
      {
         double factor = (double)y / (currentHeight - 1);        //--- Compute factor
         color currentColor = (StatsBackgroundMode == SingleColor) ? GetTopColor() : InterpolateColor(GetTopColor(), GetBottomColor(), factor); //--- Get color
         uchar alpha = (uchar)(255 * BackgroundOpacity);         //--- Compute alpha
         uint argbFill = ColorToARGB(currentColor, alpha);       //--- Convert fill
         
         for (int x = 0; x < statsWidth; x++)                    //--- Loop columns
         {
            uint currentPixel = canvasStats.PixelGet(x, y);      //--- Get pixel
            uint blendedPixel = BlendPixels(currentPixel, argbFill); //--- Blend
            canvasStats.PixelSet(x, y, blendedPixel);            //--- Set blended
         }
      }
   }
   
   if (StatsBackgroundMode != NoColor)                           //--- Check mode for borders
   {
      double reduction = BorderOpacityPercentReduction / 100.0;  //--- Compute reduction
      double opacity = MathMax(0.0, MathMin(1.0, BackgroundOpacity * (1.0 - reduction))); //--- Compute opacity
      uchar alpha = (uchar)(255 * opacity);                      //--- Set alpha
      double darkenReduction = BorderDarkenPercent / 100.0;      //--- Compute darken
      double darkenFactor = MathMax(0.0, MathMin(1.0, 1.0 - darkenReduction)); //--- Set factor
      
      for (int y = 0; y < currentHeight; y++)                    //--- Loop vertical
      {
         double factor = (StatsBackgroundMode == SingleColor) ? 0.0 : (double)y / (currentHeight - 1); //--- Get factor
         color baseColor = (StatsBackgroundMode == SingleColor) ? GetTopColor() : InterpolateColor(GetTopColor(), GetBottomColor(), factor); //--- Get base
         color darkColor = DarkenColor(baseColor, darkenFactor); //--- Darken color
         uint argb = ColorToARGB(darkColor, alpha);              //--- Convert to ARGB
         
         canvasStats.PixelSet(0, y, argb);                       //--- Set left outer
         canvasStats.PixelSet(1, y, argb);                       //--- Set left inner
         
         canvasStats.PixelSet(statsWidth - 1, y, argb);          //--- Set right outer
         canvasStats.PixelSet(statsWidth - 2, y, argb);          //--- Set right inner
      }
      
      double factorTop = 0.0;                                    //--- Set top factor
      color baseTop = GetTopColor();                             //--- Get top base
      color darkTop = DarkenColor(baseTop, darkenFactor);        //--- Darken top
      uint argbTop = ColorToARGB(darkTop, alpha);                //--- Convert top
      for (int x = 0; x < statsWidth; x++)                       //--- Loop top
      {
         canvasStats.PixelSet(x, 0, argbTop);                    //--- Set top outer
         canvasStats.PixelSet(x, 1, argbTop);                    //--- Set top inner
      }
      
      double factorBot = (StatsBackgroundMode == SingleColor) ? 0.0 : 1.0; //--- Set bottom factor
      color baseBot = (StatsBackgroundMode == SingleColor) ? GetTopColor() : GetBottomColor(); //--- Get bottom base
      color darkBot = DarkenColor(baseBot, darkenFactor);        //--- Darken bottom
      uint argbBot = ColorToARGB(darkBot, alpha);                //--- Convert bottom
      for (int x = 0; x < statsWidth; x++)                       //--- Loop bottom
      {
         canvasStats.PixelSet(x, currentHeight - 1, argbBot);    //--- Set bottom outer
         canvasStats.PixelSet(x, currentHeight - 2, argbBot);    //--- Set bottom inner
      }
   }
   else                                                          //--- Handle no color
   {
      uint argbBorder = ColorToARGB(GetBorderColor(), 255);      //--- Convert border
      canvasStats.Line(0, 0, statsWidth - 1, 0, argbBorder);     //--- Draw top outer
      canvasStats.Line(statsWidth - 1, 0, statsWidth - 1, currentHeight - 1, argbBorder); //--- Draw right outer
      canvasStats.Line(statsWidth - 1, currentHeight - 1, 0, currentHeight - 1, argbBorder); //--- Draw bottom outer
      canvasStats.Line(0, currentHeight - 1, 0, 0, argbBorder);  //--- Draw left outer
      canvasStats.Line(1, 1, statsWidth - 2, 1, argbBorder);     //--- Draw top inner
      canvasStats.Line(statsWidth - 2, 1, statsWidth - 2, currentHeight - 2, argbBorder); //--- Draw right inner
      canvasStats.Line(statsWidth - 2, currentHeight - 2, 1, currentHeight - 2, argbBorder); //--- Draw bottom inner
      canvasStats.Line(1, currentHeight - 2, 1, 1, argbBorder);  //--- Draw left inner
   }
   
   color labelColor = GetStatsLabelColor();                      //--- Get label color
   color valueColor = GetStatsValueColor();                      //--- Get value color
   color headerColor = GetStatsHeaderColor();                    //--- Get header color
   
   int yPos = 20;                                                //--- Set initial y
   canvasStats.FontSet("Arial Bold", StatsHeaderFontSize);       //--- Set header font
   uint argbHeader = ColorToARGB(headerColor, 255);              //--- Convert header
   canvasStats.TextOut(statsWidth / 2, yPos, "Account Stats", argbHeader, TA_CENTER); //--- Draw account header
   yPos += 30;                                                   //--- Increment y
   
   canvasStats.FontSet("Arial Bold", StatsFontSize);             //--- Set font
   uint argbLabel = ColorToARGB(labelColor, 255);                //--- Convert label
   uint argbValue = ColorToARGB(valueColor, 255);                //--- Convert value
   
   canvasStats.TextOut(10, yPos, "Name:", argbLabel, TA_LEFT);   //--- Draw name label
   canvasStats.TextOut(statsWidth - 10, yPos, AccountInfoString(ACCOUNT_NAME), argbValue, TA_RIGHT); //--- Draw name value
   yPos += 20;                                                   //--- Increment y
   
   canvasStats.TextOut(10, yPos, "Balance:", argbLabel, TA_LEFT); //--- Draw balance label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2), argbValue, TA_RIGHT); //--- Draw balance value
   yPos += 20;                                                   //--- Increment y
   
   canvasStats.TextOut(10, yPos, "Equity:", argbLabel, TA_LEFT); //--- Draw equity label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2), argbValue, TA_RIGHT); //--- Draw equity value
   yPos += 30;                                                   //--- Increment y
   
   canvasStats.FontSet("Arial Bold", StatsHeaderFontSize);       //--- Set header font
   canvasStats.TextOut(statsWidth / 2, yPos, "Current Bar Stats", argbHeader, TA_CENTER); //--- Draw bar header
   yPos += 30;                                                   //--- Increment y
   
   canvasStats.FontSet("Arial Bold", StatsFontSize);             //--- Set font
   
   double barOpen = iOpen(_Symbol, _Period, 0);                  //--- Get open
   double barHigh = iHigh(_Symbol, _Period, 0);                  //--- Get high
   double barLow = iLow(_Symbol, _Period, 0);                    //--- Get low
   double barClose = iClose(_Symbol, _Period, 0);                //--- Get close
   
   canvasStats.TextOut(10, yPos, "Open:", argbLabel, TA_LEFT);   //--- Draw open label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barOpen, _Digits), argbValue, TA_RIGHT); //--- Draw open value
   yPos += 20;                                                   //--- Increment y
   
   canvasStats.TextOut(10, yPos, "High:", argbLabel, TA_LEFT);   //--- Draw high label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barHigh, _Digits), argbValue, TA_RIGHT); //--- Draw high value
   yPos += 20;                                                   //--- Increment y
   
   canvasStats.TextOut(10, yPos, "Low:", argbLabel, TA_LEFT);    //--- Draw low label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barLow, _Digits), argbValue, TA_RIGHT); //--- Draw low value
   yPos += 20;                                                   //--- Increment y
   
   canvasStats.TextOut(10, yPos, "Close:", argbLabel, TA_LEFT);  //--- Draw close label
   canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barClose, _Digits), argbValue, TA_RIGHT); //--- Draw close value
   
   canvasStats.Update();                                         //--- Update canvas
}

Hier implementieren wir die Funktion „UpdateStatsOnCanvas“, um das Statistik-Panel auf dem „canvasStats“-Objekt darzustellen, das Konto- und aktuelle Kennzahlen der Bars mit dem Design der Hintergründe, Füllungen, Rahmen und Text anzeigt. Wir löschen die Canvas, indem wir Erase auf Null setzen. Wenn „UseBackground“ wahr ist und „bg_pixels_stats“ mit den Dimensionen übereinstimmt (halbe Grafikbreite mal Höhe), wird in einer Schleife über Zeilen und Spalten durchlaufen, um jedes Pixel aus dem skalierten Hintergrund-Array mit der Methode PixelSet zu setzen.

Wenn „StatsBackgroundMode“ nicht „NoColor“ ist, berechnen wir in einer Schleife über die Höhe einen vertikalen Faktor, bestimmen die Zeilenfarbe als „GetTopColor“, wenn es sich um einen Einzeldesign handelt, oder interpoliert zwischen oben und unten mit „InterpolateColor“, wenn es sich um einen Farbverlauf handelt, berechnen Alpha aus „BackgroundOpacity“ mal 255, konvertieren in ARGB. Für jedes x in der Zeile lesen wir das aktuelle Pixel mit PixelGet aus, mischen es mit der Füllung mit „BlendPixels“ und setzen das gemischte Pixel.

Für Ränder in Füllmodi berechnen wir die reduzierte Deckkraft aus „BorderOpacityPercentReduction“ geteilt durch 100,0, geklemmt auf 0,0 bis 1,0 mal „BackgroundOpacity“, Alpha als 255 mal das und den Verdunkelungsfaktor als 1,0 minus „BorderDarkenPercent“ über 100,0, geklemmt. Wir iterieren in einer Schleife über y, um den Zeilenfaktor zu erhalten (0,0, wenn einzeln, sonst normalisiert), Grundfarbe als obere oder interpolierte Farbe, Abdunkeln mit „DarkenColor“, Konvertierung in ARGB mit Alpha, Setzen der linken äußeren/inneren und rechten äußeren/inneren Pixel. Für die oberste Zeile verwenden wir den Faktor 0,0, die Grundfarbe oben, Verdunkeln, ARGB, iterieren über x, um oben außen/innen einzustellen. Für unten, Faktor 1,0 oder 0,0, wenn einfach, Basis unten oder oben, abdunkeln, ARGB, unten außen/innen einstellen. Wenn keine Füllung, konvertieren wir den Rahmen von „GetBorderColor“ in ARGB bei 255, zeichnen äußere und innere horizontale/vertikale Linien mit „Line“ für oben/rechts/unten/links.

Wir rufen die Farben des Designs für Beschriftungen, Werte und Überschriften mithilfe von Gettern ab. Anfangswert y auf 20 setzen, Schriftart Arial Bold bei „StatsHeaderFontSize“, Kopfzeile in ARGB konvertieren, Kontostatistiken mit TextOut zentriert zeichnen, y um 30 erhöhen.

Schriftart auf Arial Bold bei „StatsFontSize“ setzen, Label und Wert in ARGB konvertieren. Den Namen zeichnen wir linksbündig: bei x zehn, rechtsbündig Kontoname aus AccountInfoString mit ACCOUNT_NAME bei Breite minus zehn, Inkrement y zwanzig. Ähnlich für den Kontostand mit AccountInfoDouble „ACCOUNT_BALANCE“ auf zwei Dezimalstellen und Equity mit ACCOUNT_EQUITY. Inkrementieren von y um 30, Schriftart der Kopfzeile festlegen, die Statistik der aktuellen Bar zentriert zeichnen, y um 30 erhöhen. Schriftart zurücksetzen, aktuellen Bar Open/High/Low/Close mit iOpen/iHigh/iLow/iClose für Symbol/Periode/Bar Zero abrufen. Open: Beschriftung und Wert in _Digits Dezimalen rechtsbündig zeichnen, y zwanzig inkrementieren; für High:, Low:, Close: wiederholen. Schließlich wird die Canvas mit Update, aktualisiert um die Statistiken anzuzeigen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

STATISTIKPANEL

Aus dem Bild können wir ersehen, dass wir das Statistikpanel mit der Farbinterpolation erstellt haben. Wenn Sie auf die Interpolation verzichten wollen, können Sie einen harten Übergang ohne Interpolation wie folgt durchführen: Lassen Sie die Mathematik einfach weg.

//+------------------------------------------------------------------+
//| Color selection WITHOUT interpolation (hard switch only)         |
//+------------------------------------------------------------------+
color InterpolateColor(color start, color end, double factor)
{
   // Clamp factor just to be safe
   if(factor <= 0.0)
      return start;

   if(factor >= 1.0)
      return end;

   // HARD boundary — no mixing at all
   return (factor < 0.5 ? start : end);
}

Wenn Sie diesen Ansatz wählen, erhalten Sie das folgende Ergebnis.

ERGEBNIS DER FARBMISCHUNG FÜR HARTE GRENZFLÄCHEN

Auf dem Bild ist zu erkennen, dass sich die Grenzen nicht linear vermischen. Es liegt also wieder an Ihnen, den Ansatz zu wählen, der zu Ihrem Stil passt. Damit ist das Dashboard-Rendering für das dunkle Design, den wir als Standard ausgewählt haben, abgeschlossen. Um die Interaktion mit dem Chart zu ermöglichen, müssen wir die Mausbewegungen während der Initialisierung einschalten. Jetzt sieht der Initialisierungs-Event-Handler am Ende wie folgt aus.

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit()

   //--- Existing init logic
   
   DrawHeaderOnCanvas();                                         //--- Draw header
   UpdateGraphOnCanvas();                                        //--- Update graph
   if (EnableStatsPanel) UpdateStatsOnCanvas();                  //--- Update stats
   
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);             //--- Enable mouse events
   ChartRedraw();                                                //--- Redraw chart
   return(INIT_SUCCEEDED);                                       //--- Return success
}

Wir verwenden einfach die Funktion ChartSetInteger, um die Erkennung der Mausbewegung auf true zu setzen. Damit können wir einige Hilfsfunktionen erstellen, um die Mauszustände zu verfolgen und Änderungen vorzunehmen, wenn wir mit unseren Dashboard-Objekten interagieren.

//+------------------------------------------------------------------+
//| Check if mouse is over header (excluding buttons)                |
//+------------------------------------------------------------------+
bool IsMouseOverHeader(int mouse_x, int mouse_y)
{
   int header_x = currentCanvasX;                                //--- Get header x
   int header_y = currentCanvasY;                                //--- Get header y
   int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width
   int header_h = header_height;                                 //--- Get height
   
   if (mouse_x < header_x || mouse_x > header_x + header_w || mouse_y < header_y || mouse_y > header_y + header_h) return false; //--- Check outside
   
   int theme_left = header_x + header_w + theme_x_offset - button_size / 2; //--- Compute theme left
   int theme_right = theme_left + button_size;                   //--- Compute theme right
   int theme_top = header_y;                                     //--- Set theme top
   int theme_bottom = theme_top + header_h;                      //--- Compute theme bottom
   if (mouse_x >= theme_left && mouse_x <= theme_right && mouse_y >= theme_top && mouse_y <= theme_bottom) return false; //--- Check in theme
   
   int min_left = header_x + header_w + minimize_x_offset - button_size / 2; //--- Compute minimize left
   int min_right = min_left + button_size;                       //--- Compute minimize right
   int min_top = header_y;                                       //--- Set minimize top
   int min_bottom = min_top + header_h;                          //--- Compute minimize bottom
   if (mouse_x >= min_left && mouse_x <= min_right && mouse_y >= min_top && mouse_y <= min_bottom) return false; //--- Check in minimize
   
   int close_left = header_x + header_w + close_x_offset - button_size / 2; //--- Compute close left
   int close_right = close_left + button_size;                   //--- Compute close right
   int close_top = header_y;                                     //--- Set close top
   int close_bottom = close_top + header_h;                      //--- Compute close bottom
   if (mouse_x >= close_left && mouse_x <= close_right && mouse_y >= close_top && mouse_y <= close_bottom) return false; //--- Check in close
   
   return true;                                                  //--- Return in header
}

//+------------------------------------------------------------------+
//| Check if mouse over theme button                                 |
//+------------------------------------------------------------------+
bool IsMouseOverTheme(int mouse_x, int mouse_y)
{
   int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width
   int theme_left = currentCanvasX + header_w + theme_x_offset - button_size / 2; //--- Compute left
   int theme_right = theme_left + button_size;                   //--- Compute right
   int theme_top = currentCanvasY;                               //--- Set top
   int theme_bottom = theme_top + header_height;                 //--- Compute bottom
   return (mouse_x >= theme_left && mouse_x <= theme_right && mouse_y >= theme_top && mouse_y <= theme_bottom); //--- Check in theme
}

//+------------------------------------------------------------------+
//| Check if mouse over minimize button                              |
//+------------------------------------------------------------------+
bool IsMouseOverMinimize(int mouse_x, int mouse_y)
{
   int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width
   int min_left = currentCanvasX + header_w + minimize_x_offset - button_size / 2; //--- Compute left
   int min_right = min_left + button_size;                       //--- Compute right
   int min_top = currentCanvasY;                                 //--- Set top
   int min_bottom = min_top + header_height;                     //--- Compute bottom
   return (mouse_x >= min_left && mouse_x <= min_right && mouse_y >= min_top && mouse_y <= min_bottom); //--- Check in minimize
}

//+------------------------------------------------------------------+
//| Check if mouse over close button                                 |
//+------------------------------------------------------------------+
bool IsMouseOverClose(int mouse_x, int mouse_y)
{
   int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width
   int close_left = currentCanvasX + header_w + close_x_offset - button_size / 2; //--- Compute left
   int close_right = close_left + button_size;                   //--- Compute right
   int close_top = currentCanvasY;                               //--- Set top
   int close_bottom = close_top + header_height;                 //--- Compute bottom
   return (mouse_x >= close_left && mouse_x <= close_right && mouse_y >= close_top && mouse_y <= close_bottom); //--- Check in close
}

//+------------------------------------------------------------------+
//| Check if mouse over resize borders                               |
//+------------------------------------------------------------------+
bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rmode)
{
   if (panels_minimized) return false;                           //--- Check if minimized
   int graph_x = currentCanvasX;                                 //--- Get graph x
   int graph_y = currentCanvasY + header_height + gap_y;         //--- Get graph y
   int graph_right = graph_x + currentWidth;                     //--- Compute right
   int graph_bottom = graph_y + currentHeight;                   //--- Compute bottom
   bool over_right = (mx >= graph_right - resize_thickness && mx <= graph_right + resize_thickness) && (my >= graph_y && my <= graph_bottom); //--- Check right
   bool over_bottom = (my >= graph_bottom - resize_thickness && my <= graph_bottom + resize_thickness) && (mx >= graph_x && mx <= graph_right); //--- Check bottom
   if (over_bottom && over_right)                                //--- Check corner
   {
      rmode = BOTTOM_RIGHT;                                      //--- Set bottom-right
      return true;                                               //--- Return true
   }
   else if (over_bottom)                                         //--- Check bottom only
   {
      rmode = BOTTOM;                                            //--- Set bottom
      return true;                                               //--- Return true
   }
   else if (over_right)                                          //--- Check right only
   {
      rmode = RIGHT;                                             //--- Set right
      return true;                                               //--- Return true
   }
   return false;                                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Toggle theme                                                     |
//+------------------------------------------------------------------+
void ToggleTheme()
{
   is_dark_theme = !is_dark_theme;                               //--- Switch theme
   Print("Switched to ", (is_dark_theme ? "Dark" : "Light"), " theme"); //--- Print switch
   DrawHeaderOnCanvas();                                         //--- Redraw header
   UpdateGraphOnCanvas();                                        //--- Update graph
   if (EnableStatsPanel) UpdateStatsOnCanvas();                  //--- Update stats
   ChartRedraw();                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Toggle minimize state                                            |
//+------------------------------------------------------------------+
void ToggleMinimize()
{
   panels_minimized = !panels_minimized;                         //--- Toggle minimized
   if (panels_minimized)                                         //--- Handle minimize
   {
      canvasGraph.Destroy();                                     //--- Destroy graph
      graphCreated = false;                                      //--- Reset graph flag
      if (EnableStatsPanel)                                      //--- Check stats
      {
         canvasStats.Destroy();                                  //--- Destroy stats
         statsCreated = false;                                   //--- Reset stats flag
      }
   }
   else                                                          //--- Handle maximize
   {
      if (!canvasGraph.CreateBitmapLabel(0, 0, canvasGraphName, currentCanvasX, currentCanvasY + header_height + gap_y, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Recreate graph
      {
         Print("Failed to recreate Graph Canvas");               //--- Print error
      }
      graphCreated = true;                                       //--- Set graph flag
      UpdateGraphOnCanvas();                                     //--- Update graph
      if (EnableStatsPanel)                                      //--- Check stats
      {
         int statsX = currentCanvasX + currentWidth + PanelGap;  //--- Compute stats X
         if (!canvasStats.CreateBitmapLabel(0, 0, canvasStatsName, statsX, currentCanvasY + header_height + gap_y, currentWidth / 2, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Recreate stats
         {
            Print("Failed to recreate Stats Canvas");            //--- Print error
         }
         statsCreated = true;                                    //--- Set stats flag
         UpdateStatsOnCanvas();                                  //--- Update stats
      }
   }
   
   int new_header_width = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute new width
   canvasHeader.Resize(new_header_width, header_height);         //--- Resize header
   ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, new_header_width); //--- Update header width
   ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Update header height
   DrawHeaderOnCanvas();                                         //--- Redraw header
   canvasHeader.Update();                                        //--- Update header
   ChartRedraw();                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Close the dashboard                                              |
//+------------------------------------------------------------------+
void CloseDashboard()
{
   canvasHeader.Destroy();                                       //--- Destroy header
   canvasGraph.Destroy();                                        //--- Destroy graph
   if (EnableStatsPanel) canvasStats.Destroy();                  //--- Destroy stats
   ChartRedraw();                                                //--- Redraw chart
}

Hier implementieren wir mehrere Mouseover-Erkennungsfunktionen, um Mauspositionen über bestimmten Bereichen wie der Kopfzeile (mit Ausnahme der Schaltflächen) und einzelnen Schaltflächen für das Design, das Minimieren und das Schließen zu erkennen, wobei die aktuellen Positionen und Größen verwendet werden, um Boolesche Werte für Statusaktualisierungen zurückzugeben. Wir fügen auch Überprüfungen für die Größenanpassung der Ränder des Grafikpanels hinzu, indem wir das Design (unten, rechts oder in der Ecke) anhand der Dicke und des Verweises auf aktualisierte Mouseover- oder Größenanpassungsaufzählungen bestimmen.

Außerdem erstellen wir Umschaltfunktionen für den Umschalten zwischen hellem und dunklem Design, indem wir das Flag invertieren, das neue Design ausdrucken und alle Canvas neu zeichnen, und für die Minimierung, indem wir den Status umschalten, Diagramm- und Statistik-Leinwände je nach Bedarf zerstören/neu erstellen, die Größe der Kopfzeile ändern, sie neu zeichnen und aktualisieren. Schließlich definieren wir eine Schließfunktion, um alle Canvas zu zerstören und das Chart neu zu zeichnen. Wir definieren die Funktion „IsMouseOverHeader“, um zu prüfen, ob sich die Maus über die Kopfzeile befindet, ohne die Schaltflächen zu überlappen, und geben einen booleschen Wert zurück. Die Position der Kopfzeile wird aus „currentCanvasX“ und „currentCanvasY“ ermittelt, die Breite einschließlich optionaler Statistiken und Abstand, falls nicht minimiert, die Höhe aus „header_height“, und es wird „false“ zurückgegeben, falls sie außerhalb der Grenzen liegt.

Dann berechnen wir die Schaltflächenbereiche für das Design, „Minimize“ und „Close“ mithilfe von Offsets und „button_size“ und geben andernfalls true zurück, wenn sich der Mauszeiger über der Kopfzeile befindet. Die nächste Funktion müssen wir nicht im Detail erläutern, da wir einen ähnlichen Ansatz bereits in den vorherigen Artikeln dieser Serie verwendet haben. Wir haben Kommentare zur Verdeutlichung hinzugefügt. Als Nächstes werden wir diese Funktionen in der Ereignisbehandlung des Charts wie folgt verwenden.

//+------------------------------------------------------------------+
//| Handle chart event                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if (id == CHARTEVENT_CHART_CHANGE)                            //--- Check change event
   {
      DrawHeaderOnCanvas();                                      //--- Redraw header
      UpdateGraphOnCanvas();                                     //--- Update graph
      if (EnableStatsPanel) UpdateStatsOnCanvas();               //--- Update stats
      ChartRedraw();                                             //--- Redraw chart
   }
   else if (id == CHARTEVENT_MOUSE_MOVE)                         //--- Handle mouse move
   {
      int mouse_x = (int)lparam;                                 //--- Get mouse x
      int mouse_y = (int)dparam;                                 //--- Get mouse y
      int mouse_state = (int)sparam;                             //--- Get mouse state
      
      bool prev_header_hovered = header_hovered;                 //--- Store previous header
      bool prev_min_hovered = minimize_hovered;                  //--- Store previous minimize
      bool prev_close_hovered = close_hovered;                   //--- Store previous close
      bool prev_theme_hovered = theme_hovered;                   //--- Store previous theme
      bool prev_resize_hovered = resize_hovered;                 //--- Store previous resize
      
      header_hovered = IsMouseOverHeader(mouse_x, mouse_y);      //--- Check header hover
      theme_hovered = IsMouseOverTheme(mouse_x, mouse_y);        //--- Check theme hover
      minimize_hovered = IsMouseOverMinimize(mouse_x, mouse_y); //--- Check minimize hover
      close_hovered = IsMouseOverClose(mouse_x, mouse_y);        //--- Check close hover
      resize_hovered = IsMouseOverResize(mouse_x, mouse_y, hover_mode); //--- Check resize hover
      
      if (resize_hovered || resizing)                            //--- Check resize state
      {
         hover_mouse_local_x = mouse_x - currentCanvasX;         //--- Set local x
         hover_mouse_local_y = mouse_y - (currentCanvasY + header_height + gap_y); //--- Set local y
      }
      
      bool hover_changed = (prev_header_hovered != header_hovered || prev_min_hovered != minimize_hovered || 
                            prev_close_hovered != close_hovered || prev_theme_hovered != theme_hovered ||
                            prev_resize_hovered != resize_hovered); //--- Check change
      
      if (hover_changed)                                         //--- If changed
      {
         DrawHeaderOnCanvas();                                   //--- Redraw header
         UpdateGraphOnCanvas();                                  //--- Update graph
         ChartRedraw();                                          //--- Redraw chart
      }
      else if ((resize_hovered || resizing) && (mouse_x != last_mouse_x || mouse_y != last_mouse_y)) //--- Check position change
      {
         UpdateGraphOnCanvas();                                  //--- Update graph
         ChartRedraw();                                          //--- Redraw chart
      }
      
      string header_tooltip = "";                                //--- Initialize tooltip
      if (theme_hovered) header_tooltip = "Toggle Theme (Dark/Light)"; //--- Set theme tooltip
      else if (minimize_hovered) header_tooltip = panels_minimized ? "Maximize Panels" : "Minimize Panels"; //--- Set minimize tooltip
      else if (close_hovered) header_tooltip = "Close Dashboard"; //--- Set close tooltip
      ObjectSetString(0, canvasHeaderName, OBJPROP_TOOLTIP, header_tooltip); //--- Set header tooltip
      
      string resize_tooltip = "";                                //--- Initialize resize tooltip
      if (resize_hovered || resizing)                            //--- Check resize
      {
         ENUM_RESIZE_MODE active_mode = resizing ? resize_mode : hover_mode; //--- Get mode
         switch (active_mode)                                    //--- Switch mode
         {
            case BOTTOM: resize_tooltip = "Resize Bottom"; break; //--- Set bottom
            case RIGHT: resize_tooltip = "Resize Right"; break;  //--- Set right
            case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set corner
            default: break;
         }
      }
      ObjectSetString(0, canvasGraphName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set graph tooltip
      
      if (mouse_state == 1 && prev_mouse_state == 0)             //--- Check mouse down
      {
         if (header_hovered)                                     //--- Check header
         {
            panel_dragging = true;                               //--- Start drag
            panel_drag_x = mouse_x;                              //--- Set drag x
            panel_drag_y = mouse_y;                              //--- Set drag y
            panel_start_x = currentCanvasX;                      //--- Set start x
            panel_start_y = currentCanvasY;                      //--- Set start y
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);       //--- Disable scroll
            DrawHeaderOnCanvas();                                //--- Show drag color
            ChartRedraw();                                       //--- Redraw chart
         }
         else if (theme_hovered)                                 //--- Check theme
         {
            ToggleTheme();                                       //--- Toggle theme
         }
         else if (minimize_hovered)                              //--- Check minimize
         {
            ToggleMinimize();                                    //--- Toggle minimize
         }
         else if (close_hovered)                                 //--- Check close
         {
            CloseDashboard();                                    //--- Close dashboard
         }
         else                                                    //--- Handle resize
         {
            ENUM_RESIZE_MODE temp_mode = NONE;                   //--- Initialize temp
            if (!panel_dragging && !resizing && IsMouseOverResize(mouse_x, mouse_y, temp_mode)) //--- Check resize
            {
               resizing = true;                                  //--- Start resizing
               resize_mode = temp_mode;                          //--- Set mode
               resize_start_x = mouse_x;                         //--- Set start x
               resize_start_y = mouse_y;                         //--- Set start y
               start_width = currentWidth;                       //--- Set start width
               start_height = currentHeight;                     //--- Set start height
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);    //--- Disable scroll
               UpdateGraphOnCanvas();                            //--- Show icon
               ChartRedraw();                                    //--- Redraw chart
            }
         }
      }
      else if (panel_dragging && mouse_state == 1)               //--- Handle dragging
      {
         int dx = mouse_x - panel_drag_x;                        //--- Compute dx
         int dy = mouse_y - panel_drag_y;                        //--- Compute dy
         int new_x = panel_start_x + dx;                         //--- Compute new x
         int new_y = panel_start_y + dy;                         //--- Compute new y
         
         int chart_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
         int chart_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
         int full_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute full width
         int full_h = header_height + gap_y + (panels_minimized ? 0 : currentHeight); //--- Compute full height
         new_x = MathMax(0, MathMin(chart_w - full_w, new_x));   //--- Clamp x
         new_y = MathMax(0, MathMin(chart_h - full_h, new_y));   //--- Clamp y
         
         currentCanvasX = new_x;                                 //--- Update x
         currentCanvasY = new_y;                                 //--- Update y
         
         ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, new_x); //--- Update header x
         ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, new_y); //--- Update header y
         if (!panels_minimized)                                  //--- Check if shown
         {
            ObjectSetInteger(0, canvasGraphName, OBJPROP_XDISTANCE, new_x); //--- Update graph x
            ObjectSetInteger(0, canvasGraphName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Update graph y
            if (EnableStatsPanel)                                //--- Check stats
            {
               int statsX = new_x + currentWidth + PanelGap;     //--- Compute stats x
               ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, statsX); //--- Update stats x
               ObjectSetInteger(0, canvasStatsName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Update stats y
            }
         }
         ChartRedraw();                                          //--- Redraw chart
      }
      else if (resizing && mouse_state == 1)                     //--- Handle resizing
      {
         int dx = mouse_x - resize_start_x;                      //--- Compute dx
         int dy = mouse_y - resize_start_y;                      //--- Compute dy
         int new_width = currentWidth;                           //--- Initialize new width
         int new_height = currentHeight;                         //--- Initialize new height
         if (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT) //--- Check right
         {
            new_width = MathMax(min_width, start_width + dx);    //--- Update width
         }
         if (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT) //--- Check bottom
         {
            new_height = MathMax(min_height, start_height + dy); //--- Update height
         }
         
         if (new_width != currentWidth || new_height != currentHeight) //--- Check change
         {
            int chart_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
            int chart_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
            int avail_w = chart_w - currentCanvasX;              //--- Compute available width
            int avail_h = chart_h - (currentCanvasY + header_height + gap_y); //--- Compute available height
            new_height = MathMin(new_height, avail_h);           //--- Clamp height
            if (EnableStatsPanel)                                //--- Check stats
            {
               double max_w_d = (avail_w - PanelGap) / 1.5;      //--- Compute max width
               int max_w = (int)MathFloor(max_w_d);              //--- Floor max
               new_width = MathMin(new_width, max_w);            //--- Clamp width
            }
            else                                                 //--- No stats
            {
               new_width = MathMin(new_width, avail_w);          //--- Clamp width
            }
            
            currentWidth = new_width;                            //--- Update width
            currentHeight = new_height;                          //--- Update height
            
            if (UseBackground && ArraySize(original_bg_pixels) > 0) //--- Check background
            {
               ArrayCopy(bg_pixels_graph, original_bg_pixels);   //--- Copy graph
               ScaleImage(bg_pixels_graph, (int)orig_w, (int)orig_h, currentWidth, currentHeight); //--- Scale graph
               if (EnableStatsPanel)                             //--- Check stats
               {
                  ArrayCopy(bg_pixels_stats, original_bg_pixels); //--- Copy stats
                  ScaleImage(bg_pixels_stats, (int)orig_w, (int)orig_h, currentWidth / 2, currentHeight); //--- Scale stats
               }
            }
            
            canvasGraph.Resize(currentWidth, currentHeight);     //--- Resize graph
            ObjectSetInteger(0, canvasGraphName, OBJPROP_XSIZE, currentWidth); //--- Update graph width
            ObjectSetInteger(0, canvasGraphName, OBJPROP_YSIZE, currentHeight); //--- Update graph height
            
            if (EnableStatsPanel)                                //--- Check stats
            {
               int stats_width = currentWidth / 2;               //--- Compute stats width
               canvasStats.Resize(stats_width, currentHeight);   //--- Resize stats
               ObjectSetInteger(0, canvasStatsName, OBJPROP_XSIZE, stats_width); //--- Update stats width
               ObjectSetInteger(0, canvasStatsName, OBJPROP_YSIZE, currentHeight); //--- Update stats height
               int stats_x = currentCanvasX + currentWidth + PanelGap; //--- Compute stats x
               ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, stats_x); //--- Update stats x
            }
            
            canvasHeader.Resize(currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0), header_height); //--- Resize header
            ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0)); //--- Update header width
            ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Update header height
            
            DrawHeaderOnCanvas();                                //--- Redraw header
            UpdateGraphOnCanvas();                               //--- Update graph
            if (EnableStatsPanel) UpdateStatsOnCanvas();         //--- Update stats
            ChartRedraw();                                       //--- Redraw chart
         }
      }
      else if (mouse_state == 0 && prev_mouse_state == 1)        //--- Check mouse up
      {
         if (panel_dragging)                                     //--- Check dragging
         {
            panel_dragging = false;                              //--- Stop drag
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Enable scroll
            DrawHeaderOnCanvas();                                //--- Reset color
            ChartRedraw();                                       //--- Redraw chart
         }
         if (resizing)                                           //--- Check resizing
         {
            resizing = false;                                    //--- Stop resize
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Enable scroll
            UpdateGraphOnCanvas();                               //--- Remove icon
            ChartRedraw();                                       //--- Redraw chart
         }
      }
      
      last_mouse_x = mouse_x;                                    //--- Update last x
      last_mouse_y = mouse_y;                                    //--- Update last y
      prev_mouse_state = mouse_state;                            //--- Update state
   }
}

Wenn die id CHARTEVENT_CHART_CHANGE ist, rufen wir im OnChartEvent-Ereignishandler „DrawHeaderOnCanvas“ auf, um die Kopfzeile neu zu zeichnen, „UpdateGraphOnCanvas“ für die Grafik, „UpdateStatsOnCanvas“, wenn „EnableStatsPanel“ aktiviert ist und ChartRedraw, um die Anzeige zu aktualisieren. Für CHARTEVENT_MOUSE_MOVE wird lparam auf Maus x, dparam auf y und sparam auf state als Ganzzahlen gesetzt. Wir speichern frühere Mouseover-Zustände in lokalen Werten und aktualisieren dann „header_hovered“ mit „IsMouseOverHeader“, „theme_hovered“ mit „IsMouseOverTheme“, „minimize_hovered“ mit „IsMouseOverMinimize“, „close_hovered“ mit „IsMouseOverClose“, und „resize_hovered“ mit „IsMouseOverResize“, wobei ein Verweis auf „hover_mode“ übergeben wird. Wenn „resize“ im Mouseover-Zustand ist oder „resizing“ auf „true“ gesetzt ist, werden „hover_mouse_local_x“ und „hover_mouse_local_y“ relativ zur Position im Diagramm festgelegt. Wir prüfen, ob sich der Mouseover-Status geändert hat, indem wir den vorherigen mit dem aktuellen vergleichen, und wenn ja, zeichnen wir die Kopfzeile und das Diagramm neu, dann zeichnen wir neu. Andernfalls, wenn der Größenänderungsstatus und die Mausposition von „last_mouse_x“/“last_mouse_y“ abweichen, wird das Diagramm aktualisiert und neu gezeichnet.

Wir initialisieren eine Kopfzeilen-Tooltip-Zeichenfolge, die bei „theme_hovered“ auf „Toggle Theme (Dark/Light)“, bei „minimize_hovered“ auf „maximierter/minimierter Tooltip-Text abhängig vom Zustand von „panels_minimized“, bei „close_hovered“ auf „close Dashboard“ und bei „canvasHeaderName“ mit ObjectSetString auf OBJPROP_TOOLTIP angewendet wird. In ähnlicher Weise bestimmen Sie für den Tooltip zur Größenänderung das aktive Design aus „resize_mode“ oder „hover_mode“, setzen die Zeichenfolge auf der Grundlage von „bottom/right/bottom-right“ und wenden sie auf „canvasGraphName“ an.

Wenn der Mausstatus eins und „prev_mouse_state“ null ist, wird nach unten gedrückt: wenn „header_hovered“, setze „panel_dragging“ true, speichere Drag/Start-Koordinaten, deaktiviere Chart-Maus-Scroll mit ChartSetInteger „CHART_MOUSE_SCROLL“ false, zeichne Header neu, zeichne Chart neu; sonst wenn „theme_hovered“, rufe „ToggleTheme“; wenn „minimize_hovered“wenn „minimize_hovered“, Aufruf von „ToggleMinimize“; wenn „close_hovered“, Aufruf von „CloseDashboard“; wenn nicht ziehen/verkleinern und „IsMouseOverResize“ true in das temporäre Design, setzen von „resize“ true, „resize_mode“ auf temp, Startkoordinaten/Dimms speichern, Scrollen deaktivieren, Grafik aktualisieren, neu zeichnen.

Wenn „panel_dragging“ auf „true“ gesetzt ist und der Status „held“ lautet, berechnen wir die Deltas, neue x/y vom Start plus Deltas, ermitteln Grafikbreite/-höhe mit ChartGetInteger „CHART_WIDTH_IN_PIXELS“/CHART_HEIGHT_IN_PIXELS, volle Panelbreite/-höhe einschließlich optionaler Statistiken/Abstand/Kopfzeile, wenn nicht minimiert, klemme neue x/y auf Null bis zum Chart minus volle Dims mit „MathMax“/“MathMin“, Aktualisierung von „currentCanvasX“/“currentCanvasY“, Festlegen der x/y-Abstände des Objekts für die Kopfzeile und, falls nicht minimiert, für das Chart und die optionalen Statistiken (Berechnung von stats x), dann erneutes Zeichnen.

Wenn „resizing“ true und state one ist, werden die Deltas berechnet, die neue Breite/Höhe wird mit der aktuellen initialisiert, dx wird addiert, wenn der rechte oder Eckmodus auf „min_width“ geklemmt ist, dy für unten oder Ecke auf „min_height“. Falls geändert, werden die Chartabmessungen, die verfügbare Breite/Höhe von der aktuellen Position aus ermittelt, die neue Höhe wird auf die verfügbare Höhe geklemmt, die Breite wird auf den Boden (verfügbare Breite minus Abstand)/1,5 geklemmt, andernfalls auf die verfügbare Höhe, und die Ströme werden aktualisiert. Wenn ein Hintergrund verwendet wird, kopieren wir die Originalpixel in das Diagramm/die Statistik und skalieren sie mit „ScaleImage“ auf die neuen Maße (halbe Breite der Statistik). Wir ändern die Größe von „canvasGraph“ mit Resize und setzen „OBJPROP_XSIZE“/“YSIZE“, ebenso für „stats“, falls aktiviert, und aktualisieren die X-Position und die Kopfzeile auf volle Breite. Kopfzeile/Grafik/Statistiken neu zeichnen, falls aktiviert, Chart neu zeichnen.

Wenn der Zustand Null und „prev_mouse_state“ Eins für „Hoch“: Wenn „panel_dragging“, auf „false“ setzen, Scrollen aktivieren, Kopfzeile neu zeichnen, Chart neu zeichnen; wenn „resizing“, auf „false“ setzen, Scrollen aktivieren, Grafik aktualisieren, Chart neu zeichnen. Wir aktualisieren „last_mouse_x“/“last_mouse_y“ auf current, „prev_mouse_state“ auf state. Wir müssen auch das Dashboard pro Tick aktualisieren, um die neuen Kurse widerzuspiegeln.

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick()
{
   static datetime lastBarTime = 0;                              //--- Initialize last time
   datetime currentBarTime = iTime(_Symbol, _Period, 0);         //--- Get current time
   if (currentBarTime > lastBarTime)                             //--- Check new bar
   {
      UpdateGraphOnCanvas();                                     //--- Update graph
      if (EnableStatsPanel) UpdateStatsOnCanvas();               //--- Update stats
      ChartRedraw();                                             //--- Redraw chart
      lastBarTime = currentBarTime;                              //--- Update last time
   }
}

Im OnTick-Ereignishandler verwenden wir eine statische „lastBarTime“-Datetime, die auf Null initialisiert ist, um die Eröffnungszeit der vorherigen Bar zu verfolgen und die aktuelle Bar-Zeit mit iTime für das Symbol, die Periode und die Bar Null zu erhalten. Wenn die aktuelle Zeit größer als „lastBarTime“ ist, was auf einen neuen Bars hinweist, rufen wir „UpdateGraphOnCanvas“ auf, um das Kurschart zu aktualisieren, „UpdateStatsOnCanvas“, wenn „EnableStatsPanel“ für Statistiken aktiviert ist, zeichnen das Chart neu und aktualisieren „lastBarTime“ auf den aktuellen Wert. Schließlich müssen wir gerenderte Objekte löschen, um Unordnung zu vermeiden, wenn sie nicht benötigt werden.

//+------------------------------------------------------------------+
//| Deinitialize expert                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   canvasHeader.Destroy();                                       //--- Destroy header
   if (graphCreated) canvasGraph.Destroy();                      //--- Destroy graph if created
   if (statsCreated) canvasStats.Destroy();                      //--- Destroy stats if created
   ChartRedraw();                                                //--- Redraw chart
}

Im OnDeinit-Ereignishandler zerstören wir das Header-Canvas mit „canvasHeader.Destroy“, dann, bedingt, das Grafikpanel, wenn „graphCreated“ wahr ist, mit „canvasGraph.Destroy“, und das Stats-Canvas, wenn „statsCreated“ wahr ist, mit „canvasStats.Destroy“. Zum Schluss wird das Chart neu gezeichnet, um sicherzustellen, dass alle Überreste aus der Anzeige entfernt werden. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

CANVAS DASHBOARD TEST

Anhand der Visualisierung können wir sehen, dass wir das Canvas-Dashboard korrekt eingerichtet haben und dass alle Canvas-Komponenten korrekt gerendert und interaktiv sind, sodass wir unser Ziel erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im vorangegangenen Abschnitt behandelt wurde.


Backtests

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung als einzelne Animation im GIF-Format.

CANVAS DASHBOARD BACKTEST


Schlussfolgerung

Abschließend haben wir ein canvasbasiertes Kurs-Dashboard in MQL5 entwickelt, das die CCanvas-Klasse verwendet, um verschiebbare und in der Größe veränderbare Panels für Echtzeit-Kursverläufe mit Liniendiagrammen, gefüllten Bereichen und Nebeleffekten zu erstellen, zusammen mit einem optionalen Statistikpanel für Kontometriken wie Balance/Equity und aktuellem Bar-OHLC, das Hintergrundbilder, Farbverläufe, Umschalten zwischen hellem und dunklem Design und effiziente Ereignisbehandlung für Interaktivität unterstützt. Das System umfasst eine bikubische Skalierung für eine reibungslose Größenänderung, Alpha-Blending für Überlagerungen und Aktualisierungen bei neuen Bars und bietet so ein anpassbares Werkzeug für die visuelle Überwachung ohne native Objekte. Im nächsten Teil werden wir das Dashboard erweitern, indem wir einen Canvas-basierten Textbildschirm zum Lesen und Scrollen hinzufügen, der verschiedene Textdesign integrieren kann, mit einer dynamischen Bildlaufleiste, wie sie in den neuen aktualisierten MetaQuotes-Terminals zu finden ist. Bleiben Sie dran.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21038

Beigefügte Dateien |
Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5 Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5
So erstellen Sie in MQL5 für MetaTrader 5 einen Supertrend ohne Repainting von Grund auf. Wir verwenden ein iATR-Handle und CopyBuffer für die Volatilität, binden Puffer mit SetIndexBuffer und konfigurieren Plots (DRAWCOLORCANDLES plus zwei Linienbänder) über PlotIndexSetInteger. Die Logik wird nur bei geschlossenen Kerzen mit EMPTY_VALUE aktualisiert, um inaktive Bänder zu unterdrücken, wobei die Eingabeparameter atrPeriod und atrMultiplier für den Nutzer verfügbar gemacht werden. Sie erhalten ein sauberes, EA-fähiges Overlay mit dokumentierten Puffern für Strategien und Signale.
Data Science und ML (Teil 48): Sind Transformer für das Trading wirklich relevant? Data Science und ML (Teil 48): Sind Transformer für das Trading wirklich relevant?
Von ChatGPT über Gemini bis hin zu zahlreichen KI-Tools zur Text-, Bild- und Videogenerierung – Transformer haben die KI-Welt tiefgreifend verändert. Aber sind sie auch auf den Finanzbereich (Handel) anwendbar? Finden wir es heraus.
Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors
In dieser Diskussion knüpfen wir an den zuvor entwickelten Multi-Signal-Expert Advisor an, mit dem Ziel, verfügbare Optimierungsmethoden zu erforschen und anzuwenden. Ziel ist es, festzustellen, ob die Handelsleistung des EA durch systematische Optimierung auf Basis historischer Daten sinnvoll verbessert werden kann.
Marktsimulation (Teil 20): Erste Schritte mit SQL (III) Marktsimulation (Teil 20): Erste Schritte mit SQL (III)
Obwohl wir Operationen mit einer Datenbank mit etwa 10 Datensätzen durchführen können, lässt sich das Thema deutlich besser verstehen, wenn wir mit einer Datei arbeiten, die mehr als 15 Tausend Datensätze enthält. Das heißt, wenn wir versuchen würden, eine solche Datenbank manuell zu erstellen, wäre dies ein enormer Aufwand. Es ist jedoch selbst zu Lernzwecken schwierig, eine solche Datenbank zum Download zu finden. Aber in Wirklichkeit müssen wir nicht darauf zurückgreifen – wir können MetaTrader 5 verwenden, um eine Datenbank für uns zu erstellen. Im heutigen Artikel werden wir uns ansehen, wie man das macht.