D3.js系列——布局:弦图和集群图/树状图
一、弦图
1、弦图是什么
弦图(Chord),主要用于表示两个节点之间的联系的图表。两点之间的连线,表示谁和谁具有联系。
2、数据
初始数据为:
var city_name = [ "北京" , "上海" , "广州" , "深圳" , "香港" ];
var population = [
[ 1000, 3045 , 4567 , 1234 , 3714 ],
[ 3214, 2000 , 2060 , 124 , 3234 ],
[ 8761, 6545 , 3000 , 8045 , 647 ],
[ 3211, 1067 , 3214 , 4000 , 1006 ],
[ 2146, 1034 , 6745 , 4764 , 5000 ]
];
数据是一些城市名和一些数字,这些数字表示城市人口的来源。其意思如下:
北京 | 上海 | |
北京 | 1000 | 3045 |
上海 | 3214 | 2000 |
左边第一列是被统计人口的城市,上边第一行是被统计的来源城市,即:
北京市的人口有 1000 个人来自本地,有 3045 人是来自上海的移民,总人口为 1000 + 3045。
上海市的人口有 2000 个人来自本地,有 3214 人是来自北京的移民,总人口为 3214 + 2000。
好了!!!对于这样一组数据,怎么进行可视化。
3、布局(数据转换)
弦图的布局如下:
var chord_layout = d3.layout.chord()
.padding(0.03) //节点之间的间隔
.sortSubgroups(d3.descending) //排序
.matrix(population); //输入矩阵
然后,应用此布局转换数据。
var groups = chord_layout.groups();
var chords = chord_layout.chords();
console.log( groups );
console.log( chords );
population 经过转换后,实际上分成了两部分:groups 和 chords。前者是节点,后者是连线,也就是弦。chords 就是上图中的连线。chords 里面又分为 source 和 target ,也就是连线的两端。
在控制台输出一下节点和连线,看看得到了怎样的数据。
节点:
连线(弦):
4、绘制节点
先定义相关变量,很熟悉了
var width = 600;
var height = 600;
var innerRadius = width/2 * 0.7;
var outerRadius = innerRadius * 1.1;
var color20 = d3.scale.category20();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")")
绘制节点(即分组,有多少个城市画多少个弧形),及绘制城市名称
var outer_arc = d3.svg.arc() //获取圆弧生成器的路径值
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var g_outer = svg.append("g"); //添加分组
g_outer.selectAll("path")
.data(groups)
.enter()
.append("path")
.style("fill", function(d) { return color20(d.index); })
.style("stroke", function(d) { return color20(d.index); })
.attr("d", outer_arc ); //绑定路径的属性值
g_outer.selectAll("text")
.data(groups)
.enter()
.append("text")
.each( function(d,i) {
d.angle = (d.startAngle + d.endAngle) / 2;
d.name = city_name[i];
})
.attr("dy",".35em")
.attr("transform", function(d){
return "rotate(" + ( d.angle * 180 / Math.PI ) + ")" +
"translate(0,"+ -1.0*(outerRadius+10) +")" +
( ( d.angle > Math.PI*3/4 && d.angle < Math.PI*5/4 ) ? "rotate(180)" : "");
})
.text(function(d){
return d.name;
});
节点位于弦图的外部。节点数组 groups 的每一项,都有起始角度和终止角度,因此节点其实是用弧形来表示的,这与饼状图类似。
然后就是节点的文字(即城市名称),有两个地方要特别注意。
each():表示对任何一个绑定数据的元素,都执行后面的无名函数 function(d,i) ,函数体里做两件事:
- 计算起始角度和终止角度的平均值,赋值给 d.angle 。
- 将 city_name[i] 城市名称赋值给 d.name 。
transform 的参数:用 translate 进行坐标变换时,要注意顺序: rotate -> translate(先旋转再平移)。 此外,
( ( d.angle > Math.PI*3/4 && d.angle < Math.PI*5/4 ) ? "rotate(180)" : "")
意思是,当角度在 135° 到 225° 之间时,旋转 180°。不这么做的话,下方的文字是倒的。
5、绘制连线(弦)
绘制连线(即所有城市人口的来源,即有 5 * 5 = 25 条弧)
var inner_chord = d3.svg.chord()
.radius(innerRadius);
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chords)
.enter()
.append("path")
.attr("d", inner_chord ) //path值
.style("fill", function(d) { return color20(d.source.index); })
.style("opacity", 1)
.on("mouseover",function(d,i){
d3.select(this)
.style("fill","yellow");
})
.on("mouseout",function(d,i) {
d3.select(this)
.transition()
.duration(1000)
.style("fill",color20(d.source.index));
});
SVG 中没有现成的弦元素(例如圆有 <circle>,但是弦却没有 <chord>),需要用路径元素 <path> 来制作。至于路径值是什么呢?我们不需要手动计算,D3 提供了 d3.svg.chord() ,只需要将弦的对象传递给它,即可得到路径值。
上面还有几句关于交互式操作的代码: mouseover 和 mouseout 。
二、集群图
集群图,是一种用于表示包含与被包含关系的图表。
1、数据
初始数据先写在一个 JSON 文件中,再用 D3 来读取。现有数据如下:
{
"name":"中国",
"children":
[
{
"name":"浙江" ,
"children":
[
{"name":"杭州" },
{"name":"宁波" },
{"name":"温州" },
{"name":"绍兴" }
]
},
{
"name":"广西" ,
"children":
[
{"name":"桂林"},
{"name":"南宁"},
{"name":"柳州"},
{"name":"防城港"}
]
},
{
"name":"黑龙江",
"children":
[
{"name":"哈尔滨"},
{"name":"齐齐哈尔"},
{"name":"牡丹江"},
{"name":"大庆"}
]
},
{
"name":"新疆" ,
"children":
[
{"name":"乌鲁木齐"},
{"name":"克拉玛依"},
{"name":"吐鲁番"},
{"name":"哈密"}
]
}
]
}
这段数据表示:“中国 – 省份名 – 城市名”的包含于被包含关系。
2、布局(数据转换)
定义一个集群图布局:
var cluster = d3.layout.cluster()
.size([width, height - 200]);
布局保存在变量 cluster 中,变量 cluster 可用于转换数据。
size() 设定尺寸,即转换后的各节点的坐标在哪一个范围内。
接下来,转换数据:
d3.json("city.json", function(error, root) {
var nodes = cluster.nodes(root);
var links = cluster.links(nodes);
console.log(nodes);
console.log(links);
}
d3.json() 是用来读取 JSON 文件的。要注意,d3.json() 不能读取本地文件。例如,将 html 文件与 json 文件放到本地同一目录,打开 html 文件是不能顺利读取的。需要搭建一个网络服务器来使用它,可用 Apache 搭建一个简单的服务器。否则,浏览器(Chrome)的控制台中,会出现以下错误:XMLHttpRequest cannot load file:///D:/*******/city.json. Cross origin requests are only supported for HTTP.
经过测试,Firefox 可以直接读取本地文件,无需搭服务器,其他大多数浏览器不行。建议搭建服务器进行测试,这是正确的做法。
d3.json() 函数后面跟一个无名函数 function(error) ,参数 root 是读入的数据。后两行代码调用 cluster 转换数据,保存到变量 nodes 和 links 中。然后输出转换后的数据,结果如下图所示:
转换后的顶点数据(nodes):
转换后的连接线数据(links):
nodes 中有各个节点的子节点(children)、深度(depth)、名称(name)、位置(x,y)信息,其中名称(name)是 json 文件中就有的属性。
links 中有连线两端( source , target )的节点信息。
3、绘制
D3 已经基本上为我们准备好了绘制的函数:d3.svg.diagonal() 。这是一个对角线生成器,只需要输入两个顶点坐标,即可生成一条贝塞尔曲线。
创建一个对角线生成器:
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
projection() 是一个点变换器,默认是 [ d.x , d.y ],即保持原坐标不变,如果写成 [ d.y , d.x ] ,即是说对任意输入的顶点,都交换 x 和 y 坐标。
绘制连线时,使用方法如下:
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal); //使用对角线生成器path值
绘制节点时,还是用 <svg> 中的 <circle> 来绘制。
//获取所有节点元素
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
//节点元素绘制圆
node.append("circle")
.attr("r", 4.5);
//添加文本
node.append("text")
.attr("dx", function(d) { return d.children ? -8 : 8; })
.attr("dy", 3)
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.name; });
三、树状图
树状图( Tree )用于表示层级、上下级、包含与被包含关系,其布局的用法与集群图几乎完全相同。