6

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({

MomasVII
  • 4,641
  • 5
  • 35
  • 52

2 Answers2

5

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
Codebling
  • 10,764
  • 2
  • 38
  • 66
  • Thanks for the help. I started trying to implement but there is just so much to add. Is everything really necessary like `useDemoConfig`, `useLagRadar` etc. I was hoping for a really striped back version. I have a very minaml working example and to think I have to triple the code and add more dependencies to get color doesn't seem ideal. Without the errors I have updated my code to a working example ifit can be built from that. Sorry but I am not experienced enough to refactor your code. – MomasVII Jun 28 '21 at 07:19
  • 1
    @MomasVII You do not need `useLagRadar()` or `useDemoConfig()`. Triple what code? There are 3 charts in the example, delete the ones you don't need. Your question is asking to convert the example from Function Component to Class Component, and presents an attempt at converting it, which by your admission is broken. The code I present works and is error free. It includes the 3 examples from the example page you linked to (and none of your broken code, since you didn't explain what you were trying to do). Simply take what you need and leave the rest. – Codebling Jun 29 '21 at 17:15
  • 1
    @MomasVII let me know what else you're looking for, happy to help – Codebling Jul 02 '21 at 17:30
3

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

pavlovic265
  • 491
  • 2
  • 8
  • Thanks dude that is perfect. Can I ask why it needs 4 linearGradient elements? One seems to work but maybe i'm missing something. – MomasVII Jul 02 '21 at 04:00
  • There is no reason and you are not missing anything. I kept 4 lines as it was in the example. In data.json there are 4 `Series` representing each line, If you just need one you can just remove the series from data.json and remove `linearGradient`. – pavlovic265 Jul 02 '21 at 04:09