d3.js

时间:2018-05-02 18:32:40

标签: d3.js svg canvas

我是d3.js的新手。我想通过两种方法来绘制对象 - SVG和Canvas。 我的用例大约是<100个节点和边缘。我已经尝试过几个使用canvas的例子,它看起来很棒。

我看到the difference between SVG and Canvas附近有一篇SO帖子。

两者似乎都适合我使用,但是,我倾向于画布(因为我已经很少有例子工作)。如果我在d3.js上下文中遗漏任何内容,请纠正我

1 个答案:

答案 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转换。

以下示例具有完整的进入/退出/更新周期,包含过渡,演示带有数据绑定的画布:

&#13;
&#13;
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;
&#13;
&#13;

Block version

<强>摘要

使用canvas,数据绑定需要一组虚拟元素,但是一旦绑定,您就可以轻松使用转换和更新/进入/退出循环。但是,渲染与更新/进入/退出和转换分离 - 由您决定如何以及何时重绘可视化。此绘图发生在更新/进入/退出和转换方法之外。

使用svg,输入/更新/退出周期和转换可视化中的更新元素,一步连接渲染和数据。

在具有虚拟元素数据绑定的画布中,可视化代表虚假节点。在svg中,可视化是节点。

数据绑定是一个根本区别,惯用D3在SVG中需要它,但是我们可以选择在使用Canvas时是否要使用它。 但是,与下面提到的D3相比,Canvas和SVG之间存在其他差异:

交互

使用Canvas的最大问题可能是它是无状态的,只是像素而不是元素的集合。这使得与特定渲染形状交互时鼠标事件变得困难。虽然鼠标可以与Canvas交互,但触发标准事件以与特定像素进行交互。

因此,使用SVG,我们可以为力布局中的每个节点分配一个点击监听器(例如),使用Canvas,我们为整个画布设置一个点击监听器,然后根据位置确定应该考虑哪个节点&#34;点击的&#34;

上面提到的D3-force画布example使用了力布局的.find方法,并使用它来查找最接近鼠标点击的节点,然后将拖动主题设置为该节点。

有几种方法可以确定与之交互的渲染形状:

  1. 创建隐藏的画布,为渲染的形状提供参考地图
  2. 可见画布中的每个形状都在不可见的画布上绘制,但在不可见的画布上,它具有独特的颜色。在可见画布上获取鼠标事件的xy,我们可以使用它来在不可见的画布上获得相同xy的像素颜色。由于颜色是HTML中的数字,我们可以将该颜色转换为基准索引。

    1. 热图/网格化数据(example

    2. 反转比例(缩放xy位置到未缩放的输入值)
    3. 使用未渲染的Voronoi图表.find方法查找最近的事件节点(对于点,圆圈)

    4. 使用强制布局的.find方法查找最近的事件节点(对于点,圆圈,主要是在力布局的上下文中)
    5. 使用直接数学,四叉树或其他方法
    6. 第一种可能是最常见的,当然也是最灵活的,但根据具体情况,其他可能更为可取。

      效果

      我会很快触及表现。在questin的链接帖子&#34; What's the difference between SVG and Canvas&#34;它的答案可能不够大胆,但一般来说,canvas和svg在处理数千个节点时的渲染时间不同,特别是在渲染数千个动画节点时。

      随着更多节点的渲染以及节点做更多事情(过渡,移动等),Canvas的性能越来越高。

      这里是Canvas(在人造节点上绑定数据)和SVG以及19 200个同时转换的快速比较:

      画布应该是两者中更顺畅的。

      D3模块

      最后,我将介绍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路径数据。