如何使用 Chart.js 检测轴标签的点击
在下面的示例中,我只能检测到图形本身的点击
您需要实现一个自定义插件来监听画布的所有事件:
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>
如果 X 轴上的标签有旋转,那么您可以尝试下面的代码。不要在此处运行代码,只需复制并在您的 IDE 中尝试即可。
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>
使用 ng2-charts (chart-js v3.7.1) 适配 Angular
只需使用Chart.register
即将以下函数放入组件 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)
}
}
}
}
);
}
更新了 stackblitz 中的示例 https://stackblitz.com/edit/ng2-charts-bar-template-qchyz6