现如今,随着 web2.0 及其相关技术越来越流行,web 矢量图技术也变得越来越重要,尤其是在 web 数据的可视化方面,矢量图技术发挥着不可磨灭的作用。Dojo,一个流行且强大的 web 开发控件库,有着一个专门针对 web 矢量图开发的控件包 -- "dojox.charting",里面封装了很多功能完善的矢量图控件。开发人员只需要基于已有的数据序列,再加上极少量的代码,就可以快速的画出复杂且美观的 web 矢量图形。与此同时,开发人员也可以通过设定相关参数以实现基于矢量图本身的交互效果。"dojox.charting"控件包不仅包括基本矢量图的接口(如:线状图、柱状图、饼状图等等),也包括很多复杂的工业级的矢量图控件(如股票走势图、雷达图、甘特图等等)。这篇文章会介绍一些"dojox.charting"的高级编程技巧,如 x,y 轴的特殊定制,plot 定制的一些特殊的参数,以及 3-D 的矢量图等等,让开发人员能够更加升入的了解 Dojo 的矢量图控件库。
简介
Dojo 有一个非常强大的矢量图组件库—— dojox.charting 组件库,你只需要很少的代码,就能在页面上画出功能强大且美观的矢量图形(如柱状图,饼图等等)。与此同时,对于高级的用户, dojox.charting 也提供了许多定制参数,满足许多更为深层次的需要,如坐标轴的定制,矢量图类型的扩展定制以及基于数据序列(data series)的图形控件的定制。本文着力于探讨 dojox.charting 组件库的这些鲜为人知的高级功能。
高级矢量图
Dojox charting 图库除了很多基本的矢量图外(线图,柱状图等等),还有一些偏向于商业应用行业的图,举其中的 High-low 矢量图和 Candlesticks 矢量图为例:
High-low 矢量图
这种矢量图主要用于查看走势,以下是该图形的例图:
图 1. High-low 矢量图
该图形创建方法如下:
清单 1. High-low 矢量图
(new dojox.charting.Chart(node)). addPlot("default",{type: "Candlesticks", gap: 1, minBarSize: 2, maxBarSize: 5 }). addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true}). addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true}). addSeries("Series A", [ { open: 24, close: 8, high: 24, low: 5 }, { open: 8, close: 16, high: 22, low: 2 }, { open: 16, close: 12, high: 19, low: 7 }, { open: 12, close: 20, high: 22, low: 8 }, { open: 20, close: 16, high: 22, low: 8 }, ................ ],{ stroke: { color: "green" }, fill: "lightgreen" } ). render();
可以看出,除了 series,其它图形属性与一般基本图形无异。Series 的设定多出了四种属性:“open”,“close”,“high”,“low”,一般情况下,对应着初始值,结束值,最高值和最低值。
与此同时,该图形还可以设定 minBarSize 和 maxBarSize ,以限定该图形(plot)中柱子的最大宽度和最小宽度,单位为 px。
Candlesticks 矢量图
这种矢量图比 high-low 图更加完善,更多的用于股票行情走势,以下是该图形的例图:
图 2. Candlesticks 矢量图
该图形创建方法与 high-low 矢量图基本类似,如下:
清单 2. Candlesticks 矢量图
(new dojox.charting.Chart(node)). addPlot("default", {type: "Candlesticks", gap: 1}). addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true}). addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true}). addSeries("Series A", [ { open: 20, close: 16, high: 22, low: 8 }, { open: 16, close: 22, high: 26, low: 6, mid: 18 }, { open: 22, close: 18, high: 22, low: 11, mid: 21 }, { open: 18, close: 29, high: 32, low: 14, mid: 27 }, { open: 29, close: 24, high: 29, low: 13, mid: 27 }, { open: 24, close: 8, high: 24, low: 5 }, { open: 8, close: 16, high: 22, low: 2 }, { open: 16, close: 12, high: 19, low: 7 }, { open: 12, close: 20, high: 22, low: 8 }, { open: 20, close: 16, high: 22, low: 8 }, { open: 16, close: 22, high: 26, low: 6, mid: 18 }, { open: 22, close: 18, high: 22, low: 11, mid: 21 }, ................ ],{ stroke: { color: "green" }, fill: "lightgreen" } ). render();
可以看出,Series 设定的四种属性:“open”,“close”,“high”,“low”,可以说分别对应着股票的开盘点,收盘点,最高点以及最低点。Candlesticks 可以设定其“mid”属性,表示着当日平均值,更加接近股票走势图。
Spider 矢量图
这种图俗称蛛网图,又名雷达图。当你需要看到某个对象的一系列属性情况时,雷达图是再适合不过了。比如,需要查看某个人的综合素质(专业素质,管理能力,沟通能力等等),或者某个公司的各项财务指标(主营业务收入,辅助业务收入,营业外收入等等),用雷达图能非常清晰直观明了的看出来其各项指标的情况。雷达图还有一个最为强大的功能:比较不同对象间各个相同属性的优劣,如比较不同人的各项综合素质,又如:比较不同国家的各项经济环境指标。
图 3. Spider 矢量图
如图,可以清晰的看到各个国家(中,法,德,美等等)的各项经济环境指标(GDP 国民生产总值,Population 人口,Inflation 通货膨胀情况等等)。很明显,中国的人口最多,但是国民生产总值很一般。
使用 Spider 矢量图也很容易:
清单 3. Spider 矢量图
chart1 = new dojox.charting.Chart("test1"); chart1.setTheme(dojox.charting.themes.PlotKit.blue); chart1.addPlot("default", { type: "Spider", labelOffset: -10, divisions: 6, axisColor: "lightgray", spiderColor: "silver", seriesFillAlpha: 0.2, spiderOrigin: 0.16, markerSize: 3, precision:0, spiderType: "polygon" }); chart1.addSeries("China", {data: {"GDP": 2,"area": 6,"population": 2000,"inflation": 15, "growth": 12}}, { fill: "blue" } ); chart1.addSeries("France", {data: {"GDP": 6,"area": 15,"population": 500,"inflation": 5, "growth": 6}}, { fill: "red" }); chart1.addSeries("USA", {data: {"GDP": 3,"area": 20,"population": 1500,"inflation": 10, "growth": 3}}, { fill: "green" }); chart1.addSeries("Japan", {data: {"GDP": 4,"area": 2,"population": 1000,"inflation": 20, "growth": 2}}, { fill: "yellow" }); chart1.addSeries("Korean", {data: {"GDP": 10,"area": 10,"population": 800,"inflation": 2, "growth": 18}}, { fill: "orange" }); chart1.addSeries("Canada", {data: {"GDP": 1,"area": 18,"population": 300,"inflation": 3, "growth": 15}}, { fill: "purple" }); chart1.render();
雷达图(或称蛛网图)有很多参数,这些参数都有默认值,使用时可以不用给它们一一设定,如果需要做一些高级个性化定制,可以参考如下参数设定描述:
labelOffset: -10, // 坐标轴标签偏移量,负值代表箭头向外方向的 偏移量,以 px 为单位
divisions: 6, // 坐标轴截段数
axisColor: "lightgray", // 坐标轴的颜色
spiderColor: "silver", // 蛛网格的颜色
axisWidth: 0, // 坐标轴的宽度
spiderWidth: 0, // 蛛网格的宽度
seriesFillAlpha: 0.2, // 各多边形序列图的透明度
spiderOrigin: 0.16, // 各坐标轴起始点占坐标轴总长的比例
markerSize: 3, // 多边形序列图顶点的半径
spiderType: "polygon" // 蛛网格的形式,可以为多边形,也可以为圆形
animationType: dojo.fx.easing.backOut,// 变换动画类型
其中 divisions 不宜设置过大(默认为 3):过多则容易使坐标轴上的坐标太过密集而不易阅读。也不宜过少:过少则无法区分和比较各个不同对象的属性值情况。labelOffset 通常推荐使用负值,让其置于箭头之外。
animationType 支持所有的 dojo.fx.easing 的动画模式。
回页首
3-D 图库
Dojo 现在主要支持两种 3-D 矢量图:圆柱状 3-D 和条状 3-D
圆柱状 3-D 图
图 4. 圆柱状 3-D 图
从图上可以看出,它主要包括 3 个数据序列,并分别以红,黄,蓝三种方式呈现,这种 3-D 立体的方式能更加直观的看出趋势以及它们相互之间的差别。其用法也是比较简单的:
清单 4. 圆柱状矢量图
var m = dojox.gfx3d.matrix; var chart = new dojox.charting.Chart3D("test", { lights: [{direction: {x: 5, y: 5, z: -5}, color: "white"}], ambient: {color:"white", intensity: 2}, specular: "white" }, [m.cameraRotateXg(10), m.cameraRotateYg(-10), m.scale(0.8), m.cameraTranslate(-50, -50, 0)] ); var plot1 = new dojox.charting.plot3d.Cylinders(500, 500, {gap: 10, material: "yellow"}); plot1.setData([1,2,3,2,1,2,3,4,5]); chart.addPlot(plot1); var plot2 = new dojox.charting.plot3d.Cylinders(500, 500, {gap: 10, material: "red"}); plot2.setData([2,3,4,3,2,3,4,5,5]); chart.addPlot(plot2); var plot3 = new dojox.charting.plot3d.Cylinders(500, 500, {gap: 10, material: "blue"}); plot3.setData([3,4,5,4,3,4,5,5,5]); chart.addPlot(plot3); chart.generate().render();
这里的用法可能稍显繁琐,是因为里面配置了不少参数,这些参数都是有默认值的,所以大部分可以省略。这里我们来详细介绍一下这些参数:
Lights:这里主要用于配置光源,其中的 direction 就是光源的光照方向,color 为光的颜色。
Ambient:设定物体环境参数
Specular:反射参数设定
m.cameraRotateXg(10),m.cameraRotateYg(10):坐标旋转参数
m.scale(0.8):镜头的缩放参数定制
m.cameraTranslate(-50, -50, 0):镜头的偏移,分别对应(x 轴,y 轴,z 轴)
Material:三维物体的材质,主要用于配置其外观肤色。
条状 3-D 图
条状 3-D 图类似于横向的柱状 3-D 图,其参数配置与柱状 3-D 图基本一致。
回页首
矢量图 widget 控件
清单 5. Dojo 矢量图 widget
<div dojoType="dojox.data.HtmlStore" dataId="tableExample" jsId="tableStore"></div> <table id="tableExample" style="display: none;"> <thead> <tr><th>value</th> </tr> </thead> <tbody> <tr><td>6.3</td></tr> <tr><td>1.8</td></tr> <tr><td>3 </td></tr> <tr><td>0.5</td></tr> <tr><td>4.4</td></tr> <tr><td>2.7</td></tr> <tr><td>2 </td></tr> </tbody> </table> <div dojoType="dojox.charting.widget.Chart2D" theme="dojox.charting.themes.Tufte" margins="{ l: 0, r: 0, t: 0, b: 0 }" style="width: 100px; height: 15px;"> <div class="plot" name="default" type="Lines" ></div> <div class="series" name="Series A" store="tableStore" valueFn="Number(x)"></div> </div>
事实上 Dojo 矢量图 widget 可以直接写在 HTML 代码里,同我们经常用到的 Dojo widget 一样。上述代码就是一个线状图的 Dojo 矢量图 widget,其数值序列基于 dojox.data.HtmlStore 的“tableStore”,其它属性也是通过在 HTML 里直接设定,可以看出,此时的 Dojo 矢量图 widget 就如同一个矢量图的 Dojo 控件(widget)。
清单 6. Sparkline 矢量图 widget
<div dojoType="dojox.charting.widget.Sparkline" style="width: 100px; height: 15px;" store="msftStore" field="Close" count="100" sort="[{ attribute: 'Date', descending: false }]" valueFn="Number(x)" ></div>
这是另一种矢量图 widget,可以看出其属性设定与之前的稍有区别,这种写法与我们熟悉的 Dojo widget 就几乎一模一样了。
回页首
Dojo Data 矢量图
所谓 Data Chart,其实有点像 Dojo 的 Data Grid,用过 dojox.grid 的人可能都知道,它其实就是一个基于 dojo.data 的控件,即其数据内容是来自于 dojo.data 对象,通过该对象加载最原始的数据,然后填充到 Dojo chart 中。这种 Data Chart 适用于数据量比较大的情况。
Data 矢量图
图 5. Data 矢量图例图
Dojo Data 矢量图支持几乎所有的基本矢量图,其区别主要是它的数据源不再是简单的数组或对象数组,而是一个包装了原始 JSON 数据的 dojo.data 对象。
清单 7. 创建 Data 矢量图
var linesC = new dojox.charting.DataChart("linesC", { displayRange:6, xaxis:{labelFunc:"seriesLabels"}, yaxis:{minorLabels:true}, type: dojox.charting.plot2d.Lines, comparative:true }); linesC.setStore(store, {symbol:"*"}, "price");
其中,store 的数据结构如下:
清单 8. Data 矢量图数据源的数据结构
{ "identifier": "symbol", "idAttribute":"symbol", "label": "symbol","items": [ { "symbol":"ANDT", "price":3.52, "low":3.13, "high":3.69 }, { "symbol":"ATEU", "price":6.76, "low":6.56, "high":6.77 }, { "symbol":"BGCN", "price":3.98, "low":3.77, "high":4.11 }, { "symbol":"BAYC", "price":9.60, "low":9.60, "high":9.81 }, { "symbol":"CRCR", "price":8.44, "low":8.22, "high":8.44 }, { "symbol":"DTOA", "price":2.47, "low":2.11, "high":3.01 } ]}
这里详细解释一下它的各个参数:
displayRange:限制 x 轴的范围,其值代表坐标轴主标记的限制数量。
labelFunc:坐标轴标签生成函数,这里的“seriesLabels”是 Data 矢量图内置的一个坐标轴标签生成函数,表示标签由该 Data 矢量图数据源的 label 属性值替代。这里的数据源的 label 属性值为“symbol”对应的值,所以我们可以看到“AUNT”,“ATEU”等等。
setStore(store, {symbol:"*"}, "price"):这里表示 Data 矢量图的 Y 轴数据源为该 Data 矢量图数据源中的“price”属性值构成。这里注意,“{symbol:"*"}”是用于过滤 Identifier 属性的,这里“*”号相当于是没做任何过滤。
Data Series
Data Series 也是基于 dojo.data 构建矢量图数据源序列。在使用 Data Series 时,其它的初始化 Dojo chart 的方式不变,只有在“addSeries”的时候,使用 dojox.charting.DataSeries 对象。
清单 9. 使用 Data Series 序列
chartL = new dojox.charting.Chart("lines"). setTheme(dojox.charting.themes.ThreeD). addAxis("x", {fixLower: "major", fixUpper: "major", natural: true, majorTickStep: 5}). addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", includeZero: true}). addPlot("default", {type: dojox.charting.plot2d.Markers}). addSeries("Price", new dojox.charting.DataSeries( store, {query: {symbol: "*"}}, "price")). render();
可以看出,其参数设置方式和 Data 矢量图的“setStore”基本一样。“{query: {symbol: "*"}}”这里也是用于过滤。
Store Series
类似 Data Series,Dojo 矢量图还能够基于 dojo.store 构建数据源序列。
图 6. Dojo 矢量图(Store Series)例图
清单 10. 使用 Store Series 序列
dojo.xhrGet({ url: "stock.json", sync: true, handleAs: "json" }).then(function(data){ store = dojo.store.Observable(new dojo.store.Memory({data:data})); }); chartC = new dojox.charting.Chart("cols"). setTheme(dojox.charting.themes.ThreeD). addAxis("x", {natural: true}). addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", includeZero: true}). addPlot("default", {type: dojox.charting.plot2d.Columns}). addSeries("Low", new dojox.charting.StoreSeries( store, {query: {}}, dojo.hitch(null, valTrans, "low"))). addSeries("Price", new dojox.charting.StoreSeries( store, {query: {}}, dojo.hitch(null, valTrans, "price"))). addSeries("High", new dojox.charting.StoreSeries( store, {query: {}}, dojo.hitch(null, valTrans, "high"))). render();
可见,其使用方式与 Data Series 基本相同,但是它的数据源是基于 dojo.store 的,所以,其有些参数的设定是按照 dojo.store 的参数设定方式。
回页首
特殊定制参数
Dojo 矢量图中其实有很多地方是可以定制的,但是由于种种原因,这些定制参数可能并不为人所知,这里我们来详细讨论一下这些参数。
手动触发图形的相关事件
使用过 Dojo 矢量图的人可能知道,Dojo 矢量图可以加入很多交互效果,它支持鼠标的 onmouseover,onmouseout,onclick 等等交互事件。
图 7. Dojo Chart fire event 例图
图 7 说明,对“Series 3”的第 3 段手动执行“mouseover”的操作,于是该区域(红框)会出现和鼠标悬停在上一样的效果。其使用方式其实很简单:
清单 11. chart.fireEvent 介绍
chart.fireEvent( "Series 3", "onmouseover", "3" );
利用这种功能 , 可以做出很多很好的 Dojo 矢量图的附加效果 .
数据序列中支持空 ("Null") 值
其实这是 Dojo 矢量图的内置功能,并不需要程序员额外的参数配置。
图 8. “NULL”值的例图
程序员只需要给没有数据的地方置“null”即可。
清单 12. “NULL”的用法
new dojox.charting.Chart("test" + (i + 1)) .addAxis("x", { vertical: false, min: 0, max: 15 }) .addAxis("y", { vertical: true, min: 0, max: 15 }) .addPlot("default", { type: type }) .addSeries("Series A", [3, 5, null, 4, 0, 4, null, 1, 6, 5] , { stroke: {color: "blue"}, fill: "lightblue" }) .addSeries("Series B", [6, 9, null, 5, 10, 7, 6, 3, 8, 6] , { stroke: {color: "red"}, fill: "lightpink" }) .render();
将数据源序列某些之置“NULL”,即可达到截断效果。
对图形块和数据序列的重新排序
Dojo 矢量图支持多个图形区域(plot),即在一个矢量图上可以显示多种类型的矢量图,如线状图和柱状图并存,多个泡状图并存。这些图形区域均有一定的前后关系。
图 9. 多个 Bubble plot 的 Dojo chart
由图 9 可见,最先渲染的 plot1 离原点最近。如果我们改变它们的顺序,则显示方式会变化,见图 10。(chart.setPlotOrder(["plot2", "plot1", "plot3"]) 或者 chart1.movePlotToFront("Plot1"))
图 10. 重新排序 plot 后的多 Bubble plot 的 Dojo 矢量图
可见,顺序变成了红 1,绿 2,蓝 3,于是红色的 plot 离原点最近。
其实,dojo 矢量图的 plot 对应的数据序列 series 也能排序,其中排在最前面的会显示在最上方,然后依次遮盖排在后面的 series。(chart.setSeriesOrder(["plot2", "plot1", "plot3"]) 或者 chart1.moveSeriesToFront("Plot1"))
图 11. 重新排序 series 后的多 Bubble plot 的 Dojo 矢量图
坐标轴 (Axis) 的高级定制
关于 Dojo 矢量图坐标轴的定制也有很多高级用法。
清单 13. 坐标轴 label 旋转
chart.addAxis("x", { htmlLabels: false, fixLower: "major", fixUpper: "major", min: 0, max: 6, labels: [ {value: 0, text: "zero"}, {value: 1, text: "one"}, {value: 2, text: "two"}, {value: 3, text: "three"}, {value: 4, text: "four"}, {value: 5, text: "five"}, {value: 6, text: "six"} ], rotation: 45 })
只需要设定 rotation 参数即可实现坐标轴的旋转,这里是 x 轴 label 逆时针旋转 45 度。
图 12. 旋转的 label
坐标轴标签(label)不仅能旋转,还可以截断,这对于比较长的 label 相当有效。
清单 14. 坐标轴标签(label)截断
var chart1 = new dojox.charting.Chart("test1"). addAxis("x", { fixLower: "major", fixUpper: "major", includeZero: true, labels: [ {value: 0, text: "first start point"}, {value: 2, text: "two"}, {value: 5, text: "last end point"} ], maxLabelSize: 20, trailingSymbol: "." }).
“maxLabelSize”代表 label 可显示的最大长度(以 px 为单位),如果超出,则用省略符“。。。”替代,当然,这里的省略符也可以定制,在“trailingSymbol”中可以设定其为任意字符。当鼠标悬浮在 label 上的时候,后在 tooltip 中显示全部 label 信息。
图 13. 截断的 label
而且,在 dojo1.6 中引入了 Dojo 矢量图标题和坐标轴标题,其设定也很简单,如下:
清单 15. Dojo 矢量图标题
var chart2 = new dojox.charting.Chart("test2", { title: "Production(Quantity)", titlePos: "bottom", titleGap: 25, titleFont: "normal normal normal 15pt Arial", titleFontColor: "orange" })
清单 16. 坐标轴 标题
chart1.addAxis("x", { title: "Quarter of 2010", titleGap: gap, titleFontColor: "orange", titleOrientation: orientation, rotation: rotation, includeZero: true, natural: true, fixLower: "major", fixUpper: "major", labels: [ {value: 0, text: ""},{value: 1, text: "1 Qtr"},{value: 2, text: "2 Qtr"}, {value: 3, text: "3 Qtr"},{value: 4, text: "4 Qtr"},{value: 5, text: "End"} ] })
可见,通过“title”,“titleGap”, “titleFontColor”等等属性,可以定制你要的 title 展示样式:
titleGap:表示 title 离主矢量图边缘的距离。
titleOrientation:表示 title 的朝向,此属性专用于定制坐标轴标题,如果为“axis”,则代表文字朝向坐标轴,“away”表示文字“背对”坐标轴。
图 14. Dojo chart 的标题
另外,在一个矢量图中,可以随时隐藏坐标轴
清单 17. 坐标轴隐藏
var chart3 = new dojox.charting.Chart("test3"). setTheme(customTheme). addAxis("x", {type: "Invisible", fixLower: "major", fixUpper: "major", natural: true, includeZero: true}). addAxis("y", {type: "Invisible",vertical: true, fixLower: "major", fixUpper: "major", natural: true, includeZero: true}). addPlot("default", {type: "Markers"}). addSeries("Series A", [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 1}]). addSeries("Series B", [{x: 3, y: 2}, {x: 4, y: 3}, {x: 5, y: 2}]). addSeries("Series C", [{x: 5, y: 3}, {x: 6, y: 4}, {x: 7, y: 3}]). render();
这里其实只需要加上一个“type: "Invisible"”即可。
Tension 的定制
Tension 字面上讲是“张力”的意思,其实这在 Dojo 矢量图里面主要用于对线状图的定制,点与点之间可以用直线连接,也可以用曲线甚至贝叶斯曲线的方式连接。
清单 18. Tension 定制
var chart = new dojox.charting.Chart("test_chart"). setTheme(theme). addAxis("x", {natural: true, min: 0, max: 3}). addAxis("y", {vertical: true, natural: true, min: 0, max: 3}). addPlot("S tension", {tension: "S"}). addSeries("\"S\" tension", data, {plot: "S tension"}). addPlot("x tension", {tension: "x"}). addSeries("\"x\" tension", data, {plot: "x tension"}). addPlot("X tension", {tension: "X"}). addSeries("\"X\" tension", data, {plot: "X tension"}). addPlot("no tension", {markers: true, tension: ""}). addSeries("No Tension", data, {plot: "no tension"}). render();
定制很简单,只需要在 plot 的参数里面加上“tension”参数即可,程序员可以参考如下示意图:
图 15. Tension 定制效果
回页首
矢量图皮肤的定制
可以通过“dojox/charting/tests/test_themes.html”案例来查看 Dojo 皮肤的效果,进而选择相应的自己喜欢的皮肤
图 16. 皮肤定制
所有“dojox.charting.themes”下面的对象都是 Dojo 支持的皮肤类型,开发人员只需要“charts.setTheme(dojox.charting.themes.claro).render();”一句话即可实现皮肤的切换。
回页首
结束语
这篇文章介绍了 Dojo 开发中关于 Dojo 矢量图开发的一些更深层次的知识,基于 Dojo 的实例源代码,阐述了 Dojo 矢量图控件的很多高级功能,高级参数的定制以及高级对象的使用方式等等,同时,也介绍了 Dojo 矢量图数据源的多种定制方式,包括与 dojo.data,dojo.store 结合的方式。最后还提到了一些比较特殊的定制参数,这些大小细节我们可以在开发过程中尽量注意一下,以尽可能多的完善我们的 Web 应用。
原文链接:
http://www.ibm.com/developerworks/cn/web/1109_zhouxiang_dojocharting/