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

D3.js中SVG文本换行失效问题求助

解决D3.js SVG文本自动换行与响应式排版问题

在D3.js向SVG添加文本时,遇到文本截断、无法自动换行的问题,现有代码及尝试的wrap函数如下:

现有D3代码

var margin = {top: 40, right: 0, bottom: 40, left: 0},
    width = width - margin.left - margin.right,
    height = height - margin.top - margin.bottom;


var svg = div.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 + ")");
    
var g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var data_access_group = "Ashland County"
var numCases = "26"
var firstYr = "2017"
var lastDate = "6/16/2022"
     
var text = svg.append("text")
  .attr("dy", 0)
  .attr("x",0)
  .attr("font-weight", 400)
  .text("Overdose Fatalities Among Residents of ")
  .attr("class", "overview-text")
  .attr("id", "overview-text-1")
  .append("tspan")
  .attr("font-weight", 900)
  .text(data_access_group)
  .attr("class", "overview-text")


svg.append("text")
  .attr("class", "overview-text")
  .attr("id", "overview-text-2")
  .attr("dy", "3.8em")
  .attr("x",0)
  .attr("font-weight", 900)
  .text(numCases)
  .append("tspan")
  .attr("font-weight", 400)
  .text(" overdose fatalities since ")
  .append("tspan")
  .attr("font-weight", 900)
  .text(firstYr)
    
svg.append("text")
  .attr("class", "overview-text")
  .attr("id", "overview-text-3")
  .attr("dy", "8.8em")
  .attr("x",0)
  .attr("font-weight", 400)
  .text("Includes only resident fatalities. Most recent overdose fatality: "+ lastDate)

配套CSS

#overview-text-1 {
  font-size:30px !important;
}

尝试的wrap函数(未达预期)

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")),
        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);
      }
    }
  });
} 

setTimeout(()=>{
  d3.selectAll(".overview-text")
    .call(wrap,500);
}, 0);  

解决方案

1. 修复wrap函数,支持带样式的tspan

原wrap函数会清空原有tspan并丢失样式,修改后的函数可保留文本的字体权重等样式,同时实现自动换行:

function wrapStyledText(textElem, maxWidth) {
  const text = d3.select(textElem);
  const originalTSpans = text.selectAll("tspan").nodes();
  const textSegments = [];

  // 提取主文本内容与样式
  if (text.text().trim()) {
    textSegments.push({
      content: text.text().trim(),
      styles: {
        "font-weight": text.attr("font-weight"),
        "font-size": text.attr("font-size") || getComputedStyle(text.node()).fontSize
      }
    });
    text.text("");
  }

  // 提取每个tspan的内容与样式
  originalTSpans.forEach(tspan => {
    const tspanSel = d3.select(tspan);
    textSegments.push({
      content: tspanSel.text().trim(),
      styles: {
        "font-weight": tspanSel.attr("font-weight"),
        "font-size": tspanSel.attr("font-size") || getComputedStyle(tspan).fontSize
      }
    });
    tspan.remove();
  });

  let currentLine = [];
  let currentLineWidth = 0;
  let lineNumber = 0;
  const lineHeight = 1.2;
  const baseY = text.attr("y") || 0;
  const baseDy = parseFloat(text.attr("dy")) || 0;

  // 创建临时元素计算文本宽度
  const tempText = d3.select(document.createElementNS("http://www.w3.org/2000/svg", "text"))
    .style("position", "absolute")
    .style("visibility", "hidden")
    .attr("x", 0)
    .attr("y", 0);
  document.body.appendChild(tempText.node());

  textSegments.forEach(segment => {
    const words = segment.content.split(/\s+/);
    words.forEach(word => {
      tempText.style("font-weight", segment.styles["font-weight"])
             .style("font-size", segment.styles["font-size"])
             .text(word + " ");
      const wordWidth = tempText.node().getComputedTextLength();

      if (currentLineWidth + wordWidth > maxWidth && currentLine.length > 0) {
        createLine(text, currentLine, lineNumber, baseY, baseDy, lineHeight);
        currentLine = [];
        currentLineWidth = 0;
        lineNumber++;
      }
      currentLine.push({ word, styles: segment.styles });
      currentLineWidth += wordWidth;
    });
  });

  if (currentLine.length > 0) {
    createLine(text, currentLine, lineNumber, baseY, baseDy, lineHeight);
  }

  tempText.remove();
}

// 创建带样式的行tspan
function createLine(text, lineSegments, lineNumber, baseY, baseDy, lineHeight) {
  const tspan = text.append("tspan")
    .attr("x", 0)
    .attr("y", baseY)
    .attr("dy", `${baseDy + lineNumber * lineHeight}em`);
  
  lineSegments.forEach(seg => {
    tspan.append("tspan")
      .style("font-weight", seg.styles["font-weight"])
      .style("font-size", seg.styles["font-size"])
      .text(seg.word + " ");
  });
}

2. 实现响应式换行(窗口resize时自动更新)

移除setTimeout,改用窗口resize监听,确保文本在窗口尺寸变化时重新计算换行:

// 初始化执行文本换行
function updateTextWrap() {
  const availableWidth = parseInt(d3.select("svg").attr("width")) - margin.left - margin.right;
  d3.selectAll(".overview-text").each(function() {
    wrapStyledText(this, availableWidth);
  });
}

updateTextWrap();

// 监听窗口resize事件
window.addEventListener("resize", function() {
  // 更新SVG宽度为窗口可用宽度
  const newWidth = window.innerWidth - margin.left - margin.right;
  d3.select("svg").attr("width", newWidth + margin.left + margin.right);
  // 重新计算文本换行
  updateTextWrap();
});

3. 额外优化建议

  • 给SVG添加viewBox属性,增强响应式适配能力
  • 调整lineHeight参数,匹配设计稿的行间距要求
  • 避免使用CSS!important,优先通过D3设置样式或调整CSS优先级

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 15:55:20