Reputation: 1792
In D3 I would like to wrap texts which have a middle text anchor. I looked at this example from Mike Bostok but I cannot wrap my head around the settings.
I would like to see the text to be in the center (horizontal and vertical) of the red box.
var plot = d3.select(container)
.insert("svg")
.attr('width', 100)
.attr('height', 200);
plot.append("text")
.attr("x", 50)
.attr("y", 100)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "alphabetic")
.text("The brown fox jumps!")
.call(wrap, 100);
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 = 1,
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);
}
}
});
}
Given the pointers from Shashank I was now able to position the text central with the translate method. Just setting the y of the text didn't seem to change anything. I also added a yTrack variable to be able to continue drawing below the wrapped text.
let yTrack = 100,
plot = d3.select(container)
.insert("svg")
.attr('width', 100)
.attr('height', 200);
plot.append("text")
.attr("x", 50)
.attr("y", yTrack)
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text("The quick brown fox jumps over the lazy dog.")
.call(wrap, 100);
let height = parseInt(plot.select('text').node().getBoundingClientRect().height);
plot.select("text").attr('transform', 'translate(0, ' + (-height / 2) + ')');
//plot.select("text").attr('y', 0);
yTrack += (parseInt(height / 2) + 10);
plot.append('rect')
.attr("x", 0)
.attr("y", yTrack)
.attr("width", 100)
.attr("height", 10)
.style('fill', '#999');
function wrap(text, width) {
text.each(function() {
let text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 1.1,
tspan = text.text(null).append("tspan").attr("x", x).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", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
#container {
height: 200px;
width: 100px;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container"></div>
Upvotes: 1
Views: 10441
Reputation: 5660
From the docs for text-anchor, middle
will result in the following:
The rendered characters are aligned such that the middle of the text string is at the current text position.
and in your case, as you are breaking down the text into tspan
s with x
equal to 0
-- the current text position would be the start of the svg (i.e. at point 0). Also, the x:50
applied to the text
element wouldn't matter at all.
One approach would be to apply the center value i.e. 50 to the tspan
s:
tspan.attr("x", 50)
Here's a snippet doing that:
var plot = d3.select(container)
.insert("svg")
.attr('width', 100)
.attr('height', 200);
plot.append("text")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "alphabetic")
.text("The brown fox jumps!")
.call(wrap, 100);
var height = plot.select('text').node().getBoundingClientRect().height;
plot.select('text').attr('y', 100-height/2);
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 0.1, // ems
y = text.attr("y"),
dy = 1,
tspan = text.text(null).append("tspan").attr("x", 50).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", 50).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
#container {
height: 200px;
width: 100px;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container"></div>
Another approach would be to apply 'transform' property to the text in the following way:
plot.select('text').attr('transform', 'translate(50, ' + (100-height/2)+')');
Here's a snippet with this method:
var plot = d3.select(container)
.insert("svg")
.attr('width', 100)
.attr('height', 200);
plot.append("text")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "alphabetic")
.text("The brown fox jumps!")
.call(wrap, 100);
var height = plot.select('text').node().getBoundingClientRect().height;
plot.select('text').attr('transform', 'translate(50, ' + (100-height/2)+')');
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 0.1, // ems
y = text.attr("y"),
dy = 1,
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);
}
}
});
}
#container {
height: 200px;
width: 100px;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container"></div>
In both of the above approaches, I'm not using the y-center value i.e. 100 instead I'm calculating the mid-point based on the text height as well using getBoundingClientRect()
function.
var height = plot.select('text').node().getBoundingClientRect().height;
plot.select('text').attr('y', 100-height/2);
Hope this helps. :)
Upvotes: 3