まくろぐ
更新:
作成:

svg 要素内の各描画要素に設定する座標値のデフォルト単位はピクセル (px) ですが、上位の svg 要素に viewBox 属性が設定されている場合は、その論理的な座標系内での座標値を指定することになります。 例えば、次のように viewBox を設定すると、画面上での svg 要素の表示サイズは 800x600 となりますが、svg 要素内に配置する各描画要素 (rect など)は、40x30 という 仮想的なサイズ の中で座標指定することになります。

<svg viewBox="0 0 40 30" width="800" height="600">

下記の例では 1 つの rect 要素を配置していますが、これは svg 要素の左上 4 分の 1 を占める矩形を描画します。

<svg viewBox="0 0 40 20" width="200" height="100" style="border: thin solid gray">
  <rect x="0" y="0" width="20" height="10" fill="steelblue" />
</svg>
表示例

関連記事

更新:
作成:

d3-zoom とは

D3.js に組み込まれている d3-zoom モジュールは、下記のようなユーザー操作をハンドルためのモジュールです。

  • マウスホイールによるズーム操作
  • マウスドラッグによるパン操作
  • タッチパネルのピンチイン、ピンチアウトによるズーム操作
  • タッチパネルのスワイプによるパン操作

d3-zoom によるイベントハンドリング処理は描画処理とは独立しているので、SVG や Canvas、スケール (d3-scale)、軸 (d3-axis) などと自在に組み合わせて使うことができます。

d3-zoom の簡単な使用例

実装例

下記の svg 画像の中で、ドラッグやホイール操作を行うと、矩形のズームや移動を行うことができます。

図: d3-zoom によるズーム/パン操作
実装コード
<svg id="svg-gh4oas2" width="200" height="100"></svg>
<script>
const svg = d3.select("#svg-gh4oas2")
const topLayer = svg.append("g")  // このレイヤーごと動かすことにする

// 矩形を 1 つ追加
topLayer
  .append("rect")
  .attr("x", 80)
  .attr("y", 40)
  .attr("width", 40)
  .attr("height", 20)
  .attr("fill", "green")

svg.call(d3.zoom().on("zoom", zoomed))  // zoom behavior を登録する

function zoomed(event) {
  // イベントが発生したらイベントオブジェクトの内容に従って transform するだけ
  topLayer.attr("transform", event.transform);
}
</script>

zoom オブジェクトの作成

d3-zoom は次のような手順で使用します。

  1. d3.zoom() でズーム操作をハンドルする behavior オブジェクトを作成する(以下 zoom オブジェクトと呼びます)
  2. zoom オブジェクトの on() メソッドでイベントハンドラを設定する
  3. 任意の D3 セレクションオブジェクトに対して selection.call(zoom) でイベントハンドラを適用する
    • 実質 zoom(selection) を呼び出すの同じですが、メソッドチェーンに組み込むために selection.call() を使うことができます。

上記手順をまとめてコード化すると次のような感じになります。 多くの場合、zoom() 関数の適用対象は svg 要素のセレクションオブジェクトです。

svg.call(d3.zoom().on("zoom", zoomed));

ズームイベントの処理

d3.zoom().on("zoom", zoomed) のように登録されたイベントハンドラー zoomed には、ユーザーのズーム/パン操作を表現するイベントオブジェクトが渡されて呼び出されます。 このイベントオブジェクトには、g 要素などに対してどのような transform 処理を行えばよいかの情報 (event.transform) が含まれています。 つまり、基本的にはその内容に従って、g 要素などの transform 属性の値をアップデートしてやるだけで OK です。

function zoomed(event) {
  topLayer.attr("transform", event.transform);
}

(応用)ズーム倍率やパン範囲を制限する

zoom オブジェクトの下記のメソッドを使うことで、ズームやパンをどの程度まで許容するかを設定できます。

  • zoom.scaleExtent() … ズーム倍率を制限する
  • zoom.translateExtent() … パン範囲を制限する

パン範囲 (translateExtent) の方は、現在のズーム倍率などによって調整が必要で難しかったりするので、とりあえずズーム倍率 (scaleExtent) の制限だけしておくのがよいかもしれません。

図: d3-zoom によるズーム/パン操作
実装コード
<svg id="svg-agsdno7" width="200" height="100"></svg>
<script>
const svg = d3.select("#svg-agsdno7")
const width = +svg.attr("width")
const height = +svg.attr("height")
const topLayer = svg.append("g")

topLayer.append("circle")
  .attr("cx", 100)
  .attr("cy", 50)
  .attr("r", 20)
  .attr("fill", "#f48")

const zoom = d3.zoom()
  .scaleExtent([1, 3])  // ズームは 1 倍 ~ 3 倍の範囲
  .translateExtent([  // パンは元の svg サイズの半分まで(中央の要素を端に移動できる範囲)
    [-width * 0.5, -height * 0.5],
    [width * 1.5, height * 1.5]
  ])
  .on("zoom", zoomed)

svg
  .call(zoom)
  .on("wheel", (event) => event.preventDefault())

function zoomed(event) {
  topLayer.attr("transform", event.transform);
}
</script>

上記の例では、 svg 要素の wheel イベントのデフォルト動作を抑制 (preventDefault()) していることに注意してください。 これを入れておかないと、マウスホイールをズーム倍率範囲を超えて回転させたときに、ページ全体がスクロールしてしまいます。

