捆图(Bundle)是 D3 中比较奇特的一个布局,只有两个函数,而且需要与其它布局配合使用。本文讲述捆图的制作方法。

有关捆图的例子极少,很容易找到的是:http://bl.ocks.org/mbostock/1044242

但是此例有些复杂,不太容易弄懂,而且也没有相应的说明文字(D3官网上的例大多没有相应文字解说),根本无法作为参考。本文提供一个简单的例子,讲解捆图到底是什么,应该怎么做。

捆图只有两个函数:

d3.layout.bundle():创建一个捆图布局。

bundle(links):根据数组links的source和target,计算路径。

捆图的布局之所以函数少,是因为它常与其它层级布局一起使用。所谓层级布局,是指采用嵌套结构(父子节点关系)来描述节点信息,根据层级布局扩展出来的布局即:集群图、打包图、分区图、树状图、矩阵树图。最常见的是与集群图一起使用,使用集群图布局计算节点的位置,再用捆图布局计算连线路径。也就是说,捆图布局只干一件事:

计算连线的路径。


下面举一个例子。中国的高铁已经在很多城市开通,如北京到上海,北京到桂林等。现要制作一图来表示经过哪一座城市的高铁最密集。

1. 数据

有9座城市:

  1. var cities = {
  2. name: "",
  3. children:[
  4. {name: "北京"},{name: "上海"},{name: "杭州"},
  5. {name: "广州"},{name: "桂林"},{name: "昆明"},
  6. {name: "成都"},{name: "西安"},{name: "太原"}
  7. ]
  8. };

这9座城市所属的节点有一个公共的父节点,父节点名称为空,稍后并不绘制此父节点。另外还有连接各城市高铁的数组,如下:

  1. var railway = [
  2. {source: "北京", target: "上海"},
  3. {source: "北京", target: "广州"},
  4. {source: "北京", target: "杭州"},
  5. {source: "北京", target: "西安"},
  6. {source: "北京", target: "成都"},
  7. {source: "北京", target: "太原"},
  8. {source: "北京", target: "桂林"},
  9. {source: "北京", target: "昆明"},
  10. {source: "北京", target: "成都"},
  11. {source: "上海", target: "杭州"},
  12. {source: "昆明", target: "成都"},
  13. {source: "西安", target: "太原"}
  14. ]; //(该数据为假设,并没有经过调查)

source和target分别表示高铁的两端。

2. 布局(数据转换)

前面提到,捆图布局要和其他布局联合使用,在这里与集群图布局联合。分别创建一个集群图布局和一个捆图布局。

  1. var cluster = d3.layout.cluster()
  2. .size([360, width/2 - 50])
  3. .separation(function(a, b) {
  4. return (a.parent == b.parent ? 1 : 2) / a.depth;
  5. });
  6.  
  7. var bundle = d3.layout.bundle();

从集群图布局的参数可以看出,接下来节点将要分布呈圆形。捆图布局没有参数可以设置,只创建即可,保存在变量bundle中。

先使用集群图布局计算节点:

  1. var nodes = cluster.nodes(cities);
  2. console.log(nodes);

将计算后的节点数组保存在nodes中,并输出该数组,结果如图1所示,第一个节点有9个子节点,其他的节点都有且只有一个父节点,没有子节点。这是接下来捆图要使用的节点数组,但是却是用集群图布局计算而来的。

图1

下一步是重点,要使用数组railway。由于railway中存储的source和target都只有城市名称,因此先要将其对应成nodes中的节点对象。写一个函数,按城市名将railway中的source和target替换成节点对象。

  1. function map( nodes, links ){
  2. var hash = [];
  3. for(var i = 0; i < nodes.length; i++){
  4. hash[nodes[i].name] = nodes[i];
  5. }
  6. var resultLinks = [];
  7. for(var i = 0; i < links.length; i++){
  8. resultLinks.push({ source: hash[ links[i].source ],
  9. target: hash[ links[i].target ]
  10. });
  11. }
  12. return resultLinks;
  13. }

使用该函数返回的数组,即可作为捆图布局bundle的参数使用:

  1. var oLinks = map(nodes, railway);
  2. console.log(oLinks);
  3.  
  4. var links = bundle(oLinks);
  5. console.log(links);

map()返回的结果保存在oLinks,bundle()返回的结果保存在links中,输出结果分别如图2和图3所示,即捆图布局的连线数组转换前后的变换。

图2

