HTML内でSVGが使いたくなった

htmlだけでは実現しにくいグラフィカルな表現が欲しくなったけれど、canvasでガチガチな描画処理を実装するほど大げさでもないという時の微妙なニーズを満たすためにSVGを採用することがあるかもしれない。
特にフローチャートやシーケンス図などUMLなドキュメントを表現したい時はSVGが活躍することが多いのではないだろうか。

TEXTを図形で囲みたくなった

もしフローチャートを表現したいのであれば、当然のことながら図形内にテキストを配置する必要がある。
SVGにはテキスト表示用のtextタグ、図形表示用のpolygonタグ、divのようにグループを作れるgタグが揃っているのでこれらを組み合わせるだけで必要なものは揃う。矩形や円などは専用のタグを利用した方が簡単。今回は座標で表現する必要がある図形を想定。

<g style="font-family : 'sans-serif';">  
  <polygon fill="green" points="0,0 0,0 0,0 0,0" />
  <text x="100" y="100" font-size="16" >
    テキスト内容
  </text>
</g>  

polygonタグのpointsで図形の座標を指定するわけだが、この時点では座標を確定することができない。なぜかというと囲まれる対象であるtextタグの大きさは実際に描画してみないと判明しないのである。

canvasを使っている場合は描画コンテキストのmeasureTextメソッドを利用すれば実際に描画せずと幅は取得可能だったりする(といってもcanvasの仕組み的に描画後にサイズが分かるはずもないが)。

ということで潔くそういうものだと諦めて一旦背景図形は未完成のままdomを作って描画してしまう。描画さえしてしまえば、textのdomでgetBBoxメソッドを呼び出すことでテキストを囲む矩形の情報を簡単に取得することができる。この時得られる座標はちゃんとSVG空間のものになっているので心配無用。
ちなみにフォントをどこかで指定しておかないとブラウザによってフォントが変わってサイズも変化してしまう可能性が高い。ブラウザ間差異をなるべく抑えたいなら指定しておくのが無難。上記の例ではgタグで指定している。

var text = <text />のdom;  
// textを囲む矩形を取得
var bbox = text.getBBox();

var polygon = <polygon />のdom;  
// ポリゴンの座標を取得(points[0]というアクセス方法はブラウザ依存機能)
var p0 = polygon.points.getItem(0);  
// 座標を変更
p0.x = bbox.left - bbox.width / 2;  

ちなみにcirclerなどパラメータが属性値の場合はbaseValを経由して変更する必要があるので注意。

var circle = <circle />のdom  
circle.r.baseVal.value = bbox.width / 2;  

予めpolygonの座標数が分かっているなら適当な座標を数合わせで作成しておき、上記のpoints.getItemを使って各座標の値を変更すれば良い。
ただし座標数が分からないなどの場合は座標自体も動的に追加していく必要が出てくる。
このとき作る座標はSVGPointという専用domで、svgdomのメソッドを通して作る必要がある。 svgdomはidなどつけて取得してもいいが、ownerSVGElementという便利なプロパティから簡単に取得できる。

var polygon = <polygon />のdom;  
var svg = polygon.ownerSVGElement;  
var point = svg.createSVGPoint();  
// 適当に調整
point.x = 10;  
point.y = 20;  
// pointを座標リストに追加
polygon.points.appendItem(point);  

おまけ

もしreactを使っている場合、render関数内ではdomの描画が完了しておらずtextのサイズも取得することができない。componentDidUpdateというdom描画完了後に実行されるメソッドが用意されているので、このメソッド内でサイズを取得して各座標を調整していく必要がある。