1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell】Windows標準機能でグラフ描画 〜カスタマイズ編〜

Posted at

前回の記事で、PowerShellでグラフを作成する方法の基本を紹介しました。

今回の記事では、前回PowerShellで作成したグラフをより情報が伝わりやすいように見た目をカスタマイズしていく方法を解説します。

before

after

目次

やりたいこと

グラフの見た目のカスタマイズも自動化し、データ可視化作業および情報伝達全体の効率化を図ります。

前回の記事と同様、会社でPython等は使えないという前提なので、Windows標準機能のPowerShellで実現します。

( PowerShell 5.1 + .Net 4.8 )
 

 
今回の記事では、

  • 単純に各種要素の設定を変更してグラフの見た目を変更する基本カスタマイズ
  • データの内容に応じて見た目を変更する動的カスタマイズ

の2パートに分けて説明をします。

【Part 1】見た目を整える基本カスタマイズ

ここからは、実際にグラフの見た目を整えていきます。
Part 1では、基本編で作成したスクリプトをベースに各種カスタマイズを加えていきます。

1-1. マーカーの装飾

まずは、グラフの主役であるデータ点、マーカーの装飾からです。
もとの青い丸印も悪くありませんが、色や形を変えるだけで、グラフの印象は大きく変わります。

マーカーの色を変更する

01_マーカーの色_combined.png

グラフで最も重要な要素の一つが「色」です。
色は、情報をグループ化したり、特定のデータを強調したりするのに役立ちます。そういったデータ内容に応じたカスタマイズはPart2で触れるとして、今回はマーカーの色を単色に変更する方法だけ解説しておきます。

# マーカーの色を指定
#  - 以下のいづれかの方法で色を指定する
$series.Color = [System.Drawing.Color]::Red
$series.Color = [System.Drawing.ColorTranslator]::FromHtml("#ff0000")
$series.Color = [System.Drawing.Color]::FromArgb(255, 0, 0)
$series.Color = "Red"
  • マーカーの色は、$seriesオブジェクトのColorプロパティで変更できます。

  • Colorに渡す値は、いくつかの方法で指定できます

    • [System.Drawing.Color]::<色名>色名から指定(例:Red)
    • [System.Drawing.ColorTranslator]::FromHtmlカラーコード指定(例:"#0088FF")
    • [System.Drawing.Color]::FromArgbRGBそれぞれの値を数値で指定
    • 色名を文字列で直接指定(入力補完などは出来ないので注意が必要)
  • 使用できる色名は"HTML 140 色"等で検索すれば様々なサイトで確認できます。一例↓

マーカーに透明度を設定する

02_マーカーに透明度を設定する_combined.png

散布図では、データが密集しているとマーカー同士が重なってしまい、その部分のデータがどのくらいあるのか分かりにくくなることがあります。そんなときは、マーカーを少し透明にしてあげると、重なり具合が視覚的に分かりやすくなります。

# FromArgbを使って、透明度(Alpha)を指定して色を作成
#  - A(Alpha:透明度), R(赤), G(緑), B(青) の順
#  - 透明度は 0 (完全透明) ~ 255 (完全不透明) で指定
$series.Color = [System.Drawing.Color]::FromArgb(191, 255, 0, 0)
  • Seriesには直接透明度を指定するプロパティがないので、透明度の情報を加えた色をColorに指定します。
    [System.Drawing.Color]::FromArgb()は引数が3つだとRGBの数値を受け取りますが、引数が4つだと1つ目の引数がA(Alpha(アルファ)、透明度)になります。
    透明度という言葉なので混乱するのですが、0が完全透明、255が不透明になります。不透明度と覚えたほうが良いかもしれません。
    今回は191を指定したので、だいたい75%くらいの不透明度(25%の透明度)になります。

既存の色に対して透明度を追加するには以下のようにします。

# ベースとなる色を変数に保存
$baseColor = [System.Drawing.Color]::Red
# 色オブジェクトのRGBプロパティを使用し、透明度を加えて色指定
$series.Color = [System.Drawing.Color]::FromArgb(191, $baseColor.R, $baseColor.G, $baseColor.B) # 75%の不透明度
  • ベースとなる色のSystem.Drawing.Colorオブジェクトを取得した後、FromArgbに透明度とオブジェクトから取得したRGB値を指定して色を再取得しています。
    この場合、ベースとなる色の指定に「文字列による色指定」は使用できないので注意が必要です。

マーカーの形を変更する

03_マーカーの形を変更する_combined.png

データの種類が複数ある場合、色だけでなく形でデータを区別することもあります。
マーカーの形はMarkerStyleプロパティで変更できます。

