2020/09/15

グラフのX軸をスクロールバーで移動




1.背景

データの分析は、データを目に見えるようにグラフ化するところから始まります。Excelはデータ分析をするには非常に便利なアプリです。ワークシートは100万行以上あり、色々なデータを1つのシート上に置くことが出来ます。
しかし一方で古いバージョンではグラフ要素数に制限があったり、またPCの性能で表示できる要素数に限界が出てきてしまい、「全データを遠目に眺める」ことが出来ない場合があります。

そこで今回はグラフのX軸をスライド可能にし、軸を移動させながら全体を見る事のできるアプリを紹介します。
またX軸のデータ数も可変にしましたので、部分的に拡大させてデータを見ることも出来ます。

2.概要

グラフ化に使うデータは、図2-1のように縦方向に並んでいるものとします。データ(今回は2行目以降)の直前行(今回は1行目)にはタイトル行(空白行でも可)が存在する事が前提です。(データが1行目から始まっているとエラーとなります。)
尚サンプルファイルでは、ある会社の株式変動をデータとしました。
また今回アプリは、シート上のボタンで起動するようにしていますが、実際にはアドイン等でExcelに組み込むことが必要です。
(参考)株式データの並び
図2-1

アプリ起動後、まずX軸を決めるために、図2-2のように「X軸の先頭セル」を指定します。
X軸の先頭セルを指定
図2-2

次に図2-3のように「Y軸の列」を指定します。A・B・C等の列番号をクリックします。複数列を指定する場合、隣接列の場合はクリックしたまま横に移動させ、また離れた列の場合は「Ctrlキーを押しながら」必要な列を選択して下さい。
Y軸の列を指定
図2-3

X軸Y軸の指定が完了すると図2-4のように新グラフシートが挿入され、折れ線付き散布図が表示されます。
また、グラフを操作するためのダイアログが表示されます。
グラフの表示(初期状態)
図2-4

初期状態のグラフは「データ先頭から50データ数(データ数が50未満の時は、データの最後まで)」でのグラフになっています。
操作ダイアログで調整することにより、図2-5のように4000データ(今回は4000を上限にしています)までのグラフ表示が可能です。
ダイアログでのグラフの調整
図2-5

操作ダイアログでは図2-6のように、「グラフとして表示されているデータの位置(上側のスクロールバー)」と「グラフのデータ数(下側のスクロールバー)」が調整できます。
また、「データの内、どの位置のデータを表示しているか」「表示しているデータ数は何個か」をラベルとして表示しています。
なおデータの位置は「データ先頭行を1とする相対的行位置」で、Excelの絶対的な行番号ではありません。
ダイアログの機能と表示
図2-6


3.プログラムの流れ

図3-1のように、ユーザーに「X軸の先頭セル」と「Y軸の列」を指定してもらうことで、グラフのデータ範囲(グラフに使用する列、データの行数)を得ます。初回は新規にグラフシートを作成し、そのシートに対して初期のデータ範囲を指定しグラフを完成させます。
操作ダイアログ内のスクロールバーをユーザーが操作すると「データの位置」「データ数」が変更されますので、その変更値を使ってグラフに使用するデータセル範囲を再度計算をし、グラフのデータセル範囲を入れ替えます。
プログラムの流れ
図3-1

図3-1には書き込まれていないプログラムとして「スクロールバーの設定」があります。
詳細は6-3項6-4項で説明しますが、2つのスクロールバーの内「データ数」のスクロールバー(ScrollBar2)は、全データ数のみでMax値・LargeChange値などの設定値が独立して決まりますので、初期に設定すれば完了です。
しかし「データの位置」のスクロールバー(ScrollBar1)の設定値には「グラフの表示データ数」が影響して来ますので、「データ数のスクロールバー」の値を変更した時は「データの位置のスクロールバー」の設定値を変更しています。

4.標準モジュール

4-1.宣言部

標準モジュールの宣言部では図4-1のように、プロジェクト内で使用する定数5つ、及び標準モジュール内で使用する変数3つを宣言しています。
なお同様のアプリである「複数系列のデータを連続的にグラフ化」の時は、変数「myChart」をPublic変数にしてフォームモジュール側でも使用したのですが、今回は変数「myChart」を使うプロシージャは全て標準モジュール側で実行させるように工夫しました。
  1. '========== ⇩① 宣言部 ====================
  2. Public Const sChange As Long = 1       'ScrollBarのSmallChangeの値
  3. Public Const minWidth As Long = 50      'ScrollBar2のMin値(データ数が多い時)
  4. Public Const maxWidth As Long = 4000     'ScrollBar2のMax値(データ数が多い時)
  5. Public Const StartRow As Long = 0      '初期グラフは先頭行からグラフ化
  6. Public Const OverLap As Single = 0.2    'グラフX軸のシフト量(全体の何%がズレるか)
  7. Dim myChart As Object             '作成したグラフオブジェクト
  8. Dim Xstart As Range              'X軸列の先頭セル
  9. Dim Ycol As Range               'Y軸の列群
図4-1

2~6行目は、ダイアログ上のスクロールバーの設定値です。本来はフォームモジュール側で宣言すれば良い定数ですが、どうしても一部の定数を標準モジュール側で使わなければならず、まとめて全ての定数を標準モジュール側でPublic宣言することにしました。
スクロールバーは2つあり、ScrollBar1はX軸の位置決め用、ScrollBar2はX軸のデータ数設定用です。スクロールバーの設定は、ScrollBar1Configプロシージャ(図6-8)、ScrollBar2Configプロシージャ(図6-3)で行いますが、2~6行目の定数をどちらのスクロールバー設定で使用するかを図4-2にまとめました。
なお、5行目の定数StartRowは初回グラフ作成のために図4-3の27行目で使用しています。

スクロールバー定数の使用先一覧
プロパティX軸の位置決め用(ScrollBar1)X軸のデータ数設定用(ScrollBar2)
Maxデータ総数maxWidth(4行目)
Min0(データ先頭行)minWidth(3行目)
Value0(データ先頭行)・・・初期値minWidth(3行目)・・・初期値
LargeChangeScrollBar2.Value値 × OverLap(6行目)minWidth(3行目)
SmallChangesChange(2行目)sChange(2行目)
図4-2

図4-2はデータ数が4000データ(maxWidth値)以上の場合です。それ未満の場合にはグラフ表示及びスクロールバー操作がおかしくなるため、プログラムで設定値を調整しています。
なお、定数の値は自由に調整して構いませんが、maxWidth値は表示するグラフ要素数ですので、「Excelのバージョンや操作するPCの能力」を考慮して決めた方が良いと思います。

7行目は、新しく作るグラフシートオブジェクトを示すmyChart変数です。
8行目のXstartは、ユーザーが指定した「X軸の先頭セル」、9行目のYcolはユーザーが指定した「Y軸の列」を示すRange型変数です。

4-2.メインプログラム

