react-chartjs-2でrenderが複数回呼び出されるときに生じた不具合の対処

技術備忘録

【2021年10月追記】Chart.js 3系ではredrawを使わなくても値の変更を反映することができるようになりました。Chart.js3.x系でグラフ上のクリック位置に値をセットする方法

react-chartjs-2を使ってグラフを表示する際、APIとの連携などを行うとグラフが複数回呼ばれることになります。chartjsは複数回renderを行うと不具合が生じるらしく、render前にdestroy()を呼び出す必要があるそうです。(https://www.chartjs.org/docs/latest/developers/api.html#destroy

react-chartjs-2では下記のようにredrawを追加することで実現できました。

<LineChart data={data} redraw />

スポンサーリンク

複数回renderで生じた問題

以前の記事でクリックする位置に値をセットする方法を説明しましたが、複数のdatasetを使用して複数回のrenderが行われる際にうまく値がセットされないという不具合が生じてしまいました。

Chart.jsでクリック位置に値をセットする方法

function ChartInput(props){
    const chartReference = createRef()
    const data = () => {
        return {
            datasets: [
                {
                    type: 'bar',
                    label: 'data0',
                    data: props.data[0],
                    backgroundColor: 'rgba(51, 255, 153, 0.2)',
                    borderColor: 'rgba(51, 255, 153, 1)',
                    borderWidth: 2,
                },{
                    type: 'bar',
                    label: 'data1',
                    data: props.data[1],
                    backgroundColor: 'rgba(255, 99, 132, 0.2)',
                    borderColor: 'rgba(255, 99, 132, 1)',
                    borderWidth: 2,
                }
            ]
        }
    }
    const optionsChartInput = () => {
        return {
            labels: props.labels,
            scales: {
                xAxes: [{
                    stacked: true,
                    labels: props.labels
                }],
                yAxes: [{
                    ticks: {
                        max: 100,
                        min: -100,
                        stepSize: 100,
                        beginAtZero: true
                    },
                }]
            },
            onClick: function(evt) {
                let chartInstance = chartReference.current.chartInstance
                let x, y, dataIndex;
                x = chartInstance.scales["x-axis-0"].getValueForPixel(evt.clientX-chartInstance.canvas.getBoundingClientRect().left)
                y = chartInstance.scales["y-axis-0"].getValueForPixel(evt.clientY-chartInstance.canvas.getBoundingClientRect().top)
                // 対象の範囲内の時のみ値を変える
                if (x>=0 && x<props.labels.length){
                    if (y>0){ // 正の部分がクリックされた場合
                        dataIndex = 0
                        y = Math.floor(y)
                        y = y > chartInstance.scales["y-axis-0"].max ? chartInstance.scales["y-axis-0"].max : y
                    }else{  // 負の部分がクリックされた場合
                        dataIndex = 1
                        y = Math.floor(y)+1
                        y = y < chartInstance.scales["y-axis-0"].min ? chartInstance.scales["y-axis-0"].min : y
                    }
                    // chartInstanceを直接編集
                    chartInstance.data.datasets[dataIndex].data[x] = y
                    chartInstance.update()
                    props.setNewData(dataIndex, x, y) // 親側でpropsを変更させる
                }
            },
            responsive: true,
            maintainAspectRatio: false
        }
    }
    return (
        <Bar width={props.width} height={props.height} 
            ref={chartReference}
            data={data()} options={optionsChartInput()}/>
    )
}

このようにクリックされた位置のyの正負に応じてdatasetsのインデックスを切り替えて値をセットするという実装を行ったところ、下記のように、両方のデータセットに同じ値がセットされるという挙動になってしまいました。

// 修正後
<Bar redraw width={props.width} height={props.height} 
    ref={chartReference} 
    data={data()} options={optionsChartInput()} />
クリック位置に応じてdatasetsのインデックスを切り替えられた

参考ページ

react-chartjs state update error

コメント

タイトルとURLをコピーしました