Chart JS will not re-render in React - javascript

I'm having trouble getting my Chart from Chart.js to update. I'm using React so it should re-render every time I modify my state, however it is not behaving the way I would like.
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) {
chartDataSeason = [];
xLabelsSeason = [];
let seasonNum = e.target.value.slice(6)
console.log(seasonNum);
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,
});
// 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: 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>
);
}
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>
);
}
}
}
So right now, chartIt displays a graph and that works fine. However in the handleChange(e) method, I am trying to reset the data the Chart.js calls from by first setting chartDataSeason = []; and then xLabelsSeason = [];.
I am able to console.log the data and it is all there, I am unable to get it to render in a new Line chart though.
Any help would be appreciated. Also any tips on better organizing my code would be helpful, I am pretty new to React and I realize there is probably a better way to do all of this.

When setState is called inside handleChange it will tell React to re-render the Graph component. There are some special cases where this would not happen (PureComponent or others) but as this is a plain component, it should call the render function. You can add a console.log there to confirm it.
However, even if render method is called, that doesn't mean that all the children components will be re-render too. React has a smart way to detect if a component in the tree needs to update or not, and it will only do it when necessary for performance reasons.
For this case in particular, there are a few options you can try:
The this.state.chartItSeason is the actual data passed to the Line component, and the one which should be updated. Because the component is updating nested properties, React is probably not detecting it like a prop change. Try to replace the object reference, for example, inside of handleChange:
this.setState({
selectValue: e.target.value,
seasonSelected: true,
chartItSeason: {
...this.state.chartItSeason
}
});
This will create a new object which Chart.js may consider as a new data when comparing if the props have changed.
You can always pass a key prop to any component that whenever it changes it will force the complete re-render of that component. That is almost always not the best approach and it should be avoided. You can read more about the use case for key in the official docs
Also, as a side note, the two top-level variables chartDataSeason and xLabelsSeason should probably be better moved to the state or as instance properties (e.g. this.chartDataSeason) to allow more than one component to be rendered at the same time

Related

How to show "loading..." text as a placeholder before my Apexcharts actually loads?

