[Chartjs]-How to get onClick Event for a Label in ChartJS and React?

1👍

Note: This answer implementation doesn’t implement strikethrough. Strikethrough could be implemented by putting unicode character \u0366 between each character of the label string. Here’s an example how do this with Javascript. The reason I’m not showcasing this here, is because it didn’t really look that great when I tested it on codesandbox.

In a newer version of chart.js radial scale point label positions were exposed. In the example below I’m using chart.js version 3.2.0 and react-chartjs-2 version 3.0.3.

We can use the bounding box of each label and the clicked position to determine if we’ve clicked on a label.

I’ve used a ref on the chart to get access to the label data.

I’ve chosen to set the data value corresponding to a label to 0. I do this, because if you were to remove an element corresponding to a label, the label would disappear along with it. My choice probably makes more sense if you see it in action in the demo below.

const swapPreviousCurrent = (data) => {
  const temp = data.currentValue;
  data.currentValue = data.previousValue;
  data.previousValue = temp;
};

class Chart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chartData: props.chartData
    };
    this.radarRef = {};
    this.labelsStrikeThrough = props.chartData.datasets.map((dataset) => {
      return dataset.data.map((d, dataIndex) => {
        return {
          data: {
            previousValue: 0,
            currentValue: d
          },
          label: {
            previousValue: `${props.chartData.labels[dataIndex]} (x)`,
            currentValue: props.chartData.labels[dataIndex]
          }
        };
      });
    });
  }

  render() {
    return (
      <div className="chart">
        <Radar
          ref={(radarRef) => (this.radarRef = radarRef)}
          data={this.state.chartData}
          options={{
            title: {
              display: true,
              text: "Testing",
              fontSize: 25
            },
            legend: {
              display: true,
              position: "right"
            }
          }}
          getElementAtEvent={(element, event) => {
            const clickX = event.nativeEvent.offsetX;
            const clickY = event.nativeEvent.offsetY;
            const scale = this.radarRef.scales.r;
            const pointLabelItems = scale._pointLabelItems;
            pointLabelItems.forEach((pointLabelItem, index) => {
              if (
                clickX >= pointLabelItem.left &&
                clickX <= pointLabelItem.right &&
                clickY >= pointLabelItem.top &&
                clickY <= pointLabelItem.bottom
              ) {
                // We've clicked inside the bounding box, swap labels and label data for each dataset
                this.radarRef.data.datasets.forEach((dataset, datasetIndex) => {
                  swapPreviousCurrent(
                    this.labelsStrikeThrough[datasetIndex][index].data
                  );
                  swapPreviousCurrent(
                    this.labelsStrikeThrough[datasetIndex][index].label
                  );

                  this.radarRef.data.datasets[datasetIndex].data[
                    index
                  ] = this.labelsStrikeThrough[datasetIndex][
                    index
                  ].data.previousValue;

                  this.radarRef.data.labels[index] = this.labelsStrikeThrough[
                    datasetIndex
                  ][index].label.previousValue;
                });
                // labels and data have been changed, update the graph
                this.radarRef.update();
              }
            });
          }}
        />
      </div>
    );
  }
}

So I use the ref on the chart to get acces to the label positions and I use the event of getElementAtEvent to get the clicked x and y positions using event.nativeEvent.offsetX and event.nativeEvent.offsetY.

When we’ve clicked on the label I’ve chosen to update the value of the ref and swap the label data value between 0 and its actual value. I swap the label itself between itself and itself concatenated with '(x)'.

sandbox example

The reason I’m not using state here is because I don’t want to rerender the chart when the label data updates.

0👍

You could run a function that modifies your dataset:

You would create the function where you have your data set

chartClick(index) {
    console.log(index);
    //this.setState({}) Modify your datasets properties
  }

Pass the function as props

<Chart chartClick={this.chartClick} chartData={this.state.chartData} />

Execute the function when clicked

onClick: (e, element) => {
              if (element.length) {
                this.props.chartClick(element[0]._datasetIndex);
              }
            }

Leave a comment