无法合并选择

时间:2019-03-27 23:55:17

标签: javascript d3.js

首先,我是D3的新手,但是搜索没有找到适合我的情况的结果。

我正在使用最新的D3 v5。

我正在尝试让d3用单列填充表格。我能够以违反DRY的方式完成此任务:

var TRS = d3.select("#queueTable tbody")
    .selectAll("tr")
    .data(data);

TRS.exit().remove();

TRS.enter()
    .append("tr")
    .selectAll("td")
    .data(function(row){return [row];})
    .enter()
    .append("td")
    .text(function fillTD(d){return d.name;});

TRS
    .selectAll("td")
    .data(function(row){return [row];})
    .text(function fillTD(d){
        return d.name;
    });

如您所见,我正在复制粘贴函数“ fillTD”。

谷歌搜索使我进入了合并功能,然后陷入了使它正常工作的困境:

var TRS = d3.select("#queueTable tbody")
    .selectAll("tr")
    .data(data);

TRS.exit().remove();

var TDS = TRS.selectAll("td")
.data(function(row){return [row];});

var RowsEntered = TRS.enter()
    .append("tr")

var TDsEntered = RowsEntered.selectAll("td")
    .data(function(row){return [row];})
    .enter()
    .append("td");


var Merged = TDS.merge(TDsEntered);

Merged
    .text(function fillTD(d){return d.name;});

这样,就可以很好地生成TR和TD。调用合并后,功能之争就完全结束了。

作为合并参数传递的变量的值永远不会显示。也就是说,当我有TDsEntered.merge(TDS)时,仅通过TDS的选择,就好像合并从未发生过,并且仅创建了新项目。尽管现在仅发生更新,但TDS.merge(TDsEntered)仍然存在此行为。

如果我从此处删除.data行:

var TDS = TRS.selectAll("td")
.data(function(row){return [row];});

它的工作原理相同。

如果我将RowsEntered换成TDsEntered:

var RowsEntered = TRS.enter()
    .append("tr")

var TDsEntered = TRS.selectAll("td")
    .data(function(row){return [row];})
    .enter()
    .append("td");

我得到相同的行为,但是仅在第二次通过触发该事件的fromEvent之后。有道理。

在调试器中检查时,TDS和TDsEntered具有可变数量的“组”和“父母”,具体取决于是否正在使用它们。例如,TDsEntered将具有4个组和对应于4个新条目的父级。如果对任何现有条目都没有更改,则TDS将依次具有0个组和父级。

如果我尝试合并TRS和RowsEntered,则不是这种情况。它们的父母和组数相等,合并后的结果具有相同的值。

所以,我认为我可以使用TRS和RowsEntered:

var Merged = TDS.merge(TDsEntered);
var Merge2 = TRS.merge(RowsEntered);
Merge2
    .selectAll('td')
    .text(function fillTD(d){return d.name;});

但是现在只有新条目起作用。我翻转了TRS和RowsEntered的位置,但没有变化:仍然只有新条目。

好的。

因此我通过复制制作TDsEntered时使用的相同数据选择器来再次违反DRY:

var TRS = d3.select("#queueTable tbody")
    .selectAll("tr")
    .data(data);

TRS.exit().remove();

var TDS = TRS.selectAll("td");

var RowsEntered = TRS.enter()
    .append("tr")

var TDsEntered = RowsEntered.selectAll("td")
    .data(function(row){return [row];})
    .enter()
    .append("td");


var Merged = TDS.merge(TDsEntered);
var Merge2 = TRS.merge(RowsEntered);
Merge2
    .selectAll('td')
    .data(function(row){return [row];})
    .text(function fillTD(d){return d.name;});

这是可行的。

但是我又一次违反了DRY,

因此,我尝试使用该公式制作“ TDSReselected”以防止DRY:


var TDSReselected = RowsEntered.selectAll("td")
.data(function(row){return [row];});

var TDsEntered = TDSReselected
    .enter()
    .append("td");


var Merge1 = TDS.merge(TDsEntered);
var Merge2 = TRS.merge(RowsEntered);
var Merge3 = TDsEntered.merge(TDSReselected);
Merge3
    .text(function fillTD(d){return d.name;});