I spent a day figuring this out but was unavailable to find a solution. Please if anyone can help.
So I was making a cryptocurrency tracker in React. I managed to create a table displaying several currencies and also a column where I render Apexchart for various currencies based on the JSON data that's already saved with me in a javascript file i.e I'm not making any call to API to get the data. I already have static data with me. I just rendered the static data in the form of a table.
Now the problem is with the loading time of the page. As I need to render apexcharts for all the currencies(I'm displaying 100 in total), displaying them slows down the user experience.
To improve the user experience I want to add a placeholder text "loading..." and when the apexchart is done loading only then I want to display the chart.
Below is my Graph component that's responsible for loading my apexchart.
import Chart from 'react-apexcharts';
import { ChartData } from '../data/ChartData';
class Graph extends Component {
state = {
options: {
stroke: {
width: 1.7,
},
grid: {
show: false,
},
datalabels: {
enabled: false,
},
tooltip: {
enabled: false,
},
chart: {
animations: {
enabled: false,
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
yaxis: {
show: false,
labels: {
formatter: function () {
return '';
},
},
},
xaxis: {
labels: {
formatter: function () {
return '';
},
},
tooltip: {
enabled: false,
},
},
},
series: [
{
name: 'USD',
data: ChartData[this.props.idx].data,
},
],
};
render() {
return (
<div className="graph">
<div className="row">
<div className="mixed-chart">
<Chart
options={this.state.options}
series={this.state.series}
type="line"
width="200px"
height="100px"
/>
</div>
</div>
</div>
);
}
}
export default Graph;
![As you can see I have my Apexchart plotted. Now as it takes time to load the chart I want to add a placeholder text "loading" and when the chart loading completes I want to display the chart giving a better user experience]Screenshot
You just need to add the object nodata to the existing options object. The following is the object definition:
noData: {
text: "Loading...",
align: 'center',
verticalAlign: 'middle',
offsetX: 0,
offsetY: 0,
style: {
color: "#000000",
fontSize: '14px',
fontFamily: "Helvetica"
}
}
Refer to it in the documentation on below link.
https://apexcharts.com/docs/options/nodata/

How to clear/destroy Chart.js canvas when the chart is represented as a react JSX element

I am running into this issue with chart.js where I have a chart on a specific report. When I switch pages to another page that contains chartjs elements and switch back the chart is showing data labels on the charts data points as "x: number, y: number".
After reading a bit about this I believe it's because the canvas is not being correctly reset when I switch back to the original chart.
the examples of fixes using the clear() or destroy() command that I've found reference the charts canvas.
example: Destroy chart.js bar graph to redraw other graph in same <canvas>
However in react that's a bit more tricky to get to.
My question is, how can I clear the canvas before drawing my graph. Below is the chart component used to draw the graph.
cont Chart: FunctionComponent<Props> = ({data}) => {
const options = (): ChartOptions => ({
responsive: true,
maintainAspectRatio: false,
legend: {
display: false
},
tooltips: chartTooltips,
elements: chartElementsNoLines,
scales: {
xAxes: [
{
display: true,
id: 'x-axis-0',
ticks: {
suggestedMin: minMax.min,
suggestedMax: minMax.max,
maxTicksLimit: 7,
maxRotation: 0,
fontFamily: 'Roboto, sans-serif',
fontSize: 10,
autoSkip: true,
callback: value => currency(value, { precision: 0 }).format()
},
gridLines: {
display: false
}
}
],
yAxes: [
{
type: 'linear',
display: false,
ticks: {
min: 0,
max: maxY
},
id: 'y-axis-0',
gridLines: {
display: false
}
}
]
}
})
return (
<section>
<div className={classes.chartContainer}>
<HorizontalBar
data-cy="limit-chart"
data={data}
options={{ ...options(), ...annotationPlugin() }}
redraw={true}
height={80}
/>
</div>
</section>
)
}
I had problems with data and size changes on rerendering, and i found a way to force updating it using ref (profitChartRef.current.chartInstance.destroy();) to destroy chartInstance and then render completely new instance reflecting exactly what has been updated
export const ProfitReportChart = () => {
const profitChartRef = useRef();
if (profitChartRef?.current) {
profitChartRef.current.chartInstance.destroy();
}
return <Bar ref={profitChartRef} data={data} options={options} redraw/>;
}

chartjs-plugin-datasource implementation in React (react2chartjs)

as the title suggest, i am trying to import data from Excel and display it as charts using ChartJs. Got some idea based on this question : (Import data from Excel and use in Chart.js) and got it working.
But what im trying to do now is to implement this in React using react2chartjs. But failed to do so, got error 'ChartDataSource' is not defined. When i did define it like this:-
import ChartDataSource from 'chartjs-plugin-datasource';
The error resides but the chart display no information
Any idea?.
THE CODE
import React, {Component} from "react";
import {Bar} from 'react-chartjs-2';
import 'chartjs-plugin-datasource';
class Chart extends Component {
constructor(props){
super(props);
this.state = {
chartData:{
datasets: [
{
hoverBorderWidth:5,
hoverBorderColor:'black',
pointStyle: 'rect',
backgroundColor:'green'
//backgroundColor:Array.apply(null, Array(4)).map(_ => getRandomColor())
},
{
hoverBorderWidth:5,
hoverBorderColor:'black',
pointStyle: 'rect',
backgroundColor:'red'
//backgroundColor:Array.apply(null, Array(4)).map(_ => getRandomColor())
}
]
}
}
}
render() {
const option = {
title:{
display:true,
text: 'Test Chart',
fontSize:23,
},
legend:{
display:true,
position:'top',
labels:{
fontColor:'Black',
usePointStyle: true
}
},
layout:{
padding:{
left:0,
right:0,
bottom:0,
top:0
}
},
tooltips:{
enabled:true
},
scales: {
yAxes: [{
ticks: {
suggestedMin: 0,
}
}]
},
plugins: {
datasource: {
url: 'Book1.xlsx',
rowMapping: 'index'
}
}
}
return (
<div className="Chart">
<Bar
data={this.chartData}
options={option}
plugins={ChartDataSource}
/>
</div>
)
}
}
export default Chart;
I know this is old, but in case anyone else comes here for a similar issue.
<Bar
data={chartData}
plugins={[ChartDataSource]}// pass as array
options={option}
/>
and url: 'Book1.xlsx' means you have Book1.xlsx in Your project\public\Book1.xlsx

Highcharts 3D render problem on setState()

I am implementing react highchart 3d pie chart. Whenever I try to use this.setState() in lifecycle method componentDidMount(), the 3d chart displaces from it's position to right side diagonally. And if I remove this.setState() from componentDidMount(), it gets back to original position fine.
I am unable to fetch api call because of this odd behaviour as it is existing only in 3d pie charts. If I use 2d pie charts, setState() inside componentDidMount(), it works fine then.
This is how my problem goes:
App.js
import React, { Component } from 'react'
import HighchartsReact from 'highcharts-react-official'
import Highcharts from 'highcharts';
import highcharts3d from 'highcharts/highcharts-3d';
highcharts3d(Highcharts);
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
test: 'testing state'
}
}
componentDidMount(){
this.setState({
test: 'changing state'
})
}
render() {
const options = {
chart: {
type: 'pie',
options3d: {
enabled: true,
alpha: 45,
beta: 0
}
},
title: {
text: 'Browser market shares at a specific website, 2014'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
depth: 35,
dataLabels: {
enabled: true,
format: '{point.name}'
}
}
},
series: [{
type: 'pie',
name: 'Browser share',
data: [
['Firefox', 45.0],
['IE', 26.8],
{
name: 'Chrome',
y: 12.8,
sliced: true,
selected: true
},
['Safari', 8.5],
['Opera', 6.2],
['Others', 0.7]
]
}]
}
return (
<div>
<HighchartsReact
options = {options}
highcharts={Highcharts}
/>
</div>
)
}
}
before setState({})
after setState({})
Kindly help to resolve this odd bug .
The options are always 'new' for the wrapper if you keep them in the render method. That causes calling chart.update() with the same options. I have reproduced that situation here: http://jsfiddle.net/BlackLabel/mwv1kotb/
The best way is to keep options in a state: https://github.com/highcharts/highcharts-react#optimal-way-to-update
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
test: "testing state",
chartOptions: {...}
}
}
componentDidMount() {
this.setState({
test: "changing state"
});
}
render() {
return (
<div>
<HighchartsReact
options={this.state.chartOptions}
highcharts={Highcharts}
/>
</div>
);
}
}
Live demo: https://codesandbox.io/s/highcharts-react-demo-om7n3
However, that problem looks like a bug, so I have reproduced it without React and reported here: https://github.com/highcharts/highcharts/issues/11928

