棒グラフをFlutterで実装したい
棒グラフを書こうとしてcharts_flutterにたどり着いた。
が、Null-Safetyに対応していないせいで使い物にならない。
(厳密には使えるが面倒くさくなるので使いたくない)
そうするとfl_chartsが代替でいいよとissueの中で紹介されていた。
が、サンプルを実行してみた記事ばかりで具体的に触れている日本語記事が1件もない。(探し方が悪いだけかも)
じゃあ俺が書くわこんちくしょう!!!!
アウトプットを残しておかないとパラメータ設定回りを絶対忘れると思った。
色んなとこで見かけるこんな感じの棒グラフを作るまでに苦労したことを書いていきます。
棒グラフの構造
まず棒グラフを実装するにあたり使うBarChartヴィジェットのコンストラクタ構造は次である。
デフォルト引数の2項目はアニメーション関連なので、今回は触れないものとする。
BarChart(
BarChartData data, // グラフの描画に当たり設定するパラメータ
{
Duration swapAnimationDuration = const Duration(milliseconds: 150),
Curve swapAnimationCurve = Curves.linear
}
)
BarChartData型を実際に指定していくわけだがこれは非常に項目が多いため、全体は公式を参照。今回利用した項目のみ解説する
BarChartData(
titlesData: FlTitlesData(), // グラフの縦横の項目名に関するパラメータ、 「上画像の競走馬名、0~20」
gridData: FlGridData(), // グリッド線に関するパラメータ、上画像の「背面破線」
borderData: FlAxisTitleData(), // グラフ周辺のボーダーに関するパラメータ、上画像の「0ライン」
axisTitleData: FlBorderData(), // Excelでいうとこの軸ラベルに関するパラメータ、上画像の「投票数」
barGroups: List<BarChartGroupData>[], // 表示するグラフのデータ、上画像の「グラフ部分」
)
各項目について全部書くと長くなりそうなので、特に調べてて時間かかったところを残しておく
fl_chartの描画処理について
分かり難いかもしれないが、fl_chartではグラフを描画する際、y軸最小値 < y < y軸最大値 の範囲を1刻み(デフォルト値)で描画処理が行われる。
分かりやすくforに直すと for(i = min+1 ; i < max; i++)
といったところだろうか。(今回のサンプルグラフだとmin=0, max=20である。)
下記図の1~5のように1行ずつ別の描画処理として扱われているわけだ。
※これは公式リファレンスに記載されているわけではなく描画処理から想定した仮設であるため理解の助けにする程度の認識でいてほしい。
グリッドの表示非表示制御
グリッド線を描画するFlGridDataは以下のようになっている
gridData: FlGridData(
checkToShowHorizontalLine: bool Function(double value), // trueが返された時だけグリッド線を表示する
getDrawingHorizontalLine: FlLine Function(double value), // グリッド線のスタイルを指定
),
引数として渡されるdouble型の値は現在の行番号yである。(1つ前の見出し参照)
そのため描画したい行番号のみtrueが返されるようにcheckToShowHorizontalLine
に渡す関数を作成すればよい。
左側の表示非表示
FlTitlesData(
leftTitles: SideTitles(
// 縦軸値の表示制御
checkToShowTitle: bool Function(double minValue, double maxValue, SideTitles sideTitles, double appliedInterval, double value);
),
),
無名関数に渡される引数はgridとは違い5項目ある。これらはこういった形になっている
引数名 | 説明 |
---|---|
minValue | その名の通り行の最小値 |
maxValue | その名の通り行の最大値 |
sideTitles | 恐らく自分自身を再帰呼び出ししている?詳細不明 |
appliedInterval | 描画処理を行う行間隔 |
value | 現在処理いている行番号 |
実際のコード
※注記:必要な部分のみ抜粋しているため、諸兄は上手く自分のコードに割り当ててほしい。
bottomLabelLists = [
'スペシャルウィーク',
'グラスワンダー',
'セイウンスカイ',
'キングヘイロー',
'エルコンドルパサー',
'タマモクロス',
'オグリキャップ',
]
...
BarChart(
BarChartData(
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (value) => const TextStyle(color: Color(0xff7589a2), fontWeight: FontWeight.bold, fontSize: 14),
margin: 32,
reservedSize: 150,
rotateAngle: 90,
getTitles: (value) {
return bottomLabelLists[value.toInt()];
},
),
leftTitles: SideTitles(
showTitles: true,
getTextStyles: (value) => const TextStyle(color: Color(0xff7589a2), fontWeight: FontWeight.bold, fontSize: 14),
margin: 32,
reservedSize: 14,
checkToShowTitle: (min, max, titles, interval, value) {
double boundary = (max + min) / 4;
return value % boundary == 0;
},
),
),
gridData: FlGridData(
show: true,
checkToShowHorizontalLine: (value) => value % 5 == 0,
getDrawingHorizontalLine: (value) => makeGridLine(value),
),
axisTitleData: FlAxisTitleData(
leftTitle: AxisTitle(
showTitle: true,
titleText: '投票数',
textStyle: TextStyle(color: Colors.black, fontSize: 15),
),
),
borderData: FlBorderData(
show: false,
),
barGroups: showingBarGroups,
),
),
...
FlLine makeGridLine(value) {
return FlLine(
color: const Color(0xff2c4260),
strokeWidth: 1,
dashArray: [5, 5], // 線5px空白5pxの破線指定
);
}