树状图
在d3 中,绘制树状图,要用到层级布局这个概念:
代码语言:javascript复制d3.hierarchy(data[, children])
根据指定的层次结构数据构造一个根节点。指定的数据 data 必须为一个表示根节点的对象。比如:
代码语言:javascript复制{
"name": "Eve",
"children": [
{
"name": "Cain"
},
{
"name": "Seth",
"children": [
{
"name": "Enos"
},
{
"name": "Noam"
}
]
},
{
"name": "Abel"
},
{
"name": "Awan",
"children": [
{
"name": "Enoch"
}
]
},
{
"name": "Azura"
}
]
}
指定的 children 访问器会为每个数据进行调用,从根 data 开始,并且必须返回一个数组用以表示当前数据的子节点,返回 null
表示当前数据没有子节点。如果没有指定 children 则默认为:
function children(d) {
return d.children;
}
返回的节点和每一个后代会被附加如下属性:
文档:https://www.d3js.org.cn/document/d3-hierarchy/#installing
其中,
- node.descendants():返回后代节点数组,第一个节点为自身,然后依次为所有子节点的拓扑排序
- node.links():返回当前 node 的
links
数组, 其中每个 link 是一个定义了 source 和 target 属性的对象。每个link
的source
为父节点,target
为子节点。
同时,需要和tree生成器一起使用,来得到绘制树所需要的节点数据和边数据。
文档: https://www.d3js.org.cn/document/d3-hierarchy/#tree
其中,
d3.tree(),创建一个树状图生成器,使用默认的设置创建一个新的树布局
d3.tree().size([size]),定义树的大小。如果指定了 size 则设置当前系统树布局的尺寸为一个指定的二元数值类型数组,表示 [width, height] 并返回当前树布局。如果 size 没有指定则返回当前系统树布局的尺寸,默认为 [1, 1]。如果返回的布局尺寸为 null
时则表示实际的尺寸根据 node size 确定。
d3.tree.nodeSize([size]),如果指定了 size 则设置系统树布局的节点尺寸为指定的数值二元数组,表示为 [width, height] 并返回当前树布局。如果没有指定 size 则返回当前节点尺寸,默认为 null
。如果返回的尺寸为 null
则表示使用 layout size 来自动计算节点大小。当指定了节点尺寸时,根节点的位置总是位于 ⟨0, 0⟩。
d3.tree().separation([separation]),定义邻居节点的距离。如果指定了 seperation, 则设置间隔访问器为指定的函数并返回当前树布局。如果没有指定 seperation 则返回当前的间隔访问器,默认为:
代码语言:javascript复制function separation(a, b) {
return a.parent == b.parent ? 1 : 2;
}
一种更适合于径向布局的变体,可以按比例缩小半径差距:
代码语言:javascript复制function separation(a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth;
}
间隔访问器用来设置相邻的两个节点之间的间隔。
绘制
1. 数据准备
代码语言:javascript复制//定义边界
var marge = {top:50, bottom:0, left:10, right:0};
var svg = d3.select("svg");
var width = svg.attr("width");
var height = svg.attr("height");
var g = svg.append("g")
.attr("transform","translate(" marge.top "," marge.left ")");
var scale = svg.append("g")
.attr("transform","translate(" marge.top "," marge.left ")");
//数据
var dataset = {
name:"中国",
children:[
{
name:"浙江",
children:[
{name:"杭州" ,value:100},
{name:"宁波",value:100},
{name:"温州",value:100},
{name:"绍兴",value:100}
]
},
{
name:"广西",
children:[
{
name:"桂林",
children:[
{name:"秀峰区",value:100},
{name:"叠彩区",value:100},
{name:"象山区",value:100},
{name:"七星区",value:100}
]
},
{name:"南宁",value:100},
{name:"柳州",value:100},
{name:"防城港",value:100}
]
},
{
name:"黑龙江",
children:[
{name:"哈尔滨",value:100},
{name:"齐齐哈尔",value:100},
{name:"牡丹江",value:100},
{name:"大庆",value:100}
]
},
{
name:"新疆" ,
children:
[
{name:"乌鲁木齐"},
{name:"克拉玛依"},
{name:"吐鲁番"},
{name:"哈密"}
]
}
]
};
2. 创建层级布局
代码语言:javascript复制var hierarchyData = d3.hierarchy(dataset)
.sum(function(d){
return d.value;
});
3. 创建一个树状图
代码语言:javascript复制//创建一个树状图
var tree = d3.tree()
.size([width-400,height-200])
.separation(function(a,b){
return (a.parent==b.parent?1:2)/a.depth;
})
4. 初始化树状图,也就是传入数据,并得到绘制树基本数据
代码语言:javascript复制var treeData = tree(hierarchyData);
代码语言:javascript复制var nodes = treeData.descendants();
var links = treeData.links();
5. 创建一个贝塞尔生成曲线生成器
代码语言:javascript复制var Bézier_curve_generator = d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; });
6. 绘制边
代码语言:javascript复制//绘制边
g.append("g")
.selectAll("path")
.data(links)
.enter()
.append("path")
.attr("d",function(d){
var start = {x:d.source.x,y:d.source.y};
var end = {x:d.target.x,y:d.target.y};
return Bézier_curve_generator({source:start,target:end});
})
.attr("fill","none")
.attr("stroke","yellow")
.attr("stroke-width",1);
注意,attr(“d”,function(d)这个函数第二个参数的格式要求。
7. 常规:建立用来放在每个节点和对应文字的分组
代码语言:javascript复制var gs = g.append("g")
.selectAll("g")
.data(nodes)
.enter()
.append("g")
.attr("transform",function(d){
var cx = d.x;
var cy= d.y;
return "translate(" cy "," cx ")";
});
8. 绘制节点和文字
代码语言:javascript复制//绘制节点
gs.append("circle")
.attr("r",6)
.attr("fill","white")
.attr("stroke","blue")
.attr("stroke-width",1);
//文字
gs.append("text")
.attr("x",function(d){
return d.children?-40:8;//如果某节点有子节点,则对应的文字前移
})
.attr("y",-5)
.attr("dy",10)
.text(function(d){
return d.data.name;
})
完整代码
代码语言:javascript复制<body>
<svg width="1000" height="1000"></svg>
<script>
var marge = {top:60,bottom:60,left:60,right:60}
var svg = d3.select("svg")
var width = svg.attr("width")
var height = svg.attr("height")
var g = svg.append("g").attr("transform","translate(" marge.top "," marge.left ")");
var scale = svg.append("g")
//1. 准备数据
var dataset = {
name:"中国",
children:[
{
name:"浙江",
children:[
{name:"杭州" ,value:100},
{name:"宁波",value:100},
{name:"温州",value:100},
{name:"绍兴",value:100}
]
},
{
name:"广西",
children:[
{
name:"桂林",
children:[
{name:"秀峰区",value:100},
{name:"叠彩区",value:100},
{name:"象山区",value:100},
{name:"七星区",value:100}
]
},
{name:"南宁",value:100},
{name:"柳州",value:100},
{name:"防城港",value:100}
]
},
{
name:"黑龙江",
children:[
{name:"哈尔滨",value:100},
{name:"齐齐哈尔",value:100},
{name:"牡丹江",value:100},
{name:"大庆",value:100}
]
},
{
name:"新疆" ,
children:
[
{name:"乌鲁木齐"},
{name:"克拉玛依"},
{name:"吐鲁番"},
{name:"哈密"}
]
}
]
};
//2. 创建层级布局
var hierarchyData = d3.hierarchy(dataset)
.sum(function(d){
return d.value;
});
//3. 创建一个树状图
var tree = d3.tree()
.size([width-400,height-200])
.separation(function(a,b){
return (a.parent==b.parent?1:2)/a.depth;
})
//4. 初始化树状图,也就是传入数据,并得到绘制树基本数据
var treeData = tree(hierarchyData);
var nodes = treeData.descendants();
var links = treeData.links();
//5. 创建一个贝塞尔生成曲线生成器
var Bézier_curve_generator = d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; });
//6. 绘制边
g.append("g")
.selectAll("path")
.data(links)
.enter()
.append("path")
.attr("d",function(d){
var start = {x:d.source.x,y:d.source.y};
var end = {x:d.target.x,y:d.target.y};
return Bézier_curve_generator({source:start,target:end});
})
.attr("fill","none")
.attr("stroke","yellow")
.attr("stroke-width",1);
//7. 常规:建立用来放在每个节点和对应文字的分组<g>
var gs = g.append("g")
.selectAll("g")
.data(nodes)
.enter()
.append("g")
.attr("transform",function(d){
var cx = d.x;
var cy= d.y;
return "translate(" cy "," cx ")";
});
//8. 绘制节点和文字
gs.append("circle")
.attr("r",6)
.attr("fill","white")
.attr("stroke","blue")
.attr("stroke-width",1);
//文字
gs.append("text")
.attr("x",function(d){
return d.children?-40:8;//如果某节点有子节点,则对应的文字前移
})
.attr("y",-5)
.attr("dy",10)
.text(function(d){
return d.data.name;
})
</script>
</body>