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!
+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 generateLegendI am using
react-chartjs-2with 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.
Most helpful comment
I've managed it by using the ref to the chart and the
generateLegendfunction. Here's my code if it helps anyone else...