问题
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