web-dev-qa-db-ja.com

d3.jsのsvgとcanvasの違い

私はd3.jsが初めてです。オブジェクトを描画するには、SVGとCanvasの2つの方法があることがわかりました。 私のユースケースは、約<100ノードおよびエッジです。私はすでにキャンバスを使用していくつかの例を試しましたが、見栄えがいいです。

SO post around SVGとCanvasの違い があります。

どちらも私の使用には問題ないように見えますが、私はキャンバスに傾倒しています(すでにいくつかの例が動作しているので)。 d3.jsコンテキストで何かが見つからない場合は、私を修正してください

11
nyi

リンクされた質問/回答に記載されている違いは、svgとcanvas(ベクター/ラスターなど)の一般的な違いを表しています。ただし、d3では、特にd3のコア部分がデータバインディングであることを考慮すると、これらの違いに追加の意味があります。

データバインディング

おそらく、d3の最も中心的な機能はデータバインディングです。 Mike Bostockは、データを要素に結合したらd3を作成する必要があると述べています。

決定的な瞬間は、初めてデータ結合が機能するようになったときです。それは魔法でした。私はそれがどのように機能するか理解していませんでしたが、それは使用するための爆発でした。作成できる視覚化の種類を不必要に制限しない視覚化のための実用的なツールがあるかもしれないことに気付きました。 リンク

SVGを使用すると、データバインディングが簡単になります。データムを個々のsvg要素に割り当て、そのデータムを使用して属性を設定/更新などすることができます。これはsvgのステートフルネスに基づいています-サークルを再選択して変更したり、プロパティにアクセスしたりできます。

キャンバスでは、キャンバスはステートレスであるため、キャンバスはピクセルのみで構成されているため、キャンバス内の図形にデータをバインドすることはできません。キャンバスには選択する要素がないため、キャンバス内の要素を選択および更新することはできません。

上記に基づいて、慣用的D3のsvgにはenter/update/exitサイクル(または基本的なappendステートメント)が必要であることがわかります。それらを表示するには要素を入力する必要があり、データに基づいてスタイルを設定することがよくあります。キャンバスでは、終了/更新と同じようにneedを入力しません。見るために追加する要素はないため、d3 svgビジュアライゼーションで使用されるenter/update/exitまたはappend/insertアプローチなしでビジュアライゼーションを描画できます必要な場合

データバインディングのないキャンバス

最後の質問 here でbl.ockの例を使用します。要素をまったく追加する(またはデータを追加する)必要がないため、forEachループを使用して各機能を描画します(SVGを使用した慣用的なD3に反します)。更新する要素がないため、各ティックごとに各フィーチャを再描画する必要があります-フレーム全体を再描画します(各ティックのキャンバスのクリアに注意してください)。ドラッグに関しては、d3.dragおよびd3.forceにはキャンバスでの使用を予測する機能がいくつかあり、ドラッグイベントを介してデータ配列を直接変更できます。DOMのノード要素がマウスと直接対話する必要性をバイパスできます(d3 .forceはデータ配列を直接変更していますが、 svgの例 でも同様です)。

データバインディングを使用しない場合、データに直接基づいて要素を描画します。

_data.forEach(function(d) {
    // drawing instructions:
    context.beginPath()....
})
_

データが変更された場合、おそらくデータを再描画します。

データバインディング付きキャンバス

つまり、キャンバスを使用してデータバインディングを実装できますが、ダミー要素を使用する別のアプローチが必要です。通常の更新/終了/入力サイクルを繰り返しますが、ダミー要素を使用しているため、何もレンダリングされません。必要に応じてキャンバスを再レンダリングし(トランジションを使用している場合は連続的になる可能性があります)、ダミー要素に基づいて物事を描画します。

ダミーの親コンテナを作成するには、次を使用できます。

_// container for dummy elements:
var faux = d3.select(document.createElement("custom"));
_

次に、enter/exit/update/append/remove/transition/etcを使用して、必要に応じて選択を行います。

_// treat as any other DOM elements:
var bars = faux.selectAll(".bar").data(data).enter()....
_

