React-chartjs-2: How to create custom legend?

Created on 14 Dec 2016  路  19Comments  路  Source: reactchartjs/react-chartjs-2

How would one use the legendCallback method with this wrapper? The Chart.js docs show the function returning an HTML string, which is of course not the React way.

Thank you for your assistance!

Most helpful comment

I've managed it by using the ref to the chart and the generateLegend function. Here's my code if it helps anyone else...

import React, { Component } from 'react';
import { Line as LineChart, defaults } from 'react-chartjs-2';
import { Parser as HtmlToReactParser } from 'html-to-react';

const htmlToReactParser = new HtmlToReactParser();

defaults.global.animation = false;

const options = {
  legend: {
    display: false,
  },
};

class LineChartComponent extends Component {
  componentDidMount() {
    this.forceUpdate();
  }

  render() {
    return (
      <div>
        <LineChart data={this.props.data} options={options} ref="chart" />
        {this.refs.chart && htmlToReactParser.parse(this.refs.chart.chart_instance.generateLegend())}
      </div>
    );
  }
}

LineChartComponent.propTypes = {
  data: React.PropTypes.object,
};

export default LineChartComponent;

All 19 comments

+1

+1

Can you give an example on codepen on how this would work with vanilla? I can't seem to get an example working. :(

Yeah I can't get this to work either

Seems legend is never used...

index.js

  renderChart() {
    const {data, options, legend, type, redraw} = this.props;
    const node = ReactDOM.findDOMNode(this);

    this.memoizeDataProps();

    this.chart_instance = new Chart(node, {
      type,
      data,
      options
    });
  }

I've managed it by using the ref to the chart and the generateLegend function. Here's my code if it helps anyone else...

import React, { Component } from 'react';
import { Line as LineChart, defaults } from 'react-chartjs-2';
import { Parser as HtmlToReactParser } from 'html-to-react';

const htmlToReactParser = new HtmlToReactParser();

defaults.global.animation = false;

const options = {
  legend: {
    display: false,
  },
};

class LineChartComponent extends Component {
  componentDidMount() {
    this.forceUpdate();
  }

  render() {
    return (
      <div>
        <LineChart data={this.props.data} options={options} ref="chart" />
        {this.refs.chart && htmlToReactParser.parse(this.refs.chart.chart_instance.generateLegend())}
      </div>
    );
  }
}

LineChartComponent.propTypes = {
  data: React.PropTypes.object,
};

export default LineChartComponent;

@mattdell thanks for the code snippet, but I can't get it working.

No idea why, but in my case this.refs is empty wherever I call it. Instead, what I did to make things work is ref={c => c.chart_instance.generateLegend()} - does the job and options.legendCallback() gets called properly.

@isld two things

1) Ensure ref="chart" is an attribute of your chart. Otherwise refs will always be empty.
2) refs will be empty on the first render, so you'll need to forceUpdate() on componentDidMount() to pass the undefined check on refs in the render method.

Those are the two things that come to mind, but your solution should also work just as fine.

EDIT: I see now my code doesn't have the componentDidMount() method. :( I'll edit that now!

Updated now. Did it on my phone so hopefully there aren't formatting issues or typos but should work now! 馃憤馃徎

it's this.refs.chart.chartInstance.generateLegend() for me, but thank you much!

hi guys, after legendCallback excute by c.chartInstance.generateLegend()
and in console it return HTML text as following:

