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

修复D3.js圆角水平条形图极端数值下边界变形问题

圆角水平条形图异常问题优化方案

问题描述

制作圆角水平条形图时存在以下问题:

  • 数值接近100%时,条形右侧圆角出现异常变形
  • 数值为1%、4%等低数值时,条形显示破损,无法正常渲染

问题分析

原实现的核心问题在于:

  1. 低数值场景下,计算得到的条形宽度barWidth可能小于圆角半径,导致路径绘制逻辑冲突,出现破损
  2. 高数值场景下的圆角动态计算逻辑不合理,导致圆角半径异常变化,引发变形
  3. 缺少裁剪机制,条形超出容器的部分未被正确约束

优化后的完整代码

<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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:34:55