如何在Apache ECharts嵌套饼图中添加内外环关联箭头?
在Apache ECharts中实现嵌套饼图的关联箭头
方案一:利用ECharts内置的graphic组件实现
ECharts的graphic组件支持自定义绘制图形元素,可直接用来添加内外环的关联箭头,步骤如下:
- 计算扇区中心坐标:通过饼图的起始角度、数据占比,算出内环目标扇区和外环“此数据库”扇区的中心坐标。
- 绘制箭头元素:用
graphic的line绘制箭杆,polygon绘制箭头头部,组合成完整箭头。 - 响应窗口 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,手动绘制箭头:
- 创建叠加Canvas:在ECharts容器内添加一个绝对定位的Canvas,覆盖在图表之上。
- 计算坐标并绘制箭头:用和方案一相同的坐标计算逻辑,调用Canvas API绘制箭杆和箭头。
- 响应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
相关产品推荐
相关产品推荐

