English Deutsch
preview
MQL5でカスタムインジケーターを作成する(第2回):Canvasと針のメカニクスを使ったゲージ型RSIインジケーターの構築

MQL5でカスタムインジケーターを作成する(第2回):Canvasと針のメカニクスを使ったゲージ型RSIインジケーターの構築

MetaTrader 5トレーディングシステム |
19 3
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第1回)では、MetaQuotes Language 5 (MQL5)を使用してピボットベースのトレンドインジケーターを作成しました。ユーザーが定義した期間にわたって高速(短期)ピボットラインと低速(長期)ピボットラインを計算し、これらのラインに対するトレンドの方向を検出し、矢印でトレンドの開始を示し、必要に応じて現在のバーを超えてラインを延長します。第2回では、Canvasと針のメカニクスを用いたゲージ型のRSI(Relative Strength Index:相対力指数)表示を作成します。このモデルは、動的な針を備えた円形ゲージ上にRSIの値を表示し、買われすぎと売られすぎのレベルを色分けした範囲と、カスタマイズ可能な凡例を備えています。また、包括的なモメンタム解析のために従来型の線グラフ表示機能も統合しています。本記事では以下のトピックを扱います。

  1. ゲージ型RSIインジケーターフレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、RSIを可視化するゲージ型のMQL5インジケーターが完成し、カスタマイズ可能な状態になります。それでは実装に進みましょう。


ゲージ型RSIインジケーターフレームワークの理解

ゲージ型RSIインジケーターは、従来のRSIを円形ダイヤルとして再設計したもので、針が0〜100のスケール上で現在のモメンタム値を指し示す仕組みになっています。70以上を買われすぎ、30以下を売られすぎとして、それぞれ異なる色のゾーンで強調することで、直感的に状況を把握できるようにしています。また、正確な読み取りのための目盛り、インジケーター名や数値表示といった補足情報の凡例、さらにゲージ表示を補完する形で、別ウィンドウに従来型の線グラフも用意し、過去のトレンドを確認できるようにしています。このダイヤルゲージ型のアプローチを採用したのは、視覚的に分かりやすく、分析結果を直感的に把握できるためです。計算自体は標準的でよく知られたRSIを使用していますが、将来的にはより複雑なデータや注釈を組み込むことも可能です。

現時点では、スケールと針といったグラフィカルなレイヤーを分離し、それぞれを独立して透明度や更新処理を制御できるモジュール構造のフレームワークを構築していきます。まず、角度範囲、色、目盛り間隔といったカスタマイズ用の入力パラメータを整理し、次に円弧、扇形、ラベルなどの描画要素を扱うための構造体を定義して、描画ロジックを整理していきます。その後、作成、パラメータ設定、再描画を担当する基底クラスを実装し、ゲージが正しく初期化され、市場の新しいRSI値に応じて更新されるようにします。すべての視覚コンポーネントにCanvas描画を活用し、内蔵のRSI計算と統合し、チャート更新全体にわたってスムーズに動作するようイベントハンドラを管理することを計画しています。以下に想定されるビジュアル表示の例を示します。理解しやすいよう、主要な要素を詳しく説明します。

ダイヤルゲージフレームワーク


MQL5での実装

MQL5でインジケーターを作成するには、まずMetaEditorを開き、ナビゲータに移動して、インジケーターフォルダを見つけ、[新規]タブをクリックして、表示される手順に従ってファイルを作成します。作成後、コーディング環境においてインジケーターのプロパティと設定を定義します。たとえばバッファの数、プロット数、さらに各ラインの個別プロパティとして色、幅、ラベルです。

//+------------------------------------------------------------------+
//|                           1. Gauge-Based RSI Indicator Part1.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label1 "RSI"
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_level1 30
#property indicator_level2 70
#property indicator_levelcolor clrGray
#property indicator_levelstyle STYLE_DOT

実装は、#propertyディレクティブを使用してインジケーターのメタデータを定義することから始めます。具体的には、indicator_separate_windowを使用して別のサブウィンドウに描画するように指定し、indicator_buffersを使用して2つのバッファを割り当て、indicator_plotsを使用して1つのプロットを設定します。グラフについては、タイプをDRAW_LINE、色をドジャーブルー、スタイルをソリッド、幅を2に設定し、「RSI」というラベルを付けました。また、indicator_minimumとindicator_maximumを用いて縦方向のスケールを0から100に固定します。さらにindicator_level1、indicator_level2、indicator_levelcolor、indicator_levelstyleを使用して30と70に点線のグレーレベルを追加し、売られ過ぎおよび買われ過ぎのゾーンを強調します。これらの特性により、モメンタム分析のために、RSIラインを専用ウィンドウに鮮明に可視化できます。次に、カスタム描画要素用のCanvasライブラリを含めます。

#include <Canvas\Canvas.mqh>

#include <Canvas\Canvas.mqh>」を使用してCanvasライブラリをインクルードすることで、ビットマップの作成やチャートへの描画操作を可能にするCCanvasクラスなど、カスタムグラフィック描画のための組み込みツールを組み込むことができます。これにより、プログラムはゲージ表示画面に円弧、円、テキストなどの視覚要素を構築できるようになります。次に、グラフィック要素を整理するための構造を定義します。

//+------------------------------------------------------------------+
//| Circle Structure                                                 |
//+------------------------------------------------------------------+
struct Struct_Circle {                     // Define circle structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   color clr;                              // Store color
   bool display;                           // Store display flag
};

//+------------------------------------------------------------------+
//| Arc Structure                                                    |
//+------------------------------------------------------------------+
struct Struct_Arc {                        // Define arc structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   double startAngle;                      // Store start angle in radians
   double endAngle;                        // Store end angle in radians
   color clr;                              // Store color
   bool display;                           // Store display flag
};

//+------------------------------------------------------------------+
//| Line Structure                                                   |
//+------------------------------------------------------------------+
struct Struct_Line {                       // Define line structure
   int startX;                             // Store start X coordinate
   int startY;                             // Store start Y coordinate
   int endX;                               // Store end X coordinate
   int endY;                               // Store end Y coordinate
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Dot Structure                                                    |
//+------------------------------------------------------------------+
struct Struct_Dot {                        // Define dot structure
   int x;                                  // Store X coordinate
   int y;                                  // Store Y coordinate
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Pie/Sector Structure                                             |
//+------------------------------------------------------------------+
struct Struct_Pie {                        // Define pie structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   int eraseRadius;                        // Store erase radius
   double startAngle;                      // Store start angle in radians
   double endAngle;                        // Store end angle in radians
   double eraseStartAngle;                 // Store erase start angle in radians
   double eraseEndAngle;                   // Store erase end angle in radians
   color clr;                              // Store color
   color eraseClr;                         // Store erase color
};

//+------------------------------------------------------------------+
//| Range Structure                                                  |
//+------------------------------------------------------------------+
struct Struct_Range {                      // Define range structure
   bool active;                            // Store active status
   double startValue;                      // Store start value
   double endValue;                        // Store end value
   color clr;                              // Store color
   Struct_Pie pie;                         // Store pie structure
};

//+------------------------------------------------------------------+
//| Case Structure                                                   |
//+------------------------------------------------------------------+
struct Struct_Case {                       // Define case structure
   bool display;                           // Store display flag
   Struct_Circle circle;                   // Store circle structure
};

//+------------------------------------------------------------------+
//| Scale Marks Structure                                            |
//+------------------------------------------------------------------+
struct Struct_ScaleMarks {                 // Define scale marks structure
   double minValue;                        // Store minimum value
   double maxValue;                        // Store maximum value
   double valueRange;                      // Store value range
   bool forwardDirection;                  // Store forward direction flag
   int nullMarkPosition;                   // Store null mark position
   double nullMarkAngle;                   // Store null mark angle
   int decimalPlaces;                      // Store decimal places
   int majorTickLength;                    // Store major tick length
   int mediumTickLength;                   // Store medium tick length
   int minorTickLength;                    // Store minor tick length
   double minAngle;                        // Store minimum angle
   double maxAngle;                        // Store maximum angle
   double angleRange;                      // Store angle range
   double multiplier;                      // Store multiplier
   string gaugeName;                       // Store gauge name
   string currentValue;                    // Store current value
   string units;                           // Store units
   int tickFontSize;                       // Store tick font size
   string tickFontName;                    // Store tick font name
   uint tickFontFlags;                     // Store tick font flags
   int tickFontGap;                        // Store tick font gap
};

//+------------------------------------------------------------------+
//| Label Area Size Structure                                        |
//+------------------------------------------------------------------+
struct Struct_LabelAreaSize {              // Define label area size structure
   int height;                             // Store height
   int width;                              // Store width
   int diagonal;                           // Store diagonal
};

//+------------------------------------------------------------------+
//| Gauge Legend Parameters Structure                                |
//+------------------------------------------------------------------+
struct Struct_GaugeLegendParams {          // Define gauge legend parameters structure
   bool enable;                            // Store enable flag
   string text;                            // Store text
   uint radius;                            // Store radius
   double angle;                           // Store angle
   uint fontSize;                          // Store font size
   string fontName;                        // Store font name
   bool italic;                            // Store italic flag
   bool bold;                              // Store bold flag
   color textColor;                        // Store text color
};

//+------------------------------------------------------------------+
//| Gauge Legend String Structure                                    |
//+------------------------------------------------------------------+
struct Struct_GaugeLegendString {          // Define gauge legend string structure
   string text;                            // Store text
   int radius;                             // Store radius
   double angle;                           // Store angle
   int fontSize;                           // Store font size
   string fontName;                        // Store font name
   uint fontFlags;                         // Store font flags
   color textColor;                        // Store text color
   color backgroundColor;                  // Store background color
   uint decimalPlaces;                     // Store decimal places
   uint x;                                 // Store x coordinate
   uint y;                                 // Store y coordinate
   bool draw;                              // Store draw flag
};

//+------------------------------------------------------------------+
//| Gauge Label Structure                                            |
//+------------------------------------------------------------------+
struct Struct_GaugeLabel {                 // Define gauge label structure
   Struct_GaugeLegendString description;   // Store description
   Struct_GaugeLegendString units;         // Store units
   Struct_GaugeLegendString multiplier;    // Store multiplier
   Struct_GaugeLegendString value;         // Store value
};

//+------------------------------------------------------------------+
//| Scale Layer Structure                                            |
//+------------------------------------------------------------------+
struct Struct_ScaleLayer {                 // Define scale layer structure
   string objectName;                      // Store object name
   CCanvas obj_Canvas;                     // Store canvas object
   uchar transparency;                     // Store transparency
   color caseColor;                        // Store case color
   Struct_Case externalCase;               // Store external case
   int borderSize;                         // Store border size
   Struct_Case internalCase;               // Store internal case
   int borderGap;                          // Store border gap
   int externalLabelArea;                  // Store external label area
   int externalScaleGap;                   // Store external scale gap
   Struct_Arc scaleArc;                    // Store scale arc
   int internalScaleGap;                   // Store internal scale gap
   int internalLabelArea;                  // Store internal label area
   Struct_ScaleMarks scaleMarks;           // Store scale marks
   Struct_GaugeLabel gaugeLabel;           // Store gauge label
   Struct_Range ranges[4];                 // Store ranges array
};

//+------------------------------------------------------------------+
//| Needle Structure                                                 |
//+------------------------------------------------------------------+
struct Struct_Needle {                     // Define needle structure
   int tipRadius;                          // Store tip radius
   int tailRadius;                         // Store tail radius
   int x[4];                               // Store x coordinates array
   int y[4];                               // Store y coordinates array
   int fillStyle;                          // Store fill style
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Needle Layer Structure                                           |
//+------------------------------------------------------------------+
struct Struct_NeedleLayer {                // Define needle layer structure
   string objectName;                      // Store object name
   CCanvas obj_Canvas;                     // Store canvas object
   uchar transparency;                     // Store transparency
   Struct_Arc needleCenter;                // Store needle center
   Struct_Needle needle;                   // Store needle
};

//+------------------------------------------------------------------+
//| Range Parameters Structure                                       |
//+------------------------------------------------------------------+
struct Struct_RangeParams {                // Define range parameters structure
   bool enable;                            // Store enable flag
   double start;                           // Store start value
   double end;                             // Store end value
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Gauge Input Parameters Structure                                 |
//+------------------------------------------------------------------+
struct Struct_GaugeInputParams {           // Define gauge input parameters structure
   int xOffset;                            // Store x offset
   int yOffset;                            // Store y offset
   int anchorCorner;                       // Store anchor corner
   int relativeMode;                       // Store relative mode
   string relativeObjectName;              // Store relative object name
   int scaleAngleRange;                    // Store scale angle range
   int rotationAngle;                      // Store rotation angle
   color scaleColor;                       // Store scale color
   int scaleStyle;                         // Store scale style
   bool displayScaleArc;                   // Store display scale arc flag
   double minScaleValue;                   // Store minimum scale value
   double maxScaleValue;                   // Store maximum scale value
   int scaleMultiplier;                    // Store scale multiplier
   int tickStyle;                          // Store tick style
   int tickSize;                           // Store tick size
   double majorTickInterval;               // Store major tick interval
   int mediumTicksPerMajor;                // Store medium ticks per major
   int minorTicksPerInterval;              // Store minor ticks per interval
   int tickFontSize;                       // Store tick font size
   string tickFontName;                    // Store tick font name
   bool tickFontItalic;                    // Store tick font italic flag
   bool tickFontBold;                      // Store tick font bold flag
   color tickFontColor;                    // Store tick font color
   Struct_RangeParams ranges[4];           // Store ranges array
   color caseColor;                        // Store case color
   int borderStyle;                        // Store border style
   color borderColor;                      // Store border color
   int borderGapSize;                      // Store border gap size
   Struct_GaugeLegendParams description;   // Store description
   Struct_GaugeLegendParams units;         // Store units
   Struct_GaugeLegendParams multiplier;    // Store multiplier
   Struct_GaugeLegendParams value;         // Store value
   int needleCenterStyle;                  // Store needle center style
   color needleCenterColor;                // Store needle center color
   color needleColor;                      // Store needle color
   int needleFillStyle;                    // Store needle fill style
};

構造体については、まずstructキーワードを使用してStruct_Circle構造体を定義し、中心座標、半径、色、表示フラグなど、円形要素のプロパティを保持します。理解を助けるためにコメントも加えています。次に、円弧形状用のStruct_Arc構造体を作成し、中心点、半径、開始角度と終了角度(ラジアン単位)、色、表示状態を格納します。次に、開始座標と終了座標、および色を指定して、線を表すStruct_Line構造体を設定します。Struct_Dot構造体は、x座標、y座標、および色を含む点に対して定義されます。扇形や円グラフのスライスについては、中心、描画と消去の半径、両方の角度範囲、塗りつぶしと消去の色を指定してStruct_Pie構造体を定義します。値の範囲を管理するために、Struct_Range構造体を確立します。これには、アクティブ状態、開始値と終了値、色、および可視化のための埋め込みStruct_Pieが含まれます。

Struct_Case構造体は、表示フラグとStruct_Circleを含むゲージの枠用に作成されます。値の制限、範囲、方向、目盛りの長さ、角度、乗数、フォントのプロパティ、ギャップなどのスケールの詳細を整理するために、Struct_ScaleMarks構造体を定義します。ラベルのサイズ設定には、高さ、幅、対角線の寸法を保持するStruct_LabelAreaSize構造体を作成します。Struct_GaugeLegendParams構造体は、有効化フラグ、テキスト、半径、角度、フォントの詳細、色など、凡例の設定に使用されます。次に、テキスト、位置、フォントフラグ、色、小数点以下の桁数、座標、描画フラグを含む、特定の凡例文字列用のStruct_GaugeLegendStringを定義します。Struct_GaugeLabel構造体は、説明、単位、乗数、値など、複数の凡例文字列をグループ化します。レイヤー化のために、Struct_ScaleLayerを作成し、オブジェクト名、Canvasインスタンス、透明度、大文字小文字の色、枠線の詳細、ラベル領域、ゲージの円弧、マーク、ラベル、および範囲の配列を含むスケールコンポーネントを管理します。

ポインタには、先端と尾部の半径、座標配列、塗りつぶしスタイル、色などを指定してStruct_Needleを定義します。Struct_NeedleLayer構造体は、オブジェクト名、Canvas、透明度、中心円弧、および針のデータを含む針レイヤーを処理します。範囲設定については、Struct_RangeParamsに有効化フラグ、開始値と終了値、および色を設定します。最後に、オフセット、アンカー、スケール角度、色、表示フラグ、値の範囲、乗数、目盛り構成、フォントフラグ、範囲配列、枠プロパティ、凡例、針のスタイルなど、すべての入力オプションを統合するためにStruct_GaugeInputParamsを定義します。それが終わったら、次は最も簡単な標準RSIインジケーターの作成に移ります。その後、いずれにしてもこれらの標準RSIデータが必要になるため、複雑なゲージベースのインジケーターを作成します。これが、私たちがそれを達成するために用いた手法です。

int rsiHandle;                             //--- Declare RSI handle
double rsiBuffer[];                        //--- Declare RSI buffer

//+------------------------------------------------------------------+
//| Initialize Indicator                                             |
//+------------------------------------------------------------------+
int OnInit() {
   rsiHandle = iRSI(_Symbol, _Period, 14, 4);       //--- Get RSI handle
   if(rsiHandle == INVALID_HANDLE)                  //--- Check handle
      return(INIT_FAILED);                          //--- Return failed
   SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA);    //--- Set index buffer
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set plot empty
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14 - 1); //--- Set draw begin
   IndicatorSetInteger(INDICATOR_LEVELS, 2);        //--- Set levels
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 30); //--- Set level 0
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 70); //--- Set level 1
   return(INIT_SUCCEEDED);                          //--- Return succeeded
}

