web-dev-qa-db-fra.com

Différence entre svg et canvas dans d3.js

Je suis nouveau sur d3.js. J'ai pensé qu'il y avait deux façons de dessiner les objets - SVG et Canvas. Mon cas d'utilisation est d'environ <100 nœuds et bords . J'ai déjà essayé quelques exemples en utilisant la toile et ça a l'air super.

Je vois qu'il y a un SO poste autour la différence entre SVG et Canvas .

Les deux semblent corrects pour mon utilisation, cependant, je suis enclin à la toile (car j'ai déjà quelques exemples de travail). veuillez me corriger si je manque quelque chose dans le contexte d3.js?

11
nyi

Les différences répertoriées dans les questions/réponses liées parlent des différences générales entre svg et canvas (vecteur/raster, etc.). Cependant, avec d3, ces différences ont des implications supplémentaires, surtout si l'on considère qu'une partie centrale de d3 est la liaison de données.

Liaison de données

La liaison de données est peut-être la caractéristique la plus centrale de d3. Mike Bostock déclare qu'il devait créer d3 une fois qu'il a joint les données aux éléments:

Le moment décisif a été lorsque j'ai fait fonctionner la jointure de données pour la première fois. C'était magique. Je n'étais même pas sûr d'avoir compris comment cela fonctionnait, mais c'était génial à utiliser. J'ai réalisé qu'il pouvait y avoir un outil pratique de visualisation qui ne restreignait pas inutilement les types de visualisations que vous pouviez faire. lien

Avec SVG, la liaison de données est facile - nous pouvons assigner une donnée à un élément svg individuel et ensuite utiliser cette donnée pour définir ses attributs/le mettre à jour/etc. Ceci est construit sur l'état de svg - nous pouvons resélectionner un cercle et le modifier ou accéder à ses propriétés.

Avec Canvas, le canevas est sans état, nous ne pouvons donc pas lier des données à des formes dans le canevas car le canevas ne comprend que des pixels. En tant que tel, nous ne pouvons pas sélectionner et mettre à jour des éléments dans le canevas car le canevas n'a aucun élément à sélectionner.

Sur la base de ce qui précède, nous pouvons voir que le cycle d'entrée/mise à jour/sortie (ou les instructions d'ajout de base) sont nécessaires pour svg dans D3 idiomatique: nous devons entrer des éléments pour les voir et nous les stylons souvent en fonction de leur datum. Avec canvas, nous n'avons pas besoin pour entrer quoi que ce soit, de même pour quitter/mettre à jour. Il n'y a aucun élément à ajouter pour voir, donc nous pouvons dessiner des visualisations sans les approches enter/update/exit ou append/insert utilisées dans les visualisations d3 svg, si nous voulons.

Toile sans liaison de données

Je vais utiliser l'exemple bl.ock dans votre dernière question, ici . Parce que nous n'avons pas du tout besoin d'ajouter d'éléments (ou d'y ajouter des données), nous utilisons une boucle forEach pour dessiner chaque fonctionnalité (ce qui est contraire à D3 idiomatique avec SVG). Puisqu'il n'y a aucun élément à mettre à jour, nous devons redessiner chaque fonctionnalité à chaque tick - redessiner le cadre entier (notez l'effacement du canevas à chaque tick). En ce qui concerne le glissement, d3.drag et d3.force ont des fonctionnalités anticipant l'utilisation avec le canevas, et peuvent nous permettre de modifier le tableau de données directement via des événements de glissement - en contournant tout besoin d'éléments de noeud dans le DOM pour interagir directement avec la souris (d3 .force modifie également directement le tableau de données - mais il le fait également dans exemple svg ).

Sans liaison de données, nous dessinons directement des éléments basés sur les données:

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

Si les données changent, nous redessinerons probablement les données.

Toile avec liaison de données

Cela dit, vous pouvez implémenter la liaison de données avec le canevas, mais cela nécessite une approche différente en utilisant des éléments factices. Nous passons par le cycle régulier de mise à jour/sortie/entrée, mais comme nous utilisons des éléments factices, rien n'est rendu. Nous restituons le canevas à tout moment (cela peut être continu si nous utilisons des transitions) et dessinons des choses en fonction des éléments factices.

Pour créer un conteneur parent factice, nous pouvons utiliser:

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

Ensuite, nous pouvons faire des sélections selon les besoins, en utilisant enter/exit/update/append/remove/transition/etc:

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

Mais comme les éléments de ces sélections ne sont pas rendus, nous devons spécifier comment et quand les dessiner. Sans liaison de données et Canvas, nous avons dessiné des éléments directement à partir des données, avec la liaison de données et Canvas, nous dessinons en fonction de la sélection/de l'élément dans le faux DOM:

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

Ici, nous pouvons redessiner les éléments à chaque sortie/entrée/mise à jour, etc., ce qui peut présenter certains avantages. Cela permet également des transitions D3 en redessinant en continu tout en transférant les propriétés sur les faux éléments.

L'exemple ci-dessous a un cycle complet d'entrée/sortie/mise à jour avec transitions, illustrant le canevas avec liaison de données:

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>

Version de bloc .

Résumé

Avec canvas, la liaison de données nécessite un ensemble d'éléments factices, mais, une fois lié, vous pouvez facilement utiliser les transitions et le cycle de mise à jour/entrée/sortie. Mais le rendu est détaché de la mise à jour/entrée/sortie et des transitions - c'est à vous de décider comment et quand redessiner la visualisation. Ce dessin a lieu en dehors des méthodes de mise à jour/entrée/sortie et transition.