# マーカーのスタイルを「ひし形」に変更
$series.MarkerStyle = [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Diamond
  • $series.MarkerStyle[System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::<形の名前>を指定します。

  • また、= "Diamond"と文字列で指定することも可能です

  • 指定できるマーカースタイルは以下の通り

名前 マーカー
Circle marker_style_Circle.png
Diamond marker_style_Diamond.png
Triangle marker_style_Triangle.png
Square marker_style_Square.png
Cross marker_style_Cross.png
Star4 marker_style_Star4.png
Star5 marker_style_Star5.png
Star6 marker_style_Star6.png
Star10 marker_style_Star10.png

マーカーのサイズを変更する

12_マーカーサイズを変更する_combined.png
チャートエリアのサイズやデータ数によっては、マーカーのサイズを変更したほうが見やすくなる場合もあります。

# マーカーサイズを変更する
$series.MarkerSize = 12
  • $series.MarkerSizeに大きさのピクセル数を示す数値を指定します。
    マーカーの高さ(幅)に相当するため、面積は2乗に大きくなる点に注意

マーカーに枠線を設定する

04_マーカーに枠線を設定する_combined.png

マーカーに枠線を付けると、特に色が薄い場合にマーカーの輪郭がはっきりして見やすくなります。

# マーカーの枠線の色と太さを指定
$series.BorderColor = [System.Drawing.Color]::FromArgb(102, 0, 0, 0) # 不透明度40%の黒
$series.BorderWidth = 1
  • BorderColor: 枠線の色を指定します。
    ここでは半透明の黒を指定してみました。こうすることで、マーカー本体がどんな色でも、枠線が悪目立ちせずに輪郭を強調できます。
  • BorderWidth: 枠線の太さをピクセル単位で指定します。

 
ここまでの結果

  • マーカースタイルは元のCircle、サイズは8を指定。
    色は#ff8ab3(明るめの赤系)に変更

印象は少し変わりましたが、まだまだ見やすいとは言い難いですね。

1-2. 凡例の追加

グラフに描かれたマーカーや線が「何を表しているのか」を示す 凡例 を追加していきます。
今はデータが1種類だけなので凡例がなくても困りませんが、複数のデータを同じグラフに描画する際には必須の要素になります。

# ...(チャートの作成処理)...

# 凡例オブジェクトを作成
$legend = [System.Windows.Forms.DataVisualization.Charting.Legend] @{
    Name = "ジャンル別データ"
    Font = [System.Drawing.Font]::new("Meiryo UI", 10)
    # Docking = [System.Windows.Forms.DataVisualization.Charting.Docking]::Right
}
# チャートに凡例を追加
$chart.Legends.Add($legend)

# ...(フォームにチャートを追加する処理)...
  • 凡例を追加するには、まず[System.Windows.Forms.DataVisualization.Charting.Legend]クラスを使って凡例オブジェクトを作成し、それを$chart.Legends.Add()メソッドでチャートに追加する、という2段階の手順を踏みます。

  • 追加先はChartAreaではなく、Chartなので注意が必要です

  • 主要なプロパティは以下の通りです。

    • Name: 凡例を識別するための内部的な名前です。プログラムが「どの凡例のことか」を把握するために使います。込み入ったレイアウトを作らないのであれば省略してもOKです。
    • Font: 凡例のテキストで使われるフォントとサイズを指定します。
    • Docking: 凡例をチャート内のどこに配置するかを指定できます。今回はRightを指定し、明示的に右側に配置しました。(コメントアウトしてあります)
凡例に表示されるテキストと順番

凡例で表示される項目名は、Seriesを追加するときに指定した名前 ($series = $chart.Series.Add("ここに入れた名前")) が自動的に使われます。
また、項目が表示される順番は、原則として $chart.Series.Add()Seriesを追加した順番になります。
この仕様はPart 2で重要になってくるので、頭の片隅に置いておくことにします。

 
ここまでの結果

グラフの右側に凡例が追加されました。
(窮屈になったのでwidth = 800にして少し幅を広げています)

1-3. チャートエリアの装飾

チャートエリアとは、軸やグリッド線、そしてデータマーカーが描画されるグラフの中心的な領域のことです。
ここの背景色やグリッド線を調整することで、グラフ全体の雰囲気や見やすさをコントロールできます。

チャートエリアの背景色を変更する

05_チャートエリアの背景色を変更する_combined.png

まずは、背景色を変更します。デフォルトの白色から少し色味のある背景に変えるだけで、グラフに落ち着いた雰囲気が出ます。

# チャートエリアの背景色を設定(淡いグレー)
$chartArea.BackColor = [System.Drawing.Color]::FromArgb(250, 250, 250)
  • チャートエリアの背景色は$chartAreaオブジェクトのBackColorプロパティで変更できます。
  • 色の指定方法はマーカーの時と同じで、FromArgbFromHtmlが使えます。
  • ここでは、ほぼ白に近いですが少しだけグレーがかった色(250, 250, 250)を指定してみました。真っ白よりも少し色がついている方が、目への刺激が少ないと言われることもあります。

グリッド線のスタイルと色を変更する

06_グリッド線を調整する_combined.png

グリッド線は、データポイントの値を正確に読み取るための補助線です。デフォルトでは少し目立ちすぎることもあるので、色や線のスタイルを調整して、主役であるデータを引き立てるようにしました。

# 各軸に共通設定を適用するためのループ
foreach ($axis in $chartArea.Axes) {

    # グリッド線の色を半透明のグレーに設定
    $axis.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー

    # グリッド線のスタイルを破線に設定
    $axis.MajorGrid.LineDashStyle = [System.Windows.Forms.DataVisualization.Charting.ChartDashStyle]::Dash
}
  • $axis.MajorGrid.LineColor: グリッド線の色です。ここではFromArgbの透明度(Alpha)を128(約50%)に設定し、背景に馴染ませながら目立たないようにしています。
  • $axis.MajorGrid.LineDashStyle: グリッド線のスタイルを指定します。
    指定には[System.Windows.Forms.DataVisualization.Charting.ChartDashStyle]::<名前>を使用します。
    文字列による指定も可能です。
  • 選べる線の種類は以下の通り
名前 説明 スタイル
Solid 実線 line_style_Solid.png
Dash 破線 line_style_Dash.png
Dot 点線 line_style_Dot.png
DashDot 一点鎖線 line_style_DashDot.png
DashDotDot 二点鎖線 line_style_DashDotDot.png
foreachでX軸とY軸に同じ設定を適用する

X軸(AxisX)とY軸(AxisY)にはそれぞれ個別に各種設定を行なうことができます。
しかし、両方に同じ設定をしたい場合、一つずつ書くのはコードが冗長になります。

# :
# X軸の色を変更する
$chartArea.AxisX.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー
# Y軸の色を変更する
$chartArea.AxisY.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー
# :

そんなときはforeachループを活用しましょう。
$chartArea.AxesはX軸とY軸で構成されているため、foreachで要素を取り出せば「すべての軸に対して、同じ処理を実行する」というコードがスッキリ書けます

# :
# 各軸に共通設定を適用するためのループ
foreach ($axis in $chartArea.Axes) {
    # グリッド線の色を半透明のグレーに設定
    $axis.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー
# :
}

あとからプロパティを修正する際も1箇所だけ変更すれば良くなるので、このようにforeachで書くことを推奨しておきます。

 
ここまでの結果

背景に色が付き、グリッド線が柔らかい印象になりました。全体的に洗練されてきた気がします。

1-4. 軸の装飾

軸の見た目を調整することで、数値が格段に読み取りやすくなります。

今回もforeach ($axis in $chartArea.Axes)ループを使って、X軸とY軸に共通の設定を同時に指定します。

軸の色を調整する

07_軸の色を調整する_combined.png

まずは、軸の色を変更します。

foreach ($axis in $chartArea.Axes) {
    # ... (グリッド線の設定は省略) ...

    # 軸の色をグレーに設定
    $axis.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128)

}
  • $axis.LineColorプロパティで軸自体の線の色を指定します。色の指定方法は他の色設定と同様。今回は軸の主張を抑えるため、少し薄いグレーに設定しました。

数値の書式を調整する

08_数値の書式を調整する_combined.png

次に、軸の数値ラベルの書式を設定します。

整数のままでも問題はありませんが、あえて小数点を表示することで、このデータが「小数点第一位の数値的粒度を持つ」ということをグラフ上でも表すことができます。
また、小数点以下有り無しが混在していても、表示される桁数が揃うため、見やすくなります。

    # 軸目盛りのラベルのフォーマットを小数点第一位まで表示
    $axis.LabelStyle.Format = "F1"    # Fは"Fixed-point" (固定小数点)、1で小数点第一位まで表示
  • $axis.LabelStyle.Format書式を表す文字列を指定することで、数値ラベルの書式設定ができます。

  • ここでは"F1"を指定し、「Fixed-point(固定小数点)で、小数点第1位まで」表示させています。

  • 代表的な書式設定は以下の通り。
    いずれも文字列の後ろに数値を指定することで、小数点以下の桁数を指定できます。

    • P :パーセント表記
    • F :固定小数点
    • N :数値(桁区切り)
    • C :通貨(日本ではデフォルトが¥)
    • E :指数表記

その他の書式設定詳細はMicroSoftのサイトをご確認ください。
https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/standard-numeric-format-strings

目盛り線を調整する(非表示にする)

09_目盛り線を調整する_combined.png

軸の数値ラベルと補助線があれば、目盛線は不要ですね。
グラフを見やすくするポイントは不要な要素を取り除くことなので、目盛線を非表示にします。

foreach ($axis in $chartArea.Axes) {
    # ... (グリッド線の設定は省略) ...

    # 目盛りを無効化(Enabled = $false)するとラベルが軸に近づきすぎるため、透明にしてサイズを小さくすることで対応
    $axis.MajorTickMark.LineColor = [System.Drawing.Color]::Transparent
    $axis.MajorTickMark.Size = 1
}
  • $axis.MajorTickMarkで軸からちょこんと出ている短い線(目盛り線)に関する設定を変更できます。
  • これを単純に無効化 (.Enabled = $false) にすると、数値ラベルが軸に近づきすぎて窮屈な印象になります。
    09_目盛り線を調整する_無効化_combined.png

そこで今回は、目盛り線の色を透明にすることで、軸とラベルの距離を維持しながら目盛り線を非表示にしています。

  • 主目盛りの目盛り線の色は$axis.MajorTickMark.LineColorで指定します。
    色の指定方法自体はこれまでと同様ですが、[System.Drawing.Color]::Transparentは完全透明色なので、これを使用します。
  • 目盛り線のサイズは$axis.MajorTickMark.Sizeで指定します。
    ここの数字を大きくすると、軸とラベルのマージンを広げることができます。

軸タイトル・ラベルのフォントを変更する

10_軸タイトルのフォントを変更する_combined.png

軸のタイトル(「メタスコア」「ユーザースコア」など)やラベルのフォントも調整して、グラフ全体の視認性を高めます。

foreach ($axis in $chartArea.Axes) {
    # ... (上記の設定は省略) ...

    # 軸タイトルのフォント指定
    $axis.TitleFont = [System.Drawing.Font]::new("Meiryo UI", 11)
    # 軸ラベルのフォント指定
    $axis.LabelStyle.Font = [System.Drawing.Font]::new("Segoe UI", 9)
}
  • $axis.TitleFont:軸タイトルのフォント指定を行なうプロパティです。
    これまで同様、[System.Drawing.Font]オブジェクトを作成・指定します。
  • $axis.LabelStyle.Font:軸の数値ラベルのフォント指定を行なうプロパティです。
    設定方法も上と同様です。
数値の表示に適したフォント

これまで使用してきた「Meryo UI」はWindows標準フォントの中でも視認性が高く、よく使用されるフォントです。
しかし一方で、Meiryo UIはプロポーショナルフォントと呼ばれる「文字によって字幅が変化する」という性質を持つため、数値が詰められて少し読みにくくなる側面を持っています。

そこで今回は、等幅フォント(数字が異なっても字幅は変わらない)、かつ、Meiryo UIとも相性の良いとされる「Segoe UI」を採用しています。
(ちなみにSegoe UIは日本語非対応のため、他の場所では使用しづらいです)

描画範囲を明示的に指定する

11_描画範囲を明示的に指定する_combined.png
PowerShellのグラフ機能は、データに応じて自動的に描画範囲(軸の最小値・最大値)を調整してくれます。これは便利な機能ですが、時には「データのこの範囲を特に見せたい」という意図がある場合や、自動調整の結果が中途半端な数値になる場合があります。
そんなときは、描画範囲を自分で明示的に指定します。

# X軸の最小値を65に設定
$chartArea.AxisX.Minimum = 65
  • $chartArea.AxisX$chartArea.AxisYMinimumMaximumプロパティに数値を設定することで、描画範囲を自由にコントロールできます。
    今回はデフォルトだとX軸(AxisX)の数値が67と中途半端なところから始まってしまうため、最小値(Minimum)を65に設定してみました。

 
ここまでの結果

軸がスッキリと整理され、数値も読みやすくなりました。
(…メリハリが減ったため、軸の色は黒でも良かったかも)

Part 1 まとめ

お疲れ様でした! これでPart 1で紹介した基本的なカスタマイズはすべて完了です。
どんな項目がカスタマイズできるかイメージできれば、あとは作り込んでいくだけだと思います。

参考にここまでのスクリプトを載せておきます。

Part 1 完成版スクリプト(クリックして展開)
# 読み込むCSVの指定
$csvPath = ".\game_sales_dataset.csv"


# 事前準備 ================================================

# 必要なアセンブリの読み込み
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms.DataVisualization

# CSVファイルからデータを読み込む(UTF-8想定)
$gameData = Import-Csv -Path $csvPath -Encoding UTF8

# フォームの作成(表示用ウィンドウ)
$form = [System.Windows.Forms.Form] @{
    Text   = "メタスコアとユーザースコアの散布図"
    Width  = 800
    Height = 600
}

# チャートの作成 ================================================

# チャートオブジェクトの作成
$chart = [System.Windows.Forms.DataVisualization.Charting.Chart] @{
    Width  = $form.ClientSize.Width - 100
    Height = $form.ClientSize.Height - 100
    Left   = 50
    Top    = 50
}

# タイトルの設定
$title = [System.Windows.Forms.DataVisualization.Charting.Title] @{
    Text = "メタスコア vs ユーザースコア"
    Font = [System.Drawing.Font]::new("Meiryo UI", 14)
}
$chart.Titles.Add($title)

# 凡例の作成と追加
$legend = [System.Windows.Forms.DataVisualization.Charting.Legend] @{
    Name    = "凡例"
    Font    = [System.Drawing.Font]::new("Meiryo UI", 10)
    # Docking = [System.Windows.Forms.DataVisualization.Charting.Docking]::Right
}
$chart.Legends.Add($legend)

# フォームにチャートを追加
$form.Controls.Add($chart)

# チャートエリアの作成と設定 ================================================

# チャートエリアの作成
$chartArea = [System.Windows.Forms.DataVisualization.Charting.ChartArea]::new()
# チャートエリアの背景色を設定
$chartArea.BackColor = [System.Drawing.Color]::FromArgb(250, 250, 250)
# チャートにチャートエリアを追加
$chart.ChartAreas.Add($chartArea)

# 軸ラベルの設定
$chartArea.AxisX.Title = "メタスコア"
$chartArea.AxisY.Title = "ユーザースコア"
# X軸の描画範囲(最小値)を設定
$chartArea.AxisX.Minimum = 65

# 各軸に共通設定を適用
foreach ($axis in $chartArea.Axes) {
    # グリッド線の設定
    $axis.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー
    $axis.MajorGrid.LineDashStyle = [System.Windows.Forms.DataVisualization.Charting.ChartDashStyle]::Dash

    # 軸の色を設定
    $axis.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128)

    # 軸目盛りのラベルの書式を設定(小数点第一位まで)
    $axis.LabelStyle.Format = "F1"

    # 主軸目盛りを透明にして実質的に非表示にする
    $axis.MajorTickMark.LineColor = [System.Drawing.Color]::Transparent
    $axis.MajorTickMark.Size = 1

    # 軸タイトルのフォント指定
    $axis.TitleFont = [System.Drawing.Font]::new("Meiryo UI", 11)
    # 軸ラベルのフォント指定
    $axis.LabelStyle.Font = [System.Drawing.Font]::new("Segoe UI", 9)
}