//+------------------------------------------------------------------+
//| Calculate Indicator                                              |
//+------------------------------------------------------------------+
int OnCalculate(const int ratesTotal,
                const int prevCalculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tickVolume[],
                const long &volume[],
                const int &spread[]) {
   if(CopyBuffer(rsiHandle, 0, 0, ratesTotal, rsiBuffer) < 0) { //--- Copy buffer
      Print("RSI CopyBuffer error for plot"); //--- Print error
      return(0);                           //--- Return 0
   }
   return(ratesTotal);                     //--- Return rates total
}

標準のRSIインジケーターを作成するには、グローバルスコープで、参照を格納するための整数型のrsiHandleと、計算された相対強度指数値を保持するdouble型の配列rsiBufferを宣言します。

OnInitイベントハンドラでは、現在の銘柄、時間足、期間14、終値を使用して、iRSI関数でrsiHandleを初期化します。ハンドルが無効な場合は、初期化を停止するためにINIT_FAILEDを返します。次に、SetIndexBufferを使用して、インジケーターデータ用のバッファインデックス0をrsiBufferにバインドし、PlotIndexSetDoubleを使用して空のプロット値をEMPTY_VALUEに設定します。また、RSIの計算期間を考慮して、PlotIndexSetIntegerを使用して描画の開始をバー13に指定します。さらに、2つのインジケーターレベルをIndicatorSetIntegerで設定し、IndicatorSetDoubleを使用して売られ過ぎと買われ過ぎの閾値を30と70に設定してから、INIT_SUCCEEDEDを返します。

OnCalculateイベントハンドラでは、CopyBuffer関数を使用して、相対強度指数ハンドルのバッファ0から利用可能な合計レートのデータをrsiBufferにコピーします。コピーが失敗した場合は、エラーメッセージを表示して0を返し、処理を停止します。それ以外の場合は、計算が成功したことを示すために、レートの合計を返します。プログラミング言語においては、各マイルストーンごとにプログラムを実行して、すべてが順調に進んでいることを確認するのが常に良い方法です。プログラムを実行すると、以下の結果が得られます。

従来のRSI指標

画像から、標準インジケーターセットが設定されていることがわかります。これは非常に簡単で分かりやすかったです。次にやるべきことは、ゲージを描画するためのクラスを定義することです。そうすることで、後でより多くのゲージを作成する必要が生じたときに、そのクラスを簡単に再利用できます。しかし、その前に、いくつかのヘルパー関数を定義します。

//+------------------------------------------------------------------+
//| Degrees to Radians                                               |
//+------------------------------------------------------------------+
double DegreesToRadians(double degrees) {
   return((M_PI * degrees) / 180.0);       //--- Convert degrees to radians
}

//+------------------------------------------------------------------+
//| Normalize Radians                                                |
//+------------------------------------------------------------------+
double NormalizeRadians(double angle) {
   while(angle < 0.0) angle += 2.0 * M_PI; //--- Adjust negative
   while(angle >= 2.0 * M_PI) angle -= 2.0 * M_PI; //--- Adjust positive
   return(angle);                          //--- Return normalized
}

//+------------------------------------------------------------------+
//| Get Tick Font Gap                                                |
//+------------------------------------------------------------------+
int GetTickFontGap(Struct_ScaleMarks &scaleMarks, int stringLength) {
   int gap = 0;                            //--- Initialize gap
   Struct_LabelAreaSize areaSize;          //--- Declare area size
   CCanvas obj_Canvas_temp;                //--- Declare temp canvas
   if(!obj_Canvas_temp.FontSet(scaleMarks.tickFontName, scaleMarks.tickFontSize, scaleMarks.tickFontFlags, 0)) //--- Set font
      return gap;                          //--- Return gap
   string str = "000";                     //--- Set str
   obj_Canvas_temp.TextSize(str, areaSize.width, areaSize.height); //--- Get text size
   if(areaSize.width == 0 || areaSize.height == 0) //--- Check size
      return gap;                          //--- Return gap
   areaSize.diagonal = (int)MathCeil(MathSqrt((double)(areaSize.width * areaSize.width + areaSize.height * areaSize.height))); //--- Calculate diagonal
   gap = (int)(areaSize.diagonal * 0.5);   //--- Set gap
   return gap;                             //--- Return gap
}

//+------------------------------------------------------------------+
//| Get Tick Label Area Size                                         |
//+------------------------------------------------------------------+
bool GetTickLabelAreaSize(int &areaSize, Struct_ScaleMarks &scaleMarks, int stringLength) {
   CCanvas obj_Canvas_temp;                //--- Declare temp canvas
   int width = 0, height = 0;              //--- Initialize width height
   if(!obj_Canvas_temp.FontSet(scaleMarks.tickFontName, scaleMarks.tickFontSize, scaleMarks.tickFontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   string str = "000";                     //--- Set str
   obj_Canvas_temp.TextSize(str, width, height); //--- Get text size
   if(width == 0 || height == 0)           //--- Check size
      return false;                        //--- Return false
   areaSize = (int)MathCeil(MathSqrt((double)(width * width + height * height))); //--- Calculate area size
   return true;                            //--- Return true
}

まず、DegreesToRadians関数を定義します。この関数は、入力された度数をM_PIで乗算し、180で割ることによってラジアンに変換します。次に、NormalizeRadians関数を作成し、角度が0から2πの範囲内になるようにします。負の値の場合は2πを加算し、2πを超える値の場合は2πを減算します。GetTickFontGap関数は、一時的なCCanvasオブジェクトを使用して目盛りラベルの間隔を計算します。Struct_ScaleMarksパラメータからフォントを設定し、000のテキストサイズを測定し、MathCeilMathSqrtを使用して幅と高さの対角線を計算し、その対角線の半分を間隔として返します。フォントの設定が失敗した場合、またはサイズがゼロの場合は、デフォルトで0になります。

また、GetTickLabelAreaSize関数を定義し、一時的なCCanvasを使用して目盛りラベルに必要な領域を決定します。この関数は、フォントを設定し、「000」テキストの寸法を測定し、出力領域のサイズの対角線の平方根の上限を計算し、失敗した場合またはサイズがゼロの場合はfalseを返します。これらの関数を用いることで、完全なメソッド定義を備えたクラスを作成することができます。まず、必要なメソッドをすべて含む基底クラスを宣言し、後でそれらのメソッドを定義しましょう。

//+------------------------------------------------------------------+
//| Base Gauge Class                                                 |
//+------------------------------------------------------------------+
class CGaugeBase                           // Define base gauge class
{
private:
   int relativeX;                          //--- Store relative X
   int relativeY;                          //--- Store relative Y
   int centerX;                            //--- Store center X
   int centerY;                            //--- Store center Y
   double currentValue;                    //--- Store current value
   bool initializationComplete;            //--- Store initialization complete flag
   void Draw();                            //--- Declare draw method
   void CalculateNeedle();                 //--- Declare calculate needle method
   void RedrawNeedle(double value);        //--- Declare redraw needle method
   void CalculateAndDrawLegends();         //--- Declare calculate and draw legends method
   void CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString); //--- Declare calculate and draw legend string method
   void RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap); //--- Declare redraw scale marks method
   void CalculateRanges(int borderGap);    //--- Declare calculate ranges method
   bool IsValidRange(int index);           //--- Declare check valid range method
   void NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1); //--- Declare normalize range values method
   void CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr); //--- Declare calculate range pie method
   void DrawRanges();                      //--- Declare draw ranges method
   void DrawRange(Struct_Range &range);    //--- Declare draw range method
   void CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle); //--- Declare calculate inner outer radii method
   bool DrawTick(double angle, int length, Struct_Arc &scaleArc); //--- Declare draw tick method
   double CalculateAngleDelta(double angle1, double angle2, int direction); //--- Declare calculate angle delta method
   bool GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString); //--- Declare get label area size method
   bool EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr); //--- Declare erase legend string method
   bool RedrawValueDisplay(double value);  //--- Declare redraw value display method
   void SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams &param, int minRadius, int radiusDelta); //--- Declare set legend string params method
   void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap); //--- Declare calculate case elements method
   void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase); //--- Declare draw case elements method