今回のサンプルファイルのシート上の「グラフ化」ボタンをクリックすると呼び出されるプロシージャが図4-3です。
アドインをする際は、このプロシージャを実行すれば良いと思います。
  1. '========== ⇩② メインプログラム ====================
  2. Public Sub Splt_Chart_Start()
  3.  Dim XmaxRow As Long     'データ域の最終行位置
  4.  Dim myWidth As Long     '初回描画用のデータ数
  5.  Dim Xend As Range      'X軸列の最下行セル
  6.  On Error Resume Next
  7.   Set Xstart = Application.InputBox("X軸の先頭セルを指示して下さい", Type:=8)
  8.   Set Ycol = Application.InputBox("Y軸の列を指示して下さい(複数可)", Type:=8)
  9.   If Not Err.Number = 0 Then Exit Sub
  10.  On Error GoTo 0
  11.  Set Xend = Xstart(1).Offset(Rows.Count - Xstart.Row)
  12.  XmaxRow = Xend.End(xlUp).Row - Xstart.Row + 1
  13.  myWidth = Application.WorksheetFunction.Min(XmaxRow, minWidth)
  14.  Set myChart = Charts.Add
  15.  Call DrawChart(StartRow, myWidth, xlXYScatterLines)
  16.  Call UserForm1.UFstart(XmaxRow, xlXYScatterLines)
  17. End Sub
図4-3

12~14行目は、プロシージャ内で使用する変数宣言です。
12行目の「XmaxRow」はデータの要素数(データ行数)を示し、13行目の「myWidth」は初回に表示するグラフのデータ数を示します。
またデータの行数を調べる為の「X軸列の最下行セル」として、14行目で「Xend」を宣言しています。

17行目ではInputBoxを表示させ「X軸の先頭セル」をユーザーに指定してもらいます。
18行目でも、続いてInputBoxを表示させ「Y軸の列」を指定してもらいます。
両方ともRange型としてセル位置・列位置という「セル参照」を取得しますので、InputBoxメソッドの引数Typeには「8」を指定しています。
得られたセル参照は、「X軸の先頭セル」は変数 Xstart に、「Y軸の列」は変数Ycolに代入されます。

InputBoxメソッドは、「OKボタン」をクリックすればRange型を戻してくれますが、「キャンセル」や右上の×印をクリックするとBoolean型のFalseを戻して来ます。しかし代入する先の変数Xstart・YcolはRange型ですので代入できません。

代入出来ないとエラーが発生するのですが、そのエラーを無視して次の行に進めているのが16行目の「On Error Resume Next」です。
17行目か18行目でエラーが発生(=キャンセルをクリックした等)しても、「On Error Resume Next」のおかげでエラーで停止する事は無くコードは進んで行きますが、エラーが発生した時の「Err.Number」はちゃんと保持しています。
ですので19行目で「Err.Number」を調べ、ゼロでは無い(=エラーが発生している)場合はプログラムを終了します。

なお「On Error Resume Next(16行目)」は、単に「エラーを無視している」のでは無く「ユーザーがどういう操作をしたか」をエラーという形で受け取り処理しているのです。
ですので、その処理が終わった後は、20行目のように「On Error GoTo 0」で元の状態(エラーが発生したらプログラムが停止)に戻しています。

22~23行目の処理は「データの要素数」を計算しています。
データの一番下の行(=データの最後の行)を取得する方法として良く使われる方法は、以下の2種類があります。
 ①基準セルに対して「.End(xlDown)」で「空白セルの直前」のセルを取得し、行番号からデータ数を割り出す。
 ②基準セルの行位置を一旦一番下「Application.Rows.Count」まで移動し、そこからデータのあるセルまで「.End(xlUp)」で移動し、その行番号からデータ数を割り出す。

データの中に空白行が無ければ①②どちらの方法でも得られる値は同じですが、空白行がある場合には①は最初の空白行の1つ前までとなり、②の場合は空白行の行数も含んでデータ行とする計算になります。
今回は②の方法で行数を割り出しています。

まず22行目では、Xstartセル(ユーザーがX軸の先頭セルとして指定したセル)を基準とし、そのセルの下に存在する行数分(Rows.Count - Xstart.Row)だけOffset関数で下方向に移動させ、Excelとしての最終行にします。

22行目の式の中で、「Xstart(1)」と「Xstartの1番目のセル」としている理由について説明します。
もしユーザーが先頭セルとしてRange("B2:B3")の様に「複数セルを指定」した場合、Xstartには「Range("B2:B3")」が代入される事になります。もし22行目の式が「Xstart.Offset(Rows.Count - Xstart.Row)」だった場合、得られる結果は「Range("B1048576:B1048577")」になります。
これは「Xstart.Row」は、セル範囲中の先頭セルの行位置(=2)を返すからであり、「B1048577」というセルは存在しませんのでエラーが発生します。

ちなみに、図4-4の方法でもほぼ同じ値が得られます。
  1.  Set Xend = Cells(Rows.Count , Xstart.column)
図4-4

ただし「Cells(行 , 列)」だけでは、「その時点でアクティブになっているシート」が対象になってしまい、ユーザーが操作したシート以外となる可能性があります。もしそうなると間違ったセルを取得する事になってしまいます。
もし違うシートがアクティブになる可能性があるのでしたら、図4-5のように「シートを指定」する必要があります。
ちなみに図4-3の22行目はシート情報を残したままの結果が得られます。
  1.  Set Xend = Xstart.Parent.Cells(Rows.Count , Xstart.column)
図4-5

データの最下行のセル(Xend)が求まったら、23行目でデータの行数を計算します。単純にデータ先頭行位置とデータ最下行セルの行位置の引き算になります。

また「データは最初の空白行までとする」場合には「.End(xlDown)」を使うことになりますが、もしデータが1行しか無い場合に「.End(xlDown)」を使用すると、空白行の下のデータのあるセル(データが無ければ、Excelの最下行のセル)を指すことになります。
ですので、データ行が1行だけの場合も考慮して、図4-6のような条件式にする必要があります。
(「1行だけのデータをグラフにするな!」と言いたいですが、エラーを出さないのが私たちの仕事です。)
  •  If Xstart.Offset(1, 0) = "" Then
  •   XmaxRow = 1
  •  Else
  •   XmaxRow = Xstart.End(xlDown).Row - Xstart.Row + 1
  •  End If
図4-6

24行目は「データ行数」と「定数:グラフの初期表示データ数」を比較して、小さい方を変数myWidthに代入しています。
実はこの計算部分は、フォームモジュールの図6-3で同様の計算をしています。本来ならば統合して同じコードを書かない様にしたかったのですが、以下の理由から泣く泣くダブリコードとしました。

26行目では新たなグラフシートを作成し、27行目では表示するデータのセル範囲とグラフ種類を指定してグラフを完成させているのですが、この27行目と同じ機能がフォームモジュール側に存在しますので、この27行目を削除してフォーム側に統一させる事は、プログラムの流れとしては不可能ではありません。
デメリットは、「グラフシート作成」と「セル範囲指定」の間が空いてしまう(時間が掛かってしまう)ことで、モタモタしている内に「グラフシート作成」段階で「Excelは、アクティブシートにあるデータをデータセル範囲と勝手に判断しグラフを作ってしまう」のです。

グラフを勝手に作られても後から正式にセル範囲指定をするのですから、最終的には思った通りのグラフが完成するのですが、「勝手に作っている時間」が問題になります。例えばサンプルファイルの6000行余りのデータでも私のPCでは5秒以上かかり、その間画面は凍結してしまいます。
やはり「グラフシート作成」と「セル範囲指定」は連続して実行しないと実用的でなくなるようです。