# シリーズの作成と設定 ================================================

# シリーズの作成(データ系列)
$series = $chart.Series.Add("全データ")
$series.ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Point
$series.MarkerSize = 8

# マーカーの色(透明度あり)を設定
$baseColor = [System.Drawing.ColorTranslator]::FromHtml("#ff8ab3") # 明るめの赤
$series.Color = [System.Drawing.Color]::FromArgb(191, $baseColor.R, $baseColor.G, $baseColor.B) # 75%の不透明度

# マーカーの形と枠線を設定
$series.MarkerStyle = [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Circle
$series.BorderColor = [System.Drawing.Color]::FromArgb(102, 0, 0, 0) # 不透明度40%の黒
$series.BorderWidth = 1

# データの準備とバインド ================================================

# データバインドするためのXY配列を生成
# Import-Csvで読み込んだデータは文字列型のため、数値型 [double] に変換する
$xValues = @($gameData.メタスコア | ForEach-Object { [double]$_ })
$yValues = @($gameData.ユーザースコア | ForEach-Object { [double]$_ })

# データをシリーズにバインド
$series.Points.DataBindXY($xValues, $yValues)

# フォームの表示 ================================================

# フォームを表示(戻り値は不要なため $null で抑制)
$null = $form.ShowDialog()

Part 2では、いよいよ応用編です。このスクリプトをベースに、「データのジャンルごとにマーカーの色や形を自動で変える」という、より動的なカスタマイズを行なっていきます。


【Part 2】データ内容に応じた動的なカスタマイズ

さて、ここからPart 1で作成したグラフに、データ内容に応じてマーカーの色や形を変えるという動的なカスタマイズを施していきます。
今回はゲームの「ジャンル」列の内容に応じてスタイルを変更させていきます。

ポイントは「複数シリーズ化」です。

2-1. なぜ「複数シリーズ化」が必要か?

Pythonのmatplotlib等であれば、参照する列を指定して一発で色分けができるのですが、.NETDataVisualizationでは列指定で直接色分けを行なうことが出来ません
その代わりに「複数シリーズ化」によって、これを実現します。

ここまでは、「全データ」をまとめて1つのSeriesオブジェクトとして扱い、そこにスタイルを設定していました。

これを、「アクション」用のSeries、「RPG」用のSeries、「スポーツ」用のSeries…というように、ジャンルの数だけSeriesを動的に作成し、それぞれに異なる色や形を割り当てていく、というのがPart 2の核心部分になります。

2-2. 実装:複数シリーズ化したグラフに作り変える

では実際に、Part 1で完成させた単一シリーズ用のスクリプトを、複数シリーズに作り変えていきます。

Step 1: 色とマーカーのリストを定義する

まずは、各ジャンルに割り当てる色とマーカーのリストをスクリプトの冒頭に定義します。

カラーパレットはデフォルトのものもありますが、色数が少なかったりして使いづらいので自分で定義しています

# 事前準備 ================================================
# ... (Add-Type など) ...

# カラーパレットの色定義
$colorPalette = @(
    "#ff8ab3", # 明るめの赤系
    "#ff9975", # 明るめのオレンジ赤
    "#d5ca39", # 明るめの黄緑    
    "#8eda5c", # 明るめの緑
    "#00e397", # 明るめのエメラルドグリーン
    "#00ceff", # 明るめのシアン
    "#c7b5ff", # 明るめのラベンダーブルー
    "#ffa2ff", # 明るめのマゼンタ

    "#ae3600", # 暗めの赤茶  
    "#7a5b00", # 暗めのカーキ
    "#5b6600", # 暗めのオリーブグリーン
    "#007600", # 暗めの深緑
    "#0080a6", # 暗めのティールブルー
    "#0068ff", # 暗めの青
    "#6f03ff", # 暗めの深紫
    "#d000cc" # 暗めのマゼンタ
)

# マーカースタイルの定義
$markerStyles = @(
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Circle,
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Diamond,
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Triangle
)

# ... (CSVの読み込みなど) ...
  • PowerShellで配列を作成するには@()を使用します。
  • $colorPalette配列にグラフで使いたい色のHTMLカラーコードを配列として定義しておきます。あとから一律で透明度を指定するので、RGBのみのカラーコードを記載しています。
  • 色の数はデータ数に合わせる必要はありません
  • $markerStyles: こちらも同様に、使いたいマーカーの種類を配列として定義しておきます。

Step 2: ユニークなシリーズ名を取得

次に、foreachループの準備を行なうため、$gameDataからユニークな(重複しない)ジャンル名の一覧を取得します。

# ... (チャートエリアの設定まで完了) ...

# ユニークなジャンルを取得し、名前順にソート
$uniqueGenres = @( $gameData | Select-Object -ExpandProperty ジャンル -Unique | Sort-Object )

# ジャンル別に異なるシリーズを作成
foreach ($genre in $uniqueGenres) {
    # ★ここに、Part 1 で書いたシリーズ作成・設定処理を移動させる
    # (次のステップで解説)
}

  • @( ... ): カッコの中の処理の結果を、配列として取得します。理由は後述
  • $gameData |: パイプライン(|)を使用し、$gameData分解して次のコマンドに渡します$gameDataは分解すると1行分のレコード(PSCustomObject)になり、その全行が渡されます。
  • Select-Object -ExpandProperty ジャンル: PowerShellでおなじみの〇〇-Objectシリーズ。渡されたPSCustomObjectのうち、-ExpandProperty引数で指定されたプロパティを抽出します。今回の場合はジャンル列を抽出。これでジャンル名の配列が取得できました。
  • -Unique: Select-Objectのオプション引数。重複を削除し、ユニークな配列を取得します。
  • | Sort-Object: 取得したジャンル名はそのままだと出現した順番になってしまうので、Sort-Objectでアルファベット順(A→Z)に並べ替えています。

PowerShellでよくある落とし穴 ~配列で返したり、返さなかったり~

PowerShellのパイプライン処理には、結果が1件の場合に配列ではなく単一オブジェクトを返す、という少し厄介な仕様があります。

今回のコードで、
$uniqueGenres = @( $gameData | ... )
と、処理全体を@()で囲んでいるのは、まさにこの仕様への対策です。

なぜ問題になるのか?

パイプラインの実行結果が1件だけだと単一のオブジェクトが、複数件だと配列が返されます。このように結果の「型」が変わってしまうと、.Countプロパティでの件数取得やforeachでのループ処理が、オブジェクトの種類によって動いたり動かなかったりして、エラーの原因になります。

対策:@()で囲んで、常に配列として受け取る

対策はシンプルで、処理全体を@()で囲むだけです。
こうすることで、結果の件数(0件、1件、複数件)に関わらず、常に配列として結果を受け取ることができ、後続の処理を安全に記述できます。
(もともと結果が配列であっても、ネスト(配列の配列)になったりはしないので安心です。)

他にも注意が必要なコマンドレット

この問題は、Get-ChildItemWhere-ObjectImport-Csvなど、複数の結果を返しうる多くのコマンドレットで発生する可能性があります。コレクション(配列など)を扱う際は、常に@()で囲んでおくと堅牢なスクリプトになります。

 

  • 上記で取得したジャンル名を、foreachで回していきます。

Step 3: 取得したシリーズ名をもとに、ループ処理で各Seriesを作成

ここからStep 3と4に分けてループ処理の中身を作っていきます。
まずはSeriesの作成から。

まず、「$seriesを作成し、見た目を設定し、データをバインドする」という一連の処理を、先ほど用意したforeachループの中に移動させます。
そして、ループの中でデータ全体($gameData)から、そのループで注目しているジャンルのデータだけを絞り込む処理を追加しています。

# ジャンル別に異なるシリーズを作成
foreach ($genre in $uniqueGenres) {
    # --- ▼ここからがループ内に追加・変更した処理 ---

    # シリーズの作成と共通設定(シリーズ名はジャンル名を使う)
    $series = $chart.Series.Add($genre)
    $series.ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Point
    $series.MarkerSize = 8

    # (色とマーカーの設定は次のセクションで動的に変更します)
    # :

    # 該当ジャンルのデータを抽出
    $genreData = @( $gameData | Where-Object { $_.ジャンル -eq $genre } )

    # データバインドするためのXY配列を生成
    $xValues = @( $genreData.メタスコア | ForEach-Object { [double]$_ } )
    $yValues = @( $genreData.ユーザースコア | ForEach-Object { [double]$_ } )

    # データをシリーズにバインド
    $series.Points.DataBindXY($xValues, $yValues)

}

$series = $chart.Series.Add($genre):

  • Series.Add()Seriesを追加します。引数に指定しているのはシリーズ名です。
    これにより、"Action"シリーズ、"Adventure"シリーズ…といったように、ジャンルごとにSeriesが作成されます。このとき指定したシリーズ名が凡例にも表示されます。

 
$genreData = @( $gameData | Where-Object { $_.ジャンル -eq $genre } ):

  • Where-Objectは、条件に一致するデータだけを抽出するコマンドレットです。
  • { }の中が条件式で、$_は「$gameDataから渡されてきたデータ1行分」を表す特殊な変数です。
  • つまり、「ジャンル列が、ループ変数$genreと一致(-eq)するものだけを取得」という意味になります。

あとは絞り込んだ$ganreDataから目的のデータ(メタスコア、ユーザースコア)を取り出し、それぞれX・Yに指定するだけです。
基本編のときと同様、CSVから取得したデータは文字列のため、| ForEach-Object { [double]$_ } )数値型に変換するのを忘れないようにします。

