数据可视化,第 1 部分: 使用 SVG 和 D3 可视化浏览指标

了解表示数据的图形技术

在这个由两部分组成的文章系列中,将会学习如何结合使用可缩放矢量图形 (SVG) 和开源 D3 JavaScript 库创建数据可视化。形状、颜色和布局可能对从业务角度理解海量数据有很大的帮助。一个示例场景演示了如何使用 SVG 和 D3 来根据社交媒体的浏览指标创建实用的图形。

Bilal Siddiqui , 自由顾问, XML4Java

Bilal Siddiqui 是一名电子工程师、XML 顾问、技术传播人员,以及经常发表文章的技术作家。他也是 XML4Java.com 的创始人,该公司专注于简化电子商务。自 1995 年从巴基斯坦拉合尔工程技术大学毕业获得电子工程学位后,他就开始设计工业控制系统的软件解决方案。后来,他转向 XML,开始构建基于 Web 和 WAP 的 XML 处理工具、服务器端解析方案以及服务应用程序。从 2006 年开始,他专门研究基于 Java™和 XML 的开源工具和解决方案。作为开源工具的强力支持者,他不仅设计了基于开源工具的解决方案,还在拉合尔大学培训软件和 IT 人员使用开源技术。Bilal 是 JasperReports 3.6 Development Cookbook(Packt Publishing,2010 年)的作者。



2013 年 9 月 24 日

这个由两部分组成的文章系列将演示有助于从数据中提取有业务价值的信息的可视化技术,本文是这个系列的第一部分。您将看到如何使用可缩放矢量图形 (SVG) 和开源 D3 JavaScript 库创建可通过浏览器查看的可视化表示,通过形状和颜色来传达信息。我将通过一些可视化浏览指标(与社交媒体使用相关)的示例来演示这些技术。第 1 部分概述 了 SVG 和 D3 如何协同工作,还提供了一些基本示例。第 2 部分 将会更深入地剖析这个强大的开放标准技术组合的可视化功能。

分析社交媒体指标

社交媒体大数据挑战

社交媒体站点(比如 Twitter、Facebook 和 YouTube)提供了全面的 Web 服务接口来公开它们的功能。例如,YouTube Data API 支持应用程序将视频上传到 YouTube,或播放一个网站上现有的 YouTube 视频。现在这些站点也在开发分析 API。举例而言,YouTube Analytics API 向编程客户端提供了查看次数和喜欢次数(number of likes)等统计数据。结果,更多的业务应用程序可通过可视和编程接口与社交媒体互动。对所有规模的公司而言,下一个挑战是通过大数据分析将大量社交数据最有效地应用到业务中。数据可视化(整个分析场景中的一个组成部分)是这个文章系列的关注重点。您可了解 IBM 大数据平台的 IBM InfoSphere StreamsIBM InfoSphere BigInsights产品全面的分析功能。

公司理解客户行为的一种创造性方式是,通过社交媒体提出想法,并让潜在客户参与到交互式讨论中。社交媒体上的互动反映了双向的人际互动:要理解人们的好恶,您必须聆听他们在说什么,就像您希望与积极主动的人互动一样。

以一家家庭装饰公司为假设场景,该公司以博客、视频、Facebook 页面和论坛形式发布公开内容。这些内容通过社交媒体资源展示了公司的想法,并尝试发起讨论和其他形式的用户互动。这些内容迎合各个客户的口味和偏好,帮助他们从一个社交资源导航到另一个。为了判断不断变化的客户趋势并提出新方法和新设计,公司希望从三个方面分析浏览数据:

  • 流行度,由每个社交资源的 查看次数表示
  • 参与该资源上的 互动的用户数量
  • 用户从一个资源 导航到另一个资源的方向

123 分别显示了三周内用户查看、用户互动和导航次数。请注意,这些表使用了彩色名称来表示公司使用的社交媒体资源类型(比如博客和 Facebook 页面)。

表 1 显示了每个资源的用户查看次数:

表 1. 每个社交资源的用户查看次数
社交资源蓝色金色绿色红色紫红色
第 1 周70577483374938464598
第 2 周23717397458928618249
第 3 周59725672915297258983

表 1中可以看到,蓝色资源在第 1 周被查看了 7,057 次,金色资源是这一周被查看次数最多的资源。

表 2 显示了用户互动数据:

表 2. 每个社交资源上互动的用户数量
社交资源蓝色金色绿色红色紫红色
第 1 周20522089158614262632
第 2 周20712190721437822721
第 3 周30763190453238254831

