| 了解用于绘制各种排列的组件的图形计算
在这个由两部分组成的文章系列中,将学习如何结合使用可缩放矢量图形 (SVG)
和开源 D3 JavaScript 库创建数据可视化。形状、颜色和布局可能对从业务角度理解海量数据具有很大帮助。本文将演示使用
D3 的和您自己的计算结果,通过在画布上排列图形组件来表示数据的各种方式。
您将学习如何使用 D3 强大的图形计算在 SVG 画布上放置组件,以及如何将自己的图形操作与
D3 的布局相结合。我还将探讨如何使用 JavaScript 对象表示法 (JSON) 作为一种可用于可视化的数据格式。本文最后将展示如何使用布局组合在单个
SVG 画布上排列各种图形组件。
本文中的概念和示例以 第 1 部分中的概念和示例为基础,所以在继续阅读本文之前,请务必阅读或复习第
1 部分。阅读第 2 部分时,您可能会发现,在浏览器中并列打开两篇文章会很有用,这样您可参考第 1 部分中的图像。参见下载,获取第
2 部分的样例代码。
D3 的图形布局简介
我以 第 1 部分中学到的 D3 功能为基础开始构建。
回想一下,第 1 部分中的图 1 和图 2 仅在圆圈排列上存在区别,这些图的
JavaScript 代码中的 transform属性值(如第 1 部分中的清单 2 和清单 4 所示)会计算每个圆圈中心的相对位置。
在 D3 的术语中,确定各个组件相对位置的图形计算被称为 布局。D3 提供了多种强大且可重用的布局。第
1 部分中的圆弧和弦的排列就是其中之一。知道如何单独使用 D3 的布局并能与自己的图形计算相结合,会很方便。
D3 包布局 (Pack layout)(如图 1 所示)是一个较大圆圈内的一个圆圈包。(与
第 1 部分中一样,这些圆圈描绘了第 1 周的页面查看数据。)在本文中,我将演示如何结合使用包布局和我自己的一些计算。

图 1. 在一个较大圆圈中显示圆圈的包布局
图 1中的圆圈与第 1 部分中生成的圆圈相同,但采用不同的排列方式。图形计算所导致的区别由
D3 的包布局完成。
没有外部的圆圈,图 1中的布局类似于图 2:

图 2. 没有外部圆圈的包布局
第 1 部分的图 1 使用了一种基于我自己的简单图形计算的简单布局,而这里的
图 2使用了 D3 的包布局。如果将两个布局所完成的图形计算相结合,就可以直观地表示几周内的流行度数据,如图
3 所示:

图 3. 一个图形中包含三周的流行度数据
也可以将 图 3与 第 1 部分的图 3 中的嵌套圆圈排列相结合,在一个画布中一起描绘几周的流行度数据和用户互动数据,如图
4 所示:

