数据
const data = {
"name": "中国",
"children": [
{"name": "北京"},
{"name": "陕西",
"children": [
{"name": "宝鸡"},
{"name": "西安"}
]
},
{"name": "上海"},
{"name": "浙江",
"children": [
{"name": "杭州"},
{"name": "温州"}
]
},
]
}
层次化
d3.hierarchy
- d3.hierarchy(data[, children]) - 从给定的层次结构数据构造一个根节点并为各个节点指定深度等属性.
- node.ancestors - 从当前节点开始返回其祖先节点数组.
- node.descendants - 从当前节点开始返回其后代节点数组.
- node.leaves - 返回当前节点为根节点的子树的叶节点.
- node.find - 查找指定节点.
- node.path - 返回从当前节点到指定目标节点的最短路径.
- node.links - 返回当前节点所在子树的所有边.
- node.sum - 评估和汇总定量值.
- node.sort - 排序所有的后代兄弟节点.
- node.count - 统计叶节点的个数.
- node.each - 广度优先遍历当前子树.
- node.eachAfter - 后续遍历当前子树.
- node.eachBefore - 前序遍历当前子树.
- node.copy - 拷贝一个当前节点为根节点的子树的副本.
const dataSet = d3.hierarchy(data)
console.log(dataSet)
返回的节点和每一个后代会被附加如下属性:
- node.data - 关联的数据
- node.depth - 当前节点的深度, 根节点为
0
. - node.height - 当前节点的高度, 叶节点为
0
. - node.parent - 当前节点的父节点, 根节点为
null
. - node.children - 当前节点的孩子节点(如果有的话); 叶节点为
undefined
.
d3.hierarchy
默认子节点取得是children
属性,当然也可以自定义:d3.hierarchy(data, d => d.child)
。
d3.stratify
对于扁平数据,我们可以用d3.stratify
来实现数据的层次化:
let data = [
{"name": "Eve", "parent": ""},
{"name": "Cain", "parent": "Eve"},
{"name": "Seth", "parent": "Eve"},
{"name": "Enos", "parent": "Seth"},
{"name": "Noam", "parent": "Seth"},
{"name": "Abel", "parent": "Eve"},
{"name": "Awan", "parent": "Eve"},
{"name": "Enoch", "parent": "Awan"},
{"name": "Azura", "parent": "Eve"}
]
const dataSet = d3.stratify()
.id(function(d) { return d.name; })
.parentId(function(d) { return d.parent; })
(data)
树布局
- d3.tree - 创建一个新的整齐(同深度节点对齐)的树布局.
- tree - 将指定的层次数据布局为整齐的树布局.
- tree.size - 设置布局尺寸.
- tree.nodeSize - 设置节点尺寸.
- tree.separation - 设置两个相邻的节点之间的间距.
//创建树布局
const tree = d3.tree().size([300, 300])
console.log(tree)
//所有的节点
const nodes = tree(dataSet)
console.log(nodes)
console.log(nodes.descendants()) // 返回所有节点
d3.tree
对 hierarchy 进行布局,并为 root 以及它的每一个后代附加两个属性:
- node.x - 节点的 x- 坐标
- node.y - 节点的 y- 坐标
经过布局之后,我们就可以获取到对应的节点(node
)信息和连线(link
)信息:
绘制
绘制节点
const node = group.selectAll('.node')
.data(nodes.descendants())
.enter()
.append('g')
.attr('class', function(d) {
return 'node' + (d.children ? ' node--internal' : ' node--leaf');
})
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
})
node.append('circle')
.attr('r', 20)
.attr('fill', (d, i) => color(i))
绘制节点文字
node.append('text')
.attr("dy", ".33em")
.attr("font-size","12px")
.attr("text-anchor", "middle") // 文字居中
.attr('fill', '#fff')
.text(d => d.tata.name)
绘制连线
const link = group.selectAll('.link')
.data(nodes.links())
.enter()
.append('path')
.attr('class', 'link')
.attr('d', d3.linkHorizontal() // linkVertical() 垂直 linkHorizontal() 水平
.x(function(d) { return d.y; })
.y(function(d) { return d.x; })
)
.attr('fill', 'none')
.attr('stroke', '#ccc')
需要注意下的是,x和y是反着来的,如果不反着赋值,效果如下图,还有水平和垂直,大家都可以动手试试效果。
最后
树形图的绘制可以总结为:
- 处理数据,层次化
- 构建树形布局,确定节点位置和连线数据
- 绘制节点和连线