protected:
   Struct_GaugeInputParams inputParams;    //--- Store input parameters
   Struct_ScaleLayer scaleLayer;           //--- Store scale layer
   Struct_NeedleLayer needleLayer;         //--- Store needle layer
   int m_radius;                           //--- Store radius
public:
   bool Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency); //--- Declare create method
   bool CalculateLocation();               //--- Declare calculate location method
   void Redraw();                          //--- Declare redraw method
   void NewValue(double value);            //--- Declare new value method
   void Delete();                          //--- Declare delete method
   void SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc = false); //--- Declare set scale parameters method
   void SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval); //--- Declare set tick parameters method
   void SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr = clrBlack); //--- Declare set tick label font method
   void SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize); //--- Declare set case parameters method
   void SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend parameters method
   void SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend param method
   void SetRangeParameters(int index, bool enable, double start, double end, color rangeClr); //--- Declare set range parameters method
   void SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle); //--- Declare set needle parameters method
};

ゲージの可視化を構築および管理するためのコアとして機能するCGaugeBaseクラスを定義し、作成、描画、および更新に関するすべてのロジックをカプセル化します。privateセクションでは、相対位置と中心位置、現在表示されている値、および初期化フラグを追跡するための変数を宣言します。また、Drawはゲージの描画をおこなうためのメソッドとして宣言し、CalculateNeedleはポインタ位置の計算、RedrawNeedleは値に基づく針の更新を担当します。さらにCalculateAndDrawLegendsおよびCalculateAndDrawLegendStringはテキスト要素の処理をおこない、RedrawScaleMarksは目盛りおよびラベルの再描画を担当します。 CalculateRangesおよび関連ヘルパーであるIsValidRange、NormalizeRangeValues、CalculateRangePie、DrawRanges、DrawRangeはカラーゾーンの計算と描画をおこないます。CalculateInnerOuterRadiiは半径の計算を担当し、DrawTickは個々の目盛り描画をおこないます。CalculateAngleDeltaは角度差の算出をおこない、GetLabelAreaSizeおよびEraseLegendStringはラベル領域の管理を担当します。RedrawValueDisplayは表示値の更新、SetLegendStringParamsは凡例文字列の設定をおこないます。最後にCalculateCaseElementsとDrawCaseElementsはゲージ外装の要素計算および描画を担当します。

protectedセクションには、入力パラメータ、目盛りレイヤー、針レイヤー、および半径のための構造体が含まれており、派生クラスがアクセスできるようにしつつ、それらをカプセル化したままにすることができます。

publicセクションでは、ゲージを名前、位置、サイズ、相対性、コーナー、背景、透明度で初期化するCreate、位置決めをおこなうCalculateLocation、完全な更新をおこなうRedraw、新しい値で更新するNewValue、クリーンアップをおこなうDeleteなどのメソッド、および角度と値の範囲を設定するSetScaleParameters、目盛りの間隔を設定するSetTickParameters、フォントのスタイルを設定するSetTickLabelFont、枠線を設定するSetCaseParameters、凡例を設定するSetLegendParametersとSetLegendParam、ゾーンを設定するSetRangeParameters、ポインターのスタイルを設定するSetNeedleParametersなどのセッターメソッドを提供しています。これで、ゲージの作成と配置に関するロジックを定義できます。

//+------------------------------------------------------------------+
//| Create Gauge                                                     |
//+------------------------------------------------------------------+
bool CGaugeBase::Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency) {
   initializationComplete = false;         //--- Set initialization complete flag to false
   m_radius = size / 2;                    //--- Calculate radius
   inputParams.xOffset = x;                //--- Set x offset
   inputParams.yOffset = y;                //--- Set y offset
   inputParams.anchorCorner = corner;      //--- Set anchor corner
   inputParams.relativeMode = relativeMode;//--- Set relative mode
   inputParams.relativeObjectName = relativeObjectName; //--- Set relative object name
   if(!CalculateLocation())                //--- Check location calculation
      return false;                        //--- Return false if failed
   int canvasWidthHeight = (m_radius + 5) * 2; //--- Calculate canvas size
   scaleLayer.objectName = name + "_s";    //--- Set scale layer object name
   ObjectDelete(0, scaleLayer.objectName); //--- Delete scale layer object
   if(!scaleLayer.obj_Canvas.CreateBitmapLabel(scaleLayer.objectName, centerX, centerY, canvasWidthHeight, canvasWidthHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create scale canvas
      return false;                        //--- Return false if failed
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_CORNER, inputParams.anchorCorner); //--- Set corner property
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor property
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_BACK, background); //--- Set back property
   needleLayer.objectName = name + "_n";   //--- Set needle layer object name
   ObjectDelete(0, needleLayer.objectName);//--- Delete needle layer object
   if(!needleLayer.obj_Canvas.CreateBitmapLabel(needleLayer.objectName, centerX, centerY, canvasWidthHeight, canvasWidthHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create needle canvas
      return false;                        //--- Return false if failed
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_CORNER, inputParams.anchorCorner); //--- Set corner property
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor property
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_BACK, background); //--- Set back property
   scaleLayer.transparency = 255 - scaleTransparency; //--- Set scale transparency
   needleLayer.transparency = 255 - needleTransparency; //--- Set needle transparency
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Calculate Gauge Center Location                                  |
//+------------------------------------------------------------------+
bool CGaugeBase::CalculateLocation() {
   bool locationChanged = false;           //--- Initialize location changed flag
   int cX = m_radius;                      //--- Set initial X
   int cY = m_radius;                      //--- Set initial Y
   cX += inputParams.xOffset;              //--- Add X offset
   cY += inputParams.yOffset;              //--- Add Y offset
   if(centerX != cX || centerY != cY) {    //--- Check if position changed
      centerX = cX;                        //--- Update center X
      centerY = cY;                        //--- Update center Y
      locationChanged = true;              //--- Set changed flag
   }
   return locationChanged;                 //--- Return changed flag
}

ここでは、CGaugeBaseクラスのCreateメソッドを実装してゲージを初期化します。まず、initializationCompleteフラグをfalseにリセットし、m_radiusを指定されたサイズの半分として計算します。x座標とy座標のオフセット、アンカーコーナー、相対モード、相対オブジェクト名などの入力パラメータを保存します。CalculateLocationメソッドが失敗した場合、falseを返します。次に、Canvasの寸法を半径の合計の2倍に5を加えた値として決定し、スケールレイヤーオブジェクト名を基本名に「_s」を追加して割り当て、ObjectDeleteを使用して既存のオブジェクトを削除し、ARGB正規化を使用してスケールCanvasの中央位置にCreateBitmapLabelを使用して新しいビットマップラベルを作成します。コーナー、中央へのアンカー、背景状態などのプロパティはObjectSetIntegerを使用して設定します。同様に、針レイヤーについても、名前に「_n」を追加し、既に存在する場合は削除し、ビットマップラベルを作成し、同じプロパティを設定します。最後に、入力値を255から減算して透明度を調整し、成功した場合はtrueを返します。

また、ゲージの中心位置を更新するためにCalculateLocationメソッドを定義します。このメソッドでは、変更フラグをfalseに初期化し、一時的なcXとcYを半径に設定し、保存されているオフセットを追加し、それらが現在の中心値と異なるかどうかを確認します。異なる場合は、中心座標を更新し、フラグをtrueに設定してから返します。パラメータを設定する前にゲージを作成するには、クラスからグローバルオブジェクトを宣言し、それを使用して以下のようにクラスのメンバーとメソッドにアクセスする必要があります。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CGaugeBase gauge;                          //--- Declare gauge object


// In the initialization, call the class method
if(!gauge.Create("rsi_gauge", 30, 30, 250, "", 0, 0, false, 0, 0)) //--- Create gauge
   return(INIT_FAILED);                 //--- Return failed

プログラム全体を通してゲージの可視化を管理するために、CGaugeBaseクラスのグローバルインスタンスgaugeを宣言します。OnInitイベントハンドラでは、このインスタンスに対してCreateメソッドを呼び出し、パラメータとして名前rsi_gauge、xとyの位置を30、サイズを250、相対オブジェクトを空、相対モードを0、コーナーを0、背景をfalse、両方の透明度を0に設定します。作成に失敗した場合は、INIT_FAILEDを返してインジケーターの設定を停止します。コンパイルすると、次の結果が得られます。

初期ゲージ作成

初期ゲージのシルエットが作成されたことがわかります。次に必要なのは、現在持っているベースに基づいて、残りのメソッドを定義することで、ケーシングやその他のパラメータを作成することです。まず、ゲージパラメータを定義します。

//+------------------------------------------------------------------+
//| Set Scale Parameters                                             |
//+------------------------------------------------------------------+
void CGaugeBase::SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc) {
   inputParams.scaleAngleRange = angleRange; //--- Set scale angle range
   inputParams.rotationAngle = rotation;   //--- Set rotation angle
   inputParams.minScaleValue = minValue;   //--- Set minimum scale value
   inputParams.maxScaleValue = maxValue;   //--- Set maximum scale value
   inputParams.scaleMultiplier = multiplier; //--- Set scale multiplier
   inputParams.scaleStyle = style;         //--- Set scale style
   inputParams.scaleColor = scaleClr;      //--- Set scale color
   inputParams.displayScaleArc = displayArc; //--- Set display scale arc flag
}

//+------------------------------------------------------------------+
//| Set Tick Parameters                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval) {
   inputParams.tickStyle = style;          //--- Set tick style
   inputParams.tickSize = size;            //--- Set tick size
   inputParams.majorTickInterval = majorInterval; //--- Set major tick interval
   inputParams.mediumTicksPerMajor = mediumPerMajor; //--- Set medium ticks per major
   inputParams.minorTicksPerInterval = minorPerInterval; //--- Set minor ticks per interval
}

//+------------------------------------------------------------------+
//| Set Tick Label Font                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr) {
   inputParams.tickFontSize = fontSize;    //--- Set tick font size
   inputParams.tickFontName = fontName;    //--- Set tick font name
   inputParams.tickFontItalic = italic;    //--- Set tick font italic flag
   inputParams.tickFontBold = bold;        //--- Set tick font bold flag
   inputParams.tickFontColor = fontClr;    //--- Set tick font color
}

//+------------------------------------------------------------------+
//| Set Case Parameters                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize) {
   inputParams.caseColor = caseClr;        //--- Set case color
   inputParams.borderStyle = borderStyle;  //--- Set border style
   inputParams.borderColor = borderClr;    //--- Set border color
   inputParams.borderGapSize = borderGapSize; //--- Set border gap size
}

