I'm trying to implement a bar chart using react-chartjs-2, but I have some problems passing data to it.
If I declare the array directly when calling the graph, it works:
render(){
return(
<div>
<NumberField label={"n"} min={0} max={9999999} handleChange={this.changeN}/>
<NumberField label={"p"} min={0} max={1} handleChange={this.changeP}/>
<Distribution name={this.state.name}
media={this.state.media}
varianza={this.state.varianza}
desviacion={this.state.desviacion}
/>
<BarGraph _labels={['a','b']} _data= {[1,2]}/>
</div>
)
}
first way
but if I try something like:
constructor (props) {
super(props);
this.changeN = this.changeN.bind(this);
this.changeP = this.changeP.bind(this);
this.state={
name: "Binomial",
p: 0,
n: 0,
media: 0,
varianza: 0,
desviacion: 0,
labels: [],
data: []
}
}
componentDidUpdate(_prevProps, prevState) {
if (prevState.p !== this.state.p || prevState.n !== this.state.n) {
this.setState({
media: this.calcularMedia(),
varianza: this.calcularVarianza(),
desviacion: this.calcularDesviacion(),
data: [1,2],
labels: ['a', 'b']
});
}
}
·········
render(){
return(
<div>
<NumberField label={"n"} min={0} max={9999999} handleChange={this.changeN}/>
<NumberField label={"p"} min={0} max={1} handleChange={this.changeP}/>
<Distribution name={this.state.name}
media={this.state.media}
varianza={this.state.varianza}
desviacion={this.state.desviacion}
/>
<BarGraph _labels={this.state.labels} _data= {this.state.data}/>
</div>
)
}
the graph won't render correctly: second way
This is the BarGraph component:
import React, { useState, useEffect } from 'react';
import {Bar} from 'react-chartjs-2';
const BarGraph = ({_labels, _data}) =>{
const [chartData, setChartData] = useState({});
const chart = () =>{
setChartData({
labels: _labels,
datasets: [
{
label: 'label',
data: _data,
backgroundColor: 'rgba(75,192,192, 0.6)',
highlightStroke: "rgba(220,220,220,1)",
borderWidth: 0
}
]
})
}
useEffect(()=> {
chart()
}, [])
return(
<div className="App">
<div style={{height: "500px", width: "500px"}}>
<Bar data={chartData} options={{
legend:{
display: false
},
responsive: true,
title: {text: 'title', display: true},
scales:{
yAxes:[
{
ticks:{
autoSkip: true,
maxTicksLimit: 10,
beginAtZero: true
}
}
]
}
}}/>
</div>
</div>
)
}
export default BarGraph;
Thanks in advance.
Declaring the data in the main file and passing it works fine:
const graphData= {
labels: ['t1', 't2'],
datasets: [
{
backgroundColor: 'rgba(90, 172, 170, 0.8)',
data: [15,78]
}
]
}
class Binomial extends Component {
render(){
console.log(this.state.data);
console.log(this.state.labels);
return(
<div>
<BarGraph3 data={graphData}/>
</div>
)
}
}
}
Related
My code this
import React, { useEffect, useRef, useState } from "react";
import * as Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
export const PieChart = (props: any) => {
const [chartIsLoaded, setChartIsLoaded] = useState(false);
const series: any = [{
innerSize: '80%',
name: props.name,
colorByPoint: true,
data: props.chartData
}]
useEffect(() => {
setChartIsLoaded(true);
}, [])
const chartComponentRef = useRef<HighchartsReact.RefObject>(null);
const options: Highcharts.Options = {
chart: {
backgroundColor: "#0c0c0c",
borderColor: "#0c0c0c",
plotBorderWidth: 0,
plotShadow: false,
type: 'pie',
height: "70%",
},
title: {
style : {
display : 'none'
}
},
tooltip: {
pointFormat: '<b>{point.percentage:.1f}%</b>',
backgroundColor: "#1B1B1B",
borderColor: "transparent",
valueDecimals: 2,
borderRadius: 0,
style: {
color: "#fff",
fontSize: "15px"
}
},
accessibility: {
point: {
valueSuffix: '%'
}
},
legend: {
itemStyle: {color: "#fff", fontWeight: "400", fontFamily: "teragon-sans"}
},
plotOptions: {
pie: {
borderColor: "#0c0c0c",
borderWidth: 6,
allowPointSelect: true,
color: "#fff",
cursor: 'pointer',
dataLabels: {
enabled: false,
},
showInLegend: true
}
},
series: series
};
return (
<div>
{chartIsLoaded &&
<HighchartsReact
highcharts={Highcharts}
options={options}
ref={chartComponentRef}
oneToOne={true}
/>
}
</div>
);
};
chartData coming from this code:
let data = sheetData[0].data;
let invesment = await groupData(data, "Investment Type");
Problem: Chart rendering multiple times. Also, I have Bar Chart it's happening on that. There is no problem with Line Chart. The data is preparing with reduce function but its async waiting awaits. Moreover I tried with promises. Unfortunately, It was rendered multiple times again. How can I fix this situation?
Your chart options are initiated after every component update which directs to the chart update on every component update. I recommend you to keep chart options in a stare or memorize them. For example:
const PieChart = ({ name, chartData }) => {
const [chartOptions, setChartOptions] = useState(false);
useEffect(() => {
const series = [
{
innerSize: "80%",
name,
colorByPoint: true,
data: chartData
}
];
const options = {
...,
series
};
if (chartData) {
setChartOptions(options);
}
}, [chartData]);
return (
<div>
{chartOptions && (
<HighchartsReact highcharts={Highcharts} options={chartOptions} />
)}
</div>
);
};
Live demo: https://codesandbox.io/s/highcharts-react-demo-fz6rr6?file=/demo.jsx
Docs: https://www.npmjs.com/package/highcharts-react-official#optimal-way-to-update
I have a state which is an object containing an array and that array contains an object which looks something like this
[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}].
So I want to plot a bar chart using only the values from this array of objects. I want to send these values to another component which can be used to plot bar charts dynamically. But I'm not sure how to do it. can someone please show how to send the values to the barchart component and use them in the barchart as well?
This is the code
state={
analysis: {
tonal: [],
anxiety: []
}
}
Analysis = async () => {
//some api call
const {
...tonalAnalysis
} = result.scores;
const tonalArray = Object.entries(tonalAnalysis).reduce(
(carry, [tone, value]) => [
...carry,
{ tone: tone.toLowerCase(), value: parseInt(value) }
],
[]
);
this.setState({
analysis: { ...this.state.analysis, tonal: tonalArray }
});
console.log("Tonal array" + JSON.stringify(this.state.analysis.tonal)); //console logs `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]`
};
render(){
return {
<BarCharts/> // confused how to send the values as props here
}
the bar chart component where I will use
import React from "react";
import { Bar } from "react-chartjs-2";
import "./App.css";
class BarCharts extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
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)",
data: [65, 59, 80, 81, 56, 55, 40] //want to use the values here dynamically. Don't want these static values
}
]
}
};
}
render() {
const options = {
responsive: true,
legend: {
display: false
},
type: "bar"
};
return (
<Bar
data={this.state.data}
width={null}
height={null}
options={options}
/>
);
}
}
export default BarCharts;
You can create a HighChart wrapper component that can be used for any Highchart graphs.
Note:- every time the data set changes you need to destroy and re-render the graph again in order to make the graph reflect changes.
// #flow
import * as React from "react";
import merge from "lodash/merge";
import Highcharts from "highcharts";
import isEqual from "lodash/isEqual";
export type Props = {
config?: Object,
data: Array<any>,
onRendered?: () => void
};
class HighchartWrapper extends React.PureComponent<Props> {
container: ?HTMLElement;
chart: any;
static defaultProps = {
config: {},
onRendered: () => {}
};
componentDidMount() {
this.drawChart(this.props);
}
componentWillReceiveProps(nextProps: Props) {
const data= [...this.props.data];
if (!isEqual(nextProps.config, this.props.config) || !isEqual(nextProps.data, data)) {
this.destroyChart();
this.drawChart(nextProps);
}
}
destroyChart() {
if (this.chart) {
this.chart.destroy();
}
}
componentWillUnmount() {
this.destroyChart();
}
drawChart = (props: Props) => {
const { config: configProp, data, onRendered } = props;
if (this.container) {
let config = merge({}, configProp);
this.chart = new Highcharts.chart(this.container, { ...{ ...config, ...{ series: [...data] } } }, onRendered);
}
};
render() {
return <div ref={ref => (this.container = ref)} />;
}
}
export default HighchartWrapper;
In order use it for BarChart just pass the appropriate bar chart config.
<HighchartWrapper config={{
chart: {
type: "bar"
}
}}
data={[]}
>
Edit
import React from "react";
import BarChart from "./BarChart";
export default function App() {
return (
<div style={{ width: 400, height: 840 }}>
<BarChart
config={{
chart: {
height: 840,
type: "bar"
},
xAxis: {
categories: ["Positive", "Neutral", "Negative" ],
title: {
text: null
}
},
yAxis: {
min: 0,
title: {
text: "Population (millions)",
align: "high"
},
labels: {
overflow: "justify"
}
}
}}
data={[
{
name: "Series Name",
data: [90, 9, 10]
}
]}
/>
</div>
);
}
Just add your desired props in at component declaration :
<BarCharts data={this.state.analysis}/>
And on your BarChart Component you will need to just extract the values from your arrays, this just in case you need the same structure:
...
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
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)",
data: extractValues(this.props.data)
}
]
}
...
//This method can be reused in a hook or in a lifecycle method to keep data updated.
const extractValues = (data) => {
return data.map( d => d.value);
}
You can map the array so your code would be:
state={
analysis: {
tonal: [],
anxiety: []
}
}
Analysis = async () => {
//some api call
const {
...tonalAnalysis
} = result.scores;
const tonalArray = Object.entries(tonalAnalysis).reduce(
(carry, [tone, value]) => [
...carry,
{ tone: tone.toLowerCase(), value: parseInt(value) }
],
[]
);
this.setState({
analysis: { ...this.state.analysis, tonal: tonalArray }
});
console.log("Tonal array" + JSON.stringify(this.state.analysis.tonal)); //console logs `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]`
};
render(){
return {
<BarCharts values={this.state.analysis.tonal.map((entry) => entry.value)}/> // confused how to send the values as props here
}
And your barchart would be:
import React from "react";
import { Bar } from "react-chartjs-2";
import "./App.css";
class BarCharts extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
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)",
data: props.values //want to use the values here dynamically. Don't want these static values
}
]
}
};
}
render() {
const options = {
responsive: true,
legend: {
display: false
},
type: "bar"
};
return (
<Bar
data={this.state.data}
width={null}
height={null}
options={options}
/>
);
}
}
export default BarCharts;
So, i'm trying to add react-select component to my project. I'm using grouped options and trying to have the ability to select multiple options
This is my component code
class QueueFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
label: 'Partner',
options: [
{
value: {
id: 'ABCDSC',
value: 'Partner1'
},
label: 'Partner1'
},
{
value: {
id: 'ABCDSC',
value: 'Partner2'
},
label: 'Partner2'
}
]
},
{
label: 'Study',
options: [
{
value: {
id: 'ABCDSC123',
value: 'Study1'
},
label: 'Study1'
}
]
}
],
selectedFilters: []
};
this.fecthQueueFilters = this.fecthQueueFilters.bind(this);
this.onFilterChange = this.onFilterChange.bind(this);
this.applyFilter = this.applyFilter.bind(this);
}
componentDidMount(prevState, prevProps) {
if (prevProps.queueId !== this.props.queueId) {
this.fetchQueueFilters(this.props.queueId);
}
}
onFilterChange(selectedFilters) {
this.setState({ selectedFilters });
}
fecthQueueFilters(queueId) {
}
applyFilter() {
}
render() {
const groupStyles = {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
};
const groupBadgeStyles = {
backgroundColor: '#EBECF0',
borderRadius: '2em',
color: '#172B4D',
display: 'inline-block',
fontSize: 12,
fontWeight: 'normal',
lineHeight: '1',
minWidth: 1,
padding: '0.16666666666667em 0.5em',
textAlign: 'center',
};
const formatGroupLabel = data => (
<div style={groupStyles}>
<span>{data.label}</span>
<span style={groupBadgeStyles}>{data.options.length}</span>
</div>
);
const Input = (props) => {
if (props.isHidden) {
return <components.Input {...props} />;
}
return (
<div style={{ border: '1px dotted black' }}>
<components.Input {...props} />
</div>
);
};
return (
<Container fluid className="main-header">
<Row>
<Col xs="1">Queue Filters:</Col>
<Col auto>
<Select
options={this.state.options}
isMulti
isClearable
formatGroupLabel={formatGroupLabel}
components={{Input}}
closeMenuOnSelect={false}
value={this.state.selectedFilters}
onChange={this.onFilterChange}
/>
</Col>
<Col xs="1">
<Button type="button" onClick={this.applyFilter} color="success">
<Filter />
</Button>
</Col>
</Row>
</Container>
);
}
}
QueueFilter.propTypes = {
queueId: PropTypes.string
};
export default QueueFilter;
But when I select multiple options, only 1 is shown in the select field
The console shows there's 2 options selected
And, well, there's this warning in the console
If I change the the options values to be simple strings instead of objects
this.state = {
options: [
{
label: 'Partner',
options: [
{
value: 'ABCDSC:Partner1',
label: 'Partner1'
},
{
value: 'ABCDSC:Partner2',
label: 'Partner2'
}
]
},
{
label: 'Study',
options: [
{
value: 'ABCDSC123:Study1',
label: 'Study1'
}
]
}
],
selectedFilters: []
};
Well, it works as expected, but I'd rather have the object values.
Any idea how to achieve this?
You can use the prop getOptionValue
<Select
getOptionValue={option => option.value.value}
I want to change the backgroundColor of only one record from 'labels' array. In my app 'labels' is set to an array of stringified numbers that come from the database. And I want the biggest number to be, let's say green. The rest should be, let's say, pink.
I don't actually know how to access the background of each instance.
Does anybody know how to do that?
This is what I want to achieve:
This is what I was trying to do but it's just the purest form of nonsense as it doesn't work and it would change the background of the whole chart.
import React from 'react';
import { Bar, Line, Pie, Doughnut } from 'react-chartjs-2';
export default class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
chartData: {
labels: [],
datasets: [{
label: this.props.label,
data: [],
backgroundColor: '#CD5C94',
}]
}
}
}
static defaultProps = {
displayTitle: true,
}
updateChart = () => {
const newArr = this.props.indexes.map(Number);
const latestRecord = Math.max(...newArr);
let color;
console.log(color)
this.state.chartData.labels.forEach(label => {
if (label == latestRecord) {
this.setState({
chartData: {
datasets: [{
backgroundColor: '#CD5C94',
}]
}
})
} else {
this.setState({
chartData: {
datasets: [{
backgroundColor: '#CD5C94',
}]
}
})
}
})
this.setState({
chartData: {
labels: this.props.indexes, //this is the array of numbers as strings
datasets: [{
label: this.props.label, //this is the label of the chart
data: this.props.results, //this is the array of total travel cost records
// backgroundColor: ,
}]
}
})
}
render() {
return (
<div className="myChart">
<button className="bmi-form__button" onClick={this.updateChart}>DISPLAY CHART DATA</button>
<div className="chart">
<Doughnut
data={this.state.chartData}
width={100}
height={50}
options={{
title: {
display: this.props.displayTitle,
text: this.props.text,
fontSize: 25
}
}
}
/>
</div>
</div>
)
}
}
I have achieved to apply different color for each label like this:
const colorMap = {
'Entertainment': '#FF6384',
'Food': '#36A2EB',
'Gas': '#FFCE56',
'Other': '#38c172'
};
const labels = ['Food', 'Other'];
const colors = labels.map(l => colorMap[l]);
const chartData = {
labels,
datasets: [{
backgroundColor: colors,
borderColor: colors,
data: []
}]
};
<Doughnut data={chartData}/>
I have created a line chart using react-highcharts. It has 3 series and different data for each of them. And I have a range-selector that changes the data of the series dynamically. The chart looks like this:
It works all fine but the problem is whenever I change the risk value on the range-selector, the chart re-renders with new series' data. I don't want it to re-render every time. I want the series' data change with animation. Something like this: Live random data. And here is my related code:
class ContributionRiskGraph extends React.Component {
constructor() {
super();
this.state = {
riskValue: 8.161736
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.setState({
riskValue: value
});
}
render() {
const riskValue = this.state.riskValue / 100;
const LBData = getGraphPlotData(riskValue, 'lowerBound');
const EVData = getGraphPlotData(riskValue, 'expectedValue');
const UBData = getGraphPlotData(riskValue, 'upperBound');
const config = {
chart: {
animation: {
duration: 1000
}
},
title: {
text: 'Contribution Risk Graph'
},
series: [
{
name: 'Lower Bound',
data: LBData,
type: 'spline',
tooltip: {
valueDecimals: 2
}
},
{
name: 'Expected Value',
data: EVData,
type: 'spline',
tooltip: {
valueDecimals: 2
}
},
{
name: 'Upper Bound',
data: UBData,
type: 'spline',
tooltip: {
valueDecimals: 2
}
}
],
yAxis: {
gridLineWidth: 0,
opposite: true
},
xAxis: {
gridLineWidth: 2,
labels: {
formatter: function() {
if (this.value <= 1) {
return this.value + ' month';
}
return this.value + ' months';
}
}
},
};
return(
<div>
<ReactHighcharts config={config} />
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 30 }}>
<RangeSlider
label="Risk Value"
defaultValue={8}
min={1}
max={62}
handleChange={this.handleChange}
/>
</div>
</div>
)
}
}
So, I have found a workaround. It is working perfectly. I am getting the chart by ref and then set a new data using setData. This only updates the data rather than re-rendering the whole chart component. And I am using component lifecycle method shouldComponentUpdate to stop the re-rendering of the component. Here is the related code:
class ContributionRiskGraph extends React.PureComponent {
constructor() {
super();
this.state = {
riskValue: 8.161736
};
this.handleChange = this.handleChange.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
if(this.state.riskValue !== nextState.riskValue) {
return false;
}
}
handleChange(value) {
this.setState({
riskValue: value
});
let riskValue = this.state.riskValue / 100;
let chart = this.crg.getChart();
chart.series[0].setData(getGraphPlotData(riskValue, 'lowerBound'), true);
chart.series[1].setData(getGraphPlotData(riskValue, 'expectedValue'), true);
chart.series[2].setData(getGraphPlotData(riskValue, 'upperBound'), true);
}
render() {
// other code
return(
<div>
<ReactHighcharts config={config} ref={a => this.crg = a} />
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 30 }}>
<RangeSlider
label="Risk Value"
defaultValue={8}
min={1}
max={62}
handleChange={this.handleChange}
/>
</div>
</div>
)
}
}