27行目で実行するのは初回に作るグラフですから、データ先頭(StartRow)から表示し、表示するデータ数は最小データ数(myWidth)としています。
「データ先頭」は定数StartRowをそのまま使えます。一方「最小データ数」は図4-1の3行目でminWidthとして定数宣言していますが、その定数値(今回は50データ)よりもデータが少ない場合は「少ないデータ数」に合わせて表示する必要があるため、24行目でワークシート関数を使って少ない方の値を求めmyWidthを使用しています。

27行目が完了した時点で、グラフとしては一応最終仕様になっており、後はダイアログを表示するために29行目を実行します。
ダイアログは標準モジュールから直接起動するのではなく、標準モジュール側からフォームモジュール側へ値を引き継ぐために、引数を使用しています。
渡す値は、スクロールバーの設定に必要な「データ総数(XmaxRow)」と「初期グラフ種類(xlXYScatterLines)」です。この2つの引数は、フォームモジュール側でモジュールレベルの変数として使用されます。

4-3.グラフ軸の設定

グラフのX軸Y軸を設定するプロシージャが、図4-7です。
引数として、データ開始位置(StRow)、グラフ表示のデータ数(CountRow)、グラフ種類(CType)を受け取ります。
  1. '========== ⇩③ グラフ軸設定 ====================
  2. Sub DrawChart(StRow As Long, CountRow As Long, CType As XlChartType)
  3.  Dim i As Long          'グラフの系列
  4.  Dim j As Long          'Y軸指定セル範囲の中のエリア数
  5.  Dim k As Long          'Y軸指定セル範囲の中の同一エリア内の列数
  6.  Dim AxisX As Range       'X軸セル範囲
  7.  Dim AxisY As Range       'Y軸セル範囲
  8.  Dim AxisTX As Range       'X軸タイトル部
  9.  Dim AxisTY As Range       'Y軸タイトル部
  10.  Call myAxis(AxisX, AxisY, AxisTX, AxisTY, StRow, CountRow)
  11.  With myChart
  12.   .ChartType = CType
  13.   .SetSourceData Union(AxisX, AxisY, AxisTX, AxisTY), xlColumns
  14.   i = 1
  15.   For j = 1 To AxisY.Areas.Count
  16.    For k = 1 To AxisY.Areas(j).Columns.Count
  17.     .SeriesCollection(i).XValues = AxisX
  18.     .SeriesCollection(i).Values = AxisY.Areas(j).Columns(k)
  19.     .SeriesCollection(i).Name = AxisTY.Areas(j).Columns(k)
  20.     i = i + 1
  21.    Next k
  22.   Next j
  23.   .Axes(xlCategory).MinimumScale = Application.WorksheetFunction.Min(AxisX)
  24.   .Axes(xlCategory).MaximumScale = Application.WorksheetFunction.Max(AxisX)
  25.  End With
  26. End Sub
図4-7

33~39行目はプロシージャ内で使用する変数宣言です。
33~35行目の i・j・k は、47~55行目のFor~Next文内で使われるカウンタ変数です。
36~39行目のAxisが先頭に付く変数は、41行目のmyAxisプロシージャを呼び出した結果として得られるX軸Y軸のデータセル範囲・タイトルセル範囲です。(図4-10~13を参照下さい)

41行目のmyAxisプロシージャは、第5引数・第6引数にそれぞれ「データ開始位置(StRow)」、「グラフ表示のデータ数(CountRow)」を渡すことで、第1~4引数(X軸Y軸のデータセル範囲・タイトルセル範囲)を得るものです。
自分が受け取った第1引数・第2引数を、そのままmyAxisプロシージャの第5引数・第6引数に横流ししています。

43行目の「With myChart」は、これ以降の処理は全て図4-3の26行目で新作したグラフシートに対しての処理になります。

まず44行目の「.ChartType = CType」は、グラフ種の設定です。
本サイトの「複数系列のデータを連続的にグラフ化」では、「SetSourceData」の前後に「ChartType設定」を配置していましたが、今回のグラフではY軸を入れ替えてはいないので後ろ側のChartType設定は不要のようです。もし変な動きをする場合がありましたら、56行目辺りに「.ChartType = CType」を再度設定してみてください。

45行目は、X軸Y軸のセル範囲を設定しています。
41行目で、「X軸Y軸のデータセル範囲・タイトルセル範囲」はバラバラの状態で受け取っているので、Unionでセル範囲結合させてSetSourceDataに渡しています。
また第二引数として、データ方向を規制するため「xlColumns」を設定していますが、これはデータ数が極端に少ない場合にデータ方向をExcelが誤認した場合があったため、明示的に設定しています。

当初の構想では、41行目のmyAxisプロシージャでは、X軸Y軸およびタイトル範囲までも含めた「まとまったセル範囲」として受取り、そのまま45行目の引数にするつもりでいました。
しかしSetSourceDataに渡すセル範囲は「一番左列がX軸列、Y列はそれより右側の列」と勝手に解釈されるようです。
例えば、「Range("E1:E10,A1:B10,D1:D10")」などと「E列がX軸ですよ」と並べてみても、グラフになると「A列がX軸」として表示されてしまいます。
時間軸等のX軸にしたい列は最も左にあるのが通常なのでしょうが、それ以外にある場合も考えられますし、また時間軸以外をX軸にして相関関係を見たい場合だってあるはずです。

そのため47~55行目では、データ系列へのX軸Y軸範囲の入れ替えをしています。
まず例として、ユーザーがX軸1列とY軸を3列指定したとします。意に反してY列の内1列がX軸となり、Y列の残り2列とX軸1列分がY軸になってしまったとしても、完成したグラフの系列は3列のはずです(1列はX軸ですので)。
つまり割り当てが間違っていたとしても「グラフ系列数 = ユーザー指定のY軸列数」は成立します。

ですので出来上がったグラフの系列毎に、ユーザー指定のY軸列を1つ1つ当てはめ直していけば、ユーザーが期待したグラフになるはずです。47~55行目は、その当てはめ直しをしています。

まず「ユーザーが指示したY軸のセル範囲は、何列分か」を知る必要があります。例えば指定したセル範囲が図4-8だったとします。Rangeで表せば「Range(" C2:D10 , F2:F10 , H2:H10 ")」です。
セル範囲内の各要素を指定する方法
図4-8

まず「マウスの左ボタンを押したまま選択したエリア」が「Area」になります。そのAreaの数は「Areas.Count」で得られ、図4-8では3つとなります。また、それぞれのエリアは「Areas(エリア番号)」で表されます。
各エリアの中に列がいくつあるかは「Areas(エリア番号).Columns.Count」で得られ、図4-8のAreas(1)には2列あることになります。それぞれの列は「Areas(エリア番号).Columns(列番号)」で表されます。

ですので、48行目のFor文でAreasをjとして分けたのち、49行目のFor文で列をkとして分けて得られた「列データ」が
 AxisY.Areas(j).Columns(k) ・・・各列のデータ範囲(51行目)
 AxisTY.Areas(j).Columns(k) ・・・各列のタイトル範囲(52行目)
となりますので、それをグラフ系列の「.Value(Y軸のセル範囲)」および「.Name(凡例名)」に代入しています。
また、「.XValue(X軸のセル範囲)」には50行目で「AxisX(X軸のセル範囲)」を指定します。

