求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
数据可视化,使用 D3 组件进行布局
 
作者 Bilal Siddiqui,火龙果软件    发布于 2014-02-10
 

了解用于绘制各种排列的组件的图形计算

在这个由两部分组成的文章系列中,将学习如何结合使用可缩放矢量图形 (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 的链接。

相关文章

基于EA的数据库建模
数据流建模(EA指南)
“数据湖”:概念、特征、架构与案例
在线商城数据库系统设计 思路+效果
 
相关文档

Greenplum数据库基础培训
MySQL5.1性能优化方案
某电商数据中台架构实践
MySQL高扩展架构设计
相关课程

数据治理、数据架构及数据标准
MongoDB实战课程
并发、大容量、高性能数据库设计与优化
PostgreSQL数据库实战培训
 
分享到
 
 


MySQL索引背后的数据结构
MySQL性能调优与架构设计
SQL Server数据库备份与恢复
让数据库飞起来 10大DB2优化
oracle的临时表空间写满磁盘
数据库的跨平台设计
更多...   


并发、大容量、高性能数据库
高级数据库架构设计师
Hadoop原理与实践
Oracle 数据仓库
数据仓库和数据挖掘
Oracle数据库开发与管理


GE 区块链技术与实现培训
航天科工某子公司 Nodejs高级应用开发
中盛益华 卓越管理者必须具备的五项能力
某信息技术公司 Python培训
某博彩IT系统厂商 易用性测试与评估
中国邮储银行 测试成熟度模型集成(TMMI)
中物院 产品经理与产品管理
更多...