You need to enable JavaScript to run this app. 实现带差值线与百分比标注的动态响应式D3柱状图及反向差值计算
优惠活动
大模型
产品
解决方案
定价
更多

实现带差值线与百分比标注的动态响应式D3柱状图及反向差值计算

D3带差值标注柱状图动态改造需求

我此前咨询过如何制作带百分比差值标注和箭头线的D3柱状图,相关问题为:How to create a % difference arrow line with value in a bar chart using D3.js

本次改造的具体实现目标如下:

  • 实现响应式适配:可在任意宽高的Div容器中正常渲染,兼容不同分辨率
  • 实现反向差值计算:当前版本为正向差值计算、显示正向箭头,希望改为从最后一根柱子开始向前计算,显示反向箭头

补充说明:计算逻辑从最后一根柱子开始,依次向前遍历到第一根柱子,计算示例:(6453-7345)/7345 = -12%、(6453-5388)/6453 = -17%
反向差值效果示意图:
反向差值效果示意图

  • 支持计算首根柱子与最后一根柱子的差值,效果示例:

首尾差值效果示意图

现有实现代码如下:

JS代码

var barData = [{
    "Time": "2019",
    "Value": 5388
    },
    {
    "Time": "2020",
    "Value": 6453
    },
    {
    "Time": "2021",
    "Value": 4345
    },
    {
    "Time": "2022",
    "Value": 7345
    },
    {
    "Time": "2023",
    "Value": 5345
    }];

    // 此处width和Height是"graphID"容器的动态宽高,用于适配响应式设计
    const divWidth = 700,
    divHeight = 700;
    // 可视化区域边距设置
    var margin = {top: 30, right: 50, bottom: 0, left: 50},
    width = parseInt(divWidth,10) - margin.left - margin.right,
    height = parseInt(divHeight,10) - margin.top - margin.bottom;
    
    // 给DOM节点添加svg容器                    
    const svg = d3.select("#graphID").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 + ")");
    
    // 基于可视化区域宽度设置x轴宽度                    
    const xScale = d3.scaleBand()
    .domain(barData.map(d => d.Time))
    .range([0, width]);


    const xAxis = d3.axisBottom(xScale);
    
    // 给svg添加x轴g元素
    svg.append('g')
    .attr("transform", "translate(10," + (height - 50) + ")") 
    .call(xAxis);
    
    const yAxisMax = Math.max.apply(barData, barData.map(function(o) { return o.Value; }));
    
    const yScale = d3.scaleLinear()
    .domain([0, yAxisMax+(yAxisMax/2)])
    .range([height, 0]);

    const yAxis = d3.axisLeft(yScale).ticks(4);
    
    svg.append('g')
    .attr("transform", "translate(10,-50)") 
    .call(yAxis);

    const bars = svg.selectAll('g.bar')
    .data(barData)
    .enter()
    .append('g')
    .classed('bar', true)
    .attr('transform', d => `translate(${xScale(d.Time) + 50 + xScale.bandwidth() / 20}, 170)`)

    bars.append('rect')
    .attr('x', -10)
    .attr('width', 40)
    .attr('y', d =>  -height + yScale(d.Value))
    .attr('height', d => yScale(d.Value) )
    .style('fill', 'blue')

    bars.append('text')
    .text(d => d.Value)
    .attr('text-anchor', 'middle')
    .attr('y', d => -(height - 55) + yScale(d.Value))

    bars.filter((d, i) => i < barData.length - 1)
    .append('path')
    .attr('d', (d, i) => `M 5,${-170 + yScale(d.Value)} V ${-210 + yScale(d.Value)} H ${xScale.bandwidth() - 5} V ${-180 + yScale(barData[i + 1].Value)}`)
    .style('stroke', 'gray')
    .style('fill', 'none')
    .attr('marker-end', 'url(#arrowhead)')

    bars.filter((d, i) => i < barData.length - 1)
    .append('rect')
    .attr('x', 15)
    .attr('y', d => -220 + yScale(d.Value))
    .attr('width', xScale.bandwidth() - 30)
    .attr('height', 20)
    .attr('rx', 10)
    .style('fill', 'white')
    .style('stroke', 'gray');

    bars.filter((d, i) => i < barData.length - 1)
    .append('text')
    .text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : '-'}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
    .attr('x', xScale.bandwidth() / 2)
    .attr('y', d => -207 + yScale(d.Value))
    .attr('text-anchor', 'middle')
    .style('fill', 'black');

CSS代码

#graphID
{
    width:500px;
    height:700px
}
text {
  font-size: 12px;
  font-family: "Ubuntu";
}

HTML代码

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graphID" width=500 height=700>

</div>

内容的提问来源于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.10.20 15:15:04