LoginSignup
1
0

More than 5 years have passed since last update.

C++ Builder XE4 > TeeChart > クリックした点に近い系列データの明示 > fix bug: Clicked()で位置ずれする探索の補正 + 備考

Last updated at Posted at 2018-12-11
動作環境
C++ Builder XE4
TeeChart Lite v2013.08.130414 32bit VCL

関連

Clicked()で探索したインデックスは位置ずれすることがある。その補正を検討した。

検討内容

  • Clicked()で1つずれる
  • 1つずれた位置から前後のインデックスを含めて最探索する
  • [位置ずれの補正前]と[補正後]を見ることができるように、Series5を[補正後]として追加
  • Series1に対してSeries2のマーカーを表示する
  • Series3に対して以下の2つのマーカーを表示する
    • Series4: 補正前
    • Series5: 補正後

フォームデザイン

2018-12-11_16h18_55.png

実装 v0.2

Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
#include <VCLTee.Chart.hpp>
#include <VCLTee.Series.hpp>
#include <VCLTee.TeEngine.hpp>
#include <VCLTee.TeeProcs.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    TChart *Chart1;
    TFastLineSeries *Series1;
    TEdit *E_xvalue;
    TEdit *E_yvalue;
    TPointSeries *Series2;
    TFastLineSeries *Series3;
    TPointSeries *Series4;
    TPointSeries *Series5;
    void __fastcall Chart1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y);
private:    // ユーザー宣言
    int __fastcall TForm1::addMarker_nearestIndex(TDateTime referenceDt, int oneOffIndex, TFastLineSeries *targetPtr, TPointSeries *markerPtr);
public:     // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
//#include <float.h>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

/*
v0.2 2018/12/11
    - fix bug: Clicked()により位置ずれが起きる
        + addMarker_nearestIndex()追加
*/


__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // テスト用の系列データ追加

    Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"nn:ss";

    TDateTime dt;

    double yval;
    // 一つ目のシリーズ
    dt = Now();
    for (int idx=0; idx < 10; idx++) {
        yval = (1+ idx) % 3;
        Series1->AddXY(dt, yval, "", clRed);
        dt = IncSecond(dt, 1);
    }

    // 二つ目のシリーズ
    dt = Now();
    for (int idx=0; idx < 10; idx++) {
        yval = (1+ idx + 1) % 3;
        Series3->AddXY(dt, yval, "", clRed);
        dt = IncSecond(dt, 1);
    }
}
//---------------------------------------------------------------------------

#define POINT_NOT_FOUND (-1)

void __fastcall TForm1::Chart1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
    TFastLineSeries *targetPtrs[] = { Series1, Series3 };
    int numSer = sizeof(targetPtrs) / sizeof(targetPtrs[0]);
    TPointSeries *markerPtrs[] = { Series2, Series4 };

    // グラフ上端から下端まで走査してインデックスを見つける
    int top = Chart1->ChartRect.Top;
    int btm = Chart1->ChartRect.Bottom;
    int index = POINT_NOT_FOUND;  // 系列のインデックスが入る

    for(int tgtIdx = 0; tgtIdx < numSer; tgtIdx++) {  // tgtIdx: target index
        for(int ypos = top; ypos <= btm; ypos++) {
            index = targetPtrs[tgtIdx]->Clicked(X, ypos);
            if (index != POINT_NOT_FOUND) {
                break;
            }
        }

        // A. クリックが外れていたときに消す
        if (index == POINT_NOT_FOUND) {
            if (markerPtrs[tgtIdx]->Count() > 0) {
                markerPtrs[tgtIdx]->Delete(0);
                E_xvalue->Text = L"";
                E_yvalue->Text = L"";
            }
            continue;
        }

        // B. クリックが線上の時に表示

        // 既存のものは消す
        if (markerPtrs[tgtIdx]->Count() > 0) {
            markerPtrs[tgtIdx]->Delete(0);
        }

        // 点の追加
        double xval = targetPtrs[tgtIdx]->XValue[index];
        double yval = targetPtrs[tgtIdx]->YValue[index];
        markerPtrs[tgtIdx]->AddXY(xval, yval);

#if 1 // 2018/12/11
        // Clicked()でずれるため、近傍を見つけなおす. 対象として[Series5]をマーカーとして使う
        if (tgtIdx == 0) {
            int correctedIdx = addMarker_nearestIndex(/*referenceDt=*/xval, /*oneOffIndex=*/index,
                /*targetPtr=*/Series3, /*markerPtr=*/Series5);
            int nop = 1; // nop(No Operation) // for breakpoint
        }
#endif

        E_xvalue->Text = String().sprintf(L"%.3f", xval);
        E_yvalue->Text = String().sprintf(L"%.3f", yval);
    }
}
//---------------------------------------------------------------------------

