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

Angular+Highcharts:绘制连接实时时间与固定点的动态带箭头圆形

Angular + Highcharts 实现动态带箭头圆形方案

核心思路

利用 Highcharts 的 renderer API 绘制自定义图形(圆形+箭头),通过定时器每秒更新当前时间点坐标,重绘图形实现动态效果;箭头长度通过独立变量控制,不依赖两点间距。


1. 组件内定义关键变量

import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import * as Highcharts from 'highcharts';
import { HighchartsChartComponent } from 'highcharts-angular';

@Component({
  selector: 'app-chart-component',
  templateUrl: './chart-component.component.html'
})
export class ChartComponent implements OnInit, OnDestroy {
  @ViewChild('chartRef') chartRef!: HighchartsChartComponent;
  Highcharts = Highcharts;

  // 固定点配置(X为时间戳,Y为数值)
  fixedPoint = { x: 1717200000000, y: 25 };
  // 独立控制左右箭头长度
  leftArrowLength = 20;
  rightArrowLength = 30;
  // 存储绘制的图形元素,用于更新时销毁
  circleElement: Highcharts.SVGElement | null = null;
  leftArrowElement: Highcharts.SVGElement | null = null;
  rightArrowElement: Highcharts.SVGElement | null = null;
  // 实时更新定时器
  updateInterval: number | null = null;

  // 你的现有xAxis、yAxis配置
  chartOptions: Highcharts.Options = {
    xAxis: {
      type: 'datetime',
      lineWidth: 0,
      tickPositions: [...this.tickPositions],
      labels: {
        x: 0,
        y: chartStyles.y,
        formatter(): any {
          let dateLabel = Highcharts.dateFormat('%H:%M', new Date(this.value));
          return (
            '<span style="width:10px; height:24px; color:var(--chart-text-color); background-color: var(--rectangle-bg-color);  padding: 3px 4px;margin-top:90px; margin-bottom:10px; border-radius:4px;">' +
            dateLabel +
            '</span>'
          );
        },
        useHTML: true,
      },
      min: this.slotsStart(),
      max: this.slotsEnd(),
      gridLineColor: '#e6e6e6',
      gridLineWidth: 0.5,
      plotLines: [
        {
          name: 'Current Time',
          color: 'var(--default-text-color)',
          width: 2,
          value: this.getCurrentDate(),
          zIndex: 6,
          label: {
            align: 'bottom',
            x: -22,
            y: 287,
            enabled: true,
            rotation: 0,
            formatter() {
              return '<span style="display: inline-block;color:var(--chart-text-color); background-color: var(--rectangle-bg-color);padding: 3px 4px;border-radius:4px;">' + Highcharts.dateFormat('%H:%M', new Date(this.options.value)) + '</span>';
            },
            useHTML: true,
          } as any,
        },
      ],
    },
    yAxis: {
      lineWidth: 0,
      gridLineDashStyle: 'longdash',
      gridLineColor: 'var(--chart-row-color)',
      max: 45,
      min: 0,
      title: {
        text: '',
      },
      labels: {
        x: -5,
        y: 8,
        formatter() {
          return (
            '<span style="width:20px; height:24px; color:var(--chart-text-color); background-color: var(--frame-color); padding: 3px 6px; margin-bottom:10px; border-radius:4px;">' +
            this.value +
            '</span>'
          );
        },
        useHTML: true,
      } as any,
    },
    series: this.series,
    chart: {
      events: {
        load: () => {
          const chart = this.chartRef.highcharts();
          if (chart) {
            this.drawDynamicCircle(chart);
            // 每秒更新图形
            this.updateInterval = window.setInterval(() => {
              this.drawDynamicCircle(chart);
            }, 1000);
          }
        },
        destroy: () => {
          if (this.updateInterval) clearInterval(this.updateInterval);
        }
      }
    }
  };