//+------------------------------------------------------------------+
//| Set Legend Parameters                                            |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr) {
   switch(legendType) {                    //--- Switch on legend type
   case 0:                                 //--- Handle description
      SetLegendParam(inputParams.description, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set description param
      break;                               //--- Break
   case 1:                                 //--- Handle units
      SetLegendParam(inputParams.units, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set units param
      break;                               //--- Break
   case 2:                                 //--- Handle multiplier
      SetLegendParam(inputParams.multiplier, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set multiplier param
      break;                               //--- Break
   case 3:                                 //--- Handle value
      SetLegendParam(inputParams.value, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set value param
      break;                               //--- Break
   }
}

//+------------------------------------------------------------------+
//| Set Individual Legend Parameter                                  |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr) {
   legendParam.enable = enable;            //--- Set enable flag
   legendParam.text = text;                //--- Set text
   legendParam.radius = radius;            //--- Set radius
   legendParam.angle = angle;              //--- Set angle
   legendParam.fontSize = fontSize;        //--- Set font size
   legendParam.fontName = fontName;        //--- Set font name
   legendParam.italic = italic;            //--- Set italic flag
   legendParam.bold = bold;                //--- Set bold flag
   legendParam.textColor = textClr;        //--- Set text color
}

//+------------------------------------------------------------------+
//| Set Range Parameters                                             |
//+------------------------------------------------------------------+
void CGaugeBase::SetRangeParameters(int index, bool enable, double start, double end, color rangeClr) {
   if(index >= 0 && index < 4) {           //--- Check index range
      inputParams.ranges[index].enable = enable; //--- Set enable flag
      inputParams.ranges[index].start = start; //--- Set start
      inputParams.ranges[index].end = end; //--- Set end
      inputParams.ranges[index].clr = rangeClr; //--- Set color
   }
}

//+------------------------------------------------------------------+
//| Set Needle Parameters                                            |
//+------------------------------------------------------------------+
void CGaugeBase::SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle) {
   inputParams.needleCenterStyle = centerStyle; //--- Set needle center style
   inputParams.needleCenterColor = centerClr; //--- Set needle center color
   inputParams.needleColor = needleClr;    //--- Set needle color
   inputParams.needleFillStyle = fillStyle;//--- Set needle fill style
}

ここでは、CGaugeBaseクラスにSetScaleParametersメソッドを定義し、指定された角度範囲、回転角度、最小値と最大値、乗数インデックス、スタイル、色、および円弧表示フラグをinputParamsの対応するフィールドに割り当てることで、ゲージのスケールを設定します。目盛り関連のオプションを設定するためにSetTickParametersメソッドを定義し、スタイル、サイズ、主目盛り間隔、主目盛りあたりの中目盛りの数、および中目盛りあたりの補助目盛りの数をinputParamsに格納します。SetTickLabelFontメソッドは、目盛りラベルのフォント設定を処理し、inputParamsをフォントサイズ、名前、斜体と太字のフラグ、および色で更新します。指定がない場合は、デフォルトで黒が使用されます。ゲージの外枠を定義するためにSetCaseParametersメソッドを作成し、枠の色、枠線のスタイル、枠線の色、およびギャップのサイズをinputParamsに割り当てます。

凡例については、SetLegendParametersメソッドを実装しています。このメソッドは、凡例のタイプ(0は説明、1は単位、2は乗数、3は値)に基づいてswitch文を使用し、SetLegendParamヘルパーメソッドを介して適切なStruct_GaugeLegendParamsにパラメータをルーティングします。デフォルトのテキストカラーは濃い灰色です。SetLegendParamメソッドは、渡された凡例パラメータ構造体内の有効化フラグ、テキスト、半径、角度、フォントサイズ、名前、斜体、太字、およびテキストの色を直接設定します。最大4つの範囲を設定するためにSetRangeParametersメソッドを追加し、inputParams.ranges配列のenable、start、end、およびcolorを設定する前に、インデックスが0~3の範囲内であることを検証します。最後に、SetNeedleParametersメソッドは、ポインタ構成のために、中心スタイル、中心色、針の色、および塗りつぶしスタイルをinputParamsに割り当てます。その他の要素を描画するために、以下のロジックを採用します。

//+------------------------------------------------------------------+
//| Calculate Case Elements                                          |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) {
   if(borderSize > 0) {                    //--- Check border size
      externalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set external center X
      externalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set external center Y
      externalCase.circle.radius = m_radius; //--- Set external radius
      externalCase.circle.clr = inputParams.borderColor; //--- Set external color
      externalCase.display = true;         //--- Set display flag
   } else                                  //--- Handle no border
      externalCase.display = false;        //--- Set display flag false
   internalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set internal center X
   internalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set internal center Y
   internalCase.circle.radius = m_radius - borderSize; //--- Set internal radius
   internalCase.circle.clr = inputParams.caseColor; //--- Set internal color
   internalCase.display = true;            //--- Set display flag
}

//+------------------------------------------------------------------+
//| Draw Case Elements                                               |
//+------------------------------------------------------------------+
void CGaugeBase::DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) {
   if(externalCase.display)                //--- Check external display
      scaleLayer.obj_Canvas.FillCircle(externalCase.circle.centerX, externalCase.circle.centerY, externalCase.circle.radius, ColorToARGB(externalCase.circle.clr, scaleLayer.transparency)); //--- Fill external circle
   if(internalCase.display)                //--- Check internal display
      scaleLayer.obj_Canvas.FillCircle(internalCase.circle.centerX, internalCase.circle.centerY, internalCase.circle.radius, ColorToARGB(internalCase.circle.clr, scaleLayer.transparency)); //--- Fill internal circle
}

//+------------------------------------------------------------------+
//| Calculate Needle                                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateNeedle() {
   int innerRadius = 0, outerRadius = 0;   //--- Initialize radii
   if(inputParams.minorTicksPerInterval > 0) //--- Check minor ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.minorTickLength, inputParams.tickStyle); //--- Calculate for minor
   else if(inputParams.mediumTicksPerMajor > 0) //--- Check medium ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.mediumTickLength, inputParams.tickStyle); //--- Calculate for medium
   else if(inputParams.majorTickInterval > 0) //--- Check major ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate for major
   needleLayer.needle.tipRadius = outerRadius; //--- Set tip radius
   needleLayer.needle.clr = inputParams.needleColor; //--- Set needle color
   needleLayer.needle.fillStyle = inputParams.needleFillStyle; //--- Set fill style
   needleLayer.needle.tailRadius = needleLayer.needleCenter.radius * 2; //--- Set tail radius
}

//+------------------------------------------------------------------+
//| Redraw Needle                                                    |
//+------------------------------------------------------------------+
void CGaugeBase::RedrawNeedle(double value) {
   needleLayer.obj_Canvas.Erase();         //--- Erase canvas
   double normalizedValue = 0;             //--- Initialize normalized value
   if(scaleLayer.scaleMarks.minValue < scaleLayer.scaleMarks.maxValue) { //--- Check direct order
      if(value < scaleLayer.scaleMarks.minValue) //--- Check min value
         value = scaleLayer.scaleMarks.minValue; //--- Clamp to min
      if(value > scaleLayer.scaleMarks.maxValue) //--- Check max value
         value = scaleLayer.scaleMarks.maxValue; //--- Clamp to max
      normalizedValue = value - scaleLayer.scaleMarks.minValue; //--- Normalize
   } else {                                //--- Handle inverse order
      if(value > scaleLayer.scaleMarks.minValue) //--- Check min value
         value = scaleLayer.scaleMarks.minValue; //--- Clamp to min
      if(value < scaleLayer.scaleMarks.maxValue) //--- Check max value
         value = scaleLayer.scaleMarks.maxValue; //--- Clamp to max
      normalizedValue = scaleLayer.scaleMarks.minValue - value; //--- Normalize
   }
   if(scaleLayer.scaleMarks.valueRange == 0) //--- Check value range
      return;                              //--- Return if zero
   double currentAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - ((normalizedValue * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Calculate current angle
   needleLayer.needle.x[0] = (int)(scaleLayer.scaleArc.centerX - needleLayer.needle.tipRadius * MathCos(M_PI - currentAngle)); //--- Set x0
   needleLayer.needle.y[0] = (int)(scaleLayer.scaleArc.centerY - needleLayer.needle.tipRadius * MathSin(M_PI - currentAngle)); //--- Set y0
   double bufferX[3], bufferY[3];          //--- Declare buffers
   bufferX[0] = scaleLayer.scaleArc.centerX - needleLayer.needle.tipRadius * MathCos(M_PI - currentAngle); //--- Set bufferX0
   bufferY[0] = scaleLayer.scaleArc.centerY - needleLayer.needle.tipRadius * MathSin(M_PI - currentAngle); //--- Set bufferY0
   double tailX = scaleLayer.scaleArc.centerX - needleLayer.needle.tailRadius * MathCos(2 * M_PI - currentAngle); //--- Calculate tail X
   double tailY = scaleLayer.scaleArc.centerY - needleLayer.needle.tailRadius * MathSin(2 * M_PI - currentAngle); //--- Calculate tail Y
   int r = (int)(needleLayer.needle.tailRadius / 3.0); //--- Calculate r
   bufferX[1] = tailX - r * MathCos(0.5 * M_PI - currentAngle); //--- Set bufferX1
   bufferY[1] = tailY - r * MathSin(0.5 * M_PI - currentAngle); //--- Set bufferY1
   bufferX[2] = tailX - r * MathCos(1.5 * M_PI - currentAngle); //--- Set bufferX2
   bufferY[2] = tailY - r * MathSin(1.5 * M_PI - currentAngle); //--- Set bufferY2
   uint clr = ColorToARGB(needleLayer.needle.clr, needleLayer.transparency); //--- Get color
   needleLayer.obj_Canvas.LineAA((int)bufferX[0], (int)bufferY[0], (int)bufferX[1], (int)bufferY[1], clr); //--- Draw line AA 0-1
   needleLayer.obj_Canvas.LineAA((int)bufferX[1], (int)bufferY[1], (int)bufferX[2], (int)bufferY[2], clr); //--- Draw line AA 1-2
   needleLayer.obj_Canvas.LineAA((int)bufferX[2], (int)bufferY[2], (int)bufferX[0], (int)bufferY[0], clr); //--- Draw line AA 2-0
   double centroidX = (bufferX[0] + bufferX[1] + bufferX[2]) / 3.0; //--- Calculate centroid X
   double centroidY = (bufferY[0] + bufferY[1] + bufferY[2]) / 3.0; //--- Calculate centroid Y
   needleLayer.obj_Canvas.Fill((int)centroidX, (int)centroidY, clr); //--- Fill
   needleLayer.obj_Canvas.LineAA(scaleLayer.scaleArc.centerX, scaleLayer.scaleArc.centerY, (int)bufferX[0], (int)bufferY[0], clr); //--- Draw line AA center to 0
   if(needleLayer.needleCenter.display)    //--- Check display
      needleLayer.obj_Canvas.FillCircle(needleLayer.needleCenter.centerX, needleLayer.needleCenter.centerY, needleLayer.needleCenter.radius, ColorToARGB(needleLayer.needleCenter.clr, needleLayer.transparency)); //--- Fill needle center
}

//+------------------------------------------------------------------+
//| Calculate and Draw Legends                                       |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateAndDrawLegends() {
   if(inputParams.description.enable)      //--- Check description enable
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.description); //--- Calculate and draw description
   if(inputParams.units.enable)            //--- Check units enable
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.units); //--- Calculate and draw units
   if(inputParams.multiplier.enable) {     //--- Check multiplier enable
      scaleLayer.gaugeLabel.multiplier.text = scaleMultiplierStrings[inputParams.scaleMultiplier]; //--- Set multiplier text
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.multiplier); //--- Calculate and draw multiplier
   }
   if(inputParams.value.enable) {          //--- Check value enable
      scaleLayer.gaugeLabel.value.decimalPlaces = 0; //--- Set decimal places
      if(inputParams.value.text != "" ) {  //--- Check text
         int digits = (int)StringToInteger(inputParams.value.text); //--- Get digits
         if(digits >= 1 && digits <= 8)    //--- Check digits range
            scaleLayer.gaugeLabel.value.decimalPlaces = (uint)digits; //--- Set decimal places
      }
      scaleLayer.gaugeLabel.value.text = " "; //--- Set text
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.value); //--- Calculate and draw value
   }
}