なお47行目・53行目で使用している「i」は、グラフの系列数のカウンタ変数になっています。

57~58行目は、X軸の最小値「.Axes(xlCategory).MinimumScale」および最大値「.Axes(xlCategory).MaximumScale」の設定です。X軸範囲である「AxisX」に対してワークシート関数のMin関数、Max関数を使用することで最小値最大値が得られます。
なお、57~58行目の設定をしない場合には、グラフのX軸の最小最大は自動的に「ほど良い間隔を空けて」くれます。但しスクロールバーで表示位置を移動していった場合には、「ほど良さが一定していない」ためにグラフがバタバタ暴れて移動する感じになります。

4-4.グラフ軸のセル範囲計算

図4-7の41行目から呼び出されるのが図4-9になります。
第5引数に「データ開始位置(StRow)」、第6引数に「グラフ表示のデータ数(CountRow)」を受け取り、計算した結果として第1~4引数で「X軸データセル範囲」「Y軸データセル範囲」「X軸タイトルセル範囲」「Y軸タイトルセル範囲」を戻します。
  1. '========== ⇩④ グラフ軸セル範囲計算 ====================
  2. Sub myAxis(AxisX As Range, AxisY As Range, AxisTX As Range, AxisTY As Range, _
  3.                         StRow As Long, CountRow As Long)
  4.  Set AxisX = Xstart.Offset(StRow).Resize(CountRow, 1)
  5.  Set AxisY = Intersect(AxisX.EntireRow, Ycol)
  6.  Set AxisTX = Xstart(1).Offset(-1)
  7.  Set AxisTY = Intersect(Ycol, Xstart(1).EntireRow).Offset(-1)
  8. End Sub
図4-9

64行目は「X軸データセル範囲」を計算しています。図4-10のように、ユーザーが指定したX軸先頭セル「Xstart」を基準にし、Offset関数で引数StRow分だけ下に移動①させます。そこを新基準としセル範囲を下にResize関数でCountRow行分まで拡大した範囲②を「AxisX」としています。
X軸のデータ範囲の計算手順
図4-10

ここで図4-3の22行目の時と同じように、「間違えてXstartに複数セルを設定しまった」時のことを考えてみます。例えば図4-10でXstartがRange("B2:C3")となってしまったような場合です。
その場合、①「Offset(StRow)」の結果はRange("B4:C5")となってしまいますが、②「Resize(CountRow, 1)」では「範囲Range("B4:C5")の先頭セルを基準にResizeをする」ので、結果はRange("B4:B8")となります。
要は「Resize(CountRow, 1)」の「第二引数の1」が列数を先頭セル側に絞ってくれているのです。
(頭が混乱するようでしたら「Xstart(1).Offset(StRow).Resize(CountRow, 1)」としてもOKです。)

65行目は「Y軸データセル範囲」の計算です。図4-11のように、64行目で求めた「AxisX」を含んだ行全体(.EntireRow)①と、ユーザーが指定したY軸列(Ycol)②の重なった範囲(Intersect)③を求め、「AxisY」としています。
このIntersectを使えば、離れた列を選択してYcolが複数のAreaに分かれていたとしても、分かれた状態を保ったまま重なった範囲を返してくれます。
Y軸のデータ範囲の計算手順
図4-11

66行目は「X軸タイトルセル範囲」の計算です。図4-12のように、ユーザーが指定したX軸の先頭セル「Xstart」を基準に一つ上のセル(.Offset(-1))を取得しています。
X軸のタイトル範囲の計算手順
図4-12

ここで図4-10の時と同様に「間違えてXstartに複数セルを設定しまった」時のことを考えてみます。
残念ながら「Xstart.Offset(-1)」という式では、複数セル範囲のまま1つ上に上がるだけですので、セル値が確定せず「X軸のタイトル」を取得することが出来なくなります。
ですので、「Xstart(1).Offset(-1)」と移動の対象を先頭セル1つに絞り、それに対してOffset(-1)をする必要があります。

67行目は「Y軸タイトルセル範囲」の計算です。図4-13のように、ユーザーが指定したX軸先頭セル(Xstart)を含む行全体①と、これもユーザーが指定したY軸列②の重なった範囲(Intersect)③を求めます。そのセル範囲に対してOffset(-1)をすることで、Y軸のタイトル「AxisTY」を得ます。
Y軸のタイトル範囲の計算手順
図4-13

ここでも図4-10の時と同様に「間違えてXstartに複数セルを設定しまった」時のことを考えてみます。
やはり「Xstart.EntireRow」としてしまうと、Intersectで重なった範囲は複数行になってしまい、Offset(-1)でも複数行となりY軸のタイトルを得る事ができなくなってしまいます。
ですので「Xstart(1).EntireRow」とXstartの先頭セルを基準に計算する必要があります。

なお別な方法として、AxisTXのEntireRowとYcolの重なった部分としてAxisTYを求めるやり方もあります。

4-5.グラフ種類の変更

ダイアログの「G種類変更」ボタン(CommandButton1)をクリックした時に間接的に呼び出されるのが図4-14です。
処理対象であるグラフシート「myChart」は、図4-1の7行目で標準モジュールレベル変数として宣言していますので、フォームモジュールからは可視できません。そのため、標準モジュール側で処理を行っています。
  1. '========== ⇩⑤ グラフ種類変更 ====================
  2. Function ChartChange()
  3.  myChart.Activate
  4.  Application.Dialogs(xlDialogChartType).Show
  5.  ChartChange = myChart.ChartType
  6. End Function
図4-14

「G種類変更」ボタンをクリックすると、まず処理対象のグラフシートをアクティブ(71行目)にします。
72行目では組み込みダイアログを表示させ、グラフ種類の変更が可能になります。変更可能なグラフ種は図4-15の内、どれでも可能である訳ではありません。選択しているセル範囲から自動的に「変更可能なグラフ」だけが変更対象グラフ種になります。

グラフ種を変更した、または「キャンセル」ボタンをクリック(=元のグラフ種から変更しない)したら、変更されても されなくても、グラフシートmyChartのグラフ種類をFunctionの戻り値として返します(73行目)。

