Reactでchartjsを使ったときにY軸のMAX値の設定ではまった話

## はじめに

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
```

## データの準備

とりあえず適当に準備する。

```js:App.js
    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）を入れておく。
後でレンダー時に割合表示なら書き換える。

```js:App.js
    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`コンポーネントに渡せばグラフが描画できる。
データとオプションは実数表示か割合表示かを変更するように関数を呼んで生成

```js:App.js
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`で値だけコピーして、そちらを加工する。

割合表示時は小数点以下第二位を四捨五入する。

```js:App.js
  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`だけ変更する。

```js:App.js
  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軸の最大値は自動で調整される。

![2020-05-28_20h46_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619552/d003c758-cd74-0626-762a-e759c09490df.png)

### 割合表示時

`scales.yAxes[0].ticks.max`を設定していないので、Y軸の最大値は自動で調整される。

しかし四捨五入しているので、全部加算した時に100を超えることがあるので、Y軸の最大値が100ではなく120となってしまう。

なので、割合表示時だけY軸の最大値を100にしたい。

![2020-05-28_20h49_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619552/f50d2846-75f0-5ab5-8d58-ed8364a92d06.png)

## this.changeOptionsに追記

割合表示の時だけ`options.scales.yAxes[0].ticks.max = 100;`を追記して、Y軸の最大値を100にしてみる。

```js:App.js
  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になっている！！

![2020-05-28_20h54_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619552/97cf78e8-9e1c-3163-409e-196421e12335.png)

しかし、もう一度変更ボタン押して、実数表示に戻すと・・・

![2020-05-28_20h55_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619552/ee33bcd8-9ec5-8e46-eecf-170132ba66c9.png)

Y軸の最大値が100のまま戻らなくなった・・

`Bar`コンポーネントに一度渡したオプションが残るっぽいです。

## 実数表示時にも最大値を入れてみる

実数表示時にも`options.scales.yAxes[0].ticks.max`を書き換えれば問題とおもったのでやろうとしたのですが、
合計値がどれくらいの大きさになるかわからない場合にすべてをif文とかで書くのも厳しいし、そもそもグラフのY軸のメモリもいい感じに自動で調整されるので、そのあたりが無理と気づく・・・

## で結果どう対応したのか・・・

`round`で四捨五入しているところを`floor`で切り捨ててとりあえず対応した・・・
合計値が100を超えないのでY軸の最大値も自動調整のままでいけた。

微妙ですが、見た目もそんなにおかしくないような気がするのでまあいいでしょう。（ダメかもしれない・・・）

```js:App.js
dataset.datasets[i].data[j] = _.floor(
  (dataset.datasets[i].data[j] / sum) * 100, 1 );
```

[コメントより追記]

`undefined`を設定すれば、最大値の設定が消えるみたいです。
なので、こちらがベストプラクティスだと思います。

```js:App.js
    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管理でグラフ描画を切り替えるときにはまった話を書きました。

~~結局、納得できる解決はできていないので、こうしたらいいんじゃない？とかありましたらコメントお願いします！！~~

解決はできたが、もっとこうしたらいいんじゃない？とかありましたらコメントお願いします！！