Step 4: 色とマーカーを動的に割り当てる

いよいよ最後の仕上げです。foreachループが一周するごとに、あらかじめ定義しておいた色のパレットとマーカーのスタイルリストから、スタイルを一つずつ取り出してSeriesに適用していきます。

カウンター変数の準備

まず、foreachループが始まる前に、現在どの色・どのマーカースタイルを使っているかを記録しておくための「カウンター変数」を準備します。

# ... (ユニークなジャンルを取得した後) ...

# カラーインデックスの初期化
$colorIndex = 0
# マーカースタイルインデックスの初期化
$markerStyleIndex = 0

# ジャンル別に異なるシリーズを作成
foreach ($genre in $uniqueGenres) {
    # ...
}
  • $colorIndex$markerStyleIndexという変数をカウンター変数として扱います。初期値は0を指定します。
ループ内でスタイルを順番に割り当てる

次に、foreachループの中で、カウンター変数を使って配列から色と形を取り出し、Seriesに設定します。

# ジャンル別に異なるシリーズを作成
foreach ($genre in $uniqueGenres) {
    # シリーズの作成
    $series = $chart.Series.Add($genre)
    # ... (ChartType, MarkerSizeの設定) ...

    # --- ▼ここからが動的割り当ての処理 ---

    # 1. 色の選択と設定
    $color = $colorPalette[$colorIndex]    # カラーパレットから色情報を抽出
    $baseColor = [System.Drawing.ColorTranslator]::FromHtml($color)    # Colorオブジェクトに変換
    $series.Color = [System.Drawing.Color]::FromArgb(191, $baseColor.R, $baseColor.G, $baseColor.B) # 透明度を設定

    # 2. 色のインデックスを次に進める(末尾まで行ったら先頭に戻る)
    $colorIndex = ($colorIndex + 1) % $colorPalette.Count

    # 3. マーカースタイルの選択と設定
    $series.MarkerStyle = $markerStyles[$markerStyleIndex]

    # 4. マーカースタイルのインデックスを次に進める
    $markerStyleIndex = ($markerStyleIndex + 1) % $markerStyles.Count

    # ... (枠線の設定、データ絞り込み、データバインド処理) ...
}
  1. 色情報の取得
    • $colorPalette配列の、$colorIndex番目の要素を取り出しています。最初のループでは$colorIndex0なので、配列の0番目(つまり先頭)のHTMLカラーコードが$color変数に格納されます。
    • その次の2行は、マーカーに透明度を指定したときと同じ処理です。
      Colorオブジェクトに変換した後、透明度を設定しています。
       
  2. $colorIndex = ($colorIndex + 1) % $colorPalette.Count
    • ループの最後に、次の色を使うためにインデックスを+1しています。
    • ここで登場する%(パーセント)は、剰余(じょうよ)演算子といい、「割り算の余り」を計算する記号です。
    • $colorPalette.Countは、$colorPalette配列に入っている要素の総数(今回は16)を返します。
    • 例えば、$colorIndex0から14のときは、+1した結果(1~15)を16で割っても余りはそのまま1~15です。
    • そして$colorIndex15(最後の色)のとき、(15 + 1) % 1616 % 16となり、余りは0になります。これによりカウンター変数は0にリセットされ、再度1,2…と増分していきます。
    • つまり、インデックスが配列の最後まで到達したら、次は自動的に0番目に戻るという仕組みです。これにより、ジャンルの数が色の数より多くても、エラーにならずに色を循環させて使うことができます。
       
  3. マーカースタイルも、色と全く同じ仕組みで順番に割り当てています。

 
