I have a React project that utilizes Chart JS to plot charts based on data.
I have a drop down list selector to select which dataset to draw from. However, after I switch to another dataset and then switch back to the initial rendered dataset, it does not appear to work anymore. Here is my code:
import React from 'react';
import { Line } from 'react-chartjs-2';
import { chartData } from './Search.js';
import { xLabels } from './Search.js';
import { allEpsData } from './Search.js';
let chartDataSeason = [];
let xLabelsSeason = [];
export class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
selectValue: '',
seasonSelected: false,
chartIt: {
labels: xLabels,
datasets: [
{
label: 'Rating',
data: chartData,
fill: false,
borderColor: '#00B4CC',
},
],
},
chartItSeason: {
labels: xLabelsSeason,
datasets: [
{
label: 'Rating',
data: chartDataSeason,
fill: false,
borderColor: '#00B4CC',
},
],
},
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
if (e.target.value === 'All Seasons') {
this.setState({
seasonSelected: false,
chartIt: {
labels: xLabels,
datasets: [
{
label: 'Rating',
data: chartData,
fill: false,
borderColor: '#00B4CC',
},
],
},
});
console.log(chartData);
} else {
chartDataSeason = [];
xLabelsSeason = [];
let seasonNum = e.target.value.slice(6);
for (let i = 0; i < allEpsData[seasonNum - 1].length; i++) {
chartDataSeason.push(allEpsData[seasonNum - 1][i].imdbRating);
xLabelsSeason.push(
`s${seasonNum}e${allEpsData[seasonNum - 1][i].Episode} "${
allEpsData[seasonNum - 1][i].Title
}"`
);
}
this.setState({
selectValue: e.target.value,
seasonSelected: true,
chartItSeason: {
labels: xLabelsSeason,
datasets: [
{
label: 'Rating',
data: chartDataSeason,
fill: false,
borderColor: '#00B4CC',
},
],
},
});
}
// console.log(chartDataSeason)
}
render() {
let seasons = [];
for (let i = 0; i < allEpsData.length; i++) {
seasons.push(i);
}
if (this.state.seasonSelected === false) {
return (
<div>
<select // selector
className="select"
value={this.state.selectValue}
onChange={this.handleChange}
>
<option>All Seasons</option>
{seasons.map((el) => {
return <option key={el}>season {el + 1}</option>;
})}
</select>
<div className="line-container">
<Line
data={this.state.chartIt}
width={600}
height={400}
options={{
maintainAspectRatio: false,
scales: {
xAxes: [
{
ticks: {
display: true, //this will remove only the label
},
gridLines: {
show: false,
display: false,
},
},
],
yAxes: [
{
gridLines: {
show: false,
drawBorder: false,
},
},
],
},
animation: {
duration: 200,
easing: 'easeInOutQuart',
},
tooltips: {
enabled: true,
},
}}
/>
</div>
<div className="seasons-graph-container"></div>
</div>
);
} else {
return (
<div>
<select
className="select"
value={this.state.selectValue}
onChange={this.handleChange}
>
<option>All Seasons</option>
{seasons.map((el) => {
return <option key={el}>season {el + 1}</option>;
})}
</select>
<div className="line-container">
<Line
data={this.state.chartItSeason}
width={600}
height={400}
options={{
maintainAspectRatio: false,
scales: {
xAxes: [
{
ticks: {
display: false, //this will remove only the label
},
gridLines: {
show: false,
display: false,
},
},
],
yAxes: [
{
gridLines: {
show: false,
drawBorder: false,
},
},
],
},
animation: {
duration: 200,
easing: 'easeInOutQuart',
},
tooltips: {
enabled: true,
},
}}
/>
</div>
<div className="seasons-graph-container"></div>
</div>
);
}
}
}
the problem lies with chartData, it is not getting re-rendered correctly -- all the data associated with it has been replaced with the data of the last menu item. However oddly enough, xLabels does get rendered properly. Nothing is changing the value of chartData or xLabels but only xLabels gets correctly updated. Is there anything that I'm missing here? Apologies in advance for the spaghetti code, I am still learning React!
Related
Is there any way to get the visible values on a chart after zooming or panning, using react-chartjs-2? I found a solution without the react version, but I'm not sure how to reference the "chart object" in react. I tried the solution as is, but it doesn't display the visible values, but rather keeps increasing the height of my charts (stretches downwards).
So what would the react solution look like?
Here's my attempt with the linked solution
options:
const chartStyle = {
options: {
animation: false,
maintainAspectRatio: true,
responsive: true,
plugins: {
zoom: {
zoom: {
wheel: {
enabled: true,
modifierKey: "shift",
},
pinch: {
enabled: true,
},
enabled: true,
drag: true,
mode: "x",
onZoomComplete: getVisibleValues,
},
pan: {
enabled: true,
mode: "x",
speed: 2,
onPanComplete: getVisibleValues,
},
mode: "xy",
},
legend: {
display: false,
},
},
scales: {
y: {
type: "linear",
display: "true",
position: "left",
grid: {
drawBorder: true,
color: "#000000",
},
ticks: {
beginAtZero: true,
color: "#000000",
},
},
x: {
max: 9,
grid: {
drawBorder: true,
color: "#00000",
},
ticks: {
beginAtZero: false,
color: "#000000",
},
},
},
},
};
function getVisibleValues({ chart }) {
let x = chart.scales["x-axis-0"];
document.getElementById("vv").innerText = JSON.stringify(
chart.data.datasets[0].data.slice(x.minIndex, x.maxIndex + 1)
);
}
export default chartStyle;
Here's my attempt at accessing the chart instance with useRef(), but ref.scales is undefined:
const SingleChart = ({ chosenDevice, variable }) => {
const [chartData, setChartData] = useState({
labels: [],
datasets: [
{
label: variable,
data: [],
fill: false,
backgroundColor: "rgb(0,0,0)",
borderColor: "rgba(0,0,0, 0.8)",
color: "rgb(0,0,0)",
pointRadius: 4,
yAxisID: "y",
},
],
});
const [apiData, setApiData] = useState({});
const processEnv = process.env;
// USEREF ATTEMPT
const ref = useRef();
console.log(ref.scales);
let x = ref.scales["x"];
console.log("min index", x.minIndex);
console.log("max index", x.maxIndex);
useEffect(() => {
ChartComp.register(zoomPlugin);
var chartDataUrl = processEnv.REACT_APP_CHART_API + chosenDevice;
async function updatePPK() {
if (chosenDevice !== "") {
const dataResponse = await fetch(chartDataUrl);
var dataBody = await dataResponse.json();
for (var key in dataBody) {
dataBody[key].PV_POWER =
dataBody[key].PV_CURR * dataBody[key].PV_CURR;
}
setApiData(dataBody);
}
}
updatePPK();
}, [chosenDevice]);
useEffect(() => {
var chartLabels = [];
var chartValues = [];
console.log(apiData);
console.log("keys", Object.keys(apiData) !== 0);
if (Object.keys(apiData).length !== 0) {
for (var key in apiData) {
if (variable == "GEN_ON") {
chartValues.push(apiData[key][variable] == "ON" ? 1 : 0);
} else {
chartValues.push(apiData[key][variable]); // for every element in the passed in chartData object from hookMqtt, add the variable the graph requests (this.variable) to an array of data to be displayed
}
chartLabels.push(apiData[key]["TIME_COMMITED"].substring(11, 19));
}
setChartData({
labels: chartLabels,
datasets: [
{
label: variable,
data: chartValues,
fill: false,
backgroundColor: "rgb(0,0,0)",
borderColor: "rgba(0,0,0, 0.8)",
color: "rgb(0,0,0)",
pointRadius: 4,
yAxisID: "y",
},
],
});
console.log(chartData);
}
}, [chosenDevice, apiData]);
return (
<div>
<Line
ref={ref}
data={chartData}
options={variable != "GEN_ON" ? chartStyles.options : genStyle.options}
width={20}
height={10}
/>
</div>
);
};
SingleChart.propTypes = {
chartData: PropTypes.any,
chosenDevice: PropTypes.any,
setDate: PropTypes.any,
variable: PropTypes.any,
state: PropTypes.any,
};
export default SingleChart;
I am building a covid-19 tracker using react charts but when my data is displayed on the screen it does not come out be as expected.
This is my chart code
<div >
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data,
},
],
}}
options={options}
/>
)}
</div>
[enter image description here][1]
These are my option parameters
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 0,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,0");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "MM/DD/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
// Include a dollar sign in the ticks
callback: function (value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
My graph is coming out to be stretched and its height is increasing.
Looking for some help on why my options for the y-axis scale are being ignored. There are no errors in the console, however the y-axis minimum is still generated as the lowest value in the dataset, in this case 24.
The expected result would set the y-axis minimum to zero.
JS
Vue.component('bar-chart', {
extends: VueChartJs.Bar,
props: {
labels: Array,
datasets: Array,
options: Object
},
mounted () {
this.renderChart({
labels: this.labels,
datasets: this.datasets,
options: this.options
}, {responsive: true, maintainAspectRatio: false})
}
})
const home = {
template: '#home',
data: function () {
return {
labels: ['06/04/21', '06/11/21', '06/18/21', '06/25/21', '07/02/21'],
datasets: [
{
label: 'Count',
backgroundColor: '#05a7ff',
data: ['24', '24', '32', '32', '32']
/*trendlineLinear: {
style: "rgb(43 ,66 ,255, 0.3)",
lineStyle: "dotted",
width: 2
}*/
},
],
options: {
responsive: true,
legend: {
position: 'bottom',
},
plugins: {
datalabels: {},
},
scales: {
yAxes: [{
ticks: {
min: 0,
beginAtZero: true
}
}]
}
}
}
}
}
HTML
<template id="home">
<bar-chart :labels="labels" :datasets="datasets" :options="options"></bar-chart>
</template>
I've added a condition in componentDidMount to showcase data for either 1 or 2 datasets based on the pros recieved. However, I'm getting an error in the if condition. Here is BarChart component. Inside it, I've already imported Chart from 'chart.js'.
class BarChart1 extends Component {
constructor(props) {
super(props);
}
chartRef = React.createRef();
componentDidMount() {
const myChartRef = this.chartRef.current.getContext("2d");
let dataCheck = null;
if(this.props.data2){
let dataCheck = {
labels: this.props.labels,
datasets: [
{
label: this.props.label1,
backgroundColor: 'rgba(26,179,148,0.5)',
borderColor: "rgba(26,179,148,0.7)",
data: this.props.data1
}, {
label: this.props.label2,
backgroundColor: 'rgba(220, 220, 220, 0.5)',
pointBorderColor: "#fff",
data: this.props.data2
}
]
}
}
else {
let dataCheck = {
labels: this.props.labels,
datasets: [
{
label: this.props.label1,
backgroundColor: 'rgba(26,179,148,0.5)',
borderColor: "rgba(26,179,148,0.7)",
data: this.props.data1
}
]
}
}
new Chart(myChartRef, {
type: 'bar',
data: {dataCheck},
options: {
responsive: true,
maintainAspectRatio: false,
legend: {
display: true,
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: false
}
}],
xAxes: [{
gridlines: {
display: false
},
ticks: {
fontSize: 9
}
}]
}
},
plugins: [{
beforeInit: function(chart) {
chart.data.labels.forEach(function(e, i, a) {
if (/\n/.test(e)) {
a[i] = e.split(/\n/);
}
});
}
}]
})
}
render() {
return <canvas ref={this.chartRef} />
}
}
I've edited my question. I'm now getting a blank chart without the error.
There are two errors in your code.
Your have to end the variable declaration with ; instead of ,
You have to declare datacheck outside the if else block unless you want it undefined
componentDidMount() {
...
let dataCheck;
if(this.props.data2){
dataCheck = {
...
}
}
else {
dataCheck = {
...
}
}
}
I am creating a webapp in react that shows data based on Economic, social, and political indicators. Since the data is too big to render on to one single chart (a nightmare for mobile UI) I've decided to split the data into 4 objects in an array. I am trying to find a way to loop through that array. Here is the code
import React from 'react';
import { Bar } from 'react-chartjs-2';
import ChartDataSource from 'chartjs-plugin-datasource';
const config = {
data: {
datasets: [
{
backgroundColor: 'rgba(255,99,132,0.2)',
borderColor: 'rgba(255,99,132,1)',
borderWidth: 1,
hoverBackgroundColor: 'rgba(255,99,132,0.4)',
hoverBorderColor: 'rgba(255,99,132,1)',
},
],
},
options: {
maintainAspectRatio: true,
plugins: {
datasource: {
type: 'sheet',
url: 'GDP1.xlsx', {/*<----- This is where the array gets looped*/}
rowMapping: 'index',
},
},
title: {
display: true,
text: 'GDP per country',
},
scales: {
xAxes: [
{
scaleLabel: {
display: true,
labelString: 'Countries',
},
},
],
yAxes: [
{
id: '2018',
gridLines: {
drawOnChartArea: false,
},
scaleLabel: {
display: true,
labelString: 'GDP',
},
},
],
},
},
};
function BarChart() {
const graphslist = [ {/* Ths is the array i want to loop through*/}
{ url: 'GDP1.xlsx' },
{ url: 'GDP2.xlsx' },
{ url: 'GDP3.xlsx' },
{ url: 'GDP4.xlsx' },
];
return (
<div>
<h2>GDP per country</h2>
<Bar
data={config.data}
options={config.options}
width={100}
height={100}
plugins={[ChartDataSource]}
/>
</div>
);
}
export default BarChart;
I've been trying to use the .map function so config.options.plugins.url loops over the 4 objects in the array therefore rendering 4 charts but I keep getting unexpected token. Any help is welcome
you can try this without the plugins.
const theDatasets= (()=>{
let arr = []
for (let i = 0; i < graphslist .length; i++) {
arr.push(
{
data: graphslist [i],
backgroundColor: 'rgba(255,99,132,0.2)',
borderColor: 'rgba(255,99,132,1)',
borderWidth: 1,
hoverBackgroundColor: 'rgba(255,99,132,0.4)',
hoverBorderColor: 'rgba(255,99,132,1)',
label: graphslist [i],
}
)
}
return arr
})()
on the render
<Bar
data={theDatasets}
options={config.options} // your option without plugins
width={100}
height={100}
/>