图 4. 组合多个布局来显示几周的流行度数据和用户互动数据
现在我将介绍显示图 1 到图 4 的 SVG 和基于 D3 的 JavaScript
代码。
绘制一个圆圈包
清单 1 给出了绘制 图 1中所示的圆圈包的 SVG 代码:
清单 1. 绘制图 1 中的圆圈包的 SVG 代码
<?xml version=";1.0"; standalone=";no";?> <!DOCTYPE svg PUBLIC ";-//W3C//DTD SVG 1.1//EN"; ";http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>
<svg xmlns=";http://www.w3.org/2000/svg";
version=";1.1";
width=";1000"; height=";1000";>
<g>
<circle
r=";250";
style=";fill: #da70d6;";
transform=";translate(250,250)";>
</circle>
<text
x=";-10";
fill=";grey";
transform=";translate(250,250)";>
26733
</text>
</g>
<g>
<circle
r=";91.28916405756645";
style=";fill: #0000ff;";
transform=
";translate(172.869706621408,159.71405581800542)";>
</circle>
<text
x=";-10"; fill=";grey";
transform=
";translate(172.869706621408,159.71405581800542)";>
7057
</text></g>
<g>
<circle
r=";94.00415374552455";
style=";fill: #ffd700;";
transform=
";translate(120.90301328980084,337.57095449403647)";>
</circle>
<text
x=";-10";
fill=";grey";
transform=
";translate(120.90301328980084,337.57095449403647)";>
7483
</text>
</g>
<g>
<circle
r=";66.53756320431968";
style=";fill: #008000;";
transform=
";translate(271.77788076862765,282.7036851574789)";>
</circle>
<text
x=";-10";
fill=";grey";
transform=
";translate(271.77788076862765,282.7036851574789)";>
3749
</text>
</g>
<g>
<circle
r=";67.39284824138821";
style=";fill: #ff0000;";
transform=
";translate(405.70829221433553,282.7036851574789)";>
</circle>
<text
x=";-10";
fill=";grey";
transform=
";translate(405.70829221433553,282.7036851574789)";>
3846
</text>
</g>
<g>
<circle
r=";73.68747158608183";
style=";fill: #800000;";
transform=
";translate(337.8448727920879,159.01774033373852)";>
</circle>
<text
x=";-10";
fill=";grey";
transform=
";translate(337.8448727920879,159.01774033373852)";>
4598
</text>
</g>
</svg> |
可以看到,清单 1中的根 <svg>标记包含 6 个 <g>子标记,每个子标记有一对
<circle>和 <text>标记。第一个 <g>标记绘制了较大的外部圆圈,包含这个圆圈只是为了让您了解
D3 的包布局。在样例应用程序中并不需要外部圆圈,所以我通过将它的颜色从 da70d6(淡紫色)更改为fffff(白色)而让它不可见,结果得到了
图 2而不是 图 1。剩余 5 个 <circle>标记中的每一个标记绘制了 图 2中 5
个圆圈中的一个圆圈,每个圆圈代表一种社会资源的流行度。
关于这 5 个 <circle>标记,有两点值得注意:
每个 <circle>标记的 r属性(每个圆圈的半径)表示该圆圈的社会资源的流行度(资源越流行,圆圈越大)。
transform属性的 translate(x,y)值将每个圆圈放在这个包中。
r、x和 y值完全决定了每个圆圈的大小和位置。D3 的包布局为您计算了这三个属性。清单
2 给出了使用 D3 执行图形计算并生成 清单 1中的 SVG 的 JavaScript 代码:
清单 2. 生成圆圈的包布局的基于 D3 的 JavaScript 代码
<!DOCTYPE html> <meta charset="utf-8"> <body> <!-- <script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script> --> <script src="d3.v3.js"></script> <!--Save d3.v3.js in the same folder--> <script>
var views = [
[7057, 7483, 3749, 3846, 4598],
[ 2371, 7397, 4589, 2861, 8249],
[ 5972, 5672, 9152, 9725, 8983],
[ 9763, 8462, 9782, 1953, 5182],
[ 9567, 1571, 2895, 2783, 1874],
[ 2371, 7397, 4589, 2861, 8249]
];
var width = 1000, height = 1000;
var colors = [
"white",//"orchid", if you
want to see the outer bigger circle.
"blue",
"gold",
"green",
"red",
"maroon"
];
var week = 0;
//Step 1: Make JSON representation of popularity
data.
var viewsJSON = getJSONForOneWeekOfPopularityData(0);
//Step 2: Pass on JSON to pack layout and get
graphical
//calculations in return.
var pack = d3.layout.pack()
.size([500, 500])
.value(function(d) { return d.popularity; });
var packCalculations = pack.nodes(viewsJSON);
//Step 3: Use calculations to generate SVG.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
.attr("r", function(d){return d.r})
.style("fill", function(d, i){return
colors[i%6];})
.attr("transform", function(d,i){return
"translate(" + d.x + "," +
d.y + ")"});
g.append("text")
.attr("x", -10)
.attr("fill", "grey")
.text(function(d){return d.value})
.attr("transform", function(d,i){return
"translate(" + d.x + "," +
d.y + ")"});
function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString =
"{ \"name\": \"week"
+ week + "\", \"children\":
[" ;
for (var count=0;
count<5;
count++ ){
viewsJSONString +=
"{ \"name\": \"" + colors[count+1]
+ " \", \"popularity\": "
+ views[week][count] + " } ";
if (count<4) viewsJSONString += ",";
}
viewsJSONString += "]}"
return JSON.parse(viewsJSONString);
}
</script>
</body> |
可在 清单 2中看到三个步骤:
创建流行度的一种是用 JSON 表示,D3 的包布局可使用它执行图形计算。
将该 JSON 对象传递到包布局并返回图形计算结果。
使用计算结果生成所需的 SVG 代码。
下面介绍以下每个步骤。
第 1 步:使用 JSON
包布局是一种分层布局:它以在较大的(父)圆圈内包装较小的(子)圆圈形式表示分层数据。(D3
没有对分层级别的数量设置限制;您可以有父圆圈、祖父圆圈、曾祖父圆圈等。)示例的流行度和用户交互数据只有两个级别:父级别是第几周,子级别是每一周的流行度和用户互动数据。
包布局不直接处理 JavaScript 数组。它接受数据的一种 JSON
表示作为输入。JSON 数据格式可以很好地表示分层数据,而且受现代浏览器的直接支持。
清单 3 显示了流行度数据的一种 JSON 表示与相同数据的一种数组表示:
清单 3. 三周的流行度数据的数组和 JSON 表示
//array representation var views = [ [7057, 7483, 3749, 3846, 4598], [ 2371, 7397, 4589, 2861, 8249], [ 5972, 5672, 9152, 9725, 8983] ];
//JSON representation
var viewsJSON =
{
"name": "PopularityData",
"children": [
{
"name": "week1",
"children": [
{"name": "blue", "popularity":
7057},
{"name": "gold", "popularity":
7483},
{"name": "green", "popularity":
3749},
{"name": "red", "popularity":
3846},
{"name": "maroon", "popularity":
4598}
]
},
{
"name": "week2",
"children": [
{"name": "blue", "popularity":
2371},
{"name": "gold", "popularity":
7397},
{"name": "green", "popularity":
4589},
{"name": "red", "popularity":
2861},
{"name": "maroon", "popularity":
8249}
]
}
{
"name": "week3",
"children": [
{"name": "blue", "popularity":
5972},
{"name": "gold", "popularity":
5672},
{"name": "green", "popularity":
9152},
{"name": "red", "popularity":
9725},
{"name": "maroon", "popularity":
8983}
]
}
]}; |
请注意,JSON 表示使用名称 - 值对。整个数据对象的名称是 PopularityData。PopularityData有三个子对象,它们分别名为Week1、Week2和
Week3。而且每个子对象有 5 个根据社交资源命名的子对象。(社交资源的名称是颜色名称,回想 第 1
部分您就会知道。)所有这些对象都被称为 节点;没有子节点的节点(也就是树分层结构的末端)被称为 叶节点(leaf
node)。流行度数据是每个叶节点的popularity属性的值。
清单 4 展示了如何为一周的流行度数据创建一个 JSON 对象:
清单 4. JSON 格式的一周的流行度数据
var viewsJSON = {
"name": "week1",
"children": [
{"name": "blue", "popularity":
7057},
{"name": "gold", "popularity":
7483},
{"name": "green", "popularity":
3749},
{"name": "red", "popularity":
3846},
{"name": "maroon", "popularity":
4598}
]
}; |
清单 5 展示了如何向 清单 3的 JSON 对象中的流行度数据添加用户互动:
清单 5. JSON 格式的三周流行度数据和用户互动数据
var viewsJSON = { "name": "PopularityData", "children": [ { "name": "week1", "children": [ {"name": "blue", "popularity": 7057, "user-interaction": 2052}, {"name": "gold", "popularity": 7483}, "user-interaction": 2089}, {"name": "green", "popularity": 3749}, "user-interaction": 1586}, {"name": "red", "popularity": 3846}, "user-interaction": 1426}, {"name": "maroon", "popularity": 4598} "user-interaction": 2632}, ] }, { "name": "week2", "children": [ {"name": "blue", "popularity": 2371}, "user-interaction": 2071}, {"name": "gold", "popularity": 7397}, "user-interaction": 2190}, {"name": "green", "popularity": 4589}, "user-interaction": 7214}, {"name": "red", "popularity": 2861}, "user-interaction": 3782}, {"name": "maroon", "popularity": 8249} "user-interaction": 2721}, ] } { "name": "week3", "children": [ {"name": "blue", "popularity": 5972}, "user-interaction": 3076}, {"name": "gold", "popularity": 5672}, "user-interaction": 3190}, {"name": "green", "popularity": 9152}, "user-interaction": 4532}, {"name": "red", "popularity": 9725}, "user-interaction": 3825}, {"name": "maroon", "popularity": 8983} "user-interaction": 4831}, ] } ]}; |
从 清单 5可以看到,要添加用户互动数据,只需向每个叶节点添加一个名为
user-interaction的属性。
可以在 JavaScript 中以多种方式使用 JSON 数据。一种方式是将它直接包含在
JavaScript 文件中。另一种方式是通过 <script>标记将 JSON 文件(具有
.json 扩展名)链接到您的页面。或者可以拥有另一种格式的数据(比如 JavaScript 数组),它可在运行时动态地转换为
JSON。
将外部 JSON 文件与 JavaScript 页面相链接可能产生安全问题。相比较而言,在服务器端创建
JavaScript 文件时,数组提供了一种轻松而又明确的方式来存储数据。因此,我选择第 3 个选项:将数据存储在
JavaScript 数组中,并在同一个 JavaScript 文件中使用 “数组到 JSON” 转换逻辑。使用此选项,您可在需要数据的
JSON 表示时调用 “数组到 JSON” 转换逻辑。
您可能已经猜到,要绘制 图 1中的布局,需要一周流行度数据的 清单 4所示格式的
JSON 表示。名为getJSONForOneWeekOfPopularityData()的简单 JavaScript
函数(如清单 6 所示)以这种格式生成了一个 JSON 对象:
清单 6. 从 JavaScript 数组生成一个 JSON 对象
function getJSONForOneWeekOfPopularityData(week){
//Step 1: Author a string to JSON format.
var viewsJSONString =
"{ \"name\": \"week"
+ week + "\", \"children\":
[" ;
for (var count=0;
count<5;
count++ ){
viewsJSONString +=
"{ \"name\": \"" + colors[count+1]
+ " \", \"popularity\": "
+ views[week][count] + " } ";
if (count<4) viewsJSONString += ",";
}
viewsJSONString += "]}"
//Step 2: Parse the string to create a JSON
object.
return JSON.parse(viewsJSONString);
}
</script>
</body> |
从 清单 6中可以看到,生成一个 JSON 对象是一个简单的两步转换过程。首先,创建一个与您想要生成的
JSON 格式完全一致的 JavaScriptString表示。在创作字符串表示时,应该使用社交资源的实际名称,以便让它看起来类似于
清单 4。在第二步中,使用 JSON.parse()方法将该字符串转换为一个 JSON 对象。大多数现代浏览器都支持与
JSON 相关的功能,所以您可直接调用 JSON.parse()函数。我使用 Chrome Version
26.0.1410.64 m 试验过本文的代码。如果您的应用程序以旧版浏览器为目标,那么您可以通过一个
<script>标记在页面中包含一个 https://github.com/douglascrockford/JSON-js/blob/master/json2.js
库(它实现了 JSON 功能)。
第 2 步:使用包布局执行图形计算
现在有了合适的 JSON 对象,您可以将它传递到 D3 的包布局来执行所需的图形计算。清单
7(为了简便起见,它重复了 清单 2中的第 2 步)表明从包布局抓取图形计算结果有多么简单:
清单 7. 使用 D3 在一个圆圈包中为一周的流行度数据执行图形计算
//Step 2: Pass on JSON to pack layout and get graphical //calculations in return. var pack = d3.layout.pack() .size([500, 500]) .value(function(d) { return d.popularity; }); var packCalculations = pack.nodes(viewsJSON); |
您只需创建一个 d3.layout.pack()对象,然后调用 size()函数设置包布局的大小。size()函数接受长度和宽度作为参数,这设置了用来绘制圆圈包的尺寸。D3
在内部依据布局的大小而缩放每个圆圈的大小。
接下来,要为包布局对象调用 value()函数,并写入您自己的函数作为值。这个函数为每个
JSON 节点返回一个值。在它执行图形计算时,D3 在内部调用此函数,对每个节点调用一次,将该节点被作为
d参数传递。在 清单 7中可以看到,我返回 d.popularity作为一个节点的值,因此告诉 D3 执行基于流行度数据的图形计算。结果,包中的每个圆圈依据节点的流行度数据而调整大小。
包布局对象现在已准备好接受 JSON 对象并执行计算。接下来,我调用布局对象的
nodes()函数,将 JSON 对象传递给该函数调用。
nodes()函数执行所有图形计算,以便将流行度圆圈放在包布局中。计算的结果为节点数组格式,每个节点表示大小并绘制一个圆圈的位置。整个节点数组包含绘制整个流行度圆圈包的所有数据。
简单来讲:您将 JavaScript 数组转换为 JSON 并将该 JSON
传递给 D3 的包布局。包布局执行图形计算并将计算结果放在另一个数组中(这一次是一个节点数组),在 清单
7中,该数组是一个名为 packCalculations的数组。
对于 packCalculations数组中的每个节点,D3 提供了多个属性,包括
r、x和 y。现在您将看到如何使用这些属性创作 清单 1中的 SVG 代码。
第 3 步:使用包布局计算结果来生成 SVG
第 2 步完成后,packCalculations数组拥有绘制圆圈包所需的所有图形数据。第
3 步(如清单 8 中所述)使用计算结果生成 SVG:
清单 8. 使用图形计算结果绘制圆圈
//Step 3: Use calculations to generate SVG. var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
.attr("r", function(d){return d.r})
.style("fill", function(d, i){return
colors[i%6];})
.attr("transform", function(d,i)
{return "translate(" + d.x+ ","
+ d.y+ ")"});
g.append("text")
.attr("x", -10)
.attr("fill", "grey")
.text(function(d){return d.value})
.attr("transform", function(d,i)
{return "translate(" + d.x + ","
+ d.y + ")"}); |
您在 清单 8中看到的大部分代码都来自 第 1 部分。惟一的区别是,这一次数据来自
packCalculations数组。不要担忧如何获取数组中的每个节点并逐个绘制每个圆圈。可将 packCalculations数组传递给富有魔力的
.data(packCalculations)D3 函数,它会处理大部分工作。
请注意,清单 8中使用了 d.r、d.x和 d.y属性。要调整和放置每个圆圈,需要使用这些
r、x和 y属性。d.r属性用于创作圆圈的 r属性(半径),而 d.x和 d.y属性用于创作放置每个圆圈的
transform属性的值。
如果运行 清单 2中的 JavaScript 代码,就会看到 图 2中所示的图像。
将外部计算与包布局相结合
下面介绍了如何将包布局与您自己的图形计算相结合,以绘制 图 3中的图形。您可以看到,图
3对圆圈的包布局使用了三次(一次针对一周),得到了三组圆圈(每组 5 个),一组圆圈表示一周的流行度数据。
清单 9 显示了 图 3的 SVG 代码:
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="1000" height="1000">
<g transform="translate(0,143)">
<text x="60">Week 1</text>
<!-- resource-popularity tags omitted.-->
</g>
<g transform="translate(320,143)">
<text x="60">Week 2</text>
<!-- resource-popularity tags omitted.-->
</g>
<g transform="translate(640,143)">
<text x="60">Week 3</text>
<!-- resource-popularity tags omitted.-->
</g>
</svg> |
清单 9显示了根 <svg>标记的三个 <g>子标记。我将这三个
<g>标记称为 周包装器标记,因为每一个标记表示一周的数据。
每个周包装器标记有 6 个 <g>子标记(一个较大的圆圈加上 5 个流行度圆圈);我将它们称为
资源流行度标记。我省略了资源流行度标记,因为在 清单 1中,您已知道它们是什么样子。
现在看看周包装器(week-wrapper)标记的 transform属性。这些值将第一个圆圈包放在点
0, 143;将第二个放在 320, 143;将第三个放在 640, 143。这种放置是我自己的简单图形计算的结果,它包装了所有圆圈包。下面将介绍我是如何做的。
生成清单 9 中的 SVG 代码的 JavaScript
为了生成 清单 9中的 SVG,清单 10 中的 JavaScript
代码将 D3 的包布局与我自己的一些图形计算相结合:
清单 10. 将包布局与更多图形计算相结合的 JavaScript
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
-->
<script src="d3.v3.js"></script>
<script>
var views = [
[7057, 7483, 3749, 3846, 4598],
[ 2371, 7397, 4589, 2861, 8249],
[ 5972, 5672, 9152, 9725, 8983],
];
var colors = [
"white",
"blue",
"gold",
"green",
"red",
"maroon" ];
var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
for (var count=0; count<3; count++){
var pack = d3.layout.pack()
.size([widthOfOnePack, heightOfOnePack])
.value(function(d) {
return d.popularity; });
packCalculations[count] =
pack.nodes(
getJSONForOneWeekOfPopularityData(count)
);
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g1 = svg.selectAll("g")
.data(packCalculations).enter().append("g")
.attr("transform", function(d,i){
return "translate(" +
(widthOfOnePack + spaceBetweenPacks )*i +
"," + height / 7 + ")" });
g1.append("text")
.text(function(d, i){return "Week "
+ (i+1)})
.attr("x", 60);
var g2 = g1.selectAll("g")
.data(function(d){return d;})
.enter()
.append("g");
g2.append("circle")
.attr("r", function(d, i){return d.r})
.style("fill", function(d, i){return
colors[i%6];})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
g2.append("text")
.attr("x", -10)
.attr("fill", "grey")
.text(function(d, i){return d.value})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString =
"{ \"name\": \"week"
+ week + "\", \"children\":
[" ;
for (var count=0;
count<5;
count++ ){
viewsJSONString +=
"{ \"name\": \"" + colors[count+1]
+ " \", \"popularity\": "
+ views[week][count] + " } ";
if (count<4) viewsJSONString += ",";
}
viewsJSONString += "]}"
return JSON.parse(viewsJSONString);
}
</script>
</body> |
我突出显示了 清单 10中的 packCalculations和 transform增强,以帮助您将此代码与
清单 2进行对比。
在 清单 2中,我将 packCalculations声明为一个简单变量,而在
清单 10中,我将它声明为一个数组。这是因为 清单 10处理三个圆圈包,每个包都拥有自己的图形计算。
声明 packCalculations数组后,我运行了一个简单循环,在其中使用
D3 的包布局执行在包中放置每个圆圈的计算。D3 返回每周的一个节点数组。我将 D3 的每组计算结果存储在
packCalculations数组中。所以 packCalculations现在是一个节点数组的数组。
在这里应当注意一个有趣的地方。D3 的所有计算都是为一个包执行的,与其他包独立,就像只需要处理一个包。这些计算没有考虑到其他包可能位于同一个
SVG 画布上的情况。但是当 transform属性转换一个周包装器标记的位置时,周包装器中的所有对象都是相对于包装器而进行放置的,所以您可以轻松地放置多个圆圈包。
现在看看计算周包装器标记的 transform属性值的计算(已在 清单
10中以粗体形式显示)。我将一个包的宽度加上一些空白再乘以索引 i。执行此调整后,放置第一个周包装器 (i=0)
时不需要执行任何水平位移。第二个周包装器 (i=1) 水平位移第一个周包装器的宽度加上少量空白。类似地,第三个包装器移位前两个周包装器的宽度加上额外的空白。
清单 10的剩余部分与 清单 2相同,您将在其中绘制每周的各个圆圈。显然,您可以:
通过设计 <g>标记的父子排列来设置布局。
使用每个 <g>标记的 transform属性值将各个组件或完整的组件布局放在想要放置的位置。
嵌套圆圈的一种包布局
下面介绍您可以对 清单 10执行的一些有趣增强,可以用它们在 图 3的布局中添加用户互动数据,从而形成
图 4中所示的嵌套圆圈包。
清单 11 给出了绘制 图 4的 JavaScript:
清单 11. 绘制图 4 的 JavaScript
<!DOCTYPE html> <meta charset="utf-8"> <body> <!-- <script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script> --> <script src="d3.v3.js"></script>
<script>
var viewsAndInteraction = [
[ [7057, 2052], [7483, 2089], [3749, 1586], [3846,
1426], [4598, 2632] ],
[ [5972, 2071], [5672, 2190], [9152, 7214], [9725,
3782], [8983, 2721] ],
[ [8749, 3076], [4768, 3190], [6738, 4532], [9546,
3825], [6983, 4831] ]
];
var viewColors = [ "white", "blue",
"gold", "green", "red",
"maroon" ];
var interactionColors = [ "white", "lightblue",
"yellow", "lightgreen",
"lightcoral", "indianred"
];
var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
for (var count=0;
count<3;
count++ ){
var pack = d3.layout.pack()
.size([widthOfOnePack, heightOfOnePack])
.value(function(d) { return d.size; });
packCalculations[count] = pack.nodes(
getJSONForOneWeekOfPopularityAndInteractionData(
count));
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g1 = svg.selectAll("g")
.data(packCalculations).enter().append("g")
.attr("transform", function(d,i){
return "translate(" +
(widthOfOnePack + spaceBetweenPacks)*i + ","
+
height / 7 + ")" }
);
g1.append("text")
.text(function(d, i){return "Week "
+ (i+1)})
.attr("x", 60);
var g2 = g1.selectAll("g")
.data(function(d){return d;})
.enter()
.append("g");
g2.append("circle")
.attr("r", function(d, i){return d.r})
.style("fill", function(d, i){return
viewColors[i%6];})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
g2.append("circle")
.attr("r", function(d, i){
return ((d.interaction*d.r)/d.value);})
.style("fill", function(d, i){return
interactionColors[i%6];})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
g2.append("text")
.attr("x", -15)
.attr("y", 35)
.attr("fill", "white")
.text(function(d, i){return d.value})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
g2.append("text")
.attr("x", -15)
.attr("y", 5)
//.attr("fill", "grey")
.text(function(d, i){return d.interaction})
.attr("transform", function(d,i){
return "translate(" + d.x + ","
+ d.y + ")"});
function getJSONForOneWeekOfPopularityAndInteractionData(week){
var viewsAndInteractionJSON =
"{ \"name\": \"week"
+ week + "\", \"children\":
[" ;
for (var count=0; count<5; count++){
viewsAndInteractionJSON +=
"{ \"name\": \"" + viewColors[count+1]
+
" \", \"size\": \""
+ viewsAndInteraction[week][count][0] +
"\", \"interaction\": \""
+
viewsAndInteraction[week][count][1] + "\"
} ";
if (count<4) viewsAndInteractionJSON += ",";
}
viewsAndInteractionJSON += "]}";
return JSON.parse(viewsAndInteractionJSON) ;
}
</script>
</body> |
清单 11中的增强(以粗体显示)包括:
“数组到 JSON” 转换逻辑更加复杂,因为这一次您必须生成 清单 5中的
JSON 代码,其中每个叶节点有两部分数据:流行度和用户互动。
包布局对象的 value()函数仍然使用 d.popularity属性,并忽略了
user-interaction属性。包布局的图形计算仍然基于流行度数据。用户互动数据仅用于绘制外部流行度圆圈中的内部圆圈。请注意内部用户互动圆圈的
r属性值(半径)的计算,它在 清单 11中以粗体显示。这是用户互动数据发挥作用的地方。为了计算内部圆圈的半径,我将用户互动数据乘以外部(流行度)圆圈的半径与实际流行度数据的比率。这种细微的图形操作会根据
D3 用来执行它自己的内部计算的比例来缩放内部圆圈。
流行度和用户互动数据的条形图表示
我讨论了如何在不同布局中使用圆圈表示社交数据。现在我添加一点花样。例如,除了表示相同的社交数据的圆圈之外,图
5 还显示了条形图:

图 5. 社交数据的条形图表示
您可以看到,图 5中的条形图也表示相同的三周流行度和用户互动数据。这些竖条使用我绘制内部和外部圆圈所用的相同颜色和阴影。每个竖条的实心部分表示流行度,浅色阴影部分表示用户互动。
清单 12 显示了绘制 图 5中的条形图的 SVG 代码(省略了圆圈的代码):
清单 12. 条形图的 SVG
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="1500" height="1000">
<g>
<!--Tags for circles omitted.-->
</g>
<g>
<g transform="translate(100,250)">
<g transform="translate(0,200)">
<rect height="141.14" width="20"
fill="blue"></rect>
<rect height="41.04" width="20"
fill="lightblue"></rect>
</g>
<g transform="translate(22,200)">
<rect height="149.66" width="20"
fill="gold"></rect>
<rect height="41.78" width="20"
fill="yellow"></rect>
</g>
<g transform="translate(44,200)">
<rect height="74.98" width="20"
fill="green"></rect>
<rect height="31.72" width="20"
fill="lightgreen"></rect>
</g>
<g transform="translate(66,200)">
<rect height="76.92" width="20"
fill="red"></rect>
<rect height="28.52" width="20"
fill="lightcoral"></rect>
</g>
<g transform="translate(88,200)">
<rect height="91.96" width="20"
fill="maroon"></rect>
<rect height="52.64" width="20"
fill="indianred"></rect>
</g>
</g>
<g transform="translate(420,250)">
<g transform="translate(0,200)">
<rect height="119.44" width="20"
fill="blue"></rect>
<rect height="41.42" width="20"
fill="lightblue"></rect>
</g>
<g transform="translate(22,200)">
<rect height="113.44" width="20"
fill="gold"></rect>
<rect height="43.8" width="20"
fill="yellow"></rect>
</g>
<g transform="translate(44,200)">
<rect height="183.04" width="20"
fill="green"></rect>
<rect height="144.28" width="20"
fill="lightgreen"></rect>
</g>
<g transform="translate(66,200)">
<rect height="194.5" width="20"
fill="red"></rect>
<rect height="75.64" width="20"
fill="lightcoral"></rect>
</g>
<g transform="translate(88,200)">
<rect height="179.66" width="20"
fill="maroon"></rect>
<rect height="54.42" width="20"
fill="indianred"></rect>
</g>
</g>
<g transform="translate(740,250)">
<g transform="translate(0,200)">
<rect height="174.98" width="20"
fill="blue"></rect>
<rect height="61.52" width="20"
fill="lightblue"></rect>
</g>
<g transform="translate(22,200)">
<rect height="95.36" width="20"
fill="gold"></rect>
<rect height="63.8" width="20"
fill="yellow"></rect>
</g>
<g transform="translate(44,200)">
<rect height="134.76" width="20"
fill="green"></rect>
<rect height="90.64" width="20"
fill="lightgreen"></rect>
</g>
<g transform="translate(66,200)">
<rect height="190.92" width="20"
fill="red"></rect>
<rect height="76.5" width="20"
fill="lightcoral"></rect>
</g>
<g transform="translate(88,200)">
<rect height="139.66" width="20"
fill="maroon"></rect>
<rect height="96.62" width="20"
fill="indianred"></rect>
</g>
</g>
</g>
</svg> |
您可以在 清单 12中看到熟悉的 <g>标记排列。每三个 <g>标记包装一周的数据。然后,每个周包装器标记有
5 个子 <g>标记,每个子标记包装一对 <rect>标记,用于表示实际的流行度和互动数据。一个
<rect>标记绘制一个矩形条,就像一个 <circle>标记绘制一个圆圈一样。
每个 <rect>标记有 width和 height属性。width属性值对所有
<rect>标记是固定的,height属性值与矩形条所表示的社交数据呈正比。竖条的固定宽度和可变高度带来了条形图的印象。
同样地,<g>标记的 transform属性将每个竖条放在正确的位置。换言之,圆圈和竖条的
SVG 代码之间没有根本区别。
生成 图 5中的条形图的基于 D3 的 JavaScript 代码包含在本文的源代码下载中。
加导航数据
您常常需要在一个页面上显示各种图形组件,所以我最后将所有内容一起放在一个
SVG 画布上。图 6 使用您在 第 1 部分中看到的弦图添加了三周的导航数据:

图 6. 单个图形中的圆圈、圆弧、弦、布局和条形图
结束语
在这个篇幅不长的文章系列中,我们经历了一次漫长的数据可视化之旅,从 第
1 部分的 图 1 到本文中的复杂安排。我希望这些文章能够激发您了解 D3 的其他特性的兴趣,这些特性通过探索
D3 网站可以找到。
D3 的一大优势在于它是基于 JavaScript 的,而 JavaScript
是顶尖业务应用程序的首选前端技术。如果您使用的是一个支持 JavaScript 的业务应用程序平台,比如
IBM Business Process Manager,那么您可以根据从此文章系列中学到的知识来构建图形特性,将它们应用于您的业务应用程序中。要了解
IBM Business Process Manager 及其对外部 JSON 和 JavaScript
库的支持,请参阅 参考资料,获取到达 IBM Business Process Manager Information
Center 的链接。
|