级别: 中级 Jack D Herrington (jherr@pobox.com), 高级软件工程师, Leverage Software Inc.
2006 年 10 月 16 日 学习如何使用 XML、PHP 和 Adobe 的可缩放向量图形(SVG)标准创建商业图像。SVG 标准为图像提供了无限级的向量缩放、可视化效果以及基于脚本的交互性。
我承认,我是一个图像痴迷者。从孩提时候起我就喜欢有漂亮图像的计算机。这也是与 TRS-80 相比我更喜欢 Apple II 计算机的原因。不过有谁不喜欢图片呢?谁不曾被 Pixar 电影征服?“一张图片胜过千言万语” 这句老话没有错,因为一幅图片能够又快又容易地传达大量信息。
图像对于商业数据的重要性其他任何地方都比不上。通过 SVG 之类的标准来充分利用图像代码非常重要,因为众所周知,Web 上早就不缺乏图像了。当然可以把图片放在网页中,但通常这些图片的作用不大。这些照片不能缩放和滚动,不能交互,不能很好地打印和调整比例。不过,我相信 Web 2.0 将改变这种局面。不再需要强调这种技术的重要性。本文的目的是给用户以包括图像的体验。打开 Google 的 Finance 页面,如 图 1 所示。查看股票的时候,可以使用交互式图像控件滚动数据,找到感兴趣的地方。是否使用 Macromedia Flash 实现有关系吗?没有。重要的是最终结果 —— 客户体验。
图 1. Google Finance 页面
本文将通过例子说明如何使用 Adobe SVG 格式和 PHP 编程语言创建漂亮的交互式图形。首先,了解一下 SVG 的背景知识及其与 Web 图像技术的关系。
可缩放向量图形
Adobe 的 SVG 标准是一种基于 XML 的表示向量图形的格式。基础是直线、矩形、形状、图片和文本这些元素。所有这些元素都在 “视图坐标” 中指定,坐标值不是像素,只是适合应用程序的需要而定义的任意数值范围。这样就可以将 XML 指定的图像模型呈现到任意的图像空间中 —— 无论多么大或者多么小 —— 并进行适当的缩放。向量图像可以用打印机的最大分辨率打印,不会出现位图放大打印时常见的锯齿。
这种格式还允许对任何对象或对象组应用特效。其中包括投影、斜角、纹理、外测发光、内测发光等等。如果熟悉 Adobe PhotoShop 或 Elements,就会知道这些效果。还可以使用旋转、倾斜、透明、剪裁等技术。
不仅如此,SVG 的标记还可用于动态改变这些属性,因此可以沿着路径移动图形对象或者实现淡入淡出效果。此外,SVG 还允许在模型中添加 JavaScript 代码,为图形元素、效果和动画加上行为。
我第一次看到 SVG 的时候,立刻被它吸引并钻了进去。照我看来,Adobe 是把 PhotoShop 引擎变成了能够嵌入到网页中的控件。事实上,今天看来仍然如此。不足之处是,我发现并非所有的客户机上都安装了 SVG,而且安装它需要下载某些软件。我不可能这样要求客户。因此有段时间我把 SVG 放在了一边,直到最近发现 SVG 的一个子集安装到了 Mozilla Firefox V1.5 中。现在,我想对于 SVG 来说事情将向好的方面转化。
但是我们再后退一步,将 SVG 置于 Web 图像的大背景中。
SVG 和它所属的世界
几年以前,如果想为 Web 创建图像,选择很有限。您可以即时创建 PHP 图片和建立 .jpeg、.gif 或 .png 文件。但是这些图像往往很简陋,因为 PHP 图像库非常原始,不支持特效。此外图像的缩放也不够好。
技术有了一点进步,现在有更多的选择了。当然,其中包括 SVG。但是还有 Flash。Flash 可以使用画布对象和 JavaScript 代码绘制任何图像。还能够从服务器上直接以 XML 或 JavaScript Serialized Object Notation (JSON) 的形式把数据读入到图像中。
在 Flash 之上有两种相对较新的技术。第一个是 Adobe Flex,这种基于 XML 标记的语言可以呈现为 Flash,并且包含图形库。Flex 的竞争对手之一是 Laszlo。Laszlo 也使用标记,优点是开放源代码。
还可以在浏览器中使用 <canvas> 标记。这种新标记是一个画布,可以在其上绘制直线和矩形、放置图片、进行旋转等。听起来似乎不错,但 Microsoft® Internet Explorer 不支持它 —— 目前来说。幸运的是,Google 有一个开放源代码项目 InternetCanvas,在 Internet Explorer 中提供了相同的能力。
刚出现的另一种选择是 Microsoft Windows Presentation Foundation (WPF),它提供的 XML 标记可以创建能够嵌入浏览器的 Windows User Interfaces。是不是只能用于 Microsoft Windows® 呢?的确。但是还有 WPF/E,它为 Mac 和 Windows 上的所有主流浏览器提供了 WPF 的一个功能子集。
确实有不少选择。不过本文选择 SVG,并结合使用 PHP 为股票数据绘制一些图像。
SVG 的 Hello World
为了确保您的 Firefox V1.5 浏览器能够正确处理 SVG 或者有一个安装了 SVG 插件的浏览器,我提供了一个简单的类似 “Hello World” 的例子供您测试。首先,我创建了一个文件 start.html,如 清单 1 所示。作为引用 .svg 文件的网页。
清单 1. Start.html
<html>
<body>
<embed src="start.svg" height="300" width="300" type="image/svg+xml"
pluginspage="http://www.adobe.com/svg/viewer/install/"
style="border: 1px solid black; padding:5px;"/>
</body>
</html>
|
然后需要一个 start.svg 文件,如 清单 2 所示。可以看到 .html 文件中 <embed> 标记引用了该 .svg 文件。
清单 2. Start.svg
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg style="shape-rendering:geometricPrecision;"
viewBox="0 0 100 100" xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet">
<path fill-rule="nonzero"
style="fill:#000000;stroke:#FF0000;"
d="M0 0 L100 100 Z"/>
<path fill-rule="nonzero"
style="fill:#000000;stroke:#00FF00;"
d="M50 0 L50 100 Z"/>
<path fill-rule="nonzero"
style="fill:#000000;stroke:#FF0000;"
d="M0 100 L100 0 Z"/>
<path fill-rule="nonzero"
style="fill:#000000;stroke:#00FF00;"
d="M0 50 L100 50 Z"/>
</svg>
|
在浏览器中打开该文件时,可以看到 图 2 所示的结果。
图 2. .svg 测试文件
回头再看看那个文件,其中定义了四条路径:两条从角到角,另外两条绿色的沿中心从上到下。如果看到该图像,说明您的浏览器至少能呈现 SVG 的一个小子集。
获取数据
绘图从数据开始。这里我使用两家公司 31 天的股票数据。包含股票数据的 XML 文档的一部分如 清单 3 所示。
清单 3. data.xml 片段
<stocks>
<stock>
<day high="35.13" low="32" close="33.75" />
<day high="32.25" low="28.75" close="31.75" />
<day high="29" low="28.5" close="28.87" />
<day high="29.25" low="28.75" close="28.75" />
<day high="29.5" low="28.5" close="29.25" />
<day high="30.25" low="29" close="29.25" />
...
</stock>
</stocks>
|
格式很简单。根标记 <stocks> 包含一组 <stock> 标记。每个 <stock> 标记包含一些 day 标记,带有 high、low 和 close 属性。
接下来要编写将该文件读入内存以便用于生成 SVG 的代码。这里我用了三个 PHP 类,如 清单 4 所示。
清单 4. Data.php
<?php
class Day
{
var $low;
var $high;
var $close;
public function __construct( $low, $high, $close )
{
$this->low = $low;
$this->high = $high;
$this->close = $close;
}
}
class Trace
{
var $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 < $this->low ) $this->low = $low;
if ( $high > $this->high ) $this->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 );
$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 Data
{
var $traces = array();
var $high = 0;
var $low = 100000;
function parseXML( $file )
{
$data_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'),
(float)$day->getAttribute('close') );
}
$this->traces []= $trace;
if ( $trace->high > $this->high ) $this->high = $trace->high;
if ( $trace->low < $this->low ) $this->low = $trace->low;
}
}
}
?>
|
Data 类包含文件中的所有数据。每支股票存储在一个 Trace 对象中。每 Trace 对象包含一组 Day 对象,定义了当天的最高价、最低价和收盘价。此外,Trace 对象知道如何为图像中的最高、最低和收盘价部分创建 Path 对象。
Path 对象是一个 PHP 类,是我自己为了方便创建 SVG 路径专门编写的。这个帮助器类在 svg.php 文件中定义,如 清单 5 所示。
清单 5. Svg.php
<?php
interface ITransform
{
function xscale( $x );
function yscale( $x );
}
class Transform implements ITransform
{
protected $ox;
protected $oy;
protected $xscale;
protected $yscale;
protected $xoffset = 0;
protected $yoffset = 0;
public function xscale( $x ) {
return $this->ox + ( ( $x - $this->xoffset ) * $this->xscale );
}
public function yscale( $y ) {
return $this->oy - ( ( $y - $this->yoffset ) * $this->yscale );
}
}
class Point
{
var $x;
var $y;
public function __construct( $x, $y )
{
$this->x = $x;
$this->y = $y;
}
}
class Path
{
private $points = array();
public function add( $x, $y )
{
$this->points []= new Point( $x, $y );
}
public function toSVG()
{
$svg = "";
$svg .= "M ".$this->points[0]->x." ".$this->points[0]->y." ";
foreach( $this->points as $pt ) {
$svg .= "L ".$pt->x." ".$pt->y." ";
}
return $svg;
}
}
?>
|
这里定义了两个类,都很容易理解:Point 和 Path。Point 类表示一个 X-Y 值对,Path 类是一组 Points。ITransform 接口和 Transform 基类更有意思。为了绘制股票图像,必须将美元和每日数据的尺度转化成 SVG 视图坐标。ITransform 接口就是为了完成这项任务而定义的,但是不仅仅完成该任务。
绘制图像
我将分阶段逐步完成整个图像。
版本 1:绘制收盘价路径
第一个版本中仅仅绘制收盘价路径,这样可以看出 data.xml 文件中的两支股票在 31 天中的走势。清单 6 显示了第一个版本的代码。
清单 6. graph.php 的第一个版本
<?php
require_once( 'svg.php' );
require_once( 'data.php' );
class GraphTransform extends Transform
{
public function __construct( $ox, $oy, $width,
$height, $low, $high, $days )
{
$this->ox = $ox;
$this->oy = $oy;
$this->xscale = $width / ( $days - 1 );
$this->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->traces[0]->days ) );
header( "Content-type: text/xml" );
echo( "<?xml version=\"1.0\" standalone=\"no\"?>\n" );
?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg style="shape-rendering:geometricPrecision;"
viewBox="0 0 100 100" xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet">
<style type="text/css">
.close { fill-opacity: 0; stroke-opacity:0.8;
stroke-width: 0.4; }
</style>
<?php
foreach( $d->traces as $trace )
{
$closePath = $trace->closePath( $gt );
$closeSVG = $closePath->toSVG();
$color = "#ff0000";
?>
<path fill-rule="nonzero"
d="<?php echo( $closeSVG ); ?>" class="close"
fill="<?php echo( $color ); ?>"
stroke="<?php echo( $color ); ?>"
/>
<?php
}
?>
</svg>
|
文件的一开始创建了 ITransform 接口的一个版本,它把美元值沿着 Y 轴、当日价格沿着 X 轴转化成 SVG 的点。然后在文件的后面,使用 foreach 迭代器遍历数据中的轨迹列表。对于每个轨迹,使用 SVG Path ($closeSVG) 获得收盘价,并将该值使用 toSVG() 方法转化成 SVG 绘图命令。然后创建一个 SVG <path> 标记,并使用 PHP echo 命令写入绘图命令以及 fill 和 color 值。
浏览引用该图像的 .html 文件时得到了如 图 3 所示的结果。
图 3. 第一个版本
看起来不怎么样,但它是正确的,这仅仅是开始。
版本 2:撑起收盘线
下一步要用稍微亮一点的图形撑起收盘线,它表示当日的最高价和最低价。这样可以让客户了解股票价格的变化。
为此修改了 PHP 绘图文件,如 清单 7 所示。
清单 7. graph.php 的第二个版本
<style type="text/css">
.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; }
</style>
<?php
$colors = array( '#ff0000','#0000ff','#00ff00' );
$c = 0;
foreach( $d->traces as $trace )
{
$closePath = $trace->closePath( $gt );
$closeSVG = $closePath->toSVG();
$hiloPath = $trace->hiloPath( $gt );
$hiloSVG = $hiloPath->toSVG();
$color = $colors[$c];
$c += 1;
?>
<path fill-rule="nonzero"
d="<?php echo( $hiloSVG ); ?>" class="hilo"
fill="<?php echo( $color ); ?>"
stroke="<?php echo( $color ); ?>"
/>
<path fill-rule="nonzero"
d="<?php echo( $closeSVG ); ?>" class="close"
fill="<?php echo( $color ); ?>"
stroke="<?php echo( $color ); ?>"
/>
<?php
}
?>
|
与原来的收盘线基本类似,只不过使用了 hiloPath。然后我使用 close 类创建了另一条路径。要注意 SVG 如何运用了将级联样式表(CSS)类应用于不同图形对象的思想。可以在类级别或者实例级别设置笔触、透明度、填充以及其他任何图形样式,如果需要甚至可以使用脚本随时改变。
图 4 显示了修改后的版本。
图 4. 显示了最低价和最高价的图像
现在可以看到股价的变动了。如果仔细观察图像的开始部分,会看到最低价和最高价图形的交叠,就是说透过红色的踪迹仍然能看到蓝色的踪迹。
版本 3:添加边框和文本
下一个版本中增加了边框和表明美元价格的文本。变化如 清单 8 所示。
清单 8. graph.php 的第三个版本
<style type="text/css">
.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; }
.range { text-anchor: end; font-size: 5pt; }
</style>
...
<line x1="18" y1="10" x2="20" y2="10"
stroke="black" stroke-width="0.2" />
<line x1="20" y1="10" x2="20" y2="92"
stroke="black" stroke-width="0.2" />
<line x1="18" y1="90" x2="100" y2="90"
stroke="black" stroke-width="0.2" />
<line x1="100" y1="90" x2="100" y2="92"
stroke="black" stroke-width="0.2" />
<text x="18" y="12" class="range"><?php echo($yend); ?>
</text>
<text x="18" y="92" class="range"><?php echo($ystart); ?>
</text>
</svg>
|
呈现图像的 PHP 代码没有变。仅仅在 Y 轴上画了几条线和一些文本。修改后的版本如 图 5 所示。
图 5. 带有边框和 Y 轴坐标值的图像
版本 4:添加背景
最后一步在数据后面增加了一点渐变填充的背景色来突出数据。清单 9 表明添加这种填充效果是多么简单。
清单 9. 渐进填充
<svg style="shape-rendering:geometricPrecision;"
viewBox="0 0 100 100" xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="BackGradient"
gradientUnits="objectBoundingBox"
gradientTransform="rotate(90)">
<stop offset="0%" stop-color="#ccc"/>
<stop offset="50%" stop-color="white"/>
<stop offset="100%" stop-color="#ccc"/>
</linearGradient>
</defs>
<style type="text/css">
.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; }
.range { text-anchor: end; font-size: 5pt; }
.background { stroke-width: 0; fill:url(#BackGradient); }
</style>
<rect x="20" y="10" width="80" height="80" class="background" />
...
|
代码中的新类 background 使用了 BackGradient。linearGradient 填充在浅灰到白色之间有三站,然后再过渡到浅灰。结果如 图 6 所示。
图 6. 最终的图像
现在举例说明 SVG 中的缩放,我稍微修改了页面,增加了 <embed> 标记的宽和高。结果如 图 7 所示。
图 7. 图像的缩放
各处都得到了完美的放大,没有任何锯齿。是不是棒极了?……我告诉您,太棒了。我特别喜欢 SVG 对单位的处理,它非常适合程序员,无需直接使用像素。
制作 SVG 的其他方法
显然,PHP 不是创建 .svg 文件的惟一手段。任何编程语言 —— C#、C、Java™ 语言、Perl —— 都能创建 SVG 文本文件。Perl 的 Comprehensive Perl Archive Network (CPAN) 库中有一个 SVG 模块。PHP 有支持 SVG 的 PEAR 模块,使用 Java 的人可以考虑 Apache Foundation 的 Batik SVG Toolkit。
另一种替代语言是可扩展样式表语言转换(XSLT),很容易将 XML 数据文件转换成 .svg 文件。ChartSVG 是一个开放源代码的绘图项目,使用以 XSLT 编写的 SVG。
作为前端工具,Adobe Illustrator 和 GoLive 可以创建 .svg 文件。GNU Image Manipulation Program (GIMP) 也支持从路径导出 SVG。我最喜欢的绘图程序 OmniGraffle V4.0 也支持导出 SVG。至于科学应用程序,Mathematica 有一个导出 SVG 的扩展。
结束语
Firefox V1.5 中增加的 SVG,即便非常基本,仍然为 Web 设计者打开了一个新的世界。Firefox 的确不如 Internet Explorer 普及,但是它拥有不断增长的技术社区所特有的共享情结。共享为其他制造商提供了强大的动力,在其浏览器中包含更好的 SVG 支持,打开了为客户创造更丰富的图像体验的大门。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Contains start.html and start.svg | x-graphxmlsvg/start.zip | 1KB | HTTP |
|---|
| The data.xml file | x-graphxmlsvg/data.zip | 1KB | HTTP |
|---|
| The graph.php file | x-graphxmlsvg/graph.zip | 2KB | HTTP |
|---|
参考资料 学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
- PHP 主页:PHP 主页包含大量关于这种脚本语言的资料。
- W3C XML 页面:万维网联盟(W3C))XML 页面是进一步学习 XML 和相关标准的好去处。
- SVG 标准:W3C 也接纳了 SVG 标准。
- Adobe 的 SVG 网站:SVG 的优秀资源站点,提供了丰富的信息。
- Mozilla SVG Project:Mozilla SVG Project 解释了 Firefox 支持什么不支持什么。
- SVG.org:该 SVG 主页包括关于 SVG 的新闻和信息。
- SVG Essentials:David Eisenberg (O'Reilly, 2002) 撰写的 SVG Essentials 详细介绍了 SVG。
- 在 SVG 中添加交互性(developerWorks,2003 年 8 月):为 SVG 文档增加能够响应用户输入的交互元素。
- XML 问题: 使用 SVG 编程(developerWorks,2005 年4 月):了解 SVG 脚本和动画、如何通过 DOM 和其他常见 XML 工具以及库来操作 SVG。
- 交互式动态可伸缩向量图形(developerWorks,2003 年 6 月):使用 XSLT 和其他技术从 XML 数据动态创建 SVG 图像,比如支持 XML 的关系数据库管理系统。该教程中的例子使用 JavaScript,让用户能够动态控制用 SVG 呈现的建筑平面图的内容和形式。
- SVG-可伸缩向量图形介绍(developerWorks,2004 年 3 月):了解如何从数据库信息生成图像(如图形和图表),包括动画和交互性。该教程示范了创建 SVG 文档需要了解的概念,包括基本形状、路径、文本和绘图模型以及动画和脚本。
- IBM XML 1.1 认证:了解如何才能成为一名 IBM 认证的 XML 1.1 及相关技术的开发人员。
- XML:developerWorks 中国网站 XML 专区提供了各种技术文章和技巧、教程、标准和 IBM 红皮书。
获得产品和技术
- WPF 和 XAML:WPF 和可扩展应用程序标记语言(XAML)是 Microsoft 对 SVG 的回应。
- SVG CPAN 模块:该手册非常适合 Perl 程序员。
- Batik:Apache XML Project 的 Batik 是一种用于 Java 语言的 SVG 工具箱。
- ChartSVG:Hardcoded Software 的 ChartSVG 是一种用于创建 SVG 图像的 XSLT 转换系统。
- OmniGraffle:Omni Group 的 OmniGraffle 是一种用于商业和技术设计的 Macintosh 应用程序。
- Mathematica:Wolfram Research 的 Mathematica 是一种科学工具箱,能够导出 SVG。
关于作者
对本文的评价
|