VueJs highstock cant draw graph properly

This is my VueJS code, i use this lib https://www.npmjs.com/package/highcharts-vue
So i dont know how i can get data and set it to series before the graph is drawn. Or if this is not posible, how can i redraw graph properly? Becouse now i set some default data, then get data from page, and redraw graph, but when its done and i see my graph, the scrollbar go to the left side and has a very small range. So how set options without change scrollbar and range selector?
<template>
<highcharts :constructor-type="'stockChart'" :options="options" :updateArgs="[true, false]" ref="linerchart"></highcharts>
</template>
<script>
import {Chart} from 'highcharts-vue'
import Highcharts from 'highcharts'
import stockInit from 'highcharts/modules/stock'
stockInit(Highcharts)
export default {
data: () => ({
obj: [],
names: ['CAFF'],
options: {
credits: { enabled: false },
rangeSelector: {
selected: 1,
inputEnabled: false,
buttonTheme: {
visibility: 'visible'
}
},
yAxis: {
labels: {
formatter: function () {
return (this.value > 0 ? ' + ' : '') + this.value + '%';
}
},
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
plotOptions: {
series: {
compare: 'percent',
showCheckbox: false,
events: {
checkboxClick: function (event) {
if (event.checked) {
this.show();
} else {
this.hide();
}
}
}
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> USD<br/>',
split: true
},
series: [{
name: "CAFF",
data: [1,2,3,4,5,6]
}]
}
}),
methods: {
linerGraph() {
var that = this;
this.names.forEach(function(name, i){
axios.get('/test/account/CAFF')
.then(response => {
console.log(response.data)
that.obj.push({name: name, data: response.data});
});
});
this.options.series = that.obj
},
},
components: {
highcharts: Chart
},
mounted() {
console.log(this.$refs.linerchart)
this.linerGraph();
}
}
</script>
You should use other VueJS lifecycle hook point to run axios request, because you are trying to download it when the component is already mounted(), so please try to use one of hook points before that one, e.g created().
Here is the Lifecycle diagram from Vue General Documentation:

Categories

Resources