Visual Studio の WindowsForm では、マイクロソフト標準のMSChartコントロールが使えるのですが、WPFではサポートされておらずツールボックスからも消えています。
しかし、使えない事はありません。
WPFからWindowsFormのコンポーネントを呼び出す機能(WindowsFormsHost)が用意されていて、これを使う事でMSChartをWPFで利用できるようになります。
今回は、MSChartの使い方についてWPFを使って解説したいと思います。
尚、画面のレイアウトはXAMLを使っていますが、C#のコード部分はWindowsFormと共通なので、「WindowsFormでMSChartを使ってグラフを書きたい!」という方についても、参考にして頂ける内容となっています。
MSChartの概要
MSChartには35種類のグラフ描画機能が用意されています。
実際には「カクカクした折れ線」と「滑らかな折れ線」などのように少しだけ違う似た者同士のグラフも多いので、厳密に言うともう少し減りますが、それでも、点、折れ線、縦/横棒、積み上げ、面、ピラミッド、ドーナツ、ローソク、レーダーチャート、練行足、範囲、株価など、様々な用途で使えるグラフが用意されています。
MSChartが他のフリーのチャートコントロールに勝る点は次の2つです。
- 折れ線グラフの描画は高速で、Scott Plot に勝るとも劣らない
- 3Dグラフに対応している
マイクロソフトの公式サイトも用意されていますが、ひたすらクラスの仕様(メソッド、プロパティ等)が記載されているだけで特にサンプルソースは見当たらず、これからグラフを書こうという方にはほとんど参考になりません。
ただ、MSChartの歴史が古いため、Google検索でヒットするブログ記事などには数多くのサンプルソースと解説が載っていますので、そちらは非常に参考になります。
ここがマイナスポイント
OxyPlot もそうでしたが、初期値のままだと見栄えが良くありません。
また、他のチャートコントロールに比べてグラフを描画するまでの処理が多いです。
デザインに関しては、以下のところがマイナスポイントです。
特に小さい領域でグラフと凡例を同時に表示することは避けた方が良さそうです。
インストール方法
MSChartはVisual StudioのWindowsFormに標準搭載されていますので、WindowsFormアプリケーションを作るのであれば、特に何もする必要はありません。
しかし、WPFでMSChartコントロールを使う場合は必ず参照設定が必要です。
VisualStudioのソリューションエクスプローラーから「参照」をクリックし、表示されるメニューから「参照の追加」を選択します。
すると、参照マネージャーというダイアログが表示されますので、左端の「アセンブリ」をクリックし、一覧の中から
System.Drawing と System.Windows.Forms.DataVisualization にチェックを入れてください。
使い方
WindowsFormの場合はツールボックスにコントロールが表示されるのでドラッグ&ドロップで張り付けられますが、WPFの場合はMSChartコントロール自身がツールボックスに表示されません。
代わりにWindowsFormsHost を画面にドラッグ&ドロップするか、XAMLに直接 WindowsFormsHostのタグを記述して、そのタグの中にMSChartを記述しなければなりません。
ドラッグ&ドロップしても結局XAMLを編集する必要があるので、最初からXAMLに記述する方が楽です。
XAMLへの記述は簡単で、次の様に書くだけです。
C#からMSChartにアクセスするためには名前が必要なので、ついでにx:Name で名前も付けておきましょう。
1 2 3 |
<WindowsFormsHost Height="315" Width="300" Margin="22,46,990,58" HorizontalAlignment="Left"> <wfc:Chart x:Name="uxChart"/> </WindowsFormsHost> |
MSChartの構造
XAMLの参照が終われば、あとはWindowsFormで MSChartを使うのとまったく同じ手順になります。
描画手順は、MSChartコントロールに対して、タイトルや軸、プロットデータをセットすればグラフ表示されるのですが、他のフリーのチャートコントロールと異なる点は、Seriesクラスが1つしか用意されておらず、ChartType プロパティにグラフの種類をセットするという点です。
クラス構成は次の様になっています。
クラス構成を見てみると、Title、Legend、ChartArea、Seriesが全て並列に並んでいて、主従関係が無い構成になっています。
これに関しては、MSChart独特の考え方になっていますので、後ほど詳細を説明しますが、今はこのようなクラス構成であるという点だけ理解いただければと思います。
以下は、MSChartが対応しているグラフの一覧になります。
例えば、Seriesクラスの ChartType プロパティに ChartType.Point を代入すると、点グラフが表示され、ChartType.Lineを代入すると折れ線グラフが表示されるようになっています。
No | 種類 | ChartStyle | No | 種類 | ChartStyle |
---|---|---|---|---|---|
1 | ポイント グラフ | Point | 19 | ピラミッド グラフ | Pyramid |
2 | FastPoint グラフ | FastPoint | 20 | ドーナツ グラフ | Doughnut |
3 | バブル チャート | Bubble | 21 | 株価チャート | Stock |
4 | 折れ線グラフ | Line | 22 | ローソクチャート | Candlestick |
5 | スプライン グラフ | Spline | 23 | 範囲グラフ | Range |
6 | StepLine グラフ | StepLine | 24 | スプライン範囲グラフ | SplineRange |
7 | FastLine グラフ | FastLine | 25 | RangeBar グラフ | RangeBar |
8 | 横棒グラフ | Bar | 26 | 範囲縦棒グラフ | RangeColumn |
9 | 積み上げ横棒グラフ | StackedBar | 27 | レーダー チャート | Radar |
10 | 100% 積み上げ横棒グラフ | StackedBar100 | 28 | 極座標チャート | Polar |
11 | 縦棒グラフ | Column | 29 | 誤差範囲グラフ | ErrorBar |
12 | 積み上げ縦棒グラフ | StackedColumn | 30 | ボックス プロット グラフ | BoxPlot |
13 | 100% 積み上げ縦棒グラフ | StackedColumn100 | 31 | 練行足チャート | Renko |
14 | 面グラフ | Area | 32 | ThreeLineBreak グラフ | ThreeLineBreak |
15 | スプライン面グラフ | SplineArea | 33 | かぎ足チャート | Kagi |
16 | 積み上げ面グラフ | StackedArea | 34 | PointAndFigure グラフ | PointAndFigure |
17 | 100% 積み上げ面グラフ | StackedArea100 | 35 | じょうごグラフ | Funnel |
18 | 円グラフ | Pie |
MSChartの各クラスとグラフの関係
ChartコントロールのTitle、Legend、ChartArea、Seriesと描画されるグラフには、次のような関係があります。
Titleにはグラフのタイトルを保持するためのオブジェクトを登録します。
Legendは凡例のオブジェクトを登録しますが、1つの凡例オブジェクトで複数の凡例をひとまとまりに表示するため、「凡例表示エリア」と考えた方が分かりやすいと思います。
ChartAreaはグラフを表示するためのオブジェクトで、SeriesはChartAreaに表示される個々のグラフオブジェクトを登録します。
Title、Legend、ChartArea、SeriesはいずれもCollection クラスなのでオブジェクトはAddメソッドで登録していきます。
Title、Legend、Seriesは、どのChartAreaに表示するかを指定できる
Title、Legend、ChartArea、Seriesに主従関係が無く、Chartコントロールに同列でぶら下がっており、それぞれCollectionとして登録できる構造は、パッと見た感じ分かり難いかもしれません。
私も最初はこの構造の意味が分かりませんでした。
ChartAreaに、Title、Legend、Sereiesの登録メソッドが有れば分かりやすいのですが、そうではなかったからです。
では、これらをどうやってChartAreaに関連付けるかと言うと、答えはChartAreaのNameプロパティになります。
ChartAreaのNameプロパティに設定した名前を、Title、Legend、Seriesのオブジェクトに指定してあげることで、全てが関連づきます。
この仕組みを使う事で、1つのChartコントロールの中に、独立した複数のグラフを表示することができるようになります。
複数のChartコントロールを張り付ければ同じ事ができるので、この仕組みを使う機会は少ないとは思いますが、理解しておいた方が間違いが少ないでしょう。
サンプルソース1
それでは、次の線グラフを描画するためのサンプルソースで少し詳しく解説していきます。
今回は、コントロールに "uxChart1" という名前を付けています。
XAMLのソースコード
以下がXAMLのソースコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<Window x:Class="MsChartTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wfc="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization" xmlns:local="clr-namespace:MsChartTest" mc:Ignorable="d" Title="MainWindow" Height="450" Width="1320"> <Grid> <WindowsFormsHost> <wfc:Chart x:Name="uxChart1"/> </WindowsFormsHost> </Grid> </Window> |
では、次にC#のソースコードです。
C#のソースコード
まず初めに、冒頭に次の2行を記述しておきます。
1 2 |
using System.Windows.Forms.DataVisualization.Charting; using System.Drawing; |
折れ線グラフのデータを作る部分、チャート全体の設定、折れ線グラフを描画するという3つの構成になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//--------------------------------------------------- //折れ線グラフのデータ作成 //--------------------------------------------------- double[] xs = Enumerable.Range(0, 100).Select(i => (double)i).ToArray(); double[] ys1 = Enumerable.Range(0, 100).Select(i => Math.Sin(i * 0.1)).ToArray(); double[] ys2 = Enumerable.Range(0, 100).Select(i => 2 + Math.Cos(i * 0.1)).ToArray(); double[] ys3 = Enumerable.Range(0, 100).Select(i => 4 + Math.Sin(i * 0.1) * Math.Cos(i * 0.1)).ToArray(); //--------------------------------------------------- // チャート全体の設定 //--------------------------------------------------- //タイトル、Series、凡例をクリア //初期状態としてChartAreaとSeriesが1つだけ登録されているが、複数のグラフを描画する際の //ループ処理が面倒なので事前にクリアしておく uxChart1.Titles.Clear(); uxChart1.Series.Clear(); uxChart1.Legends.Clear(); uxChart1.ChartAreas.Clear(); //チャートエリアを追加(名前は必須) uxChart1.ChartAreas.Add(""); //タイトルを設定 uxChart1.Titles.Add("タイトル1"); //凡例の表示エリアを登録 uxChart1.Legends.Add(""); //--------------------------------------------------- // 折れ線グラフの描画 //--------------------------------------------------- //=========== 1つ目の線グラフを描画 ================ //Seriesを追加 var seri1 = new Series("凡例1") { ChartType = SeriesChartType.Line }; //Seriesを生成 Enumerable.Range(0, ys1.Length).Select(i => seri1.Points.AddXY(xs[i], ys1[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri1); //=========== 2つ目の線グラフを描画 ================ //Seriesを追加 var seri2 = new Series("凡例2") { ChartType = SeriesChartType.Line }; //Seriesを生成 Enumerable.Range(0, ys2.Length).Select(i => seri2.Points.AddXY(xs[i], ys2[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri2); //=========== 3つ目の線グラフを描画 ================ //Seriesを追加 var seri3 = new Series("凡例3") { ChartType = SeriesChartType.Line }; //Seriesを生成 Enumerable.Range(0, ys3.Length).Select(i => seri3.Points.AddXY(xs[i], ys3[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri3); |
MSChartを使う時、最初にCollectionはクリアしておく
MSChartを張り付けた状態では、Legend、ChartArea、Seriesの各コレクションには、1つだけオブジェクトが登録された状態になっています。
わざわざオブジェクトを生成しなくても、次の様に添え字を使ってプロパティを設定すればグラフ描画が出来ますので、1つのグラフだけを描画するのであれば特に気にする必要はありません。
1 |
uxChart1.ChartArea[0].IsDockedInsideChartArea = true; |
しかし、1つのMSChartコントロールに複数のグラフを表示し、しかもボタンを押して何度も再描画するような仕様の場合だと、「プログラム起動時の初回は、Titleだけ追加して、Legend、ChartArea、Seriesに初期状態で登録されているオブジェクトはそのまま使い、ボタンが押されたときはこれらを全てクリアして、Title、Legend、ChartArea、Seriesにオブジェクトを追加する」という場合分け処理が必要となってしまいます。
それならボタンが押されたタイミングでコレクションをクリアしておく方が、ループ内の場合分けをしなくて済む分楽になります。
チャート全体の設定でClear()メソッドを呼んだ後で、再度Addしてオブジェクトをコレクションに登録しているのはこのためです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uxChart1.Titles.Clear(); uxChart1.Series.Clear(); uxChart1.Legends.Clear(); uxChart1.ChartAreas.Clear(); //チャートエリアを追加(名前は必須) uxChart1.ChartAreas.Add(""); //タイトルを設定 uxChart1.Titles.Add("タイトル1"); //凡例の表示エリアを登録 uxChart1.Legends.Add(""); |
サンプルソース2
次は、ChartAreaとTitle、Legend、Seriesを関連付けて、1つのMSChartコントロールに複数のグラフを表示するサンプルを紹介します。
XAMLのソースコード
サンプル1のソースコードと全く同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<Window x:Class="MsChartTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wfc="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization" xmlns:local="clr-namespace:MsChartTest" mc:Ignorable="d" Title="MainWindow" Height="450" Width="1320"> <Grid> <WindowsFormsHost> <wfc:Chart x:Name="uxChart1"/> </WindowsFormsHost> </Grid> </Window> |
C#のソースコード
ChartAreaを2つ用意し、”Area1”と”Area2”という名前を付けています。
そして、Title、Legend、Seriesについて、”Area1”と”Area2”のどちらに表示するかを、TitleとLegendについてはDockedToChartAreを、SeriesについてはChartAreaとLegendプロパティに設定しています。
Seriesオブジェクト1つに付き、どこのChartArea(グラフ表示エリア)にグラフを表示し、どこのLegend(凡例表示エリア)に凡例を表示するかを指定できるようになっている点にご注意下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
//--------------------------------------------------- //折れ線グラフのデータ作成 //--------------------------------------------------- double[] xs = Enumerable.Range(0, 100).Select(i => (double)i).ToArray(); double[] ys1 = Enumerable.Range(0, 100).Select(i => Math.Sin(i * 0.1)).ToArray(); double[] ys2 = Enumerable.Range(0, 100).Select(i => 2 + Math.Cos(i * 0.1)).ToArray(); double[] ys3 = Enumerable.Range(0, 100).Select(i => 4 + Math.Sin(i * 0.1) * Math.Cos(i * 0.1)).ToArray(); //--------------------------------------------------- // チャート全体の設定 //--------------------------------------------------- //タイトル、Series、凡例をクリア //初期状態としてChartAreaとSeriesが1つだけ登録されているが、複数のグラフを描画する際の //ループ処理が面倒なので事前にクリアしておく uxChart11.Titles.Clear(); uxChart1.Series.Clear(); uxChart1.Legends.Clear(); uxChart1.ChartAreas.Clear(); //チャートエリアを追加(名前は必須) uxChart1.ChartAreas.Add("Area1"); uxChart1.ChartAreas.Add("Area2"); //タイトルを設定 uxChart1.Titles.Add(new Title("タイトル1") { DockedToChartArea = "Area1",IsDockedInsideChartArea = false }); uxChart1.Titles.Add(new Title("タイトル2") { DockedToChartArea = "Area2", IsDockedInsideChartArea = false }); //凡例の表示エリアを登録 uxChart1.Legends.Add(new Legend("Area1") { DockedToChartArea = "Area1", IsDockedInsideChartArea = false }); uxChart1.Legends.Add(new Legend("Area2") { DockedToChartArea = "Area2", IsDockedInsideChartArea = false }); //--------------------------------------------------- // 折れ線グラフの描画 //--------------------------------------------------- //=========== 1つ目の線グラフを描画 ================ //Seriesを追加 var seri1 = new Series("凡例1") { ChartType = SeriesChartType.Line,ChartArea="Area1",Legend="Area1" }; //Seriesを生成 Enumerable.Range(0, ys1.Length).Select(i => seri1.Points.AddXY(xs[i], ys1[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri1); //=========== 2つ目の線グラフを描画 ================ //Seriesを追加 var seri2 = new Series("凡例2") { ChartType = SeriesChartType.Line, ChartArea = "Area1", Legend = "Area1" }; //Seriesを生成 Enumerable.Range(0, ys2.Length).Select(i => seri2.Points.AddXY(xs[i], ys2[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri2); //=========== 3つ目の線グラフを描画 ================ //Seriesを追加 var seri3 = new Series("凡例3") { ChartType = SeriesChartType.Line, ChartArea = "Area2", Legend = "Area2" }; //Seriesを生成 Enumerable.Range(0, ys3.Length).Select(i => seri3.Points.AddXY(xs[i], ys3[i])).ToArray(); //ChartにSeriesを登録 uxChart1.Series.Add(seri3); |
まとめ
今回は Visual Studio のWindowsFormに標準搭載されているMSChart について、その構造とサンプルを交えた使い方について解説しました。
NuGetでインストールすることなく、参照設定だけで使えること、線グラフの表示が高速であること、3Dグラフが書けることがMSChartのメリットです。
クラス構造は少し特殊ですが、数多くの種類のグラフに対応しているので、特にビジネス用途では重宝すると思います。
WPFから使う場合、WindowsFormsHost を経由して利用する必要はありますが、XAMLに数行記述するだけでWindowsFormと同じように使えるので、是非挑戦してみて下さい。