//+------------------------------------------------------------------+
//| Calculate and Draw Legend String                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString) {
   if(legendString.text != "") {           //--- Check text
      legendString.draw = true;            //--- Set draw flag
      scaleLayer.obj_Canvas.FontSet(legendString.fontName, legendString.fontSize, legendString.fontFlags, 0); //--- Set font
      double normalizedAngle = NormalizeRadians(DegreesToRadians(legendString.angle + 90)); //--- Normalize angle
      legendString.x = (uint)(scaleLayer.scaleArc.centerX - legendString.radius * MathCos(M_PI - normalizedAngle)); //--- Set x
      legendString.y = (uint)(scaleLayer.scaleArc.centerY - legendString.radius * MathSin(M_PI - normalizedAngle)); //--- Set y
      legendString.backgroundColor = scaleLayer.caseColor; //--- Set background color
      scaleLayer.obj_Canvas.TextOut(legendString.x, legendString.y, legendString.text, ColorToARGB(legendString.textColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
   }
}

ゲージの外部および内部ケーシングを準備するために、CGaugeBaseクラスにCalculateCaseElementsメソッドを定義します。枠線サイズが正の場合、外枠の円をゲージの円弧の中心座標、半径全体、枠線色で構成し、表示フラグをtrueに設定します。そうでない場合は、表示を無効にします。内枠については、常にその中心をゲージの円弧に合わせ、半径を枠線サイズ分縮小し、枠の色を適用し、表示を有効にします。DrawCaseElementsメソッドは、これらの外枠を目盛りレイヤーCanvas上に描画します。外枠が表示されている場合は、ColorToARGB関数で透明度を調整したARGB変換色を使用して、FillCircleでその円を塗りつぶします。同様に、内枠の場合、有効になっている場合は、その円を適切なARGBカラーで塗りつぶします。

目盛りの構成に基づいて針の寸法を決定するために、CalculateNeedleメソッドを定義します。内半径と外半径を初期化し、設定されている間隔に応じて、主目盛り、中間目盛り、補助目盛りの長さを使用してCalculateInnerOuterRadiiを条件付きで呼び出し、ゲージの円弧半径と目盛りスタイルを渡します。針の先端に外半径を割り当て、入力パラメータから色と塗りつぶしスタイルを設定し、針の中心半径の2倍として尾部の半径を計算します。

RedrawNeedleメソッドでは、まず消去で針レイヤーのCanvasを消去します。入力値をスケールの最小値と最大値にクランプすることで正規化し、スケールが上昇しているか下降しているかに基づいて正規化された値を計算します。値の範囲がゼロの場合は、早期に終了します。NormalizeRadiansを使用して現在の角度を計算し、正規化された値が角度範囲に占める割合を調整します。針の先端の座標を、π調整付きのコサイン関数とサイン関数を使用して設定し、尾部の位置と尾部の半径の3分の1のオフセットポイントを計算して尾部三角形のバッファ配列を準備し、色をARGBに変換し、LineAAを使用して三角形のエッジにアンチエイリアス線を描画し、Fillを使用して三角形を塗りつぶすための重心を見つけ、中心から先端まで線を描画し、中心が表示されている場合は、それをARGB色で円として塗りつぶします。

有効になっている場合は、さまざまな凡例要素を処理するためのCalculateAndDrawLegendsメソッドを作成します。説明と単位については、CalculateAndDrawLegendStringを直接呼び出します。乗数については、描画前にスケール乗数インデックスに基づいて、事前に定義された配列からテキストを設定します。値については、小数点以下の桁数をデフォルトで0に設定し、入力テキストを解析して1から8までの数字を取得し、小数点以下の桁数を上書きし、初期テキストをスペースに設定して描画します。CalculateAndDrawLegendStringメソッドは、テキストが存在する場合に個々の凡例を処理し、描画フラグを設定し、FontSetでフォントを設定し、90度を追加して角度を正規化し、DegreesToRadiansとNormalizeRadiansで変換し、コサインとサインを使用してxとyの位置を計算し、枠の色を背景色として割り当て、ARGBカラーと配置フラグを使用してTextOutでテキストを中央揃えで描画します。目盛りと範囲については、以下のロジックを使用します。

//+------------------------------------------------------------------+
//| Redraw Scale Marks                                               |
//+------------------------------------------------------------------+
void CGaugeBase::RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap) {
   int majorIndex, mediumIndex, minorIndex;           //--- Declare indices
   double angle = 0, mediumAngle = 0, minorAngle = 0; //--- Declare angles
   scaleLayer.scaleMarks.multiplier = scaleMultipliers[inputParams.scaleMultiplier]; //--- Set multiplier
   if(scaleLayer.scaleMarks.multiplier <= 0)          //--- Check multiplier
      scaleLayer.scaleMarks.multiplier = 1.0;         //--- Set default multiplier
   scaleLayer.scaleMarks.minValue = inputParams.minScaleValue; //--- Set min value
   scaleLayer.scaleMarks.maxValue = inputParams.maxScaleValue; //--- Set max value
   scaleLayer.scaleMarks.decimalPlaces = 0;           //--- Set decimal places
   if(scaleLayer.scaleMarks.maxValue > scaleLayer.scaleMarks.minValue) { //--- Check direct order
      scaleLayer.scaleMarks.forwardDirection = true;  //--- Set forward direction
      scaleLayer.scaleMarks.valueRange = scaleLayer.scaleMarks.maxValue - scaleLayer.scaleMarks.minValue; //--- Set value range
   } else {                                           //--- Handle inverse order
      scaleLayer.scaleMarks.forwardDirection = false; //--- Set forward direction false
      scaleLayer.scaleMarks.valueRange = scaleLayer.scaleMarks.minValue - scaleLayer.scaleMarks.maxValue; //--- Set value range
   }
   scaleLayer.scaleMarks.nullMarkPosition = 1;         //--- Set null mark position
   scaleLayer.scaleMarks.minAngle = scaleArc.endAngle; //--- Set min angle
   scaleLayer.scaleMarks.maxAngle = scaleArc.startAngle; //--- Set max angle
   if(scaleArc.endAngle > scaleArc.startAngle)         //--- Check angles
      scaleLayer.scaleMarks.angleRange = NormalizeRadians(scaleArc.endAngle - scaleArc.startAngle); //--- Set angle range
   else                                                //--- Handle wrap around
      scaleLayer.scaleMarks.angleRange = NormalizeRadians(scaleArc.endAngle + (2 * M_PI - scaleArc.startAngle)); //--- Set angle range
   int leftMarkCount = 0;                              //--- Initialize left mark count
   int rightMarkCount = 0;                             //--- Initialize right mark count
   double markBuffer[361][2];                          //--- Declare mark buffer
   int bufferCenterIndex = (int)(361 / 2);             //--- Set buffer center index
   double tempValue = 0;                               //--- Initialize temp value
   int sign = 0;                                       //--- Initialize sign
   double multiplier = scaleMultipliers[inputParams.scaleMultiplier]; //--- Set multiplier
   markBuffer[bufferCenterIndex][0] = 0;               //--- Set zero value
   markBuffer[bufferCenterIndex][1] = scaleLayer.scaleMarks.minAngle; //--- Set zero angle
   tempValue = 0;                                      //--- Reset temp value
   sign = scaleLayer.scaleMarks.forwardDirection ? 1 : -1; //--- Set sign
   for(majorIndex = 1; majorIndex < (int)(361 / 2); majorIndex++) { //--- Loop major indices
      tempValue = majorIndex * inputParams.majorTickInterval; //--- Calculate temp value
      if(tempValue <= scaleLayer.scaleMarks.valueRange) { //--- Check range
         markBuffer[bufferCenterIndex + majorIndex][0] = (majorIndex * inputParams.majorTickInterval * sign) / multiplier; //--- Set mark value
         markBuffer[bufferCenterIndex + majorIndex][1] = NormalizeRadians(scaleLayer.scaleMarks.minAngle - ((majorIndex * inputParams.majorTickInterval * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set mark angle
         rightMarkCount++;                             //--- Increment right count
      } else                                           //--- Handle out of range
         break;                                        //--- Break loop
   }
   double majorAngleStep, mediumAngleStep, minorAngleStep; //--- Declare angle steps
   majorAngleStep = (inputParams.majorTickInterval * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set major step
   mediumAngleStep = 0;                                //--- Initialize medium step
   if(inputParams.mediumTicksPerMajor != 0)            //--- Check medium ticks
      mediumAngleStep = ((inputParams.majorTickInterval / (inputParams.mediumTicksPerMajor + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set medium step
   minorAngleStep = 0;                                 //--- Initialize minor step
   if(inputParams.minorTicksPerInterval != 0) {        //--- Check minor ticks
      if(mediumAngleStep != 0)                         //--- Check medium step
         minorAngleStep = (((inputParams.majorTickInterval / (inputParams.mediumTicksPerMajor + 1)) / (inputParams.minorTicksPerInterval + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set minor step with medium
      else                                             //--- Handle no medium
         minorAngleStep = ((inputParams.majorTickInterval / (inputParams.minorTicksPerInterval + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set minor step without medium
   }
   CalculateRanges(borderGap);             //--- Calculate ranges
   DrawRanges();                           //--- Draw ranges
   int innerR, outerR;                     //--- Declare radii
   double startX, startY, endX, endY;      //--- Declare coordinates
   int textX, textY;                       //--- Declare text coordinates
   string markText;                        //--- Declare mark text
   int digits;                             //--- Declare digits
   scaleLayer.obj_Canvas.FontSet(scaleLayer.scaleMarks.tickFontName, scaleLayer.scaleMarks.tickFontSize, scaleLayer.scaleMarks.tickFontFlags, 0); //--- Set font
   rightMarkCount++;                       //--- Increment right count
   for(majorIndex = 0; majorIndex < rightMarkCount; majorIndex++) { //--- Loop right marks
      angle = markBuffer[bufferCenterIndex + majorIndex][1];        //--- Get angle
      CalculateInnerOuterRadii(innerR, outerR, (int)scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
      startX = scaleArc.centerX - innerR * MathCos(M_PI - angle);   //--- Set start X
      startY = scaleArc.centerY - innerR * MathSin(M_PI - angle);   //--- Set start Y
      endX = scaleArc.centerX - outerR * MathCos(M_PI - angle);     //--- Set end X
      endY = scaleArc.centerY - outerR * MathSin(M_PI - angle);     //--- Set end Y
      textX = (int)(scaleArc.centerX - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathCos(M_PI - angle)); //--- Set text X
      textY = (int)(scaleArc.centerY - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathSin(M_PI - angle)); //--- Set text Y
      scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
      digits = (markBuffer[bufferCenterIndex + majorIndex][0] == 0) ? 0 : scaleLayer.scaleMarks.decimalPlaces; //--- Set digits
      markText = DoubleToString(markBuffer[bufferCenterIndex + majorIndex][0], digits); //--- Get mark text
      scaleLayer.obj_Canvas.TextOut(textX, textY, markText, ColorToARGB(inputParams.tickFontColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
      if(mediumAngleStep != 0) {           //--- Check medium step
         mediumAngle = angle;              //--- Set medium angle
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(mediumAngle - minorAngleStep * minorIndex);         //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))        //--- Draw minor tick
               break;                      //--- Break if failed
         }
         for(mediumIndex = 1; mediumIndex <= inputParams.mediumTicksPerMajor; mediumIndex++) { //--- Loop medium
            mediumAngle = NormalizeRadians(angle - mediumAngleStep * mediumIndex);            //--- Calculate medium angle
            if(!DrawTick(mediumAngle, scaleLayer.scaleMarks.mediumTickLength, scaleArc))      //--- Draw medium tick
               break;                      //--- Break if failed
            for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
               minorAngle = NormalizeRadians(mediumAngle - minorAngleStep * minorIndex);      //--- Calculate minor angle
               if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))     //--- Draw minor tick
                  break;                   //--- Break if failed
            }
         }
      } else {                             //--- Handle no medium
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(angle - minorAngleStep * minorIndex); //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc)) //--- Draw minor tick
               break;                      //--- Break if failed
         }
      }
   }
   for(majorIndex = 0; majorIndex < (leftMarkCount + 1); majorIndex++) { //--- Loop left marks
      angle = markBuffer[bufferCenterIndex - majorIndex][1];      //--- Get angle
      CalculateInnerOuterRadii(innerR, outerR, (int)scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
      startX = scaleArc.centerX - innerR * MathCos(M_PI - angle); //--- Set start X
      startY = scaleArc.centerY - innerR * MathSin(M_PI - angle); //--- Set start Y
      endX = scaleArc.centerX - outerR * MathCos(M_PI - angle);   //--- Set end X
      endY = scaleArc.centerY - outerR * MathSin(M_PI - angle);   //--- Set end Y
      textX = (int)(scaleArc.centerX - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathCos(M_PI - angle));  //--- Set text X
      textY = (int)(scaleArc.centerY - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathSin(M_PI - angle));  //--- Set text Y
      digits = (markBuffer[bufferCenterIndex - majorIndex][0] == 0) ? 0 : scaleLayer.scaleMarks.decimalPlaces; //--- Set digits
      markText = DoubleToString(markBuffer[bufferCenterIndex - majorIndex][0], digits);        //--- Get mark text
      if(majorIndex > 0 || (majorIndex == 0 && scaleLayer.scaleMarks.nullMarkPosition == 3)) { //--- Check condition
         scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
         scaleLayer.obj_Canvas.TextOut(textX, textY, markText, ColorToARGB(inputParams.tickFontColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
      }
      if(mediumAngleStep != 0) {           //--- Check medium step
         mediumAngle = angle;              //--- Set medium angle
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(mediumAngle + minorAngleStep * minorIndex);         //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))        //--- Draw minor tick
               break;                      //--- Break if failed
         }
         for(mediumIndex = 1; mediumIndex <= inputParams.mediumTicksPerMajor; mediumIndex++) { //--- Loop medium
            mediumAngle = NormalizeRadians(angle + mediumAngleStep * mediumIndex);             //--- Calculate medium angle
            if(!DrawTick(mediumAngle, scaleLayer.scaleMarks.mediumTickLength, scaleArc))       //--- Draw medium tick
               break;                      //--- Break if failed
            for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
               minorAngle = NormalizeRadians(mediumAngle + minorAngleStep * minorIndex);       //--- Calculate minor angle
               if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))      //--- Draw minor tick
                  break;                   //--- Break if failed
            }
         }
      } else {                             //--- Handle no medium
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) {  //--- Loop minor
            minorAngle = NormalizeRadians(angle + minorAngleStep * minorIndex);                //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))         //--- Draw minor tick
               break;                      //--- Break if failed
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate Ranges                                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateRanges(int borderGap) {
   int innerR, outerR;                     //--- Declare radii
   CalculateInnerOuterRadii(innerR, outerR, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
   for(int rangeIndex = 0; rangeIndex < 4; rangeIndex++) { //--- Loop ranges
      if(IsValidRange(rangeIndex))         //--- Check valid range
         CalculateRangePie(scaleLayer.ranges[rangeIndex], innerR, borderGap, outerR, inputParams.ranges[rangeIndex].start, inputParams.ranges[rangeIndex].end, inputParams.ranges[rangeIndex].clr, scaleLayer.caseColor); //--- Calculate pie
   }
}

//+------------------------------------------------------------------+
//| Check Valid Range                                                |
//+------------------------------------------------------------------+
bool CGaugeBase::IsValidRange(int index) {
   if(!inputParams.ranges[index].enable)   //--- Check enable
      return false;                        //--- Return false
   if(inputParams.ranges[index].start == inputParams.ranges[index].end) //--- Check start end
      return false;                        //--- Return false
   double paramMin, paramMax, rangeMin, rangeMax; //--- Declare mins maxs
   NormalizeRangeValues(paramMin, paramMax, inputParams.minScaleValue, inputParams.maxScaleValue); //--- Normalize param
   NormalizeRangeValues(rangeMin, rangeMax, inputParams.ranges[index].start, inputParams.ranges[index].end); //--- Normalize range
   if(rangeMin < paramMin && rangeMax < paramMin) //--- Check below param
      return false;                        //--- Return false
   if(rangeMin > paramMax && rangeMax > paramMax) //--- Check above param
      return false;                        //--- Return false
   if(rangeMin < paramMin)                 //--- Check min
      rangeMin = paramMin;                 //--- Clamp min
   if(rangeMax > paramMax)                 //--- Check max
      rangeMax = paramMax;                 //--- Clamp max
   inputParams.ranges[index].start = rangeMin; //--- Set start
   inputParams.ranges[index].end = rangeMax;   //--- Set end
   return true;                                //--- Return true
}

//+------------------------------------------------------------------+
//| Normalize Range Values                                           |
//+------------------------------------------------------------------+
void CGaugeBase::NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1) {
   if(val0 < val1) {                       //--- Check val0 < val1
      minValue = val0;                     //--- Set min
      maxValue = val1;                     //--- Set max
   } else {                                //--- Handle val0 >= val1
      minValue = val1;                     //--- Set min
      maxValue = val0;                     //--- Set max
   }
}

//+------------------------------------------------------------------+
//| Calculate Range Pie                                              |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr) {
   range.startValue = rangeStart;          //--- Set start value
   range.endValue = rangeEnd;              //--- Set end value
   double rangeStartNorm, rangeEndNorm;    //--- Declare norms
   if(range.startValue > range.endValue) { //--- Check start > end
      rangeStartNorm = range.startValue;   //--- Set start norm
      rangeEndNorm = range.endValue;       //--- Set end norm
   } else if(range.startValue < range.endValue) { //--- Check start < end
      rangeEndNorm = range.startValue;     //--- Set end norm
      rangeStartNorm = range.endValue;     //--- Set start norm
   } else                                  //--- Handle equal
      return;                              //--- Return
   if(scaleLayer.scaleMarks.minValue > scaleLayer.scaleMarks.maxValue) { //--- Check inverse
      double temp = rangeStartNorm;        //--- Temp start
      rangeStartNorm = -rangeEndNorm;      //--- Set start norm
      rangeEndNorm = -temp;                //--- Set end norm
   }
   range.active = true;                    //--- Set active
   range.clr = rangeClr;                   //--- Set color
   range.pie.centerX = scaleLayer.scaleArc.centerX; //--- Set center X
   range.pie.centerY = scaleLayer.scaleArc.centerY; //--- Set center Y
   range.pie.radius = innerRadius;         //--- Set radius
   range.pie.eraseRadius = outerRadius;    //--- Set erase radius
   double angularOffset = MathArcsin(((double)radialGap / (double)range.pie.radius) / 2.0); //--- Calculate offset
   if(scaleLayer.scaleMarks.minValue < scaleLayer.scaleMarks.maxValue) { //--- Check direct
      range.pie.startAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set start angle
      range.pie.endAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set end angle
      range.pie.eraseStartAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) - angularOffset); //--- Set erase start
      range.pie.eraseEndAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) + angularOffset); //--- Set erase end
   } else {                                //--- Handle inverse
      range.pie.startAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((scaleLayer.scaleMarks.minValue + rangeStartNorm) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set start angle
      range.pie.endAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((scaleLayer.scaleMarks.minValue + rangeEndNorm) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set end angle
      range.pie.eraseStartAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm + scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) - angularOffset); //--- Set erase start
      range.pie.eraseEndAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm + scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) + angularOffset); //--- Set erase end
   }
   range.pie.clr = rangeClr;               //--- Set pie color
   range.pie.eraseClr = caseClr;           //--- Set erase color
}

//+------------------------------------------------------------------+
//| Draw Ranges                                                      |
//+------------------------------------------------------------------+
void CGaugeBase::DrawRanges() {
   for(int i = 0; i < 4; i++)              //--- Loop indices
      DrawRange(scaleLayer.ranges[i]);     //--- Draw range
}

//+------------------------------------------------------------------+
//| Draw Range                                                       |
//+------------------------------------------------------------------+
void CGaugeBase::DrawRange(Struct_Range &range) {
   if(!range.active)                       //--- Check active
      return;                              //--- Return
   int r_min = MathMin(range.pie.radius, range.pie.eraseRadius); //--- Get min r
   int r_max = MathMax(range.pie.radius, range.pie.eraseRadius); //--- Get max r
   for(int r = r_min + 1; r <= r_max; r++) { //--- Loop radii
      double frac = (double)(r - r_min) / (r_max - r_min); //--- Calculate frac
      uchar alpha = (uchar)(scaleLayer.transparency * frac); //--- Calculate alpha
      uint col = ColorToARGB(range.pie.clr, alpha); //--- Get color
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.startAngle, range.pie.endAngle, col); //--- Draw arc
      uint erase_col = ColorToARGB(range.pie.eraseClr, scaleLayer.transparency); //--- Get erase color
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.eraseStartAngle, range.pie.startAngle, erase_col); //--- Draw left erase
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.endAngle, range.pie.eraseEndAngle, erase_col); //--- Draw right erase
   }
}

//+------------------------------------------------------------------+
//| Calculate Inner Outer Radii                                      |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle) {
   innerRadius = baseRadius;               //--- Set inner radius
   outerRadius = baseRadius - tickLength;  //--- Set outer radius
}

//+------------------------------------------------------------------+
//| Draw Tick                                                        |
//+------------------------------------------------------------------+
bool CGaugeBase::DrawTick(double angle, int length, Struct_Arc &scaleArc) {
   int innerR, outerR;                     //--- Declare radii
   double startX, startY, endX, endY;      //--- Declare coordinates
   double arcStartAngle = scaleArc.startAngle; //--- Get start angle
   double arcEndAngle = scaleArc.endAngle; //--- Get end angle
   double deltaToStart = CalculateAngleDelta(arcStartAngle, angle, -1); //--- Calculate delta to start
   double deltaToEnd = CalculateAngleDelta(arcEndAngle, angle, 1); //--- Calculate delta to end
   double totalArcDelta = CalculateAngleDelta(arcStartAngle, arcEndAngle, -1); //--- Calculate total delta
   if(MathAbs(totalArcDelta - (deltaToEnd + deltaToStart)) < (M_PI / 180.0)) //--- Check within arc
      return false;                        //--- Return false
   CalculateInnerOuterRadii(innerR, outerR, scaleArc.radius, length, inputParams.tickStyle); //--- Calculate radii
   startX = scaleArc.centerX - innerR * MathCos(M_PI - angle); //--- Set start X
   startY = scaleArc.centerY - innerR * MathSin(M_PI - angle); //--- Set start Y
   endX = scaleArc.centerX - outerR * MathCos(M_PI - angle); //--- Set end X
   endY = scaleArc.centerY - outerR * MathSin(M_PI - angle); //--- Set end Y
   scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Calculate Angle Delta                                            |
//+------------------------------------------------------------------+
double CGaugeBase::CalculateAngleDelta(double angle1, double angle2, int direction) {
   double normAngle1 = NormalizeRadians(angle1); //--- Normalize angle1
   double normAngle2 = NormalizeRadians(angle2); //--- Normalize angle2
   double delta1, delta2;                  //--- Declare deltas
   if(normAngle1 == normAngle2)            //--- Check equal
      return 0;                            //--- Return 0
   if(normAngle1 > normAngle2) {           //--- Check angle1 > angle2
      delta1 = normAngle1 - normAngle2;    //--- Set delta1
      delta2 = normAngle2 + (2 * M_PI - normAngle1); //--- Set delta2
   } else {                                //--- Handle angle1 <= angle2
      delta1 = normAngle1 + (2 * M_PI - normAngle2); //--- Set delta1
      delta2 = normAngle2 - normAngle1;    //--- Set delta2
   }
   if(direction < 0)                       //--- Check direction
      return delta1;                       //--- Return delta1
   return delta2;                          //--- Return delta2
}

//+------------------------------------------------------------------+
//| Set Legend String Params                                         |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams &param, int minRadius, int radiusDelta) {
   if(param.enable && param.fontName != "") { //--- Check enable and font
      legendString.text = param.text;      //--- Set text
      legendString.angle = param.angle;    //--- Set angle
      legendString.radius = minRadius + (int)((radiusDelta * param.radius * 10) / 100.0); //--- Set radius
      legendString.fontName = param.fontName; //--- Set font name
      legendString.fontFlags = 0;          //--- Initialize flags
      if(param.italic)                     //--- Check italic
         legendString.fontFlags |= FONT_ITALIC; //--- Add italic
      if(param.bold)                       //--- Check bold
         legendString.fontFlags |= FW_BOLD; //--- Add bold
      legendString.fontSize = (int)(((param.fontSize + 2) * radiusDelta) / 64); //--- Set font size
      legendString.textColor = param.textColor; //--- Set text color
   }
}

//+------------------------------------------------------------------+
//| Delete Gauge                                                     |
//+------------------------------------------------------------------+
void CGaugeBase::Delete() {
   ObjectDelete(0, scaleLayer.objectName); //--- Delete scale object
   ObjectDelete(0, needleLayer.objectName);//--- Delete needle object
}

//+------------------------------------------------------------------+
//| Set New Value                                                    |
//+------------------------------------------------------------------+
void CGaugeBase::NewValue(double value) {
   if(!initializationComplete)             //--- Check initialization
      return;                              //--- Return
   currentValue = value;                   //--- Set current value
   if(scaleLayer.gaugeLabel.value.draw)    //--- Check draw
      RedrawValueDisplay(currentValue);    //--- Redraw value
   RedrawNeedle(currentValue);             //--- Redraw needle
   needleLayer.obj_Canvas.Update(true);    //--- Update canvas
}

//+------------------------------------------------------------------+
//| Get Label Area Size                                              |
//+------------------------------------------------------------------+
bool CGaugeBase::GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString) {
   if(!scaleLayer.obj_Canvas.FontSet(legendString.fontName, legendString.fontSize, legendString.fontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.TextSize(legendString.text, areaSize.width, areaSize.height); //--- Get text size
   if(areaSize.width == 0 || areaSize.height == 0) //--- Check size
      return false;                        //--- Return false
   areaSize.diagonal = (int)MathCeil(MathSqrt((double)(areaSize.width * areaSize.width + areaSize.height * areaSize.height))); //--- Calculate diagonal
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Erase Legend String                                              |
//+------------------------------------------------------------------+
bool CGaugeBase::EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr) {
   Struct_LabelAreaSize areaSize;          //--- Declare area size
   if(!GetLabelAreaSize(areaSize, legendString)) //--- Get area size
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.FillRectangle((int)legendString.x - (areaSize.width / 2) - 4, (int)legendString.y - (areaSize.height / 2), (int)legendString.x + (areaSize.width / 2) + 4, (int)legendString.y + (areaSize.height / 2), ColorToARGB(eraseClr, scaleLayer.transparency)); //--- Fill rectangle
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Redraw Value Display                                             |
//+------------------------------------------------------------------+
bool CGaugeBase::RedrawValueDisplay(double value) {
   if(StringLen(scaleLayer.gaugeLabel.value.text) > 0) { //--- Check text length
      if(!EraseLegendString(scaleLayer.gaugeLabel.value, scaleLayer.gaugeLabel.value.backgroundColor)) //--- Erase string
         return false;                     //--- Return false
   }
   scaleLayer.gaugeLabel.value.text = DoubleToString(value, (int)scaleLayer.gaugeLabel.value.decimalPlaces); //--- Set text
   if(!scaleLayer.obj_Canvas.FontSet(scaleLayer.gaugeLabel.value.fontName, scaleLayer.gaugeLabel.value.fontSize, scaleLayer.gaugeLabel.value.fontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.TextOut(scaleLayer.gaugeLabel.value.x, scaleLayer.gaugeLabel.value.y, scaleLayer.gaugeLabel.value.text, ColorToARGB(scaleLayer.gaugeLabel.value.textColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
   scaleLayer.obj_Canvas.Update(true);     //--- Update canvas
   return true;                            //--- Return true
}

まず、RedrawScaleMarksメソッドを実装して、目盛りの目盛りとラベルを再生成します。インデックスと角度を宣言し、乗数を定義済みの配列から設定するか、無効な場合はデフォルトで1に設定し、入力から最小値と最大値を割り当て、小数点以下の桁数を0とします。スケールが上昇しているかどうかを確認し、進行方向を設定して、それに応じて値の範囲を計算します。ヌルマークの位置を1に固定し、ゲージの円弧から最小角度と最大角度を割り当て(開始と終了を入れ替えて)、必要に応じてラップアラウンドを処理しながらNormalizeRadiansを使用して角度範囲を計算します。左右のマーク数を0に初期化し、マーク用の361エントリのバッファ配列を作成し、インデックス180を中心として、ゼロマークの値と角度を設定します。

方向とループに基づいて標識を作成し、右側の主目盛りのマークを配置します。乗数と角度によって比例的に調整された値を計算し、範囲を超えるまでカウントを増やします。主目盛り、中間目盛り(主目盛りごとに設定されている場合)、補助目盛り(中間目盛りの存在を考慮して調整)について、角度のステップを計算します。まず、カラーゾーンを処理するためにCalculateRangesとDrawRangesを呼び出します。

FontSetを使ってCanvasにフォントを設定し、正しいカウントをインクリメントします。右目盛りの場合、ループして各角度を取得し、主目盛りの内半径と外半径を計算し、MathCosMathSinを使用して円周率調整をおこない、開始位置、終了位置、テキスト位置を計算し、ARGB色でLineAAを使用して目盛り線を描画し、桁数を決定し(0はゼロ値)、DoubleToStringを使用して目盛り値を文字列に変換し、TextOutを使用してラベルを中央に描画します。中間目盛りがある場合は、各中間目盛りの前に副目盛りを描画し、続いて中間目盛りを描画し、その後にも副目盛りを描画します。中間目盛りがない場合は、副目盛りのみを描画します。描画にはDrawTickを使用し、失敗した場合は処理を中断します。左側のマーク(ここではカウントは0ですが、ロジックは対称です)についても同様にループしますが、角度を正に調整し、位置が3でない限りゼロマーク以外の場合にのみ描画するという条件を含め、反対方向に補助目盛り/中間目盛りを描画します。

カラーゾーンを準備するためにCalculateRangesメソッドを定義します。このメソッドでは、主目盛りの内側と外側の半径を計算し、4つの範囲をループ処理します。IsValidRangeがtrueを返す場合は、CalculateRangePieを呼び出し、枠線ギャップを半径ギャップとして含む調整済みパラメータを渡します。他のメソッドは簡単です。理解を助けるためにコメントも加えています。最後に、以下のメソッドを呼び出して、実際の処理を実行させます。

//+------------------------------------------------------------------+
//| Redraw Gauge                                                     |
//+------------------------------------------------------------------+
void CGaugeBase::Redraw() {
   Draw();                                 //--- Call draw
   initializationComplete = true;          //--- Set initialization complete
}

//+------------------------------------------------------------------+
//| Draw Scale and Needle                                            |
//+------------------------------------------------------------------+
void CGaugeBase::Draw() {
   double diameter = m_radius * 2.0;       //--- Calculate diameter
   scaleLayer.scaleMarks.majorTickLength = (int)((diameter * 10.0) / 100.0); //--- Set major tick length
   scaleLayer.scaleMarks.mediumTickLength = (int)((diameter * 7.5) / 100.0); //--- Set medium tick length
   scaleLayer.scaleMarks.minorTickLength = (int)((diameter * 5.0) / 100.0); //--- Set minor tick length
   scaleLayer.scaleMarks.tickFontName = inputParams.tickFontName; //--- Set tick font name
   scaleLayer.scaleMarks.tickFontFlags = 0; //--- Initialize tick font flags
   if(inputParams.tickFontItalic)          //--- Check italic flag
      scaleLayer.scaleMarks.tickFontFlags |= FONT_ITALIC; //--- Add italic flag
   if(inputParams.tickFontBold)            //--- Check bold flag
      scaleLayer.scaleMarks.tickFontFlags |= FW_BOLD; //--- Add bold flag
   scaleLayer.scaleMarks.tickFontSize = (int)((diameter * 6.5) / 100.0); //--- Set tick font size
   scaleLayer.scaleMarks.tickFontGap = GetTickFontGap(scaleLayer.scaleMarks, 3); //--- Set tick font gap
   scaleLayer.externalLabelArea = 0;       //--- Set external label area
   scaleLayer.internalLabelArea = 0;       //--- Set internal label area
   GetTickLabelAreaSize(scaleLayer.internalLabelArea, scaleLayer.scaleMarks, 3); //--- Get tick label area size
   scaleLayer.borderSize = (int)((diameter * 2) / 100.0); //--- Set border size
   scaleLayer.borderGap = (int)((diameter * 3.0) / 100.0); //--- Set border gap
   scaleLayer.externalScaleGap = 0;        //--- Set external scale gap
   scaleLayer.internalScaleGap = scaleLayer.scaleMarks.majorTickLength; //--- Set internal scale gap
   if(inputParams.scaleAngleRange < 30)    //--- Check min angle range
      inputParams.scaleAngleRange = 30;    //--- Set min angle range
   if(inputParams.scaleAngleRange > 320)   //--- Check max angle range
      inputParams.scaleAngleRange = 320;   //--- Set max angle range
   int halfAngleRange = inputParams.scaleAngleRange / 2; //--- Calculate half angle range
   int startAngle = 90 + halfAngleRange + inputParams.rotationAngle; //--- Calculate start angle
   int endAngle = 90 - halfAngleRange + inputParams.rotationAngle; //--- Calculate end angle
   scaleLayer.scaleArc.centerX = m_radius + 5; //--- Set scale arc center X
   scaleLayer.scaleArc.centerY = m_radius + 5; //--- Set scale arc center Y
   scaleLayer.scaleArc.radius = m_radius - (scaleLayer.borderSize + scaleLayer.borderGap + scaleLayer.externalLabelArea + scaleLayer.externalScaleGap); //--- Set scale arc radius
   scaleLayer.scaleArc.startAngle = NormalizeRadians(DegreesToRadians(endAngle)); //--- Set start angle
   scaleLayer.scaleArc.endAngle = NormalizeRadians(DegreesToRadians(startAngle) - 0.0001); //--- Set end angle
   scaleLayer.scaleArc.clr = inputParams.scaleColor; //--- Set scale arc color
   needleLayer.needleCenter.radius = (int)((diameter * 5) / 100.0); //--- Set needle center radius
   needleLayer.needleCenter.display = true; //--- Set display flag
   needleLayer.needleCenter.centerX = scaleLayer.scaleArc.centerX; //--- Set needle center X
   needleLayer.needleCenter.centerY = scaleLayer.scaleArc.centerY; //--- Set needle center Y
   needleLayer.needleCenter.clr = inputParams.needleCenterColor; //--- Set needle center color
   int maxLegendRadius = m_radius - (scaleLayer.borderSize + scaleLayer.borderGap); //--- Calculate max legend radius
   int minLegendRadius = needleLayer.needleCenter.radius; //--- Set min legend radius
   int legendRadiusDelta = maxLegendRadius - minLegendRadius; //--- Calculate legend radius delta
   SetLegendStringParams(scaleLayer.gaugeLabel.description, inputParams.description, minLegendRadius, legendRadiusDelta); //--- Set description params
   SetLegendStringParams(scaleLayer.gaugeLabel.units, inputParams.units, minLegendRadius, legendRadiusDelta); //--- Set units params
   SetLegendStringParams(scaleLayer.gaugeLabel.multiplier, inputParams.multiplier, minLegendRadius, legendRadiusDelta); //--- Set multiplier params
   SetLegendStringParams(scaleLayer.gaugeLabel.value, inputParams.value, minLegendRadius, legendRadiusDelta); //--- Set value params
   CalculateCaseElements(scaleLayer.externalCase, scaleLayer.internalCase, scaleLayer.borderSize, scaleLayer.borderGap); //--- Calculate case elements
   scaleLayer.caseColor = inputParams.caseColor; //--- Set case color
   DrawCaseElements(scaleLayer.externalCase, scaleLayer.internalCase); //--- Draw case elements
   if(inputParams.displayScaleArc)         //--- Check display scale arc
      scaleLayer.obj_Canvas.Arc(scaleLayer.scaleArc.centerX, scaleLayer.scaleArc.centerY, scaleLayer.scaleArc.radius, scaleLayer.scaleArc.radius, scaleLayer.scaleArc.startAngle, scaleLayer.scaleArc.endAngle, ColorToARGB(scaleLayer.scaleArc.clr, scaleLayer.transparency)); //--- Draw scale arc
   RedrawScaleMarks(scaleLayer.internalCase, scaleLayer.scaleArc, scaleLayer.borderGap); //--- Redraw scale marks
   CalculateAndDrawLegends();              //--- Calculate and draw legends
   CalculateNeedle();                      //--- Calculate needle
   scaleLayer.obj_Canvas.Update(true);     //--- Update scale canvas
   needleLayer.obj_Canvas.Update(true);    //--- Update needle canvas
}

CGaugeBaseクラスにRedrawメソッドを実装し、Drawメソッドを呼び出してゲージの可視化を更新し、セットアップが完了したことを示すinitializationCompleteフラグをtrueに設定します。次に、スケールと針のレイヤーの完全な描画を処理するDrawメソッドを定義します。

直径は半径の2倍として計算し、目盛りの長さを比例的に設定します。主目盛りは直径の10%、中間目盛りは7.5%、補助目盛りは5%とします。入力からチェックマークのフォント名を割り当て、フォントフラグを0に初期化し、それぞれのフラグが設定されている場合はFONT_ITALICまたは"FW_BOLD"を追加します。目盛りのフォントサイズを直径の6.5%に拡大し、文字列の長さを3としてGetTickFontGapでフォントギャップを計算します。外部および内部ラベル領域を0にリセットし、その後GetTickLabelAreaSizeを使用して内部領域を更新します。外枠の太さを直径の2%、余白を3%に設定し、外側スケールとの間隔は0、内側スケールとの間隔は主目盛りの長さと同じに設定します。スケール角度の範囲が30度から320度の範囲外の場合は、その範囲の半分を計算し、90度を中心とした回転を加えた開始角度と終了角度を導出します。

スケール円弧の中心をx軸とy軸の両方で半径+5の位置に配置し、メイン半径から枠線、ギャップ、外部ラベル、スケールギャップを差し引いて半径を計算し、NormalizeRadiansとDegreesToRadiansを使用して開始角度と終了角度を正規化ラジアンで設定し(終了角度は微調整)、スケールの色を割り当てます。針の中心については、半径を直径の5%に設定し、表示を有効にし、中心をスケール円弧に合わせ、入力された中心色を適用します。凡例の最大半径は、メイン半径から枠線とギャップを差し引いて決定し、最小半径は針の中心半径、デルタはそれらの差として決定します。次に、これらの半径を使用してSetLegendStringParamsで各凡例文字列(説明、単位、乗数、値)を設定します。

CalculateCaseElements関数を使用して枠線サイズとギャップを渡して枠要素を準備し、枠の色を設定して、DrawCaseElements関数で描画します。スケール円弧表示が有効になっている場合、ARGBカラーを使用して円弧でCanvas上に描画します。内枠、ゲージの円弧、枠線ギャップを渡してRedrawScaleMarksでマークを再描画し、CalculateAndDrawLegendsで凡例を計算して描画し、CalculateNeedleで針を準備し、Updateをtrueに設定してスケールCanvasと針Canvasの両方を更新します。これでクラスは完成し、それぞれのメソッドを呼び出すことで必要に応じてプロパティを設定できますが、まず最初に、以下のようにグローバルスコープでスケール乗数と文字列配列を設定する必要があります。

double scaleMultipliers[9] = {10000, 1000, 100, 10, 1, 0.1, 0.01, 0.001, 0.0001}; //--- Define scale multipliers array
string scaleMultiplierStrings[9] = {"x10k", "x1k", "x100", "x10", " ", "/10", "/100", "/1k", "/10k"}; //--- Define multiplier strings array

scaleMultipliers配列は、スケーリング係数を含む9つの要素を持つグローバルなdouble型配列として定義されます。10000、1000、100、10、1、0.1、0.01、0.001、および0.0001は、ゲージスケール上のマーク値を調整するために使用されます。また、scaleMultiplierStrings配列を、表示ラベルを提供する9つの要素を持つグローバル文字列配列として定義します。これらの要素は、x10k、x1k、x100、x10、スペース、/10、/100、/1k、/10kであり、凡例における視覚的表現のための乗数に対応します。これで初期化関数に進み、更新されたプロパティでゲージを描画できます。

gauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 1); //--- Set case parameters
gauge.SetScaleParameters(250, 0, 0, 100, 4, 0, clrBlack, false); //--- Set scale parameters
gauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set tick parameters
gauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set tick label font
gauge.SetRangeParameters(0, true, 0, 30, clrLimeGreen); //--- Set range 0
gauge.SetRangeParameters(1, true, 70, 100, clrCoral); //--- Set range 1
gauge.SetRangeParameters(2, true, 30, 70, clrYellow); //--- Set range 2
gauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set range 3
gauge.SetLegendParameters(0, true, "RSI", 8, -180, 20, "Arial", false, false, clrBlueViolet); //--- Set legend 0
gauge.SetLegendParameters(3, true, "2", 4, 180, 13, "Arial", true, false, clrRed); //--- Set legend 3
gauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1); //--- Set needle parameters
gauge.Redraw();                         //--- Redraw gauge
gauge.NewValue(0);                      //--- Set new value 0

OnInitイベントハンドラでは、枠の色をミントクリーム、枠線のスタイルを1、枠線の色をライトスカイブルー、ギャップサイズを1としてgauge.SetCaseParametersを呼び出すことで、ゲージの枠を設定します。スケールプロパティはgauge.SetScaleParametersを使用して設定し、角度範囲を250度、回転なし、最小値0、最大値100、乗数インデックス4(1に相当)、スタイル0、黒色、円弧表示をfalseとします。目盛りについては、スタイル0、サイズ2、主目盛りの間隔を10とし、各区間に中間目盛りを1つ、補助目盛りを4つ配置します。目盛りラベルのフォントは、サイズ1、フォント名Arial、イタリック体や太字なし、色黒で、gauge.SetTickLabelFontを使用して適用します。gauge.SetRangeParametersで範囲を定義します。インデックス0は0から30までライムグリーンで有効、インデックス1は70から100までコーラルで有効、インデックス2は30から70まで黄色で有効、インデックス3は無効でダミー値が灰色で表示されます。

凡例については、gauge.SetLegendParametersを使用して、説明(タイプ0)をテキストRSI、半径8、角度-180、フォントサイズ20、Arial、斜体または太字なし、青紫色で有効にし、値(タイプ3)をテキスト2(小数点用)、半径4、角度180、サイズ13、Arial、斜体、太字なし、赤色で有効に設定します。中心スタイル1、中心色黒、針色薄灰色、塗りつぶしスタイル1を使用して、gauge.SetNeedleParametersで針を設定します。最後に、gauge.Redrawを呼び出してゲージを描画し、gauge.NewValueを0で呼び出してポインタの位置を初期化します。コンパイルすると、次の結果が得られます。

ゲージ初期化

可視化から、ゲージにすべてのプロパティを設定したことがわかります。残る課題は、新しい値が計算された際にデータに反応するように、システムに命を吹き込むことです。それを実現するには、OnCalculateイベントハンドラ内で該当する関数を呼び出す必要があります。

static datetime lastBarTime = 0;        //--- Declare last bar time
bool isNewBar = (ratesTotal > 0 && time[ratesTotal - 1] != lastBarTime); //--- Check new bar
if(isNewBar)                            //--- If new bar
   lastBarTime = time[ratesTotal - 1];  //--- Update last bar time
if(isNewBar) {                          //--- If new bar
   int barsCalculated = BarsCalculated(rsiHandle); //--- Get bars calculated
   if(barsCalculated > 0) {             //--- Check calculated
      double currentRsiValue[1];        //--- Declare current value
      if(CopyBuffer(rsiHandle, 0, 0, 1, currentRsiValue) < 0) //--- Copy buffer
         Print("RSI CopyBuffer error for gauge"); //--- Print error
      else                              //--- Else
         gauge.NewValue(currentRsiValue[0]); //--- Set new value
   }
}

ここでは、OnCalculate関数の呼び出し間で最後に処理されたバーのタイムスタンプを追跡するために、0に初期化された静的なdatetime変数lastBarTimeを宣言します。ratesTotalが0より大きく、time[ratesTotal - 1]のタイムスタンプがlastBarTimeと異なる場合、isNewBarをtrueに設定することで、新しいバーが形成されたかどうかを判断します。isNewBarがtrueの場合、lastBarTimeを現在のバーのタイムスタンプに更新します。isNewBarの別のチェックでは、BarsCalculatedを使用して相対力指数ハンドルの計算済みバーの数を取得します。これが0より大きい場合、単一要素のdouble配列currentRsiValueを作成し、CopyBufferを使用してハンドルのバッファ0の位置0から最新の値をコピーしようと試み、コピーが失敗した場合はエラーを出力し、それ以外の場合は値をgauge.NewValueに渡してゲージ表示を更新します。ティックごとの値を表示したい場合は、新しいバーのロジックを無視しても構いません。コンパイルすると、次の結果が得られます。

ライブRSIゲージ

ゲージに命を吹き込んだ後は、描画されたオブジェクトを削除するためにゲージを削除するだけで、それで完了です。

//+------------------------------------------------------------------+
//| Deinitialize Indicator                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   gauge.Delete();                         //--- Delete gauge
   ChartRedraw();                          //--- Redraw chart
}

インジケーターがチャートから削除されたとき、またはターミナルがシャットダウンされたときに呼び出されるOnDeinitイベントハンドラでは、gauge.Deleteメソッドを呼び出してゲージのスケールと針レイヤーオブジェクトを削除し、グラフィックリソースのクリーンアップを確実におこないます。次に、ChartRedrawを呼び出してチャート表示を更新し、ゲージ表示の残りをすべて消去します。結果として、以下のことが分かりました。

ゲージ削除GIF

可視化から、インジケーターを計算し、ゲージを描画し、パラメータを設定し、不要になったら削除することで、目的を達成していることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

RSI CANVASゲージバックテストGIF


結論

MQL5でゲージ型RSIインジケーターを作成しました。このインジケーターは、動的な針、買われすぎと売られすぎのゾーンの色分けされた範囲、正確に読み取るための目盛り、情報補足用の凡例を備えた円形ダイヤル上にモメンタム値を表示し、従来の線グラフを統合し、組み込みのiRSI関数を介して新しいバーの更新を最適化します。このインジケーターは、スケール、フォント、ビジュアルなどのパラメータを柔軟に設定できるため、市場分析のための魅力的なツールとなります。今後の記事では、コードをモジュール化して、より高度でスタイリッシュなゲージを作成できるようにする方法を詳しく解説します。どうぞご期待ください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20632

添付されたファイル |
最後のコメント | ディスカッションに移動 (3)
Clemence Benjamin
Clemence Benjamin | 18 12月 2025 において 20:36
独創的なアイデアをありがとう。
Allan Munene Mutiiria
Allan Munene Mutiiria | 20 12月 2025 において 15:56
Clemence Benjamin #:
独創的なアイデアをありがとう。
クレマンス・ベンジャミンさん、ご丁寧なフィードバックをありがとう。
Brian Mutuku Mwanthi
Brian Mutuku Mwanthi | 22 12月 2025 において 05:50
ありがとうございます。

関係ないのですが、このチャートには別のEAが複数のトレードを行っているようです。

各ローソク足に複数のトレードがありますが、どのEAでしょうか?
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
共和分株式による統計的裁定取引(第9回):バックテストポートフォリオのウェイト更新 共和分株式による統計的裁定取引(第9回):バックテストポートフォリオのウェイト更新
本記事では、共和分関係にある銘柄を通じた統計的裁定取引を利用する平均回帰ベースの戦略において、ポートフォリオのウェイト更新をバックテストするためにCSVファイルを使用する方法について説明します。データベースへのローリングウィンドウ固有ベクトル比較(RWEC, Rolling Windows Eigenvector Comparison)の結果入力から、バックテストレポートの比較までを網羅します。その一方で、各RWECパラメータの役割と、それが全体的なバックテスト結果に与える影響を詳しく説明し、相対的なドローダウンの比較がこれらのパラメータをさらに改善するのにどのように役立つかを示します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
古典的な戦略を再構築する(第20回):現代のストキャスティクス 古典的な戦略を再構築する(第20回):現代のストキャスティクス
本記事では、古典的なテクニカル指標であるストキャスティクスを、従来の平均回帰ツールとしての使い方にとどまらず、どのように再解釈および再活用できるかを解説します。異なる分析視点からこの指標を捉え直すことで、慣れ親しんだ手法が新たな価値を生み出し、トレンドフォロー型の解釈を含む代替的な売買ルールの構築にも応用できることを示します。最終的に、MetaTrader 5ターミナルに搭載されているあらゆるテクニカル指標には未開拓の可能性が潜んでおり、試行錯誤を慎重に重ねることで、従来の見方では気づきにくい有意義な解釈を発見できることを示します。