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
domainFn
measureFn
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の概念がちょっとハマったけど、慣れるとそこそこ便利。ラベルの色を柔軟に変えづらいのがちょっとネックかも。