1、SVG 与 XML 商业图表实务运用 Adobe 的 SVG 技术从 XML 数据创建漂亮的商业图像级别: 中级Jack D Herrington (), 高级软件工程师, Leverage Software Inc.学习如何使用 XML、PHP 和 Adobe 的可缩放向量图形(SVG)标准创建商业图像。SVG 标准为图像提供了无限级的向量缩放、可视化效果以及基于脚本的交互性。我承认,我是一个图像痴迷者。从孩提时候起我就喜欢有漂亮图像的计算机。这也是与 TRS-80 相比我更喜欢 Apple II 计算机的原因。不过有谁不喜欢图片呢?谁不曾被 Pixar 电影征服?“一张图片胜过千言万语”
2、这句老话没有错,因为一幅图片能够又快又容易地传达大量信息。 图像对于商业数据的重要性其他任何地方都比不上。通过 SVG 之类的标准来充分利用图像代码非常重要,因为众所周知,Web 上早就不缺乏图像了。当然可以把图片放在网页中,但通常这些图片的作用不大。这些照片不能缩放和滚动,不能交互,不能很好地打印和调整比例。不过,我相信 Web 2.0 将改变这种局面。不再需要强调这种技术的重要性。本文的目的是给用户以包括图像的 体验 。打开 Google 的 Finance 页面,如 图 1 所示。查看股票的时候,可以使用交互式图像控件滚动数据,找到感兴趣的地方。是否使用 Macromedia Flash
3、 实现有关系吗?没有。重要的是最终结果 客户体验。图 1. Google Finance 页面本文将通过例子说明如何使用 Adobe SVG 格式和 PHP 编程语言创建漂亮的交互式图形。首先,了解一下 SVG 的背景知识及其与 Web 图像技术的关系。可缩放向量图形Adobe 的 SVG 标准是一种基于 XML 的表示向量图形的格式。基础是直线、矩形、形状、图片和文本这些元素。所有这些元素都在 “视图坐标” 中指定,坐标值不是像素,只是适合应用程序的需要而定义的任意数值范围。这样就可以将 XML 指定的图像模型呈现到任意的图像空间中 无论多么大或者多么小 并进行适当的缩放。向量图像可以用打印
4、机的最大分辨率打印,不会出现位图放大打印时常见的锯齿。这种格式还允许对任何对象或对象组应用特效。其中包括投影、斜角、纹理、外测发光、内测发光等等。如果熟悉 Adobe PhotoShop 或 Elements,就会知道这些效果。还可以使用旋转、倾斜、透明、剪裁等技术。不仅如此,SVG 的标记还可用于动态改变这些属性,因此可以沿着路径移动图形对象或者实现淡入淡出效果。此外,SVG 还允许在模型中添加 JavaScript 代码,为图形元素、效果和动画加上行为。我第一次看到 SVG 的时候,立刻被它吸引并钻了进去。照我看来,Adobe 是把 PhotoShop 引擎变成了能够嵌入到网页中的控件。事
5、实上,今天看来仍然如此。不足之处是,我发现并非所有的客户机上都安装了 SVG,而且安装它需要下载某些软件。我不可能这样要求客户。因此有段时间我把 SVG 放在了一边,直到最近发现 SVG 的一个子集安装到了 Mozilla Firefox V1.5 中。现在,我想对于 SVG 来说事情将向好的方面转化。但是我们再后退一步,将 SVG 置于 Web 图像的大背景中。SVG 和它所属的世界几年以前,如果想为 Web 创建图像,选择很有限。您可以即时创建 PHP 图片和建立 .jpeg、.gif 或 .png 文件。但是这些图像往往很简陋,因为 PHP 图像库非常原始,不支持特效。此外图像的缩放也不
6、够好。技术有了一点进步,现在有更多的选择了。当然,其中包括 SVG。但是还有 Flash。Flash 可以使用画布对象和 JavaScript 代码绘制任何图像。还能够从服务器上直接以 XML 或 JavaScript Serialized Object Notation (JSON) 的形式把数据读入到图像中。在 Flash 之上有两种相对较新的技术。第一个是 Adobe Flex,这种基于 XML 标记的语言可以呈现为 Flash,并且包含图形库。Flex 的竞争对手之一是 Laszlo。Laszlo 也使用标记,优点是开放源代码。还可以在浏览器中使用 标记。这种新标记是一个画布,可以在其
7、上绘制直线和矩形、放置图片、进行旋转等。听起来似乎不错,但 Microsoft Internet Explorer 不支持它 目前来说。幸运的是,Google 有一个开放源代码项目 InternetCanvas,在 Internet Explorer 中提供了相同的能力。刚出现的另一种选择是 Microsoft Windows Presentation Foundation (WPF),它提供的 XML 标记可以创建能够嵌入浏览器的 Windows User Interfaces。是不是只能用于 Microsoft Windows 呢?的确。但是还有 WPF/E,它为 Mac 和 Window
8、s 上的所有主流浏览器提供了 WPF 的一个功能子集。确实有不少选择。不过本文选择 SVG,并结合使用 PHP 为股票数据绘制一些图像。SVG 的 Hello World为了确保您的 Firefox V1.5 浏览器能够正确处理 SVG 或者有一个安装了 SVG 插件的浏览器,我提供了一个简单的类似 “Hello World” 的例子供您测试。首先,我创建了一个文件 start.html,如 清单 1 所示。作为引用 .svg 文件的网页。清单 1. Start.html然后需要一个 start.svg 文件,如 清单 2 所示。可以看到 .html 文件中 标记引用了该 .svg 文件。清单
9、 2. Start.svg在浏览器中打开该文件时,可以看到 图 2 所示的结果。图 2. .svg 测试文件回头再看看那个文件,其中定义了四条路径:两条从角到角,另外两条绿色的沿中心从上到下。如果看到该图像,说明您的浏览器至少能呈现 SVG 的一个小子集。获取数据绘图从数据开始。这里我使用两家公司 31 天的股票数据。包含股票数据的 XML 文档的一部分如 清单 3 所示。清单 3. data.xml 片段.格式很简单。根标记 包含一组 标记。每个 标记包含一些 day 标记,带有 high、low 和 close 属性。接下来要编写将该文件读入内存以便用于生成 SVG 的代码。这里我用了三个
10、 PHP 类,如 清单 4 所示。清单 4. Data.phplow = $low;$this-high = $high;$this-close = $close;class Tracevar $days = array();var $high = 0.0;var $low = 100000.0;public function addDay( $low, $high, $close )$this-days = new Day( $low, $high, $close );if ( $low low ) $this-low = $low;if ( $high $this-high ) $this
11、-high = $high;public function hiloPath( $trans )$p = new Path();$d = 0;foreach( $this-days as $day )$x = $trans-xscale( $d );$y = $trans-yscale( $day-low );$p-add( $x, $y );$d += 1;for( $d = (count( $this-days ) - 1); $d = 0; $d -= 1 )$x = $trans-xscale( $d );$y = $trans-yscale( $this-days$d-high );
12、$p-add( $x, $y );return $p;public function closePath( $trans )$p = new Path();$d = 0;foreach( $this-days as $day )$x = $trans-xscale( $d );$y = $trans-yscale( $day-close );$p-add( $x, $y );$d += 1;return $p;class Datavar $traces = array();var $high = 0;var $low = 100000;function parseXML( $file )$da
13、ta_dom = new DomDocument();$data_dom-load( $file );$elStocks = $data_dom-getElementsByTagName( stock );foreach( $elStocks as $stock )$trace = new Trace();$days = $stock-getElementsByTagName( day );foreach( $days as $day )$trace-addDay( (float)$day-getAttribute(low),(float)$day-getAttribute(high),(fl
14、oat)$day-getAttribute(close) );$this-traces = $trace;if ( $trace-high $this-high ) $this-high = $trace-high;if ( $trace-low low ) $this-low = $trace-low;Data 类包含文件中的所有数据。每支股票存储在一个 Trace 对象中。每 Trace 对象包含一组 Day 对象,定义了当天的最高价、最低价和收盘价。此外,Trace 对象知道如何为图像中的最高、最低和收盘价部分创建 Path 对象。Path 对象是一个 PHP 类,是我自己为了方便创建
15、SVG 路径专门编写的。这个帮助器类在 svg.php 文件中定义,如 清单 5 所示。清单 5. Svg.phpox + ( ( $x - $this-xoffset ) * $this-xscale );public function yscale( $y ) return $this-oy - ( ( $y - $this-yoffset ) * $this-yscale );class Pointvar $x;var $y;public function _construct( $x, $y )$this-x = $x;$this-y = $y;class Pathprivate $p
16、oints = array();public function add( $x, $y )$this-points = new Point( $x, $y );public function toSVG()$svg = “;$svg .= “M “.$this-points0-x.“ “.$this-points0-y.“ “;foreach( $this-points as $pt ) $svg .= “L “.$pt-x.“ “.$pt-y.“ “;return $svg;?这里定义了两个类,都很容易理解:Point 和 Path。Point 类表示一个 X-Y 值对,Path 类是一组
17、Points。ITransform 接口和 Transform 基类更有意思。为了绘制股票图像,必须将美元和每日数据的尺度转化成 SVG 视图坐标。ITransform 接口就是为了完成这项任务而定义的,但是不仅仅完成该任务。绘制图像我将分阶段逐步完成整个图像。版本 1:绘制收盘价路径第一个版本中仅仅绘制收盘价路径,这样可以看出 data.xml 文件中的两支股票在 31 天中的走势。清单 6 显示了第一个版本的代码。清单 6. graph.php 的第一个版本ox = $ox;$this-oy = $oy;$this-xscale = $width / ( $days - 1 );$this
18、-yscale = $height / ( $high - $low );$this-yoffset = $low;$d = new Data();$d-parseXML( data.xml );$ystart = (int)$d-low - 2;$yend = (int)$d-high + 2;$gt = new GraphTransform( 20, 90, 80, 80,$ystart, $yend, count( $d-traces0-days ) );header( “Content-type: text/xml“ );echo( “n“ );?.close fill-opacity
19、: 0; stroke-opacity:0.8;stroke-width: 0.4; traces as $trace )$closePath = $trace-closePath( $gt );$closeSVG = $closePath-toSVG();$color = “#ff0000“;?“ class=“close“fill=“stroke=“/文件的一开始创建了 ITransform 接口的一个版本,它把美元值沿着 Y 轴、当日价格沿着 X 轴转化成 SVG 的点。然后在文件的后面,使用 foreach 迭代器遍历数据中的轨迹列表。对于每个轨迹,使用 SVG Path ($clos
20、eSVG) 获得收盘价,并将该值使用 toSVG() 方法转化成 SVG 绘图命令。然后创建一个 SVG 标记,并使用 PHP echo 命令写入绘图命令以及 fill 和 color 值。浏览引用该图像的 .html 文件时得到了如 图 3 所示的结果。图 3. 第一个版本看起来不怎么样,但它是正确的,这仅仅是开始。版本 2:撑起收盘线下一步要用稍微亮一点的图形撑起收盘线,它表示当日的最高价和最低价。这样可以让客户了解股票价格的变化。为此修改了 PHP 绘图文件,如 清单 7 所示。清单 7. graph.php 的第二个版本.close fill-opacity: 0; stroke-opacity:0.8; stroke-width: 0.4; .hilo fill-opacity: 0.2; stroke-opacity:0.8; stroke-width: 0.1;