How to send an array of object as a prop? - javascript

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;

Related

React Chart.js BarGraph not updating

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>
)
}
}
}

Printing a list by clicking chart Chart js + react

Hi i'm having troubles printing in a alert (by clicking one of the portions), a list of users for a specific answer in chart.js + react here's my chart component
Piechart.js
import React,{ Component } from 'react';
import {Chart} from 'react-chartjs-2';
class Piechart extends Component {
constructor(props){
super(props)
this.chartReference = React.createRef();
this.state = {
data:[]
};
}
async componentDidMount(){
const url = "https://api-tesis-marco.herokuapp.com/api/v1/questiondata/"+this.props.title;
const data = await fetch(url)
.then(response => response.json());
this.setState({data:data});
this.myChart = new Chart(this.chartReference.current,{
type: 'pie',
data:{
labels: this.state.data.map(d=>d.Respuesta),
datasets: [{
data: this.state.data.map(d=>d.porcentaje),
backgroundColor: this.props.colors
}],
},
options: {
title: {
display: true,
text: this.props.title,
fontSize: 20,
fontStyle: 'bold'
},
legend: {
position:'right'
},
onClick: clicked
}
});
function clicked(evt){
var element = this.getElementAtEvent(evt);
if(element[0]){
alert();
}
}
}
render(){
return(
<canvas ref={this.chartReference} />
)
}
}
export default Piechart;
//i having troubles passing the lists data of my request
function clicked(evt){
var element = this.getElementAtEvent(evt);
if(element[0]){
//i don't know what to do here
alert();
}
}
Here is the json response of my request:
Data:
[
{
"Respuesta": "A",
"porcentaje": 7,
"quien": [
"1",
"visita1"
]
},
{
"Respuesta": "B",
"porcentaje": 3,
"quien": [
"coco"
]
},
{
"Respuesta": "C",
"porcentaje": 3,
"quien": [
"Dani3l"
]
},
{
"Respuesta": "D",
"porcentaje": 10,
"quien": [
"Gabi",
"test",
"visita prueba"
]
},
{
"Respuesta": "No ha respondido",
"porcentaje": 76,
"quien": [
"9punto5",
"Colita de algodón",
"KarmenQueen",
"Prueba",
"ancova",
"cehum2",
"chuky",
"dev",
"felipe",
"gabs",
"icom2019",
"invunche",
"john",
"laura",
"marian",
"marti",
"pablazozka",
"prueba",
"test1",
"titicaco",
"visita 1",
"visita test"
]
}
]
in my clicked function how can i pass the "quien" lists for the specific portion of my pie chart?, so in the alert i can print the list of that portion , i'm using this as guide https://jsfiddle.net/u1szh96g/208/ but is difficult for me adapt this to react
well after some mixed tutorials and guides, i came with the solution
Piechart.js:
import React,{ Component } from 'react';
import {Chart} from 'react-chartjs-2';
class Piechart extends Component {
constructor(props){
super(props)
this.chartReference = React.createRef();
this.state = {
data:[]
};
}
async componentDidMount(){
const url = "https://api-tesis-marco.herokuapp.com/api/v1/questiondata/"+this.props.title;
const data = await fetch(url)
.then(response => response.json());
this.setState({data:data});
var datasets = [{data: this.state.data.map(d=>d.Count),
backgroundColor: this.props.colors
},
{
data: this.state.data.map(d=>d.Percent)
},
{
data: this.state.data.map(d=>d.Who)}]
this.myChart = new Chart(this.chartReference.current,{
type: 'pie',
data:{
labels: this.state.data.map(d=>d.Answer),
datasets: [{
data: datasets[0].data,
backgroundColor: datasets[0].backgroundColor
}]
},
options: {
title: {
display: true,
text: this.props.title,
fontSize: 20,
fontStyle: 'bold'
},
legend: {
position:'right'
},
tooltips:{
callbacks: {
title: function(tooltipItem, data) {
return 'Respuesta:'+data['labels'][tooltipItem[0]['index']];
},
label: function(tooltipItem, data) {
return 'Total:'+data['datasets'][0]['data'][tooltipItem['index']];
},
afterLabel: function(tooltipItem) {
var dataset = datasets[1];
var total = dataset['data'][tooltipItem['index']]
return '(' + total+ '%)';
}
},
backgroundColor: '#FFF',
titleFontSize: 16,
titleFontColor: '#0066ff',
bodyFontColor: '#000',
bodyFontSize: 14,
displayColors: false
},
onClick: clicked
}
});
function clicked(evt){
var element = this.getElementAtEvent(evt);
if(element[0]){
var idx = element[0]['_index'];
var who = datasets[2].data[idx];
alert(who);
}
}
}
render(){
return(
<canvas ref={this.chartReference} />
)
}
}
export default Piechart;
as you can see i only set an datasets array outside
var datasets = [{
data: this.state.data.map(d=>d.Count),
backgroundColor: this.props.colors
},
{
data: this.state.data.map(d=>d.Percent)
},
{
data: this.state.data.map(d=>d.Who)}]
this contains all the datasets of the request, then in the chart instance i only pass the dataset i want to plot, then for my question, in the clicked function only call the element of the array wich contains the list of users for the specific answer
Cliked function:
function clicked(evt){
var element = this.getElementAtEvent(evt);
if(element[0]){
var idx = element[0]['_index'];
var who = datasets[2].data[idx];
alert(who);
}
}
i made a custom tooltip as well, but i have an issue with this(with the default tooltip is the same) because i use this component to plot 4 piecharts but when i hover the mouse only 2 of the 4 charts show me the tooltip, the 2 chart who shows the tooltip are random (when refresh localhost pick randomly 2 of the 4 charts), and i don't know what is happend or how to fix this, i hope this is usefull to someone

