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