d3.jsな日々

d3.jsでデータ可視化する際の覚書です

zoomってどう使う?

可視化コンテンツではズームやパンが必要になることが多いが、実装はなかなか面倒なもの。しかし心配は不要。d3.jsには強力なズーム&パン機能が備わっている。このAPIが、d3.behavior.zoom() だ。

参考:Zoom Behavior · mbostock/d3 Wiki · GitHub
例:
SVG Geometric Zooming


1.zoomに設定できるもの
まず、zoom APIの設定パラメータ。

# zoom(selection)
指定したセレクションにzoomビヘイビアを設定する。

d3.behavior.zoom(selection)

としてもよいが、よりd3.js的な'Chain Syntax'で書くと、

selection.call(d3.behavior.zoom().以下略)

となる。
このselectionがズームイベントのイベントリスナとなる。


# zoom.translate([translate])
変位(translation)ベクタの初期値を設定。デフォルトは[0,0]。

# zoom.scale([scale])
スケールの初期値を設定。デフォルトは 1。

# zoom.scaleExtent([extent])
ズームスケールの範囲(extent)を設定。デフォルトは[0,∞]。

# zoom.center([center])
ズームの中心座標を2次元配列で設定。デフォルトはnullで、マウスカーソル座標がズーム中心になる(これは便利!)。

# zoom.size([size])
ビューポートのサイズを指定。デフォルトは[960, 500]。サイズを指定しないとスムーズなトランジションにならない。(たとえば、拡大中心がずれる、などの問題がおきるようだ。)

# zoom.x([scale-x])
# zoom.y([scale-y])

それぞれ、ズームにあわてXスケール、Yスケールのドメインが自動的に調節される。横軸、縦軸があるグラフの場合に必須。指定子ない場合はnullとなる。
スケールをプログラムで変更した場合は、zoom.x, zoom.yも再度呼び出す必要がある。
また、Xスケール・Yスケールを設定すると、translate, scaleはデフォルト値(tranalateは[0,0], scaleは1)に戻るので注意。

# zoom.on(type, listener)
イベントと、該当するイベントリスナーを設定。
イベントは、
zoomstart - ズーム開始時(たとえば、指を触れた時)
zoom -ズーム中
zoomend - ズーム終了時(たとえば、指を離した時)
の3種類。すでにリスナが設定された状態で設定すると、新しいリスナに上書きされる。複数のリスナを設定したい場合はnamespaceを使って、namespace.funcA, namescape.funcBというように書く。
リスナを破棄したい場合は、リスナに’null'を設定する。

マウスホイールの場合は物理的なzoomstart/zoomendがないので、ひとつのズーム操作の開始・終了から50ミリ秒以内にイベントが発行される。(つまりあくまでも近似。注意。)

イベントには次のプロパティをもつ。
scale - 現在のスケール:スカラ数
translate -現在のtranslationベクタ:2次元配列

# zoom.event(selection)
'selection'がセレクションの場合、登録されたリスナにただちにズーム・ジェスチャ(zoomstart, zoom, zoomend)を発行する。これはプログラム上でズームと同等の操作を行った時に有用。
'selection'がトランジションの場合、該当するtweenが登録される。これによって、トランジション時にもズームイベントが発行される。なおトランジション終了前にユーザがズーム操作を行なうと、トランジションは中断される。


2−1.zoomを使ったスクリプトの例 単純なズーム

SVG要素('svg_vis')をズーム&ドラッグする例。参考:SVG Geometric Zooming

ズームの設定

var svg_vis = svg_vis_base
		    .append("g").attr("id", "svg_vis")
                  .call(d3.behavior.zoom()
                            .size([w_vis, h_vis])
                            .scaleExtent([1, 8])
                            .on("zoom", zoomed));

イベントリスナ

//When zoomed
function zoomed() {
   svg_vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

zoom自身はセレクションに操作は行わない。ただ、ユーザの操作にしたがってtranslateとscaleを教えてくれるのみ。リスナ側でユーザ操作にしたがってセレクションの視覚的操作(必要ならそれ以外の操作)を記述しなければならない。


2−2.zoomを使ったスクリプトの例 要素の大きさ不変

2−1の例では拡大・縮小時に描画要素の大きさも拡大・縮小される。要素の大きさは不変で位置のみ変える場合は、ズームにあわせてscaleを変更し、変更されたScaleで要素を再度配置する、という手順で行なう。

通常のX-Scale、Y-Scaleの場合は次の参考事例を見ればわかると思う。SVG Semantic Zooming


地図のような2次元のprojectionの場合は、この事例が参考になる。mousewheel-zoom + click-to-center

少し解説する。

var zoom = d3.behavior.zoom()
    .translate(projection.translate())
    .scale(projection.scale())
    .scaleExtent([height, 8 * height])
    .on("zoom", zoomed);
(途中略)
 var g = svg.append("g")
    .call(zoom);

ここがzoomの設定部分だが、肝は、scale(projection.scale())の部分。これによってズームのスケールとプロジェクションのスケールが対応づけされる。

イベントリスナー、

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);
}

の1行目は、translateとscaleをズームイベントから取得し、projectionのtranslateとscaleを変更している。2行目は、すべてのPath要素に対して、再描画する操作。ちなみに、pathは、

var path = d3.geo.path()
    .projection(projection);

という変換関数で、その中にprojectionをひもづけしているから、新しいprojection設定後に再度pathで変換すればpath要素は新しい位置に描画される。(要素のpathと変換関数のpathを混同しないように!)

他の描画要素がある場合でも、新しいprojectionで座標のみ描画しなおせばよい。