d3.js v4: ¿Cómo acceder al índice de referencia del grupo padre?

La descripción de la función selection.data incluye un ejemplo con múltiples grupos ( enlace ) donde una matriz bidimensional se convierte en una tabla HTML.

En d3.js v3, para dimensiones inferiores, las funciones de acceso incluían un tercer argumento que era el índice del dato del grupo padre:

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

En v4, este argumento j ha sido reemplazado por NodeList de la selección. ¿Cómo accedo ahora al índice de referencia del grupo padre?

Bueno, a veces una respuesta no proporciona una solución , porque la solución puede no existir. Este parece ser el caso.

De acuerdo con Bostock:

He fusionado la nueva implementación de selección binivel en master y también simplifiqué cómo se hace un seguimiento de los padres mediante el uso de una matriz de padres paralelos.

Una buena propiedad de este nuevo enfoque es que selection.data puede evaluar la función de valores exactamente de la misma manera que otras funciones de selección: se pasa la función de valores {d, i, nodes} donde este es el nodo padre, d es el padre datum, i es el índice padre (grupo) y nodes es la matriz de nodos padres (uno por grupo). Además, la matriz de padres se puede reutilizar mediante subsecciones que no reagrupan la selección, como selection.select, ya que la matriz de parents es inmutable.

Este cambio restringe la funcionalidad, en el sentido de que no se puede acceder al nodo principal desde una función de selección, ni a los datos principales, ni al índice grupal , pero creo que esto es en última instancia, una buena cosa porque fomenta un código más simple.

(énfasis mío)

Aquí está el enlace: https://github.com/d3/d3-selection/issues/47

Por lo tanto, no es posible obtener el índice del grupo principal utilizando la selection (el índice del grupo principal se puede recuperar utilizando selection.data , como se muestra en este fragmento).

 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"); 
  

Mi solución es algo similar a la de Dinesh Rajan , suponiendo que se necesita el índice principal para el atributo someAttr de 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"); }); 

Terminé definiendo una variable externa “j” y luego la incremento cada vez que “i” es 0

ejemplo de fragmento de V3 a continuación.

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

y en V4 , código convertido como abajo.

 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); }) 

Si j es el nodeList …

  • j[i] es el nodo actual (por ejemplo, el elemento td) ,
  • j[i].parentNode es el padre de nivel 1 (por ejemplo, el elemento de fila) ,
  • j[i].parentNode.parentNode es el padre de nivel 2 (por ejemplo, el elemento de tabla) ,

  • j[i].parentNode.parentNode.childNodes es la matriz de padres de nivel 1 (por ejemplo, una matriz de elementos de fila) que incluye el padre original.

Entonces la pregunta es, ¿cuál es el índice del padre (la fila) con respecto a su padre (la tabla)?

Podemos encontrar esto usando Array.prototype.indexOf así …

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

Puede ver en el siguiente fragmento que la fila se imprime en cada celda td cuando se devuelve k .

 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; }); 
  

Aquí hay un ejemplo de cómo usar el método selection.each() . No creo que sea desordenado, pero ralentizó el render en una matriz grande. Tenga en cuenta que el siguiente código supone una selección de table existente y una llamada a 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); }, 

Supongamos que quiere hacer una selectiom anidada, y sus datos son una matriz donde cada elemento a su vez contiene una matriz, digamos “valores”. Entonces probablemente tengas un código como este:

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

Puede reemplazar la matriz con los valores por una nueva matriz, donde almacena en caché los índices dentro del grupo.

 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; }) // ... 

Suponga que su matriz externa se ve así: [{nombre “X”, valores: [“A”, “B”]}, {nombre “y”, valores: [“C”, “D”]}
Con el primer enfoque, la selección anidada te trae de aquí

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

hacia aqui.

  di ------------------------------------------------------------------ root XA "A" 0 B "B" 1 YC "C" 2 D "D" 3 

Con la matriz aumentada, terminas aquí:

  di ------------------------------------------------------------------ root XA {datum: "A", outerIndex: 0, innerIndex: 0} 0 B {datum: "B", outerIndex: 0, innerIndex: 1} 1 YC {datum: "C", outerIndex: 1, innerIndex: 0} 2 D {datum: "D", outerIndex: 1, innerIndex: 1} 3 

De modo que tiene dentro de las selecciones anidadas, en cualquier función (d, i), toda la información que necesita.