ただし、これらの選択範囲内の要素はレンダリングされないため、それらをいつどのように描画するかを指定する必要があります。データバインディングとCanvasを使用せずに、データに直接基づいて要素を描画し、データバインディングとCanvasを使用して、faux DOMの選択/要素に基づいて描画します。

_bars.each(function() {
  var selection = d3.select(this);
  context.beginPath();
  context.fillRect(selection.attr("x"), selection.attr("y")...
  ...
})
_

ここでは、終了/入力/更新などを行うたびに要素を再描画できます。これにはいくつかの利点があります。これにより、フェイクエレメントのプロパティを遷移させながら連続的に再描画することにより、D3遷移も可能になります。

以下の例には、トランジションを含む完全な入力/終了/更新サイクルがあり、データバインディングを備えたキャンバスを示しています。

_var canvas = d3.select("body")
  .append("canvas")
  .attr("width", 600)
  .attr("height", 200);
  
var context = canvas.node().getContext("2d");

var data = [1,2,3,4,5];

// container for dummy elements:
var faux = d3.select(document.createElement("custom"));

// normal update exit selection with dummy elements:
function update() {
  // modify data:
  manipulateData();
  
  
  var selection = faux.selectAll("circle")
    .data(data, function(d) { return d;});
    
  var exiting = selection.exit().size();
  var exit = selection.exit()
    .transition()
    .attr("r",0)
          .attr("cy", 70)
          .attr("fill","white")
    .duration(1200)
          .remove();
    
  var enter = selection.enter()
    .append("circle")
    .attr("cx", function(d,i) { 
       return (i + exiting) * 20 + 20; 
    })
    .attr("cy", 50)
    .attr("r", 0)
        .attr("fill",function(d) { return ["orange","steelblue","crimson","Violet","yellow"][d%5]; });
        
        enter.transition()
    .attr("r", 8)
        .attr("cx", function(d,i) { 
       return i * 20 + 20; 
    })
    .duration(1200);
    
  selection
    .transition()
    .attr("cx", function(d,i) {
      return i * 20 + 20;
    })
    .duration(1200);
        
}


// update every 1.3 seconds
setInterval(update,1300);


// rendering function, called repeatedly:
function render() {
  context.clearRect(0, 0, 600, 200);
  faux.selectAll("circle").each(function() {
    var sel = d3.select(this);
    context.beginPath();
    context.arc(sel.attr("cx"),sel.attr("cy"),sel.attr("r"),0,2*Math.PI);
        context.fillStyle = sel.attr("fill");
    context.fill();
        context.stroke();
  })
  window.requestAnimationFrame(render) 
}

window.requestAnimationFrame(render)

// to manipulate data:
var index = 6; // to keep track of elements.
function manipulateData() {
  data.forEach(function(d,i) {
    var r = Math.random();
    if (r < 0.5 && data.length > 1) {
      data.splice(i,1);
    }
    else {
      data.Push(index++);
    }
  })
}_
_<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>_

ブロックバージョン

概要

キャンバスでは、データバインディングにはダミー要素のセットが必要ですが、バインドされると、遷移と更新/入力/終了サイクルを簡単に使用できます。ただし、レンダリングは更新/入力/終了および遷移から切り離されています。視覚化を再描画する方法とタイミングを決定するのはユーザー次第です。この描画は、update/enter/exitおよびtransitionメソッドの外部で行われます。

Svgを使用すると、Enter/Update/Exitサイクルとトランジションが視覚化の要素を更新し、レンダリングとデータを1ステップでリンクします。

フェイクエレメント上のデータバインディングを持つキャンバスでは、視覚化はフェイクノードを表します。 svgでは、視覚化はノードです。

データバインディングは根本的な違いであり、慣用的なD3はSVGでそれを必要としますが、Canvasで作業するときにデータバインディングを使用するかどうかを選択できます。ただし、後述のD3に関してCanvasとSVGには他にも違いがあります。

双方向性

おそらく、Canvasを使用する際の最も大きな懸念事項は、ステートレスであり、要素ではなくピクセルのコレクションであるということです。これにより、特定のレンダリングされた図形を操作するときにマウスイベントが困難になります。マウスはCanvasと対話できますが、特定のピクセルとの対話に対して標準イベントがトリガーされます。

したがって、SVGではクリックリスナーを(たとえば)強制レイアウトの各ノードに割り当てることができますが、Canvasでは1つのクリックリスナーをキャンバス全体に設定し、位置に基づいて「クリック」と見なされるノードを決定する必要があります。

上記のD3-forceキャンバス は、強制レイアウトの_.find_メソッドを使用し、それを使用してマウスクリックに最も近いノードを検索し、ドラッグ対象をそのノードに設定します。

どのレンダリングシェイプが相互作用しているかを判断する方法はいくつかあります。

  1. レンダリングされた図形の参照マップを提供する非表示のキャンバスの作成

表示されているキャンバスの各図形は、表示されていないキャンバスに描画されますが、表示されていないキャンバスでは、一意の色を持ちます。可視キャンバス上のマウスイベントのxyを取得すると、それを使用して、非表示キャンバス上の同じxyのピクセルカラーを取得できます。色はHTMLの数値であるため、その色をデータのインデックスに変換できます。

  1. ヒートマップ/グリッドデータのスケールの反転(スケールされたxy位置からスケールされていない入力値へ)( example

  2. レンダリングされていないボロノイ図の_.find_メソッドを使用して、イベントに最も近いノードを見つける(ポイント、円の場合)

  3. 強制レイアウトの_.find_メソッドを使用して、イベントに最も近いノードを検索します(ポイント、円、主に強制レイアウトのコンテキストで)
  4. まっすぐな数学、四分木、または他の方法を使用する

最初のものは最も一般的で、確かに最も柔軟性がありますが、状況によっては他のものが好ましい場合があります。

性能

すぐにパフォーマンスについて触れます。クエストのリンクされた投稿 " SVGとCanvasの違いは何ですか "では、そこの答えでは十分に太字ではないかもしれませんが、一般的にcanvasとsvgは、特にアニメーション化されている数千のノードをレンダリングします。

より多くのノードがレンダリングされ、ノードがより多くのこと(遷移、移動など)を行うと、Canvasのパフォーマンスが向上します。

次に、Canvas(フェイクノードにデータバインディングを使用)とSVGおよび19 200の同時遷移の簡単な比較を示します。

Canvasは2つのうちのより滑らかなはずです。

D3モジュール

最後に、D3のモジュールに触れます。これらのほとんどはDOMとまったく対話せず、SVGまたはCanvasのいずれでも簡単に使用できます。たとえば、d3-quadtreeまたはd3-time-formatは、DOMまたはレンダリングをまったく処理しないため、SVGまたはCanvas固有ではありません。 d3-hierarchyなどのモジュールは実際には何もレンダリングしませんが、CanvasまたはSVGでレンダリングするために必要な情報を提供します。

Most SVGパスデータを提供するモジュールとメソッドは、キャンバスパスメソッド呼び出しの生成にも使用できるため、SVGとCanvasのどちらにも比較的簡単に使用できます。

ここでいくつかのモジュールについて具体的に言及します。

D3-selection

明らかにこのモジュールには選択が必要であり、選択には要素が必要です。したがって、enter/update/exitサイクルまたは選択.append/remove/lower/raiseのようなものにCanvasでこれを使用するには、Canvasでfaux要素を使用します。

Canvasでは、selection.on()が割り当てられたイベントリスナーはデータバインディングの有無にかかわらず動作できます。マウス操作の課題は上記のとおりです。

D3-transition

このモジュールは要素のプロパティを移行するため、一般的にCanvasで使用されるのは、faux要素でデータバインディングを使用している場合のみです。

D3-axis

このモジュールは、キャンバスを使用するためにかなりの量の作業を行わない限り、厳密にSVGです。このモジュールは、SVGを使用する場合、特に軸を移行する場合に非常に役立ちます。

D3-path

これは、Canvasパスコマンドを受け取り、それらをSVGパスデータに変換します。 SVGの状況にキャンバスコードを採用するのに役立ちます。 SVGパスデータを生成するために、D3で内部的に使用されます。

29
Andrew Reid