2020/08/30

複数系列のデータを連続的にグラフ化




1.背景

ある挙動の要因を調べるために、その周辺の様々な挙動を同時に計測し比較する手法があります。
たとえば自動車の運転席の振動が大きい時、その原因を調べるために運転席の振動と一緒に「エンジンや車軸の振動値、またエンジン回転数や車速など」も同時に測り比較することによって、原因を突き止めるようなことです。

今回は、その様な複数のデータ系列があった場合、「グラフのX軸を固定しY軸を連続的に変更することで相関性を比較確認できる」ものを紹介します。
実際には固有振動数などが複雑にからむので「そんなに単純では無い!」と怒られそうですが、傾向を掴むくらいのレベルでしたら役に立つかもしれません。

2.概要

測定したデータは、通常は時系列的な形をしていると思います。今回は、例えば図2-1のようにB列に時刻データがあり、C~G列に5系列のデータが並んでいるものを想定しています。
なお、時間軸が無いデータでも問題ありませんが「データ列は全て繋がっている」ことが必要です。
複数系列のデータ
図2-1

データを比較するために、まず基準となるデータ列(X軸)を選びます。データシート上の「グラフ化」ボタンをクリックすると、図2-2のようにInputBoxが現れますので「基準列(グラフのX軸)の先頭セル」をクリックします。
基準データ列の選択
図2-2

すると新たにグラフシートが作成され「基準列(X軸)」と「他の列(Y軸)」の間の散布図が表示されます。(図2-3)
「他の列」は複数あります(図2-1では5列)ので、ダイアログのスクロールバーを移動することで比較対象列が移動します。
(スクロールバーの位置によっては「同じデータ列同士の比較」になり、グラフ上は45度の直線上に並びます。)
尚ダイアログ上には、グラフのX軸Y軸のデータ範囲がアドレス形式で表示されます。また両データの相関係数も表示されます。

測定値のグラフ表示
図2-3

なお、グラフ種類を変更する際にはダイアログの「G種類変更」ボタンをクリックすると、図2-4のようにExcel組み込みダイアログが現れ、グラフ種類の変更が可能です。
グラフ種類の変更
図2-4

終了する場合は、ダイアログの「G削除 終了」ボタンをクリックすると、グラフシートが削除されマクロ終了します。
もしグラフシートをそのままにしたいのであれば、ダイアログ右上の×印で終了して下さい。

3.プログラムの流れ

全体の流れは図3-1のようになっています。
プログラムは暫定的にシート上の「グラフ化」ボタンで起動させていますが、実際にはプログラムをアドイン等にして「データとは異なる場所から起動」させる必要があります。
アドインへの登録方法については、下記を参照願います。ファイル名が異なるだけで手順は全く同じです。
・「セルの罫線を矢印キーで引く
・「西暦・和暦対照表

プログラムの流れ
図3-1

初めに、ユーザー側にX軸(グラフの固定軸)の先頭セルを指示してもらい、そのセルを手掛かりにデータ範囲を把握します。
次に初期のY軸列を仮決めし、グラフシートを作成します。この時はグラフ内には表示するデータは入っていません。
その後、グラフ化する際のX軸Y軸のセル範囲を計算し、新規作成したグラフシートに表示するデータを指定します。この段階で初期のグラフは完成します。

グラフが完成した後でダイアログを表示させ、ユーザーがスクロールバーを移動すると、グラフの軸データを「変更後のX軸Y軸」に入れ替えることで、別の組み合わせのグラフを表示することが出来ます。
尚、図3-1には記載していませんが、グラフ種類も変更可能です。グラフ種類の値は共通変数として保持し、グラフ軸変更の際にはグラフ種類も共に変更指示を出しています。

4.標準モジュール

4-1.宣言部

標準モジュールの宣言部では図4-1のように、プロジェクト(標準モジュール+フォームモジュール)内で共通して使用するPublic変数1つと標準モジュール内で使用する変数6つを宣言しています。
  1. '========== ⇩① 宣言部 ====================
  2. Public myChart As Object    '作成したグラフオブジェクト
  3. Dim Xstart As Range      'X軸列の先頭セル
  4. Dim StartCol As Long      'データ域の先頭列位置
  5. Dim Xcol As Long        'X軸の列位置
  6. Dim EndCol As Long      'データ域の最終列位置
  7. Dim StartRow As Long     'データ域の先頭行位置
  8. Dim EndRow As Long      'データ域の最終行位置
