How to detect click on an axis label with chart.js
In the example bellow, I can only detect click on the graph itself
How to detect click on an axis label with chart.js
In the example bellow, I can only detect click on the graph itself
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>
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();
}
}
};
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>
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
© 2022 - 2024 — McMap. All rights reserved.