static const int kIndex_notFound = -1; // 探索が失敗した時の値

int __fastcall TForm1::addMarker_nearestIndex(TDateTime referenceDt, int oneOffIndex, TFastLineSeries *targetPtr, TPointSeries *markerPtr)
{
    // [探索済みインデックス±1]のインデックスにおいてDateTimeが一番近いインデックスを調べなおす
    // Clicked()でインデックスがずれるための対処
    //
    // 戻り値: 探索した[targetPtr]のインデックス

    // 1. 引数エラー処理
    if (targetPtr == NULL ||
        markerPtr == NULL) {
        return kIndex_notFound;
    }

    // 2. 近傍の探索しなおし
    __int64 diff_msec = 1e9; // 1e9: 任意の値 | 最小値を見つけるための初期値
    int minPos = kIndex_notFound; // 探索後のインデックス値(>=0)を格納する

    //   2-1 既存の点は消す
    if (markerPtr->Count() > 0) {
        markerPtr->Delete(0);
    }

    //   2-2 近傍を探しなおし
    for(int idx=-1; idx<=1 ; idx++) {  // 近傍の日時をチェック
        double xval = targetPtr->XValue[oneOffIndex + idx];
        __int64 wrk = MilliSecondsBetween(xval, referenceDt);  // 絶対値で返る
        if (diff_msec > wrk) {
            diff_msec = wrk;
            minPos = (oneOffIndex + idx);
        }
    }

    // 3. マーカーを加える
    if (minPos != kIndex_notFound) {
        double xval = targetPtr->XValue[minPos];
        double yval = targetPtr->YValue[minPos];
        markerPtr->AddXY(xval, yval);

        return minPos;
    }

    return kIndex_notFound;

}

動作例

2018-12-11_15h59_08.png

  • 赤色: 位置ずれした結果 (Clicked()の結果そのまま)
  • 黄色: 位置補正した結果 (今回の実装で補正)

関連

実装 v0.3 > 関数の役割の変更

v0.3 2018/12/11 関数の整理
    - refactor > addMarker_nearestIndex()をfindFastLineSeriesIndex_oneOffIndexOf()に変更
        + マーカー追加処理はChart1MouseDown()へ変更
        + インデックスを返すように変更
        + マーカー追加処理の除去
Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
#include <VCLTee.Chart.hpp>
#include <VCLTee.Series.hpp>
#include <VCLTee.TeEngine.hpp>
#include <VCLTee.TeeProcs.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    TChart *Chart1;
    TFastLineSeries *Series1;
    TEdit *E_xvalue;
    TEdit *E_yvalue;
    TPointSeries *Series2;
    TFastLineSeries *Series3;
    TPointSeries *Series4;
    TPointSeries *Series5;
    void __fastcall Chart1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y);
private:    // ユーザー宣言
    int __fastcall TForm1::findFastLineSeriesIndex_oneOffIndexOf(TDateTime referenceDt, int oneOffIndex, TFastLineSeries *targetPtr);
public:     // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
//#include <float.h>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

/*
v0.3 2018/12/11 関数の整理
    - refactor > addMarker_nearestIndex()をfindFastLineSeriesIndex_oneOffIndexOf()に変更
        + マーカー追加処理はChart1MouseDown()へ変更
        + インデックスを返すように変更
        + マーカー追加処理の除去
v0.2 2018/12/11
    - fix bug: Clicked()により位置ずれが起きる
        + addMarker_nearestIndex()追加
        + [kIndex_notFound]追加
*/

static const int kIndex_notFound = -1; // 探索が失敗した時の値


__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // テスト用の系列データ追加

    Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"nn:ss";

    TDateTime dt;

    double yval;
    // 一つ目のシリーズ
    dt = Now();
    for (int idx=0; idx < 10; idx++) {
        yval = (1+ idx) % 3;
        Series1->AddXY(dt, yval, "", clRed);
        dt = IncSecond(dt, 1);
    }

    // 二つ目のシリーズ
    dt = Now();
    for (int idx=0; idx < 10; idx++) {
        yval = (1+ idx + 1) % 3;
        Series3->AddXY(dt, yval, "", clRed);
        dt = IncSecond(dt, 1);
    }
}
//---------------------------------------------------------------------------

