D3 (Data-Driven Documents)的学习和使用

d3.js是一个强大的JS绘图库。
本文提到的API都是来自 d3 4.0 (d3 4.0 和 d3 3.x 的接口在形式上有很大差别)。

d3的思路和关键概念

d3的全称:Data-Driven Documents 数据驱动的文档。
这名字看不出来和绘图有啥关联,因为实际上d3也确实并只为绘图设计,虽然它确实有很强大的绘图功能。

d3的目的就是要在数据和html文档中的元素之间建立一种关联。用户给出数据,并设置好需要创建的元素类型以及元素属性和数据的关系,d3就会根据数据产生元素并添加到文档中或者从文档中删除和数据没有关联的元素。

从这个思路看,d3提供的API应该是围绕数据来定义的,但是,实际上正好相反,d3的核心API——d3.selection()是针对文档中的元素来定义的,而数据和对数据的操作是作为API的参数。

selection

官方说明: https://bost.ocks.org/mike/selection/
selection的含义还是很明显的,如果你能想起CSS中有个叫selector的东西。selection就是根据某个selector从文档中选出的一个元素集合,这个集合的类型继承自JS中的Array类型,但是和Array也有很大的区别。
selection中元素的组织
selection中的元素并不是放在一个平铺开的array中,而是进行了分组。为什么分组?我个人觉得,分组之后,更容易将数据和元素进行绑定。
selection是可以级联的,例如d3.selectAll("tr").selectAll("td").selectAll("span"),分组的组织方式就是说一个selection中同一组的所有元素来都是从上一级selection中的同一个元素的子元素中select出来的。当然,如果只有一级select,例如 d3.selectAll("tr"), 那就只有一个组。

将selection和数据关联

在d3中,将selection和数据关联起来叫做 data join (https://bost.ocks.org/mike/join/)。

有两个接口可以用于join data:

  • selection.data([data[, key]])
    data可以是任何形式:一个数组、一个数字、一个函数等等。
    data是按组分配的,而key决定了每组内部每个元素和数据之间的对应关系。
    如果data是一个函数,这个函数的参数是该组的父元素对应的数据(还有一些其他的参数)。

    • 如果不指定key, 那么在一组内部,第n个元素和第n个数据对应(n=0,1,2,...)。
    • 如果指定了参数key, 这个key必须是一个函数。这个函数会用来生成key, 既用于给数据生成key, 也用于给元素生成key;首先遍历元素,针对每个元素调用key, 然后遍历数据,针对每个数据调用key。
      API设计成这样完全莫名其妙(设计成selection.data(data, keyForElem, keyForData)多好),首先不好理解,看了老半天官方的API说明加上自己动手实验才明白key这个函数的意义,而且这样的API导致key这个函数必须包含两种逻辑,两种逻辑对应的输入参数的含义还不一致,这不是逼别人写复杂混乱的代码么;并且,如果这个元素之前已经绑定了数据,key这个函数内部如何区分第三个参数到底是current group还是group's new data呢?(key的第三个参数在两种情况下分别表示current group 和 group's new data,如果一定要区分当前对key的调用是针对元素还是数据,可以查看第三个参数的原型,但是,这么做显然不是一种合理的参数使用方式。所以,我觉得作者可能并不觉得实际使用中需要在元素和数据间建立很复杂的绑定关系))。
  • selection.datum([value])
    给selection中每个元素设置对应的数据,如果value不是函数,则所有元素设置为同样的value。如果value是函数,则调用value计算每个元素对应的数据,value的参数是:being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i])。
    If a value is not specified, returns the bound datum for the first (non-null) element in the selection.