  // 你的现有方法:getCurrentDate、slotsStart、slotsEnd等...
  getCurrentDate() {
    return new Date().getTime();
  }
  slotsStart() {
    // 返回时间范围起始戳
    return new Date().getTime() - 3600000;
  }
  slotsEnd() {
    // 返回时间范围结束戳
    return new Date().getTime() + 3600000;
  }

2. 核心绘制与更新函数

// 绘制动态圆形和箭头
  drawDynamicCircle(chart: Highcharts.Chart) {
    // 获取当前时间点的像素坐标
    const currentX = this.getCurrentDate();
    const currentYPixel = chart.yAxis[0].toPixels(0, true); // 假设当前时间轴点Y值为0,可按需调整
    const currentPixel = chart.xAxis[0].toPixels(currentX, true);
    
    // 获取固定点的像素坐标
    const fixedPixelX = chart.xAxis[0].toPixels(this.fixedPoint.x, true);
    const fixedPixelY = chart.yAxis[0].toPixels(this.fixedPoint.y, true);

    // 计算圆心和半径
    const centerX = (currentPixel + fixedPixelX) / 2;
    const centerY = (currentYPixel + fixedPixelY) / 2;
    const radius = Math.sqrt(
      Math.pow(currentPixel - fixedPixelX, 2) + Math.pow(currentYPixel - fixedPixelY, 2)
    ) / 2;

    // 销毁旧图形,避免重复渲染
    this.circleElement?.destroy();
    this.leftArrowElement?.destroy();
    this.rightArrowElement?.destroy();

    // 绘制圆形
    this.circleElement = chart.renderer.circle(centerX, centerY, radius)
      .attr({
        stroke: '#2196F3',
        strokeWidth: 2,
        fill: 'rgba(33, 150, 243, 0.1)'
      })
      .add();

    // 计算箭头方向角度
    const angleToCurrent = Math.atan2(currentYPixel - centerY, currentPixel - centerX);
    const angleToFixed = Math.atan2(fixedPixelY - centerY, fixedPixelX - centerX);

    // 绘制指向当前时间点的箭头
    const rightArrowEndX = centerX + Math.cos(angleToCurrent) * (radius + this.rightArrowLength);
    const rightArrowEndY = centerY + Math.sin(angleToCurrent) * (radius + this.rightArrowLength);
    this.rightArrowElement = this.drawArrow(chart, centerX, centerY, rightArrowEndX, rightArrowEndY, '#2196F3');

    // 绘制指向固定点的箭头
    const leftArrowEndX = centerX + Math.cos(angleToFixed) * (radius + this.leftArrowLength);
    const leftArrowEndY = centerY + Math.sin(angleToFixed) * (radius + this.leftArrowLength);
    this.leftArrowElement = this.drawArrow(chart, centerX, centerY, leftArrowEndX, leftArrowEndY, '#2196F3');
  }

  // 辅助函数:绘制箭头
  drawArrow(chart: Highcharts.Chart, x1: number, y1: number, x2: number, y2: number, color: string) {
    const arrowHeadLength = 8;
    const arrowAngle = Math.PI / 6;

    const angle = Math.atan2(y2 - y1, x2 - x1);
    const arrowX1 = x2 - arrowHeadLength * Math.cos(angle - arrowAngle);
    const arrowY1 = y2 - arrowHeadLength * Math.sin(angle - arrowAngle);
    const arrowX2 = x2 - arrowHeadLength * Math.cos(angle + arrowAngle);
    const arrowY2 = y2 - arrowHeadLength * Math.sin(angle + arrowAngle);

    return chart.renderer.path([
      'M', x1, y1,
      'L', x2, y2,
      'L', arrowX1, arrowY1,
      'M', x2, y2,
      'L', arrowX2, arrowY2
    ])
    .attr({
      stroke: color,
      strokeWidth: 2
    })
    .add();
  }

  ngOnDestroy() {
    // 组件销毁时清理资源
    if (this.updateInterval) clearInterval(this.updateInterval);
    this.circleElement?.destroy();
    this.leftArrowElement?.destroy();
    this.rightArrowElement?.destroy();
  }
}

3. 模板绑定图表引用

<highcharts-chart 
  [Highcharts]="Highcharts"
  [options]="chartOptions"
  #chartRef
></highcharts-chart>

关键说明

  • 动态更新:每秒通过定时器重绘图形,先销毁旧元素再绘制新元素,避免内存泄漏。
  • 箭头独立控制:修改leftArrowLength和rightArrowLength即可单独调整两侧箭头长度,不受两点距离影响。
  • 坐标转换:通过xAxis.toPixels和yAxis.toPixels将数据坐标转为图表像素坐标,确保图形位置精准。
  • 资源清理:在组件销毁和图表销毁时清除定时器、销毁图形元素,避免内存泄漏。

内容的提问来源于stack exchange,提问作者angular

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 05:03:11