0

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;
AVANISH RAJBHAR
  • 527
  • 3
  • 9
henrydoe
  • 398
  • 2
  • 7
  • 22
  • Did you try ``? Then you can access them from your `BarCharts` components with `this.props.analysis` – Jackson Feb 26 '20 at 17:16
  • won't it send everything in the analysis object? even if I send the whole thing. how do I destructure in the Barchart component? `const {values} = this.props.analysis.tonal`? – henrydoe Feb 26 '20 at 17:19

3 Answers3

1

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);
}
Ricardo Gonzalez
  • 1,827
  • 1
  • 14
  • 25
  • getting an error `TypeError: data.map is not a function` – henrydoe Feb 26 '20 at 17:24
  • 1
    In this cases you can always check you console log ands see if you are passing the right values, and if your data is `async` you should check a flag that is ready or, check if `data` exist – Ricardo Gonzalez Feb 26 '20 at 17:26
  • where should I use the `const extractValues = (data) ...... ` part? should I call it in the App.js component or in the BarChart component? sorry for being ignorant but I'm quite new to react. – henrydoe Feb 26 '20 at 17:29
  • 1
    data is async, that is, when I click a button, then the data is sent from the backend via an api to the client side – henrydoe Feb 26 '20 at 17:30
  • 1
    @henrydoe i mean to use it atthe `BarCharts` component using his `lifecycle` methods just to make sure that you are not gonna run the method at every rerender – Ricardo Gonzalez Feb 26 '20 at 17:33
  • I did that in the componentDidMount lifecycle method yet still getting the `TypeError: data.map is not a function` error. created a sandbox to show you where I added it. where exactly am I doing wrong https://codesandbox.io/s/charming-davinci-30qy7 – henrydoe Feb 26 '20 at 17:40
  • yeah it won't work cause I don't have a direct api url. I just put there to show you if I have put the const extractValues thing in the correct place or not? – henrydoe Feb 26 '20 at 17:54
  • 1
    You are not declarin the `extractValues`on the right place, check this [updated sandbox](https://codesandbox.io/s/beautiful-firefly-g0jxf) @henrydoe – Ricardo Gonzalez Feb 26 '20 at 18:00
  • getting `TypeError: this.extractValues is not a function` now. looks like it's still not declared in the right place. Error is in this line `data: this.extractValues(this.props.data)` – henrydoe Feb 26 '20 at 18:06
  • 1
    I think that you may change `cons extractValues` to `this.extractValues = data => { return data.map(d => d.value); };` – Ricardo Gonzalez Feb 26 '20 at 18:57
  • well, still the same error `TypeError: this.extractValues is not a function` – henrydoe Feb 26 '20 at 19:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/208654/discussion-between-ricardo-gonzalez-and-henrydoe). – Ricardo Gonzalez Feb 27 '20 at 16:20
1

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

Abhishek
  • 1,302
  • 10
  • 18
  • I'm confused still. Would I have to wrap the BarChart component with this highChart component? Can you please show how to do this in this sandbox https://codesandbox.io/s/charming-davinci-30qy7 – henrydoe Feb 26 '20 at 19:48
  • 1
    @henrydoe I have edited your sandbox with a working example. You can check - https://codesandbox.io/s/crimson-tree-s78m5 – Abhishek Feb 26 '20 at 20:17
  • 1
    @henrydoe Glad, it helped! Please, consider accepting my answer – Abhishek Feb 26 '20 at 22:31
  • I'm not getting any bars in the graph. I sent the data as `data={this.state.analysis.tonal}` but I guess the data is not being passed. How should I send the data? I also tried `this.state.analysis` but got an error saying `data not iterable` – henrydoe Feb 27 '20 at 15:05
  • probably because there's another array inside the data array in your example while in my example, it's just an example. How should I just sent a single value instead of the array like in your example? I will gladly accept this answer after passing this last hurdle – henrydoe Feb 27 '20 at 15:12
  • this is what my data looks like state.analysis.tonal = `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]`. this image is how I want the chart to look like. https://i.imgur.com/C99ZdaJ.jpg. when I click an analyze button, the current and previous bars would be same. when I click analyse again the previous results will be shown with the previous bar and the new scores will show with the current bars but that is for later part. right now I just want the bars to be shown. not sure how to even pass the scores from this array of objects – henrydoe Feb 27 '20 at 16:09
  • Check my edits. You can play with the config to customize chart as per your need. You need to transform analytics data as per highchart data format. Also check categories in config. – Abhishek Feb 27 '20 at 16:34
  • not sure why it's not displaying the bars in my app. I send the data as `` and you can see in my question how I've declared the tonal analysis. It should work but it isn't working, no idea why – henrydoe Feb 27 '20 at 17:04
  • You can't simply pass analysis.tonal to highchart. It has to be in highchart supported format. Did you try the format in my edits? – Abhishek Feb 27 '20 at 17:14
  • I send the format as you did in the edit. but instead of hardcoding, I send the state tonal from analysis but it doesn't recognize the format I think. how should I send the props then cause it has to be dynamic? I tried your edits, same, no bars – henrydoe Feb 27 '20 at 17:18
  • Can you add this to codesandbox? So that I can take a look – Abhishek Feb 27 '20 at 17:21
  • https://codesandbox.io/s/admiring-wright-fzrjh . BarChart component has been added at the bottom in App.js – henrydoe Feb 27 '20 at 17:25
  • I can't see the same format used as I described. You are still passing data as `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]` . you need to pass `[{name: "Tone", data:[0, 91, 9]}]`. you need to write a transformer somthing like this - `const series = this.state.analysis.tonal.map((tonal) => tonal.value); ` – Abhishek Feb 27 '20 at 20:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/208695/discussion-between-henrydoe-and-abhishek). – henrydoe Feb 28 '20 at 10:51
0

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;
Viki Green
  • 54
  • 3
  • I even added `` in the BarChart component but it didn't do anything either. – henrydoe Feb 26 '20 at 17:34
  • you might need to make your Analysis into a React Component: import React from "react"; import BarCharts from "./BarCharts"; class Analysis extends React.Component { state = { analysis: { tonal: [{"tone": "negative", "value": 0}, {"tone": "neutral", "value": 91}, {"tone": "positive", "value": 9}], anxiety: [] } }; componentDidMount() { //some api call and data hanlding } render() { return entry.value)}/> // confused how to send the values as props here } }; – Viki Green Feb 26 '20 at 17:42
  • It is a function that is invoked when I click a button inside a component, I just took out the important bits from the entire component. – henrydoe Feb 26 '20 at 17:43