enter 和 exit
当使用 selection.data() 这个接口把元素和数据join起来的时候,实际上将这个selection分成了3部分:

  1. 和某个数据匹配上的元素(被称为update selection)
    可以在selection.data()返回的结果上更新这部分元素,例如:

    var circle = svg.selectAll("circle") 
      .data(data) 
      .style("fill", "blue"); 
    
  2. 没有和任何数据匹配上的元素 (通过selection.exit()获取 )

  3. 多余的数据对应的元素
    其实还不存在,但是这些数据都有对应的placeholder nodes, 可以通过 selection.enter()获取,然后通过append操作添加真正的元素,例如:

    var div = d3.select("body")
    .selectAll("div")
      .data([4, 8, 15])
      .enter().append("div")
      .text(function(d) { return d; });
    
    产生的结果如下:
    <div>4</div>
    <div>8</div>
    <div>15</div>
    

https://bost.ocks.org/mike/join/ 上有个上述3类元素集合的示意图。

selection的API

selection.on()
这个接口用于设置事件的处理函数。
有一点需要特备注意: 传给on的处理函数的签名并非标准的web api中的签名,而是d3自己规定的签名,具体来说: listener will be evaluated for the element, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
显然,这个处理函数的参数中缺少一项重要内容:事件对象。如果需要使用事件对象,需要使用d3.event获取当前的事件对象(如果你只import了selection, 例如 import * as select from 'd3-selection', 则是select.event),另外d3提供了一些快捷的获取鼠标事件发生时鼠标位置的函数,包括 d3.mouse(),d3.touch()等等。

D3的一些特点

数据绑定到DOM

通过selection.data() join data之后,绑定了数据的DOM会增加一个属性:__data__ ,存储和该DOM对应的数据。
充分利用这种数据和元素的绑定,会给图形的绘制带来极大方便。
你接收到的输入数据往往是一个整体,这样一整块数据必定有某种组织方式,而当你绘图的时候,图上的元素的组织方式一般都不会和数据的组织方式完全一致。并且你在绘图过程中(比如计算布局、设置class、id)肯定需要访问这些数据,由于图形的绘制往往是分成几个步骤,后面绘制的图形很可能要利用前面绘制的图形的相关信息,所以你很可能需要多次访问这些信息,如果每次都从原始的数据源去访问,很可能十分麻烦。而d3将数据和元素绑定之后,可以通过元素访问数据,也就是说,数据之间的关联与图形之间的关联协调一致了。
那么,问题来了,这种一致性并不是凭空而来的,是需要我们去处理原始数据并通过合适的绑定方法才能得到的。当然,d3提供了一些辅助处理数据的API,比如d3.hierarchy 。

设置数据访问函数

大量的设置API的形式是 someApi(dataAccessor), 不提供dataAccessor则返回当前的设置。例如:

selection.attr(name[, value])  
transition.duration([value])

这里的[value]可以是一个函数,并且该函数的第一个参数都是当前元素对应的数据。
当元素都绑定了数据,这种API形式就很自然很方便。

链式调用

d3中的对象的API一般的返回值都是这个对象本身,这样就可以一种链式调用的形式,obj.Method1().Method2()...。
这对于设置元素属性是很方便的,因为元素需要设置的属性往往不止一个,这种链式调用比较简洁。

d3.select("body")
  .append("svg")
    .attr("width", 960)
    .attr("height", 500)
  .append("g")
    .attr("transform", "translate(20,20)")
  .append("rect")
    .attr("width", 920)
    .attr("height", 460);

svg vs canvas

使用d3绘图基本图形的一大优势就是: d3 针对 svg 和 canvas提供了同样的接口。
例如你使用 myLine = d3.line()创建了一个 line generator, 如果你设置了context,则myLine(data)会自动使用context进行绘图,如果你没有设置context,则该myLine返回一个描述path的字符串,可以用于设置为svg中<path>元素的d属性。

d3.Shape()

Curves

Cureves一般不会直接被构建或者调用,而是作为line.curve and area.curve的参数,指定curve的类型。例如:

var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); })
.curve(d3.curveBasis);

转载于:https://www.cnblogs.com/a-distant-bright-star/p/6762492.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值