表 2中可以看到,第 1 周在蓝色资源上有 2,052 名用户参与了互动,在这一周紫红色资源上的用户互动次数最高。

表 3 显示了从蓝色资源导航到其他资源的用户数量:

表 3. 蓝色社交资源的导航数据
社交资源蓝色到金色蓝色到绿色蓝色到红色蓝色到紫红色
第 1 周3057348387498456
第 2 周2371739745892861
第 3 周5972567291529725

表 3可以看到,在第 1 周有 3,057 名用户在访问蓝色资源后导航到了金色资源,而且红色资源收到了来自蓝色资源的最多观众。


浏览数据的可视化

可视内容提供了一种比数字表格更容易、更快捷的方式来解释大数据量。可通过多种方式以图形方式表示表 123中的数据。例如,图 1 就是一种显示 表 1中的第 1 周数据的简单方式:

图 1. 用圆圈表示的每种社交资源在第 1 周的查看次数
查看次数

图 1 将每种资源的查看次数表示为一个圆圈。圆圈的相对大小与它们表示的次数成正比,所以查看最多的资源(金色)由最大的圆圈表示。每个圆圈还显示了每种资源第 1 周获得的实际查看次数。

图 2 是 图 1的一种细微变形,使用了一种不同的圆圈布局:

图 2. 以稍微不同的圆圈布局显示的第 1 周查看次数
圆圈与图 1 中相同,但以弧形放置而不是水平放置。

每种资源的流行度只是公司希望分析的一个数据维度。图 3 同时显示了第 1 周每种资源的查看次数和希望在该资源上互动的用户数量 —一种可视化表示中包含两种维度:

图 3. 嵌套的圆圈表示了第 1 周的用户查看次数和互动的用户数量
由嵌套圆圈组成的可视化表示显示了每种资源上的用户查看次数和用户互动次数

图 3 中,外圈表示资源的总查看次数,内圈表示在该资源上互动的用户数量。我还在每个圆圈中放入了数字来表示实际的查看和互动数据。

图 1图 2图 3 显示了组合颜色、形状和实际数字来表示数据的简单方式。通过查看 图 3,您可看到金色资源在第 1 周吸引了最多用户查看,而紫红色资源上的互动最多。


使用开放技术实现数据可视化

动态生成的可视化 JavaScript

D3、DVG 和 JavaScript 相结合,形成了一个基于浏览器的完整的数据可视化套件。为了在此文章系列中演示它们的结合使用,我在示例代码中应编码了示例数据。通过服务器端组件动态组合了真实项目中的大部分 JavaScript 代码。在大数据分析应用程序中,这些组件可能包括基于 Apache Hadoop 的集群,托管各种各样服务器端模块的 Web 和应用服务器,以及数据库服务器。使用 D3 以及来自这类来源的 JavaScript 生成 SVG 代码,这种做法非常适合支持 JavaScript 的现代业务应用程序,比如 IBM Business Process Manager(参见 参考资料)。

目前的开放标准和开源工具已足够强大,能够支持数据的图形表示。SVG 是一个开放的万维网联盟 (W3C) 标准,它定义了一种基于 XML 格式来绘制二维图形对象。(参见 参考资料,获取一篇关于 SVG 的介绍性文章的链接。)SVG 受多种浏览器支持。我在 Google Chrome 上测试了本文的所有 SVG 和 JavaScript 代码。

如果您在处理数据和 SVG,D3 库提供了一些不错功能。它获取您的数据及绘图说明,将您的数据与所需的 SVG 标记关联起来,然后迅速生成您可在浏览器中查看的 SVG 代码。D3 在绘制复杂的图形对象时将数据放在容易获取的地方,所以您可不断深入地设计您的图形,一次一个步骤,而且仅需处理绘制图形的每一小部分所需的数据。

对于本文和 第 2 部分 中的数据,我从简单到复杂图形由浅入深地探索 D3 的功能,提供每个图形的 SVG 和 JavaScript 代码。我从最简单的情形开始:生成 图 1中所示圆圈的 SVG 代码。

SVG 数据表示