以上のループで、すべてのシリーズに各種設定を割り当てれば、複数シリーズのグラフの完成です!

最終完成スクリプト

これまでのすべてのカスタマイズを盛り込んだ、最終的なスクリプトの全文はこちらです。

# 読み込むCSVの指定
$csvPath = ".\game_sales_dataset.csv"


# 事前準備 ================================================

# 必要なアセンブリの読み込み
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms.DataVisualization

# カラーパレットの色定義
$colorPalette = @(
    "#ff8ab3", # 明るめの赤系
    "#ff9975", # 明るめのオレンジ赤
    "#d5ca39", # 明るめの黄緑    
    "#8eda5c", # 明るめの緑
    "#00e397", # 明るめのエメラルドグリーン
    "#00ceff", # 明るめのシアン
    "#c7b5ff", # 明るめのラベンダーブルー
    "#ffa2ff", # 明るめのマゼンタ

    "#ae3600", # 暗めの赤茶  
    "#7a5b00", # 暗めのカーキ
    "#5b6600", # 暗めのオリーブグリーン
    "#007600", # 暗めの深緑
    "#0080a6", # 暗めのティールブルー
    "#0068ff", # 暗めの青
    "#6f03ff", # 暗めの深紫
    "#d000cc" # 暗めのマゼンタ
)

