Tick text/variable labels not wrapping in d3 bar chart

房东的猫 提交于 2021-01-28 10:59:21

问题


I'm trying apply line wrapping to long variable labels along the x-axis in a d3 bar chart. Here's an my chart in an Observable notebook: https://observablehq.com/@unfpamaldives/figure4

I've attempted to apply a solution from this block, consisting essentially of the following:

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)
      }
    }
  })
}  

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(0, ${height})`)
      .call(xAxis)
    .selectAll(".tick text")
      .call(wrap, x.bandwidth())

What it looks like:
What I'm trying to achieve: I've also tried moving the wrap function elsewhere, earlier and later in the code, as well as tried moving the following lines

.selectAll(".tick text")
  .call(wrap, x.bandwidth())

to work from somewhere in this block of code, like so:

xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
  .style("font-family", "HelveticaNeueLTStd-Cn")
  .style("font-size", "9px")
.call(d3.axisBottom(x).tickSizeOuter(0))
.selectAll(".tick text")
  .call(wrap, x.bandwidth())

But this too (attempting to adapt Bostock's solution) does not work. (I've gotten text wrapping to work in other d3 visualizations before, for what it's worth.) Can anyone demonstrate a working solution based on a fork of my Observable notebook?


回答1:


I successfully applied a text wrapping technique for the x-axis bar/variable labels featured in this example from Gerardo Furtado. It entails the following code:

  svg.append("g")
      .call(yAxis);

  const axis = svg.append("g")
      .call(xAxis);  

  setTimeout(()=>{
  axis.selectAll(".tick text")
      .style("font-family", "HelveticaNeueLTStd-Cn")
      .style("font-size", "9px")
    .call(wrap, x.bandwidth());
  }, 0); 

Here's the working solution.




回答2:


The reason why wrap is not working, already hinted in a comment, is due to how Observable's cells run. According to Mike Bostock, Observable (and D3) creator,

[this problem] occurs commonly in Observable because the standard pattern is to implement cells as “pure” functions where you create detached elements and return them, and then they are inserted into the DOM; so within a cell, you should nearly always be working with detached elements. (source)

So, when you call tspan.node().getComputedTextLength() there is nothing in the DOM yet, and therefore it returns zero.

My solution, as you found out, is simply using a setTimeout with 0 milliseconds, so the elements will be in the DOM when you need to measure them. One could argue that this is hacky, and it is indeed! By the way, Bostock's solution, which is along these lines...

const foo = html`<html structure here>`;
document.body.appendChild(foo);
//get the length of foo...
foo.remove();

... is just as hacky, possibly even more.



来源:https://stackoverflow.com/questions/60421252/tick-text-variable-labels-not-wrapping-in-d3-bar-chart

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!