LoginSignup
0
0

More than 5 years have passed since last update.

C++ Builder XE4 > TeeChart > 軸 > グラフをクリックした時、その地点に近いSeries1のY値を表示する v0.1-v0.3

Last updated at Posted at 2019-01-08
動作環境
C++ Builder XE4

関連

概要

  • クリック地点のTDateTime値を計算
  • その値に近い日時に対応するインデックスをSeries1から取得
  • そのインデックスのSeries1のY値を取得し表示

関数 Search_binary()

UtilListSearch_double.cpp/.hにおいて実装している。

注意: この関数がないと下記の実装は動作しない

内容はC > 配列の中から一番近い値を探索 (double型) > v0.1のBinarySearch()と同じものであるが、C++ Builderで使うようにクラス化し、static関数としている(インスタンスでの使用は検討していない)。

実装 v0.1

Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
#include "Unit1.h"
#include "UtilListSearch_double.h" // 自前実装の探索処理
//
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
    Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"mm/dd hh:nn";

    TDateTime dt;

    dt = Now();

    double yval;
    for (int idx=0; idx < 1000; idx++) {
        yval = (1+ idx) % 200;
        Series1->AddXY(dt, yval, "", clRed);
        dt = IncMinute(dt, 5);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chart1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
    String msg;

    // 1. 開始日時と終了日時の表示
    TDateTime startdt = Chart1->BottomAxis->Minimum;
    TDateTime enddt = Chart1->BottomAxis->Maximum;

    msg = L"[" + startdt.FormatString(L"yyyy/mm/dd hh:nn");
    msg += L" to " + enddt.FormatString(L"yyyy/mm/dd hh:nn") + L"]";
    Memo1->Lines->Add(msg);

    // 2. クリックしたXから日時を計算
    TDateTime dtclick = calcClickedDateTime(X, Chart1);
    if (SecondsBetween(dtclick, 0) == 0) { // 範囲外
        return;
    }
    msg = L"Click:" + dtclick.FormatString(L"yyyy/mm/dd hh:nn");
    Memo1->Lines->Add(msg);

    // 3. 近い日時のインデックスを探索し、Series1の値を表示
    int idx = Chart_findIndex(dtclick, Series1);
    if (idx >= 0) {
        TDateTime findDt = Series1->XValue[idx];
        msg = L"nearest:" + findDt.FormatString(L"yyyy/mm/dd hh:nn") +
            L" [" + Series1->YValue[idx] + L"]";
        Memo1->Lines->Add(msg);
    }
}

TDateTime __fastcall TForm1::calcClickedDateTime(int Xpos, TChart *chrPtr)
{
    // クリックした地点の日時を計算

    if (chrPtr == NULL) {
        return 0;  // error
    }

    if (Xpos < chrPtr->ChartRect.left) {
        return 0;  // error
    }
    if (Xpos > chrPtr->ChartRect.right) {
        return 0;  // error
    }

    int xrange = chrPtr->ChartRect.Width();  // 日時の幅
    int xclick = Xpos - chrPtr->ChartRect.Left; // クリック地点の開始日時からの幅 [単位:Xに基づく]
    double ratio = (double)xclick / (double)xrange;

    int elapsed = SecondsBetween(chrPtr->BottomAxis->Maximum, chrPtr->BottomAxis->Minimum) * ratio;
    TDateTime dtclick;  // クリック地点の日時

    dtclick = IncSecond(chrPtr->BottomAxis->Minimum, elapsed);
    return dtclick;
}

int __fastcall TForm1::Chart_findIndex(TDateTime clickDt, TFastLineSeries *serPtr)
{
    // クリックした地点に近いSeriesの日時を探索
    // Error時の戻り値: -1

    if (serPtr == NULL) {
        return -1;  // error
    }

    double xvals[20000] = {0.0};
    for(int idx=0; idx < serPtr->Count(); idx++) {
        xvals[idx] = serPtr->XValue[idx];
    }

    // Note: [UtilListSearch_double]の関数を使用
    int xpos = CUtilListSearchDouble::Search_binary(xvals, serPtr->Count(), clickDt);
    return xpos;
}
//---------------------------------------------------------------------------

