Convert react-charts funtion component example to class component

Viewed 390

Hey I'm having a lot of trouble trying to implement this example into my React project. The example is different to how I've set mine up and I can't convert it from their way to my way. I have a graph but I really would like the gradient colors to be included. This is as far as I got....

import React from 'react'
import { Chart } from 'react-charts'

class GraphClub extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      chartData: [
        {
          label: 'Won',
          data: [
            [0, 0],
          ],
        },
      ],
    }
  }


  componentDidMount() {
      //Get chartData
  }

  render() {
    return (
      <>
        <Chart
          data={this.state.chartData}
          axes={[
            {
              primary: true,
              type: 'linear',
              position: 'bottom',
              show: this.props.axis,
            },
            {
              type: 'linear',
              position: 'left',
              show: this.props.axis,
            },
          ]}
          series={{ type: 'line', showPoints: false }}
          tooltip
        />
      </>
    )
  }
}

export default GraphClub

The graph works but when I try and add the colors I can't get very far without getting errors. Would love some help.

Thanks

My first error is Error: Invalid hook call. Hooks can only be called inside of the body of a function component. Which is obvious seeing as it's a class component and not a functional on this line const [{ activeSeriesIndex, activeDatumIndex }, setState] = React.useState({

2 Answers

Preface

A couple of notes:

  • The conversion to Class Component was more complicated because the React Charts author seems to use and create hooks in a non-standard way. Either they are a genius or they are abusing the the hook system to use it as a glorified computational cache. Or both.
  • The whole React ecosystem is moving to Function Components. Maybe you have a very valid reason to use Class Components (interop with legacy code, I guess?), but if you're only sticking with Class Components simply because that's how you learned React, I strongly suggest that you bite the bullet and run through the Hooks/Function Component sections of the docs. Shouldn't take you long to learn, it will make your life easier, and you will have to do it eventually anyway.

Conversion to Class Component

First, start from the example code in the repo, not the snippet on that page, which already fails to compile.

Next, convert the Function Components one by one and verify each step of the way before you add your own code. I've uploaded my changes to a branch here.

The two custom hooks useLagRadar() and useDemoConfig() used by the example proved to be far too much effort to convert into Class Components, so I simply added them to a Higher-Order Component. To do this, I renamed the MyChart class to MyChartInner and made a new Function Component HOC called MyChart which uses the hooks mentioned above.

import React from "react";
import ReactDOM from "react-dom";

import { Chart } from "react-charts";

import useDemoConfig from "./useDemoConfig";
import useLagRadar from "./useLagRadar";
import ResizableBox from "./ResizableBox";
import "./styles.css";

export default class App  extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeSeriesIndex: -1,
      activeDatumIndex: -1,
    };
  }   
  
  
  render() {
    const {
      activeSeriesIndex, 
      activeDatumIndex,
    } = this.state;

    const setState = newState => this.setState(newState);

    return (
      <div>
        {JSON.stringify({ activeSeriesIndex, activeDatumIndex }, null, 2)}
        <MyChart
          elementType="line"
          setState={setState}
          activeDatumIndex={activeDatumIndex}
          activeSeriesIndex={activeSeriesIndex}
        />
        <MyChart
          elementType="area"
          setState={setState}
          activeDatumIndex={activeDatumIndex}
          activeSeriesIndex={activeSeriesIndex}
        />
        <MyChart
          elementType="bar"
          setState={setState}
          activeDatumIndex={activeDatumIndex}
          activeSeriesIndex={activeSeriesIndex}
        />
      </div>
    );
  }
}

const MyChart = props => {
  useLagRadar();
  // const { data, grouping, randomizeData } = useDemoConfig({
  const demoConfig = useDemoConfig({
    series: 4,
    height: 200,
    grouping: "primary",
    dataType: "ordinal",
    show: ["elementType", "grouping"]
  });
  return (
    <MyChartInner 
      {...demoConfig}
      {...props}
    />
  );
}

class MyChartInner extends React.Component {
  constructor(props) {
    super(props);
  }    

