Reputation: 1856
I'm having an issue positioning css web-font styled, svg text elements one after another using D3. When I first position the text elements on load they'll overlap.
However, when I position the elements moments later using the same function triggered on a delay or through a button they wont overlap.
The issue appears to be with getBBox()
and getBoundingClientRect()
neither of which return the proper width for the element at first.
Any ideas on how to get the correct width at first?
The JS is below, an example is here: http://bl.ocks.org/yanofsky/6618496 and the full code here: https://gist.github.com/yanofsky/6618496
//helper function to grab transform coordinates
function transformCoordOf(elem) {
var separator = elem.attr("transform").indexOf(",") > -1 ? "," : " ";
var trans = elem.attr("transform").split(separator);
return { x: (trans[0] ? parseFloat(trans[0].split("(")[1]) : 0), y: (trans[1] ? parseFloat(trans[1].split(")")[0] ): 0) };
}
//position the elements based on the one before it
function positionElements() {
txts.filter(function(d,i){return i != 0}) //filter out the first element
.attr("transform",function(d,i){
var prev = d3.select(txts[0][i]), //use i b/c the list shifts on filter
prevWidth = parseFloat(prev.node().getBoundingClientRect().width)
prevCoords = transformCoordOf(prev);
var cur = d3.select(this),
curWidth = parseFloat(cur.node().getBoundingClientRect().width)
curCoords = transformCoordOf(cur);
var y = prevCoords.y,
x = prevCoords.x + prevWidth + 10;
return "translate("+x+","+y+")";
})
}
var names = ["apples","oranges","bananas"]
var canvas = d3.select("#content")
.append("svg")
.attr("width","600px")
.attr("height","100px");
var txts = canvas.selectAll("text").data(names)
.enter()
.append("text")
.attr("transform","translate(10,50)")
.text(function(d){return d});
positionElements()
d3.select("button").on("click",function(){positionElements()})
Upvotes: 1
Views: 2666
Reputation: 3900
I haven't tried the browser load event (I also know practically nothing about web fonts), but when we ran into this issue (hacking on Chartbuilder, nonetheless), we solved it by using WebFontLoader and waiting for the active event. One wrinkle with this: the text has to appear on the page in order to ever be loaded, so you'll need a hidden span (or whatever) that uses your font.
Here's our code (we load the font from fonts.com:
WebFont.load({
monotype: {
projectId: '65980087-55e2-40ca-85ae-729fca359467',
},
active: function(name) {
$(document).ready(function() {
ChartBuilder.start();
});
}
});
Addendum: I don't think it really makes any practical difference, but we also switched to using getComputedTextLength()
instead of measuring the bounding box, just because it seemed more correct. See the code here.
Upvotes: 2
Reputation: 5333
The font you're referencing hasn't been downloaded at the time you first render the <text>
elements and measure their width. So at this point in time you're getting a smaller width that you get later on after the referenced font has been loaded.
Waiting for the browser's load event (as Robert suggests) is something worth trying, but I'm not sure that will work on all browsers. I read something earlier that implied that some browsers don't actually load remote @font-face fonts until they encounter a first usage not just the CSS declaration. But I haven't had any experience with this so I'm not positive.
Here is a related SO question that has a couple of good answers full of links about @font-face and download timing.
Upvotes: 1