D3默认的树状图画图使用的是svg, 好比这个来自D3做者的例子:javascript
使用svg有好有坏:node
在大多数数据量不是特别大状况下, 使用svg的好处是远远盖过坏处的,但若是咱们真的须要渲染大量的数据呢?git
github.com/ssthouse/or…github
ssthouse.github.io/organizatio…npm
上面的demo就是使用 D3.js + Canvas 的方式实现的, 在组织的层数超过300时才会出现明显的卡顿, 能知足大部分的组织结构图的数据.canvas
虚拟Dom
中画好图像虚拟Dom
中的数据 (坐标 & 线的path) 等绘制到Canvas上Unique-color
的方式实现Canvas 的用户交互虚拟Dom
中画好图像首先调使用D3建立 Tree的虚拟Dom:bash
this.data = this.d3.hierarchy(data)
this.treeGenerator = this.d3.tree()
.nodeSize([this.nodeWidth, this.nodeHeight])
let nodes = this.treeData.descendants()
let links = this.treeData.links()
复制代码
上面的变量 nodes
和 links
如今就包含告终构图中每一个 组织节点
和 链接线
的坐标信息.dom
虚拟Dom
中的数据 (坐标 & 线的path) 等绘制到Canvas上在 drawShowCanvas中, 经过 d3.select拿到虚拟的dom节点, 再使用 Canvas的绘图函数进行绘制, 这里用到了一些 Util的工具方法, 具体实现请参考源码.ide
drawShowCanvas () {
this.context.clearRect(-50000, -10000, 100000, 100000)
let self = this
// draw links
this.virtualContainerNode.selectAll('.link')
.each(function () {
let node = self.d3.select(this)
let linkPath = self.d3.linkVertical()
.x(function (d) {
return d.x
})
.y(function (d) {
return d.y
})
.source(function () {
return {x: node.attr('sourceX'), y: node.attr('sourceY')}
})
.target(function () {
return {x: node.attr('targetX'), y: node.attr('targetY')}
})
let path = new Path2D(linkPath())
self.context.stroke(path)
})
this.virtualContainerNode.selectAll('.orgUnit')
.each(function () {
let node = self.d3.select(this)
let treeNode = node.data()[0]
let data = treeNode.data
self.context.fillStyle = '#3ca0ff'
let indexX = Number(node.attr('x')) - self.unitWidth / 2
let indexY = Number(node.attr('y')) - self.unitHeight / 2
// draw unit outline rect (if you want to modify this line ===> please modify the same line in `drawHiddenCanvas`)
Util.roundRect(self.context, indexX, indexY, self.unitWidth, self.unitHeight, 4, true, false)
Util.text(self.context, data.name, indexX + self.unitPadding, indexY + self.unitPadding, '20px', '#ffffff')
// Util.text(self.context, data.title, indexX + self.unitPadding, indexY + self.unitPadding + 30, '20px', '#000000')
let maxWidth = self.unitWidth - 2 * self.unitPadding
Util.wrapText(self.context, data.title, indexX + self.unitPadding, indexY + self.unitPadding + 24, maxWidth, 20)
})
}
复制代码
Unique-color
的方式实现Canvas 的用户交互下图中能够看到, 其实是有两张Canvas的, 其中下面的Canvas除了的节点颜色不一样外, 和上面的Cavans绘制的数据彻底相同.
drawCanvas () {
this.drawShowCanvas()
this.drawHiddenCanvas()
}
复制代码
在上面一张Canvas上监听用户点击事件, 经过象素的坐标, 在下面一张图中拿到用户点击的节点 (注意: 颜色和节点的键值对 是在下面一张Canvas绘制的时候就已经建立好的.)
setClickListener () {
let self = this
this.canvasNode.node().addEventListener('click', function (e) {
let colorStr = Util.getColorStrFromCanvas(self.hiddenContext, e.layerX, e.layerY)
let node = self.colorNodeMap[colorStr]
if (node) {
// let treeNodeData = node.data()[0]
// self.hideChildren(treeNodeData, true)
self.toggleTreeNode(node.data()[0])
self.update(node.data()[0])
}
})
}
复制代码
下面是建立 unique-color和节点的 键值对 的参考代码:
addColorKey () {
// give each node a unique color
let self = this
this.virtualContainerNode.selectAll('.orgUnit')
.each(function () {
let node = self.d3.select(this)
let newColor = Util.randomColor()
while (self.colorNodeMap[newColor]) {
newColor = Util.randomColor()
}
node.attr('colorKey', newColor)
node.data()[0]['colorKey'] = newColor
self.colorNodeMap[newColor] = node
})
}
复制代码
please replace the data in /src/base/data-generator
with your own nested data.
please add your data drawing logic in /src/components/org-chart.js #drawShowCanvas
if you like it , welcome to star and fork :tada:
# install dependencies
npm install
# serve with hot reload at localhost
npm run dev
# build for production with minification (build to ./docs folder, which can be auto servered by github page 🤓)
npm run build
复制代码
这里是个人 D3.js 、 数据可视化 的github 地址, 欢迎 start & fork :tada: