我是d3.js的新手。我想通过两种方法来绘制对象 - SVG和Canvas。 我的用例大约是<100个节点和边缘。我已经尝试过几个使用canvas的例子,它看起来很棒。
我看到the difference between SVG and Canvas附近有一篇SO帖子。
两者似乎都适合我使用,但是,我倾向于画布(因为我已经很少有例子工作)。如果我在d3.js上下文中遗漏任何内容,请纠正我
答案 0 :(得分:22)
链接问题/答案中列出的差异与svg和canvas(矢量/栅格等)之间的一般差异有关。然而,对于d3,这些差异具有其他含义,特别是考虑到d3的核心部分是数据绑定。
也许d3最核心的功能是数据绑定。迈克博斯托克表示,一旦他将数据加入到元素中,他就需要创建d3:
决定性的时刻是我为第一个数据连接工作的时候 时间。这很神奇。我甚至不确定我是否理解它是如何工作的,但是 这是一个爆炸使用。我意识到可以有一个实用的工具 不必要地限制类型的可视化 你可以做出的可视化。 link
使用SVG,数据绑定很简单 - 我们可以为单个svg元素分配一个数据,然后使用该数据设置其属性/更新它/ etc。这是基于svg的状态 - 我们可以重新选择一个圆圈并修改它或访问它的属性。
使用Canvas,canvas是无状态的,因此我们无法将数据绑定到画布中的形状,因为画布只包含像素。因此,我们无法在画布中选择和更新元素,因为画布没有任何要选择的元素。
基于以上所述,我们可以看到在惯用D3中svg需要输入/更新/退出周期(或基本附加语句):我们需要输入元素来查看它们,我们经常根据它们的基准来设置它们的样式。使用画布,我们不需要输入任何内容,与退出/更新相同。没有要附加的元素,因此我们可以在没有enter / update / exit或d3 svg可视化中使用的append / insert方法的情况下绘制可视化,如果我们想要,则。
没有数据绑定的画布
我将在您的上一个问题here中使用示例bl.ock。因为我们根本不需要附加元素(或向它们附加数据),所以我们使用forEach循环来绘制每个特征(这与使用SVG的惯用D3相反)。由于没有要更新的元素,我们必须重新绘制每个特征每个刻度 - 重绘整个帧(注意每个刻度清除画布)。关于拖拽,d3.drag和d3.force具有一些预期与canvas一起使用的功能,并且可以允许我们通过拖动事件直接修改数据数组 - 绕过DOM中节点元素的任何需要以直接与鼠标交互(d3 .force还直接修改数据数组 - 但它也在svg example中执行此操作。
没有数据绑定,我们直接根据数据绘制元素:
data.forEach(function(d) {
// drawing instructions:
context.beginPath()....
})
如果数据发生变化,我们可能会重新绘制数据。
带数据绑定的画布
也就是说,您可以使用canvas实现数据绑定,但它需要使用虚拟元素的不同方法。我们经历了常规更新/退出/进入循环,但由于我们使用的是虚拟元素,因此不会渲染任何内容。我们随时重新渲染画布(如果我们使用过渡,它可能会连续渲染),并根据虚拟元素绘制内容。
要制作一个虚拟的父容器,我们可以使用:
// container for dummy elements:
var faux = d3.select(document.createElement("custom"));
然后我们可以根据需要使用enter / exit / update / append / remove / transition / etc进行选择:
// treat as any other DOM elements:
var bars = faux.selectAll(".bar").data(data).enter()....
但由于这些选择中的元素未被渲染,我们需要指定绘制它们的方式和时间。没有数据绑定和Canvas我们直接根据数据绘制元素,使用数据绑定和Canvas我们基于人造DOM中的选择/元素绘制:
bars.each(function() {
var selection = d3.select(this);
context.beginPath();
context.fillRect(selection.attr("x"), selection.attr("y")...
...
})
这里我们可以在退出/输入/更新等时重绘元素,这可能有一些优点。这也允许通过在转换虚假元素上的属性时连续重绘来进行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++);
}
})
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
&#13;
<强>摘要强>
使用canvas,数据绑定需要一组虚拟元素,但是一旦绑定,您就可以轻松使用转换和更新/进入/退出循环。但是,渲染与更新/进入/退出和转换分离 - 由您决定如何以及何时重绘可视化。此绘图发生在更新/进入/退出和转换方法之外。
使用svg,输入/更新/退出周期和转换可视化中的更新元素,一步连接渲染和数据。
在具有虚拟元素数据绑定的画布中,可视化代表虚假节点。在svg中,可视化是节点。
数据绑定是一个根本区别,惯用D3在SVG中需要它,但是我们可以选择在使用Canvas时是否要使用它。 但是,与下面提到的D3相比,Canvas和SVG之间存在其他差异:
使用Canvas的最大问题可能是它是无状态的,只是像素而不是元素的集合。这使得与特定渲染形状交互时鼠标事件变得困难。虽然鼠标可以与Canvas交互,但触发标准事件以与特定像素进行交互。
因此,使用SVG,我们可以为力布局中的每个节点分配一个点击监听器(例如),使用Canvas,我们为整个画布设置一个点击监听器,然后根据位置确定应该考虑哪个节点&#34;点击的&#34;
上面提到的D3-force画布example使用了力布局的.find
方法,并使用它来查找最接近鼠标点击的节点,然后将拖动主题设置为该节点。
有几种方法可以确定与之交互的渲染形状:
可见画布中的每个形状都在不可见的画布上绘制,但在不可见的画布上,它具有独特的颜色。在可见画布上获取鼠标事件的xy,我们可以使用它来在不可见的画布上获得相同xy的像素颜色。由于颜色是HTML中的数字,我们可以将该颜色转换为基准索引。
热图/网格化数据(example)
使用未渲染的Voronoi图表.find
方法查找最近的事件节点(对于点,圆圈)
.find
方法查找最近的事件节点(对于点,圆圈,主要是在力布局的上下文中)第一种可能是最常见的,当然也是最灵活的,但根据具体情况,其他可能更为可取。
我会很快触及表现。在questin的链接帖子&#34; What's the difference between SVG and Canvas&#34;它的答案可能不够大胆,但一般来说,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-过渡强>
此模块转换元素的属性,因此只有在我们使用与虚假元素的数据绑定时,它才会与Canvas一起使用。
<强> D3轴强>
这个模块是严格意义上的SVG,除非愿意做大量的工作以将其制作成Canvas使用。使用SVG时,此模块非常有用,特别是在转换轴时。
<强> D3-路径强>
这将获取Canvas路径命令并将它们转换为SVG路径数据。用于采用画布代码到SVG情况。主要在D3内部使用,以生成SVG路径数据。