Javascript 更模块化的D3.js编码
考虑一下代码片段Javascript 更模块化的D3.js编码,javascript,performance,d3.js,svg,Javascript,Performance,D3.js,Svg,考虑一下代码片段 let circles = svg.selectAll("circle") .data(data) .attr("cx", d => d.x) .attr("cy", d => d.y) .attr("r", 2); 三行attr-cx、attr-cy和attr-r使用以下伪代码在内部运行: foreach d in update
let circles = svg.selectAll("circle")
.data(data)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 2);
三行attr-cx
、attr-cy
和attr-r
使用以下伪代码在内部运行:
foreach d in update-selection:
d.cx = (expression)
foreach d in update-selection:
d.cy = (expression)
foreach d in update-selection:
d.r = (constant)
现在假设我们想做不同的事情。我们希望改为运行:
foreach d in update-selection:
d.cx = (expression)
d.cy = (expression)
d.r = (constant)
写
let circles = svg.selectAll("circle")
.data(data)
.myfunction(d => d);
或
我们可能希望这样做,因为:
attr-cx
、attr-cy
和attr-r
的顺序不仅仅是三条语句,而是由几十条或数百条语句组成的序列(这些语句操作属性,以及其他更改),为了可读性和可测试性,我们希望将它们隔离到一个单独的块中attr
语句
更新
这是Mike Bostock的一篇罕见的文章,提出了一种通过将大部分代码分离到单独的模块来组织可视化的方法。您知道其余的:模块化有助于重用,通过针对API编程增强团队合作,支持测试等。其他D3.js示例在很大程度上受到单片编程的影响,而单片编程更适合于可丢弃的一次性可视化。您是否知道模块化D3.js代码的其他努力?D3选择的.call()
方法是否符合您的要求?文件在
我有时使用这种方法来定义一个更“模块化”的感觉更新函数,甚至将不同的函数传递到call()
以根据需要执行不同的操作
在你的例子中,我认为我们可以做到:
function updateFunction(selection){
selection
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 2);
}
let circles = svg.selectAll("circle")
.data(data)
.call(updateFunction);
TL;DR:对于一次设置所有属性的单个函数,更改链接的
attr
方法不会提高性能
我们可以同意,一个典型的D3代码是相当重复的,有时有十几个
attr
方法链接在一起。作为一名D3程序员,我现在已经习惯了,但我知道很多程序员都把它作为他们对D3的主要抱怨
在这个回答中,我不会讨论这是好是坏,丑陋还是美丽,美好还是不愉快。那只是一种意见,毫无价值。在这个回答中,我将只关注性能
首先,我们考虑一些假设的解决方案:
d3 selection multi
只需获取传递的对象并调用selection.attr
多次,就像第一个代码片段一样
但是,如果性能(您的#1)不是问题,您唯一关心的是可读性和可测试性(如您的#2),我会选择d3选择多selection.each
:我相信大多数D3程序员会立即考虑在each
方法中封装链接的attr
。但事实上,这并没有改变什么:
selection.each((d, i, n)=>{
d3.select(n[i])
.attr("foo", foo)
.attr("bar", bar)
//etc...
});
如您所见,链接的attr
仍然存在。更糟糕的是,并不是我们有一个额外的每个
(attr
使用选择。每个内部)
selection.call
或任何其他替代方法,并将相同的链式attr
方法传递给所选内容attr
的属性,我们可以看到,在内部,它使用Element.setAttribute
或Element.setAttributes
。有了这些信息,让我们尝试使用只循环选择一次的方法重新创建伪代码。为此,我们将使用选择。每个,如下所示:
selection.each((d, i, n) => {
n[i].setAttribute("cx", d.x);
n[i].setAttribute("cy", d.y);
n[i].setAttribute("r", 2);
})
最后,让我们测试一下。对于这个基准测试,我编写了一个非常简单的代码,设置了一些圆的cx
、cy
和r
属性。这是默认方法:
const data=d3.range(100.map)(()=>({
x:Math.random()*300,
y:Math.random()*150
}));
const svg=d3.选择(“主体”)
.append(“svg”);
const circles=svg.selectAll(空)
.数据(数据)
.输入()
.附加(“圆圈”)
.attr(“cx”,d=>d.x)
.attr(“cy”,d=>d.y)
.attr(“r”,2)
.样式(“填充”、“青色”)代码>
这是否回答了您的问题@烟熏客不确定。让我想一想仍然缺少的优势。你的第一点一定是真的吗?假设有10个元素,每个元素有2个属性。您建议迭代10个属性更快,然后在每次迭代中迭代2个属性。为什么您确定这比迭代2个属性要快,在每次迭代中迭代10个元素。@SmokeyShakers您对每个数据都是正确的。一旦数据是一个非平凡的或“胖”的数据,那么与数据相比,可能没有那么多数据可以忽略其他可数数据。不过,我们的想法是,我们不仅仅是在更改attr
s,而是在每个fat数据中有一组不同的操作。在这个集合中,我们甚至没有迭代。这只是我们需要做/更新的一系列操作。哈,谢谢,但在灭绝之前我是一只基本的R恐龙。对我来说没有管道和变异。在我的家乡,我们没有提到某些熊和蛇。也许我会把一些东西放在Observable上,我们可以在那里继续讨论。你带来了很多想法。很遗憾,我没有说“出于以下任何原因,我们可能希望这样做:”。如果每个数据本身是一个层次结构,而不是一个rect
或圆
,那么模块化就成为了主要目标,定义一个数据的enter
/update
/exit更明智
selection.each((d, i, n) => {
n[i].setAttribute("cx", d.x);
n[i].setAttribute("cy", d.y);
n[i].setAttribute("r", 2);
})