DataGridView:セル内にスパークライン(折れ線グラフ)を表示する方法(サンプルあり)

📋 目次(クリックで展開)

売上の推移や在庫数の変動など、数字の羅列だけでは把握しにくいデータの傾向を、一覧画面のセルの中に小さな折れ線グラフ(スパークライン)として視覚的に表現したい場合があります。

DataGridView には標準のグラフ列はありませんが、描画イベントで Graphics クラスの機能を利用することで、セルの中に動的な折れ線グラフを描くことができます。

今回は、CellPainting イベントを利用して、セル内の座標を計算しながらスパークラインを描画する手順について解説します。

スパークラインを描画する仕組み

セル内にグラフを描画するには、DataGridView の CellPainting イベントを利用します。

このイベントの中で、対象の列がグラフを表示するための列であるかを判定し、バインドされている数値データの配列やリストをもとに、セルの枠内(e.CellBounds)の中で折れ線の座標を計算します。

セルの高さや幅に収まるように数値をスケーリング(縮尺の調整)し、e.Graphics.DrawLines メソッドを使用して線を描画します。

実装コード

以下は、BindingList をデータソースとしてバインドし、各行が持つ過去6ヶ月分の売上データ(List<int>)を、グリッド内の「SparklineCombo」列に折れ線グラフとして描画するコード例です。

Form1.cs

Form1.cs
using System.ComponentModel;
namespace DataGridView_Sparkline;
public partial class Form1 : Form
{
private BindingList<ProductSales> _products = new BindingList<ProductSales>();
public Form1()
{
InitializeComponent();
InitializeDataGridView();
}
private void InitializeDataGridView()
{
// テスト用データの準備(過去6ヶ月の売上推移データを含む)
_products.Add(new ProductSales("P001", "ノートパソコン", new List<int> { 10, 15, 8, 20, 25, 30 }));
_products.Add(new ProductSales("P002", "USBメモリ", new List<int> { 50, 45, 48, 30, 20, 10 }));
_products.Add(new ProductSales("P003", "周辺機器", new List<int> { 12, 12, 14, 13, 15, 14 }));
// グリッドにデータソースを設定
dataGridView1.DataSource = _products;
// グラフ描画用の非データバインド列を末尾に追加
var graphCol = new DataGridViewTextBoxColumn();
graphCol.Name = "SparklineCombo";
graphCol.HeaderText = "売上推移(6ヶ月)";
dataGridView1.Columns.Add(graphCol);
// グラフが見やすくなるように、行の高さと幅を調整
dataGridView1.RowTemplate.Height = 40;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// セル描画イベントを購読
dataGridView1.CellPainting += DataGridView1_CellPainting;
}
/// <summary>
/// セルの描画が行われるときに発生するイベント
/// </summary>
private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
// e.ColumnIndex >= 0 の判定を追加し、行ヘッダー描画時のエラーを防ぎます
if (e.RowIndex >= 0 && e.ColumnIndex >= 0 && dataGridView1.Columns[e.ColumnIndex].Name == "SparklineCombo")
{
// 1. 背景や選択状態の枠線など、標準の背景を先に描画する
e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground);
// 2. 行のデータプロパティからグラフ用の数値リストを取得
if (dataGridView1.Rows[e.RowIndex].DataBoundItem is ProductSales product && product.MonthlySales.Count > 1)
{
var salesData = product.MonthlySales;
// 3. 描画領域(セル内の余白を考慮したサイズ)の計算
int paddingX = 6;
int paddingY = 6;
int graphLeft = e.CellBounds.Left + paddingX;
int graphTop = e.CellBounds.Top + paddingY;
int graphWidth = e.CellBounds.Width - (paddingX * 2);
int graphHeight = e.CellBounds.Height - (paddingY * 2);
// 4. データ内の最大値と最小値を特定(セルの高さに収めるスケーリングのため)
int maxVal = salesData.Max();
int minVal = salesData.Min();
int valRange = maxVal - minVal;
if (valRange == 0) valRange = 1; // すべて同値の場合のゼロ除算防止
// 5. 各データ点に対応するセル内の座標(PointF)を計算
var points = new PointF[salesData.Count];
float xStep = (float)graphWidth / (salesData.Count - 1);
for (int i = 0; i < salesData.Count; i++)
{
float x = graphLeft + (i * xStep);
// 値が大きいほど画面上では「上(Y座標が小さい方向)」になるよう計算
float y = graphTop + graphHeight - ((float)(salesData[i] - minVal) / valRange * graphHeight);
points[i] = new PointF(x, y);
}
// 6. 計算した座標を結ぶ折れ線を描画(アンチエイリアスで線を滑らかにします)
var oldMode = e.Graphics.SmoothingMode;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// システム共通の青いペンを使用して描画
e.Graphics.DrawLines(Pens.DodgerBlue, points);
// 描画モードを元に戻す
e.Graphics.SmoothingMode = oldMode;
}
// 7. システム側の標準テキスト描画処理をスキップさせる
e.Handled = true;
}
}
}
/// <summary>
/// 各行のデータを表すモデルクラス
/// </summary>
public class ProductSales
{
public string Code { get; set; }
public string Name { get; set; }
// グラフの元データとなる数値リスト(グリッドには自動生成されないように指定)
[Browsable(false)]
public List<int> MonthlySales { get; set; }
public ProductSales(string code, string name, List<int> monthlySales)
{
Code = code;
Name = name;
MonthlySales = monthlySales;
}
}

