はじめに
Reactでchart.jsを使って積み上げ棒グラフ表示したときに、Y軸のMAX値の設定ではまったことがあったので、まとめてみます。
使ったもの
- react(v16.13.1)
- react-chartjs-2(v2.9.0)
- chart.js(v2.9.3)
- lodash(v4.17.15)
作りたかったもの
- 積み上げ棒グラフ
- ボタンクリックで実数表示⇔割合表示を切り替える
- 実数表示のY軸の最大値はauto(kg)にしたい(単位は適当)
- 割合表示のY軸の最大値は100(%)にしたい
イメージはボタンクリックでstateで管理してるフラグを切り替えて、chart.js
のコンポーネントに渡すdata
とoptions
を変化させるイメージです。
プロジェクト作成
create-react-appでReactのプロジェクトを作成して、上記のとおり必要なパッケージをインストールします。(react以外)
create-react-app app
npm install --save react-chartjs-2 chart.js lodash
データの準備
とりあえず適当に準備する。
this.dataset = {
labels: ["A", "B", "C", "D", "E"],
datasets: [
{
label: "label1",
data: [36.1, 41.2, 50.4, 32.9, 29.4],
backgroundColor: "red",
},
{
label: "label2",
data: [51.9, 60.5, 45.6, 55.5, 75.5],
backgroundColor: "blue",
},
{
label: "label3",
data: [80.1, 88.2, 75.7, 69.4, 100.2],
backgroundColor: "green",
},
{
label: "label4",
data: [120.5, 110.5, 128.9, 130.2, 150.5],
backgroundColor: "purple",
},
{
label: "label5",
data: [80.5, 85.9, 77.7, 68.5, 60.9],
backgroundColor: "yellow",
},
],
};
オプションの準備
積み上げ棒グラフのオプションを準備する。
とりあえず、実数表示用にscales.yAxes[0].scaleLabel.labelString
に実数(kg)を入れておく。
後でレンダー時に割合表示なら書き換える。
this.options = {
scales: {
xAxes: [
{
stacked: true,
display: true,
scaleLabel: {
display: true,
},
},
],
yAxes: [
{
stacked: true,
display: true,
scaleLabel: {
display: true,
labelString: "実数(kg)",
},
ticks: {
min: 0,
},
},
],
},
maintainAspectRatio: false,
};
レンダー部分
データとオプションをBar
コンポーネントに渡せばグラフが描画できる。
データとオプションは実数表示か割合表示かを変更するように関数を呼んで生成
render() {
const data = this.changeDataSet();
const options = this.changeOptions();
console.log(options);
return (
<div>
<input type="button" value="変更" onClick={this.onClickButton} />
<Bar data={data} options={options} height={400} />
</div>
);
}
this.changeDataSet部分
this.state.isProb
で実数表示か割合表示かを切り替える。
lodash
のcloneDeep
で値だけコピーして、そちらを加工する。
割合表示時は小数点以下第二位を四捨五入する。
changeDataSet() {
let dataset = _.cloneDeep(this.dataset);
// 割合表示の時にデータを変更
let sum = 0;
if (this.state.isProb === true) {
for (let j = 0; j < dataset.datasets[0].data.length; j++) {
sum = 0;
for (let i = 0; i < dataset.datasets.length; i++) {
sum += dataset.datasets[i].data[j];
}
for (let i = 0; i < dataset.datasets.length; i++) {
if (sum > 0) {
dataset.datasets[i].data[j] = _.round(
(dataset.datasets[i].data[j] / sum) * 100, 1 );
} else {
dataset.datesets[i].data[j] = 0;
}
}
}
}
return dataset;
}
this.changeOptions部分
同じくthis.state.isProb
で実数表示か割合表示かを切り替える。
lodash
のcloneDeep
で値だけコピーして、そちらを加工する。
とりあえずscales.yAxes[0].scaleLabel.labelString
だけ変更する。
changeOptions(data) {
let options = _.cloneDeep(this.options);
if (this.state.isProb === true) {
options.scales.yAxes[0].scaleLabel.labelString = "割合(%)";
}
return options;
}
確認してみる
実数表示時(初期表示時)
scales.yAxes[0].ticks.max
を設定していないので、Y軸の最大値は自動で調整される。
割合表示時
scales.yAxes[0].ticks.max
を設定していないので、Y軸の最大値は自動で調整される。
しかし四捨五入しているので、全部加算した時に100を超えることがあるので、Y軸の最大値が100ではなく120となってしまう。
なので、割合表示時だけY軸の最大値を100にしたい。
this.changeOptionsに追記
割合表示の時だけoptions.scales.yAxes[0].ticks.max = 100;
を追記して、Y軸の最大値を100にしてみる。
changeOptions(data) {
let options = _.cloneDeep(this.options);
if (this.state.isProb === true) {
options.scales.yAxes[0].scaleLabel.labelString = "割合(%)";
options.scales.yAxes[0].ticks.max = 100;
}
return options;
}
再度確認してみる
割合表示時
うまくいった!!Y軸の最大値が100になっている!!
しかし、もう一度変更ボタン押して、実数表示に戻すと・・・
Y軸の最大値が100のまま戻らなくなった・・
Bar
コンポーネントに一度渡したオプションが残るっぽいです。
実数表示時にも最大値を入れてみる
実数表示時にもoptions.scales.yAxes[0].ticks.max
を書き換えれば問題とおもったのでやろうとしたのですが、
合計値がどれくらいの大きさになるかわからない場合にすべてをif文とかで書くのも厳しいし、そもそもグラフのY軸のメモリもいい感じに自動で調整されるので、そのあたりが無理と気づく・・・
で結果どう対応したのか・・・
round
で四捨五入しているところをfloor
で切り捨ててとりあえず対応した・・・
合計値が100を超えないのでY軸の最大値も自動調整のままでいけた。
微妙ですが、見た目もそんなにおかしくないような気がするのでまあいいでしょう。(ダメかもしれない・・・)
dataset.datasets[i].data[j] = _.floor(
(dataset.datasets[i].data[j] / sum) * 100, 1 );
[コメントより追記]
undefined
を設定すれば、最大値の設定が消えるみたいです。
なので、こちらがベストプラクティスだと思います。
if (this.state.isProb === true) {
options.scales.yAxes[0].scaleLabel.labelString = "割合(%)";
options.scales.yAxes[0].ticks.max = 100;
} else {
options.scales.yAxes[0].ticks.max = undefined;
}
まとめ
Reactでchart.jsを使うときに、state管理でグラフ描画を切り替えるときにはまった話を書きました。
結局、納得できる解決はできていないので、こうしたらいいんじゃない?とかありましたらコメントお願いします!!
解決はできたが、もっとこうしたらいいんじゃない?とかありましたらコメントお願いします!!