グラフの種類一覧(1)
定数意味
xl3DArea-40983 D エリア
xl3DAreaStacked783D 積み上げ面
xl3DAreaStacked10079100% 積み上げ面
xl3DBarClustered603D 集合横棒
xl3DBarStacked613D 積み上げ横棒
xl3DBarStacked100623D 100% 積み上げ横棒
xl3DColumn-41003D 縦棒
xl3DColumnClustered543D 集合縦棒
xl3DColumnStacked553D 積み上げ縦棒
xl3DColumnStacked100563D 100% 積み上げ縦棒
xl3DLine-41013D の線
xl3DPie-41023D 円グラフ
xl3DPieExploded70分割 3-d 円
xlArea1分野
xlAreaStacked76積み上げ面グラフ
xlAreaStacked10077100% 積み上げ面
xlBarClustered57集合横棒
xlBarOfPie71バーの円
xlBarStacked58積み上げ横棒
xlBarStacked10059100% 積み上げ横棒
xlBubble15バブル
xlBubble3DEffect873-d 効果付きバブル
xlColumnClustered51集合縦棒
xlColumnStacked52積み上げ縦棒グラフ
xlColumnStacked10053100% 積み上げ縦棒
xlConeBarClustered102集合円錐型横棒
xlConeBarStacked103積み上げ円錐型横棒
xlConeBarStacked100104100% 積み上げ円錐型横棒を横
xlConeCol1053-d 円錐型縦棒
xlConeColClustered99集合円錐型縦棒
xlConeColStacked100積み上げ円錐型縦棒
xlConeColStacked100101100% 積み上げ円錐型縦棒
xlCylinderBarClustered95集合円柱横棒
xlCylinderBarStacked96積み上げ円柱型横棒
xlCylinderBarStacked10097100% 積み上げ円柱型横棒を横
xlCylinderCol983D 円柱型縦棒
xlCylinderColClustered92集合円錐型縦棒
グラフの種類一覧(2)
定数意味
xlCylinderColStacked93積み上げ円錐型縦棒
xlCylinderColStacked10094100% 積み上げ円柱縦棒
xlDoughnut-4120ドーナツ
xlDoughnutExploded80分割ドーナツ グラフ
xlLine4Line
xlLineMarkers65マーカー付き折れ線
xlLineMarkersStacked66マーカー付き積み上げ折れ線
xlLineMarkersStacked10067100% 積み上げ折れ線マーカー付き
xlLineStacked63積み上げ折れ線
xlLineStacked10064100% 積み上げ折れ線
xlPie5
xlPieExploded69分割円グラフ
xlPieOfPie68補助円グラフ付き
xlPyramidBarClustered109集合ピラミッド型横棒
xlPyramidBarStacked110積み上げピラミッド型横棒
xlPyramidBarStacked100111100% 積み上げピラミッド型横棒を横
xlPyramidCol1123-d ピラミッド型縦棒
xlPyramidColClustered106集合ピラミッド型縦棒
xlPyramidColStacked107積み上げピラミッド型縦棒
xlPyramidColStacked100108100% 積み上げピラミッド型縦棒
xlRadar-4151レーダー
xlRadarFilled82塗りつぶしレーダー
xlRadarMarkers81データ マーカー付きレーダー
xlStockHLC88高値-安値-終値
xlStockOHLC89開く-高値-安値-終値
xlStockVHLC90ボリューム-高-安値-終値
xlStockVOHLC91ボリュームに開く-高-安値-終値
xlSurface833D 表面
xlSurfaceTopView85表面 (トップ ビュー)
xlSurfaceTopViewWireframe86表面 (トップ ビューがワイヤ フレーム)
xlSurfaceWireframe843 D サーフェス (ワイヤー フレーム)
xlXYScatter-4169散布図
xlXYScatterLines74折れ線付き散布図
xlXYScatterLinesNoMarkers75行とデータ マーカーなしの散布図
xlXYScatterSmooth72平滑線付き散布図
xlXYScatterSmoothNoMarkers73データ マーカーなし平滑線付き散布図
   
図4-15

4-6.グラフの削除・ダイアログ終了

ダイアログの「G削除・終了」ボタン(CommandButton2)をクリックした時に間接的に呼び出されるのが図4-16です。
処理対象であるグラフシート「myChart」は、図4-1の7行目でモジュールレベル変数として宣言していますので、フォームモジュールからは可視できません。そのため標準モジュール側で処理を行っています。
  1. '========== ⇩⑥ グラフ削除・終了 ====================
  2. Sub ChartDel()
  3.  Application.DisplayAlerts = False
  4.   myChart.Delete
  5.  Application.DisplayAlerts = True
  6.  Unload UserForm1
  7. End Sub
図4-16

78行目はグラフシートを削除してます。
通常、シートを削除する時にはアラート(図4-17)が発生して、OKボタンをクリックしないと削除できません。
グラフシートを削除しようとした時に発生するアラート
図4-17

ですので、そのアラートを出さない様に、77行目で「Application.DisplayAlerts = False」とアラートを非表示にしてからグラフシートを削除しています。
削除後は「Application.DisplayAlerts = True」(79行目)でアラート表示を元の状態に戻しています。

グラフシートを削除後、80行目でダイアログを消しています。
ここで「Unload UserForm1」の代わりに「UserForm1.Hide」を使用してしまうと、ダイアログを隠した時点でのスクロールバー(ScrollBar1)のValue値が残ります。
フォーム起動時のScrollBar1の設定(ScrollBar1Configプロシージャ:図6-8)では、ScrollBar1のValue値はあえて設定していませんので、次回再起動した時には初回なのに「データの先頭からグラフ化」しなくなってしまいます。
Value値をあえて設定しないのは、ScrollBar2を操作した時でも「表示されているグラフの開始行は一緒」の方がユーザーにとって使い易い(粗くグラフを見て、気になったところを拡大できるイメージ)と判断したからです。

なお削除の対象は「図4-3の26行目で作成したグラフシートmyChart」ですので、ダイアログ右上の×印で終了した時に残されたグラフシートはそのまま残ったままとなります。
これはプログラムミスでは無く、グラフ分析中にユーザーが「残しておきたい」と思ったグラフを残す機能が今回無いため、ダイアログ右上×印操作の時には「グラフ削除しない」ことにしました。

5.フォーム外観作成

フォームには図5-1のように、「X軸の位置」を変更するScrollBar1と「X軸の表示データ数」を変更するScrollBar2を配置し、それぞれの上にLabelを配置し、スクロールバーの示している値を表示します。
また、右下には「グラフ種類変更」ボタンと「グラフシート削除・マクロ終了」ボタンを配置しています。
フォーム外観の作成
図5-1

ボタン表面の文字列、及びLabel1~2のタイトルLabel(表示行、表示スパン の文字列)は事前にプロパティで文字列を設定しています。

6.フォームモジュール

6-1.フォームモジュールレベルの変数宣言

フォームモジュールレベルでの変数宣言部分では、図6-1のように2つの変数を宣言しています。
  1. '========== ⇩⑦ 変数宣言 ====================
  2. Dim XmaxRow As Long       'データ総行数
  3. Dim CType As XlChartType    'グラフ種類
図6-1

83行目の「XmaxRow」はデータの総行数、84行目の「CType」はグラフ種類になります。
「XmaxRow」は図6-2の90行目で標準モジュール側から与えられ、「CType」も89行目で初期値として与えられます。「CType」はその後「グラフ種類変更」を行った際には書き換えられます。

6-2.フォームの起動

標準モジュールの図4-3の29行目から呼び出されるプロシージャが図6-2になります。
引数としては、「XMR(データ行総数)」と「CT(グラフ種類)」を受け取ります。
  1. '========== ⇩⑧ フォーム起動 ====================
  2. Sub UFstart(XMR As Long, CT As XlChartType)
  3.  Dim Sb2 As Long          'ScrollBar2の初期設定値(Value値)
  4.  XmaxRow = XMR           '行数の値
  5.  CType = CT             'グラフ種類
  6.  Sb2 = ScrollBar2Config       'ScrollBar2の設定
  7.  Call ScrollBar1Config(Sb2)     'ScrollBar1の設定
  8.  Me.Show 0
  9. End Sub
図6-2