Nivo bar chart calling label function hundreds of times

I'm using Nivo bar to represent a user's progress on a budget. I've normalized the data by dividing the category balance by the category goal. Example data.
[{
"category": "Gas",
"budget": 0.24,
"over_budget": 0.0
},
{
"category": "Groceries",
"budget": 1.0,
"over_budget": 0.26
}]
I don't want these values to be used as the label on the chart. I plan to use the actual balance value as the label. I have an endpoint that will return the balance for a category and have attempted the following to use that value:
<ResponsiveBar
...
label={d => this.getDollarAmount(d.value)}
...
>
With the function POC as:
getDollarAmount(value) {
console.log("hitting getDollarAmount")
return 1
};
The log message gets logged 500+ times. My expectation would be that the function would only be hit once for each bar in the chart.
I'm still learning react so this could be something obvious. Thanks in advance!
EDIT - Here's the entire BarChart component:
import axios from 'axios';
import React, { Component } from "react";
import { ResponsiveBar } from '#nivo/bar'
// Nivo theming
const theme = {
axis: {
ticks: {
line: {
stroke: "#e9ecee",
strokeWidth: 40
},
text: {
// fill: "#919eab",
fill: "black",
fontFamily: "BlinkMacSystemFont",
fontSize: 16
}
}
},
grid: {
line: {
stroke: "#e9ecee",
strokeWidth: 5
}
},
legends: {
text: {
fontFamily: "BlinkMacSystemFont"
}
}
};
let budgetStatusAPI = 'http://127.0.0.1:8000/api/budget_status/?auth_user=1&month=2020-02-01';
class BarChart extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
this.getDollarAmount = this.getDollarAmount.bind(this);
}
componentDidMount() {
console.log("component did mount")
axios.get(budgetStatusAPI).then(response => {
this.setState({
data: response.data
}, function () {
console.log(this.state.data);
})
});
}
componentDidUpdate() {
console.log("component did update")
}
getDollarAmount(value) {
console.log("hitting getDollarAmount")
console.log(value)
return 1
};
render() {
const hard_data = [
{
"category": "Groceries",
"budget_status": 1.0,
"over_budget": .26,
},
{
"category": "Gas",
"budget_status": .24,
"over_budget": 0.0,
}]
return(
<ResponsiveBar
maxValue={1.5}
markers={[
{
axis: 'x',
value: 1,
lineStyle: { stroke: 'rgba(0, 0, 0, .35)', strokeWidth: 2 },
legend: 'Goal',
legendOrientation: 'horizontal',
legendPosition: 'top'
},
]}
enableGridX={false}
gridXValues={[1]}
enableGridY={false}
data={this.state.data}
// data={hard_data}
keys={['budget_status', 'over_budget']}
indexBy="category"
margin={{ top: 25, right: 130, bottom: 50, left: 125 }}
padding={0.3}
layout="horizontal"
colors={{ scheme: 'set2' }}
theme={theme}
defs={[
{
id: 'dots',
type: 'patternDots',
background: 'inherit',
color: '#38bcb2',
size: 4,
padding: 1,
stagger: true
},
{
id: 'lines',
type: 'patternLines',
background: 'inherit',
color: '#eed312',
rotation: -45,
lineWidth: 6,
spacing: 10
}
]}
borderColor={{ from: 'color', modifiers: [ [ 'darker', 1.6 ] ] }}
axisBottom={null}
label={d => this.getDollarAmount(d.value)}
isInteractive={false}
animate={true}
motionStiffness={90}
motionDamping={15}
/>
)
}
}
export default BarChart;
Reproduced here: https://codesandbox.io/s/nivo-bar-label-issue-k4qek
The multiple calling is happening because the bar chart is calling label function for each animation tick/frame render. If we setup a counter, we'll see with animate prop set to true it will render from 450+ to 550+ times, but if we set the prop animate to false, we'll it renders 6 times which is exactly how many price values are > 0.0.
If you want to avoid all these renders, you'll have to disable animation using animate={false} prop like this:
getDollarAmount(value) {
// Remove every console.log inside this function
return `$${value}`
}
render() {
return (
<ResponsiveBar
animate={false}
label={d => this.getDollarAmount(d.value)}
...
);
}
You can check it running to your cloned CodeSandbox. I have set animate to false and the counter log inside getDollarAmount is calling 6 times. Try to change animate to true and you'll see the 500+- renders.
Also, you don't have to create a function for each label call, you can just pass the getDollarAmount function and let it handle the whole d parameter like this:
getDollarAmount(d) {
// Remove every console.log inside this function
return `$${d.value}`
}
render() {
return (
<ResponsiveBar
animate={false}
label={this.getDollarAmount}
...
);
}

