I'm relatively new to react and Javascript. I'm trying to create a Doughnut component with chartjs but after two days of trying different approaches, I don't succeed in producing the Doughnet that I want. The idea is to have a doughnut with the sum value in the middle. Pretty straight forward, I thought. Rendering the doughnut with the correct labels and values I managed to do pretty easily. The problem that is keeping me awake at night is the value in the middle. Here is the data that is passed via props:
[
{
"account_type": "basic",
"balance": 96548852,
"count": 1529
},
{
"account_type": "miner",
"balance": 34532244,
"count": 9466
},
{
"account_type": "validator",
"balance": 726277542,
"count": 135
},
{
"account_type": "community",
"balance": 710828589,
"count": 14
}
]
This data set I was planning to use for two different Doughnut charts. One would use account_type
and balance
, the other account_type
and count
.
In App.jsx
, the code look pretty straight forward. My api delivers the data, that's tested and confirmed. The problem is rendering the charts. Here is App.jsx
:
import React, { Component } from "react";
import axios from "axios";
import { Route, Routes } from "react-router-dom";
import DataPie from "./components/DataPie";
class App extends Component {
constructor() {
super();
this.state = {
dataSet: [],
};
}
componentDidMount() {
this.getDataSet();
}
getDataSet = () => {
axios
.get(`${process.env.REACT_APP_API_SERVICE_URL}/dataSet`)
.then((res) => {
this.setState({ dataSet: res.data });
})
.catch((err) => {
console.log(err);
});
};
render() {
return (
<div>
<section className="section">
<div className="container">
<div className="container is-max-desktop">
<Routes>
<Route
exact
path="/balances"
element={
<div className="columns">
<div className="column box">
<DataPie
dataSet={this.state.dataSet} /* same data set is passed */
labelElem='account_type' /* labels for donut*/
valueElem='balance' /* values for donut */
divider='1000000' /* center value divider */
suffix=' M' /* suffix for center value */
/>
</div>
<div className="column box">
<DataPie
dataSet={this.state.dataSet} /* same data set is passed */
labelElem='account_type' /* labels for donut */
valueElem='count' /* values for donut */
/>
</div>
</div>
}
/>
</div>
</div>
</section>
</div>
);
}
}
export default App;
And the what I've been meddling with for two days now, the component:
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
ChartJS.register(ArcElement, Tooltip, Legend);
const DataPie = (props) => {
const [data, setData] = useState({});
const [plugins, setPlugins] = useState([]);
useEffect(() => {
let labels = [];
let values = [];
props.dataSet.forEach(element => {
labels.push(element[props.labelElem]);
values.push(element[props.valueElem]);
});
setData({
labels: labels,
datasets: [
{
data: values,
backgroundColor: [
"rgba(255, 99, 132, 0.8)",
"rgba(54, 162, 235, 0.8)",
"rgba(255, 206, 86, 0.8)",
"rgba(75, 192, 192, 0.8)",
"rgba(153, 102, 255, 0.8)",
"rgba(255, 159, 64, 0.8)"
],
borderColor: [
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)"
],
borderWidth: 1,
polyline: {
color: "gray",
labelColor: "gray",
formatter: (value) => `formatted ${value}`
}
}
]
});
let suffix = (props.suffix ? props.suffix : '' );
let divider = (props.divider ? parseInt(props.divider) : 1 );
let valueSum = 0;
props.dataSet.forEach(element => {
valueSum = valueSum + element[props.valueElem];
});
setPlugins([
{
beforeDraw: function (chart) {
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 160).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "top";
var text = Math.round(valueSum / divider).toString() + suffix,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}
]);
}, [props]);
const options = {
plugins: {
legend: {
display: false
}
}
};
return (
<Doughnut
data={data}
options={options}
plugins={plugins}
/>
);
}
DataPie.propTypes = {
dataSet: PropTypes.array.isRequired,
labelElem: PropTypes.string.isRequired,
valueElem: PropTypes.string.isRequired,
};
export default DataPie;
Problems started to occur when I added the plugins part of the Doughnut where I calculate the sum of all values in the chart and try to display them in the center of the doughnut. This code works, because I could display a static value. But when I try to add a calculated value, it never showed up on my chart.
At my last attempt I started using { useState, useEffect }
but now my Doughnut doesn't even render anymore with various errors like:
- Error: Too many re-renders. React limits the number of...
- Uncaught TypeError: Cannot read properties of undefined (reading 'map')
- The above error occurred in the <ForwardRef(ChartComponent)> component...
Another attempt where I did all the calculation within the component without using setState
and useEffect
, it looked like this:
const DataPie = (props) => {
const labels = [];
const values = [];
let valueSum = 0;
let suffix = (props.suffix ? props.suffix : '' );
let divider = (props.divider ? parseInt(props.divider) : 1 );
props.dataSet.forEach(element => {
labels.push(element[props.labelElem]);
values.push(element[props.valueElem]);
valueSum = valueSum + element[props.valueElem];
});
console.log(valueSum);
const data = [...] /* < labels and values where inserted here */
const options = {...}
const plugins = {...} /* < valueSum is inserted here */
...
The result of this code was a rendered doughnut with correct data in the pieces but the center value always resulted in 0
and 0 M
. I added a console log here to see what is happening with the values and I see two times two renders, so for each component instance two renders. The first value per component was always 0 and the second render was the correct value.
So please help my peanut brain understand where I'm screwing things up... Thanks in advance.