捆图(Bundle)是 D3 中比较奇特的一个布局,只有两个函数,并且须要与其它布局配合使用。本文讲述捆图的制做方法。javascript
有关捆图的例子极少,很容易找到的是:http://bl.ocks.org/mbostock/1044242html
可是此例有些复杂,不太容易弄懂,并且也没有相应的说明文字(D3官网上的例大多没有相应文字解说),根本没法做为参考。本文提供一个简单的例子,讲解捆图究竟是什么,应该怎么作。java
捆图只有两个函数:node
d3.layout.bundle():建立一个捆图布局。数组
bundle(links):根据数组links的source和target,计算路径。app
捆图的布局之因此函数少,是由于它常与其它层级布局一块儿使用。所谓层级布局,是指采用嵌套结构(父子节点关系)来描述节点信息,根据层级布局扩展出来的布局即:集群图、打包图、分区图、树状图、矩阵树图。最多见的是与集群图一块儿使用,使用集群图布局计算节点的位置,再用捆图布局计算连线路径。也就是说,捆图布局只干一件事:svg
计算连线的路径。wordpress
下面举一个例子。中国的高铁已经在不少城市开通,如北京到上海,北京到桂林等。现要制做一图来表示通过哪一座城市的高铁最密集。函数
有9座城市:布局
var cities = { name: "", children:[ {name: "北京"},{name: "上海"},{name: "杭州"}, {name: "广州"},{name: "桂林"},{name: "昆明"}, {name: "成都"},{name: "西安"},{name: "太原"} ] };
这9座城市所属的节点有一个公共的父节点,父节点名称为空,稍后并不绘制此父节点。另外还有链接各城市高铁的数组,以下:
var railway = [ {source: "北京", target: "上海"}, {source: "北京", target: "广州"}, {source: "北京", target: "杭州"}, {source: "北京", target: "西安"}, {source: "北京", target: "成都"}, {source: "北京", target: "太原"}, {source: "北京", target: "桂林"}, {source: "北京", target: "昆明"}, {source: "北京", target: "成都"}, {source: "上海", target: "杭州"}, {source: "昆明", target: "成都"}, {source: "西安", target: "太原"} ]; //(该数据为假设,并无通过调查)
source和target分别表示高铁的两端。
前面提到,捆图布局要和其余布局联合使用,在这里与集群图布局联合。分别建立一个集群图布局和一个捆图布局。
var cluster = d3.layout.cluster() .size([360, width/2 - 50]) .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; }); var bundle = d3.layout.bundle();
从集群图布局的参数能够看出,接下来节点将要分布呈圆形。捆图布局没有参数能够设置,只建立便可,保存在变量bundle中。
先使用集群图布局计算节点:
var nodes = cluster.nodes(cities); console.log(nodes);
将计算后的节点数组保存在nodes中,并输出该数组,结果如图1所示,第一个节点有9个子节点,其余的节点都有且只有一个父节点,没有子节点。这是接下来捆图要使用的节点数组,可是倒是用集群图布局计算而来的。
图1
下一步是重点,要使用数组railway。因为railway中存储的source和target都只有城市名称,所以先要将其对应成nodes中的节点对象。写一个函数,按城市名将railway中的source和target替换成节点对象。
function map( nodes, links ){ var hash = []; for(var i = 0; i < nodes.length; i++){ hash[nodes[i].name] = nodes[i]; } var resultLinks = []; for(var i = 0; i < links.length; i++){ resultLinks.push({ source: hash[ links[i].source ], target: hash[ links[i].target ] }); } return resultLinks; }
使用该函数返回的数组,便可做为捆图布局bundle的参数使用:
var oLinks = map(nodes, railway); console.log(oLinks); var links = bundle(oLinks); console.log(links);
map()返回的结果保存在oLinks,bundle()返回的结果保存在links中,输出结果分别如图2和图3所示,即捆图布局的连线数组转换先后的变换。
图2
如图2,转换前,连线数组的每一项都只有两个变量:source和target,内容是节点对象。对于第一个连线,是从“北京”到“上海”。如图3,转换以后,source和target不见了,取而代之的是0、一、2,变成了一个数组。很明显,该数组的第一项和source的内容同样,最后一项和target的内容同样,但中间多出了一项(图3的红框内)。多出的这一项是根据source和target公共的父节点计算出来的。因而,该数组表示了一条路径。
其实,捆图布局根据各连线的source和target为咱们计算了一条条连线路径,咱们能够把捆图布局的做用简单地解释为:使用这些路径绘制的线条能更美观地表示“通过哪座城市的高铁最多”。
图3
通过捆图布局转换后的数据很适合用d3.svg.line()和d3.svg.line.radial()来绘制,前者是线段生成器,后者是放射式线段生成器。在line.interpolate()所预约义的插值模式中,有一种就叫作bundle,正是为捆图准备的。
因为本例中用集群图布局计算节点数组使用的使用圆形的,所以要用放射式的线段生成器。先建立一个:
var line = d3.svg.line.radial() .interpolate("bundle") .tension(.85) .radius(function(d) { return d.y; }) .angle(function(d) { return d.x / 180 * Math.PI; });
此线段生成器是用来获取连线路径的。接下来,添加一个分组元素<g>,用来放全部与捆图相关的元素。
gBundle = svg.append("g") .attr("transform", "translate(" + (width/2) + "," + (height/2) + ")"); var color = d3.scale.category20c(); //颜色比例尺
而后,先在gBundle中添加连线路径:
var link = gBundle.selectAll(".link") .data(links) .enter() .append("path") .attr("class", "link") .attr("d", line); //使用线段生成器
在该连线的样式中,添加透明度可以在连线汇聚处更能显示出“捆”的效果。例如样式设定为:
.link { fill: none; stroke: black; stroke-opacity: .5; stroke-width: 8px; }
连线的绘制结果如图4所示。
图4
最后,向图中添加节点。节点用一个圈,里面写上城市的名称来表示。首先,绑定节点数组,并添加与之对应的<g>元素。
var node = gBundle.selectAll(".node") .data( nodes.filter(function(d) { return !d.children; }) ) .enter() .append("g") .attr("class", "node") .attr("transform", function(d) { return "rotate(" + (d.x- 90) + ")translate(" + d.y + ")" + "rotate("+ (90 - d.x) +")"; });
要注意,被绑定的数组是通过过滤后的nodes数组。此处的filter是JavaScript数组对象自身的函数,粗体字的部分表示只绑定没有子节点的节点,也就是说9座城市的公共父节点不绘制。而后只要在该分组元素<g>中分别加入<circle>和<text>便可。
node.append("circle") .attr("r", 20) .style("fill",function(d,i){ return color(i); }); node.append("text") .attr("dy",".2em") .style("text-anchor", "middle") .text(function(d) { return d.name; });
结果如图5所示。因为通过北京的高铁线路最多,连线在北京的圆圈处最密集,就好像将不少条绳子“捆”在这里同样。
图5
当节点和连线变得不少很复杂时,就变成了本文开头处的例子。就像本文开头说的同样,那个例子完成度过高,根本不能提供参考。但愿本文的例子能给你们做为捆图的参考范例。
完整代码请点击如下连接查看:
http://www.ourd3js.com/demo/G-2.0/bundle.html
谢谢阅读。