How to change 'backgroundColor' of a specific 'labels' instance in "react-chartjs-2"

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}/>

Passing dynamic data Victory-native charts

I am using victory-native chart to render a pie chart. I am confused on how to pass the data fetched from Rest API to the {data} being passed to the pie chart for it's 'y' values. Here is my code:
import React, {Component} from "react";
import { StyleSheet, View } from "react-native";
import axios from 'axios'
import { VictoryPie, VictoryGroup, VictoryTheme } from "victory-native";
import Svg from 'react-native-svg';
export default class Chart extends Component {
state= {
id: '********************',
data: []
}
componentDidMount(){
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Headers': 'x-access-token',
'x-access-token': this.state.id
}
axios.post('http://bi.servassure.net/api/SalesOverviewOEMLevel2', {
oem:'all'
},
{headers: headers}).then((res) => {
let TotalSales = res.data.data[0].TotalSales;
this.setState({
data: TotalSales
})
console.log(this.state.data);
})
.catch(err => {
console.log(err)
});
}
render() {
const myColorScale = ["#1b4f72", "#21618c", "#2e86c1","#3498db", "#76d7c4", "#d1f2eb"];
const data = [
{ x: 1, y: 6, i: 0 },
{ x: 2, y: 2, i: 1 },
{ x: 3, y: 3, i: 2 },
{ x: 4, y: 5, i: 3 },
]
return (
<View style={styles.container}>
<Svg
height={200}
width={200}
viewBox={'0 0 300 300'}
preserveAspectRatio='none'
>
<VictoryGroup width={300} theme={VictoryTheme.material}>
<VictoryPie
style={{
data: {
fill: (d) => myColorScale[d.i]
},
labels: { fill: "white", fontSize: 10, fontWeight: "bold" }
}}
radius={100}
innerRadius={60}
labelRadius={70}
data={data}
labels={(d) => `y: ${d.y}`}
/>
</VictoryGroup>
</Svg>
</View>
);
}
}
This is the consoled form of my data that I want to pass into as 'Y' values to the chart:
The static data is rendering chart perfectly, but stuck on dynamic on how to loop the values. Please help to figure out.
You need to transform your data to match your example data:
Try this:
const your_data = this.state.data.map((val, index) => {
// you can also pass an i- value, but that's up to you
return { x: index, y: val };
});
and then:
<VictoryPie
style={{
data: {
fill: (d) => myColorScale[d.x] // d.x, because i didn't specify an "i-value"
},
labels: { fill: "white", fontSize: 10, fontWeight: "bold" }
}}
radius={100}
innerRadius={60}
labelRadius={70}
data={your_data} // pass here your new data
labels={(d) => `y: ${d.y}`}
/>

Categories

Resources