87行目のSb2は、初回のグラフ表示データ数であるScrollBar2のValue値(=ScrollBar2Configの戻り値)を保存する変数です。この値はScrollBar1の設定に必要になります。
89~90行目は、プロシージャの引数として受け取った値をモジュールレベル変数に代入しています。

92行目はScrollBar2の設定を実施しています。「ScrollBar2Config」はFunctionプロシージャ(図6-3)で、戻り値として「初回のグラフデータ数」を戻して来ますので、それを変数Sb2に代入します。
93行目では、92行目で得たSb2を引数にして「ScrollBar1Config(図6-8)」を呼出し、ScrollBar1の設定を実行します。
スクロールバー設定の詳細については、次項・次々項で説明します。

95行目では、フォームをモードレス(シート側の操作も可能)で起動します。

6-3.ScrollBar2の設定

グラフ表示データ数用のスクロールバー(ScrollBar2)は、起動時に1回限り設定するだけです(図6-3)。
  1. '========== ⇩⑨ ScrollBar2の設定 ====================
  2. Private Function ScrollBar2Config()
  3.  Dim maxDataElement As Long    'グラフX軸の最大要素数
  4.  Dim minDataElement As Long    'グラフX軸の最小要素数
  5.  Select Case XmaxRow        'データ行数
  6.   Case Is < minWidth
  7.    minDataElement = XmaxRow
  8.    maxDataElement = XmaxRow
  9.   Case Is < maxWidth
  10.    minDataElement = minWidth
  11.    maxDataElement = XmaxRow
  12.   Case Else
  13.    minDataElement = minWidth
  14.    maxDataElement = maxWidth
  15.  End Select
  16.  Me.ScrollBar2.Max = maxDataElement
  17.  Me.ScrollBar2.Value = minDataElement
  18.  Me.ScrollBar2.Min = minDataElement
  19.  If (maxDataElement - minDataElement) < minDataElement Then
  20.   Me.ScrollBar2.LargeChange = (maxDataElement - minDataElement)
  21.  Else
  22.   Me.ScrollBar2.LargeChange = minDataElement
  23.  End If
  24.  Me.ScrollBar2.smallChange = sChange
  25.  ScrollBar2Config = Me.ScrollBar2.Value
  26. End Function
図6-3

6-3-1.ScrollBar2の設定の考え方

ScrollBar2の設定を行うに当たり、基本的考え方を図6-4に示します。

ScrollBar2の設定値の考え方
図6-4

図4-1ではPublic定数としてScrollBar2のMin値・Max値を宣言しています。(今回はMin=50、Max=4000)
しかし、下記の様に「データの総行数が定数より少ない場合」には、グラフの表示数よりもデータ総行数の方が少なくなり、グラフに余白が出来てしまうことになります。
 ・データ総行数<Min値 の場合
 ・データ総行数<Max値 の場合で、スクロールバーをMax側に持ってきた時
ですので、図6-4の斜め線の部分のように、データ総行数が定数よりも少ない時は「データ総行数そのものをグラフの表示数」とすることにしました。

また、スクロールバーの設定値にはLargeChangeとSmallChangeがあります。動作としては、図6-5に於ける「レール部分」をクリックする事でLargeChangeの値だけ変化し、「スクロール矢印部分」をクリックする事でSmallChangeの値だけ変化します。
スクロールバーの部位名
図6-5

また図6-6に、スクロールバーの操作部位と操作方法で、動く量と発生するイベントの関係を整理しておきます。

スクロールバーの操作と発生するイベントの関係
クリックする場所操作動く量・示す値発生イベント
スクロール矢印クリックSmallChangeプロパティの量Changeイベント
スクロールボックスと
スクロール矢印の間の領域(レール)
クリックLargeChangeプロパティの量Changeイベント
スクロールボックスクリックし移動中スクロールボックスの位置のValue値
(動く量はValue=1ずつ)
Scrollイベント
移動後クリックを離すクリックを離した場所のValue値Changeイベント
図6-6

図6-5では「スクロールバーには必ずスクロールボックスがある」ように思えますが、そうではありません。
図6-7に、Max値・Min値・LargeChange値を色々変えた時のスクロールバー外観をまとめました。
Max値・Min値・LargeChange値によるスクロールバーの外観
図6-7

図6-7を見て分かる通り、「(Max値 - Min値)< LargeChange値 」の場合には、スクロールバーのスクロールボックスが非表示になるのです。
これは外観の問題だけでは無く結構重要な事です。つまり、スクロールボックスが非表示と言う事は「スクロール矢印でのみ操作する」ことになるので、SmallChangeに1より大きな値を設定していた場合には「スクロールバーでは設定できない値(Value値)が発生してしまう」ことになります。

ですので設定できない値が無いように(=スクロールボックスが表示されるように)、Max値・Min値を見ながらLargeChange値を決める必要があります。
また、スクロールボックスのサイズは、赤い数字で表したように「LargeChange / (Max + LargeChange)」が全長に対する割合のようですが、LargeChange値とMax値の比がある程度小さかったり大きかったりすると、この式は当てはまらないようです。

6-3-2.ScrollBar2の設定 その1(Max・Min・Value値)

99~100行目では、プロシージャ内変数として「最大値(maxDataElement)」と「最小値(minDataElement)」を宣言しています。変数を使用せずに、ScrollBar2のMaxプロパティとMinプロパティに直接代入しても問題はありませんが、変数を使用した方が分かり易そうと思ったまでです。

102~112行目は、図6-4の可動範囲(黄色+オレンジのエリア)をコード化している部分です。
このサンプルファイルでは「minWidth=50、maxWidth=4000 」としていますので、数値を念頭に解読すると分かり易いと思います。

102行目は、データの総行数「XmaxRowの値」により選別しています。
図6-4に置き換えれば、横軸が①「0~minWidth」の時が103行目、②「minWidth~maxWidth」の時が106行目、③「maxWidth~」の時が109行目に相当します。

まず図6-4の①「0~minWidth」の場合は「Max値=Min値=XmaxRowの値」ですので、104~105行目の式になります。
②「minWidth~maxWidth」の場合は、「Max値=XmaxRowの値」「Min値=minWidth(一定値)」で、107~108行目の式になります。
③「maxWidth~」の場合は、「Max値=maxWidth(一定値)」「Min値=minWidth(一定値)」で、110~111行目の式になります。

データの総行数「XmaxRowの値」によって、Maxプロパティ、Minプロパティに代入する値が決まりましたので、114~116行目でスクロールバーの設定をします。
尚、Value値には今回は最小値(minDataElement)を代入しています。もちろん最大値(minDataElement)を代入しても良いのですが、初回のグラフ表示の設定ですので「素早く表示が完了するグラフ」になるようにデータ数の少ない最小値の方を選択しました。

6-3-3.ScrollBar2の設定 その2(LargeChange・SmallChange値)

118~123行目は「LargeChange」プロパティ、「SmallChange」プロパティの設定です。
118行目で「minDataElement」と「minDataElement」の差を求め、それを「minDataElement」と比較します。
(図6-4の①の部分は除外(スクロールボックスが表示されない為)されるため「minWidth」と比較しても結果は同じです。)
図6-4に置き換えれば、横軸が④の時(濃いオレンジ色部分)が119行目、⑤の時(明るいオレンジ色部分)が121行目に相当します。

