如何在ng2-charts中实现鼠标悬停图例高亮对应图表系列?
实现ng2-charts图例悬停高亮对应系列
针对你使用的chart.js 4.2.1、ng2-charts 4.1.1版本,要实现鼠标悬停图例标签时高亮对应系列的功能,需借助Chart.js的图例钩子方法,结合Angular组件的图表实例操作完成,具体步骤和代码修改如下:
1. 导入必要依赖
在组件中新增BaseChartDirective导入,用于获取ng2-charts的图表实例:
import { BaseChartDirective } from 'ng2-charts';
2. 获取图表实例
替换原canvas相关的ViewChild,改为获取BaseChartDirective实例:
@ViewChild(BaseChartDirective) chart: BaseChartDirective; // 移除以下原代码: // @ViewChild('canvas') // canvas: ElementRef<HTMLCanvasElement>; // public getCanvas(): HTMLCanvasElement { // return this.canvas.nativeElement; // }
3. 保存数据集原始样式
在fetchData方法中,给每个数据集添加原始样式存储,用于后续恢复状态:
for (let el of barChartDataMap.keys()) { let data = barChartDataMap.get(el); let dataset = {}; dataset['data'] = data; dataset['label'] = el['caption']; dataset['barThickness'] = 20; dataset['borderRadius'] = 5; dataset['borderColor'] = "rgba(0,0,0,0)"; dataset['borderWidth'] = 2; dataset['backgroundColor'] = el['color']; // 保存原始背景色与边框色 dataset['originalBackgroundColor'] = el['color']; dataset['originalBorderColor'] = "rgba(0,0,0,0)"; datasets.push(dataset); }
4. 配置图例交互钩子
修改barChartOptions,添加legend.onHover和legend.onLeave回调,实现悬停高亮与离开恢复:
public barChartOptions: ChartOptions = { responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { onHover: (event, legendItem) => { const chartInstance = this.chart?.chart; if (!chartInstance) return; // 遍历所有数据集,调整样式 chartInstance.data.datasets.forEach((dataset, index) => { if (index === legendItem.datasetIndex) { // 高亮当前系列:加深颜色+添加边框 dataset.backgroundColor = this.lightenColor(dataset.originalBackgroundColor, -20); dataset.borderColor = 'rgba(0,0,0,0.3)'; } else { // 淡化其他系列 dataset.backgroundColor = this.lightenColor(dataset.originalBackgroundColor, 40); dataset.borderColor = 'rgba(0,0,0,0)'; } }); chartInstance.update(); }, onLeave: (event, legendItem) => { const chartInstance = this.chart?.chart; if (!chartInstance) return; // 恢复所有数据集的原始样式 chartInstance.data.datasets.forEach(dataset => { dataset.backgroundColor = dataset.originalBackgroundColor; dataset.borderColor = dataset.originalBorderColor; }); chartInstance.update(); } } } }
5. 添加颜色调整辅助方法
在组件内新增颜色亮度调整方法,适配rgba和十六进制两种颜色格式:
// delta为正颜色变亮,为负颜色变暗 private lightenColor(color: string, delta: number): string { // 处理rgba格式 if (color.startsWith('rgba')) { const rgba = color.match(/\d+/g); if (!rgba || rgba.length < 3) return color; let r = parseInt(rgba[0]), g = parseInt(rgba[1]), b = parseInt(rgba[2]); r = Math.max(0, Math.min(255, r + delta)); g = Math.max(0, Math.min(255, g + delta)); b = Math.max(0, Math.min(255, b + delta)); return `rgba(${r}, ${g}, ${b}, ${rgba[3] || 1})`; } // 处理十六进制格式 if (color.startsWith('#')) { color = color.slice(1); let r = parseInt(color.slice(0, 2), 16); let g = parseInt(color.slice(2, 4), 16); let b = parseInt(color.slice(4, 6), 16); r = Math.max(0, Math.min(255, r + delta)); g = Math.max(0, Math.min(255, g + delta)); b = Math.max(0, Math.min(255, b + delta)); return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; } return color; }
完整修改后的组件代码
整合所有修改后的完整组件代码如下:
import { Component, Input, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Chart, ChartData, ChartOptions, ChartType } from 'chart.js'; import { ListService } from '../../services/component-services/list.service'; import { BaseChartDirective } from 'ng2-charts'; @Component({ selector: 'app-bar-chart', templateUrl: './bar-chart.component.html', styleUrls: ['./bar-chart.component.scss'], }) export class BarChartComponent implements OnInit, OnDestroy { subscriptions = []; @Input() guiConfig; @Input() minValue; @Input() maxValue; @ViewChild(BaseChartDirective) chart: BaseChartDirective; ngOnDestroy(): void { this.subscriptions.forEach((sub) => { sub.unsubscribe(); }); } public barChartType: ChartType = 'bar'; public barChartData: ChartData<'bar'>; constructor(private listService: ListService) { } ngOnInit() { this.fetchData(); } fetchData() { let sub = this.listService.getBehaviorSubject(this.guiConfig['dataId']).subscribe((data) => { let barChartDataMap: Map<JSON, object[]> = new Map<JSON, object[]>(); let labels = []; let values = []; let captions: JSON[] = []; if (data !== undefined && data.length !== 0) { for (let index = 0; index < data.length; index++) { const element = data[index]; if (!labels.includes(element['xValue'])) { labels.push(element['xValue']); } values.push(element['yValue']); let caption = element['caption']; let jsonObj = {}; jsonObj['caption'] = caption; jsonObj['color'] = element['color']; let extistsKey = captions.find((caption) => { return JSON.stringify(caption) == JSON.stringify(jsonObj); }); if (!extistsKey) { captions.push(<JSON>jsonObj); } } let datasets = []; for (let index = 0; index < captions.length; index++) { const caption = captions[index]; let dataArray = []; for (let innerIndex = 0; innerIndex < data.length; innerIndex++) { const dataPoint = data[innerIndex]; if (dataPoint['caption'] == caption['caption']) { dataArray.push(dataPoint['yValue']); } } barChartDataMap.set(caption, dataArray); } for (let el of barChartDataMap.keys()) { let data = barChartDataMap.get(el); let dataset = {}; dataset['data'] = data; dataset['label'] = el['caption']; dataset['barThickness'] = 20; dataset['borderRadius'] = 5; dataset['borderColor'] = "rgba(0,0,0,0)"; dataset['borderWidth'] = 2; dataset['backgroundColor'] = el['color']; // 保存原始样式 dataset['originalBackgroundColor'] = el['color']; dataset['originalBorderColor'] = "rgba(0,0,0,0)"; datasets.push(dataset); } let barChartData: ChartData<'bar'> = { datasets: datasets, labels: labels } this.barChartData = barChartData; } }); this.subscriptions.push(sub); } public barChartOptions: ChartOptions = { responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { onHover: (event, legendItem) => { const chartInstance = this.chart?.chart; if (!chartInstance) return; chartInstance.data.datasets.forEach((dataset, index) => { if (index === legendItem.datasetIndex) { dataset.backgroundColor = this.lightenColor(dataset.originalBackgroundColor, -20); dataset.borderColor = 'rgba(0,0,0,0.3)'; } else { dataset.backgroundColor = this.lightenColor(dataset.originalBackgroundColor, 40); dataset.borderColor = 'rgba(0,0,0,0)'; } }); chartInstance.update(); }, onLeave: (event, legendItem) => { const chartInstance = this.chart?.chart; if (!chartInstance) return; chartInstance.data.datasets.forEach(dataset => { dataset.backgroundColor = dataset.originalBackgroundColor; dataset.borderColor = dataset.originalBorderColor; }); chartInstance.update(); } } } } private lightenColor(color: string, delta: number): string { if (color.startsWith('rgba')) { const rgba = color.match(/\d+/g); if (!rgba || rgba.length < 3) return color; let r = parseInt(rgba[0]), g = parseInt(rgba[1]), b = parseInt(rgba[2]); r = Math.max(0, Math.min(255, r + delta)); g = Math.max(0, Math.min(255, g + delta)); b = Math.max(0, Math.min(255, b + delta)); return `rgba(${r}, ${g}, ${b}, ${rgba[3] || 1})`; } if (color.startsWith('#')) { color = color.slice(1); let r = parseInt(color.slice(0, 2), 16); let g = parseInt(color.slice(2, 4), 16); let b = parseInt(color.slice(4, 6), 16); r = Math.max(0, Math.min(255, r + delta)); g = Math.max(0, Math.min(255, g + delta)); b = Math.max(0, Math.min(255, b + delta)); return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; } return color; } }
关键说明
- 借助Chart.js的图例钩子方法,在鼠标交互时直接修改数据集样式
- 保存原始样式确保鼠标离开图例后能恢复初始状态
- 通过
chart.update()触发图表重绘,实时应用样式变化 - 颜色调整方法兼容两种常见颜色格式,适配不同配置场景
内容的提问来源于stack exchange,提问作者user17182037
相关产品推荐
相关产品推荐

