如何在Chart.js中同时使用chartjs-plugin-zoom与自定义插件?
Chart.js v4 + chartjs-plugin-zoom v2 与自定义插件共存配置方案
核心问题分析
- 自定义
hoverLine插件未完成注册流程,Chart.js无法识别执行 - 图表初始化时未传入完整
defaultOptions,导致插件配置不生效 - 自定义插件内画线逻辑错误(原代码画对角线而非垂直线)
解决方案步骤
1. 注册双插件(二选一即可)
Chart.js v4 要求插件必须注册才能生效,可选择全局或局部注册:
全局注册(所有图表生效)
在组件初始化时同时注册核心组件、zoom插件和自定义插件:
ngOnInit(): void { Chart.register(...registerables, zoomPlugin, this.hoverLine); this.createChart(); }
局部注册(仅当前图表生效)
创建图表时直接传入插件数组:
createChart() { this.chart = new Chart("serverChart", { type: this.chartType, data: { datasets: this.chartDataSets, labels: this.chartDataLabels || [], }, plugins: [zoomPlugin, this.hoverLine], // 局部注入插件 options: this.defaultOptions }); }
2. 补全图表初始化配置
原createChart方法未传入options,导致defaultOptions里的zoom配置完全失效,必须补充:
createChart() { this.chart = new Chart("serverChart", { type: this.chartType, data: { datasets: this.chartDataSets, labels: this.chartDataLabels || [], }, options: this.defaultOptions // 传入预设配置 }); }
3. 修复自定义hoverLine插件的画线逻辑
原代码会画对角线,若要实现鼠标悬停时的垂直参考线,修改如下:
hoverLine = { id: 'hoverLine', afterDatasetsDraw() { const { ctx, chartArea: { top, bottom }, tooltip, scales: { x }, } = this.chart; if(tooltip._active && tooltip._active.length > 0) { const xCord = x.getPixelForValue(tooltip.dataPoints[0].dataIndex); ctx.save(); ctx.beginPath(); ctx.lineWidth = 1; ctx.strokeStyle = "rgba(33, 33, 33, 1)"; // 绘制垂直线:从图表顶部到底部,x坐标固定为悬停点位置 ctx.moveTo(xCord, top); ctx.lineTo(xCord, bottom); ctx.stroke(); ctx.closePath(); ctx.restore(); // 恢复画布初始状态 } } }
4. 验证共存效果
完成修改后,两个插件会同时生效:
- Zoom插件按
defaultOptions.plugins.zoom配置实现X轴拖拽缩放 - hoverLine插件在鼠标悬停数据点时,自动绘制垂直参考线
完整组件代码示例
export class ChartComponent implements OnInit, OnChanges { public chart: any; @Input() chartTitle: string = "Traffic in bit/s"; @Input() displayTotal: { title: String; value: { value: number; unit: string } } = { title: "Total Traffic", value: { value: 1230, unit: "bit/s" } } @Input() chartType: ChartConfiguration["type"] = "line"; @Input() chartDataSets?: any; @Input() chartDataLabels?: any; @Input() chartOptions?; @Output() zoomComplete = new EventEmitter<{ min: number; max: number; }>(); // 修复后的自定义hoverLine插件 hoverLine = { id: 'hoverLine', afterDatasetsDraw() { const { ctx, chartArea: { top, bottom }, tooltip, scales: { x }, } = this.chart; if(tooltip._active && tooltip._active.length > 0) { const xCord = x.getPixelForValue(tooltip.dataPoints[0].dataIndex); ctx.save(); ctx.beginPath(); ctx.lineWidth = 1; ctx.strokeStyle = "rgba(33, 33, 33, 1)"; ctx.moveTo(xCord, top); ctx.lineTo(xCord, bottom); ctx.stroke(); ctx.closePath(); ctx.restore(); } } }; public defaultOptions = { elements: { line: { tension: 0.5, fill: "-1", }, point: { radius: 1, } }, scales: { y: { position: "left", stacked: true, min: 0, border: { dash: [8, 4], } }, x: { stacked: true, type: "time", time: { parser: 'dd - HH:mm', displayFormats: { hour: 'HH:mm', day: 'MMM-dd' }, }, grid: { display: false, }, offset: true, ticks: { major: { enabled: true, }, font: (context) => { return context.tick && context.tick.major ? { weight: 'bold', size: 14 } : ''; }, autoSkip: true, stepSize: 1, maxTicksLimit: 12, }, }, }, plugins: { zoom: { pan: { enabled: false, }, zoom: { drag: { enabled: true, }, mode: 'x', onZoomComplete: () => { const { min, max } = this.chart.scales.x; this.zoomComplete.emit({ min, max }); }, }, limits: { x: { min: 'original', max: 'original', minRange: 1000 * 60 * 10, }, }, }, }, }; constructor() {} ngOnInit(): void { Chart.register(...registerables, zoomPlugin, this.hoverLine); this.createChart(); } ngOnChanges(changes: SimpleChanges): void { if (this.chart) { this.chart.data.datasets = this.chartDataSets; this.chart.options = this.defaultOptions; this.chart.update(); } } createChart() { this.chart = new Chart("serverChart", { type: this.chartType, data: { datasets: this.chartDataSets, labels: this.chartDataLabels || [], }, options: this.defaultOptions }); } fetchData = () => { const { min, max } = this.chart.scales.x; this.zoomComplete.emit({ min, max }); } }
内容的提问来源于stack exchange,提问作者Liran_T
相关产品推荐
相关产品推荐

