Excel VBA を使ってグラフを描く

素人、グラフに挑戦する

ExcelVBAでグラフを描く

実は、今までほとんどExcelでグラフを使ったことがなかったのである。

まったくグラフを使うことがなかったわけではないが、ずーっと前に誰かが作ったグラフをちょこちょこっといじくる程度で乗りきっていたのである。

ところが、なぜか職場では Excelができる人認定されている私は、

Excelができる=グラフもバリバリ

という魔の方程式のせいで、

アンタ、Excel得意やからグラフを用いたデータ分析なんかもチョチョイのチョイやろ?

と、やけに軽々しく

データ分析&見栄え良く加工する業務

を割り当てられたのであった(´・ω・`)

グラフの種類を概観するマクロ

ずーっと前に、『Excel VBAでデータ分析』(川上恭子著)という本を購入していたのを思い出して引っ張り出してみた。

f:id:akashi_keirin:20180121153659j:plain

その本によると、

118ページ

新しくグラフシートを追加して、そのシートにグラフだけを表示します。

ソースコード
Sub グラフシートに作成()
'グラフシートにマーカー付折れ線グラフを作成'
    With Charts.Add
        .ChartType = xlLineMarkers
        .SetSourceData Source:=Sheets("Sheet1").Range("A3:G8")
    End With
End Sub

とあった。

ぱっと見た感じ、単にグラフを描くだけなら、

  1. ChartsコレクションにAddしたら、(たぶん)新しいChartオブジェクトが得られ、
  2. 1.で得られたChartオブジェクトのChartTypeプロパティにグラフの種類をセットして、
  3. SetDataSourceメソッドでグラフ化する元データを指定する

という流れだけっぽい。

とりあえずこれだけなら素人の私にもできそうだ。

前掲書の119ページには、

グラフの種類

ChartTypeプロパティに設定する定数を以下のように指定すると、グラフの種類を変更できます。また定数には値を指定することもできます。

ChartTypeプロパティ
グラフ 定数
集合縦棒 xlColumnClustered 51
積み上げ縦棒 xlColumnStacked 52
100%積み上げ縦棒 xlColumnStacked100 53
3-D集合縦棒 xl3DColumnClustered 54
3-D積み上げ縦棒 xl3DColumnStacked 55
3-D100%積み上げ縦棒 xl3DColumnStacked100 56
3-D縦棒 xl3DColumn -4100
集合横棒 xlBarClustered 57
3-D集合横棒 xl3DBarClustered 60
折れ線 xlLine 4
マーカー付き折れ線 xlLineMarkers 65
xlPie 5
補助円グラフ付き円 xlPieOfPie 68
補助縦棒グラフ付き円 xlBarOfPie 71
3-D円 xl3 DPie -4102
ドーナツ xlDoughnut -4120
xlArea 1
積み上げ面 xlAreaStacked 76
3-D面 xl3DArea -4098
散布図 xlXYScatter -4169
バブル xlBubble 15
株価チャート(高値-安値-終値 xlStockHLC 88
レーダー xlRadar -4151
マーカー付きレーダー xlRadarMarkers 81

とあり、ChartTypeプロパティに渡す値を変えれば、同じデータからいろんなグラフが作れそうだ。

ちなみに、オブジェクトブラウザーで見てみると、

f:id:akashi_keirin:20180121152423j:plain

これらの定数は、「XLChartType列挙体」のメンバのようだ。【→参考

とりあえずやってみる

まずは、単純に、引数としてグラフの種類を表す定数と、グラフ化する元データの範囲を与えたら、新しいシートにサクッとグラフを作ってくれるマクロを作ってみる。

リスト1 標準モジュール
Public Function createGraph(ByVal typeOfGraph As String, _
                            ByVal sourceRange As Range) As Boolean
  If getGraphType(typeOfGraph) = 0 Then    '……(*)'
    createGraph = False
    Exit Function
  End If  
  With Charts.Add
    .ChartType = getGraphType(typeOfGraph)    '……(*)'
    .SetSourceData Source:=sourceRange
  End With
  createGraph = True
End Function

引数typeOfGraphがString型であること、あと(*)の

getGraphType(typeOfGraph)

がナゾだと思う。getGraphTypeというのは自作Functionなんだから仕方がない。

あと、なんでSubじゃなくてFunctionにしているのか、についても後述する。

引数でグラフの種類と元データの範囲を受け取って、それをもとにグラフを新規作成する、というだけのマクロ。もちろん、単独でこのプロシージャを呼び出しても動かない。次のgetGraphTypeが必要になる。

スト2 標準モジュール
Private Function getGraphType(ByVal typeOfGraph As String) As XlChartType
  Select Case typeOfGraph
    Case "集合縦棒"
      getGraphType = xlColumnClustered
    Case "積み上げ縦棒"
      getGraphType = xlColumnStacked
    Case "100%積み上げ縦棒"
      getGraphType = xlColumnStacked100
    Case "3-D集合縦棒"
      getGraphType = xl3DColumnClustered
    Case "3-D積み上げ縦棒"
      getGraphType = xl3DColumnStacked
    Case "3-D100%積み上げ縦棒"
      getGraphType = xl3DColumnStacked100
    Case "3-D縦棒"
      getGraphType = xl3DColumn
    Case "集合横棒"
      getGraphType = xlBarClustered
    Case "3-D集合横棒"
      getGraphType = xl3DBarClustered
    Case "折れ線"
      getGraphType = xlLine
    Case "マーカー付き折れ線"
      getGraphType = xlLineMarkers
    Case "円"
      getGraphType = xlPie
    Case "補助円グラフ付き円"
      getGraphType = xlPieOfPie
    Case "補助縦棒グラフ付き円"
      getGraphType = xlBarOfPie
    Case "3-D円"
      getGraphType = xl3DPie
    Case "ドーナツ"
      getGraphType = xlDoughnut
    Case "面"
      getGraphType = xlArea
    Case "積み上げ面"
      getGraphType = xlAreaStacked
    Case "3-D面"
      getGraphType = xl3DArea
    Case "散布図"
      getGraphType = xlXYScatter
    Case "バブル"
      getGraphType = xlBubble
    Case "株価チャート(高値-安値-終値)"
      getGraphType = xlStockHLC
    Case "レーダー"
      getGraphType = xlRadar
    Case "塗りつぶしレーダー"
      getGraphType = xlRadarFilled
    Case "マーカー付きレーダー"
      getGraphType = xlRadarMarkers
    Case Else    '……(*)'
      Call makeUserSick("すまん、そのグラフの種類には対応しとらんわwww")
      getGraphType = 0
  End Select
End Function

すっげータテ長ですまん。見たら分かると思うけど、グラフの種類名を引数として渡したら、その名前に応じた定数を返すようにした。

データの入力規則のリストに名前を登録しといたら、それを選ぶだけで良いようにしたのだ。

一見、コードを書くのが死ぬほどメンドクサそうに見えると思うけれど、返り値の型を「XLChartType型」にしているので、それほどでもなかった。

ただ、XLChartTypeのメンバ全てに対応するのはちょっと数が多すぎて嫌になるので、基本的には、前掲書の119ページの表に載っていたものだけに対応した(追加したのは「マーカー付きレーダー」だけです)。

その関係で、選から漏れたグラフの種類を指定された場合には、ユーザーを軽く煽ってから、「0」を返すことにした。

要するに、対応していないグラフの種類とか、間違った名前を指定されたら、リスト1

If typeOfGraph = 0 Then createGraph = False: Exit Function

の条件式がTrueになるので、返り値Falseを持って呼び出し元へ帰る、ということになる。

グラフを描いてみる

ワークシートを、

f:id:akashi_keirin:20180121152434j:plain

こんなふうに作っておく。

A2セルには、データの入力規則で、特定のグラフの種類名しか入力できないように指定しておく。

んで、C1:H6にグラフの元データを準備した。

いちいちVBEから呼び出すのもメンドウなので、呼び出し用のコマンドボタンも置いておく。

エントリポイントのコードは次の通り。

リスト3 標準モジュール
Public Sub testCreateGraph()
  Dim Sh As Worksheet
  Set Sh = ThisWorkbook.Worksheets("Main")
  With Sh
    If Not createGraph(typeOfGraph:=.Range("A2").Value, _
                       sourceRange:=.Range("C1:H6")) Then    '……(*)'
      Call makeUserSick("すまん、グラフ描けんかったわwww")
    End If
  End With
End Sub

グラフの種類の指定がおかしかったら、リスト1の先頭で撥ねられて、Falseを持って帰ってくるので、(*)のところで

Call makeUserSick("すまん、グラフ描けんかったわwww")

が実行される。

グラフの種類の指定が正しければ、createGraphの中身が実行されてグラフが追加されることになる。

実行結果

f:id:akashi_keirin:20180121152434j:plain

この状態で実行してみる。A2セルには、「集合縦棒」が入っている。

f:id:akashi_keirin:20180121152502j:plain

ほれ、このとおり。

f:id:akashi_keirin:20180121152520j:plain

A2セルには「折れ線」。

f:id:akashi_keirin:20180121152601j:plain

ほれ、このとおり。

f:id:akashi_keirin:20180121152614j:plain

A2セルには「マーカー付きレーダー」。

f:id:akashi_keirin:20180121152628j:plain

ほれ、このとおり。

A2セルには入力規則で制限を掛けているので、通常おかしなグラフの種類名が渡るはずはないが、ムリヤリリスト1の引数typeOfGraphにデタラメな文字列を与えてみると、

Public Sub testCreateGraph()
  Dim Sh As Worksheet
  Set Sh = ThisWorkbook.Worksheets("Main")
  With Sh
    If Not createGraph(typeOfGraph:="ち~んw", _    '……(*)'
                       sourceRange:=.Range("C1:H6")) Then
      Call makeUserSick("すまん、グラフ描けんかったわwww")
    End If
  End With
End Sub

f:id:akashi_keirin:20180121152639j:plain

f:id:akashi_keirin:20180121152651j:plain

無事、グラフが作成されずに、煽られるだけ、となりますw

おわりに

現状、ボタンを押したらいきなり新規シートにグラフを作成する、という乱暴なものになっている。

Chartオブジェクト関係のオブジェクトモデルをもっとちゃんと理解して、もう少し使い勝手の良いものにしないといけないなあ。