  render() {
    const {
      elementType,
      activeDatumIndex,
      activeSeriesIndex,
      setState
    } = this.props; 

    //useLagRadar();

    
    const { data, grouping, randomizeData } = this.props;
  

    const series = {
      type: elementType
    };

    const axes = [
      {
        primary: true,
        type: "ordinal",
        position: "bottom"
      },
      {
        type: "linear",
        position: "left",
        stacked: true
      }
    ];

    const getSeriesStyle = 
      series => ({
        color: `url(#${series.index % 4})`,
        opacity:
          activeSeriesIndex > -1
            ? series.index === activeSeriesIndex
              ? 1
              : 0.3
            : 1
      });

    const getDatumStyle = 
      datum => ({
        r:
          activeDatumIndex === datum.index &&
          activeSeriesIndex === datum.seriesIndex
            ? 7
            : activeDatumIndex === datum.index
            ? 5
            : datum.series.index === activeSeriesIndex
            ? 3
            : datum.otherHovered
            ? 2
            : 2
      });

    const onFocus = 
      focused =>
        setState({
          activeSeriesIndex: focused ? focused.series.id : -1,
          activeDatumIndex: focused ? focused.index : -1
        });

    return (
      <>
        <button onClick={randomizeData}>Randomize Data</button>
        <br />
        <br />
        <ResizableBox>
          <Chart
            data={data}
            grouping={grouping}
            series={series}
            axes={axes}
            getSeriesStyle={getSeriesStyle}
            getDatumStyle={getDatumStyle}
            onFocus={onFocus}
            tooltip
            renderSVG={() => (
              <defs>
                <linearGradient id="0" x1="0" x2="0" y1="1" y2="0">
                  <stop offset="0%" stopColor="#17EAD9" />
                  <stop offset="100%" stopColor="#6078EA" />
                </linearGradient>
                <linearGradient id="1" x1="0" x2="0" y1="1" y2="0">
                  <stop offset="0%" stopColor="#FCE38A" />
                  <stop offset="100%" stopColor="#F38181" />
                </linearGradient>
                <linearGradient id="2" x1="0" x2="0" y1="1" y2="0">
                  <stop offset="0%" stopColor="#42E695" />
                  <stop offset="100%" stopColor="#3BB2B8" />
                </linearGradient>
                <linearGradient id="3" x1="0" x2="0" y1="1" y2="0">
                  <stop offset="0%" stopColor="#F4Ea0A" />
                  <stop offset="100%" stopColor="#df4081" />
                </linearGradient>
              </defs>
            )}
          />
        </ResizableBox>
      </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

To run:

# Clone the repo and checkout the branch
git clone --branch stackoverflow-q68091135 https://github.com/codebling/react-charts.git

# Switch to the "custom-styles" example directory
cd react-charts/examples/custom-styles/

# Install the dependencies
npm i 

# Run the demo
npm start

Hope this is what you were looking for. This is taken from here, as by first glance same as a first comment, but simplifying their version and taking only a few things that are needed for drawing gradient lines.

Here is example in codesandbox

import React from "react";
import ReactDOM from "react-dom";

import { Chart } from "react-charts";

import jsonData from "./data.json";

class GraphClub extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: jsonData,
      axes: [
        {
          primary: true,
          type: "ordinal",
          position: "bottom"
        },
        {
          type: "linear",
          position: "left",
          stacked: true
        }
      ]
    };
  }

  getSeriesStyle(series) {
    return {
      color: `url(#${series.index % 4})`,
      opacity: 1
    };
  }

  render() {
    const { data, axes } = this.state;
    return (
      <div style={{ width: "500px", height: "300px" }}>
        <Chart
          data={data}
          axes={axes}
          getSeriesStyle={this.getSeriesStyle}
          tooltip
          renderSVG={() => (
            <defs>
              <linearGradient id="0" x1="0" x2="0" y1="1" y2="0">
                <stop offset="0%" stopColor="#df4081" />
                <stop offset="100%" stopColor="#42E695" />
              </linearGradient>
              <linearGradient id="1" x1="0" x2="0" y1="1" y2="0">
                <stop offset="0%" stopColor="#df4081" />
                <stop offset="100%" stopColor="#42E695" />
              </linearGradient>
              <linearGradient id="2" x1="0" x2="0" y1="1" y2="0">
                <stop offset="0%" stopColor="#df4081" />
                <stop offset="100%" stopColor="#42E695" />
              </linearGradient>
              <linearGradient id="3" x1="0" x2="0" y1="1" y2="0">
                <stop offset="0%" stopColor="#df4081" />
                <stop offset="100%" stopColor="#42E695" />
              </linearGradient>
            </defs>
          )}
        />
      </div>
    );
  }
}

export default function App() {
  return <GraphClub />;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You can find data.json in codesandbox

Let me know if this works for you.

Edit: Convert it to class component and in a sandbox, I do not get any error

Related