如图2,转换前,连线数组的每一项都只有两个变量:source和target,内容是节点对象。对于第一个连线,是从“北京”到“上海”。如图3,转换之后,source和target不见了,取而代之的是0、1、2,变成了一个数组。很明显,该数组的第一项和source的内容一样,最后一项和target的内容一样,但中间多出了一项(图3的红框内)。多出的这一项是根据source和target公共的父节点计算出来的。于是,该数组表示了一条路径。

其实,捆图布局根据各连线的source和target为我们计算了一条条连线路径,我们可以把捆图布局的作用简单地解释为:使用这些路径绘制的线条能更美观地表示“经过哪座城市的高铁最多”

图3

3. 绘制

经过捆图布局转换后的数据很适合用d3.svg.line()和d3.svg.line.radial()来绘制,前者是线段生成器,后者是放射式线段生成器。在line.interpolate()所预定义的插值模式中,有一种就叫做bundle,正是为捆图准备的。

由于本例中用集群图布局计算节点数组使用的使用圆形的,因此要用放射式的线段生成器。先创建一个:

  1. var line = d3.svg.line.radial()
  2. .interpolate("bundle")
  3. .tension(.85)
  4. .radius(function(d) { return d.y; })
  5. .angle(function(d) { return d.x / 180 * Math.PI; });

此线段生成器是用来获取连线路径的。接下来,添加一个分组元素<g>,用来放所有与捆图相关的元素。

  1. gBundle = svg.append("g")
  2. .attr("transform",
  3. "translate(" + (width/2) + "," + (height/2) + ")");
  4.  
  5. var color = d3.scale.category20c(); //颜色比例尺

然后,先在gBundle中添加连线路径:

  1. var link = gBundle.selectAll(".link")
  2. .data(links)
  3. .enter()
  4. .append("path")
  5. .attr("class", "link")
  6. .attr("d", line); //使用线段生成器

在该连线的样式中,添加透明度能够在连线汇聚处更能显示出“捆”的效果。例如样式设定为:

  1. .link {
  2. fill: none;
  3. stroke: black;
  4. stroke-opacity: .5;
  5. stroke-width: 8px;
  6. }

连线的绘制结果如图4所示。

图4

最后,向图中添加节点。节点用一个圈,里面写上城市的名称来表示。首先,绑定节点数组,并添加与之对应的<g>元素。

  1. var node = gBundle.selectAll(".node")
  2. .data( nodes.filter(function(d) { return !d.children; }) )
  3. .enter()
  4. .append("g")
  5. .attr("class", "node")
  6. .attr("transform", function(d) {
  7. return "rotate(" + (d.x- 90) + ")translate("
  8. + d.y + ")" + "rotate("+ (90 - d.x) +")";
  9. });

要注意,被绑定的数组是经过过滤后的nodes数组。此处的filter是JavaScript数组对象自身的函数,粗体字的部分表示只绑定没有子节点的节点,也就是说9座城市的公共父节点不绘制。然后只要在该分组元素<g>中分别加入<circle>和<text>即可。

  1. node.append("circle")
  2. .attr("r", 20)
  3. .style("fill",function(d,i){ return color(i); });
  4.  
  5. node.append("text")
  6. .attr("dy",".2em")
  7. .style("text-anchor", "middle")
  8. .text(function(d) { return d.name; });

结果如图5所示。由于经过北京的高铁线路最多,连线在北京的圆圈处最密集,就好像将很多条绳子“捆”在这里一样。

 

图5

当节点和连线变得很多很复杂时,就变成了本文开头处的例子。就像本文开头说的一样,那个例子完成度太高,根本不能提供参考。希望本文的例子能给大家作为捆图的参考范例。

完整代码请点击以下链接查看:

http://www.ourd3js.com/demo/G-2.0/bundle.html

谢谢阅读。

文档信息

