I am trying to use ChartJS, and make it possible for my labels to be editable updating my data live. Meaning all the labels on the x-axis and the value labels above the chart bar will be editable live changing the data for the chart.
The problem I am facing here, is that the state responsible for the position of inputs responsible for the value of bars is "one step behind". I have to use getDatasetMeta to access the metadata of the chart, where I get the positions and sizes of each bars. Based on the height of each bar, I calculate the position of the label. The problem is, that when looping through the metadata, I always get back the old value, until I change the next input or change my code. In the code, you can see uncommented logs - the one logging the metadata prints the correct values, however, the one pointing to a particular value meta[0].y returns the old one. This is super weird. I tried setting metadata as state variable and using [...] to get the copy of the object, and also using multiple useEffects. Nothing of these worked. Any help? Thank you very much.
import { useState, useEffect } from "react";
import { useImmer } from "use-immer";
import React, { MouseEvent, useRef } from "react";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
Plugin,
} from "chart.js";
import {
Chart,
getDatasetAtEvent,
getElementAtEvent,
getElementsAtEvent,
} from "react-chartjs-2";
ChartJS.register(
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip
);
export const options = {
responsive: true,
plugins: {
legend: {
position: "top" as const,
},
title: {
display: true,
text: "Chart.js Bar Chart",
},
datalabels: {
color: "black",
font: {
weight: "bold",
},
align: "top",
anchor: "end",
display: true,
},
},
};
const ChartRender = () => {
const [data, setData] = useImmer({
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "Dataset 1",
data: [50, 150, 600, 500, 430, 600, 200],
backgroundColor: "rgba(255, 99, 132, 0.5)",
},
],
});
const [wasDataUpdated, setWasDataUpdated] = useState(false);
const [nothingFirstElDifference, setNothingFirstElDifference] = useState(0);
const [elDifference, setElDifference] = useState(0);
const [valueYPosList, setValueYPosList] = useState([0]);
const chartRef = useRef<ChartJS>(null);
function changeFirstValue(value, index) {
setData((draft) => {
draft.datasets[0].data[index] = value;
});
setWasDataUpdated(!wasDataUpdated);
}
const executeAfterRender = () => {
let heightValuesArray = [];
const chart = chartRef.current;
if (!chart) return;
let height = 261;
let meta = chart.getDatasetMeta(0)["data"];
setNothingFirstElDifference(meta[0].x * 0.50059774);
setElDifference(meta[2].x - meta[1].x);
for (let i = 0; i < meta.length; i++) {
heightValuesArray.push(height - meta[i].y);
console.log(meta);
console.log(meta[0].y);
}
setValueYPosList(heightValuesArray);
setWasDataUpdated(!wasDataUpdated);
// console.log("executeAfterRander");
};
// console.log(valueYPosList);
useEffect(() => {
executeAfterRender();
}, [data]);
useEffect(() => {
if (wasDataUpdated) {
executeAfterRender();
}
}, [wasDataUpdated]);
// console.log(valueYPosList);
return (
<div className="relative h-full w-full">
<div className="relative">
<Chart ref={chartRef} type="bar" options={options} data={data} />
{data["labels"].map((month, index) => {
const width = elDifference;
const leftPosition =
index === 0
? nothingFirstElDifference
: nothingFirstElDifference + elDifference * index;
return (
<div key={index}>
<input
type="text"
value={month}
className="text-xs absolute text-center"
style={{
left: `${leftPosition}px`,
width: `${width}px`,
}}
/>
</div>
);
})}
{valueYPosList.map((yPos, index) => {
const width = elDifference;
const leftPosition =
index === 0
? nothingFirstElDifference
: nothingFirstElDifference + elDifference * index;
return (
<input
key={`value-${index}`}
className="text-xs absolute text-center mb-1 bg-transparent"
value={data["datasets"][0].data[index]}
onChange={(e) => {
const newValue = e.target.value;
changeFirstValue(newValue, index);
}}
style={{
bottom: `${yPos}px`,
left: `${leftPosition}px`,
width: `${width}px`,
}}
/>
);
})}
</div>
</div>
);
};
export default ChartRender;