# マーカースタイルの定義(順繰り)
$markerStyles = @(
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Circle,
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Diamond,
    [System.Windows.Forms.DataVisualization.Charting.MarkerStyle]::Triangle
)

# CSVファイルからデータを読み込む(UTF-8想定)
$gameData = Import-Csv -Path $csvPath -Encoding UTF8

# フォームの作成(表示用ウィンドウ)
$form = [System.Windows.Forms.Form] @{
    Text   = "メタスコアとユーザースコアの散布図"
    Width  = 800
    Height = 600
}

# チャートの作成 ================================================

# チャートの作成(固定配置)
$chart = [System.Windows.Forms.DataVisualization.Charting.Chart] @{
    Width  = $form.ClientSize.Width - 100
    Height = $form.ClientSize.Height - 100
    Left   = 50
    Top    = 50
}

# タイトルの設定
$title = [System.Windows.Forms.DataVisualization.Charting.Title] @{
    Text = "メタスコア vs ユーザースコア"
    Font = [System.Drawing.Font]::new("Meiryo UI", 14)
}
$chart.Titles.Add($title)

# 凡例の作成
$legend = [System.Windows.Forms.DataVisualization.Charting.Legend] @{
    Name    = "ジャンル別データ"
    Font    = [System.Drawing.Font]::new("Meiryo UI", 10)
    Docking = [System.Windows.Forms.DataVisualization.Charting.Docking]::Right
}
$chart.Legends.Add($legend)

