C++ Builder XE4 > TeeChart > グラフ位置固定処理 > 凡例、グラフキャプション文字サイズ、Tick長さによらず「同じ位置に」グラフを表示する実装

```txt:動作環境
C++ Builder XE4
TeeChart Lite v2013.08.130414
```


### 自動グラフ描画

TeeChartにてはグラフ位置を自動で計算する機能がある。
これを使うことで、開発者はグラフ位置を気にせず、グラフ描画を行うことができる。

一方で、位置自動計算には「同じ位置に複数のグラフを出力できない」という弊害がある。

グラフ位置固定処理を実装してみた。

## v0.1

### フォームデザイン

![2019-03-14_09h25_33.png](https://qiita-image-store.s3.amazonaws.com/0/32870/2167cea0-e470-4e65-f31a-4350b703bb44.png)

4色の長方形は「位置マーカー」として使う。
位置補正処理が正常に機能しているかの確認用である。

### 実装

```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>
#include "SHDocVw_OCX.h"
#include <Vcl.OleServer.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// Component managed by IDE
	TChart *Chart1;
	TFastLineSeries *Series1;
	TShape *SHP_originalLeft;
	TShape *SHP_originalRight;
	TCheckBox *CHK_correctGeom;
	TShape *SHP_originalTop;
	TShape *SHP_originalBottom;
	TCheckBox *CHK_showGraphTitle;
	TCheckBox *CHK_useRightAxis;
	TLabel *Label1;
	TLabel *Label2;
	TCheckBox *CHK_showLegend;
	TButton *B_larger;
	TLabel *Label3;
	TButton *B_smaller;
	TLabel *Label4;
	TButton *B_longer;
	TButton *B_shorter;
	void __fastcall CHK_useRightAxisClick(TObject *Sender);
	void __fastcall CHK_showLegendClick(TObject *Sender);
	void __fastcall B_largerClick(TObject *Sender);
	void __fastcall B_smallerClick(TObject *Sender);
	void __fastcall B_longerClick(TObject *Sender);
	void __fastcall B_shorterClick(TObject *Sender);
	void __fastcall CHK_showGraphTitleClick(TObject *Sender);
private:	// User declaration
	void __fastcall updateGraphView(TChart *chrPtr);
	void __fastcall addDummyData();
	void __fastcall changeChartRect(int left, int top, int right, int bottom);
	void __fastcall correctGraphGeometry();
	int __fastcall countLeftAxisSeries();
	int __fastcall countRightAxisSeries();
public:		// User declaration
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

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

#include <vcl.h>
#pragma hdrstop

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

// この位置を基準としてグラフ描画領域の上下左右を計算
static const int kLeft_chartRect = 100;
static const int kRight_chartRect = 500;
static const int kTop_chartRect = 50;
static const int kBottom_chartRect = 300;

__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	addDummyData();

	// 補正位置確認用のマーカー配置
	SHP_originalLeft->Left = kLeft_chartRect;
	SHP_originalTop->Top = kTop_chartRect;
	SHP_originalRight->Left = kRight_chartRect;
	SHP_originalBottom->Top = kBottom_chartRect;
}
//---------------------------------------------------------------------------
// 画像更新用
void __fastcall TForm1::updateGraphView(TChart *chrPtr)
{
	if (chrPtr == NULL) {
		return;
	}

	// グラフ描画の更新を促すためフォントサイズを変更する
	int pre = chrPtr->Title->Font->Size;
	chrPtr->Title->Font->Size = pre + 1;
	chrPtr->Title->Font->Size = pre;

	//
	Application->ProcessMessages();  // ***正しい[ChartRect.Top]を得るために必要
	Sleep(100); // msec
}
//---------------------------------------------------------------------------
// グラフにダミーデータを追加
void __fastcall TForm1::addDummyData()
{
	Chart1->Series[0]->XValues->DateTime = true;
    Chart1->BottomAxis->DateTimeFormat = L"nn:ss";

	TDateTime dt = Now();

    for (int idx=0; idx < 10; idx++) {
        double yval = (1+ idx) % 2;
		Series1->AddXY(dt, yval, "", clRed);
		dt = IncSecond(dt, 1);
    }
}

//---------------------------------------------------------------------------
// グラフの領域変更
void __fastcall TForm1::changeChartRect(int left, int top, int right, int bottom)
{
	Chart1->CustomChartRect = true;
	Chart1->ChartRect = Rect(left, top, right, bottom);
	updateGraphView(Chart1);  // ***正しい[ChartRect]を得るために必要
}

//---------------------------------------------------------------------------
// ********************************
// *** 位置正処理の核となる実装 ***
// ********************************
void __fastcall TForm1::correctGraphGeometry()
{
	// 1.
	// 後の補正処理が有効となるようにするため、一度サイズを変更しておく
	// 引数の値は任意であるが、一固定用の値を使っている
	changeChartRect(kLeft_chartRect, kTop_chartRect, kRight_chartRect, kBottom_chartRect);

	// 2.
	int diff_left = Chart1->ChartRect.Left - kLeft_chartRect;
	int diff_right = Chart1->ChartRect.Right - kRight_chartRect;
	int diff_top = Chart1->ChartRect.Top - kTop_chartRect;
	int diff_bottom = Chart1->ChartRect.Bottom - kBottom_chartRect;
	//
	int correct_left = (kLeft_chartRect- diff_left);
	int correct_right = (kRight_chartRect - diff_right);
	int correct_top = (kTop_chartRect - diff_top);
	int correct_bottom = (kBottom_chartRect - diff_bottom);

	// 軸のTickによる誤差の補正
	if (countLeftAxisSeries() > 0) {
		correct_left -= (Chart1->LeftAxis->Tick.Length - 6);  // 6: 結果を見て調整した
	}
	if (countRightAxisSeries() > 0) {
		correct_right += (Chart1->RightAxis->Tick.Length - 6);  // 6: 結果を見て調整した
	}
	// 凡例表示時のずれの補正
	if (Chart1->Legend->Visible) {
		correct_right += 2;  // 2: 結果を見て調整した
	}

	changeChartRect(correct_left, correct_top, correct_right, correct_bottom);
}

//---------------------------------------------------------------------------
// { Operation
void __fastcall TForm1::CHK_useRightAxisClick(TObject *Sender)
{
	if (CHK_useRightAxis->Checked) {
		Chart1->Series[0]->VertAxis = aRightAxis;
	} else {
		Chart1->Series[0]->VertAxis = aLeftAxis;
	}

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}

void __fastcall TForm1::CHK_showGraphTitleClick(TObject *Sender)
{
	if (CHK_showGraphTitle->Checked) {
		Chart1->Title->Text->Text = L"Graph Title";
	} else {
		Chart1->Title->Text->Text = L"";
	}

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}

void __fastcall TForm1::CHK_showLegendClick(TObject *Sender)
{
	if (CHK_showLegend->Checked) {
		Chart1->Legend->Visible = true;
	} else {
		Chart1->Legend->Visible = false;
	}

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}
// } Operation
//---------------------------------------------------------------------------
// 左軸、右軸が表示されているか、の確認に使う
int __fastcall TForm1::countLeftAxisSeries()
{
	int res = 0;

	for(int idx=0; idx < Chart1->SeriesCount(); idx++) {
		if (Chart1->Series[idx]->VertAxis == aLeftAxis) {
			res++;
		}
	}
	return res;
}
int __fastcall TForm1::countRightAxisSeries()
{
	int res = 0;

	for(int idx=0; idx < Chart1->SeriesCount(); idx++) {
		if (Chart1->Series[idx]->VertAxis == aRightAxis) {
			res++;
		}
	}
	return res;
}
//---------------------------------------------------------------------------
// フォントサイズ変更
void __fastcall TForm1::B_largerClick(TObject *Sender)
{
	Chart1->Title->Font->Size += 2;

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}

void __fastcall TForm1::B_smallerClick(TObject *Sender)
{
	Chart1->Title->Font->Size -= 2;

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}
//---------------------------------------------------------------------------
// 軸のTickLength変更
void __fastcall TForm1::B_longerClick(TObject *Sender)
{
	Chart1->LeftAxis->TickLength += 5;
	Chart1->RightAxis->TickLength += 5;

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}
void __fastcall TForm1::B_shorterClick(TObject *Sender)
{
	Chart1->LeftAxis->TickLength -= 5;
	Chart1->RightAxis->TickLength -= 5;

	if (CHK_correctGeom->Checked) {
		correctGraphGeometry();
	}
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
```

## 動作例

### 位置補正機能OFF

ソフト起動直後
![2019-03-14_09h27_29.png](https://qiita-image-store.s3.amazonaws.com/0/32870/0961ec2a-77d7-a4b9-0378-ba15c0531159.png)

左軸表示から右軸表示へ変更
![2019-03-14_09h28_04.png](https://qiita-image-store.s3.amazonaws.com/0/32870/d57d52d2-1fbf-5553-cd13-3e7935b1d3fe.png)

Tick長さを増やした後
![2019-03-14_09h28_46.png](https://qiita-image-store.s3.amazonaws.com/0/32870/13c2860f-4e94-787f-772d-dec1784ff2e9.png)

グラフキャプションのフォントサイズを大きくした後
![2019-03-14_09h29_23.png](https://qiita-image-store.s3.amazonaws.com/0/32870/e0fbddcf-a571-a592-fa15-76066df8f4af.png)

**まとめ: グラフ描画領域はそれぞれ異なる**

### 位置補正機能ON


ソフト起動直後
![2019-03-14_09h27_29.png](https://qiita-image-store.s3.amazonaws.com/0/32870/0961ec2a-77d7-a4b9-0378-ba15c0531159.png)

**CHK_correctionにチェックを入れた**

左軸表示から右軸表示へ変更
![2019-03-14_09h30_50.png](https://qiita-image-store.s3.amazonaws.com/0/32870/df58204f-c3c6-963a-b116-0ed027177e02.png)

Tick長さを増やした後
![2019-03-14_09h31_18.png](https://qiita-image-store.s3.amazonaws.com/0/32870/a137e61e-f6e7-d62b-6398-1705145df1a5.png)

グラフキャプションのフォントサイズを大きくした後
![2019-03-14_09h31_54.png](https://qiita-image-store.s3.amazonaws.com/0/32870/481748ec-75bc-d912-10c0-54d021ecd192.png)


**まとめ: グラフ描画領域を同じにすることができた**

### はまった点

- ChartRectの値が描画のタイミングにより古い値になることがある
    - updateGraphView()を使うことで、最新値を取得できるようになった
- correctGraphGeometry()において補正処理が有効とならない場合があった
    - 一旦、別の定義でグラフ領域を設定することで対応できた

### 備考

- コードは最適なものでない
    - Chart1を直接使ったり、引数で取得したりなど

ここから先（非公開）では、自分が使いたい形に別ソース化する。


### 備考2

「グラフの位置固定表示」
希望するユーザも散見している。

それをTeeChartで「簡単に」実装する方法を検討したが、自分の調査力のなさで見つけられなかった。

forumにも時々「固定位置にならないがどうしたらいい？」という質問が見られるため、TeeChartにはないのかもしれない。

### 別の案

別の案も検討したが、実装はしなかった。

- フォントを先に表示しておき、色を背景色と同じにする
    - 右軸の表示に難有り
        - 右軸へシリーズを割当てする必要あり
            - 割当てすると不要なGridの表示なども発生する
        - 右軸表示用のダミーシリーズが必要になり、ソースの可読性が低下する
- 軸表示をなくして、自分で描画
    - 軸の数値の計算、描画の実装が大変
    - Grid表示も実装が大変
    - 大変な実装はメンテナンスが大変

など


### 備考3 > グラフキャプションの左位置

グラフキャプションの左位置は「ずれ」ます。
表示文字列の長さによっても変化し、これを固定化するのはまた別の処理が必要でしょうね。