web-dev-qa-db-fra.com

d3.js v4: Comment accéder à l'index de référence du groupe parent?

La description de la fonction selection.data inclut un exemple avec plusieurs groupes ( link ) où un tableau à deux dimensions est transformé en un tableau HTML.

Dans d3.js v3, pour les dimensions inférieures, les fonctions d’accesseur incluaient un troisième argument qui était l’index du datum du groupe parent:

td.text(function(d,i,j) {
  return "Row: " + j;
});

Dans la v4, cet argument j a été remplacé par la liste de noeuds de la sélection. Comment puis-je accéder à l'index de référence du groupe parent maintenant?

19
Oliver

Parfois, une réponse ne fournit pas une solution, car la solution peut ne pas exister. Cela semble être le cas.

Selon Bostock:

J'ai fusionné la nouvelle implémentation de la sélection à deux niveaux dans le master et simplifié également le suivi des parents en utilisant un tableau de parents parallèles.

Une bonne propriété de cette nouvelle approche est que selection.data can évaluer les valeurs fonctionnent exactement de la même manière que les autres fonctions de sélection: la fonction de valeurs est passée {d, i, nœuds} où il s’agit du noeud parent, d est la donnée parent, i est le parent (group) index, et nodes est le tableau de noeuds parents (un par groupe) . De plus, le tableau des parents peut être réutilisé par des sous-sélections qui n'en ont pas regrouper la sélection, telle que selection.select, puisque les parents Le tableau est immuable.

Cette modification restreint les fonctionnalités, en ce sens que vous ne pouvez pas accéder au nœud parent depuis une fonction de sélection, ni le données parent, ni l'index du groupe - mais je crois que c'est finalement A Bonne chose parce que cela encourage le code plus simple.

(c'est moi qui souligne)

Voici le lien: https://github.com/d3/d3-selection/issues/47

Donc, il n'est pas possible d'obtenir l'index du groupe du parent en utilisant selection (l'index du groupe du parent peut être récupéré en utilisant selection.data, comme le montre l'extrait ci-dessous).

var testData = [
[
  {x: 1, y: 40},
  {x: 2, y: 43},
  {x: 3, y: 12},
  {x: 6, y: 23}
], [
  {x: 1, y: 12},
  {x: 4, y: 18},
  {x: 5, y: 73},
  {x: 6, y: 27}
], [
  {x: 1, y: 60},
  {x: 2, y: 49},
  {x: 3, y: 16},
  {x: 6, y: 20}
 ] 
];

var svg = d3.select("body")
	.append("svg")
  .attr("width", 300)
  .attr("height", 300);
  
var g = svg.selectAll(".groups")
    .data(testData)
    .enter()
    .append("g");
    
var rects = g.selectAll("rect")
    .data(function(d, i , j) { console.log("Data: " + JSON.stringify(d), "\nIndex: " + JSON.stringify(i), "\nNode: " + JSON.stringify(j)); return d})
    .enter()
    .append("rect");
<script src="https://d3js.org/d3.v4.min.js"></script>

15
Gerardo Furtado

Ma solution de contournement est un peu similaire à Dinesh Rajan ', en supposant que l'index parent est nécessaire pour l'attribut someAttr of g.nestedElt:

v3:

svg.selectAll(".someClass")
    .data(nestedData)
  .enter()
  .append("g")
    .attr("class", "someClass")
  .selectAll(".nestedElt")
    .data(Object)
  .enter()
  .append("g")
    .attr("class", "nestedElt")
    .attr("someAttr", function(d, i, j) {

    });

v4:

svg.selectAll(".someClass")
    .data(nestedData)
  .enter()
  .append("g")
    .attr("class", "someClass")
    .attr("data-index", function(d, i) { return i; }) // make parent index available from DOM
  .selectAll(".nestedElt")
    .data(Object)
  .enter()
  .append("g")
    .attr("class", "nestedElt")
    .attr("someAttr", function(d, i) {
      var j = +this.parentNode.getAttribute("data-index");
    });
5
Zim

J'ai fini par définir une variable externe "j" puis l'incrémenter chaque fois que "i" vaut 0

exemple V3 snippet ci-dessous.

rowcols.enter().append("rect")
 .attr("x", function (d, i, j) { return CalcXPos(d, j); })
 .attr("fill", function (d, i, j) { return GetColor(d, j); })

et dans V4 , code converti comme ci-dessous. 

var j = -1;
rowcols.enter().append("rect") 
 .attr("x", function (d, i) { if (i == 0) { j++ }; return CalcXPos(d, j); })
 .attr("fill", function (d, i) { return GetColor(d, j); })
4
Dinesh Rajan

Voici un exemple d'utilisation de la méthode selection.each(). Je ne pense pas que cela soit compliqué, mais cela a ralenti le rendu sur une grande matrice. Notez que le code suivant suppose une sélection table existante et un appel à update().

update(matrix) {
        var self = this;
        var tr = table.selectAll("tr").data(matrix);

        tr.exit().remove();

        tr.enter().append("tr");

        tr.each(addCells);

        function addCells(data, rowIndex) {
            var td = d3.select(this).selectAll("td")
                .data(function (d) {
                    return d;
                });
            td.exit().remove();

            td.enter().append("td");

            td.attr("class", function (d) {
                return d === 0 ? "dead" : "alive";
            });

            td.on("click", function(d,i){
                matrix[rowIndex][i] = d === 1 ? 0 : 1; // rowIndex now available for use in callback.                   
            });
        }

        setTimeout(function() {
            update(getNewMatrix(matrix))
        }, 1000);
    },
2
david004

Supposons que vous souhaitiez créer un sélecteur imbriqué et que votredata soit un tableau dans lequel chaque élément, à son tour, contient un tableau, disons "valeurs". Ensuite, vous avez probablement un code comme celui-ci:

var aInnerSelection = oSelection.selectAll(".someClass") //
    .data(d.values) //
    ...

Vous pouvez remplacer le tableau par les valeurs par un nouveau tableau, oùvous mettez en cache les index du groupe.

var aInnerSelection = oSelection.selectAll(".someClass") //
    .data(function (d, i) {
        var aData = d.values.map(function mapValuesToIndexedValues(elem, index) {
                return {
                    outerIndex: i,
                    innerIndex: index,
                    datum: elem
                };
            })
            return aData;
    }, function (d, i) {
        return d.innerIndex;
    }) //
    ...

Supposons que votre tableau externe ressemble à ceci: [{Nom "X", valeurs: ["A", "B"]}, {nom "y", valeurs: ["C", "D"]}
Avec la première approche, la sélection imbriquée vous amène à partir de maintenant

                  d                                i
------------------------------------------------------------------
root dummy X      {name "X", values: ["A", "B"]}    0
     dummy Y      {name "Y", values: ["C", "D"]}    1

jusqu'ici.

             d       i
------------------------------------------------------------------
root X  A    "A"     0
        B    "B"     1
     Y  C    "C"     2
        D    "D"     3

Avec le tableau augmenté, vous vous retrouvez ici à la place:

             d                                              i
------------------------------------------------------------------
root X  A    {datum: "A", outerIndex: 0, innerIndex: 0}     0
        B    {datum: "B", outerIndex: 0, innerIndex: 1}     1
     Y  C    {datum: "C", outerIndex: 1, innerIndex: 0}     2
        D    {datum: "D", outerIndex: 1, innerIndex: 1}     3

Ainsi, vous avez dans les sélections imbriquées, dans toute fonction (d, i), toutes les informations dont vous avez besoin.

2
Frithjof

Si j est la liste de noeuds ... 

  • j[i] est le noeud actuel (par exemple, l'élément td)} _,
  • j[i].parentNode est le parent niveau-1 _ ​​(par exemple, l’élément de ligne)},
  • j[i].parentNode.parentNode est le parent niveau-2 _ ​​(par exemple, l’élément de la table)},

  • j[i].parentNode.parentNode.childNodes est le tableau des parents de niveau 1 _ ​​(par exemple. tableau des éléments de ligne)}, y compris le parent d'origine.

