MQL5でカスタムインジケーターを作成する(第2回):Canvasと針のメカニクスを使ったゲージ型RSIインジケーターの構築
はじめに
前回の記事(第1回)では、MetaQuotes Language 5 (MQL5)を使用してピボットベースのトレンドインジケーターを作成しました。ユーザーが定義した期間にわたって高速(短期)ピボットラインと低速(長期)ピボットラインを計算し、これらのラインに対するトレンドの方向を検出し、矢印でトレンドの開始を示し、必要に応じて現在のバーを超えてラインを延長します。第2回では、Canvasと針のメカニクスを用いたゲージ型のRSI(Relative Strength Index:相対力指数)表示を作成します。このモデルは、動的な針を備えた円形ゲージ上にRSIの値を表示し、買われすぎと売られすぎのレベルを色分けした範囲と、カスタマイズ可能な凡例を備えています。また、包括的なモメンタム解析のために従来型の線グラフ表示機能も統合しています。本記事では以下のトピックを扱います。
最終的には、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を返し、処理を停止します。それ以外の場合は、計算が成功したことを示すために、レートの合計を返します。プログラミング言語においては、各マイルストーンごとにプログラムを実行して、すべてが順調に進んでいることを確認するのが常に良い方法です。プログラムを実行すると、以下の結果が得られます。

画像から、標準インジケーターセットが設定されていることがわかります。これは非常に簡単で分かりやすかったです。次にやるべきことは、ゲージを描画するためのクラスを定義することです。そうすることで、後でより多くのゲージを作成する必要が生じたときに、そのクラスを簡単に再利用できます。しかし、その前に、いくつかのヘルパー関数を定義します。
//+------------------------------------------------------------------+ //| 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のテキストサイズを測定し、MathCeilとMathSqrtを使用して幅と高さの対角線を計算し、その対角線の半分を間隔として返します。フォントの設定が失敗した場合、またはサイズがゼロの場合は、デフォルトで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 ¶m, 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 ¶m, 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にフォントを設定し、正しいカウントをインクリメントします。右目盛りの場合、ループして各角度を取得し、主目盛りの内半径と外半径を計算し、MathCosとMathSinを使用して円周率調整をおこない、開始位置、終了位置、テキスト位置を計算し、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に渡してゲージ表示を更新します。ティックごとの値を表示したい場合は、新しいバーのロジックを無視しても構いません。コンパイルすると、次の結果が得られます。

ゲージに命を吹き込んだ後は、描画されたオブジェクトを削除するためにゲージを削除するだけで、それで完了です。
//+------------------------------------------------------------------+ //| Deinitialize Indicator | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { gauge.Delete(); //--- Delete gauge ChartRedraw(); //--- Redraw chart }
インジケーターがチャートから削除されたとき、またはターミナルがシャットダウンされたときに呼び出されるOnDeinitイベントハンドラでは、gauge.Deleteメソッドを呼び出してゲージのスケールと針レイヤーオブジェクトを削除し、グラフィックリソースのクリーンアップを確実におこないます。次に、ChartRedrawを呼び出してチャート表示を更新し、ゲージ表示の残りをすべて消去します。結果として、以下のことが分かりました。

可視化から、インジケーターを計算し、ゲージを描画し、パラメータを設定し、不要になったら削除することで、目的を達成していることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。
結論
MQL5でゲージ型RSIインジケーターを作成しました。このインジケーターは、動的な針、買われすぎと売られすぎのゾーンの色分けされた範囲、正確に読み取るための目盛り、情報補足用の凡例を備えた円形ダイヤル上にモメンタム値を表示し、従来の線グラフを統合し、組み込みのiRSI関数を介して新しいバーの更新を最適化します。このインジケーターは、スケール、フォント、ビジュアルなどのパラメータを柔軟に設定できるため、市場分析のための魅力的なツールとなります。今後の記事では、コードをモジュール化して、より高度でスタイリッシュなゲージを作成できるようにする方法を詳しく解説します。どうぞご期待ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20632
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
共和分株式による統計的裁定取引(第9回):バックテストポートフォリオのウェイト更新
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
古典的な戦略を再構築する(第20回):現代のストキャスティクス
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
独創的なアイデアをありがとう。