编写d3-selection插件
在使用d3中遇到问题: 在渲染中, 根据数据渲染自定义节点, 那么如何进行优雅的操作呢.
目标
先来抛出问题. 需求需要根据数组中元素的某个字段来画出配置中的图标, 如:
1 | [{ |
其中每个iconId对应了一个配置的好的svg, 那么如何把这些数据注入并画出对应配置的svg.
在d3-selection文档提到了扩充方法的办法以及如何嵌套joining data. 以这两个示例为入口扩展如何实现需求.
文档中的例子
d3.selection()
通过d3.select()
得到的都是d3.selection()
对象, 所以类似.attr()
, .style()
, .append()
的方法都是挂载在d3.selection()
上的. 所以拓展d3.selection()
的prototype就可以进行d3的拓展.
1 | d3.selection.prototype.checked = function(value) { |
这样对selection的.checked()
做出了拓展, 就可以直接在链式操作中使用啦.
1 | d3.selectAll("input[type=checkbox]").checked(true); |
嵌套joining data
文档中有个例子, 一个二维数组对应一个表格, 需要把第一次已经join data的selection赋值给一个中间变量, 再继续join data. 第二次调用.data()
方法需要传入function参数.
1 | var matrix = [ |
这里感到奇怪的是tr其实是一个multi-selection了, 对他进行join data以后的td又是什么样的selection呢, 我们在后面深入了解.
深入
selection结构
打印了selection, 每个selection结构为:
- 属性:
_groups
; 结构: 数组; 内容: 数组(内容 dom节点) - 属性:
_parents
; 结构: 数组; 内容: dom节点
那么接下来看一下在各个情况下selection的内容是什么.
d3.select()
和d3.selectAll()
的返回值
在页面上有2个类名为.test
的div. 分别选择:
d3.select(".test")
:
1 | { |
d3.selectAll(".test")
1 | { |
这里看出, 无论选取到多少dom, selection._groups
的长度还是为1, 只是数组第一个元素变成了node list.
嵌套joining data 的selection 结构
我们以[之前提到的例子](#嵌套joining\ data)来看. 这两个变量的结构分别为:
tr
1 | { |
td
1 | { |
开始我不负责的分析:
var tr = d3.select("body") .append("table") .selectAll("tr")
因为tr._groups[0]是个数组(而不是dom list), 所以执行了`.data`以后`._groups`扩展到了4个元素的数组, 并进行了和上一步一样的行为.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
直到这里, 当前selection还是table.
2. **`.data(matrix)`**
此时table的dom里被加上了`.__data__`属性, 值为matrix.
3. **`.enter()`**
因为table的`.__data__`的值第一层有4个元素. 所以被留了4个空位
4. **`.append("tr")`**
在空位上创建tr元素, 并返回了新的selection(因为`.append`方法返回的新的selection). 并把parent设为table.
5. ```js
var td = tr.selectAll("td")
.data(function(d) { return d; })
.enter().append("td")
.each()
, .call()
.attr()
, .style()
这样的方法可以对每个进行操作, 那么他们是如何对上面这样的selection进行操作的呢.
在attr的源码中可以看到selection还有一个.each
方法. 顺着看到了文档, 如下:
.each
接受参数function(datum, index, nodes)
, 还有一个重要的角色是this
,
.each
遍历的是当前selection的_.groups[0]的元素, 所以this
每个遍历到的元素构成的selection.
还看到了.call
方法, 作用是调用对传入的第一个参数为selection的函数做处理的快捷方式.
应用: 完成需求
于是写了一个满足需求的插件.
1 | import * as d3 from "d3-selection" |
d3.selection.prototype.createIcon = ...
: 在selection的prototype上写可以直接调用this.each()
,return this
: 因为需求是每个图标不同, 所以需要分别获取到每个子元素所绑定的__data__
, 调用.each
方法, 并在最后return this
返回当前selection以便于继续链式操作.let id = typeof fn === "function" ? fn(...params) : "0"
: d3的selection的所有动态参数都接受3个: datum, index, nodes. 所以看都不看直接z在每个子selection上调用插件的第一个参数, 如果传的不是方法就写个默认值(其实应该使用fn, 避免再判断是否存在配置)svg.call(drawIcon, config[id].path)
: drawIcon方法是把第二个参数的配置画到第一个参数的selection上, 调用了selection的快捷方法.call
.
(本文完)
如果你觉得本文对你有帮助, 你可以请我喝一杯咖啡
本文遵循cc协议
你可以在注明出处和非商用的前提下任意复制及演绎