相对于echart, highchart等其余图表库算是一个比较底层的可视化工具,简单来说他不提供任何一种现成的图表,全部的图表都是咱们在它的库里挑选合适的方法构建而成。前端
基于上面的理解,d3无疑会复杂不少可是也强大自由的多,另外由于d3基于svg因此修改图表的样式和结构也会方便不少,可是一样是这个缘由,d3的性能比canvas类库差了很多,dom毕竟是拖累浏览器性能的罪魁祸首。顺口提一句,d3也是能够基于canvas构建图表的。可是这篇文章就不提了。node
对于d3咱们能够简单的将其分个类:数据处理, dom处理,事件以及其余。 其实dom和事件其实能够合到一块儿。react
前端作可视化的时候确定须要对数据进行处理,d3提供了一下经常使用的方法。ajax
由于d3是基于svg因此跟dom打交道确定是必须的,这里必定程度替代了jQuery之类的功能。算法
事件的话其实就是一些交互好比滚轮,拖拽等等都是基础功能能够进行一系列组合排序canvas
请求就是ajax请求数据源了。后端
数据处理就很简单了,就是对于数组和集合以及时间的一些处理方法, 好比数组求中位数方差等等,和lodash的一些方法有重合,可是仍是偏向数学方面,方法有点多这里不一一列出了:api
// array的方法
d3.min([1, 2, 3, 4]) // 1 不一样于Math,min忽略NaN undefined等
d3.range(1, 10) // [1, 2 ... 10]
// collection的方法
d3.entries({foo: 42, bar: true}); // [{key: "foo", value: 42}, {key: "bar", value: true}]
var map = d3.map([{name: "foo"}, {name: "bar"}], function(d) { return d.name; });
map.get("foo"); // {"name": "foo"}
map.get("bar"); // {"name": "bar"}
map.get("baz"); // undefined
// time的方法
d3.timeDays(new Date('2014-01-11'), new Date('2014-02-12')) // 获取2014-01-11 到2014-02-12的日期数组
复制代码
上面是单纯的数据处理也就是工具类,可是d3的强大不只仅在于此,d3提供了一个强大算法库,好比力导向图的碰撞检测以及tick等等,这里的功能也属于数据处理可是又跟插入dom密不可分。数组
d3的数据不只仅是这些有些跟dom耦合极深没办法彻底拎出来讲, 并且d3的api极多, 这些东西不少时候也只能边看文档边作。好在d3的示例不少,基本需求都能知足。浏览器
关于dom操做d3也提供了一系列方便的接口,好比d3.select
,d3.append
等等, 这部分的接口至关多,我的也无法一一说明, 只能说用法都是同样的,和jQuery至关相似:
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 2.5);
复制代码
上面的代码是把circle
跟data
进行数据绑定并插入对应的dom节点(引用自连接):
- 首先,
svg.selectAll("circle")
返回一个空选集,由于当前 SVG 尚未任何子元素,该选集的父节点是这个 SVG 容器。- 而后将该选集与数据绑定,产生三个新的子选集,分别表明三种可能的状态:enter、update 和 exit。因为当前选集为空,因此 update 和 exit 子选集也为空,enter 子选集就包含了每条数据对应的元素的占位符。
- update 子选集直接经过
selection.data
返回,enter 和 exit 子选集分别经过selection.enter
和selection.exit
返回。- 那些缺乏的元素经过对 enter 子选集调用
selection.append
方法来添加到 SVG 中,这样就为每条数据添加了一个新的圆点到 SVG 中。
如上都是链式操做
不一样于canvas这里能够直接触发原生事件,让人亲切不少。
事件是指基于dom的一些交互操做,包括但不限于click
等原生事件,相似jQuery,事件是经过on
进行绑定的:
selection.on('click', function (d) {}) // this指向事件元素, d是绑定的数据能够直接使用
复制代码
同时,d3提供了不少自定义事件诸如drag, zoom,brush等等,这时候就是经过call
调用了:
const brush = d3.brushX()
.extent([[50, 50], [1100, 150]])
.on('start brush', brushed)
.on('end', brushended)
svg.append("g")
.call(brush)
复制代码
上面是调用brush事件,同时调用相应的回调, 都是字面意思,至于还有不少有意思的事件,都隐藏在文档中。
这个其余
就包含了不少东西, 好比异步请求,解析excel,动画等等,这里不一一说明了, 可是若是发现有需求无法实现不妨看看文档,说不定就内置了呢。
下面给个示例, 简单力导向图示例jsfiddle:
核心代码以下:
const height = 200
const width = 200
const svg = d3.select('body').append('svg')
const graph = {
nodes: [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' }
],
links: [
{ source: 1, target: 2 }
]
}
const simulation = d3.forceSimulation()
.force('charge', d3.forceManyBody().strength(-700).distanceMin(100).distanceMax(1000))
.force('link', d3.forceLink().id(d => d.id))
.force('center', d3.forceCenter(width / 2, height / 2))
const link = svg.selectAll('link')
.data(graph.links)
.enter()
.append('line')
.attr('class', 'link')
const node = svg.selectAll('node')
.data(graph.nodes)
.enter().append('g')
.attr('class', 'node')
node.append('circle')
.attr('r', 13)
.attr('fill', '#999')
node.append('text')
.attr('dx', -18)
.attr('dy', 8)
.style('font-family', 'overwatch')
.style('font-size', '18px')
.text(d => d.name)
const ticked = function () {
link.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node.attr('transform', d => `translate(${d.x}, ${d.y})`)
}
const { nodes, links } = graph
simulation.nodes(nodes).on('tick', ticked)
simulation.force('link').links(links)
复制代码
下面简单解析一下代码部分,const svg = d3.select('body').append('svg')
就是上面提到的d3操做dom的部分,就是相似jQuery的插入操做, 总之咱们获取到了svg画布, graph
是提供了数据关系模型,可是通常来说后端不会这么提供严格的对应关系, 这时候就须要咱们队数据进行处理以获取合理的数据格式, 通常来说数据格式都是如上。
力导向图的核心是forceSimulation
, 如字面上的意思就是来模拟力的,这是d3的内部算法咱们基本干涉不了, 因此d3的力导向图怎么动最后停在哪都是咱们无法精确控制的, forceSimulation
定义了力导向图的基本形态好比key值是否居中等等, 可是到这一步还没对数据进行任何处理。
const link
和 const node
, 简单讲就是把数据和dom进行绑定插入对应的dom节点, 一直到这一步, 咱们完成了基本的步骤:根据关系模型绘制对应节点, 因为不是canvas, 每一个数据节点都有一个对应的dom节点, 这里能够对样式进行精确的处理。
截止上面也并非非得用d3不可,就是一些dom插入操做, 原生js也是能够实现的。 simulation.nodes(nodes).on('tick', ticked)
跟simulation.force('link').links(links)
才是d3真正的做用所在,它会修改原来的数据模型在上面挂载一些位置信息, 如图所示:
能够看到,nodes和link上面分别多了很多数据,暂时咱们不须要了解那么多, 只要知道x
和y
是节点的位置信息便可,另外力导向图会不停的tick
(300次左右),每次tick
,d3都会修改graph
上的位置信息,它内部确定作了不少事情, 好比碰撞检测等等。当每次tick
触发的时候咱们都已调用一个callback,在这个callback里更新全部节点的位置信息,也就是上面代码的ticked
, 咱们就是修改了node和link的位置信息也就是x1
之类的, 这些都是svg提供的接口这里很少作说明了。 到这里, 一个完整的力导向图算是完成了,虽然数据少了点可是并不妨碍咱们去理解其中的原理。
经过上面一个完整示例, 咱们发现,d3的核心并不在于绘制图形,这些都是dom操做,而是数据的处理,数据驱动dom,到这里是否是跟现代mvvm又挂上钩了,而且d3是基于dom的, 咱们彻底能够把d3当作一个算法库,处理数据,至于图像的绘制彻底能够交由react等框架,这是canvas类库所作不到的。用上virtual dom性能可能还会更高一点。dom操做是昂贵的,virtual dom跟d3搭配味道可能更佳。若是把d3做为一个算法库咱们还缺乏最佳实践。还须要学习。