【问题标题】:d3.js v4: How to access parent group's datum index?d3.js v4:如何访问父组的基准索引?
【发布时间】:2016-11-09 00:52:03
【问题描述】:

selection.data 函数的描述包括一个包含多个组 (link) 的示例,其中将二维数组转换为 HTML 表格。

在 d3.js v3 中,对于较低维度,访问器函数包含第三个参数,即父组数据的索引:

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

在 v4 中,此 j 参数已被选择的 NodeList 替换。我现在如何访问父组的基准索引?

【问题讨论】:

    标签: d3.js


    【解决方案1】:

    嗯,有时答案并不能提供解决方案,因为解决方案可能不存在。好像是这样的。

    根据博斯托克:

    我已将新的双级选择实现合并到 master 中,并通过使用并行父数组简化了跟踪父母的方式。

    这种新方法的一个很好的特性是 selection.data 可以 以与其他函数完全相同的方式评估值函数 选择函数:values 函数被传递 {d, i, nodes} 这是父节点,d 是父基准,i 是父节点 (group) 索引,nodes 是父节点的数组(每组一个)。 此外,父母数组可以被不支持的子选择重用 重新组合选择,例如 selection.select,因为父母 数组是不可变的。

    此更改限制了功能——从某种意义上说,您不能 从选择函数中访问父节点,也不是 父数据,也不是组索引——但我相信这最终是 A 好事,因为它鼓励更简单的代码。

    (强调我的)

    这是链接:https://github.com/d3/d3-selection/issues/47

    因此,无法使用selection 获取父组的索引(可以使用selection.data 检索父组的索引,如下面的 sn-p 所示)。

    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>

    【讨论】:

    • 我没有看到这个问题,所以感谢您指出。使用.each() 来规避这个限制会使我的代码变得非常丑陋,但我想在这一点上,别无他法。
    • 但是你如何索引到.append("rect").on("click", someFunc)中的'someFunc'
    【解决方案2】:

    我的解决方法有点类似于Dinesh Rajan 的,假设g.nestedElt 的属性someAttr 需要父索引:

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

    【讨论】:

    • 使用箭头操作符时是否有任何形式可以访问父级?即,.attr("someAttr", (d, i) => ???)
    【解决方案3】:

    我最终定义了一个外部变量“j”,然后每当“i”为 0 时递增它

    下面的示例 V3 sn-p。

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

    V4中,代码转换如下。

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

    【讨论】:

    • 我认为这仅在将新行附加到表末尾时才有效。如果在中间插入一行,它将收到错误的“j”索引。 (此代码还假定 d3.js 处理节点的特定顺序。由于此顺序未在任何地方记录,因此我会犹豫是否使用它。)
    • 同意......这或多或少是一种“hacky”方式......如果需要快速而肮脏的转换,猜测会做
    【解决方案4】:

    如果j 是节点列表...

    • j[i]是当前节点(例如td元素)
    • j[i].parentNodelevel-1 父级 (例如行元素)
    • j[i].parentNode.parentNode2 级父级 (例如表格元素)

    • j[i].parentNode.parentNode.childNodes一级父级数组 (例如行元素数组)包括原始父级。

      李>

    所以问题是,父级(行)相对于其父级(表)的索引是多少?

    我们可以像这样使用Array.prototype.indexOf 找到它...

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

    您可以在下面的 sn-p 中看到,当返回 k 时,该行会打印在每个 td 单元格中。

    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>

    预订

    这种方法使用 DOM 顺序作为数据索引的代理。在许多情况下,如果在 D3 中不再可能,我认为这是一个可行的创可贴解决方案(如 this answer 中所报告的那样)。

    可能需要在操作 DOM 选择以匹配数据方面付出一些额外的努力。例如,过滤j[i].parentNode.parentNode.childNodes 中的<tr> 元素只是为了确定行——一般来说,childNodes 数组可能与选择不匹配,并且可能包含额外的元素/垃圾。

    虽然这不是万能药,但我认为在大多数情况下它应该可以工作或可以工作,假设 DOM 和可以利用的数据之间存在某种逻辑联系,这允许您将 DOM 子索引用作数据索引的代理。

    【讨论】:

    • 这只是给出了 DOM 节点相对于其父 DOM 节点的位置。在某些情况下(例如您的示例),它可能是正确的值,但通常情况下,这将是错误的。我需要的是绑定到 DOM 的数据数组的索引——事实上,这就是我首先使用 d3.js 的原因。一旦我的数据数组发生变化或者父节点包含其他子节点,你的方法就会失败。
    • 优秀的分数。我认为在许多情况下,这可以作为一种解决方案,但我同意你的观点,它不是一个固有的解决方案,因为它依赖于使用 DOM 索引/顺序作为数据索引的代理。在阅读了@gerardo 的回答后,我写了这个答案作为一种可能的解决方法,以前的功能可能不再存在了。
    【解决方案5】:

    这是一个如何使用selection.each() 方法的示例。我不认为这很混乱,但它确实减慢了大型矩阵的渲染速度。请注意,以下代码假定现有 table 选择和对 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);
        },
    

    【讨论】:

    • 我喜欢这样,因为它不需要您将数据元素包装到对象中以保存对其父级的引用,而是使用闭包来访问索引。速度损失可能是由于在包含许多行的表上发出大量 selectAll/exit/enter 命令,但这只是猜测。
    【解决方案6】:

    假设您要进行嵌套选择,并且您的 数据是一些数组,其中每个元素依次 包含一个数组,比方说“值”。然后你 可能有一些这样的代码:

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

    您可以将数组替换为新数组的值,其中 您缓存组内的索引。

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

    假设您的外部数组如下所示: [{名称“X”,值:[“A”,“B”]},{名称“y”,值:[“C”,“D”]}
    使用第一种方法,嵌套选择会将您带到这里

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

    到这里。

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

    使用扩充数组,您最终会出现在此处:

                 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
    

    所以你在嵌套选​​择中,在任何函数(d,i)中,所有 您需要的信息。

    【讨论】:

    • 这实际上是我现在在大部分代码中所做的。我本来希望 d3.js 提供一种访问索引的方法(就像以前一样),而不必将每个值包装到一个额外的对象中。但既然现在已经不存在了,我想我们将不得不采用这个解决方案(@david004's answer 可能是一个不错的选择)。
    【解决方案7】:

    在重新记住 .each 用于嵌套的用法后,这是一个 sn-p I crafter,我认为它可能对最终来到这里的其他人有用。这个例子创建了两层圆圈,父组索引用于确定圆圈的颜色——第一层的圆圈为白色,顶层的圆圈为黑色(本例中只有两层)。

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

    【讨论】:

      【解决方案8】:

      我的解决方案是将此信息嵌入到提供给 d3js 的数据中

      data = [[1,2,3],[4,5,6],[7,8,9]]
      flattened_data = data.reduce((acc, v, i) => {
          v.forEach((d, j) => {
            data_item = { i, j, d };
            acc.push(data_item);
          });
          return acc;
        }, []);
      
      

      然后就可以从函数的data arg中访问i、j、d了

      td.text(function(d) {
        // Can access i, j and original data here
        return "Row: " + d.j;
      });
      

      【讨论】:

        猜你喜欢
        • 2012-07-05
        • 2017-12-14
        • 2013-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-13
        • 2016-12-27
        • 1970-01-01
        相关资源
        最近更新 更多