但是不,这是行不通的。回到同一个问题。

有什么作用?

有没有办法解决我的data()逻辑不放在两个地方的问题?我在做什么错了?

编辑GERARDO的解释后:

现在可以使用。 Gerardo揭示的关键是首先需要合并,然后在合并的行的基础上构建TD的update&enter:

var TRS = d3.select("#queueTable tbody")
    .selectAll("tr")
    .data(data);

TRS.exit().remove();

var RowsEntered = TRS.enter()
    .append("tr")

var RowsMerged = TRS.merge(RowsEntered);

var TDS = RowsMerged.selectAll("td")
        .data(function(row){return [row];});

var TDsEntered = TDS
    .enter()
    .append("td");

var TDMerged = TDS.merge(TDsEntered);
TDMerged
    .text(function fillTD(d){return d.name;});

这使一切正常,我们不再重复任何逻辑!

注意:我不退出TD,因为在我的特定情况下(静态定义的列计数表的TD计数将保持静态),我不需要这样做。 如果我的列数是动态的,则需要退出TD。

1 个答案:

答案 0 :(得分:1)

首先,根据DRY原理的创造者,DRY与避免重复代码并不完全相同:

  

“大多数人都采用DRY表示您不应重复编写代码。那不是它的意图。 DRY背后的想法远不止于此”(source

在D3中,您会发现自己一次又一次地重复一些片段。只要您知道自己在做什么,这本身就不是问题。

回到您的问题:

如果您清楚地知道每个选择正在做什么,则可以简化代码并避免造成混乱。比只说每个选择更好,我们应该说每个更新模式

在您的情况下,您应该创建,更新和删除两个元素:<tr><td>

因此,您可以为tr编写一个非常简洁的模式:

let tr = table.selectAll("tr")
  .data(data);

const trExit = tr.exit().remove();

const trEnter = tr.enter()
  .append("tr");

tr = trEnter.merge(tr);

还有一个td

let td = tr.selectAll("td")
  .data(function(d) {
    return [d]
  });

const tdExit = td.exit().remove();

const tdEnter = td.enter()
  .append("td");

td = tdEnter.merge(td);

td.text(function(d) {
  return d.name
});

由于在每个<td>(单列)中仅附加了一个<tr>,因此可以大大简化最后一个模式。但是,让我们暂时保持这种状态。

此外,别忘了为选择提供有意义的名称:

const body = d3.select("body");
const table = body.append("table");

这是一个演示:

const body = d3.select("body");

const table = body.append("table");

const data1 = [{
  name: "foo"
}, {
  name: "bar"
}, {
  name: "baz"
}];

const data2 = [{
  name: "foobar"
}, {
  name: "barbaz"
}, {
  name: "bazfoo"
}, {
  name: "foobaz"
}];

d3.selectAll("button").on("click", function(_, i) {
  createTable(i ? data1 : data2)
})

function createTable(data) {
  let tr = table.selectAll("tr")
    .data(data);

  const trExit = tr.exit().remove();

  const trEnter = tr.enter()
    .append("tr");

  tr = trEnter.merge(tr);

  let td = tr.selectAll("td")
    .data(function(d) {
      return [d]
    });

  const tdExit = td.exit().remove();

  const tdEnter = td.enter()
    .append("td");

  td = tdEnter.merge(td);

  td.text(function(d) {
    return d.name
  });

};
table {
  font-family: verdana, arial, sans-serif;
  font-size: 11px;
  color: #333333;
  border-width: 1px;
  border-color: #666666;
  border-collapse: collapse;
}

th {
  border-width: 1px;
  padding: 8px;
  border-style: solid;
  border-color: #666666;
  background-color: #dedede;
}

td {
  border-width: 1px;
  padding: 8px;
  border-style: solid;
  border-color: #666666;
  background-color: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Data 1</button><button>Data 2</button>

关于您的主要问题:“我在做什么错了?” 很多内容,取决于代码段。例如,有时您要基于未合并的外部选择合并回车选择。综合答案可能太长了。看看我的代码片段,看看我们为外部级别创建了更新模式,然后为内部级别创建了更新模式,依此类推...