You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 12:22:02