清单 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 
        transform="translate(70,143)"> <circle r="70.57" style="fill: 
        #000fff;"></circle> <text x="-10" fill="grey">7057</text> </g> 
        <g transform="translate(216,143)"> <circle r="74.83" style="fill: 
        #fff555;"></circle> <text x="-10" fill="grey">7483</text> </g> 
        <g transform="translate(329,143)"> <circle r="37.49" style="fill: #aaf000;"> 
        </circle> <text x="-10" fill="grey">3749</text> </g> <g 
        transform="translate(404,143)"> <circle r="38.46" style="fill: 
        #cc0000;"></circle> <text x="-10" fill="grey">3846</text> </g> 
        <g transform="translate(489,143)"> <circle r="45.98" style="fill: 
        #993344;"></circle> <text x="-10" fill="grey">4598</text> </g> 
        </svg>

可以看到,清单 1中的根标记是 <svg><svg>标记创建了画布,在该画布上,您可以使用其他 SVG 标记来绘图,该标记也用作这些标记的包装器。<svg>标记的 widthheight属性指定了 SVG 画布的尺寸。我在 清单 1中设置的画布为 1,000 x 1,000 个单位。

<svg>标记有 5 个 <g> 子标记,每个子标记用于 图 1中的一个圆圈。<g> 标记被用作为创建每个圆圈和配套文本而实现的绘图的包装器。每个 <g>标记只有一个属性,名为 transform,它的值为 translate (X, Y)translate (X, Y)值确定了围绕其绘制圆圈的点。例如,清单 1中第一个 <g>标记的 transform属性放在圆圈的中心位置 70, 143。

5 个 <g>标记中的每一个的 transform 属性都有一个不同的 X值,但它们拥有相同的 Y值。结果,5 个圆圈排列在一条水平线上。<g>标记的不同 X 值导致每个圆圈仅挨着前一个圆圈绘制,它们之间没有明显的空白。稍后我将给出生成这些值的简单 JavaScript。

进一步分析 清单 1,您可能会注意到,每个 <g> 标记有两个子标记(一个 <circle>标记和一个 <text>标记),它们分别绘制圆圈和配套的文本。每个 <circle>标记有一个 r属性,它定义了圆圈的半径。请注意,每个 <circle> 标记的 r属性的值为查看次数除以 100。每个 <circle>标记还有一个 style属性,它指定填充圆圈的颜色。我使用了 6 位数的十六进制格式来表示颜色的 RGB(红绿蓝)代码。(参见 参考资料,了解使用颜色的更多细节。)<text>标记包装了要在每个圆圈内显示的文本。包含在每个 <text>标记中的 x="-10"属性会轻微地移位文本,以便将它放置在更好的位置。<text>标记的 fill属性指定了文本颜色。

您可以 下载本文的所有代码示例,并在浏览器窗口中打开它们。尝试在 清单 1中的 SVG 代码中添加更多圆圈或文本,并查看浏览器如何显示您的 SVG。

使用 JavaScript 和 D3 生成 SVG

清单 2 中的 JavaScript 代码使用 D3 生成了 清单 1中的 SVG 代码:

清单 2. 使用 D3 生成图 1 的 SVG
 <!DOCTYPE 
        html> <meta charset="utf-8"> <body> <script 
        src="http://d3js.org/d3.v3.min.js"></script> <script> //Step 1: var views = [ 
        [7057, 7483, 3749, 3846, 4598], [ 2371, 7397, 4589, 2861, 8249], [ 5972, 5672, 9152, 9725, 
        8983], ]; var width = 1000, height = 1000; var colors = [ "blue", "yellow", "green", "red", 
        "maroon" ]; var week = 0; //Step 2: var svg = d3.select("body").append("svg") .attr("width", 
        width) .attr("height", height) //Step 3: .selectAll("g") .data(views[week]) .enter(); //Step 
        4: var g = svg.append("g") //Step 5: .attr("transform", function(d,i){ var x = 0; for (var 
        count=0; count<i; count++ ) x+=views[week][count]; for (var count=0; count<=i; count++ 
        ) x+=views[week][count]; return "translate(" + x/100 + "," + height / 7 + ")" } ); //Step 6: 
        g.append("circle") .attr("r", function(d){return d/100}) .style("fill", function(d, 
        i){return colors[i];}); g.append("text") .attr("x", -10) .attr("fill", "grey") 
        .text(function(d){return d}); </script> </body>

我提供了注释来说明 清单 2中的步骤:

第 1 步:第 1 步是将用户查看数据存储在一个数组中。我还设置了一些变量来存储画布的尺寸和我希望在 SVG 图形中使用的演示。

第 2 步:我开始通过 d3.select("body").append("svg")使用 D3,它将一个 <svg>标记添加到 HTML 页面的正文中。这里我还通过调用 .attr()函数设置了 SVG 画布的宽度和高度。