【 D3.js 高级系列 — 2.0 】 捆图的更多相关文章

  1. 【 D3.js 高级系列 — 2.0 】 机械图 + 人物关系图

    机械图(力路线图)结合老百姓的关系图中的生活,这是更有趣. 本文将以此为证据,所列的如何图插入外部的图像和文字的力学. 在[第 9.2 章]中制作了一个最简单的力学图.其后有非常多朋友有疑问,基本的问 ...

  2. 【 D3.js 高级系列 — 10.0 】 思维导图

    思维导图的节点具有层级关系和隶属关系,很像枝叶从树干伸展开来的形状.在前面讲解布局的时候,提到有五个布局是由层级布局扩展来的,其中的树状图(tree layout)和集群图(cluster layou ...

  3. 【 D3.js 高级系列 — 3.0 】 堆栈图

    堆栈图布局(Stack Layout)能够计算二维数组每一数据层的基线,以方便将各数据层叠加起来.本文讲解堆栈图的制作方法. 先说说什么是堆栈图. 例如,有如下情况: 某公司,销售三种产品:个人电脑. ...

  4. 【 D3.js 高级系列 — 4.0 】 矩阵树图

    矩阵树图(Treemap),也是层级布局的扩展,根据数据将区域划分为矩形的集合.矩形的大小和颜色,都是数据的反映.许多门户网站都能见到类似图1,将照片以不同大小的矩形排列的情形,这正是矩阵树图的应用. ...

  5. 【 D3.js 高级系列 — 1.0 】 文本的换行

    在 SVG 中添加文本是使用 text 元素.但是,这个元素不能够自动换行,超出的部分就显示不出来了,怎么办呢? 高级系列开篇前言 从今天开始写高级系列教程.还是那句话,由于本人实力有限,不一定保证入 ...

  6. 【 D3.js 高级系列 — 9.0 】 交互式提示框

    一般来说,图表中不宜存在过多文字.但是,有时需要一些文字来描述某些图形元素.那么,可以实现一种交互:当用户鼠标滑到某图形元素时,出现一个提示框,里面写有描述文字.这是一种简单.普遍的交互式,几乎适用于 ...

  7. 【 D3.js 高级系列 — 6.0 】 值域和颜色

    在[入门 - 第 10 章]作了一张中国地图,其中各省份的颜色值都是随意赋值的.如果要将一些值反映在地图上,可以利用颜色的变化来表示值的变化. 1. 思路 例如,有值域的范围为: [10, 500] ...

  8. 【 D3.js 高级系列 — 8.0 】 标线

    有时候,需要在地图上绘制连线,表示"从某处到某处"的意思,这种时候在地图上绘制的连线,称为"标线". 1. 标线是什么 标线,是指地图上需要两个坐标以上才能表示 ...

  9. 【 D3.js 高级系列 — 7.0 】 标注地点

    有时需要告诉用户地图上的一些目标,如果该目标是只需要一个坐标就能表示的,称其为"标注". 1. 标注是什么 标注,是指地图上只需要一个坐标即可表示的元素.例如,在经纬度(116, ...

随机推荐

  1. 1090. Highest Price in Supply Chain (25)

    时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue A supply chain is a network of r ...

  2. Eclipse开发Android报错Jar mismatch! Fix your dependencies

    常常打开工程,发现项目并没有错,但是会有一个红X,然后就生成不了. 发现两个版本不同的android-support-v4.jar在使用 打开window-show views-problems Ja ...

  3. 如何将无线路由器作为交换机,将光猫(路由器A)分出来的一条网线接到自家另一台路由器B上,最大化利用网络资源

    从隔壁邻居只接了一条网线过来,由于无线网络的距离有限,不能覆盖到家里任何角落,然而,我又想家里一台台式电脑和无线设备都能够连接wifi进行上网。 摸索了一个上午,知道将家里的无线路由器B当作一个无线A ...

  4. Mysql 如何删除数据表中的重复数据!

    1.使用distinct查询所有不重复的记录 2.创建数据表相同结构的临时表,将第一步的数据复制进去 create temporary table if not exists student_temp ...

  5. 【面试题】Https

    https原理, 我很难一下子记住,   https=http+ssl 先说http,  基于tcp/IP协议 传输,有三次握手 http://blog.csdn*net/xubo_zhang/art ...

  6. ExtJs 4.2 treePanel 点击树节点 传送参数到后台(多个参数)

    //***********************************************左边树开始********************************************** ...

  7. Xcode文档下载与安装路径

    https://developer.apple.com/library/downloads/docset-index.dvtdownloadableindex ~/Library/Developer/ ...

  8. iOS 跳转到系统的设置界面-b

    在项目中,我们经常会碰到使用位置的需求.当用户设置app不允许使用位置的时候,最好的用户体验就是直接调转到系统的位置设置界面,进行设置. 本人已经测试,在5c iOS8.3系统 和 5s iOS7.1 ...

  9. SQL一列多行字符串分组合并

    最近工作遇到如下数据:需要合并后只剩下两行的数据,普通的group by 是不能实现的.(如图) 利用如下SQL代码,即可实现需求(如图): 利用 stuff 函数实现分拆合并操作 select Te ...

  10. poj 2553 The Bottom of a Graph(强连通分量+缩点)

    题目地址:http://poj.org/problem?id=2553 The Bottom of a Graph Time Limit: 3000MS   Memory Limit: 65536K ...