関連記事

更新:
作成:

SVG の g 要素は、子要素をグループ化して操作を行うための要素です。 ここでは、g 要素をどのような用途で利用できるのかをまとめておきます。

子要素に共通のスタイル(色、太さなど)をセットする

<svg width="195" height="60" style="border: solid 1px gray;">
  <g fill="#cef" stroke="blue" stroke-width="1.5">
    <rect x="15" y="15" width="30" height="30" />
    <circle cx="75" cy="30" r="15" />
  </g>
  <g fill="#ffc" stroke="darkorange" stroke-width="1.5">
    <rect x="105" y="15" width="30" height="30" />
    <circle cx="165" cy="30" r="15" />
  </g>
</svg>

この例では、2 つの g 要素の下に、rectcircle を 1 つずつ配置しています。 それらの塗りつぶし色 (fill) や枠線の色 (stroke) は、親の g 要素にセットしたものが使用されます。

多数の描画要素に同じスタイルを割り当てたいときは、g 要素でまとめてスタイル設定すると効率がよいです。

子要素の座標をまとめて移動する (transform)

Hello!
<svg width="180" height="60" style="border: thin solid gray;">
  <g transform="translate(50 30)">
    <circle cx="0" cy="0" r="10" fill="red" />
    <text x="15" y="2" dominant-baseline="middle" font-size="20" font-weight="800" fill="blue">
      Hello!
    </text>
  </g>
</svg>

g 要素の下に配置した子要素は、g 要素の transform 属性を使ってまとめて移動させることができます。 上記のサンプルコードでは、transform 属性の値として translate(50, 30) を指定しており、g 要素でグルーピングされたすべての子要素が x 軸方向へ 50、y 軸方向へ 30 だけ平行移動しています。 g 要素をどこに配置するかは transform 属性で制御できるので、g 要素でグルーピングされた部分は、自分の座標系だけを意識したコンポーネントのように記述できます。

transform の値には、次のような変換処理を指定することができます。 複数の変換処理をスペース区切りで並べて指定できます。

  • 移動 … translate(30 10) で x 軸方向にへ 30、y 軸方向に 10 だけ平行移動する
  • 拡縮 … scale(2 0.5) で x 軸方向に 2 倍、y 軸方向に 0.5 倍にスケールする
  • 回転 … rotate(30 10 10) で (10, 10) の座標を中心に 30 度回転する
  • 歪み … skewX(30)skewY(30) で x 軸あるいは y 軸方向に 30 度歪ませる

例えば、次のように指定すると、平行移動、スキュー、スケールを順番に適用します。

<g transform="translate(35 30) skewX(-30) scale(1.5)">
Hello!
図: 上記の transform 指定の結果

ちなみに、このような g 要素を D3.js で生成する場合のコードは次のような感じになります。 複数の g 要素を参照する D3 セレクションオブジェクトを変数化して使いまわすところがポイントです(下記の例では nodes 変数)。

D3.js で g 要素の transform 処理
<svg id="svg-hsae2we" w="200" height="80"></svg>
<script>
const svg = d3.select('#svg-hsae2we');

const nodesData = [
	{ label: 'AAA', x: 20, y: 20 },
	{ label: 'BBB', x: 40, y: 40 },
	{ label: 'CCC', x: 60, y: 60 }
];

// ノードを描画するための g 要素をノードデータの数だけ作成
const nodes = svg.selectAll('g').data(nodesData).join('g');

// 各 g 要素に circle 要素を追加
nodes.append('circle')
  .attr('cx', 0)
  .attr('cy', 0)
  .attr('r', 10)
  .attr('fill', 'red');

// 各 g 要素に text 要素を追加
nodes
  .append('text')
  .attr('x', 15)
  .attr('y', 2)
  .attr('dominant-baseline', 'middle')
  .attr('font-size', 20)
  .attr('font-weight', '800')
  .attr('fill', 'blue')
  .text((d) => d.label);

// 各 g 要素の transform 属性を設定
nodes.attr('transform', (d) => `translate(${d.x} ${d.y})`);
</script>

レイヤー構造を作り表示順序を制御する

D3.js を使った JavaScript コードなどで SVG を動的に構築する場合、各要素の表示順序(どちらが手前に表示されるか)が問題になったりします。 このようなケースでは、g 要素で表示順序を制御するだけのレイヤー構造を作り、そこに子要素を追加していくというテクニックが使えます。

次の例では、2 つの g 要素(layer1layer2)を作成し、その子要素として circlerect 要素を配置しています。 layer1layer2 の順番で g 要素を追加しているので、layer2 に配置した子要素(この場合は circle)の方が、手前に表示されることが保証されます。

図: レイヤー構造による表示順序の制御
<svg id="svg-rub6v9m" width="120" height="80" />

<script>
const svg = d3.select("#svg-rub6v9m")
const layer1 = svg.append("g");
const layer2 = svg.append("g");

// layer2 に追加したものは手前に表示される
layer2.append("circle")
  .attr("cx", 75)
  .attr("cy", 40)
  .attr("r", 20)
  .attr("fill", "red")

// layer1 に追加したものは奥に表示される
layer1.append("rect")
  .attr("x", 25)
  .attr("y", 15)
  .attr("width", 50)
  .attr("height", 50)
  .attr("fill", "blue");
</script>

関連記事

メニュー

まくろぐ
サイトマップまくへのメッセージ