第 3 步:三个函数调用 .selectAll().data().enter()一起构成了 D3 带来的强大功能和易用性:

  • .selectAll("g")调用选择根 SVG 标记的所有 <g>子标记。尽管它现在还无法选择任何子标记(因为没有人和子标记),但它知道我在寻找什么。
  • .data(views[week])调用向 D3 提供了所有 5 种社交资源第 1 周的用户查看数据。
  • .enter()调用将各个 <g>标记与各个用户查看数据记录相关联。

第 4 步.append("g")调用高速 D3 添加足够的 <g>标记,以便为数据中的每条记录提供一个 <g> 标记。因为最初没有任何 <g>标记,而且数据数组中的每一周都有 5 个记录(每个记录对应一种社交资源),所以 D3 向根 <svg>标记添加了 5 个 <g>标记。第一个 <g> 标记在内部与第一条数据记录关联(也就是查看数据数组中的 7057),第二个 <g> 标记与第二条记录关联,依此类推。D3 在内部将数据与 SVG 标记相关联,所以我在创作时只需要传递数据数组一次,无需担忧是否将正确的数据与每个 SVG 标记相关联。

第 5 步:我还必须为 5 个 <g>标记中的每个标记都添加一个 transform属性,所以我调用了 .attr() 函数。我只需调用该函数一次,D3 在内部已执行了循环,以确保 transform属性添加到了 5 个 <g>标记中的每一个标记上。

.attr()函数获取您创作的属性名称(在本例中为 transform),使用该名称作为它的第一个参数。回想一下对 清单 1的讨论,transform属性确定了圆圈的位置,所以它的值必须在一个函数中计算。出于此原因,.attr() 函数的第二个参数由另一个函数调用,后者本身接受两个参数:di

D3 为您提供了这个编写函数调用的机会,因为您在创作该属性值时可能需要使用与 <g>标记有关联的数据。d参数包含您目前为其创作了 transform属性值的特定 <g>标记的数据部分(例如,第一个与表示蓝色社交资源的用户查看数据记录有关联的 <g>标记的数据为 7057)。D3 的魔力在于,它在内部处理了数据与各个标记的正确关联,所以您无需担忧是否挑选了正确的数据部分。您在创作 SVG 时可将精力集中在数据处理上。

i参数是数据的从 0 开始的索引(例如 i= 0 表示与第一条记录有关联的第一个 <g>标记)。您可在这一步中看到,我使用了 i参数来计算 <g> 标记的合适位置,因此在最左侧绘制了第一种(蓝色)社交资源的圆圈,然后紧挨第一个圆圈绘制了第二个圆圈,依此类推。

如果尝试运行第 1 布到第 5 步的代码,您会看到 5 个 <g>子标记和它们的 transform属性,如清单 3 中的 SVG 代码所示。(这些 <g>标记还不会在浏览器窗口中显示图形。)

清单 3. 仅包含 5 个 <g> 标记的 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(70,143)"> </g> <g transform="translate(216,143)"> 
        </g> <g transform="translate(329,143)"> </g> <g 
        transform="translate(404,143)"> </g> <g transform="translate(489,143)"> 
        </g> </svg>

第 6 步:剩余的任务是向 5 个 <g>标记中的每一个标记添加一个 <circle>标记和一个 <text> 标记。为此,您可以调用 .append()函数两次(一次针对 <circle>标记,另一次针对 <text>标记)。

请注意,我使用了 .attr("r", function(d){return d/100})来创作圆圈的 r属性(半径)。在本例中,我必须知道特定资源的查看次数,然后才能绘制一个具有相应大小的圆圈。因此,我只需要使用 d参数,而不需要使用 i参数,所以我省略了 id参数自动包含正确的数据(查看次数),所以我将 d 的值除以 100 来确定圆圈的大小。

基于 D3 的 JavaScript 代码已经可以绘制 图 1中的圆圈。如果您在浏览器窗口中打开来自示例代码 下载部分的 Listing2.html,就会看到它生成了 清单 1的 SVG,显示了 图 1中的圆圈。

一种稍微不同的圆圈布局

您可稍微修改一下 清单 2中的代码,绘制如 图 2中所示的圆圈布局。清单 4 给出了已修改的 JavaScript:

清单 4. 绘制图 2 中的布局的已修改代码
 <!DOCTYPE 
        html> <meta charset="utf-8"> <body> <script 
        src="http://d3js.org/d3.v3.min.js"></script> <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 = [ "#0000ff", //blue "#ffd700", //gold 
        "#008000", //green "#ff0000", //red "#800000" //maroon ]; var week = 0, scale = 100; var svg 
        = d3.select("body").append("svg") .attr("width", width) .attr("height", height) 
        .selectAll("g").data(views[week]).enter(); var g = svg.append("g") .attr("transform", 
        function(d,i){ var x = 10000, y = 10000; if (!(i%2)){ for (var count=0; count<i; 
          count++) x+=views[week][count]; for (var count=0; count<=i; count++) 
          y+=views[week][count]; } else { for (var count=0; count<i; count++) 
          y+=views[week][count]; for (var count=0; count<=i; count++) x+=views[week][count]; } 
          return "translate(" + x/scale + "," + y/scale + ")"} ); g.append("circle") 
        .attr("r", function(d){return d/scale}) .style("fill", function(d, i){return colors[i];}); 
        g.append("text") .attr("x", -10) .attr("fill", "grey") .text(function(d){return d}); 
        </script> </body>

通过对比 清单 2清单 4就可以看到,修改之处在 transform属性值的计算上,在 清单 4中已经用粗体突出显示。

嵌套圆圈

现在看看清单 5,它是绘制 图 3中的圆圈的 SVG 代码(同时表示查看数据和互动数据的嵌套圆圈):

清单 5. 图 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(100,170.57)"> <circle r="70.57" style="fill: #0000ff;"> 
        </circle> <circle r="20.52" style="fill: #add8e6;"> </circle> <text 
        x="-15" y="35.52" fill="white">7057</text> <text x="-15"
        y="5">2052</text> </g> <g transform="translate(245.4,170.57)"> 
        <circle r="74.83" style="fill: #ffd700;"> </circle> <circle r="20.89"
        style="fill: #ffff00;"> </circle> <text x="-15" y="35.89"
        fill="white">7483</text> <text x="-15" y="5">2089</text> </g> 
        <g transform="translate(245.4,282.89)"> <circle r="37.49" style="fill: 
        #008000;"></circle> <circle r="15.86" style="fill: #90ee90;"></circle> 
        <text x="-15" y="30.86" fill="white">3749</text> <text x="-15"
        y="5">1586</text> </g> <g transform="translate(321.35,282.89)"> 
        <circle r="38.46" style="fill: #ff0000;"></circle> <circle r="14.26"
        style="fill: #f08080;"></circle> <text x="-15" y="29.26"
        fill="white">3846</text> <text x="-15" y="5">1426</text> </g> 
        <g transform="translate(321.35,367.33)"> <circle r="45.98" style="fill: 
        #800000;"></circle> <circle r="26.32" style="fill: #cd5c5c;"></circle> 
        <text x="-15" y="41.32" fill="white">4598</text> <text x="-15"
        y="5">2632</text> </g> </svg>

可以看到,清单 5在每个 <g>标记内有两个 <circle>和两个 <text>标记,而 清单 1只有一对 <circle><text>标记。这很容易理解,因为 图 3 必须绘制两个具有相同中心的圆圈,以提供一种嵌套圆圈的视图,而且每个圆圈包含一个关联的数字。

清单 6 显示了生成 图 3的基于 D3 的 JavaScript 代码:

清单 6. 绘制图 3 的 JavaScript
 <!DOCTYPE 
        html> <meta charset="utf-8"> <body> <script 
        src="http://d3js.org/d3.v3.min.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 width = 1000, height = 1000; var 
        viewColors = [ "blue", "gold", "green", "red", "maroon" ]; var interactionColors = [ 
        "lightblue", "yellow", "lightgreen", "lightcoral", "indianred" ]; var week = 0, scale = 100; 
        var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) 
        .selectAll("g") .data(viewsAndInteraction[week]) .enter(); var g = svg.append("g") 
        .attr("transform", function(d,i){ var x = 10000, y = 10000; if (!(i%2)){ for (var count=0; 
        count<i; count++) x+=viewsAndInteraction[week][count][0]; for (var count=0; count<=i; 
        count++) y+=viewsAndInteraction[week][count][0]; } else { for (var count=0; count<i; 
        count++) y+=viewsAndInteraction[week][count][0]; for (var count=0; count<=i; count++) 
        x+=viewsAndInteraction[week][count][0]; } return "translate(" + x/scale + "," + y/scale + 
        ")"} ); g.append("circle") .attr("r", function(d){return d[0]/scale}) .style("fill", 
        function(d, i){return viewColors[i];}); g.append("circle") .attr("r", function(d){return 
        d[1]/scale}) .style("fill", function(d, i){return interactionColors[i];}); g.append("text") 
        .attr("x", -15) .attr("y", function(d){return (15 + d[1]/scale);}) .attr("fill", "white") 
        .text(function(d){return d[0]}); g.append("text") .attr("x", -15) .attr("y", 5) 
        .text(function(d){return d[1]}); </script> </body>