サンプルアプリケーション

DataGridView_SparklineChart.zip
※ サンプルアプリケーションは、.NET 10 をターゲットに作成しています。

実行結果

「SparklineCombo」列に折れ線グラフが描画されていることが分かります。

留意すべき注意点と実装の背景

DataGridView 内にカスタム描画を行う際、表示品質や動作の安定性を保つために把握しておくとよい点が2つあります。

インデックスの範囲確認と行・列ヘッダーの考慮

CellPainting イベントは、通常のデータセルだけでなく、上端の「列ヘッダー」や左端の「行ヘッダー」が描画される際にも呼び出されます。

列ヘッダー描画時は e.RowIndex-1 になり、行ヘッダー描画時は e.ColumnIndex-1 になります。条件判定の中で dataGridView1.Columns[e.ColumnIndex]dataGridView1.Rows[e.RowIndex] のようにグリッドのコレクションを参照する場合、インデックスが -1 の状態で処理が通過すると範囲外例外(ArgumentOutOfRangeException)が発生します。

カスタム描画を行う際は、サンプルコードのように e.RowIndex >= 0 && e.ColumnIndex >= 0 という条件を明示し、データセル領域のみに処理を限定する制御が必要です。

描画イベント内でのリソース管理

CellPainting イベントは画面のスクロールやセルの選択状態が切り替わるたびに頻繁に呼び出されます。そのため、グラフの線の色や太さを変更したい場合に、イベントハンドラ内で毎回 new Pen を実行するコードは避けてください。

イベント内でオブジェクトの生成を繰り返すと、メモリの割り当てと解放の頻度が高まり、画面スクロール時のカクつきなどパフォーマンス低下の原因になります。今回はシステム側があらかじめ用意している使い回し可能なペン(Pens.DodgerBlue)を利用しています。独自のカラーや太さのペンを使用したい場合は、フォームのメンバ変数として一度だけ生成し、フォーム終了時に破棄(Dispose)する構成を検討してください。

まとめ

今回は、DataGridView のセル内にスパークラインチャートを埋め込んで表示する方法について解説しました。

標準のテキスト表示列に対し、CellPainting イベント内で座標計算を行うことで、表現力のあるグラフ列を追加できます。

  • e.RowIndex >= 0 && e.ColumnIndex >= 0 の条件でデータセル領域に描画タイミングを絞り込む
  • セルのサイズ(e.CellBounds)とデータの値からスケーリングした座標を計算する
  • スクロールへの影響を防ぐため、イベント内での不要なオブジェクト生成を避ける

数値だけでは捉えにくい変化のトレンドを直感的に伝える画面づくりの参考になりましたら幸いです。


💬 コメント