桑基图是
流图
(flow diagram
)的一种,用来描述能量,人口,经济等的流动情况。最早由爱尔兰人Matthew Henry Phineas Riall Sankey 提出。Sankey是一名船长也是工程师,1898年Sankey在土木工程师学会会报纪要的一篇关于蒸汽机能源效率的文章中首次推出了第一个能量流动图,后来被命名为Sankey图,中文音译为桑基图。
桑基图主要关注能量、物料或资本等在系统内部的流动和转移情况。起始流量和结束流量相同;在内部,不同的线条代表了不同的流量分流情况,它的宽度成比例地显示此分支占有的流量;节点不同的宽度代表了特定状态下的流量大小。
D3提供一个Sankey插件,可以用来根据输入的节点-连接数据转换为桑基图布局所用的中间数据。这些中间数据可以很方便地结合SVG的矩形元素(rect
)绘制节点,结合路径元素(path
)就可以绘制链接。使得用D3绘制Sankey图很方便。图1展示了绘制效果。
下面我们来详细介绍如果实现sankey图的绘制:
首先,绘制sankey图需要引入D3库以及Sankey插件库:
<script src="../d3.js" charset="utf-8"></script >
<script src="../sankey.js"></script>
然后,我们需要准备sankey图所需要的数据,这些数据按照一定的格式描述了网络中的节点-链接关系:
// 节点和连接数据
var data = {
‘nodes‘: [
{name: "0"},
{name: "1"},
{name: "2"},
{name: "3"},
{name: "4"},
{name: "5"},
{name: "6"},
{name: "7"},
{name: "8"}
],
‘links‘: [
{source: 0, target: 3, value: 10},
{source: 1, target: 4, value: 10},
{source: 2, target: 4, value: 5},
{source: 1, target: 5, value: 5},
{source: 3, target: 6, value: 5},
{source: 3, target: 7, value: 5},
{source: 4, target: 7, value: 10},
{source: 4, target: 8, value: 5},
{source: 5, target: 8, value: 5}
]
};
这里的数据定义为JavaScript脚本中的变量,当然也可以由其他形式的数据转换,例如加载JSON数据文件等。但转换后的最终形式必须如上所示,即含有一个节点数组和一个一个链接数组。节点数组中每个元素是一个对象,含有一个`name`属性表示节点的名称;链接数组中每个对象都含有`source`、`target`和`value`三个属性,分别代表了来源节点的索引(从0开始),目标节点的索引,以及流量。
接下来,我们可以配置sankey布局的属性值:
// 定义桑基布局
var sankey = d3.sankey()
.nodeWidth(80)
.nodePadding(40)
.size([width, height])
.nodes(data.nodes)
.links(data.links)
.layout(3);
其中各个函数的解释如下:
* `nodeWidth`表示节点水平宽度,这个属性可以用来设置矩形的宽度等;
* `nodePadding`表示矩形的垂直方向的间距;
* `size`表示整个sankey图占用的空间大小;
* `nodes`加载如上所述的节点数组;
* 同理`links`函数加载链接数组;
* `layout`中的参数表示桑基布局用来优化流布局的时间。
定义完了sankey布局之后最重要的就可以得到一个链接路径生成器:
// 路径数据生成器
var path = sankey.link();
图2 由sankey布局转换后的节点和连接数据
最后,就可以绑定数据绘制矩形节点和路径了:
// 绘制连接数据
var links = svg.append("g").selectAll("path")
.data(data.links)
.enter()
// 绑定节点数据
var nodes = svg.append("g").selectAll(".node")
.data(data.nodes)
.enter();
// 绘制连接线
links.append("path")
.attr({
fill: "none", //填充色
stroke: function(d,i){ return color(i); }, //描边色
"stroke-opacity": 0.5, //描边透明度
d: path, //路径数据
id: function(d,i){ return ‘link‘ +i } //ID
})
.style("stroke-width", function (d) { //连线的宽度
return Math.max(1, d.dy);
});
// 绘制圆形节点
nodes.append("circle")
.attr("transform",function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) { return d.dy / 2; })
.attr("cx", function(d) { return d.dx/2; })
.attr("cy", function(d) { return d.dy / 2; })
.style("fill", "tomato")
.style("stroke", "gray");
为了方便理解sankey图的流量的流动情况,我们还可以为节点和链接添加文字提示,
给节点添加文字很简单,直接给之前绑定了数据并添加了占位符的nodes
对象添加text
元素并设置文本内容和坐标即可:
// 绘制节点文本
nodes.append("text")
.attr({
x: function (d) { return d.x+sankey.nodeWidth() / 2; },
y: function (d) { return d.y+d.dy / 2; }
})
.text(function (d) { return d.name; });
给链接添加文本稍微复杂一点,需要给text元素再添加textPath子元素,并用xlink:href属性和对应的链接路径关联起来,用startOffset属性可以设置文字相对于链接路径的位置,例如这里的50%表示居中显示。
// 绘制连接文本
links.append(‘text‘)
.append(‘textPath‘)
.attr(‘xlink:href‘, function (d,i) { return ‘#link‘ + i; })
.attr(‘startOffset‘,‘50%‘)
.text(function (d) { return ‘流量:‘ + d.value; })
添加完文字之后可以清晰地展现流量变化情况,如图3所示:
当然,也可以将sankey图中的矩形换成圆形。关键是在定义sankey布局的时候指定的节点宽度不能太大,例如本例指定为1像素:
.nodeWidth(1)
然后,可以把圆平移到sankey布局计算得到的节点坐标处,并用节点的偏移量指定圆的圆心坐标和半径。
// 绘制圆形节点
nodes.append("circle")
.attr("transform",function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) { return d.dy / 2; })
.attr("cx", function(d) { return d.dx / 2; })
.attr("cy", function(d) { return d.dy / 2; })
.style("fill", "tomato");
还需要相应地调整下显示的尺寸,最后绘制效果如图4所示:
有时候为了增加sankey图的易用性,可能需要给节点和连接线添加一些交互效果。下面介绍给连接线添加鼠标悬停事件,以及给节点添加拖动事件。
用CSS控制悬浮样式,让连接线在鼠标悬停的时候高亮显示:
<style type="text/css">
path:hover{
stroke-opacity: 0.9;
}
</style>
效果如图5所示:
// 绘制矩形节点
nodes.append("rect")
.attr({
x: function (d) { return d.x; },
y: function (d) { return d.y; },
height: function (d) { return d.dy; },
width: sankey.nodeWidth(),
fill: "tomato"
})
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", dragmove)
);
这里的.origin(function(d) { return d; })
是为了防止拖动时出现跳动,相应的拖动事件监听器为:
// 拖动事件响应函数
function dragmove(d) {
d3.select(this).attr({
"x": (d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))),
"y": (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y)))
});
sankey.relayout();
paths.attr(‘d‘,path);
}
也就是在拖动过程中重新计算矩形的坐标位置,并重新启动桑基布局,之后给路径元素绑定新的路径数据。这里拖动时文本如何移动就留给读者作为练习题,最后效果如图6所示:
好了!桑基图就介绍到这了,感谢关注!
版权声明:本文为博主原创文章,未经博主允许不得转载。
【D3.js数据可视化实战】--(3)桑基图(sankey)的绘制
原文地址:http://blog.csdn.net/tianxuzhang/article/details/49624701