D3でグラフ描画なんかをやっているとSVGのtext要素を枠線で囲ったりしたくなります。どのような文字列の入力にも対応できるようにするためには、text要素の縦横幅を取得して装飾を行う必要があるため、この稿ではその方法を紹介します。
SVG要素の描画領域の短形範囲がgetBBox()
メソッドで取得できます。text要素のgetBBoxを呼ぶことでtextの縦横幅を取得することができます。
ソースコード
<svg width="300px" height="300px">
<rect width="300" height="300" fill="lightgray"/>
<text id="text1" x="50" y="30">text</text>
<text id="text2" x="50" y="60" font-size="2em">text</text>
<text id="text3" transform="translate(50, 90)rotate(45)">text</text>
<text id="text4" x="50" y="120" style="visibility: hidden;">text</text>
<text id="text5" x="50" y="150" style="display: none;">text</text>
</svg>
<table id="output">
<tr>
<th>id</th>
<th>width</th>
<th>height</th>
<th>x</th>
<th>y</th>
</tr>
</table>
<script>
var output = document.getElementById('output');
function getSize(id) {
var bbox = document.getElementById(id).getBBox();
var tr = document.createElement('tr');
var tdId = document.createElement('td');
tdId.innerHTML = id;
tr.appendChild(tdId);
var tdWidth = document.createElement('td');
tdWidth.innerHTML = bbox.width;
tr.appendChild(tdWidth);
var tdHeight = document.createElement('td');
tdHeight.innerHTML = bbox.height;
tr.appendChild(tdHeight);
var tdX = document.createElement('td');
tdX.innerHTML = bbox.x;
tr.appendChild(tdX);
var tdY = document.createElement('td');
tdY.innerHTML = bbox.y;
tr.appendChild(tdY);
output.appendChild(tr);
}
getSize('text1');
getSize('text2');
getSize('text3');
getSize('text4');
getSize('text5');
</script>
実行結果
実行結果を見ると、text要素のwidth、height、x、yの値が取得できていることがわかると思います。text2のように、font-sizeを指定するとそれに応じてwidth、heightが変化しています。text3では、transformでtranslate、rotateをかけていますが、transformを使っていないtext1と同じ結果になっています。text4は、styleでvisibility: hidden;
を指定していますが、上と同様にサイズを取得することができます。一方で、text5のようにdisplay: none;
を指定した場合、これらの値が0になることを注意してください。
D3への応用
以上をふまえて、D3.jsでtextを枠線で囲うプログラムは以下のようになります。
var texts = [{text: 'spam'}, {text: 'ham'}, {text: 'egg'}];
var textsSelection = d3.select('svg')
.selectAll('text')
.data(texts)
.enter()
.append('g')
.attr('transform', function(_, i) {
return 'translate(50,' + (50 * i) + ')';
});
textsSelection.append('text')
.text(function(d) {
return d.text;
})
.attr('font-size', '2em')
.each(function(d) {
var bbox = this.getBBox();
d.width = bbox.width;
d.height = bbox.height;
d.x = bbox.x;
d.y = bbox.y;
})
.attr('dy', function(d) {
return -d.y;
});
textsSelection.append('rect')
.attr({
width: function(d) {
return d.width;
},
height: function(d) {
return d.height;
},
fill: 'none',
stroke: 'black'
});
jsfiddle上での実行結果はこちら。
http://jsfiddle.net/likr/kJuUE/
textのサイズを決定するような属性などを与えたあと、eachの中でgetBBoxを呼び(ここでthisがtext要素を指します)、selectionで結びついているデータにサイズの情報を格納しておきます。
また、text要素のy=0の位置はベースラインになりますので、getBBoxで取得したyを使ってtext要素の上端がy=0となるように補正しています。