<ul className="0-legend"><li><span style={{backgroundColor:#FF6384}}></span>Swift 103%</li><li><span style={{backgroundColor:#36A2EB}}></span>Energy Swift 20%</li><li><span style={{backgroundColor:#FFCE56}}></span>Swift Focus 18%</li><li><span style={{backgroundColor:#009100}}></span>others26%</li></ul>

My <Pie>'s custom legend didn't show. Any idea?

my legendCallback as following

legendCallback: function (chart) {
           var text = [];
           text.push('<ul className="' + chart.id + '-legend">');
               for (var i = 0; i < chart.data.datasets[0].data.length; i++) {
                      text.push('<li><span style={{backgroundColor:' + chart.data.datasets[0].backgroundColor[i] + '}}></span>');
                      if (chart.data.labels[i]) {
                          text.push(chart.data.labels[i] + chart.data.datasets[0].data[i] + "%");
                          }
                         text.push('</li>');
                      }
                      text.push('</ul>');
                      console.log(text.join(''));
                      return text.join('');
             }

my legend display is false
legend: { display: false, }

Thank your for reading and helping!

legendCallback function is called with generateLegend()
var legend = chart_var.generateLegend();
and it needs to be initialized somewhere around canvas element like i have placed before canvas
canvas.before(legend);

@kctann are you able to solve this issue? i cant get it to work even after calling generateLegend()

I wonder why one even need generateLegend since it doesn't even do any interaction with the chart (clicking on legend items won't toggle lines) so, why not simply construct your own legend and somehow bind events to toggle lines and save the trouble of all this "hacking", forcing generateLegend to work

This is using React 16.3+ createRef() + Typescript. You can remove the Typescript parts if you want.
I did this in an hour-ish so forgive the messy code and magic variable names. I am using React 16's createRef() so make sure you got the latest React 16.3+ installed.

`
import * as React from 'react'
import { Doughnut } from 'react-chartjs-2';
import { sumBy } from 'lodash';

export const valueAsPercentage = (value, total) => ${value / total * 100}%;

type State= {
legend: string|any
}

export class Experiment extends React.Component {

myRef:any;

constructor(props: any) {
    super(props);

    this.myRef = null;
    this.state= {legend: <>no legend</>}


    this.createMarkup = this.createMarkup.bind(this);

}

componentDidMount() {

    const leg = this.generateLegend();
    this.setState({legend:leg});

}

setTextInputRef(element){

    this.myRef = element;
}

generateLegend(){
    if(!this.myRef) return null;
    return (this.myRef as any).chartInstance.generateLegend();

}

createMarkup() { 

    return {__html: this.state.legend}; 

};



render() {
    return <>
        <Doughnut
             ref={(element) => this.setTextInputRef(element)}
            data={{
                labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
                datasets: [{
                    label: '# of Votes',
                    data: [12, 19, 3, 5, 2, 3],
                    backgroundColor: [
                        'rgba(255, 99, 132, 0.2)',
                        'rgba(54, 162, 235, 0.2)',
                        'rgba(255, 206, 86, 0.2)',
                        'rgba(75, 192, 192, 0.2)',
                        'rgba(153, 102, 255, 0.2)',
                        'rgba(255, 159, 64, 0.2)'
                    ],
                    borderColor: [
                        'rgba(255,99,132,1)',
                        'rgba(54, 162, 235, 1)',
                        'rgba(255, 206, 86, 1)',
                        'rgba(75, 192, 192, 1)',
                        'rgba(153, 102, 255, 1)',
                        'rgba(255, 159, 64, 1)'
                    ],
                    borderWidth: 1
                }]
            }}
            options={{
                legend: {
                    display: true,
                },
                legendCallback: (chart) => {

                    const dataset = chart.data.datasets[0];
                     const total = sumBy(dataset.data, number => parseInt(number as any, 10));

                    return dataset.data.map((value, index) => {
                        const outputValue = dataset.percentage ? valueAsPercentage(value, total) : value;
                        return `<li>
                            <em>${outputValue}</em>
                        </li>`;
                    }).join('');                    
                }
            }}



        />

        <div dangerouslySetInnerHTML={this.createMarkup()} />

    </>

}

}

`

For me it was I did:

if (this.refs.chart && this.refs.chart.chartInstance && this.refs.chart.chartInstance.generateLegend) {
  // use generateLegend

I am using react-chartjs-2 with TS. I needed to use // @ts-ignore

For me it was I did:

if (this.refs.chart && this.refs.chart.chartInstance && this.refs.chart.chartInstance.generateLegend) {
  // use generateLegend

I am using react-chartjs-2 with TS. I needed to use // @ts-ignore

Can you give an example on codepen.

Here is my approach without generateLegend method. Thanks to this, I also do not need to use React's dangerouslySetInnerHTML so I have full control on my legend.

I've used this approach to achieve onClick events on legend because with dangerouslySetInnerHTML.

const SalesChart = (props: ChartProps) => {
  const inputEl: any = useRef(null);
  const [legendRendered, setLegendRendered] = useState(false);
  const [, forceUpdate] = useState();
  const classes = useStyles();

  const handleLegendClick = (datasetIndex: number) => {
    const chart = inputEl.current.chartInstance;
    chart.getDatasetMeta(datasetIndex).hidden =
      chart.getDatasetMeta(datasetIndex).hidden === null ? true : !chart.getDatasetMeta(datasetIndex).hidden;
    chart.update(); // re-draw chart to hide dataset
    forceUpdate({}); // re-draw component to update legend styles
  };

 ( ...... )

  const plugins = [
    {
      afterDatasetsDraw() {
        // hack to force re-render component in order to show legend
        if (!legendRendered) {
          setLegendRendered(true);
        }
      },
    },
  ];

    <React.Fragment>
      <div>
        <Line ref={inputEl} plugins={plugins} options={options} height={300} data={chart} />
      </div>

      {legendRendered && (
        <div
          style={{
            paddingLeft: `${inputEl.current.chartInstance.boxes[3].maxWidth}px`,
            paddingRight: `${inputEl.current.chartInstance.boxes[4].maxWidth}px`,
          }}
        >
          <ul className={classes.legendWrapper}>
            {inputEl.current.chartInstance.legend.legendItems.map((tick: ChartLegendLabelItem) => {
              const chart = inputEl.current.chartInstance;
              const isHidden = chart.getDatasetMeta(tick.datasetIndex).hidden;

              const bgColor: string =
                typeof tick.fillStyle === 'string'
                  ? tick.fillStyle // lines
                  : 'linear-gradient(to bottom, #13B080, rgba(23,172,126,0.4))'; // canvas gradient

              return (
                <li
                  className={`${classes.legendItem}`}
                  key={tick.datasetIndex}
                  onClick={() => handleLegendClick(tick.datasetIndex)}
                >
                  <div
                    className={`${isHidden ? classes.legendItemHidden : ''} ${classes.legendIcon}`}
                    style={{ background: bgColor }}
                  />
                  <Typography
                    variant="body1"
                    className={`${isHidden ? classes.legendItemHidden : ''} ${classes.legendItemText}`}
                  >
                    {tick.text}
                  </Typography>
                </li>
              );
            })}
          </ul>
        </div>
      )}
    </React.Fragment>

This is the approach we used for creating a custom legend. We accessed the chartInstance through a ref to the component and got the legend items:
ref.chartInstance.legend.legendItems
example: https://codesandbox.io/s/react-chartjs-2-example-7fkno

Closing for now. Please refer to the Chart.js documentation for questions around configuration and/or usage of the library. If you have a bug or enhancement related directly to Chart.js, please open an issue on their Github.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Holychung picture Holychung  路  4Comments

cbroberg picture cbroberg  路  5Comments

pkellner picture pkellner  路  4Comments

alphakennyn picture alphakennyn  路  3Comments

n1c01a5 picture n1c01a5  路  4Comments