How to detect click on chart js 3.7.1 axis label?
Asked Answered
S

4

6

How to detect click on an axis label with chart.js

In the example bellow, I can only detect click on the graph itself

https://stackblitz.com/edit/ng2-charts-bar-template-qchyz6

Saturninasaturnine answered 25/3, 2022 at 10:38 Comment(0)
K
8

You will need to implement a custom plugin that can listen to all the events of the canvas:

const findLabel = (labels, evt) => {
  let found = false;
  let res = null;

  labels.forEach(l => {
    l.labels.forEach((label, index) => {
      if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
        res = {
          label: label.label,
          index
        };
        found = true;
      }
    });
  });

  return [found, res];
};

const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
  scaleId: s.id,
  labels: s._labelItems.map((e, i) => ({
    x: e.translation[0] - s._labelSizes.widths[i],
    x2: e.translation[0] + s._labelSizes.widths[i] / 2,
    y: e.translation[1] - s._labelSizes.heights[i] / 2,
    y2: e.translation[1] + s._labelSizes.heights[i] / 2,
    label: e.label,
    index: i
  }))
})));

const plugin = {
  id: 'customHover',
  afterEvent: (chart, event, opts) => {
    const evt = event.event;

    if (evt.type !== 'click') {
      return;
    }

    const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);

    if (found) {
      console.log(labelInfo);
    }

  }
}

Chart.register(plugin);

const options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderColor: 'pink'
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderColor: 'orange'
      }
    ]
  },
  options: {}
}

const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
</body>
Kafir answered 25/3, 2022 at 11:1 Comment(2)
Works fine. Any idea to adapt the code to an Angular project using ng2-charts ?Saturninasaturnine
Should work fine in ng2-charts since I register the plugin globally on the chart object and that is supported in every wrapper since it uses chart.js directly and does not interact with the wrapperKafir
V
1

Update to make it work with ChartJs 4.4.2:

const findLabel = (labels, evt) => {
    let found = false;
    let res = null;
    labels.forEach(label => {
        if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
            res = {
                label: label.label,
                index: label.index
            };
            found = true;
        }
    });
    return [found, res];
};


const getLabelHitBoxes = (x) => {
    return x._labelItems.map((e, i) => ({
        x: e.options.translation[0] - x._labelSizes.widths[i],
        x2: e.options.translation[0] + x._labelSizes.widths[i] / 2,
        y: e.options.translation[1] - x._labelSizes.heights[i] / 2,
        y2: e.options.translation[1] + x._labelSizes.heights[i] / 2,
        label: e.label,
        index: i
    }));
};

const plugin = {
    id: 'chartClickXLabel',
    afterEvent: (chart, event, opts) => {
        const evt = event.event;

        if (evt.type !== 'click') {
            return;
        }

        const [found, labelInfo] = findLabel(getLabelHitBoxes(chart.scales.x), evt);

        if (found) {
            console.log(labelInfo);
            // opts.listeners.click();
        }

    }
};
Varese answered 2/4 at 15:33 Comment(0)
O
0

If your X-axis labels have rotation, then you can try the below code:

import {Chart as ChartJS} from 'chart.js';

// Function to calculate cotangent (ctg) from radians
const ctg = (radians: number): number => 1 / Math.tan(radians);

const findLabel = (labels: any, evt: any): LabelData | undefined => {
  let res: LabelData | undefined = undefined;
  const scalesYwidth = evt.chart.scales.y.width;
  const scalesXheight = evt.chart.scales.x.height;
  const eventY = evt.y - (evt.chart.height - scalesXheight);
  let min = Infinity;

  for (const l of labels) {
    for (const index in l.labels) {
      const label = l.labels[index];

      if (eventY > 10 && evt.x > scalesYwidth - 10) {
        if (evt.x > label.x && evt.x < label.x2) {
          const b = Math.abs(eventY * ctg(label.rotation));
          const xClosest = Math.abs(label.x2 - (evt.x + b));
     
          if (xClosest < min) {
            min = xClosest;
            res = {
              label: label.label,
              index: +index,
              axis: 'x'
            };
          }
        }
      } else if (evt.x < scalesYwidth - 10 && evt.y > label.y && evt.y < label.y2) {
        res = {
          label: label.label,
          index: +index,
          axis: 'y'
        };
      }
    }
  }

  return res;
};

const getLabelHitboxes = (scales: any) =>
  Object.values(scales).map((s: any) => {
    return {
      scaleId: s.id,
      labels: s._labelItems.map((e: any, i: number) => {
        const ts = e.options.translation;
        return {
          x: ts[0] - s._labelSizes.widths[i],
          x2: ts[0] + s._labelSizes.widths[i] / 2,
          y: ts[1] - s._labelSizes.heights[i] / 2,
          y2: ts[1] + s._labelSizes.heights[i] / 2,
          rotation: e.options.rotation,
          label: e.label,
          index: i
        };
      })
    };
  });

const getLabelEventData = (chart: ChartJS, chartEvent: any): LabelData | undefined =>
  findLabel(getLabelHitboxes(chart.scales), chartEvent);

export const getChartPluginLabelClick = (onClick?: (data?: LabelData) => void) => ({
  id: 'customHover',
  afterEvent: (chart: ChartJS, event: any, opts: any) => {
    const evt = event.event;
    if (evt.type !== 'click') return;
    const data = getLabelEventData(chart, evt);
    onClick && onClick(data);
  }
});
// register global
// ChartJS.register(getPlugin());

export interface LabelData {
  label: string | string[];
  index: number;
  axis: 'x' | 'y';
}
import {Chart as ChartJS} from 'chart.js';
import {Chart} from 'react-chartjs-2';
import 'chart.js/auto';

<div style={{position: 'relative', height: '600px', width: '100%'}}>
        <Chart
          ref={ref}
          type="bar"
          plugins={[
            getChartPluginLabelClick((data?: LabelData) => {
              console.log(data);
            })
          ]}
          options={options}
          data={data}
          onClick={onClick}
        />
</div>
Othelia answered 25/1 at 20:33 Comment(0)
S
-1

Adpatation for Angular with ng2-charts (chart-js v3.7.1)

Just use Chart.register

i.e. put the following functiion into component ngOnInit()

RegisterPlugin() {
    Chart.register(
      {
        id: 'yAxisCustomClick',
        afterEvent: (chart: Chart<'bar'>, event: {
          event: ChartEvent;
          replay: boolean;
          changed?: boolean | undefined;
          cancelable: false;
          inChartArea: boolean
        }) => {
          const evt = event.event;
          if (evt.type === 'click' && evt.x! < Object.values(chart.scales).filter(s => s.id === 'x')[0].getBasePixel()) {
            const labelIndex = Object.values(chart.scales).filter(s => s.id === 'y')[0].getValueForPixel(evt.y!);
            const label = Object.values(chart.scales).filter(s => s.id === 'y')[0].getTicks()[labelIndex!]?.label;
            if (label) {
              console.log('Do the stuff for', label)
            }
          }
        }
      }
    );
  }

Example in stackblitz udpated https://stackblitz.com/edit/ng2-charts-bar-template-qchyz6

Saturninasaturnine answered 26/3, 2022 at 14:42 Comment(1)
this detects clicks on the bars, not on the labelsKafir

© 2022 - 2024 — McMap. All rights reserved.