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
Related
Im stuck with rendering Chart from Primevue components. It's based on chart.js library. At this moment I have simple vue component created:
<template>
<div class="p-chart">
<h2>Chart:</h2>
<chart type="line" :data="chartData" />
</div>
</template>
<script>
import Chart from "primevue/chart";
export default {
data() {
return {
chartData: {
labels: ["Label"],
datasets: [
{
label: "Dataset",
backgroundColor: "#5F5F5F",
data: [99],
},
],
},
};
},
components: {
Chart,
},
};
</script>
Unfortunately the chart does not appear moreover I don't see any js erros in brwoser console. Can someone help what I'm missing here? Any additional setup needed?
Remove chart.js and then install this version, worked for me (but i use vue 3, maybe you need another version) :
npm i chart.js#2.9.4
According to documentation on https://www.primefaces.org/primevue/showcase/#/chart/line
I think you are missing the options attribute inside Chart tag:
<chart type="line" :data="chartData" :options="chartOptions" />
And put the object inside the data return from vue export:
data() {
return {
chartData: {
labels: ["Label"],
datasets: [
{
label: "Dataset",
backgroundColor: "#5F5F5F",
data: [99],
},
],
},
chartOptions: {
plugins: {
legend: {
labels: {
color: '#495057'
}
}
},
scales: {
x: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
},
y: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
}
}
}
};
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
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
I am using HighCharts for the first time and it's proving to be difficult to find sources to help me with this problem.
I am trying to display multiple charts with different numbers of series. Rather than set up multiple options templates I want to use just one and use a function to customize the options object each time. Here I've provided mock data for what one of the charts will look like as received from the API. In the application I plan on feeding it from HTML.
I keep running into various problems due to my lack of understanding of HighCharts, right now I am just going around in circles fixing one problem only for a past problem to pop-up again I was hoping someone can point me in the right direction.
This is my Class:
export class CompletenessTabComponent implements OnInit, AfterViewInit {
#ViewChild('chartTarget') chartTarget: ElementRef;
chart: Highcharts.ChartObject;
dataObj = {"id":0,
"tableName":"d1 vs d2",
"data":{"d1Count":[50,45,55,60,70],
"d2Count":[40,32,55,58,33],
"Errors":[2,3,4,1,0]},
"dates":[20180927,20180928,20181001,20181002,20181003]
};
constructor() { }
setHighChartOptions(data, seriesNames, chartTitle ): any {
count = 1;
highChartOptions.title.text = chartTitle;
highChartOptions.xAxis.data = data.dates;
this.chart.series[0].setData(this.dataObj.data[0]);
Object.keys(data.data).forEach((key) =>
this.chart.addSeries({
name: seriesNames[count],
data: data.data[key],
type: 'line'
}) count ++
);
return highChartOptions;
}
getHighChartOptions(){
return highChartOptions;
}
ngOnInit() {
this.chart = chart(this.chartTarget.nativeElement, this.setHighChartOptions(this.dataObj, ['d1', 'd2', 'error'], this.dataObj.tableName));
Object.keys(this.dataObj.data).forEach(key =>
console.log(key, this.dataObj.data[key]))
}
ngAfterViewInit() {
}
}
const highChartOptions = {
colors:
['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572', '#FF9655', '#FFF263', '#6AF9C4'],
credits: {
enabled: false
},
chart: {
backgroundColor: null,
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'top',
itemStyle: {
fontSize: '10px',
fontWeight: 'normal'
}
},
tooltip: {
valuePrefix: '$'
},
title: {
text: ''
},
xAxis: {
type: 'date',
data: [],
title: 'date'
},
yAxis: {
title: {
align: 'middle',
rotation: 0,
text: ''
}
},
plotOptions: {
series: {
marker: {
enabled: false
},
shadow: false
}
},
series: [{
type: 'line',
name: '',
data: [null]
}]
};
A few errors I keep receiving are:
in setHighChartOptions() : Cannot read property 'series' of undefined (at, this.chart.series[0]), & cannot read 'keys' of undefined (at the start of the ForEach loop).
I believe the big issue is the chart object is undefined which doesn't make sense since I've instantiated it at the top of the class and then onInit I set the chart options.
I hope you will be able spot my errors and assist me. Thank You.
Most probably, it's because you're trying to refer to series which is not already defined. Actually, if you calling the setHighchartsOptions for a first time, your chart is not defined yet.
In order to make it works, please try to init the chart first (even could be the empty chart, without any data), and then call another methods on Chart object, like addSeries. Something like that, but i didn't saw whole your app, so it's hard to write completely adjusted solution.
ngOnInit() {
this.chart = chart(this.chartTarget.nativeElement, {});
this.chart.update(this.setHighChartOptions(this.dataObj, ['d1', 'd2', 'error'], this.dataObj.tableName))
Object.keys(this.dataObj.data).forEach(key =>
console.log(key, this.dataObj.data[key]))
}
First of all, you must be careful to use import correctly all js package in your Angular project:
import { chart } from 'highcharts';
import * as Highcharts from 'highcharts';
Also, you must check does your package installed correctly with "npm"?
After all, As my point of view, this example can help you to better understand how to use "highcharts" package in your project, other configs depend on the version of "highcharts" you are using it.
import { Component, ElementRef, ViewChild } from '#angular/core';
import { chart } from 'highcharts';
import * as Highcharts from 'highcharts';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Highcharts Sample';
#ViewChild('chartTarget') chartTarget: ElementRef;
chart: Highcharts.ChartObject;
ngAfterViewInit() {
const options: Highcharts.Options = {
chart: {
type: 'bar'
},
title: {
text: 'Sample Title'
},
xAxis: {
categories: ['Top Test', 'Test 2', 'Test3']
},
yAxis: {
title: {
text: 'Fox Rex'
}
},
series: [{
name: 'Tomi',
data: [1, 0, 4]
}, {
name: 'Alex',
data: [5, 7, 3]
}]
};
this.chart = chart(this.chartTarget.nativeElement, options);
}
addSeries(){
this.chart.addSeries({
name:'Test',
data:[2,3,7]
})
}
}
And HTML template of sample:
<div #chartTarget>
chart target sample
</div>
Also, you must serve data to highcharts as the format is standard for that, check JSON sample data format in examples in highcharts website.
I have a component where I have to show high maps. No errors but the maps is always empty
My chart options object :
let chartData = [{ code3: "ABW", z: 105 }, { code3: "AFG", z: 35530 }];
this.chartConfigObject = new MapChart(<any>{
chart: {
borderWidth: 1,
map: 'custom/world'
},
title: {
text: 'World population 2013 by country'
},
subtitle: {
text: 'Demo of Highcharts map with bubbles'
},
legend: {
enabled: false
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
name: 'Countries',
color: '#E0E0E0',
enableMouseTracking: false
}, {
type: 'mapbubble',
name: 'Population 2016',
joinBy: ['iso-a3', 'code3'],
data: chartData,
map: mapData,
minSize: 4,
maxSize: '12%',
tooltip: {
pointFormat: '{point.properties.hc-a2}: {point.z} thousands'
}
}]
});
When I console the created MapChart object i am getting the series as null always.
What am i missing and why the maps series is always null?
I am using angular-highcharts component as explained here :
https://github.com/cebor/angular-highcharts
reproduced in stackblitz: https://stackblitz.com/edit/angular-highcharts-stock-nbvnuw?file=app%2Fapp.module.ts
You are not providing the map data to your chart , so it doesn't know which map to show. Try showing the map data like mapData: Highcharts.maps['custom/world'] under Series.
If you cannot access the maps in your Highcharts reference , try importing all the maps (JS files) from the HighMaps library into your project and reference them in your typescript component file like this:
const Highcharts = {maps: {}};
require('../../../assets/maps')(Highcharts); //your custom project path
and use mapData: Highcharts['maps']['custom/world'] or whatever is the map you wanted to use like 'countries/us/us-all'
Below is working map code using angular-highcharts to plot the map and points on it:
getMap(data) {
return new MapChart({
chart: {
events: {
load: function (point) {
//do something here on page load
}
},
spacingBottom: 20
backgroundColor: '#FFF',
style: {fontFamily: 'inherit'}
},
title: {
text: ''
},
mapNavigation: {
enabled: true
},
tooltip: {
headerFormat: '',
formatter: function () {
var toolTipTxt = '<b>' + this.point.options.name + '</b>';
return toolTipTxt;
}
},
plotOptions: {
series: {
color: '#00A778',
marker: {
radius: 6,
symbol: 'circle',
lineColor: '#00A778',
lineWidth: 1,
states: {
select: {
fillColor: '#00A778',
lineWidth: 1,
lineColor: '#00A778'
}
}
},
point: {
events: {
click: function () {
this.select(null, true);
}
}
}
}
},
series: [
{
//mapData: Highcharts['maps']['custom/world']
mapData: Highcharts.maps['custom/world'],
name: 'Basemap',
borderColor: '#A0A0A0',
nullColor: 'rgba(200, 200, 200, 0.3)',
showInLegend: false,
data: []
},
{
// Specify points using lat/lon
type: 'mappoint',
name: 'MySites',
data: data
}
]
});
}
Also you need proj4js library to plot points on the map, in case if that's your requirement as well.
Hope this helps !
Might, it can be useful.
Here is the solution for angular-highcharts:
1) Download the map from collection:
https://code.highcharts.com/mapdata/
2) Create a variable that equals to the content of this file and export it:
instead of
Highcharts.maps["custom/world"] = { ... content of the file ... }
use
const worldMap = { ... content of the file ... }
export default worldMap;
2) Import this map to your component (for world-map.ts):
import worldMap from './world-map';
3) use
chart: {
...
map: worldMap,
...
},
And, of course, don't forget to add a couple of lines to your module:
import { ChartModule, HIGHCHARTS_MODULES } from 'angular-highcharts';
import * as highmaps from 'highcharts/modules/map.src';
#NgModule({
...
providers: [
{ provide: HIGHCHARTS_MODULES, useFactory: () => [highmaps] }
],
...
})
About modules: https://www.npmjs.com/package/angular-highcharts#highmaps
Here is the working example:
https://stackblitz.com/edit/angular-highcharts-stock-byjo6a
Solution was found here:
https://forum.highcharts.com/highmaps-usage-f14/highmaps-bubble-chart-is-empty-t40432/