Avec svg, le cycle d'entrée/mise à jour/sortie et les transitions mettent à jour les éléments dans la visualisation, reliant le rendu et les données en une seule étape.

Dans un canevas avec liaison de données sur de faux éléments, la visualisation représente les faux nœuds. En svg, la visualisation correspond aux nœuds.

La liaison de données est une différence fondamentale, idiomatique D3 l'exige en SVG mais nous permet de choisir si nous voulons l'utiliser lorsque nous travaillons avec Canvas. Cependant, il existe d'autres différences entre Canvas et SVG par rapport à D3 mentionnées ci-dessous:

L'interactivité

La préoccupation la plus importante de l'utilisation de Canvas est peut-être qu'elle est sans état, juste une collection de pixels plutôt que d'éléments. Cela rend les événements de la souris difficiles lors de l'interaction avec des formes de rendu spécifiques. Alors que la souris peut interagir avec le canevas, des événements standard sont déclenchés pour des interactions avec des pixels spécifiques.

Donc, avec SVG, nous pouvons attribuer un écouteur de clics (par exemple) à chaque nœud dans une disposition de force, avec Canvas, nous définissons un écouteur de clics pour l'ensemble du canevas, puis en fonction de la position, nous devons déterminer quel nœud doit être considéré comme "cliqué". .

Le canevas D3-force exemple mentionné ci-dessus utilise la méthode .find D'une disposition de force et l'utilise pour trouver le nœud le plus proche d'un clic de souris, puis définit le sujet de glissement sur ce nœud.

Il existe plusieurs façons de déterminer avec quelle forme rendue interagit:

  1. Création d'un canevas masqué qui fournit une carte de référence pour les formes rendues

Chaque forme de la toile visible est dessinée sur la toile invisible, mais sur la toile invisible, elle a une couleur unique. En prenant le xy d'un événement de souris sur le canevas visible, nous pouvons l'utiliser pour obtenir la couleur des pixels au même xy sur le canevas invisible. Puisque les couleurs sont des nombres en HTML, nous pouvons convertir cette couleur en un index de donnée.

  1. Inverser les échelles (position xy mise à l'échelle en valeurs d'entrée non mises à l'échelle) pour les données de carte thermique/maillées ( exemple )

  2. Utilisation de la méthode .find D'un diagramme de Voronoi non rendu pour trouver le nœud le plus proche de l'événement (pour les points, les cercles)

  3. Utilisation de la méthode .find D'une disposition de force pour trouver le nœud le plus proche de l'événement (pour les points, les cercles, principalement dans le contexte des dispositions de force)
  4. Utilisation de mathématiques droites, d'arbres quadratiques ou d'autres méthodes

Le premier peut être le plus courant et certainement le plus flexible, mais les autres peuvent être préférables selon le contexte.

Performance

Je parlerai très rapidement des performances. Dans le post lié de la questine " Quelle est la différence entre SVG et Canvas " il peut ne pas être assez gras dans les réponses, mais en général canvas et svg diffèrent dans le temps de rendu lors de la manipulation de milliers de nœuds, surtout si rendu des milliers de nœuds en cours d'animation.

Canvas devient de plus en plus performant à mesure que plus de nœuds sont rendus et que les nœuds font plus de choses (transition, déplacement, etc.).

Voici une comparaison rapide de Canvas (avec liaison de données sur les faux nœuds) et SVG et 19 200 transitions simultanées:

La toile devrait être la plus douce des deux.

Modules D3

Enfin, je vais aborder les modules de D3. La plupart d'entre eux n'interagissent pas du tout avec le DOM et peuvent être utilisés facilement pour SVG ou Canvas. Par exemple, le format d3-quadtree ou d3-time-format n'est pas spécifique à SVG ou Canvas car ils ne traitent pas du tout avec le DOM ou le rendu. Les modules tels que la hiérarchie d3 ne rendent en fait rien non plus, mais fournissent les informations nécessaires pour effectuer le rendu dans Canvas ou SVG.

La plupart les modules et méthodes qui fournissent des données de chemin SVG peuvent également être utilisés pour générer des appels de méthode de chemin de canevas, et par conséquent peuvent être utilisés pour SVG et Canvas relativement facilement.

Je mentionnerai quelques modules spécifiquement ici:

Sélection D3

Évidemment, ce module nécessite des sélections, les sélections nécessitent des éléments. Donc, pour l'utiliser avec Canvas pour des choses comme le cycle d'entrée/mise à jour/sortie ou la sélection .append/remove/lower/raise, nous voulons utiliser de faux éléments avec Canvas.

Avec Canvas, les écouteurs d'événements assignés avec selection.on() peuvent fonctionner avec ou sans liaison de données, les défis des interactions avec la souris sont notés ci-dessus.

Transition D3

Ce module transite les propriétés des éléments, il ne serait donc généralement utilisé avec Canvas que si nous utilisions la liaison de données avec de faux éléments.

Axe D3

Ce module est strictement SVG, sauf s'il est disposé à faire une bonne quantité de travail pour le chausse-pied dans une utilisation Canvas. Ce module est extrêmement utile lorsque vous travaillez avec SVG, en particulier lors de la transition de l'axe.

Chemin D3

Cela prend les commandes de chemin Canvas et les convertit en données de chemin SVG. Utile pour adopter le code canvas dans des situations SVG. Principalement utilisé en interne avec D3 pour produire des données de chemin SVG.

29
Andrew Reid