Chord Diagram
今天来聊一聊Chord Diagram,暂且叫弦图吧。这里我不确定从英文翻译过来是不是叫做弦图,因为在百度百科上搜“弦图”,解释的是我国古代数学家赵爽画的 “弦图”,是研究勾股定理的图。而我们这里讨论的Chord Diagram,维基百科上是如下定义的:
A chord diagram is a graphical method of displaying the inter-relationships between data in a matrix. The data are arranged radially around a circle with the relationships between the data points typically drawn as arcs connecting the data.——维基百科
因此,这里讲的Chord Diagram是一种用来展示一组矩阵数据之间相互关系的图形,图中的数据沿圆周呈放射状排列,数据点之间的关系通常绘制为连接数据的圆弧。
先来一张D3.js官网实现的Chord Diagram的图片如下。
Chord Diagram 到底能表示一组矩阵之间的什么关系,我也纳闷了很久,因为例子只给出了一个数据的矩阵,没有说明矩阵中的行列各代表什么信息,通过查阅相关资料,了解到Chord Diagram可以用来表示类似如下场景的信息,以官网的数据为例:
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
假设这组数据是某个跨世界百货公司提供的销售和供货信息的统计结果,其中行代表供货国家,列代表销售国家
供/销 | 中国[供] | 日本[供] | 韩国[供] | 美国[供] |
---|---|---|---|---|
韩国[销] | 11975 | 5871 | 8916 | 2868 |
日本[销] | 1951 | 10048 | 2060 | 6171 |
美国[销] | 8010 | 16145 | 8090 | 8045 |
中国[销] | 1013 | 990 | 940 | 6907 |
那么,这个矩阵的内部数据之间的关系可以描述为:
1. 中国供货给韩国的货物总数为11975
2. 日本供货给韩国的货物总数为5871
3. 中国供货给日本的货物总数为1951
4. ……
5. 美国供货给中国的货物总数为6907
总算了明白了弦图存在的伟大意义了。
接下来解释这张充满数学艺术气质的美图的实现过程。
index.html详解——源码
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.group-tick line {
stroke: #000;
}
.ribbons {
fill-opacity: 0.67;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// 定义数据矩阵
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var svg = d3.select("svg"), // 获取svg元素
width = +svg.attr("width"), // 获取svg元素的宽度
height = +svg.attr("height"), // 获取svg元素的高度
// 计算外半径尺寸,这里取svg画布的宽、高的最小值的一半,减去40,表示两边留有余地;
outerRadius = Math.min(width, height) * 0.5 - 40,
// 计算内半径尺寸
innerRadius = outerRadius - 30;
// 定义数值的格式化函数
var formatValue = d3.formatPrefix(",.0", 1e3);
// 定义一个chord diagram的布局函数chord()由于通过chord()函数将matrix转换后,matrix实际分成了
// 两个部分,groups 和 chords ,其中groups
// 表示弦图上的弧,称为外弦,groups中的各个元素都被计算用添加上了angle、startAngle、endAngle、index、value
// 等字段;chords 称为内弦,表示弦图中节点的连线及其权重。chords 里面分为 source 和 target ,分别标识连线的两端。
var chord = d3.chord()
// 设置弦片段之间的间隔角度,即chord diagram 图中组成外层圆圈的各个弧段之间的角度
.padAngle(0.05)
// 设置数据矩阵matrix 的行内各列的排序顺序为降序排列
.sortSubgroups(d3.descending);
// 定义一个弧线的布局函数arc()
var arc = d3.arc()
// 设置弧线的内半径
.innerRadius(innerRadius)
// 设置弧线的外半径
.outerRadius(outerRadius);
// 定义一个弦布局函数ribbon()
var ribbon = d3.ribbon()
// 设置弦的半径为弧线的内半径
.radius(innerRadius);
// 定义一个颜色函数color(),返回离散的颜色值,即四种颜色
var color = d3.scaleOrdinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
// 定义一个组元素
var g = svg.append("g")
// 将组元素移动到画布的中心处
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
// chord(matrix)函数用来将matrix数组转换为chord diagram 所需的数据格式,
// 通过datum将转换后用于显示弦图的数据绑定到 g元素上;
.datum(chord(matrix));
// 定义一组g元素,用来绑定弦图的 groups数据,即弧线
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
// group元素是用来放置弦图的“弧”的
group.append("path")
// 设置弧的填充色用color函数来获取
.style("fill", function(d) { return color(d.index); })
// 设置弧的边缘线用比其填充色较深的颜色来画
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
// 绑定arc布局到group的d属性上,用来画弧
.attr("d", arc);
// 定义每段弧上的刻度 元素
var groupTick = group.selectAll(".group-tick")
//为每段弧的刻度元素绑定数据,数据为当前弧上的刻度的角度数组
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
// 根据角度以及外半径定位刻度位置(这里的刻度指的是弦图上外围的小短刻度线)
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
// 绘制弦图外围的刻度线
groupTick.append("line")
.attr("x2", 6);
// 定义刻度线上的文字
groupTick
// 不能被5整除的数字不显示
.filter(function(d) { return d.value % 5e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
// 给之前定义的g这个元素添加样式并绑定数据用来画弦图的弦。
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
// 弦的填充色是目标点的索引值确定的
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); });
// Returns an array of tick angles and values for a given group and step.
// 该函数用来计算弧上的刻度的角度
function groupTicks(d, step) {
// k表示单位弧度
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
</script>
至此,弦图的解释完毕。今天周四,这周时间过的好快啊。