La question est donc: quel est l'index du parent (la ligne) par rapport à son parent (la table)?

Nous pouvons trouver ceci en utilisant Array.prototype.indexOf comme si ...

k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes,j[i].parentNode);

Vous pouvez voir dans l'extrait de code ci-dessous que la ligne est imprimée dans chaque cellule td lorsque k est renvoyé.

var testData = [
[
  {x: 1, y: 1},
  {x: 1, y: 2},
  {x: 1, y: 3},
  {x: 1, y: 4}
], [
  {x: 2, y: 1},
  {x: 2, y: 2},
  {x: 2, y: 3},
  {x: 2, y: 4}
], [
  {x: 3, y: 4},
  {x: 3, y: 4},
  {x: 3, y: 4},
  {x: 3, y: 4}
 ]
];

var tableData =
  d3.select('body').selectAll('table')
    .data([testData]);

var tables =
  tableData.enter()
  .append('table');

var rowData =
  tables.selectAll('table')
    .data(function(d,i,j){
      return d;
    });

var rows =
  rowData.enter()
  .append('tr');

var eleData =
  rows.selectAll('tr')
    .data(function(d,i,j){
      return d;
    });

var ele =
  eleData.enter()
  .append('td')
    .text(function(d,i,j){
      var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes,j[i].parentNode);
      return k;
    });
<script src="https://d3js.org/d3.v4.min.js"></script>

Réservations

Cette approche utilise l'ordre DOM comme proxy pour l'index de données. Dans de nombreux cas, je pense que cette solution est viable si cela n’est plus possible dans D3 (comme indiqué dans cette réponse ).

Des efforts supplémentaires peuvent être nécessaires pour manipuler la sélection DOM afin de faire correspondre les données. Par exemple, filtrez j[i].parentNode.parentNode.childNodes pour les éléments <tr> uniquement afin de déterminer la ligne. De manière générale, le tableau childNodes peut ne pas correspondre à la sélection et peut contenir des éléments/junk supplémentaires. 

Bien que ce ne soit pas une panacée, je pense que cela devrait fonctionner ou pourrait fonctionner dans la plupart des cas, en supposant qu'il existe un lien logique entre DOM et les données pouvant être exploitées, ce qui vous permet d'utiliser l'index enfant DOM comme proxy pour index de données.

2
Steve Ladavich

Voici un extrait que je me suis fait après avoir rappelé l’utilisation de .each pour l’imbrication, j’ai pensé que cela pourrait être utile à d’autres qui se retrouvent ici. Cet exemple crée deux couches de cercles et l'index du groupe parent sert à déterminer la couleur des cercles: blanc pour les cercles du premier calque et noir pour les cercles du calque supérieur (dans ce cas, il n'y a que deux calques).

const nested = nest().key(layerValue).entries(data);

let layerGroups = g.selectAll('g.layer').data(nested);
layerGroups = layerGroups.enter().append('g').attr('class', 'layer')
  .merge(layerGroups);

layerGroups.each(function(layerEntry, j) {   
  const circles = select(this)
    .selectAll('circle').data(layerEntry.values);
  circles.enter().append('circle')
    .merge(circles)
      .attr('cx', d => xScale(xValue(d)))
      .attr('cy', d => yScale(yValue(d)))
      .attr('r', d => radiusScale(radiusValue(d)))
      .attr('fill', j === 0 ? 'white' : 'black'); // <---- Access parent index.
});
0
curran