我是d3.js的新手.我想通过两种方法来绘制对象 - SVG和Canvas. 我的用例大约是<100个节点和边缘.我已经尝试过几个使用canvas的例子,它看起来很棒.
我看到有关于SVG和Canvas之间差异的SO帖子.
两者似乎都适合我使用,但是,我倾向于画布(因为我已经很少有例子工作).如果我在d3.js上下文中遗漏任何内容,请纠正我吗?
And*_*eid 32
链接问题/答案中列出的差异与svg和canvas(矢量/栅格等)之间的一般差异有关.然而,对于d3,这些差异具有额外的含义,特别是考虑到d3的核心部分是数据绑定.
也许d3最核心的功能是数据绑定.Mike Bostock表示,一旦他将数据加入到元素中,他就需要创建d3:
决定性的时刻是我第一次使用数据连接工作.这很神奇.我甚至不确定我是否理解它是如何工作的,但这是一个爆炸使用.我意识到可以有一个实用的可视化工具,它不会不必要地限制你可以制作的可视化类型.链接
使用SVG,数据绑定很容易 - 我们可以为单个svg元素分配一个数据,然后使用该数据设置其属性/更新它/ etc.这是基于svg的状态 - 我们可以重新选择一个圆圈并修改它或访问它的属性.
使用Canvas,canvas是无状态的,因此我们无法将数据绑定到画布中的形状,因为画布仅包含像素.因此,我们无法在画布中选择和更新元素,因为画布没有任何要选择的元素.
基于以上所述,我们可以看到在惯用D3中svg需要输入/更新/退出周期(或基本附加语句):我们需要输入元素来查看它们,我们经常根据它们的基准来设置它们的样式.使用canvas,我们不需要输入任何内容,与退出/更新相同.有没有元素才能看到追加,所以我们可以得出的可视化,而不输入/更新/退出或追加/插入在D3 SVG可视化办法使用的,如果我们想.
没有数据绑定的画布
我将使用的例子bl.ock在你的最后一个问题,在这里.因为我们根本不需要附加元素(或向它们附加数据),所以我们使用forEach循环来绘制每个特征(这与使用SVG的惯用D3相反).由于没有要更新的元素,我们必须重新绘制每个特征每个刻度 - 重绘整个帧(注意每个刻度清除画布).关于拖动,d3.drag和d3.force有一些预期与canvas一起使用的功能,并且可以允许我们通过拖动事件直接修改数据数组 - 绕过DOM中节点元素的任何需要以直接与鼠标交互(d3 .force还直接修改数据数组 - 但它也在svg示例中执行此操作.
没有数据绑定,我们直接根据数据绘制元素:
data.forEach(function(d) {
// drawing instructions:
context.beginPath()....
})
Run Code Online (Sandbox Code Playgroud)
如果数据发生变化,我们可能会重新绘制数据.
带有数据绑定的画布
也就是说,您可以使用canvas实现数据绑定,但它需要使用虚拟元素的不同方法.我们经历了常规更新/退出/进入循环,但由于我们使用的是虚拟元素,因此不会渲染任何内容.我们随时重新渲染画布(如果我们使用过渡,它可能会连续渲染),并根据虚拟元素绘制内容.
要创建一个虚拟父容器,我们可以使用:
// container for dummy elements:
var faux = d3.select(document.createElement("custom"));
Run Code Online (Sandbox Code Playgroud)
然后我们可以根据需要使用enter/exit/update/append/remove/transition/etc进行选择:
// treat as any other DOM elements:
var bars = faux.selectAll(".bar").data(data).enter()....
Run Code Online (Sandbox Code Playgroud)
但由于这些选择中的元素未呈现,我们需要指定绘制它们的方式和时间.在没有数据绑定和Canvas的情况下,我们直接根据数据绘制了元素,使用数据绑定和Canvas,我们根据虚拟DOM中的选择/元素绘制:
bars.each(function() {
var selection = d3.select(this);
context.beginPath();
context.fillRect(selection.attr("x"), selection.attr("y")...
...
})
Run Code Online (Sandbox Code Playgroud)
这里我们可以在退出/输入/更新等时重绘元素,这可能有一些优点.这也允许通过在转换虚假元素上的属性时连续重绘来进行D3转换.
下面的示例具有完整的进入/退出/更新周期,包含过渡,演示带有数据绑定的画布:
var canvas = d3.select("body")
.append("canvas")
.attr("width", 600)
.attr("height", 200);
var context = canvas.node().getContext("2d");
var data = [1,2,3,4,5];
// container for dummy elements:
var faux = d3.select(document.createElement("custom"));
// normal update exit selection with dummy elements:
function update() {
// modify data:
manipulateData();
var selection = faux.selectAll("circle")
.data(data, function(d) { return d;});
var exiting = selection.exit().size();
var exit = selection.exit()
.transition()
.attr("r",0)
.attr("cy", 70)
.attr("fill","white")
.duration(1200)
.remove();
var enter = selection.enter()
.append("circle")
.attr("cx", function(d,i) {
return (i + exiting) * 20 + 20;
})
.attr("cy", 50)
.attr("r", 0)
.attr("fill",function(d) { return ["orange","steelblue","crimson","violet","yellow"][d%5]; });
enter.transition()
.attr("r", 8)
.attr("cx", function(d,i) {
return i * 20 + 20;
})
.duration(1200);
selection
.transition()
.attr("cx", function(d,i) {
return i * 20 + 20;
})
.duration(1200);
}
// update every 1.3 seconds
setInterval(update,1300);
// rendering function, called repeatedly:
function render() {
context.clearRect(0, 0, 600, 200);
faux.selectAll("circle").each(function() {
var sel = d3.select(this);
context.beginPath();
context.arc(sel.attr("cx"),sel.attr("cy"),sel.attr("r"),0,2*Math.PI);
context.fillStyle = sel.attr("fill");
context.fill();
context.stroke();
})
window.requestAnimationFrame(render)
}
window.requestAnimationFrame(render)
// to manipulate data:
var index = 6; // to keep track of elements.
function manipulateData() {
data.forEach(function(d,i) {
var r = Math.random();
if (r < 0.5 && data.length > 1) {
data.splice(i,1);
}
else {
data.push(index++);
}
})
}Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>Run Code Online (Sandbox Code Playgroud)
阻止版本.
摘要
对于canvas,数据绑定需要一组虚拟元素,但是一旦绑定,您就可以轻松使用转换和更新/进入/退出循环.但是,渲染与更新/进入/退出和转换分离 - 由您决定如何以及何时重绘可视化.此绘图发生在更新/进入/退出和转换方法之外.
使用svg,enter/update/exit循环和转换可视化中的更新元素,一步连接渲染和数据.
在具有人造元素数据绑定的画布中,可视化表示虚拟节点.在svg中,可视化是节点.
数据绑定是一个根本区别,惯用D3在SVG中需要它,但是我们可以选择在使用Canvas时是否要使用它.但是,与下面提到的D3相比,Canvas和SVG之间存在其他差异:
也许使用Canvas最重要的问题是它是无状态的,只是一组像素而不是元素.这使得与特定渲染形状交互时鼠标事件变得困难.虽然鼠标可以与Canvas交互,但触发标准事件以与特定像素进行交互.
因此,使用SVG时,我们可以为强制布局中的每个节点分配一个单击侦听器(例如),使用Canvas,我们为整个画布设置一个单击侦听器,然后根据位置确定应该将哪个节点视为"单击" .
上面提到的D3-force画布示例使用强制布局的.find方法,并使用它来查找最接近鼠标单击的节点,然后将拖动主题设置为该节点.
我们可以通过几种方法确定与之交互的渲染形状:
可见画布中的每个形状都绘制在不可见的画布上,但在不可见的画布上,它具有独特的颜色.在可见画布上获取鼠标事件的xy,我们可以使用它来在不可见的画布上获得相同xy的像素颜色.由于颜色是HTML中的数字,我们可以将该颜色转换为数据的索引.
热图/网格化数据的反转比例(缩放xy位置到未缩放的输入值)(示例)
使用未渲染的Voronoi图的.find方法来查找最接近事件的节点(对于点,圆)
.find方法查找最接近事件的节点(对于点,圆,主要在力布局的上下文中)第一种可能是最常见的,当然也是最灵活的,但根据具体情况,其他可能是最优选的.
我会很快触及表现.在questin的链接帖子" SVG和Canvas之间有什么区别 "中,它的答案可能不够大胆,但一般来说canvas和svg在处理数千个节点时的渲染时间不同,特别是在呈现数千个节点时动画.
随着更多节点的渲染以及节点执行更多操作(转换,移动等),Canvas的性能越来越高.
这是Canvas(在虚拟节点上具有数据绑定)和SVG以及19 200个同时转换的快速比较:
画布应该是两者中更平滑的.
最后,我将介绍D3的模块.其中大多数根本不与DOM交互,可以很容易地用于SVG或Canvas.例如,d3-quadtree或d3-time-format不是SVG或Canvas特有的,因为它们根本不处理DOM或渲染.诸如d3-hierarchy之类的模块实际上也不渲染任何东西,而是提供在Canvas或SVG中渲染所需的信息.
提供SVG路径数据的大多数模块和方法也可用于生成画布路径方法调用,因此可以相对容易地用于SVG和Canvas.
我在这里特别提到几个模块:
D3选择
显然这个模块需要选择,选择需要元素.因此,要将它与Canvas一起用于输入/更新/退出循环或选择.append/remove/lower/raise之类的东西,我们想要使用Canvas的虚假元素.
使用Canvas,分配的事件监听器selection.on()可以使用或不使用数据绑定,上面提到了鼠标交互的挑战.
D3过渡
此模块转换元素的属性,因此只有在我们使用与faux元素的数据绑定时,它才会与Canvas一起使用.
D3轴
这个模块是严格意义上的SVG,除非愿意做大量的工作将其制作成Canvas用途.使用SVG时,此模块非常有用,特别是在转换轴时.
D3路径
这将获取Canvas路径命令并将它们转换为SVG路径数据.用于采用画布代码到SVG情况.主要在D3内部使用以生成SVG路径数据.