# フォームにチャートを追加
$form.Controls.Add($chart)

# チャートエリアの作成 ================================================

# チャートエリアの作成
$chartArea = [System.Windows.Forms.DataVisualization.Charting.ChartArea]::new()
$chartArea.BackColor = [System.Drawing.Color]::FromArgb(250, 250, 250)
$chart.ChartAreas.Add($chartArea)

# 軸ラベル
$chartArea.AxisX.Title = "メタスコア"
$chartArea.AxisY.Title = "ユーザースコア"
$chartArea.AxisX.Minimum = 65

# 各軸に共通設定
foreach ($axis in $chartArea.Axes) {
    # グリッド線の色、スタイルを設定
    $axis.MajorGrid.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128, 128) # 50%のグレー
    $axis.MajorGrid.LineDashStyle = [System.Windows.Forms.DataVisualization.Charting.ChartDashStyle]::Dash

    # 軸の色をグレーに設定
    $axis.LineColor = [System.Drawing.Color]::FromArgb(128, 128, 128)
    
    # 副軸目盛りは完全に非表示
    $axis.MinorTickMark.Enabled = $false
    
    # 主軸目盛りはFalseにすると軸と数字の距離が近くなりすぎるので透明にして対応
    $axis.MajorTickMark.LineColor = [System.Drawing.Color]::Transparent
    $axis.MajorTickMark.Size = 1

    # 軸目盛りのラベルのフォーマットを小数点第一位まで表示
    $axis.LabelStyle.Format = "F1"    # Fは"Fixed-point" (固定小数点)、1で小数点第一位まで表示

    # 軸タイトルのフォント指定
    $axis.TitleFont = [System.Drawing.Font]::new("Meiryo UI", 11)
    # 軸ラベルのフォント指定
    $axis.LabelStyle.Font = [System.Drawing.Font]::new("Segoe UI", 9)
}

