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

如何使用D3.js在柱状图中创建带百分比差值的箭头连线及数值标注

嘿,这个需求挺实用的!我帮你梳理下用D3.js实现带连续柱子百分比差值标注的柱状图的思路和具体代码,跟着来就能搞定~

核心实现思路

要实现这个功能,核心就是两步:

  • 先计算每对连续柱子的数值差值百分比
  • 在柱子之间绘制箭头和标注文本,箭头指向数值变化的方向,文本显示百分比
具体步骤&完整代码示例

我直接给你完整的可运行代码,每一步都加了注释,你可以直接复制测试:

首先确保你已经引入了D3.js(比如通过CDN:<script src="https://d3js.org/d3.v7.min.js"></script>),然后是JS部分:

// 你的原始数据
const barData = [{ "Year": "2019", "Value": 5388 }, { "Year": "2020", "Value": 6453 }, { "Year": "2021", "Value": 7345 }];

// 1. 设置SVG容器和边距
const margin = { top: 40, right: 30, bottom: 30, left: 40 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;

const svg = d3.select("body")
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", `translate(${margin.left},${margin.top})`);

// 2. 计算连续柱子的百分比差值
const diffData = [];
for (let i = 1; i < barData.length; i++) {
  const prevItem = barData[i-1];
  const currItem = barData[i];
  const percentChange = ((currItem.Value - prevItem.Value) / prevItem.Value) * 100;
  diffData.push({
    prevYear: prevItem.Year,
    currYear: currItem.Year,
    percent: Math.round(percentChange * 10) / 10, // 保留1位小数,更美观
    prevValue: prevItem.Value,
    currValue: currItem.Value,
    isIncrease: percentChange > 0 // 标记是增长还是下降
  });
}

// 3. 设置比例尺
const xScale = d3.scaleBand()
  .domain(barData.map(item => item.Year))
  .range([0, width])
  .padding(0.2); // 柱子之间的间距

const yScale = d3.scaleLinear()
  .domain([0, d3.max(barData, item => item.Value) * 1.15]) // 多留15%空间放箭头和文本
  .range([height, 0]);

// 4. 绘制坐标轴
svg.append("g")
  .attr("transform", `translate(0,${height})`)
  .call(d3.axisBottom(xScale));

svg.append("g")
  .call(d3.axisLeft(yScale));

// 5. 绘制基础柱状图
svg.selectAll(".bar")
  .data(barData)
  .enter()
  .append("rect")
  .attr("class", "bar")
  .attr("x", item => xScale(item.Year))
  .attr("y", item => yScale(item.Value))
  .attr("width", xScale.bandwidth())
  .attr("height", item => height - yScale(item.Value))
  .attr("fill", "#4287f5");

// 6. 定义箭头标记(增长和下降两种)
const defs = svg.append("defs");

// 增长箭头(绿色)
defs.append("marker")
  .attr("id", "arrow-up")
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 8)
  .attr("refY", 0)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("path")
  .attr("d", "M0,-5L10,0L0,5")
  .attr("fill", "#2ecc71");

// 下降箭头(红色)
defs.append("marker")
  .attr("id", "arrow-down")
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 2)
  .attr("refY", 0)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("path")
  .attr("d", "M10,-5L0,0L10,5")
  .attr("fill", "#e74c3c");

// 7. 绘制差值箭头
svg.selectAll(".diff-arrow")
  .data(diffData)
  .enter()
  .append("line")
  .attr("class", "diff-arrow")
  .attr("x1", item => xScale(item.prevYear) + xScale.bandwidth()) // 前一个柱子的右侧边缘
  .attr("y1", item => yScale(item.prevValue)) // 前一个柱子的顶部
  .attr("x2", item => xScale(item.currYear)) // 当前柱子的左侧边缘
  .attr("y2", item => yScale(item.currValue)) // 当前柱子的顶部
  .attr("stroke", item => item.isIncrease ? "#2ecc71" : "#e74c3c")
  .attr("stroke-width", 2)
  .attr("marker-end", item => item.isIncrease ? "url(#arrow-up)" : "none")
  .attr("marker-start", item => !item.isIncrease ? "url(#arrow-down)" : "none");

// 8. 绘制百分比文本
svg.selectAll(".diff-text")
  .data(diffData)
  .enter()
  .append("text")
  .attr("class", "diff-text")
  .attr("x", item => (xScale(item.prevYear) + xScale.bandwidth() + xScale(item.currYear)) / 2) // 两个柱子的中间位置
  .attr("y", item => Math.min(yScale(item.prevValue), yScale(item.currValue)) - 10) // 比低柱子顶部高10px,避免重叠
  .attr("text-anchor", "middle")
  .attr("fill", item => item.isIncrease ? "#2ecc71" : "#e74c3c")
  .attr("font-size", "14px")
  .attr("font-weight", "bold")
  .text(item => `${item.isIncrease ? "+" : ""}${item.percent}%`);
一些优化小建议
  • 如果你的数据可能出现下降的情况,上面的代码已经处理了箭头方向和颜色区分,很直观
  • 可以根据需求调整箭头的大小、颜色,文本的位置和样式
  • 如果柱子数量很多,建议调整SVG的宽度,或者缩小柱子间距,避免箭头和文本拥挤
  • 可以给柱子添加hover效果,比如高亮对应的差值标注,提升交互性

内容的提问来源于stack exchange,提问作者Amit kumar Sah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 16:38:15