④の部分では、LargeChangeプロパティにminWidth値を設定してしまうと、図6-7のNo2の状態になりますのでスクロールボックスが消えてしまうことになります。ですので、121行目では「 (maxDataElement - minDataElement)」の値を設定しています。
これにより、スクロールバーのレール部分をクリックする(=LargeChange値分を動かす)事でValue値はMax値まで達すると共にスクロールボックスで1つずつの移動も可能になります。
それ以外の場合(⑤明るいオレンジ部分)は単純にminDataElement値(=minWidth値)を設定します(121行目)。

123行目はsmallChangeプロパティの設定で、今回はsChange値(=1)を設定しています。
この値を例えば20や30に設定していて「 (maxDataElement - minDataElement)」を超えていた場合でも、図6-7のNo4の状態に当たりますのでスクロールボックスはちゃんと表示されます。
また、そのような状況でsmallChange値を発生させるスクロール矢印部をクリックしても、オーバースケールする事無くsmallChange値以内での「 (maxDataElement - minDataElement)値」が発生することになり、動作的には問題ありません。

6-3-4.ScrollBar2の設定 その3(関数プロシージャとしての戻り値設定)

このScrollBar2の設定を行っているプロシージャは、関数プロシージャにしています。ですので「ScrollBar2Config関数」としての戻り値を返す必要があります。

このScrollBar2の設定のあと、ScrollBar1の設定を行います。「ScrollBar1の設定」はグラフのX軸データ数により変化しますので、「ScrollBar2のValue値」が必要になります。
ですので、関数としての戻り値は「ScrollBar2.Value」となり、125行目で設定をしてからプロシージャを終了しています。

6-4.ScrollBar1の設定

図6-2の93行目、及びScrollBar2(グラフ表示データ数調整用)を操作したときに呼び出されるのが図6-8です。
引数として、グラフ表示データ数(グラフの表示幅)であるDispWidthを受け取ります。
  1. '========== ⇩⑩ ScrollBar1の設定 ====================
  2. Private Sub ScrollBar1Config(DispWidth As Long)   '第一引数=表示幅(ScrollBar2.Value)
  3.  Me.ScrollBar1.Max = XmaxRow - DispWidth
  4.  Me.ScrollBar1.Min = 0
  5.  If Me.ScrollBar1.Max < DispWidth * OverLap Then
  6.   Me.ScrollBar1.LargeChange = Me.ScrollBar1.Max
  7.   Me.ScrollBar1.smallChange = sChange
  8.  Else
  9.   Me.ScrollBar1.LargeChange = DispWidth * OverLap
  10.   Me.ScrollBar1.smallChange = sChange
  11.  End If
  12. End Sub
図6-8

6-4-1.ScrollBar1の設定の考え方

ScrollBar1は、ScrollBar2で決めた表示幅(表示データ数)で表示位置を移動していくスクロールバーです。
移動していくイメージを図6-9に表現してみます。グレー部分がデータ総行数を示し、一点鎖線の赤枠がグラフとして表示している範囲を示します。

ScrollBar1の設定値の考え方
図6-9

図6-4の①の部分ではScrollBar2の値は「データ総行数に固定」しています。固定している理由は「データが非常に少なく、拡大縮小も移動も不要」のためです。
ですのでScrollBar1側でも操作不要(=操作不可)ですので、図6-9の一番左側([表示幅=データ数])のように移動を出来なくします。

次に図6-9の真ん中([表示幅<データ数])のようなデータ数が表示幅より少しだけ大きい場合を考えます。
データ全体を見ることはできませんが、データの最初の部分のグラフと最後の部分のグラフではダブっているデータが多くなります。つまり、ちょっとだけ表示幅を動かせば最後のデータを表示することが出来ます。
この表示幅の位置は、図6-9で言うと「太い一点鎖線枠の左端位置」としており、これがScrollBar1のValue値(=表示グラフの開始データ位置)ということになります。
ScrollBar1の動かせる限界値がMax値ですので、ScrollBar1のMaxプロパティは「データ数(XmaxRow)-表示幅(DispWidth)」となります。なお、Min値は常にゼロとしています。

このMax値の考え方は、データ数が表示幅よりもずっと大きいような図6-9の一番右側の図([表示幅 ≪ データ数])でも同じです。
しかし、一つ隣の表示幅のグラフに移動するのに「LargeChange」を用いる時にはどうでしょう。ScrollBar2の設定の時と同様に、Max値ーMin値(ScrollBar1の場合にはMin値はゼロですので、Max値そのもの)よりLargeChange値が小さくないとスクロールボックスが非表示になってしまいます。
ですのでScrollBar1の時も、スクロールボックスが表示されるようにLargeChange値を調整する必要があります。

なお図6-9は、隣に移動するのに「1画面分動く(OverLap = 1 )」とした説明図にしていますが、今回のOverLap値は図4-1の6行目で「0.2」と宣言しています。つまり、ScrollBar1のレール部をクリックした時は「画面の20%が左右に移動」することにしています。
ですので今回のLargeChangeの規定値は、表示幅(DispWidth)x移動率(OverLap)となります。

6-4-2.ScrollBar1の設定 その1(Max値、Min値の設定)

129行目では、図6-9で示したようにMax値には「データ総行数(XmaxRow)―表示幅(DispWidth)」を代入します。
130行目ではMin値にゼロを代入し、データ先頭からグラフ表示されるようにしています。

なお、データ総行数が定数minWidh(今回は50に設定)より小さい場合は「ScrollBar2の表示幅=データ総行数」ですので、本プロシージャへの引数DispWidthにもデータ総行数(=XmaxRow)が渡されます。
つまり129行目の式は、XmaxRow ― XmaxRow となり、ゼロ値がMaxプロパティに設定されます。

6-4-3.ScrollBar1の設定 その2(LargeChange値、SmallChange値の設定)

データ総行数が充分大きい場合、ScrollBar1のレール部をクリックすると「画面の20%が左右に移動」するのですから、LargeChange値に設定する値は「DispWidth * OverLap」が入るはずです。
ですので132行目では、「Max値」と「DispWidth * OverLap」を比較し、Max値の方が小さい場合に133~134行目を実行します。逆にMax値の方が大きい場合は136~137行目を実行します。

まず先に「Max値が( DispWidth * OverLap )より大きい場合」は、136行目でLargeChangeプロパティに通常の値「DispWidth * OverLap」を設定します。
一方「Max値が( DispWidth * OverLap )より小さい場合」は、図6-7で示した様にスクロールバーのスクロールボックスを非表示にさせないために、LargeChangeプロパティにはMax値を設定します。

なおsmallChangeには、変数sChange(今回の設定値は1)を134行目・137行目で設定しています。

6-5.ScrollBar1の変更イベント

グラフX軸の表示位置移動用スクロールバー(ScrollBar1)を操作した時に動作するのが図6-10です。
ChangeイベントとScrollイベントは、スクロールバーのどの部分をどう操作するかで分かれますので図6-6を参照願います。
  1. '========== ⇩⑪ ScrollBar1の変更 ====================
  2. Private Sub ScrollBar1_Change()
  3.  Call ChartRefresh
  4.  Call LabelRefresh
  5. End Sub
  6. '========== ⇩⑫ ScrollBar1の移動中 ====================
  7. Private Sub ScrollBar1_Scroll()
  8.  Call LabelRefresh
  9. End Sub
