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

如何在D3.js环形图中实现文本换行、调折线间距并解决标签重叠?

解决D3.js环形图的三个问题:文本换行、标签重叠、折线间距调整

1. 长文本自动换行实现

你的wrap函数需要适配D3 v3的JS环境,并且要在文本生成后正确调用。

修改wrap函数为JS版本

function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy") || "0"),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}

在makeTexts函数中调用wrap

修改makeTexts函数,在文本创建和过渡结束后调用wrap:

function makeTexts() {
  var text = svg.selectAll(".labels")
    .data(pie(dataset), key);

  text.enter()
    .append("text")
    .attr("dy", ".35em")
    .classed("labels", true)
    .text(function(d) {
      return d.data.label + " (" + d.data.count + ")";
    })
    .call(wrap, 120); // 设置最大换行宽度120px

  svg.selectAll(".labels").style("display", function(d) {
    return d.value == 0 ? "none" : "block";
  });

  text.transition().duration(1000)
    .attrTween("transform", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
        return "translate(" + pos + ")";
      };
    })
    .styleTween("text-anchor", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        return midAngle(d2) < Math.PI ? "start" : "end";
      };
    })
    .each("end", function() {
      d3.select(this).call(wrap, 120); // 过渡后重新换行,避免布局变化失效
    });

  text.exit().remove();
}

2. 解决图例取消后标签重叠问题

标签重叠是因为仅按角度定位,未处理相邻标签的位置冲突。我们可以添加碰撞检测,手动调整y坐标避免重叠。

修改makeTexts函数,添加标签碰撞调整

在makeTexts函数末尾添加以下代码:

function makeTexts() {
  // ... 保留原有代码 ...

  // 碰撞检测与调整
  var labels = svg.selectAll(".labels:visible");
  var labelBoxes = labels.nodes().map(function(node) {
    var bbox = node.getBBox();
    var transform = d3.transform(d3.select(node).attr("transform"));
    return {
      x: transform.translate[0],
      y: transform.translate[1],
      width: bbox.width,
      height: bbox.height,
      element: node
    };
  });

  // 左右侧标签分开处理
  var leftLabels = labelBoxes.filter(d => d.x < 0).sort((a,b) => a.y - b.y);
  var rightLabels = labelBoxes.filter(d => d.x >= 0).sort((a,b) => a.y - b.y);

  // 调整左侧标签间距
  for (let i = 1; i < leftLabels.length; i++) {
    const prev = leftLabels[i-1];
    const curr = leftLabels[i];
    const gap = curr.y - (prev.y + prev.height);
    if (gap < 10) { // 最小间距10px
      const offset = 10 - gap;
      curr.y += offset;
      d3.select(curr.element).attr("transform", `translate(${curr.x},${curr.y})`);
      // 后续标签同步偏移
      for (let j = i+1; j < leftLabels.length; j++) {
        leftLabels[j].y += offset;
        d3.select(leftLabels[j].element).attr("transform", `translate(${leftLabels[j].x},${leftLabels[j].y})`);
      }
    }
  }

  // 调整右侧标签间距
  for (let i = 1; i < rightLabels.length; i++) {
    const prev = rightLabels[i-1];
    const curr = rightLabels[i];
    const gap = curr.y - (prev.y + prev.height);
    if (gap < 10) { // 最小间距10px
      const offset = 10 - gap;
      curr.y += offset;
      d3.select(curr.element).attr("transform", `translate(${curr.x},${curr.y})`);
      // 后续标签同步偏移
      for (let j = i+1; j < rightLabels.length; j++) {
        rightLabels[j].y += offset;
        d3.select(rightLabels[j].element).attr("transform", `translate(${rightLabels[j].x},${rightLabels[j].y})`);
      }
    }
  }
}

3. 折线间距调整

通过给折线末端添加y轴偏移,分散折线位置避免重叠。

修改makePolyLines函数,添加折线偏移

修改makePolyLines中的attrTween部分:

function makePolyLines() {
  var polyline = svg.selectAll("polyline")
    .data(pie(dataset), key);

  polyline.enter()
    .append("polyline")
    .style("stroke", "#333")
    .style("fill", "none"); // 添加默认样式

  svg.selectAll("polyline").style("display", function(d) {
    return d.value == 0 ? "none" : "block";
  });

  polyline.transition().duration(1000)
    .attrTween("points", function(d, i) { // 添加索引参数i
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        // 按索引添加y偏移,每组3个折线循环偏移
        const yOffset = (i % 3) * 10 - 10;
        pos[1] += yOffset;
        pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
        return [arc.centroid(d2), outerArc.centroid(d2), pos];
      };
    });

  polyline.exit().remove();
}

内容的提问来源于stack exchange,提问作者user20377051

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:20:31