図4-1

それぞれの変数は、図4-2の部位を指しています。
変数の意味
図4-2

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

今回のサンプルファイルのワークシート上の「グラフ化」ボタンをクリックすると呼び出されるプロシージャが図4-3です。
  1. '========== ⇩② メインプログラム ====================
  2. Sub MakeChart()
  3.  Dim AxisX As Range      'グラフのX軸のデータ範囲
  4.  Dim AxisY As Range      'グラフのY軸のデータ範囲
  5.  Dim CurrentCol As Long    'グラフのY軸のデータ列位置
  6.  On Error Resume Next
  7.   Set Xstart = Application.InputBox("X軸データの先頭セルを指定して下さい", Type:=8)
  8.   If Not Err.Number = 0 Then Exit Sub
  9.  On Error GoTo 0
  10.  With Xstart
  11.   StartCol = .CurrentRegion.Column
  12.   EndCol = StartCol + .CurrentRegion.Columns.Count - 1
  13.   Xcol = .Column
  14.   StartRow = .Row
  15.   EndRow = .End(xlDown).Row
  16.  End With
  17. ' //Y軸の初期値に、どの列を割り当てるかを決める
  18.  If StartCol = EndCol Then        'データが1列しか無い場合
  19.   CurrentCol = Xcol
  20.  Else                  'データが複数列ある場合
  21.   If StartCol = Xcol Then        'データの先頭列がX軸の場合
  22.    CurrentCol = StartCol + 1
  23.   Else                  'X軸がデータ先頭列以外の場合
  24.    CurrentCol = StartCol
  25.   End If
  26.  End If
  27.  Set myChart = Charts.Add
  28.  Call myAxis(AxisX, AxisY, CurrentCol)
  29.  Call DrawChart(AxisX, AxisY, xlXYScatter)
  30.  Call UserForm1.Start(StartCol, EndCol, CurrentCol, xlXYScatter)
  31. End Sub
図4-3

4-2-1.変数宣言

11~13行目は、MakeChartプロシージャ内で使用する変数です。
「AxisX」はX軸のデータ範囲です。StartRow行からでは無く、データの1つ上のタイトル(と思われるセル)を含んでいます。
「AxisY」はY軸のデータ範囲で、X軸と同様にタイトルを含んでいます。
「CurrentCol」は「AxisYを作るためのデータ」です。グラフを新規作成した段階では、X軸の場所により最適な位置に計算で求めています(29~37行目)が、それ以降はダイアログのスクロールバーの値に同期します。

4-2-2.X軸の先頭セルの位置取得

16行目は、InputBoxメソッドを使っています。
InputBox関数と異なりInputBoxメソッドは、「引数Typeに8を指定」することで「セル範囲」を取得できます。この機能を利用して「X軸の先頭データの位置」を取得します。

InputBoxにセル範囲を入れれば(=セルをクリックすれば)、InputBoxメソッドはRange型の値を戻します。その戻り値を変数Xstart(3行目でRange型として宣言)で受け取りますが、もしキャンセルボタンをクリックしたり、ダイアログ右上の×印をクリックしたりすると「False」を戻します。
その際はRange型の変数では受けられずにエラーが発生するため、15行目で「On Error Resume Next」を設けて「エラーが発生しても次に進む」ようにし、17行目でエラー番号を調べてゼロで無い(=エラーが出ている)時は、マクロ終了しています。
18行目の「On Error GoTo 0」では「Resume Next」機能をキャンセルさせ、16行目以外でエラーが発生した場合にはキチンとエラー終了させるようにしています。

尚、このInputBoxメソッド及びエラー処理については、「サンプリング周期が異なるデータの補間法」も参照下さい。
また、ユーザーが「データで無いセルを選択」すると、17行目では引っ掛からずに別な場所でエラーが発生します。ユーザーのミスをどこまでフォローするかは、ユーザーの種類(仲間なのか、違う部署なのか、お客様なのか 等)で異なると思いますので、それに合わせて緩くするのかガチガチにするのかを決めてください。但し取扱説明書に記載するくらいは必須と思います。