図6-10

まず、スクロール矢印やレール部分、スクロールボックスを操作してマウスを離した時には「Changeイベント」が発生し、142~143行目を実行します。

142行目の「ChartRefresh」プロシージャは、各スクロールバーの値を使用してグラフのデータ範囲などを更新(=グラフ更新)する機能です。詳細は図6-13で説明します。
143行目の「LabelRefresh」プロシージャは、各スクロールバーの値(Value値)をダイアログのLabelに表示する機能です。詳細は図6-13で説明します。

この2つの機能は連動する機能ですので1つにまとめることもできます。しかし「スクロールボックスをマウスでつまみ、『どの辺りの値にしようか』と迷いながら動かす」のような操作をした時には、どのような動作を期待するでしょうか。
この時に、スクロールボックスの動き(Value値が1ずつ変化)に応じてグラフ更新がスムーズに動いてくれれば最高ですが、実際にはPCパワーが追いつきません。
ですので「スクロールボックスを操作している途中は、現在のスクロールボックスの値がどこに居るかだけでも分かる」ようにするため、グラフ更新とLabel表示の機能を分け、スクロールボックス操作の間はLabel表示のみを行うこととしました。これが147行目の「LabelRefresh」呼出しになります。

6-6.ScrollBar2の変更イベント

グラフ表示データ数変更用スクロールバー(ScrollBar2)を操作した時に動作するのが図6-11です。
ChangeイベントとScrollイベントは、スクロールバーのどの部分をどう操作するかで分かれますのでず6-6を参照願います。
  1. '========== ⇩⑬ ScrollBar2の変更 ====================
  2. Private Sub ScrollBar2_Change()
  3.  Call ScrollBar1Config(Me.ScrollBar2.Value)
  4.  Call ChartRefresh
  5.  Call LabelRefresh
  6. End Sub
  7. '========== ⇩⑭ ScrollBar2の移動中 ====================
  8. Private Sub ScrollBar2_Scroll()
  9.  Call LabelRefresh
  10. End Sub
図6-11

スクロール矢印やレール部分、スクロールボックスを操作してマウスを離した時には「Changeイベント」が発生し、151~153行目を実行します。

まず「グラフ表示データ数」は、ScrollBar1の設定値に影響を与えますので、ScrollBar2のValue値の変更を行った時点(Changeイベント)で「ScrollBar1Config」プロシージャを呼び出します。(151行目)
152行目では「ChartRefresh」プロシージャを呼び出し、グラフのデータ範囲などを更新します。
153行目では「LabelRefresh」プロシージャを呼び出し、ダイアログのLabel表示を更新します。

Scrollイベントの157行目は、ScrollBar1の理由と同じく「PCの処理時間を少なくし、グラフ表示に違和感を与えない」ため、スクロールボックスを操作している途中だけLabelを更新するようにしています。

6-7.ボタンクリックによるイベント

ダイアログ上には「グラフ種類変更(CommandButton1)」と「グラフシート削除・終了(CommandButton2)」の2種のボタンが配置してあります。各々のボタンを押した時に作動するのが図6-12です。
  1. '========== ⇩⑮ グラフ種類変更ボタン ====================
  2. Private Sub CommandButton1_Click()
  3.  CType = ChartChange
  4. End Sub
  5. '========== ⇩⑯ グラフ削除・終了ボタン ====================
  6. Private Sub CommandButton2_Click()
  7.  Call ChartDel
  8. End Sub
図6-12

CommandButton1をクリックすると、161行目で「ChartChange」Functionプロシージャ(標準モジュール:図4-14)を呼び出します。
「ChartChange」プロシージャ内では、グラフ変更の組み込みダイアログを表示し、その変更したグラフ種類を戻して来ます。
戻されたグラフ種類の値は、フォームモジュールレベルの変数CTypeに代入します。

CommandButton2をクリックすると、165行目で「ChartDel」プロシージャ(標準モジュール:図4-16)を呼び出します。
「ChartDel」プロシージャ内では、作業をしていたグラフシートを削除後、フォームを閉じます(Unload)。

6-8.グラフ・ダイアログ内ラベルの再表示

各スクロールバーを操作した時等に呼び出されるのが、図6-13になります。
168~170行目は、グラフのデータ範囲を入替え、グラフを更新する「ChartRefresh」プロシージャです。
172~175行目は、ダイアログ上のLabelを書き換える「LabelRefresh」プロシージャです。

フォームモジュール側では、対象のグラフシートを表すmyChart変数(標準モジュールレベルの変数)は可視できません。可視できる標準モジュール側で処理を行うため、標準モジュール側のプロシージャを呼び出しています。
  1. '========== ⇩⑰ グラフ再表示 ====================
  2. Private Sub ChartRefresh()
  3.  Call DrawChart(Me.ScrollBar1.Value, Me.ScrollBar2.Value, CType)
  4. End Sub
  5. '========== ⇩⑱ ダイアログ内ラベル再表示 ====================
  6. Private Sub LabelRefresh()
  7.  Me.Label1.Caption = (Me.ScrollBar1.Value + 1) & " ⇔ " & (Me.ScrollBar1.Value + Me.ScrollBar2.Value)
  8.  Me.Label2.Caption = Me.ScrollBar2.Value
  9. End Sub
図6-13

「ChartRefresh」では、「DrawChart」プロシージャ(標準モジュールの図4-7)を呼び出します。
渡す3つの引数は、以下の値となります。
 ・第一引数(StRow)  :ScrollBar1.Value   ・・・グラフX軸の開始データ位置
 ・第二引数(CountRow):ScrollBar2.Value   ・・・グラフX軸のデータ数(表示幅)
 ・第三引数(CType)  :CType        ・・・グラフ種類

「LabelRefresh」は、フォーム上のLabel1・Label2に、データの位置とデータ数を書き込みます。
173行目のLabel1は「データの〇〇から△△までが表示されている」ことを表すことにしましたので、グラフの始め側に「ScrollBar1のValue値」を、グラフの終わり側に「ScrollBar1とScrollBar2のValue値を足したもの」を表示させています。
なお、値をつなぐ記号として「⇔」を使用しています。本当は「~」を使いたいと思ったのですが、試してみると「上側に貼り付いたチルダ記号」の様になってしまうため諦めました。

174行目のLabel2は「グラフのデータ数」ですので、単純に「ScrollBar2のValue値」を書き込んでいます。

7.最後に

確認が取れなかったのですが、バージョン95とか2000辺りのExcelでは、グラフ化できるデータ行数が4000行であった気がします。ですので当時マクロを組んで(当時は、グラフ系列の「=SERIES(・・・・) 」の中身を直接変更していた気がします)、グラフのX軸を移動させていた記憶があります。
また、規則性があるような波形の場合には、トリガーを使って必要な区間の波形を切り出し、グラフ表示するようなものも作った覚えがあります。

なおExcelのグラフは非常に多機能で便利ですが、グラフの元となるデータに対して「目的の現象が再現できるようなサンプリングで取り込んでいるか」「現象とノイズは分解できるか」「ローパス・ハイパスを掛けすぎていないか」などをまず確認することが、大切なことだと思います。

グラフのX軸をスクロールバーで移動(it-039.xlsm)

セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。