#define POINT_NOT_FOUND (-1)

void __fastcall TForm1::Chart1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
    TFastLineSeries *targetPtrs[] = { Series1, Series3 };
    int numSer = sizeof(targetPtrs) / sizeof(targetPtrs[0]);
    TPointSeries *markerPtrs[] = { Series2, Series4 };

    // グラフ上端から下端まで走査してインデックスを見つける
    int top = Chart1->ChartRect.Top;
    int btm = Chart1->ChartRect.Bottom;
    int index = POINT_NOT_FOUND;  // 系列のインデックスが入る

    for(int tgtIdx = 0; tgtIdx < numSer; tgtIdx++) {  // tgtIdx: target index
        for(int ypos = top; ypos <= btm; ypos++) {
            index = targetPtrs[tgtIdx]->Clicked(X, ypos);
            if (index != POINT_NOT_FOUND) {
                break;
            }
        }

        // A. クリックが外れていたときに消す
        if (index == POINT_NOT_FOUND) {
            if (markerPtrs[tgtIdx]->Count() > 0) {
                markerPtrs[tgtIdx]->Delete(0);
                E_xvalue->Text = L"";
                E_yvalue->Text = L"";
            }
            continue;
        }

        // B. クリックが線上の時に表示

        // 既存のものは消す
        if (markerPtrs[tgtIdx]->Count() > 0) {
            markerPtrs[tgtIdx]->Delete(0);
        }

        // 点の追加
        double xval = targetPtrs[tgtIdx]->XValue[index];
        double yval = targetPtrs[tgtIdx]->YValue[index];
        markerPtrs[tgtIdx]->AddXY(xval, yval);

#if 1 // 2018/12/11
        // Clicked()でずれるため、近傍を見つけなおす. 対象として[Series5]をマーカーとして使う
        if (tgtIdx == 0) {
            int correctedIdx = findFastLineSeriesIndex_oneOffIndexOf(/*referenceDt=*/xval, /*oneOffIndex=*/index,
                /*targetPtr=*/Series3);
            int nop = 1; // nop(No Operation) // for breakpoint
            // マーカーの追加
            if (correctedIdx != kIndex_notFound) {
                if (Series5->Count() > 0) {
                    Series5->Delete(0);
                }
                double xval = Series3->XValue[correctedIdx];
                double yval = Series3->YValue[correctedIdx];
                Series5->AddXY(xval, yval);
            }
        }
#endif

        E_xvalue->Text = String().sprintf(L"%.3f", xval);
        E_yvalue->Text = String().sprintf(L"%.3f", yval);
    }
}
//---------------------------------------------------------------------------

int __fastcall TForm1::findFastLineSeriesIndex_oneOffIndexOf(TDateTime referenceDt, int oneOffIndex, TFastLineSeries *targetPtr)
{
    // [探索済みインデックス±1]のインデックスにおいてDateTimeが一番近いインデックスを調べなおす
    // Clicked()でインデックスがずれるための対処
    //
    // 戻り値: 探索した[targetPtr]のインデックス

    // 1. 引数エラー処理
    if (targetPtr == NULL) {
        return kIndex_notFound;
    }

    // 2. 近傍の探索しなおし
    __int64 diff_msec = 1e9; // 1e9: 任意の値 | 最小値を見つけるための初期値
    int minPos = kIndex_notFound; // 探索後のインデックス値(>=0)を格納する
    //
    for(int idx=-1; idx<=1 ; idx++) {  // 近傍の日時をチェック
        double xval = targetPtr->XValue[oneOffIndex + idx];
        __int64 wrk = MilliSecondsBetween(xval, referenceDt);  // 絶対値で返る
        if (diff_msec > wrk) {
            diff_msec = wrk;
            minPos = (oneOffIndex + idx);
        }
    }
    //
    if (minPos != kIndex_notFound) {
        return minPos;
    }
    //
    return kIndex_notFound;
}

備考

実際に使ってみたところ[-1,1]の探索では不十分であった。
データ数が多く、1ピクセルに多数のデータが乗っている状況では、[-30,30]などの探索範囲拡大が必要となる。

実際のデータを参考に検討することになる。

1
0
1

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
1
0