如果对比 清单 2中的 JavaScript 与 清单 6 中的代码,您可以看到,绝大多数代码都是相同的。区别在于,在 清单 6中:

  • 您必须处理查看数据和互动数据,所以一个 viewsAndInteraction数组同时包含查看数据和互动数据。
  • 有两对 g.append("circle")g.append("text")函数调用用于绘制两个圆圈,每个圆圈具有相同的中心且包含文本。

您现在已经看到了所有三幅图的 SVG 和 JavaScript。下一个示例可将会视化导航数据。


导航数据的可视化表示

图 4 使用不同颜色的圆弧和 来表示来自第 1 周的一些导航数据:从蓝色资源导航到其他资源的用户数量:

图 4. 显示导航数据的彩色圆弧和弦
包含圆弧和弦的图像

图 4中的彩色圆弧表示不同的社交资源。蓝色资源通过弦连接到其他所有资源。每条弦表示一种导航方向。

蓝色弦在蓝色资源上开始和结束,表示开始在蓝色资源上浏览并且没有进一步导航的用户数量。金色弦从蓝色资源开始到金色资源结束,表示从蓝色资源导航到金色资源的用户数量。类似地,图 4描绘了从蓝色资源到剩余资源的导航。

请注意,(从蓝色资源开始的)弦是按升序排列的,首先使用较细的弦表示较低的用户数量。所以您不需要通过数字了解哪些资源获得了更多的用户。

使用 SVG 绘制圆弧和弦

清单 7 给出了绘制 图 4的部分 SVG 代码:

清单 7. 绘制图 4 的部分 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="500" height="500"> <g 
        transform="translate(250,250)"> <path style="fill: #0000ff;"
          d="M1.1633760361312584e-14,-190A190,190 0 0,1 
          177.80498922385382,-66.97302298019123L140.372 
          3599135688,-52.873439194887816A150,150 0 0,0 
          9.18454765366783e-15,-150Z"></path> <path style="fill: 
        #ffd700;"
        d="M177.80498922385382,-66.97302298019123A190,190 0 0,1 
          141.9432774826573,126.30164677264251L112.06048222315049,99 
          .71182639945462A150,150 0 0,0 140.3723599135688,- 
          52.873439194887816Z"></path> <path style="fill: #008000;"
          d="M141.9432774826573,126.30164677264251A190,190 0 0,1 - 
          141.87314929322426,126.38041584684909L- 
          112.00511786307179,99.77401251067033A150,150 0 0,0 
          112.06048222315049,99.71182639945462Z"></path> <path 
        style="fill: #ff0000;"
          d="M-141.87314929322426,126.38041584684909A190,190 0 0,1 
          -136.03537960886078,-132.64379176830383L- 
          107.39635232278484,-104.7187829749767A150,150 0 0,0 - 
          112.00511786307179,99.77401251067033Z"></path> <path 
        style="fill: #800000;"
          d="M-136.03537960886078,-132.64379176830383A190,190 0 
        0,1 - 3.7240908056998534e-13,-190L-2.9400716887104106e-13,- 
          150A150,150 0 0,0 -107.39635232278484,- 
          104.7187829749767Z"></path> <g> <!--Drawing SVG code 
        for chords goes here. --> </g> </g> </svg>

可以看到,清单 7中的根 <svg>标记有一个 <g>子标记,它只有 5 个 <path> 标记和另一个内部 <g>子标记。这 5 个 <path>标记绘制了 5 种不同颜色的圆弧。内部的 <g>标记包装绘制弦的 SVG 代码,这稍后将会解释。首先,我将解释一下 <path>标记如何绘制 5 条圆弧。

绘制一组彩色圆弧

使用适当的颜色绘制一组圆弧,可以使用 SVG <path> 标记轻松完成此操作,这些标记的用途是定义绘制路径,就像使用钢笔在纸上描绘一样。路径可以是封闭的(例如一个三角形)或开放的(例如一组互联的线条)。您需要一个封闭路径来绘制圆弧,如图 5 所示:

