我承認,我是一個圖像癡迷者。從孩提時候起我就喜歡有漂亮圖像的計算機。這也是與 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 圖像的大背景中。
我承認,我是一個圖像癡迷者。從孩提時候起我就喜歡有漂亮圖像的計算機。這也是與 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 的 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