4-2-3.データ範囲の位置をモジュール変数に代入

20~26行目では、図4-1で宣言したモジュール内変数に値を代入しています。
ユーザーが指定したX軸データの先頭セル(=Xstart)を元にして、図4-2の各値を計算・代入しています。

4-2-4.Y軸の初期値に、どの列を割り当てるか計算

29~37行目は、グラフを新規作成した初期段階でのY軸をどの列にするかを決め、その列位置をCurrentColに代入します。
Y軸は「X軸と重ならない」ように選択しますが、「データ列が1列しか無い」場合には同じ列同士にしています。

まず29行目では、データ列が1列しか無い(=StartColとEndColが一緒)場合には、Y軸(=CurrentCol)にX軸の列位置(=Xcol)を代入します。
フルイに掛けられた31行目以降は、データ列が複数存在することになります。
そこで32行目ではX軸(=Xcol)としてデータ範囲の先頭列(=StartCol)を選択したかを調べます。その場合には、先頭列の隣の列(=StartCol + 1 )をY軸にします。
X軸が先頭列では無い場合は、先頭列をY軸にします。

4-2-5.グラフシート作成

39~41行目では、新たなグラフシートを作り、散布図を作成しています。
39行目の「Charts.Add」でグラフシートを作ります。
さて、グラフシートを作成するには「Charts.Add」と共に「Charts.Add2」というのがあります。Add2はExcel2016で追加されたメソッドの様で、「株式型や地理型のサブフィールドを並べ替えの対象に指定できる」ようです。
ちなみに「Charts.Add」と「Charts.Add2」とで作成したグラフの違いを図4-4に示します。比較してみると枠線の有無や目盛線の太さ等が異なる様ですが、「どうしてもAdd2」という人以外は旧バージョンでも使用できるAddを使用した方が良いと思います。

AddとAdd2の実行結果の比較
図4-4

40行目では、X軸・Y軸のセル範囲(Range型)を計算しています。
詳細は図4-6で説明しますが、myAxisプロシージャにはY軸の列位置「CurrentCol」を渡し、戻り値相当としてX軸セル範囲「AxisX」, Y軸セル範囲「AxisY」を得ます。

41行目では、40行目で得た「AxisX」「AxisY」、および「xlXYScatter(散布図を表す定数)」をDrawChartプロシージャ(図4-7)に与え、散布図を完成させています。
尚グラフには図4-5のように多くの種類がありますが、とりあえず初期の種類としては散布図を選びました。