图 5. 使用 <path>标记绘制圆弧
绘制圆弧

图 5 中,圆弧的封闭路径的周长使用黑色绘制,封闭路径中填入的是黄色。如果使用钢笔绘制此圆弧,可使用黑色绘制两条弧线和两条直线构成周长线,然后在内部填入黄色。

图 4中的每条圆弧对其周长和封闭路径中的填充色使用了相同的颜色。所以您需要 5 个 <path>标记,每个对应圆圈的一段圆弧。

清单 7中,可以看到 5 个 <path> 标记都有两个属性:styledstyle属性定义了绘制颜色,d属性定义绘制路径。

d属性的值(例如 M8.572244476756641e-15,-140A140,140 0 0,1 93.95877067680189,103.78703875197591L67.11340762628707,74.13359910855422A100,100 0 0,0 6.123031769111886e-15,-100Z)看起来很长、很复杂。官方 SVG 规范要求对此值使用这种格式来定义准确的绘制路径。

该格式中依次为一个 M后跟两个逗号分隔的数字,一个 A 后跟多个数字,一个 L后跟两个逗号分隔的数字,另一个 A 后跟多个数字,最后是一个 Z。这个顺序表示:移动M后的数字定义的点,然后绘制 A后的数字定义的一个 弧线,然后绘制 L 后的数字定义的一条 线,最后绘制另一条 弧线(第二条弧线)。末尾的 Z表示 返回到初始位置,从而形成一条封闭路径。

好消息是,您不需要知道如何按这个顺序准确绘制所需圆弧的任何更多细节 —因为所有这些值都使用 D3 库计算,这个库包含绘制复杂图形的易用功能。稍后,我将给出执行所有绘制计算和动态生成 图 4的完整 SVG 代码的 D3 JavaScript 代码。

每个圆圈的 5 个 <path>标记都是绘制圆弧所需要的。在浏览器中打开 Listing7.svg 文件(参见 下载),查看没有任何弦的彩色圆弧,如图 6 所示:

图 6. 彩色圆弧
彩色圆弧

现在我将介绍如何绘制弦来表示导航。

绘制弦

像圆弧一样,弦使用一个 <g>标记绘制,该标记包装了 5 个 <path>标记(分别用于每根弦),如清单 8 所示:

清单 8. 绘制 5 根弦的 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="500" height="500"> <g 
        transform="translate(250,250)"> <!--Drawing SVG code for arcs goes here. --> 
        <g> <path d="M9.18454765366783e-15,-150A150,150 0 0,1 
        19.52349842170555,-148.72401624948697Q 0,0 9.18454765366783e-15,-150Z" style="fill: 
        #0000ff;"> </path> <path d="M19.52349842170555,- 148.72401624948697A150,150 0 
        0,1 41.344235247425466,-144.18964668729006Q 0,0 
        140.3723599135688,-52.873439194887816A150,150 0 0,1 144.99722485611238,-38.41620470616511Q 
        0,0 19.52349842170555,-148.72401624948697Z" style="fill: #ffd700;"> </path> 
        <path d="M111.39598065576133,-100.4536484839712A150,150 0 0,1 
        140.3723599135688,-52.873439194887816Q 0,0 84.8772338498757,123.67641316756206A150,150 0 0,1 
        50.93706344958661,141.08655345968583Q 0,0 111.39598065576133,-100.4536484839712Z"
        style="fill: #008000;"> </path> <path 
        d="M-149.71409635840777,9.256854302914952A150,150 0 0,1 
        -140.64142529945457,-52.15351847898602Q 0,0 68.6764412000974,-133.35496400243062A150,150 0 
        0,1 111.39598065576133,-100.4536484839712Q 0,0 - 149.71409635840777,9.256854302914952Z"
        style="fill: #ff0000;"> </path> <path 
        d="M-59.58349836433014,-137.65829696268898A150,150 0 0,1 -1.6078040591602227e-13,-150Q 0,0 
        41.344235247425466,-144.18964668729006A150,150 0 0,1 68.6764412000974,-133.35496400243062Q 
        0,0 - 59.58349836433014,-137.65829696268898Z" style="fill: #800000;"> </path> 
        </g> </g> </svg>

可以看到,清单 8中弦的 <path>标记都(像 清单 7中的弦一样)有一个较长的、复杂的 d属性值需要使用 D3 来计算。

