LiveChartsのインストール方法や使い方については、こちらの記事に記載しましたが、使用頻度が高そうなグラフを簡単に描画できるよう関数化(メソッド化)してみました。
引数にLiveChartsのコントロールと描画したいデータを渡すと、簡単に描画してくれます。
必要最小限のプロパティを使っていますので、用途に応じてカスタマイズの上、お使いください。
グラフ(チャート)の種類
この関数(メソッド)を使って、次のグラフ(チャート)を描画できます。
サンプルプログラム
サンプルプログラムを作りましたので、そのソースコードを使って解説していきたいと思います。
基本的に、「【WPF】ScottPlotで楽々グラフ(チャート)描画!関数化してみました!」の記事で紹介したサンプルと同じで、グラフ描画部分をScottPlotからLiveChartsに置き換えたものとなります。
画面レイアウト
チャート1、チャート2のタブで分かれていて、合計8個のコントロールを張り付けています。
描画ボタンをクリックすると、全てのチャートが描画されるようになっているのは、ScottPlotのサンプルと同様です。
サンプルのダウンロード場所
サンプルプログラムのソースは下記からダウンロードできます。
今回は Nuget で取得した LiveCharts.Wpf.0.9.7 のライブラリも同梱していますので、任意のフォルダに解凍し、Visual Stduio 2019 でプロジェクトを開き、ビルドできると思います。
LiveChartsTestプロジェクト一式
関数(メソッド)の使い方
ScottPlotを関数化した時と仕様を合わせており、グラフ描画はDrowから始まるメソッド名になっています。
但し、「箱ひげ」「散布図」「レーダーチャート」「散布図と回帰直線」についてはLiveChartsでサポートされていないため、今回のサンプルでは実装していません。
チャート(グラフ)名 | 関数(メソッド)名 | 内容 |
---|---|---|
折れ線グラフ | DrawLine | 単一の折れ線グラフを描画 |
折れ線グラフ(複数) | DrawLine | 複数の折れ線グラフを描画 |
散布図 | DrawScatt | 散布図を描画 |
円グラフ | DrawPie | 円グラフを描画 |
縦棒グラフ | DrawColumn | 縦棒グラフを描画 |
横棒グラフ | DrawBar | 横棒グラフを描画 |
棒グラフ(グルーピング) | DrawGroupColumn | 複数の棒グラフをグルーピングして描画 |
積み上げグラフ | DrawStack | 積み上げグラフを描画 |
引数は3つあり、第一引数にコントロールを、第二引数にタイトルを、第三引数以降に描画データ(プロットデータ)を渡す仕様です。
但し、タイトルについては、今回の関数ではサポートしていません。
理由は、そもそも、LiveChartsにはグラフのタイトルを表示する機能が無いためです。
ScottPlotの関数と同じ仕様にするために、あえて第二引数を付けていますが、ここに何を指定してもタイトルは表示されませんのでご注意ください。
タイトルが必要な場合、XAML側で StackPanel 等を用いて、TextBlock と LiveChart を上下に重ねれば実現できますが、今回は後から手を加えやすい様に、出来るだけシンプルな関数にしたかったので、そこまではしていません。
実際の関数の呼び方ですが、コントロールに "uxChart1" という名前が付けられており、xs にX方向のデータ、ysにY方向のデータが格納されていると仮定した場合、次の様になります。
//グラフの描画
DrawLine(uxChart1,"折れ線グラフ",xs, ys);
第三引数のプロットデータについては、大きく分けて2つの形式があります。
折れ線や棒グラフ、円グラフ、散布図は引数にプロットデータの配列を渡す仕様ですが、複数の折れ線グラフ、グルーピングした棒グラフ、積み上げグラフは複数の項目をグルーピングする形になるため、タプルをリスト形式で渡すようにしています。
詳しくはソースコードを見て頂くとして、ここでは簡単にポイントだけ押さえておきたいと思います。
単独の折れ線グラフ、散布図、散布図と回帰直線、箱ひげ図、ヒストグラム
これらはプロットデータを配列データとして、1つまたは2つ引数に渡します。
例えば、折れ線グラフの場合は次の様になります。
DrawLine(uxChart1,"折れ線グラフ",xs, ys);
縦棒グラフ、横棒グラフ
縦棒グラフ、横棒グラフともプロットデータとして2つ渡すのは折れ線や散布図と同じですが、横軸がdouble型配列ではなく、文字列配列になっているところが異なる点です。
//棒グラフ
DrawBar(uxChart6, "横棒グラフ", labels, values);
円グラフ
円グラフは、ラベルと対応する値の配列を描画データとして渡します。
//円グラフのデータ
double[] values = { 778, 43, 283, 76, 184 };
string[] labels = { "C#", "JAVA", "Python", "F#", "PHP" };
//円グラフの描画
DrawPie(uxChart4, "円グラフ", labels, values);
複数の折れ線グラフ
タプルのリスト形式で描画データを指定します。
タプルで、凡例タイトル(legend)、X座標の配列、y座標の配列を1まとまりにして、リストに追加していき、そのリストを引数に渡します。
List<(string legend, double[] xs, double[] ys)> linedata = new List<(string legend, double[] xs, double[] ys)>();
linedata.Add(("legend1", xs, ys1));
linedata.Add(("legend2", xs, ys2));
linedata.Add(("legend3", xs, ys3));
DrawLine(uxChart2, "折れ線グラフ(複数)", linedata);
縦棒グラフ(グルーピング)、積み上げグラフ
これらは複数の値をグループ化して、まとめて表示するグラフであるため、タプルのリスト形式でプロットデータを設定します。
グループ化が少し分かりづらいかもしれませんが、項目名と内訳と考えてもらえれば分かりやすいかと思います。
LiveChartsをそのまま使う場合、値の渡し方が面倒なので、項目名と内訳という考え方でプロットデータをセットすれば良いようになっています。
//グルーピングのデータ作成
string[] group = { "コア数", "クロック数", "スレッド数", "キャッシュ", "価格" };
List<(string, double[])> datas = new List<(string, double[])>();
datas.Add(("Core-i7", new double[] { 8, 3.6, 16, 64, 70 }));
datas.Add(("Core-i5", new double[] { 6, 2.8, 12, 32, 50 }));
datas.Add(("Core-i3", new double[] { 4, 2.0, 8, 16, 30 }));
datas.Add(("Pentium", new double[] { 2, 1.8, 4, 8, 10 }));
datas.Add(("Celeron", new double[] { 2, 1.5, 4, 8, 5 }));
//棒グラフ(グルーピング)
DrawGroupColumn(uxChart1, "グループ", group, datas);
このサンプルソース上では group という変数名を使っていますが、明細(detail) とか、内訳(breakdown) という変数名の方が良かったかもしれません。
ソースコード
サンプルプロジェクトをダウンロード&解凍したあと、プロジェクトをVisual Studioで開くと、次のレイアウトが表示されます。
先ほども申しましたが、タブページが2つあって、それぞれに LiveCharts のコントロールを4つづつ張り付けています。
LiveChartsの仕様上、折れ線や棒グラフは CartesianChartコントロールを、円グラフは PieChartコントロールを使う必要があります。
従って、1ページ目の3つ目だけ、PieChart を張り付けています。
もっとも、PieChart を張り付けても、コントロールのイメージが表示されないので、何も張り付けられていないように見えてしまいますが、XAMLを見て頂くと、ちゃんと張り付けていることが分かると思います。
サンプルプログラムのXAMLのソースコードは次の様になっています。
<Window x:Class="LiveChartsTest.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:local="clr-namespace:LiveChartsTest"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="1320">
<Grid>
<TabControl>
<TabItem Header="チャート1">
<Grid>
<lvc:CartesianChart x:Name="uxChart1" Zoom="Xy" Height="315" Width="300" LegendLocation="Right" Margin="27,52,985,52" />
<lvc:CartesianChart x:Name="uxChart2" Zoom="Xy" Height="315" Width="300" LegendLocation="Right" Margin="347,52,665,52" />
<lvc:CartesianChart x:Name="uxChart3" Zoom="Xy" Height="315" Width="300" LegendLocation="Right" Margin="994,52,18,55" />
<lvc:PieChart x:Name="uxChart4" Height="315" Width="300" LegendLocation="Right" Hoverable="False" Margin="671,52,341,53" />
</Grid>
</TabItem>
<TabItem Header="チャート2">
<Grid>
<lvc:CartesianChart x:Name="uxChart5" HorizontalAlignment="Left" Margin="27,53,0,23" Width="300" />
<lvc:CartesianChart x:Name="uxChart6" HorizontalAlignment="Left" Margin="345,53,0,23" Width="300" />
<lvc:CartesianChart x:Name="uxChart7" HorizontalAlignment="Left" Margin="664,53,0,23" Width="300" />
<lvc:CartesianChart x:Name="uxChart8" HorizontalAlignment="Left" Margin="987,53,0,23" Width="300" />
</Grid>
</TabItem>
</TabControl>
<Button Content="描画" HorizontalAlignment="Left" Margin="1212,30,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
次はC#のソースコードを見てみましょう。
ソースを読み解く上で、LiveChartsの描画手順の全体像を知っていると理解が深まりますので、LiveChartsをもう少し詳しく知りたい方は、こちらの記事もご一読下さい。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
namespace LiveChartsTest
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Random rand = new Random();
//折れ線グラフのデータ作成
double[] xs = Enumerable.Range(0, 100).Select(i => (double)i).ToArray();
double[] ys = Enumerable.Range(0, 100).Select(i => (double)(i + rand.Next(0, 100) * ((rand.Next(1, 2) == 1) ? 1 : -1))).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();
//グラフの描画
DrawLine(uxChart1, "折れ線グラフ", xs, ys);
List<(string legend, double[] xs, double[] ys)> linedata = new List<(string legend, double[] xs, double[] ys)>();
linedata.Add(("legend1", xs, ys1));
linedata.Add(("legend2", xs, ys2));
linedata.Add(("legend3", xs, ys3));
DrawLine(uxChart2, "折れ線グラフ(複数)", linedata);
//散布図
DrawScatt(uxChart3, "散布図", xs, ys);
//円グラフ
double[] values = { 778, 43, 283, 76, 184 };
string[] labels = { "C#", "JAVA", "Python", "F#", "PHP" };
DrawPie(uxChart4, "円グラフ", labels, values);
//縦棒グラフ
DrawColumn(uxChart5, "縦棒グラフ", labels, values);
////横棒グラフ
DrawBar(uxChart6, "横棒グラフ", labels, values);
//グルーピングのデータ作成
string[] group = { "コア数", "クロック数", "スレッド数", "キャッシュ", "価格" };
List<(string, double[])> datas = new List<(string, double[])>();
datas.Add(("Core-i7", new double[] { 8, 3.6,16, 64, 70 }));
datas.Add(("Core-i5", new double[] { 6,2.8, 12, 32, 50 }));
datas.Add(("Core-i3", new double[] { 4,2.0, 8,16, 30 }));
datas.Add(("Pentium", new double[] { 2,1.8, 4, 8, 10 }));
datas.Add(("Celeron", new double[] { 2, 1,5, 4, 8, 5 }));
////棒グラフ(グルーピング)
DrawGroupColumn(uxChart7, "グループ", group, datas);
//積み上げグラフ
DrawStack(uxChart8, "積み上げ", group, datas);
}
/// <summary>
/// 折れ線グラフ
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="xs"></param>
/// <param name="ys"></param>
private void DrawLine(CartesianChart chart, string title, double[] xs, double[] ys)
{
chart.Series.Clear();
chart.DisableAnimations = true;
chart.LegendLocation = LegendLocation.None;
LineSeries seri = new LineSeries();
seri.Values = new ChartValues<ObservablePoint>(Enumerable.Range(0, ys.Length).Select(i => new ObservablePoint(xs[i], ys[i])));
chart.Series.Add(seri);
}
private void DrawLine(CartesianChart chart, string title, List<(string legend, double[] xs, double[] ys)> datas)
{
chart.Series.Clear();
chart.DisableAnimations = true;
foreach (var data in datas)
{
LineSeries seri = new LineSeries();
seri.Title = data.legend;
seri.Values = new ChartValues<ObservablePoint>(Enumerable.Range(0, data.ys.Length).Select(i => new ObservablePoint(data.xs[i], data.ys[i])));
chart.Series.Add(seri);
}
}
/// <summary>
/// 散布図
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="xs"></param>
/// <param name="ys"></param>
private void DrawScatt(CartesianChart chart, string title, double[] xs, double[] ys)
{
chart.Series.Clear();
chart.DisableAnimations = true;
ScatterSeries seri = new ScatterSeries();
seri.Values = new ChartValues<ObservablePoint>(Enumerable.Range(0, ys.Length).Select(i => new ObservablePoint(xs[i], ys[i])));
chart.Series.Add(seri);
}
/// <summary>
/// 円グラフ
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="labels"></param>
/// <param name="values"></param>
private void DrawPie(PieChart chart, string title, string[] labels, double[] values)
{
chart.Series.Clear();
chart.DisableAnimations = true;
SeriesCollection collection = new SeriesCollection();
for (int n = 0; n < labels.Length; n++)
{
var seri = new PieSeries();
seri.Title = labels[n];
seri.DataLabels = true;
seri.Values = new ChartValues<double> { values[n] };
collection.Add(seri);
}
chart.Series = collection;
}
/// <summary>
/// 縦棒グラフ
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="labels"></param>
/// <param name="ys"></param>
private void DrawColumn(CartesianChart chart, string title, string[] labels, double[] ys)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.AxisX.Add(new Axis() { Labels = labels, Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
ColumnSeries seri = new ColumnSeries();
seri.Values = new ChartValues<double>(ys);
chart.Series.Add(seri);
}
/// <summary>
/// 横棒グラフ
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="labels"></param>
/// <param name="ys"></param>
private void DrawBar(CartesianChart chart, string title, string[] labels, double[] ys)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.AxisY.Add(new Axis() { Labels = labels, Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
RowSeries seri = new RowSeries();
seri.Values = new ChartValues<double>(ys);
chart.Series.Add(seri);
}
/// <summary>
/// 縦棒グラフ(グループ)
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="labels"></param>
/// <param name="values"></param>
private void DrawGroupColumn(CartesianChart chart, string title, string[] labels, List<(string legend, double[] ys)> values)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.LegendLocation = LegendLocation.Right;
chart.AxisX.Add(new Axis() { Labels = values.Select(i => i.legend).ToArray(), Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
for (int n = 0; n < values[0].ys.Length; n++)
{
ColumnSeries seri = new ColumnSeries();
seri.Values = new ChartValues<double>(Enumerable.Range(0, values.Count).Select(i => values[i].ys[n]).ToArray());
seri.Title = labels[n];
chart.Series.Add(seri);
}
}
/// <summary>
/// 積み上げグラフ
/// </summary>
/// <param name="chart"></param>
/// <param name="title"></param>
/// <param name="labels"></param>
/// <param name="values"></param>
private void DrawStack(CartesianChart chart, string title, string[] labels, List<(string legend, double[] ys)> values)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.LegendLocation = LegendLocation.Right;
chart.AxisX.Add(new Axis() { Labels = values.Select(i=>i.legend).ToArray(), Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
for (int n = 0; n < values[0].ys.Length; n++)
{
StackedColumnSeries seri = new StackedColumnSeries();
var plot = values[n].ys;
seri.Values = new ChartValues<double>(Enumerable.Range(0, values.Count).Select(i => values[i].ys[n]).ToArray());
seri.Title = labels[n];
seri.StackMode = StackMode.Values;
chart.Series.Add(seri);
}
}
}
}
ソースの解説(ポイント)
ソースコードを見て頂くと使い方は分かると思いますので、ポイントだけ解説しておきます。
LiveChartsの基本形は、次の様になります。
LineSeries seri = new LineSeries();
seri.Values = new ChartValues<ObservablePoint>(point_data);
chart.Series.Add(seri);
point_data は描画するプロットデータですが、いくつかのクラスが用意されており、グラフの種類によって使い分ける必要があります。
今回の場合はLineSeries(折れ線)を使っているので、ObservablePointというオブジェクトにXとYの値を格納することになります。
例えば、double型の配列として、xs と ys を用意したとすると次のようになります。
seri.Values = new ChartValues<ObservablePoint>(Enumerable.Range(0, ys.Length).Select(i => new ObservablePoint(xs[i], ys[i])));
皆さんの用途に合ったように修正して頂く事を想定し、それぞれの関数(メソッド)では、そのグラフ(チャート)を書く上で必要最小限の記述に留めています。
アニメーションのON/OFFについて
LiveCharts は他のチャートコントロールに比べて描画速度が遅いという弱点があります。
そこで、少しでも描画を速くするために、今回はアニメーションをOFFにしました。
具体的には、チャートコントロールの DisableAnimations プロパティに true を代入すると、アニメーションしなくなります。
プロットが1000件以下の場合は十分高速に描画してくれますので、大量データのプロットをしないのであれば、DisableAnimationsを記述している行を削除するか、false を代入してアニメーションをONにしても良いと思います。
ただ、アニメーションを最初に見た時は「おっ!」と思いましたが、そのヌルっとした動作が、人によっては「動作が重い」とか「うざい」と感じるかもしれませんので、そこは状況に応じてという事で。
棒グラフについて
棒グラフは ColumnSeries を使います。この時、横軸のラベルは AxisX プロパティに対して、 new Axis(){Labels = labels} を代入します。
labels は 表示したいラベルの配列です。
データはColumnSeries.Values メソッドに new ChartValues<double>(ys) を代入します。ys はそれぞれの棒に対応する値で、一次元配列で渡します。
ここまでの設定で描画を行う場合、グラフの横幅が十分に大きくないと、X軸のラベルが所々省略されてしまいます。省略させずに、全てのラベルを表示したい場合、Axis.Separator を使う必要があります。
具体的には次の様に記述することで、全てのラベルが表示されるようになります。
new Axis() { Labels = labels, Separator = new LiveCharts.Wpf.Separator() { Step = 1 } }
private void DrawColumn(CartesianChart chart, string title, string[] labels, double[] ys)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.AxisX.Add(new Axis() { Labels = labels, Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
ColumnSeries seri = new ColumnSeries();
seri.Values = new ChartValues<double>(ys);
chart.Series.Add(seri);
}
積み上げグラフについて
LiveChartsには積み上げグラフ専用メソッドが用意されています。
積み重ねたい分だけ、StackedColumnSeries を生成しては棒グラフの値をセットするという行為を繰り返せばOKです。
この時、StackedColumnSeriseのStackMode プロパティに StackMode.Values をセットすると、通常の積み上げグラフになります。StackMode.Percentage をセットすると、全体を100%にした積み上げグラフが描画できます。
X軸のラベルは、AxisX に対して new Axsi() を代入し、その際にX軸に表示したいラベルの配列を Labelsプロパティにセットします。
private void DrawStack(CartesianChart chart, string title, string[] labels, List<(string legend, double[] ys)> values)
{
chart.Series.Clear();
chart.AxisX.Clear();
chart.AxisY.Clear();
chart.DisableAnimations = true;
chart.LegendLocation = LegendLocation.Right;
chart.AxisX.Add(new Axis() { Labels = values.Select(i=>i.legend).ToArray(), Separator = new LiveCharts.Wpf.Separator() { Step = 1 } });
for (int n = 0; n < values[0].ys.Length; n++)
{
StackedColumnSeries seri = new StackedColumnSeries();
var plot = values[n].ys;
seri.Values = new ChartValues<double>(Enumerable.Range(0, values.Count).Select(i => values[i].ys[n]).ToArray());
seri.Title = labels[n];
seri.StackMode = StackMode.Values;
chart.Series.Add(seri);
}
}
まとめ
今回はよく使うグラフ(チャート)について、LiveChartsで簡単に描画するための関数(メソッド)について解説しました。
LiveChartはビジュアルに優れているため、大量データで高速にグラフを作成したい場合、有料版を購入するか、他のチャートコントロールを検討してみて下さい。
プロット数が1000件以下のデータであれば、十分快適に動作してくれます。
アニメーションについてはインパクトがありますが、人によって「動作が重い」と捉えられる可能性もあるので、そこは状況に応じて使い分けて下さい。
いすれにせよ、色合いが美しいので、アニメーションが無くても十分にビジュアルです。
コメント