グラフの種類一覧(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-5

4-2-6.ダイアログの呼出し

43行目では、UserForm1のStartプロシージャ(図6-2)を呼び出し、ダイアログを起動させています。
引数として「StartCol」「EndCol」「CurrentCol」「xlXYScatter(初期のグラフ種)」の4つを渡していますが、初期グラフ種を除いた3つは標準モジュール側で使用しているモジュール変数です。これと同じ変数値をフォーム側のモジュール変数として使用するために引数で渡しています。

このような標準モジュール側とフォームモジュール側とを結ぶ引数渡しは、図4-1の変数宣言時に「Dimでは無くPublicとして変数宣言」をすれば必要無くなるのですが、変数の可視範囲を極力狭くするために、このような手法を取りました。
今回の機能だけであればPublic変数を使用しても悪い影響は無さそうですが、もし別機能を追加するような場合には注意しなければならない項目が増える事になってしまいます。

4-3.X軸Y軸範囲の計算

図4-3の40行目、図6-5の75行目から呼び出されるmyAxisプロシージャが図4-6です。
引数として「AxisX」「AxisY」「CurrentCol」の3つが渡されている様に見えますが、実際に受け取っているのは「CurrentCol」のみで、プロシージャ内での計算結果として「AxisX」「AxisY」を戻しています。
  1. '========== ⇩③ X軸Y軸範囲の計算 ====================
  2. Sub myAxis(AxisX As Range, AxisY As Range, CurrentCol As Long)
  3.  With Xstart.Parent
  4.   Set AxisX = Range(.Cells(StartRow - 1, Xcol), .Cells(EndRow, Xcol))
  5.   Set AxisY = Range(.Cells(StartRow - 1, CurrentCol), .Cells(EndRow, CurrentCol))
  6.  End With
  7. End Sub
図4-6

47行目の「Xstart.Parent」は、ユーザーが指定したX軸データの先頭セル(=Xstart)の「親(=Parent)」という意味ですので、「データが存在するワークシート(サンプルファイルではSheet1)」という意味になります。
このワークシート上のセル範囲を48~49行目で計算します。

48行目は、ユーザーが指定したX軸のセル範囲です。この式の中で使われている変数(StartRow、EndRow、Xcol)は、マクロが動いている間は不変の値ですので、毎回計算するのも少し無駄な気がします。しかしX軸とY軸を異なる場所から引いてくると分かり難くなりますので、このような形にしました。

49行目は、引数「CurrentCol」で指定された列位置のY軸セル範囲です。このAxisY値は、毎回列位置が変更になります。

X軸・Y軸ともに先頭行を「StartRow - 1」としているのは、データの1つ上の行に「タイトル行があると仮定」して1つ上の行までをセル範囲としています。タイトル行までをセル範囲に含めているために、グラフ上部にタイトルが表示されることになります。
もしタイトル行が無い場合でもエラーは発生しませんが、グラフ上部のタイトル部は空欄になります。そのため、ダイアログ内にX軸Y軸のセル範囲のアドレスを表示することにしました。

計算した「AxisX」「AxisY」の値は、引数の形で呼び出し元に戻されますので、呼び出し元では「AxisX」「AxisY」を戻り値と同じように、その後の計算に使っていきます。

4-4.グラフ描画

図4-3の41行目、図6-5の76行目から呼び出されるDrawChartプロシージャが図4-7です。
引数として「AxisX」「AxisY」「CType」の3つを受け取り、グラフ表示に必要なデータを書き込みます。
  1. '========== ⇩④ グラフ描画 ====================
  2. Sub DrawChart(AxisX As Range, AxisY As Range, CType As XlChartType)
  3.  With myChart
  4.   .ChartType = CType
  5.   .SetSourceData Range(AxisX.Address(External:=True) & ", " & AxisY.Address(External:=True))
  6.   .ChartType = CType
  7.  End With
  8. End Sub
図4-7

54行目は、既に作成済みのグラフシート(=Public変数のmyChart)に対して処理を行うため「With myChart」とします。
55・57行目は「myChart.ChartType = CType」と同一コードです。これはミスでは無く必要なコードです。
実は今まで「グラフのデータ範囲とグラフ種類の設定は、どの順番でも同じ」と考えていたのですが、以下の様な問題点が分かり小細工が必要となりました。

4-4-1.初期グラフ作成時の問題点

まず初期グラフを作成する場合ですが、ユーザーが時刻データを選択した場合に、どのようにグラフが生成されていくのかを調べてみました。(図4-8)
まず、図4-3の39行目で「Chrts.Add」をした段階では、図4-8の一番左側の様に「全データを使った棒グラフ」になります。
ChartTypeとSetSourceDataの設定順番で異なるグラフの比較1
図4-8

次にグラフのデータ指定とグラフ種を設定する必要がありますが、その順番の組合せとしては「SetSouceData → ChartType」と「ChartType → SetSouceData」の2通りがあります。
実行した結果ですが、「SetSouceData → ChartType」の場合は図4-8の上側のように、「時刻データとY軸データの2系列のグラフ」になる現象が発生しました。ちなみに「ChartType → SetSouceData」の場合は図4-8の下側のように「正常な散布図」になりました。
つまり「時間軸をX軸に設定」した場合には「ChartType → SetSouceData」の順で設定する必要があることが分かります。

なお、この「意図しないグラフ」になるのは「初回表示時」だけで、表示後にダイアログ上のスクロールバーを移動させて再表示させると正常表示になりますし、またX軸として時刻データ以外を選択した時も初回から正常なグラフになります。

4-4-2.グラフ移動後のグラフ種類変更時の問題点

一方で次のような場面では逆の現象が発生しました。
初期の散布図が表示された後、「ダイアログのスクロールバーでY軸を移動した先でグラフ種類を変更し、スクロールバーでY軸を元に戻す」という場合です。
図4-9のように、「ChartType → SetSouceData」の順では、グラフの「点も線も表示されない」状態になるのです。逆の「SetSouceData → ChartType」の順だと正常な表示となります。
ChartTypeとSetSourceDataの設定順番で異なるグラフの比較2
図4-9

尚この現象も、スクロールバーを行ったり来たりさせて複数回表示させると、やはり正常なグラフになります。

4-4-3.グラフのデータ指定とグラフ種を設定の順序の解決策

データ範囲とグラフ種の設定順序によりグラフ表示に問題が出る原因については、よく分かりません。但し、グラフ種変更後に、一度も表示させていないY軸グラフに移動する際には正常に表示することから、「グラフのデータ範囲とグラフ種」をExcelがどこかにキャッシュとして記憶しているのでは、と想像しています。

不具合の現象は1回限りの現象です。しかし、1回でも想定外のグラフになるのは「エラー」ですので、グラフ種を設定するコード「myChart.ChartType = CType」を、「myChart.SetSourceData」の前後である55行目と57行目の両方に置いて、想定外グラフにならないようにしています。
つまり、55行目は「新規に時系列グラフを作成した時」用、57行目は「グラフ種類変更時」用です。

なお、ChrtTypeを2つ置くことによるデメリット(不具合)は今のところ見つかっていませんが、複雑なグラフ設定をする場合には注意しておいた方が良いかもしれません。

また、第三引数を「CType As XlChartType」と宣言してありますが、「CType As Long」と宣言しても同じです。今回のマクロ内では3箇所で「As XlChartType」を宣言していますが、「As Long とゴッチャ」にしても問題なく動くようです。しかし、どちらかに統一しておいた方が良いとは思います。

4-4-4.グラフのデータ指定の方法

グラフ種設定に挟まれている56行目の「myChart.SetSourceData」の引数について説明します。
.SetSourceDataの引数にはグラフの元データのセル範囲を指定します。例えば今回で言えばX軸のセル範囲は「AxisX」、Y軸のセル範囲は「AxisY」ですので、「Union(AxisX,AxisY)」を引数にすれば良いはずです。
たしかに、X軸とY軸が異なる列の場合にはそれで問題ありません。しかしX軸とY軸が同じ場合にはどうでしょうか。

X軸とY軸が同じ場合には、「Union(AxisX,AxisY)」の計算結果は「AxisX」になります(同じセル範囲ですので当然です)。
ではこの場合にグラフの表示としてはどうなるでしょうか。やってみるとX軸(横軸)にはデータの個数が並び、Y軸に値が並ぶはずです。しかし「X軸とY軸が同じ散布図」としてユーザーが期待するのは、図4-10の左右どちらでしょうか。
X軸とY軸が同じセル範囲の時の散布図
図4-10

私であれば、迷わず「右の45度の直線に並んだグラフ」を選びます。今回ダイアログ中には相関係数を表示していますが、その値が「1.000」になっていますので、尚更です。

そこで、図4-10の右の様なグラフを作るにはどうするか です。通常2つの列をセル範囲(Area)として引数にする場合は、例えば「Range("A1:A10,C1:C10")」のように記載します。
なお間違えて「Range("A1:A10","C1:C10")」とするとA列からC列までの連続範囲になってしまいますので注意が必要です。

X軸Y軸のセル範囲は「AxisX」「AxisY」ですので、各々のセル範囲を文字列にして「,(カンマ)」でつなげば良いのですが、
「Range(AxisX.Address & ", " & AxisY.Address)」としてしまうと、今回の場合はエラーが発生します。
なぜかと言うと「.Address」で得られる文字列「(例)"A1:A10"」は「アクティブなワークシートでの "A1:A10" セル範囲」を指すことになりますが、今回実際にアクティブになっているのは「グラフシート」だからです。

そのため「シート名までを含めたアドレス」にする必要があるため、「.Address(External:=True)」とAddress にExternalパラメータを指定することで、「ワークブック、ワークシート名を含む外部参照として」セル範囲文字列を得る必要があります。
今回はブック名までは不要なのですが、残念ながらシート名だけというパラメータはありません。

この方法を「AxisX」「AxisY」の両方に使い、得られた文字列を「,(カンマ)」でつないで、以下の様な形にします。
 Range(AxisX.Address(External:=True) & ", " & AxisY.Address(External:=True)) 
これを56行目の「.SetSourceData」の引数としています。

5.フォーム外観

Y軸の切り替え等を行うためのフォームを図5-1のように作成します。
フォーム上にコントロールを配置
図5-1

ボタン表面、およびLabelの「X軸」「Y軸」「相関」は、あらかじめ文字を入れておきます。
データの行数・列数が非常に多い場合はデータのアドレスが長くなりますので、Label1~2は長めにとってあります。

6.フォームモジュール

6-1.変数宣言部

フォームモジュールの先頭に、モジュール変数(UserForm1内での共通変数)を図6-1のように宣言します。
  1. '========== ⇩⑤ フォーム内の変数宣言部 ====================
  2. Dim AxisX As Range       'グラフのX軸列範囲
  3. Dim AxisY As Range       'グラフのY軸範囲
  4. Dim CType As XlChartType    'グラフの種類
図6-1

「AxisX」「AxisY」は、それぞれX軸・Y軸のデータ範囲で、グラフのセル範囲として設定することを想定して「タイトル行を含む」範囲としています。また「CType」はグラフのグラフの種類を表します。

6-2.フォーム起動部

図4-3の43行目から呼び出されるプロシージャが、図6-2のStartプロシージャです。引数として「StartCol」「EndCol」「CurrentCol」「CT(グラフ種類の値)」を受け取ります。
  1. '========== ⇩⑥ フォーム起動部 ====================
  2. Public Sub Start(StartCol As Long, EndCol As Long, CurrentCol As Long, CT As XlChartType)
  3.  CType = CT
  4.  Me.ScrollBar1.Max = EndCol
  5.  Me.ScrollBar1.Value = CurrentCol
  6.  Me.ScrollBar1.Min = StartCol
  7.  Me.Show 0
  8. End Sub
図6-2

6-2-1.スクロールバーの初期設定の順序

67~69行目を先に説明します。
Startプロシージャが受け取った引数の内、「StartCol」「EndCol」「CurrentCol」は、グラフのY軸の移動のための「スクロールバーのMax・Min・Value値」の設定に使用します。
スクロールバーのMax値にはデータ最終列であるEndCol値を、Value値には初期散布図のY軸であるCurrentCol値を、Min値には、データ先頭列であるStartCol値を順に代入します

このスクロールバーの設定順序「Max → Value → Min」には意味があります。図6-3は、Max,Min,Valueの設定順序を図にしたものです。
スクロールバーのMax,Min,Value値の設定順序
図6-3

スクロールバーのValue値が変化すると、Changeイベントが発生します。スクロールバーを配置するのであれば、当然ながらその操作をプログラムに結び付けるべくChangeイベントプロシージャを使用しているはずです。
そのChangeイベントは、フォームの起動中(今回で言えば、Startプロシージャを動かしている時も)にも発生しますので、スクロールバー設定中にValue値が動く時にも発生し、イベントプロシージャを呼び出してしまいます。
そして図6-3で分かる通り「設定する順序により、Value値が変わる回数(=イベントプロシージャを呼び出す回数)が異なる」のです。

Changeイベントプロシージャがどんな役割を持っているかはそれぞれですが、今回の場合は「グラフのデータ範囲変更とグラフ種設定」です。何回も「グラフのデータ範囲変更と・・・」を実行するとそれだけ処理に時間が掛かりますし、設定値が毎回異なった場合にはグラフがチラチラする事にもつながります。
ですので、Value値が変わる回数がもっとも少ない「Max → Value → Min」の順番で設定しています。

6-2-2.ChartType値をモジュール変数CTypeに代入するタイミング

Value値が変わる回数が少ないと言っても、最低1回は発生(今回の場合、Value値=Y軸範囲の列位置 のため)します。そのValue値変更が発生(=Changeイベント発生)するのは68行目を実行した時です。

Changeイベントが発生すると、グラフの「データ範囲変更」及び「グラフ種設定」が行われます。「データ範囲変更」の方はスクロールバーのValue値を元に計算されますので問題無いのですが、「グラフ種設定」の方はモジュール変数CTypeの値が必要になります。
つまり、68行目より前に「モジュール変数CTypeへの値代入」が必要なので、66行目で「CType = CT」の設定をしています。(68行目以降に「CType = CT」を置くと、グラフ設定時にCType=0となるのでエラーが発生します。)

6-2-3.ダイアログの表示

起動前の準備が出来たら、71行目で UserForm1(Me でフォームを表す事が可能です)を表示します。引数にゼロ(vbModeless)を指定していますので、Excelのシート部分の操作も可能です。引数に1(既定値なので、引数を指定しないと1を指定した事になる)を指定すると、フォーム以外の操作は出来なくなります。

6-2-4.その他

スクロールバーの設定を行う際 Value値が変化しなければ、不要なChangeイベントが発生しないと説明しました。Value値をゼロにした分 Min値とMax値を調整し、実際にスクロールバーを動かした際もValue値を±すれば可能です。
しかし、Changeイベントが発生しないままフォームが起動するとデメリットもあります。

今回フォーム上のLabel1~3にデータ(X軸Y軸のアドレス、相関係数)を書き込んでいます(LabelMakeプロシージャ(図6-11))が、この操作はChangeイベントプロシージャから呼び出しています。
起動時にChangeイベントが発生しないとなると、起動直後のフォームのLabelにはデータが書き込まれていない事になるので、別途LabelMakeプロシージャを呼び出す必要が出てきます。
その場合には例えば69行目の後ろに、以下の2行を追加する等の対応が必要となります。
  • Call myAxis(AxisX, AxisY, ScrollBar1.Value)    'スクロールバーのValue値を渡し、AxisX, AxisY値を得る
  • Call LabelMake(AxisX, AxisY)            'AxisX, AxisY値からLabelデータを計算し表示する
図6-4

6-3.スクロールバー変更時

グラフのY軸を変更しようとして、スクロールバーを動かした時に発生するChangeイベントが図6-5になります。
  1. '========== ⇩⑦ スクロールバー変更時 ====================
  2. Private Sub ScrollBar1_Change()
  3.  Call myAxis(AxisX, AxisY, ScrollBar1.Value)
  4.  Call DrawChart(AxisX, AxisY, CType)
  5.  Call LabelMake(AxisX, AxisY)
  6. End Sub
図6-5

75行目で、変更されたスクロールバーのValue値をmyAxis(図4-6)に渡し、X軸Y軸のセル範囲である AxisX, AxisY を得ます。
76行目で、その AxisX, AxisY と、グラフ種類値のCTypeをDrawChart(図4-7)に渡し、グラフのX軸Y軸とグラフ種を変更します。
77行目で、LabelMake(図6-11)に AxisX, AxisY を渡し、ダイアログ上のLabel1~3に表示グラフのアドレスと相関係数を表示させます。

以上の動作はChangeイベントプロシージャに書かれているので、スクロールバーの「スクロールボックス(図6-6参照)」をクリックして動かしただけでは反応しません。
スクロールバーの部位名
図6-6

それは図6-7 のように、スクロールバーをどのように操作させるかで、発生するイベントが異なるからです。
もし、スクロールボックスを移動させただけでもグラフのX軸Y軸が変わるようにしたいのであれば、Scrollイベントプロシージャを作成し、Changeイベントプロシージャと同様のコードを記入する必要があります。

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

6-4.グラフ種類変更ボタン

ダイアログ内の「G種類変更」ボタンをクリックすることで動作するプロシージャが図6-8です。
  1. '========== ⇩⑧ グラフ種類変更ボタン ====================
  2. Private Sub CommandButton1_Click()
  3.  myChart.Activate
  4.  Application.Dialogs(xlDialogChartType).Show
  5.  CType = myChart.ChartType
  6. End Sub
図6-8

81行目は、グラフ種を変更する対象の「グラフシート上のグラフ」をアクティブにしています。
マクロ操作のみの場合にはグラフはアクティブな状態を続けているのですが、今回UserForm1はモードレスで表示させている(図6-2の71行目)ので、ユーザーがシート側を操作することが可能です。
もしグラフがアクティブになっていない状態で82行目のコードを実行すると、グラフ種変更ダイアログが表示されません。特にエラーも出ないようですので、ユーザーにしてみれば「グラフ種変更ボタンをクリックしたのに、反応しない」ことになります。

82行目は、Excelの組み込みダイアログを表示させています。非常に多くのダイアログがあり、「グラフ種変更」だけでも、「xlDialogMainChart」「xlDialogMainChartType」「xlDialogFormatCharttype」「xlDialogChartType」の4種の定数が該当します。(違いは良く分かりません。見た目は、どれも同じダイアログの様です)
今回は一番無難そうな「xlDialogChartType」を使用しています。

83行目は、変更したグラフ種類の値をモジュール変数CTypeに代入しています。
なお、グラフ種を変更せずにキャンセルボタンをクリックしても83行目は実行され、CType値は更新されます。

6-5.グラフ削除・終了ボタン

ダイアログ内の「G削除・終了」ボタンをクリックすることで動作するプロシージャが図6-9です。
  1. '========== ⇩⑨ グラフ削除・終了ボタン ====================
  2. Private Sub CommandButton2_Click()
  3.  Application.DisplayAlerts = False
  4.   myChart.Delete
  5.  Application.DisplayAlerts = True
  6.  Me.Hide
  7. End Sub
図6-9

88行目はグラフシートを削除してます。削除の対象は「図4-3の39行目で作成したグラフシート」ですので、ダイアログ右上の×印で終了した時に残されたグラフシートはそのままです。
普通に削除しようとすると、図6-10の様なアラートが発生し、OKボタンをクリックしないと削除できません。
グラフシートを削除しようとした時に発生するアラート
図6-10

ですので、そのアラートを出さない為に、87行目で「Application.DisplayAlerts = False」とアラートを非表示にしてからDeleteを実行しています。
削除後は「Application.DisplayAlerts = True」でアラート表示を元の状態に戻しています。

グラフシートを削除後、90行目でダイアログを消しています。「Me.Hide」の代わりに「Unload Me」でも問題ありません。

6-6.Labelへのデータ書込み

図6-5の77行目から呼び出される LabelMakeプロシージャが図6-11です。引数として、X軸Y軸のセル範囲を受け取ります。
  1. '========== ⇩⑩ Label1~3へのデータ書込み ====================
  2. Sub LabelMake(AxisX As Range, AxisY As Range)
  3.  Me.Label1.Caption = AxisX.Address
  4.  Me.Label2.Caption = AxisY.Address
  5.  Me.Label3.Caption = Format(Application.WorksheetFunction.Correl (AxisX, AxisY), "0.000")
  6. End Sub
図6-11

94行目でX軸のアドレスを、95行目でY軸のアドレスをダイアログ上のLabelに書き込んでいます。
今回は「AxisX.Address」等としていますので、書き込まれるアドレスは「$B$2:$B$22」になりますが、これを
「AxisX.Address(External:=True)」と変更すると「'[it-038.xlsm]Sheet1'!$B$2:$B$22」とブック・シート名も共に表示されます。

96行目は、ワークシート関数の「Correl」を使って相関係数を求め、桁数を揃えてLabel3に書き込んでいます。
Excelの相関係数を求める関数にはCorrelの他に「Pearson」がありますが、Microsoftのページを調べてみると内部式は図6-12の如く同じですので、得られる係数ももちろん一緒(戻り値はDouble型)です。
Excelの相関係数を求める関数の内部式
図6-12

また、桁数揃えにはFormat関数を使い、今回は小数点3桁で表示をしています。この桁数に理由はありませんが、本来はデータの個数やデータが持っている桁数などで有効桁数を決めるべきだと考えます。

7.最後に

データを分析する際に「各系列毎にグラフ化しシート一面にグラフを並べて見比べる」ことをした方も多いと思います。私もその一人で、小さなグラフを並べるマクロを作ったこともあります。
色々な方法があって当然ですが、今回のようにY軸を送っていく方法も系列が多い場合には有効な気がしますし、発展させてX軸Y軸両方を動かせるようなものとか、全系列の相関係数を計算して高い方から表示する等、グラフには工夫のしどころが多いと思います。

複数系列のデータを連続的にグラフ化(it-038.xlsm)

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