修复D3.js圆角水平条形图极端数值下边界变形问题
圆角水平条形图异常问题优化方案
问题描述
制作圆角水平条形图时存在以下问题:
- 数值接近100%时,条形右侧圆角出现异常变形
- 数值为1%、4%等低数值时,条形显示破损,无法正常渲染
问题分析
原实现的核心问题在于:
- 低数值场景下,计算得到的条形宽度
barWidth可能小于圆角半径,导致路径绘制逻辑冲突,出现破损 - 高数值场景下的圆角动态计算逻辑不合理,导致圆角半径异常变化,引发变形
- 缺少裁剪机制,条形超出容器的部分未被正确约束
优化后的完整代码
<html> <head> <title>multibar d3</title> <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> <style> .label { fill: white; font-size: 14px; } </style> </head> <body> <script> d3.select("body") .selectAll('svg') .remove(); // 可修改value测试不同数值:1,4,97,100 var data = [{"label": "Toblerone", "value": 97}]; var w = parseInt(280, 10), h = parseInt(120, 10); var margin = {top: 20, right: 10, bottom: 20, left: 20}; var width = w - margin.left - margin.right; var height = h - margin.top - margin.bottom; var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleBand() .range([height, 0]) .padding(0.1); x.domain([0, 100]) y.domain(data.map(function(d) { return d.value; })); var svg = d3.select("body") .append("svg") .style('background','#5902f6') .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr('class', 'barchart') .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var border =4; var bars = svg.append('g').attr('class', 'bars'); // 背景条 bars.selectAll(".bg") .data(data) .enter().append("rect") .attr("class", "bg") .attr('rx',()=> y.bandwidth()/2) .attr('ry',()=> y.bandwidth()/2) .attr('fill','#5902f6') .attr('stroke','white') .attr('stroke-width',border-1) .attr("width",x(100)) .attr("y", function(d) { return y(d.value); }) .attr("height", y.bandwidth()) .attr("id", (d,i) => `bg-${i}`); // 添加裁剪路径:每个条形对应自己的背景容器作为裁剪范围 var defs = svg.append("defs"); defs.selectAll(".bar-clip") .data(data) .enter() .append("clipPath") .attr("id", (d,i) => `clip-${i}`) .append("use") .attr("xlink:href", (d,i) => `#bg-${i}`); bars.selectAll(".bar") .data(data) .enter() .append("path") .attr("class", "bar") .attr("fill", "#2cc2d3") .attr('stroke','white') .attr('stroke-width',3.2) .attr("clip-path", (d,i) => `url(#clip-${i})`) // 应用裁剪约束 .attr("d", function(d) { const barWidth = x(d.value); const barHeight = y.bandwidth(); const cornerRadius = barHeight / 2; // 处理低数值:宽度小于等于圆角直径时,绘制胶囊形 if (barWidth <= cornerRadius * 2) { return ` M ${barWidth/2} 0 A ${barWidth/2} ${cornerRadius} 0 1 1 ${barWidth/2} ${barHeight} A ${barWidth/2} ${cornerRadius} 0 1 1 ${barWidth/2} 0 Z `; } // 处理高数值:接近100%时,右侧圆角保持最大半径,对齐容器边缘 else if (d.value >= 100 - (cornerRadius / x(1))) { return ` M ${cornerRadius} 0 H ${width - cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 ${width} ${cornerRadius} V ${barHeight - cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 ${width - cornerRadius} ${barHeight} H ${cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 0 ${barHeight - cornerRadius} V ${cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 ${cornerRadius} 0 Z `; } // 正常数值范围路径 else { return ` M ${cornerRadius} 0 H ${barWidth} V ${barHeight} H ${cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 0 ${barHeight - cornerRadius} V ${cornerRadius} A ${cornerRadius} ${cornerRadius} 0 0 1 ${cornerRadius} 0 Z `; } }) .attr("transform", function(d) { return `translate(0, ${y(d.value)})`; }); bars.selectAll(".label") .data(data) .enter().append("text") .attr("class", "label") .attr('text-anchor', function(d) { return d.value <= 20 ? 'start' : 'end'; }) .attr("x", function(d) { return d.value <= 20 ? x(d.value) + 10 : x(d.value) - 10; }) .attr("y", function(d) { return y(d.value) + y.bandwidth() / 2; }) .attr("dy", "0.35em") // 标准垂直居中对齐 .text(function(d) { return `${d.label} (${d.value}%)`; }); </script> </body> </html>
关键修改说明
- 添加裁剪机制:为每个条形创建
<clipPath>并关联背景容器,确保条形不会超出背景范围,解决溢出问题 - 修复低数值路径:当条形宽度小于等于两倍圆角半径时,绘制胶囊形状路径,避免因宽度不足导致的破损
- 优化高数值逻辑:当数值接近100%时,固定右侧圆角半径为条形高度的一半,路径终点对齐容器边缘,避免异常变形
- 优化文本对齐:使用
dy="0.35em"实现精准垂直居中,调整文本偏移量提升可读性
内容的提问来源于stack exchange,提问作者The Old County
相关产品推荐
相关产品推荐

