charts_flutter の使い方
日本語の情報がびっくりするぐらいなかったので調べた。
charts_flutter
Material Design data visualization library written natively in Dart.
Flutterでマテリアルデザインなチャートを表示するためのライブラリ。Google謹製。
flutter_chartsという似た名前のライブラリがあるので注意。
samples
この辺にある。Galleryを見て使いたいやつを決めると良い。
usage
precondition
flutter --version Flutter 1.12.13+hotfix.9 • channel stable • https://github.com/flutter/flutter.git Framework • revision f139b11009 (3 weeks ago) • 2020-03-30 13:57:30 -0700 Engine • revision af51afceb8 Tools • Dart 2.7.2
charts_flutter: v0.9.0
サンプルコード
このサンプルコードを使って説明していく。
このサンプルでは、以下のようなシンプルなBarChartを表示している。
コードは以下の通り。
class SimpleBarChart extends StatelessWidget { final List<charts.Series> seriesList; final bool animate; SimpleBarChart(this.seriesList, {this.animate}); /// Creates a [BarChart] with sample data and no transition. factory SimpleBarChart.withSampleData() { return new SimpleBarChart( _createSampleData(), // Disable animations for image tests. animate: false, ); } @override Widget build(BuildContext context) { return new charts.BarChart( seriesList, animate: animate, ); } /// Create one series with sample hard coded data. static List<charts.Series<OrdinalSales, String>> _createSampleData() { final data = [ new OrdinalSales('2014', 5), new OrdinalSales('2015', 25), new OrdinalSales('2016', 100), new OrdinalSales('2017', 75), ]; return [ new charts.Series<OrdinalSales, String>( id: 'Sales', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (OrdinalSales sales, _) => sales.year, measureFn: (OrdinalSales sales, _) => sales.sales, data: data, ) ]; } } /// Sample ordinal data type. class OrdinalSales { final String year; final int sales; OrdinalSales(this.year, this.sales); }
順を追って説明する。
まず、このクラスはStatelessWidgetを継承しており、buildメソッドでcharts.BarChartを呼んで得られるウィジェットを返している。
@override
Widget build(BuildContext context) {
return new charts.BarChart(
seriesList,
animate: animate,
);
}
chartsはcharts_flutterをインポートするときのnamespaceのようなもの。
BarChartクラスはチャートウィジェットの一種。
BarChartのコンストラクタはList<common.Series<dynamic, String>> seriesListを必須とし、それ以外はOptional named parameterとして受け取る。
animate: animateは単純に表示時にアニメーションするか、といった情報を表すのでここでは深く追求しない。
seriesListはList<charts.Series>オブジェクト。この例では、factoryメソッドの中身で初期化されており、
実態はstatic List<charts.Series<OrdinalSales, String>> _createSampleData()というstaticメソッドの返り値が格納されている。
charts.Seriesについては後ほど解説する。
OrdinalSalesはチャートの表示対象となる簡単なデータクラス。
/// Sample ordinal data type. class OrdinalSales { final String year; final int sales; OrdinalSales(this.year, this.sales); }
結果として、シンプルなチャートを表示するためには、List<charts.Series>を用意して、チャートウィジェットのコンストラクタに渡してやるだけで良い。
Series
さて、これでチャートが表示できるようになったが、ここでじゃあSeriesって何なのよということになる。
Seriesとは、チャートを表示するためのデータセットのことを指す。
What is a series? A series is a set of data, for example a line graph or one set of columns. All data plotted on a chart comes from the series object. The series object has the structure:
charts_flutterでは、Seriesクラスは以下のように定義されている。
class Series<T, D> { final String id; ... final List<T> data; ... /// Computed property on series. /// /// If the [index] argument is `null`, the accessor is asked to provide a /// property of [series] as a whole. Accessors are not required to support /// such usage. /// /// Otherwise, [index] must be a valid subscript into a list of `series.length`. typedef AccessorFn<R> = R Function(int index); typedef TypedAccessorFn<T, R> = R Function(T datum, int index); ... factory Series( {@required String id, @required List<T> data, @required TypedAccessorFn<T, D> domainFn, @required TypedAccessorFn<T, num> measureFn, ...
クラス定義を見ればわかるが、<T, D>の二つの型を取るGeneric class。
Seriesはfactoryメソッドで生成する。
引数として必要なのは、以下の通り。
- 識別子の
id - Seriesの生成に使う
List<T> data domainFnmeasureFn
domainFn と measureFn
これら二つはtypedefで定義されている通り、T, intを引数に取り、Rを返す関数のこと。
/// Computed property on series. /// /// If the [index] argument is `null`, the accessor is asked to provide a /// property of [series] as a whole. Accessors are not required to support /// such usage. /// /// Otherwise, [index] must be a valid subscript into a list of `series.length`. typedef AccessorFn<R> = R Function(int index); typedef TypedAccessorFn<T, R> = R Function(T datum, int index);
Tはdatumという名前がついているので、データのこと。datumはdataの単数形を意味する。
intはそのdatumのインデックスを指す。
RはTypedAccessorFnの二つ目のgeneric typeで、domainFnの場合はD、measureFnはnumを指している。
つまり、 domainFnは D 型を返し、 measureFn は num型を返している。
この前提を踏まえてList<charts.Series>を作っているstaticメソッドの実装を見てみる。
Seriesの生成
/// Create one series with sample hard coded data. static List<charts.Series<OrdinalSales, String>> _createSampleData() { final data = [ new OrdinalSales('2014', 5), new OrdinalSales('2015', 25), new OrdinalSales('2016', 100), new OrdinalSales('2017', 75), ]; return [ new charts.Series<OrdinalSales, String>( id: 'Sales', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (OrdinalSales sales, _) => sales.year, measureFn: (OrdinalSales sales, _) => sales.sales, data: data, ) ]; }
まず、このstaticメソッドはList<charts.Series<OrdinalSales, String>>を返す。先ほど説明した通り、SeriesはGeneric ClassでT, Dを型に取る。この例ではT: OrdinalSales, D: String。
そのため、この時点でdomainFn, measureFn の型が決定する。
@required TypedAccessorFn<OrdinalSales, String> domainFn, @required TypedAccessorFn<OrdinalSales, num> measureFn,
そして、Seriesの生成に使うデータを初期化している。
final data = [ new OrdinalSales('2014', 5), new OrdinalSales('2015', 25), new OrdinalSales('2016', 100), new OrdinalSales('2017', 75), ];
この例では直接データを用意しているが、実際のプロダクトではAPIなどから取得したデータを使用する。
このデータはList<T>であることが求められる。つまり List<OrdinalSales> となる。
そして、データを用意したらSeriesを生成する。
return [ new charts.Series<OrdinalSales, String>( id: 'Sales', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (OrdinalSales sales, _) => sales.year, measureFn: (OrdinalSales sales, _) => sales.sales, data: data, ) ];
charts.Seriesのコンストラクタに id, colorFn, domainFn, measureFn, data を渡している。
さて、 domainFn, measureFn のそれぞれの関数の返り値は既に示した。以下に再掲する。
RはTypedAccessorFnの二つ目のgeneric typeで、domainFnの場合はD、measureFnはnumを指している。
charts.Seriesは渡されたdata の、 data.length の長さぶんループ処理を行う。そして、 measureFn, domainFn はループ処理が走るたびに呼ばれ、 domainFn の返り値をLabel、 measureFn の返り値をValueとするデータセットを生成する。
この例を見ると、domainFnではグラフのラベルに使用するyearを、measureFnでは実際にグラフのデータとして使うsalesを返している。
それぞれ第二引数はこの例では使わないので、_で捨てている。
この第二引数も先ほど登場した。
intはそのdatumのインデックスを指す。
これは何かと言うと、 data をdata.length の長さだけ domainFn, measureFn が呼ばれるときのインデックスのこと。
つまり今回は data.length == 4 なので、 0, 1, 2, 3の順に値が格納される。
使いどころとしては、例えば前回の値との差分グラフを作りたいときなどに使える。
measureFn: (OrdinalSales sales, index) => data[index].sales - data[index - 1].sales,
最終的に、Series を [] でラップしたものを返している。この例では表示すべきチャートのデータセットが一つ()なので length == 1 のList<Series>となっている。積み重ねたい場合は、その分だけ Series を生成してやれば良い。
おわりに
Seriesの概念がちょっとハマったけど、慣れるとそこそこ便利。ラベルの色を柔軟に変えづらいのがちょっとネックかも。
