实现带差值线与百分比标注的动态响应式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
相关产品推荐
相关产品推荐