清单 8为圆弧省略了 <path>标记。如果在浏览器中查看 清单 8中的 SVG 代码,您将看到没有圆弧(因此没有圆圈周长)的弦,如图 7 所示:

图 7. 5 组没有圆弧的弦
没有圆弧的弦

现在,我将介绍生成 图 4的 SVG 代码的基于 D3 的 JavaScript 代码。

使用 D3 绘制带弦的圆圈

可在清单 9 中的 JavaScript 代码中看到 5 个步骤,它使用了 D3 来创作 图 4的 SVG:

清单 9. 生成图 4 中的 SVG 代码的 5 个步骤
 <!DOCTYPE 
        html> <meta charset="utf-8"> <body> <script 
        src="http://d3js.org/d3.v3.min.js"></script> <script> //Step 1: var 
        navigation = [ [3057, 3483, 8749, 8465, 4598], [ 2371, 7397, 4589, 2861, 8249], [ 5972, 
        5672, 9152, 9725, 8983], [ 9763, 8462, 9782, 1953, 5182], [ 9567, 1571, 2895, 2783, 1874] ]; 
        var navigationChord = d3.layout.chord() .matrix(navigation); var chordCalculations = 
        navigationChord.chords; var colors = [ "blue", "gold", "green", "red", "maroon" ]; var width 
        = 500, height = 500, radius = 150, arcStroke = 40; //Step 2: var svg = d3.select("body") 
        .append("svg") .attr("width", width) .attr("height", height) //Step 3: .append("g") 
        .attr("transform", function(d,i){ return "translate(" + width/2 + "," + height / 2 + ")" }); 
        //Step 4: svg.selectAll("path") .data(navigationChord.groups()) .enter() .append("path") 
        .style("fill", function(d, i) { return colors[i]; }) .attr("d", 
        d3.svg.arc().innerRadius(radius) .outerRadius(radius+arcStroke)); //Step 5: 
        navigationChord.sortSubgroups(d3.ascending); svg.append("g") .selectAll("path") 
        .data(chordCalculations) .enter() .append("path") .attr("d", d3.svg.chord().radius(radius)) 
        .style("fill", function(d, i) { if (i < colors.length) return colors[i]}) 
        .style("opacity", function(d, i){ if (!(i < colors.length)) return 0 }); </script> 
        </body>

第 1 步是存储数据和设置变量。我将导航数据存储在一个名为 navigation 的数组中,然后将它传递到 d3.layout,后者会在内部处理布局。我将 d3.layout所完成的计算存储在一个名为 chordCalculations的变量中,第 5 步会使用该变量绘制弦。

第 2 步是创作 <svg>包装器并设置它的尺寸。

第 3 步是添加用作所有绘制标记的包装器的 <g>标记。

第 4 步为您在 清单 7中看到其 SVG 代码的圆弧创作 5 个 <path>标记。使用 d3.svg.arc(),它在内部执行所有计算来创建彩色圆弧的所有 5 个 <path>标记的 d属性值。

第 5 步为弦创建了 <path> 标记。我首先将弦按升序排列,然后将弦计算结果传递给 D3 的 4 个神奇的步骤:.selectAll().data().enter().append()。最后,d3.svg.chord() 创建了绘制 5 跳线的 <path>标记的 d 属性值。


结束语

在本文中,我设定了一个场景来发现 SVG 与 D3 携手提供的全部图形功能。第 2 部分将演示 D3 布局的优势,这些布局可执行图形计算,从而在单个 SVG 画布上绘制一个形状的多个副本。我还将展示如何绘制流行度、用户互动和导航数据的图表表示。最后,我将介绍如何将圆圈、圆弧、弦、图形和布局结合到单个 SVG 图形中。


下载

描述名字大小
样例代码source-code.zip10KB

参考资料

学习

获得产品和技术

  • D3:下载 D3 JavaScript 库。
  • InfoSphere BigInsights:获取 IBM InfoSphere BigInsights 的试用版,管理和分析海量静止的结构化和非结构化数据。
  • InfoSphere Streams:获取 IBM InfoSphere Streams 的试用版,构建应用程序来快速获取、分析和关联来自数千个实时来源的信息。

讨论

  • 加入 developerWorks 社区。探索由开发人员推动的博客、论坛、群组和维基,并与其他 developerWorks 用户进行交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Web development, Information Management
ArticleID=945970
ArticleTitle=数据可视化,第 1 部分: 使用 SVG 和 D3 可视化浏览指标
publish-date=09242013