動作例

2019-01-08_19h14_52.png

それぞれクリックしたところに近いSeries1のY値が表示されている。
(予定通りの結果となっている)

ClickとNearestの日時が異なるのは、dt = IncMinute(dt, 5);のようにSeries1は5秒おきのデータであるため。

実装 v0.2

  • 変更
    • Chart_getXYValue_nearest()に処理を抽出
Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
#include "Unit1.h"
#include "UtilListSearch_double.h" // 自前実装の探索処理
//
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
    Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"mm/dd hh:nn";

    TDateTime dt;

    dt = Now();

    double yval;
    for (int idx=0; idx < 1000; idx++) {
        yval = (1+ idx) % 200;
        Series1->AddXY(dt, yval, "", clRed);
        dt = IncMinute(dt, 5);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chart1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
    double xval, yval;
    bool res = Chart_getXYValue_nearest(X, Chart1, &xval, &yval);
    if (res == false) {
        return; // not found
    }

    // 結果の表示
    TDateTime xdt = (TDateTime)xval;
    String msg = xdt.FormatString(L"yyyy/mm/dd hh:nn") + L" " +
        String().sprintf(L"%f", yval);
    Memo1->Lines->Add(msg);
}

TDateTime __fastcall TForm1::calcClickedDateTime(int Xpos, TChart *chrPtr)
{
    // クリックした地点の日時を計算

    if (chrPtr == NULL) {
        return 0;  // error
    }

    if (Xpos < chrPtr->ChartRect.left) {
        return 0;  // error
    }
    if (Xpos > chrPtr->ChartRect.right) {
        return 0;  // error
    }

    int xrange = chrPtr->ChartRect.Width();  // 日時の幅
    int xclick = Xpos - chrPtr->ChartRect.Left; // クリック地点の開始日時からの幅 [単位:Xに基づく]
    double ratio = (double)xclick / (double)xrange;

    int elapsed = SecondsBetween(chrPtr->BottomAxis->Maximum, chrPtr->BottomAxis->Minimum) * ratio;
    TDateTime dtclick;  // クリック地点の日時

    dtclick = IncSecond(chrPtr->BottomAxis->Minimum, elapsed);
    return dtclick;
}

int __fastcall TForm1::Chart_findIndex(TDateTime clickDt, TFastLineSeries *serPtr)
{
    // クリックした地点に近いSeriesの日時を探索
    // Error時の戻り値: -1

    if (serPtr == NULL) {
        return -1;  // error
    }

    double xvals[20000] = {0.0};
    for(int idx=0; idx < serPtr->Count(); idx++) {
        xvals[idx] = serPtr->XValue[idx];
    }

    // Note: [UtilListSearch_double]の関数を使用
    int xpos = CUtilListSearchDouble::Search_binary(xvals, serPtr->Count(), clickDt);
    return xpos;
}

bool __fastcall TForm1::Chart_getXYValue_nearest(int Xpos, TChart *chrPtr, double *xdst, double *ydst)
{
    if (chrPtr == NULL || xdst == NULL || ydst == NULL) {
        return false;  // error
    }

    // 1. クリックしたX位置から日時を計算
    TDateTime dtclick = calcClickedDateTime(Xpos, chrPtr);
    if (SecondsBetween(dtclick, 0) == 0) { // 範囲外
        return false;
    }

    // 2. 近い日時のインデックスを探索し、Series1の値を格納
    int idx = Chart_findIndex(dtclick, Series1);
    if (idx < 0) { // not found
        return false;
    }
    *xdst = Series1->XValue[idx];
    *ydst = Series1->YValue[idx];
    return true;
}
//---------------------------------------------------------------------------

