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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:47:19