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

如何在Apache ECharts嵌套饼图中添加内外环关联箭头?

在Apache ECharts中实现嵌套饼图的关联箭头

方案一:利用ECharts内置的graphic组件实现

ECharts的graphic组件支持自定义绘制图形元素,可直接用来添加内外环的关联箭头,步骤如下:

  1. 计算扇区中心坐标:通过饼图的起始角度、数据占比,算出内环目标扇区和外环“此数据库”扇区的中心坐标。
  2. 绘制箭头元素:用graphic的line绘制箭杆,polygon绘制箭头头部,组合成完整箭头。
  3. 响应窗口 resize:监听图表resize事件,重新计算坐标并更新箭头位置。

代码示例

// 初始化图表
const chart = echarts.init(document.getElementById('main'));

// 饼图基础配置
const baseOption = {
  series: [
    // 内环:数据库空间细分
    {
      type: 'pie',
      radius: ['0%', '40%'],
      startAngle: 90, // ECharts默认顶部为起始角度
      data: [
        { value: 150, name: '数据文件' },
        { value: 80, name: '日志文件' },
        { value: 30, name: '临时文件' }
      ]
    },
    // 外环:系统存储拆分
    {
      type: 'pie',
      radius: ['60%', '75%'],
      startAngle: 90,
      data: [
        { value: 200, name: '其他应用' },
        { value: 260, name: '此数据库', itemStyle: { color: '#ff7d00' } },
        { value: 140, name: '空闲空间' }
      ]
    }
  ]
};

// 计算饼图扇区的中心坐标
function getSectorCenter(seriesIndex, dataIndex) {
  const series = baseOption.series[seriesIndex];
  const targetData = series.data[dataIndex];
  const totalValue = series.data.reduce((sum, item) => sum + item.value, 0);
  const anglePerUnit = 360 / totalValue;
  
  // 计算当前扇区的起始角度
  let prevSum = 0;
  for (let i = 0; i < dataIndex; i++) {
    prevSum += series.data[i].value;
  }
  const startAngle = series.startAngle || 90;
  const sectorStart = startAngle - prevSum * anglePerUnit;
  const sectorEnd = sectorStart - targetData.value * anglePerUnit;
  const centerAngle = (sectorStart + sectorEnd) / 2; // 扇区中心角度
  
  // 转换为画布坐标
  const domRect = chart.getDom().getBoundingClientRect();
  const centerX = domRect.width / 2;
  const centerY = domRect.height / 2;
  const radius = series.radius[1].replace('%', '') / 100; // 取扇区外半径
  const radian = (centerAngle * Math.PI) / 180;
  
  return {
    x: centerX + radius * domRect.width * Math.cos(radian),
    y: centerY - radius * domRect.height * Math.sin(radian) // ECharts角度顺时针递减,所以用减号
  };
}

// 计算箭头头部的多边形顶点
function getArrowHeadPoints(from, to) {
  const dx = to.x - from.x;
  const dy = to.y - from.y;
  const length = Math.sqrt(dx * dx + dy * dy);
  const unitX = dx / length;
  const unitY = dy / length;
  
  const arrowLen = 10;
  const arrowWidth = 8;
  
  return [
    [to.x, to.y],
    [to.x - arrowLen * unitX + arrowWidth * unitY, to.y - arrowLen * unitY - arrowWidth * unitX],
    [to.x - arrowLen * unitX - arrowWidth * unitY, to.y - arrowLen * unitY + arrowWidth * unitX]
  ];
}

// 渲染图表并添加箭头
chart.setOption(baseOption);
const innerCenter = getSectorCenter(0, 0); // 内环第一个扇区的中心
const outerCenter = getSectorCenter(1, 1); // 外环"此数据库"扇区的中心

chart.setOption({
  graphic: [
    // 箭杆
    {
      type: 'line',
      shape: { x1: innerCenter.x, y1: innerCenter.y, x2: outerCenter.x, y2: outerCenter.y },
      style: { stroke: '#ff0000', lineWidth: 2 }
    },
    // 箭头头部
    {
      type: 'polygon',
      shape: { points: getArrowHeadPoints(innerCenter, outerCenter) },
      style: { fill: '#ff0000' }
    }
  ]
});

// 监听窗口resize,更新箭头位置
window.addEventListener('resize', () => {
  chart.resize();
  const newInner = getSectorCenter(0, 0);
  const newOuter = getSectorCenter(1, 1);
  
  chart.setOption({
    graphic: [
      {
        type: 'line',
        shape: { x1: newInner.x, y1: newInner.y, x2: newOuter.x, y2: newOuter.y },
        style: { stroke: '#ff0000', lineWidth: 2 }
      },
      {
        type: 'polygon',
        shape: { points: getArrowHeadPoints(newInner, newOuter) },
        style: { fill: '#ff0000' }
      }
    ]
  });
});

方案二:原生JavaScript操作Canvas实现

如果graphic组件满足不了需求,可以直接在图表的Canvas上层叠加一个自定义Canvas,手动绘制箭头:

  1. 创建叠加Canvas:在ECharts容器内添加一个绝对定位的Canvas,覆盖在图表之上。
  2. 计算坐标并绘制箭头:用和方案一相同的坐标计算逻辑,调用Canvas API绘制箭杆和箭头。
  3. 响应resize:窗口大小变化时,同步更新Canvas尺寸并重新绘制。

代码示例

const chart = echarts.init(document.getElementById('main'));
const baseOption = { /* 和方案一的饼图配置一致 */ };
chart.setOption(baseOption);

// 创建叠加Canvas
const overlayCanvas = document.createElement('canvas');
const container = chart.getDom();
container.style.position = 'relative';
overlayCanvas.style.position = 'absolute';
overlayCanvas.style.top = '0';
overlayCanvas.style.left = '0';
overlayCanvas.width = container.clientWidth;
overlayCanvas.height = container.clientHeight;
container.appendChild(overlayCanvas);

const ctx = overlayCanvas.getContext('2d');

// 复用方案一的getSectorCenter和getArrowHeadPoints函数
function drawArrow() {
  ctx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
  
  const innerCenter = getSectorCenter(0, 0);
  const outerCenter = getSectorCenter(1, 1);
  
  // 绘制箭杆
  ctx.beginPath();
  ctx.moveTo(innerCenter.x, innerCenter.y);
  ctx.lineTo(outerCenter.x, outerCenter.y);
  ctx.strokeStyle = '#ff0000';
  ctx.lineWidth = 2;
  ctx.stroke();
  
  // 绘制箭头头部
  const arrowPoints = getArrowHeadPoints(innerCenter, outerCenter);
  ctx.beginPath();
  ctx.moveTo(arrowPoints[0][0], arrowPoints[0][1]);
  ctx.lineTo(arrowPoints[1][0], arrowPoints[1][1]);
  ctx.lineTo(arrowPoints[2][0], arrowPoints[2][1]);
  ctx.closePath();
  ctx.fillStyle = '#ff0000';
  ctx.fill();
}

// 初始绘制箭头
setTimeout(drawArrow, 300); // 等待图表渲染完成

// 监听resize事件
window.addEventListener('resize', () => {
  chart.resize();
  overlayCanvas.width = container.clientWidth;
  overlayCanvas.height = container.clientHeight;
  drawArrow();
});

// 复用的坐标计算函数
function getSectorCenter(seriesIndex, dataIndex) { /* 和方案一一致 */ }
function getArrowHeadPoints(from, to) { /* 和方案一一致 */ }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 02:16:14