web-dev-qa-db-fra.com

D3, annexes imbriquées et flux de données

Je suis enfin en train d'apprendre D3 et je suis tombé sur un problème auquel je n'ai pas trouvé de réponse. Je ne sais pas si ma question est parce que je ne pense pas idiomatiquement avec la bibliothèque, ou si c'est à cause d'une procédure que je ne connais pas actuellement. Je dois également mentionner que je n'ai commencé à faire des choses liées au Web qu'en juin, donc je suis assez nouveau pour javascript.

Disons que nous construisons un outil qui donne à un utilisateur une liste d'aliments avec des images respectives. Et permet d'ajouter la contrainte supplémentaire que chaque élément de liste doit être étiqueté par un ID unique afin qu'il puisse être lié à une autre vue. Ma première intuition pour résoudre ce problème est de créer une liste de <div> est chacun avec son propre ID, où chaque div a son propre <p> et <img>. Le code HTML résultant ressemblerait à quelque chose comme:

<div id="chocolate">
  <p>Chocolate Cookie</p>
  <img src="chocolate.jpg" />
</div>
<div id="sugar">
  <p>Sugar Cookie</p>
  <img src="sugar.jpg" />
</div>

Les données de cet outil se trouvent dans un tableau JSON, où un JSON individuel ressemble à:

{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }

Existe-t-il un moyen de générer le HTML d'un seul coup? En commençant par un cas de base d'ajout d'un div, le code pourrait ressembler à quelque chose comme:

d3.select(containerId).selectAll('div')                                                          
   .data(food)
   .enter().append('div')
   .attr('id', function(d) { return d.label; });

Maintenant, qu'en est-il de l'ajout d'un <div> avec un <p> dedans? Ma pensée originale était de faire quelque chose comme:

d3.select(containerId).selectAll('div')                                                          
   .data(food)
   .enter().append('div')
   .attr('id', function(d) { return d.label; })
       .append('p').text('somethingHere');

Mais, je vois deux problèmes avec cela: (1) comment obtenir les données de l'élément div, et (2) comment pouvez-vous ajouter plusieurs enfants au même parent dans une chaîne déclarative? Je ne peux pas penser à un moyen de faire la troisième étape où j'ajouterais sur le img.

J'ai trouvé la mention d'une sélection imbriquée sur un autre post, qui indiquait http://bost.ocks.org/mike/nest/ . Mais la sélection imbriquée, et donc la séparation des appendices en trois morceaux, est-elle appropriée/idiomatique pour cette situation? Ou existe-t-il réellement un moyen bien construit de former cette structure en une chaîne de déclarations? Il semble qu'il puisse y avoir un moyen avec les sous-sélections mentionnées sur https://github.com/mbostock/d3/wiki/Selections , mais je ne connais pas assez le langage pour tester cette hypothèse.

D'un niveau conceptuel, ces trois objets (div, p et img) sont traités plus comme un groupe plutôt que comme des entités distinctes, et ce serait bien si le code reflétait cela aussi.

53
Connor Gramazio

Vous ne pouvez pas ajouter plusieurs éléments enfants dans une seule commande chaînée. Vous devrez enregistrer la sélection parent dans une variable. Cela devrait faire ce que vous voulez:

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

var diventer = d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; });

diventer.append("p")
    .text(function(d) { return d.text; });

diventer.append("img")
    .attr("src", function(d) { return d.img; });​

Voir violon de travail: http://jsfiddle.net/UNjuP/

Vous vous demandiez comment un élément enfant comme p ou img, accède aux données liées à son parent. Les données sont héritées automatiquement du parent lorsque vous ajoutez un nouvel élément. Cela signifie que les éléments p et img auront les mêmes données liées à eux que le div parent.

Cette propagation de données n'est pas unique pour la méthode append. Cela se produit avec les méthodes selection suivantes: append , insert , et select .

Par exemple, pour selection.append:

selection.append (nom)

Ajoute un nouvel élément avec le nom spécifié comme dernier enfant de chaque élément de la sélection actuelle. Renvoie une nouvelle sélection contenant les éléments ajoutés. Chaque nouvel élément hérite des données des éléments actuels, le cas échéant, de la même manière que pour les sous-sélections. Le nom doit être spécifié en tant que constante, bien qu'à l'avenir nous pourrions autoriser l'ajout d'éléments existants ou d'une fonction pour générer le nom de manière dynamique.

N'hésitez pas à vous renseigner sur les détails si quelque chose n'est pas clair.


ÉDITER

Vous pouvez ajouter plusieurs éléments enfants sans stocker la sélection dans une variable en utilisant le selection.each méthode. Vous pouvez ensuite également accéder directement aux données du parent:

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; })
    .each(function(d) {
        d3.select(this).append("p")
          .text(d.text);
        d3.select(this).append("img")
          .attr("src", d.img);
    });
74
nautat

Encore une fois, pas substantiellement différent, mais ma méthode préférée serait d'utiliser 'call'

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; })
  .call(function(parent){
    parent.append('p').text(function(d){ return d.text; });
    parent.append('img').attr("src", function(d) { return d.img; });​
  });

vous n'avez pas besoin de stocker de variables et vous pouvez factoriser la fonction appelée si vous souhaitez utiliser une structure similaire ailleurs.

23
Tom P

Ce n'est pas fondamentalement différent de réponse de nautat , mais je pense que le code peut être rendu un peu plus propre en enregistrant la sélection update plutôt que entrez sélection, et obtenez la sélection entrez pour l'opération qui en a besoin (en ajoutant le div environnant).

Lorsque vous insérez ou ajoutez un élément dans la sélection enter () , il est ajouté à la sélection de mise à jour avec laquelle vous pouvez travailler par la suite. Cela signifie que vous pouvez joindre les données, puis ajouter un div avec la sélection d'entrée, puis lorsque vous ajoutez avec la sélection de mise à jour, vous ajouterez dedans les div que vous avez ajoutées dans le entrez la sélection:

var cookies = [
  { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" },
  { "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" }];

var cookie = d3.select("#cookie-jar").selectAll().data(cookies);
cookie.enter().append("div");
cookie.append("p").text(function(d){ return d.text });
cookie.append("img").attr("src",function(d){ return d.img });
#cookie-jar div { border: solid 1px black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="cookie-jar"></div>
3
Joshua Taylor