INFOSYS
INFOSYS

Reputation: 1585

How to get onClick Event for a Label in ChartJS and React?

I have a Radar chart with labels, I want to have a click event on the Label of the Radar chart but the element always returns null. I have looked at other Stack over flow questions notedly 1 and this 2. one talks about doing it in vanilla JS approach and other one just is not working for me , Can some one point me to what am i doing wrong ?

End goal -> I want to get the label which is clicked and add a strike through toggle to that label so that i can toggle the data point on and off in the radar chart.

My Implementation

class Chart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chartData: props.chartData
    };
  }
  render() {
    return (
      <div className="chart">
        <Radar
          data={this.state.chartData}
          options={{
            title: {
              display: true,
              text: "Testing",
              fontSize: 25
            },
            legend: {
              display: true,
              position: "right"
            },
            onClick: function(evt, element) {
              // onClickNot working element null
              console.log(evt, element);
              if (element.length > 0) {
                console.log(element, element[0]._datasetInde);
                // you can also get dataset of your selected element
                console.log(data.datasets[element[0]._datasetIndex]);
              }
            }
          }}
        />
      </div>
    );
  }
}

**Link to the sample implementation **

Upvotes: 4

Views: 3441

Answers (2)

bas
bas

Reputation: 15722

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.

Upvotes: 1

danielm2402
danielm2402

Reputation: 778

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);
              }
            }

Upvotes: 0

Related Questions