2019-01-08_19h41_34.png

実装 v0.3

  • 変更
    • Series1の定義を関数内で直接指定でなく、関数の引数で渡すようにした
      • 他のSeriesを渡しやすくするため
    • xvalsをnew/deleteで定義 (固定要素数の廃止)
    • Qiita表示用にコード横幅を短く
Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
#include "Unit1.h"
#include "UtilListSearch_double.h" // 自前実装の探索処理
//
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
    Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"mm/dd hh:nn";

    TDateTime dt;

    dt = Now();

    double yval;
    for (int idx=0; idx < 1000; idx++) {
        yval = (1+ idx) % 200;
        Series1->AddXY(dt, yval, "", clRed);
        dt = IncMinute(dt, 5);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Chart1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
    double xval, yval;
    bool res = Chart_getXYValue_nearest(X, Chart1, Series1, &xval, &yval);
    if (res == false) {
        return; // not found
    }

    // 結果の表示
    TDateTime xdt = (TDateTime)xval;
    String msg = xdt.FormatString(L"yyyy/mm/dd hh:nn") + L" " +
        String().sprintf(L"%f", yval);
    Memo1->Lines->Add(msg);
}

TDateTime __fastcall TForm1::Chart_calcClickedDateTime(int Xpos, TChart *chrPtr)
{
    // クリックした地点の日時を計算

    if (chrPtr == NULL) {
        return 0;  // error
    }

    if (Xpos < chrPtr->ChartRect.left) {
        return 0;  // error
    }
    if (Xpos > chrPtr->ChartRect.right) {
        return 0;  // error
    }

    int xrange = chrPtr->ChartRect.Width();  // 日時の幅
    int xclick = Xpos - chrPtr->ChartRect.Left; // クリック地点の開始日時からの幅 [単位:Xに基づく]
    double ratio = (double)xclick / (double)xrange;

    TDateTime startdt = chrPtr->BottomAxis->Minimum;
    TDateTime enddt = chrPtr->BottomAxis->Maximum;
    int elapsed = SecondsBetween(enddt, startdt) * ratio;
    TDateTime dtclick;  // クリック地点の日時

    dtclick = IncSecond(chrPtr->BottomAxis->Minimum, elapsed);
    return dtclick;
}

int __fastcall TForm1::Chart_findIndex(TDateTime clickDt, TFastLineSeries *serPtr)
{
    // クリックした地点に近いSeriesの日時を探索
    // Error時の戻り値: -1

    if (serPtr == NULL) {
        return -1;  // error
    }

    int numXval = serPtr->Count();
    double *xvals = new double[numXval];
    for(int idx=0; idx < numXval; idx++) {
        xvals[idx] = serPtr->XValue[idx];
    }

    // Note: [UtilListSearch_double]の関数を使用
    int xpos = CUtilListSearchDouble::Search_binary(xvals, numXval, clickDt);
    delete [] xvals;
    return xpos;
}

bool __fastcall TForm1::Chart_getXYValue_nearest(int Xpos, TChart *chrPtr,
        TFastLineSeries *serPtr, double *xdst, double *ydst)
{
    if (chrPtr == NULL || serPtr == NULL ||
        xdst == NULL || ydst == NULL) {
        return false;  // error
    }

    // 1. クリックしたX位置から日時を計算
    TDateTime dtclick = Chart_calcClickedDateTime(Xpos, chrPtr);
    if (SecondsBetween(dtclick, 0) == 0) { // 範囲外
        return false;
    }

    // 2. [serPtr]の値から近い日時のインデックスを探索し、X,Y値を格納
    int idx = Chart_findIndex(dtclick, serPtr);
    if (idx < 0) { // not found
        return false;
    }
    *xdst = serPtr->XValue[idx];
    *ydst = serPtr->YValue[idx];
    return true;
}
//---------------------------------------------------------------------------
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0