# シリーズの作成 ================================================

# ユニークなジャンルを取得し、名前順にソート
$uniqueGenres = @($gameData | Select-Object -ExpandProperty ジャンル -Unique | Sort-Object)

# カラー/マーカーインデックスの初期化
$colorIndex = 0
$markerStyleIndex = 0

# ジャンル別に異なるシリーズを作成
foreach ($genre in $uniqueGenres) {
    $series = $chart.Series.Add($genre)
    $series.ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Point
    $series.MarkerSize = 8

    # 色の選択と設定(カラーパレットを循環)
    $color = $colorPalette[$colorIndex]
    $baseColor = [System.Drawing.ColorTranslator]::FromHtml($color)
    $series.Color = [System.Drawing.Color]::FromArgb(191, $baseColor.R, $baseColor.G, $baseColor.B)
    $colorIndex = ($colorIndex + 1) % $colorPalette.Count

    # マーカースタイルの選択と設定(マーカースタイルを循環)
    $series.MarkerStyle = $markerStyles[$markerStyleIndex]
    $markerStyleIndex = ($markerStyleIndex + 1) % $markerStyles.Count
    
    # マーカー枠線の設定
    $series.BorderColor = [System.Drawing.Color]::FromArgb(102, 0, 0, 0) # 不透明度40%の黒
    $series.BorderWidth = 1

    # 該当ジャンルのデータを抽出
    $genreData = @( $gameData | Where-Object { $_.ジャンル -eq $genre } )

    # データバインドするためのXY配列を生成
    # Import-Csvで読み込んだデータは文字列型のため、数値型 [double] に変換する
    $xValues = @( $genreData.メタスコア | ForEach-Object { [double]$_ } )
    $yValues = @( $genreData.ユーザースコア | ForEach-Object { [double]$_ } )

    # データバインド
    $series.Points.DataBindXY($xValues, $yValues)
}

# 出力 ================================================

# フォームの表示
$null = $form.ShowDialog()

まとめ

今回は、PowerShellで作成したグラフの見た目をカスタマイズする方法を紹介しました。

ポイント

  • カスタマイズできる範囲を理解すること(方法自体はググるかAIに聞けばOK)
  • ある程度雛形を作ってしまえばコピペで使いまわせる
  • 動的なカスタマイズはちょっと面倒
    1. カラーパレットなどを準備
    2. シリーズ名に該当するデータを抽出
    3. ループ処理でシリーズを作成し、同時に色やスタイルを割り当てていく

今後

ここまでの内容で、もともとやりたかった「グラフ作成の自動化」がおおよそ実現できました。
ただ、色々勉強していく中でPowerShellで作成したグラフにもインタラクティブな要素を追加できると分かったので、
次の記事では、その辺りを紹介できればと思います。
(多分また時間かかります。。)

1
3
0

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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?