内容


掌握 Dojo 工具包,第 8 部分

明日之星 - DojoX

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 掌握 Dojo 工具包,第 8 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:掌握 Dojo 工具包,第 8 部分

敬请期待该系列的后续内容。

Dojo 作为最著名的 Ajax 开源项目之一,不仅让 Web 程序员可以免费获得和使用其框架进行 Web 应用的开发,更吸引了大量的开发者对其不断的扩充,开发新的组件。DojoX 就是在这样的开发社区中产生的。DojoX 是一组基于 dojo 的开源项目的集合,这些开源项目具有很好的创意和很高的实用性。这些 DojoX 项目有可能成长为一个稳定的版本保留在 DojoX 中,也有些可能会迁移到 Dojo Core 或者 Dijit 中。本文将对 DojoX 中的项目进行一个总体的概述,并结合实例介绍其中较为有特色的项目,本文主要会介绍 DataGrid,Charting,Gfx/Gfx 3D 和 DojoX Widget。

认识 DojoX

目前 DojoX 项目主要扩展了数据结构与算法、数据处理与通信、实用工具、图形 API 以及 Web UI 等。

涉及到数据结构与算法的项目包括了 DojoX Collections、DojoX Encoding 等。Collections 定义了很多非常有用的数据集合,包括了数组(ArrayList)、二叉树(BinaryTree)、字典(Dictionary)、迭代器(Iterator)、队列(Queue)、有序列表(SortedList)、堆栈(Stack)。这些集合的使用将大大提高程序开发的效率以及程序的质量。Encoding 不仅提供了字符串与字符编码的转换,还提供了对称算法河豚(Blowfish)和 MD5 数字摘要算法等。

DojoX Data、Embed、I/O、JSON、XML、RPC 等扩展了 Dojo 的数据处理与通信能力。其中,Data 项目提供了对更多数据格式的支持,包括了对 csv 文件以及 Google、Picasa 等提供的 API 的支持等等。

DojoX 的图形 API 扩展了 Dojo 的动画效果,并提供了 2D、3D 绘图的支持。DojoX Fx 通过对 dojo core 以及 dojo fx 的扩展提供了多种动画效果;gfx 提供了一系列矢量绘图的方法;而 gfx3d 则提供了一些简单的 3D 绘图 API。

而更加丰富的 Web UI 以及 Web 小部件也是 DojoX 的一大亮点。功能强大的 Grid、实用的 Charting、以及 DojoX Image 和 DojoX Layout 使得基于 dojo 开发的 Web UI 更加丰富。DojoX Widgets 中还提供了更加丰富的小部件可以满足大部分应用开发的需求。

除以上介绍的项目外,DojoX 还收集了很多实用工具,读者可以在 dojo API 网站上获得更多的信息。http://api.dojotoolkit.org/

接下来我们就来体验一下 DojoX 给我们带来的精彩吧

注意:本教程使用的 dojo 版本为 1.2.1,由于 1.2.x 版本里 dojo 以及 dojoX 的部分组件有较大变化,因此本文仅适用于 dojo1.2.x,对于 dojo1.0 的开发者本文仅供参考,部分代码不能正确运行

DojoX DataGrid

Grid 可能是 DojoX 中最受欢迎的部件,比起普通的 Web 表格部件,Grid 更像一个基于 Web 的 Excel 组件。这使得 Grid 足可以应付较为复杂的数据展示及数据操作。在 dojox1.2 中,dojox.grid 包中新增了 DataGrid 类,该类是对原 Grid 类的强化和替代,之所以叫做 DataGrid,是由于该类与 dojo 的数据操作类 store 无缝整合在一起。而之前的 Grid 需要将 store 对象包装为 model 对象才能使用。下文如果没有特殊声明,所有 Gird 或是 DataGrid 均指新版 DataGrid,而不是 Grid1.0。

图 1 .DojoX DataGrid
图 1 .DojoX DataGrid
图 1 .DojoX DataGrid

我们为什么需要 Grid 呢?下面列出了 Grid 的特性:

  • 用户只需向下拖动滚动条,Grid 即可加载延迟的记录,省去了翻页操作,减少 Web 与服务器交互,提高了性能;
  • 可以任意的增加和删除单元格、行、或者列;
  • 对行进行统计摘要,Grid 可以生成类似于 OLAP 分析的报表;
  • Grid 超越了二维表格的功能,它可以跨行或跨列合并单元格以满足不同的数据填充的需求;
  • 行列冻结功能,使得浏览数据更加灵活方便;
  • Grid 事件采用了钩子机制,我们可以通过 onStyle 钩子完成对样式的更改;
  • 单元格具备富操作,所有的 dijit 部件都可以在单元格中使用,并且单元格可以通过单击转换为编辑状态;
  • 可以为不同的单元格设置不同的上下文菜单;
  • Grid 嵌套,也就是说 Grid 可以在单元格中嵌套其他的 Grid,从而组成更为复杂的应用;

除此之外,Grid 还有具有其他很多特性,例如,非常实用的偶数行上色、灵活的选取功能、自动调整列宽、数据的展开 / 合闭等。

DataGrid 基础

要创建一个 DojoX DataGrid,就需要对 DataGrid 的基本工作过程有一个大致的了解。一个 DataGrid 实例的组成结构如下图所示,DojoX DataGrid 是使用 DataGrid 的基础,因此在使用 Grid 的时候需要加载相关的 dojox 包;一个小部件通常由框架和样式组成,因此,我们需要指定 DataGrid 的样式表并且声明 DataGrid 实例。DataGrid 实例会组合一个 Structure 和一个 Store。Structure 是一个表头及数据模型的定义,而 Store 用于承载数据。

图 2 .DataGrid 组成结构
图 2 .DataGrid 组成结构
图 2 .DataGrid 组成结构

下面开始我们的第一个 DataGrid 应用为了在 Web 页面上创建一个 DataGrid 小部件,我们从最基本的二维表格开始。首先我们需要加载一些样式,来保证 DataGrid 能够正常显示,清单 1

清单 1. 加载样式
 <style type="text/css"> 
 @import "dojox/grid/resources/tundraGrid.css"; 
 @import "dojo/resources/dojo.css"; 
 </style>

此处的 css 文件路径为相对于测试页面的相对路径。

在开始创建 Grid 之前,我们还要引入 Dojo 的基础包 dojo.js,以用来加载其他需要的 dojo 类,并加载 dojo.data.ItemFileReadStore 类以及 dojox.grid.DataGrid 类。接下来我们就可以着手开发第一个 DataGrid 了。首先是布局的定义,如 清单 2

清单 2. 定义布局
 var layout = [ 
		 {field: 'pro_no', name: 'Product Number' }, 
		 {field: 'pro', name: 'Product' }, 
		 {field: 'min_amount', name: 'Minimum Amount' }, 
		 {field: 'max_amount', name: 'Maximum Amount' }, 
		 {field: 'avg_amount', name: 'Average Amount' } 
 ];

这里定义了一个数组 layout,其中每一个成员表示一个列的定义,其中 field 指定了使用的数据项,该取值需要遵循 javascript 变量定义规则;name 为该列显示的名称。接下来是 store 的开发,代码如 清单 3

清单 3. 开发 store
 var sampleData = { 
 identifier: 'pro_no', 
 label: 'pro_no', 
 items: [ 
 {pro_no:'2100', pro:'A Series', min_amount:346, max_amount:931, avg_amount:647}, 
 {pro_no:'2200', pro:'B Series', min_amount:301, max_amount:894, avg_amount:608}, 
 {pro_no:'2300', pro:'C Series', min_amount:456, max_amount:791, avg_amount:532}, 
 {pro_no:'2400', pro:'D Series', min_amount:859, max_amount:2433, avg_amount:1840}, 
 {pro_no:'2500', pro:'E Series', min_amount:459, max_amount:1433, avg_amount:1040} 
 ] 
 }; 
 var jsonStore = new dojo.data.ItemFileReadStore({ data: sampleData });

在这里,我们首先定义一个 JSON 数据 sampleData,这里 identifier 是对于整行的唯一标识,因此在数据中不能出现重复;数组 items 是这个表格所显示的数据,其中数据必须完全符合 JSON 的语法,字符串两端必须使用引号,否则会出现语法错误,保险的办法是所有的值均用引号括住。

接下来,我们就要在网页的 Body 元素中定义 DataGrid 实例了,如 清单 4

清单 4. 定义 DataGrid 实例
 <div id="grid" dojoType="dojox.grid.DataGrid" store="jsonStore"
			 structure="layout" autoWidth="true" ></div>

dojoType 指定了该 Web 部件为 dojox.grid.DataGrid,数据使用 jsonStore,结构为 layout,自动调整宽度。到此,第一个 Grid 就已开发完毕,完整代码如 清单 5

清单 5. 完整代码
 <html> 
 <head> 
 <title>first Grid</title> 
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> 
 <style type="text/css"> 
 @import "dojox/grid/resources/tundraGrid.css"; 
 @import "dojo/resources/dojo.css"; 
 </style> 
 <script type="text/javascript" src="dojo/dojo.js" djConfig="parseOnLoad: true"></script> 
 <script type="text/javascript"> 
 dojo.require("dojo.data.ItemFileReadStore"); 
 dojo.require("dojox.grid.DataGrid"); 
 var sampleData = {identifier: 'pro_no',label: 'pro_no', 
 items: [ 
 {pro_no:'2100', pro:'A Series', min_amount:346, max_amount:931, avg_amount:647}, 
 {pro_no:'2200', pro:'B Series', min_amount:301, max_amount:894, avg_amount:608}, 
 {pro_no:'2300', pro:'C Series', min_amount:456, max_amount:791, avg_amount:532}, 
 {pro_no:'2400', pro:'D Series', min_amount:859, max_amount:2433, avg_amount:1840}, 
 {pro_no:'2500', pro:'E Series', min_amount:459, max_amount:1433, avg_amount:1040} 
 ]}; 
 var jsonStore = new dojo.data.ItemFileReadStore({ data: sampleData }); 
 var layout = [ 
 {field: 'pro_no', name: 'Product Number' }, 
 {field: 'pro', name: 'Product' }, 
 {field: 'min_amount', name: 'Minimum Amount' }, 
 {field: 'max_amount', name: 'Maximum Amount' }, 
 {field: 'avg_amount', name: 'Average Amount' }]; 
 </script> 
 </head> 
 <body class="tundra"> 
 <div class="heading">First Grid</div> 
 <div id="grid" dojoType="dojox.grid.DataGrid" store="jsonStore"
 structure="layout" autoWidth="true"></div> 
 </body> 
 </html>

在浏览器中运行,效果如下:

图 3. 第一个 Grid 运行结果
图 3. 第一个 Grid 运行结果
图 3. 第一个 Grid 运行结果

DataGrid 开发详解

DataGrid 的创建

在 DataGrid 的开发中,有三种方法创建 DataGrid 实例,第一种是 javascript 创建结构,html 代码创建实例,我们第一个例子就是使用这种方式实现的;

第二种是由 html 代码创建结构及实例,在这种方法中,我们使用 table 标签,定义 Grid 的结构,而省去了在 javascript 中定义 structure 的部分。具体定义方式与标准的 html 书写方式非常类似,定义方式如 清单 6

清单 6. 由 html 代码创建结构及实例
 <table id="gridNode" jsid="grid" dojoType="dojox.grid.DataGrid"
 store="jsonStore" rowsPerPage="20" region="center"> 
 <thead> 
   <tr> 
     <th field="pro_no" >Product Number</th> 
     <th field="pro" >Product</th> 
     <th field="min_amount" >Minimum Amount</th> 
     <th field="max_amount" >Maximum Amount</th> 
     <th field="avg_amount" >Average Amount</th> 
   </tr> 
 </thead> 
 </table>

第三种方式就是采用纯 javascript 的方式定义 DataGrid 实例,清单 7声明网页加载完成后就在 id 为 gridNode 的页面结点上创建一个 DataGrid 实例。

清单 7. 纯 javascript 的方式定义 DataGrid 实例
 dojo.addOnLoad(function(){  // 指定页面加载完毕后执行
 var grid = new dojox.grid.DataGrid({ 
	 query: { pro_no: '*' }, 
	 id: 'grid2', 
	 store: jsonStore, 
	 structure: [ 
		 {field: 'pro_no', name: 'Product Number' }, 
		 {field: 'pro', name: 'Product' }, 
		 {field: 'min_amount', name: 'Minimum Amount' }, 
		 {field: 'max_amount', name: 'Maximum Amount' }, 
		 {field: 'avg_amount', name: 'Average Amount' } 
 ],rowsPerPage: 20 
 	 }, 'gridNode');  // 设置 grid 显示在 id 为 gridNode 的节点下
 grid.startup();  // 启动 grid 
 });

Grid1.2 可以通过这种方式很方便的与 dojo 容器结合在一起,动态创建页面布局。

Structure 详解

DataGrid 不仅可以创建简单的二维表格,还可以通过对 structure 的设计创建复杂的表格应用,同时还可以为每一列进行格式化或是取值。我们将 First Grid 进行简单的修改,得到 清单 8的代码。

清单 8. 修改 First Grid
 function formatAmount(value){ 
	 return '$ ' + value; 
 } 
 function getRange(rowIndex, item){ 
	 if(!item){return '--';} 
	 var grid = dijit.byId('grid'); 
	 var max = grid.store.getValue(item, "max_amount"); 
	 var min = grid.store.getValue(item, "min_amount"); 
	 return max - min; 
 } 
 var subrow1 = [ 
 {field: 'pro_no', name: 'Product Number', rowSpan: 2 }, 
 {field: 'pro', name: 'Product', rowSpan: 2 }, 
 {field: 'min_amount', name: 'Min. Amount',formatter: formatAmount,width: '80px' }, 
 {field: 'avg_amount', name: 'Average Amount',formatter: formatAmount, rowSpan: 2 }, 
 {field: 'range', name: 'Range',get:getRange, rowSpan: 2 } 
 ]; 
 var subrow2 = [ 
	 {field: 'max_amount', name: 'Max. Amount',formatter: formatAmount}, 
 ]; 
 var layout = [subrow1,subrow2];

这里,我们从新定义了 layout,将 layout 分为两个子行,其中子行 1 包含了五个字段,其中 pro_no、pro、avg_amount、range 具有值为 2 的 rowSpan 属性,也就表明这三列跨越了两行。第二行仅有 max_amount 一个字段。同时,我们为三个 amount 字段指定了 formatter 函数,在其数值前添加美元符号。为 range 字段指定了 get 方法来自动获取最大值与最小值的差。

显示效果如下:

图 4. DataGrid 布局示例 1
图 4. DataGrid 布局示例 1
图 4. DataGrid 布局示例 1

除了 rowSpan 属性外我们还可以使用 colSpan 属性,这两个属性的用法与 html 中的用法一致,并且可以在 html 定义表结构中使用,我们再看这个表头的例子来理解一下 colSpan 的用法。

清单 9. colSpan 的应用
 var structure = [[ 
	 {field: 'type', name: 'Type', rowSpan: 2}, 
	 {field: 'pro', name: 'Product', rowSpan: 2}, 
	 {field: 'Q20071', name: 'Q1',formatter: formatAmount }, 
	 {field: 'Q20072', name: 'Q2',formatter: formatAmount }, 
	 {field: 'Q20073', name: 'Q3',formatter: formatAmount }, 
	 {field: 'Q20074', name: 'Q4',formatter: formatAmount } 
  ],[ 
	 {field: 'Y2007', name: 'Year 2007',formatter: formatAmount, colSpan: 4 } 
 ]];

清单 9的显示效果如下:

图 5. DataGrid 布局示例 2
图 5 . DataGrid 布局示例 2
图 5 . DataGrid 布局示例 2

Store 的使用

DataGrid 使用了 Store 作为数据源,在以上的例子中,我们都是将数据写在 javascript 中然后作为 data 参数值传给 Store 的构造方法。但是在大多数情况下,数据是要动态的通过 Ajax 请求从服务器端获取的,这同样可以通过 Store 来实现。我们仅需要将声明 Store 对象时传入请求的 url 地址即可,如:new dojo.data.ItemFileReadStore({url: 'jsondata.txt' }) 。Store 包括 dojo.data.ItemFileReadStore 和 dojo.data.ItemFileWriteStore 两个类。我们在使用 DataGrid 的编辑功能时需要使用 ItemFileWriteStore 来作为数据源。下面就演示了一个多功能的 DataGrid,这个 Grid 使用外部数据源,可以对单元格进行编辑,并且可以通过右击表头弹出列选菜单。为了页面能够正确,清单 10载入了所需的 CSS。

清单 10. 加载所需 CSS
 <style type="text/css"> 
 @import "dojox/grid/resources/tundraGrid.css"; 
 @import "dijit/themes/tundra/tundra.css"; 
 @import "dojo/resources/dojo.css"; 
 </style>

清单 11的代码引入了所需的 dojo 包,并创建了可编辑的 DataGrid,将其添加到了 id 为 gridNode 的页面节点中。为了使列具有编辑功能只需要在 structure 定义中的表示该列的 JSON 定义中添加值为 true 的 editable 属性。

清单 11. 引入所需 Dojo 包
 <script type="text/javascript" src="dojo/dojo.js" djConfig="parseOnLoad: true"></script> 
 <script type="text/javascript"> 
 dojo.require("dojo.data.ItemFileWriteStore"); 
 dojo.require("dojox.grid.DataGrid"); 
 // 下面两个包用来创建右键弹出菜单
 dojo.require("dijit.Menu"); 
 dojo.require("dojox.widget.PlaceholderMenuItem"); 
 // 使用 dataGrid.txt 中的数据作为填充 DataGrid 的数据
 var jsonStore = new dojo.data.ItemFileWriteStore({ url:'dataGrid.txt'}); 
 var layout = [ 
 {feld: 'emp_no', name: 'Employee Number'}, 
 {field: 'name', name: 'Name', editable: true },  // 该列可编辑
 {field: 'gender', name: 'Gender', editable:true, 
 type:dojox.grid.cells.Select, options:['F','M'] }, // 编辑该列时使用下拉菜单
 {field: 'dept_no', name: 'Deptment Number', editable:true, 
 type:dojox.grid.cells.Select, options:['730','731','732','733','734','735']}, 
 {field: 'bonus', name: 'Bonus', editable: true } 
 ]; 
 dojo.addOnLoad(function(){ 
 var grid = new dojox.grid.DataGrid({ 
 query: { emp_no: '*' },  // 查询字符串
 id: 'grid2',            //DataGrid 的 id 
 autoWidth:true,        // 自动调整宽度
 store: jsonStore,       // 使用 jsonStore 对象
 structure: layout,       // 使用 layout 对象定义的结构
 rowsPerPage: 20,       // 每页读取 20 条记录,保证 Web 浏览器的性能
 headerMenu: gridMenu  // 指定头菜单为 gridMenu 
			 }, 'gridNode'); 
			 grid.startup(); //grid 生效
			 }); 
			 </script>

清单 12定义了菜单 gridMenu 以及承载 DataGrid 的 DIV。

清单 12. 定义菜单
 <body class="tundra"> 
 <div class="heading">Data Grid</div> 
 <div dojoType="dijit.Menu" jsid="gridMenu" id="gridMenu" style="display: none;"> 
 <div dojoType="dojox.widget.PlaceholderMenuItem" label="GridColumns" ></div> 
 </div> 
 <div id="gridNode" style="height:350px;" /> 
 </body>

本例使用了外部数据源 dataGrid.txt,该文件的内容类似于 清单 13

清单 13. dataGrid.txt
 { 	 identifier: 'emp_no', 
	 label: 'emp_no', 
	 items: [ 
     {emp_no:'2100', name:'Matt', gender:'M', dept_no:730, bonus:647}, 
	 {emp_no:'2200', name:'Lisa', gender:'F', dept_no:731, bonus:608}, 
	 {emp_no:'2300', name:'Mick', gender:'M', dept_no:732, bonus:532}, 
	 {emp_no:'2400', name:'John', gender:'M', dept_no:733, bonus:1840}, 
	 {emp_no:'2500', name:'Jan', gender:'M', dept_no:734, bonus:1040}, 
	 {emp_no:'2101', name:'Jeff', gender:'M', dept_no:730, bonus:647}, 
	 {emp_no:'2202', name:'Frank', gender:'M', dept_no:731, bonus:608}, 
	 {emp_no:'2303', name:'Fred', gender:'M', dept_no:732, bonus:532} 
 ]}

运行,结果如下图所示。

图 6. 可编辑的 DataGrid
图 6. 可编辑的 DataGrid
图 6. 可编辑的 DataGrid

DojoX Charting

Charting 是基于 DojoX 绘图包的数据可视化组件,包括了 Chart2D 和 Chart3D 来分别绘制 2D 和 3D 的图表。Chart2D 提供多种样式的饼图、柱状图、折线图、面积图、网格等图表。Chart3D 目前仅提供了 3D 柱状图和 3D 圆柱图,并且从社区获取的信息表明由于 IE 上的性能问题导致 Chart3D 的开发暂时搁置。Charting 的应用主要分为如下几个步骤:

  1. 首先引入所需要的 dojox 类,如:
    dojo.require("dojox.charting.Chart2D"); //Chart2D 所需要的 2D 类
    dojo.require("dojox.charting.Chart3D"); //Chart3D 所需要的 3D 类
    dojo.require("dojox.charting.themes.PlotKit.blue"); // 样式主题
  2. 第二,声明 Chart 对象,包括了 Chart2D 或 Char3D,
    如:var chart1=new dojox.charting.Charting.Chart2D('chart1');
    这里传入的参数为要在页面中载入 chart1 的元素的 ID,也就是 chart1 显示后的上层标签的 ID;
  3. 使用 Chart 对象的 setTheme 为 Chart 对象设置主题,来保证准确的绘制图表;
  4. 使用 Chart 对象的 addPlot 方法为 Chart 对象添加部件,可以添加多个部件;
  5. 使用 Chart 对象的 addSeries 方法为 Chart 对象添加数据;
  6. 最后,调用 render 方法将 chart 对象添加到页面节点中;

下面我们来看几个应用实例。

2D 饼图

清单 14的代码为 2D 饼图,我们可以看到该实例加载了类 Chart2D 和 themes.PlotKit.blue 对象。chart1 对象声明在 ID 为 char1 的元素下,并被添加了一个 Pie 部件作为默认部件,数据为 3、2、5、1、6、4。

清单 14.2D 饼图
 <script type="text/javascript"> 
 dojo.require("dojox.charting.Chart2D"); 
 dojo.require("dojox.charting.themes.PlotKit.blue"); 
 makeObjects = function(){ 
 var chart1 = new dojox.charting.Chart2D("chart1"); 
 chart1.setTheme(dojox.charting.themes.PlotKit.blue); 
 chart1.addPlot("default", { 
    type: "Pie", 
     font: "normal normal bold 12pt Tahoma", 
     fontColor: "black", 
	 labelOffset: -25, 
	 precision: 0 
	 }); 
  chart1.addSeries("Series A", [3, 2, 5, 1,6,4]); 
  chart1.render(); 
	 }; 
 dojo.addOnLoad(makeObjects); 
 </script> 
……
 <div id="chart1" style="width: 300px; height: 300px;"></div>

运行结果如下:

图 7. 二维饼图示例
图 7. 二维饼图示例

带网格的 2D 面积图

这个例子中我们为 Chart2D 对象添加了两个部件,网格和面积图,如 清单 15。值得注意的是,我们为 chart 添加了两个部件,Plot1 和 Plot2,其中 Plot1 的类型为 Areas,Plot2 的类型为 Grid,我们给 Plot1 添加了三组数据,并没有给 Plot2 添加数据。

清单 15. 网格和面积图
 chart = new dojox.charting.Chart2D("chart2"); 
 chart.setTheme(dojox.charting.themes.PlotKit.orange); 
 chart.addAxis("x", {origin:"max"}); 
 chart.addAxis("y", {vertical: true, leftBottom: true, min: 5000, max: 8000, 
 majorTickStep: 500, minorTickStep: 100}); 
 chart.addPlot("plot1", {type: "Areas", hAxis:"x", vAxis:"y"}); 
 chart.addPlot("plot2", {type: "Grid", hAxis:"x", vAxis:"y"}); 
 data1 = [{x:10,y:7200}, {x:20,y:6800}, {x:30,y:7000}, {x:40,y:6600}, {x:50,y:7000}, 
 {x:60,y:6800}, {x:70,y:7200}, {x:80,y:6600}, {x:90,y:6800}, {x:100,y:7000}]; 
 data2 = [{x:10,y:6800}, {x:20,y:5800}, {x:30,y:6400}, {x:40,y:5600}, {x:50,y:6000}, 
 {x:60,y:6200}, {x:70,y:6600}, {x:80,y:7200}, {x:90,y:6300}, {x:100,y:6000}]; 
 data3 = [{x:10,y:6000}, {x:20,y:6300}, {x:30,y:6800}, {x:40,y:6200}, {x:50,y:6200}, 
 {x:60,y:6600}, {x:70,y:6300}, {x:80,y:6200}, {x:90,y:6000}, {x:100,y:5900}]; 
 chart.addSeries("series B", data2, {plot: "plot1"}); 
 chart.addSeries("series C", data3, {plot: "plot1"}); 
 chart.addSeries("series A", data1, {plot: "plot1"}); 
 chart.render();

运行结果如下:

图 8. 二维面积图示例
图 8 二维面积图示例
图 8 二维面积图示例

巧用折线图进行函数图像的绘制

我们可以使用折线图或者面积图完成函数图像的绘制,原理就是按照一定的步长循环将定义域中将 x,y 值计算出来组成一组数据添加的 Chart2D 对象中,下面这个例子就是使用了折线图绘制了正弦余弦曲线。

清单16绘制正弦余弦曲线

清单 16. 绘制正弦余弦曲线
 dojo.require("dojox.charting.Chart2D"); 
 dojo.require("dojox.charting.themes.PlotKit.blue"); 
 dojo.addOnLoad(function() { 
   var period = 2 * Math.PI; 
   var tick = Math.PI / 180.0; 
   var step = 5*Math.PI / 180.0; 
   var chart = new dojox.charting.Chart2D('chart_area'); 
   chart.setTheme(dojox.charting.themes.PlotKit.blue); 
   chart.addAxis("x", {min: 0, max: period, majorTickStep: tick*30, 
                   minorTickStep: tick*10, minorLabels: false, font: '40px bold'}); 
   chart.addAxis("y", {vertical: true, min: -1.01, max: 1, majorTickStep: 0.5, 
                   minorTickStep: 0.1, minorLabels: false, font: '40px bold'}); 
   chart.addPlot("default", {type: 'Lines'}); 
   chart.addPlot("grid", {type: "Grid", vMinorLines: true}); 
   var series = {'sin' : [], 'cos' : []}; 
   for(var i = 0; i < period; i+=step) { 
      series.sin.push({'x' : i, 'y' : Math.sin(i)}); 
      series.cos.push({'x' : i, 'y' : Math.cos(i)}); 
   } 
 chart.addSeries('sin', series.sin); 
 chart.addSeries('cos', series.cos); 
 chart.render(); 
 });

运行结果如下:

图 9. 正余弦曲线图示例
图 9. 正余弦曲线图示例
图 9. 正余弦曲线图示例

3D 柱状图的绘制

使用 Chart3D 与 Chart2D 略有不同,这主要是因为 3D 绘图比 2D 绘图要复杂一些,3D 绘图一个很重要的过程就是坐标变换,以及光照和渲染都是很重要的考虑要点。不过 Chart3D 已经对 gfx3D 进行了封装,我们只需要通过对简单的几个参数的设置就可以完成一个 3D 图表。我们可以从 清单 17中看出 Chart3D 对象的声明比 Chart2D 多了一些参数,这主要是两类,光照与摄像机。光照指的是 3D 物体的周围的光环境,其中 lights 是光源数组,也就是说 3D 物体可以接受多个光源的光照;ambient 为环境光,影响物体的各个立体面;specular 是镜面反射光,这是光源照射物体特定位置发生镜面反射所产生的高光。当然了,你甚至可以不用理解这些参数的意义,在实际使用中多次调整参数以满足自己的使用需求。

清单 17. 3D 绘图
 dojo.require("dojox.charting.Chart3D"); 
 dojo.require("dojox.charting.plot3d.Bars"); 
 dojo.require("dojox.charting.plot3d.Cylinders"); 

 makeObjects = function(){ 
 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.Bars(500, 500, {gap: 10, material: "yellow"}); 
 plot1.setData([2,1,2,1,1,1,2,3,5]); 
 chart.addPlot(plot1); 
 var plot2 = new dojox.charting.plot3d.Bars(500, 500, {gap: 10, material: "red"}); 
 plot2.setData([1,2,3,2,1,2,3,4,5]); 
 chart.addPlot(plot2); 
 var plot3 = new dojox.charting.plot3d.Cylinders (500, 500, {gap: 10, material: "#66F"}); 
 plot3.setData([2,3,4,3,2,3,4,5,5]); 
 chart.addPlot(plot3); 
 var plot4 = new dojox.charting.plot3d.Cylinders (500, 500, {gap: 10, material: "#E6F"}); 
 plot4.setData([3,4,5,4,3,4,5,5,5]); 
 chart.addPlot(plot4); 
 chart.generate().render(); 
 }; 
 dojo.addOnLoad(makeObjects);

运行这段代码,显示效果如下:

图 10. 三维柱状图示例
图 10. 三维柱状图示例
图 10. 三维柱状图示例

这个 3D 柱状图结合了长方体和圆柱体,dojox.charting.plot3d.Bars 是长方体的声明类,new dojox.charting.plot3d. Cylinders 是圆柱体的声明类。

DojoX Gfx 和 Gfx3D

DojoX Gfx 和 Gfx3D 是 DojoX 中进行绘图的两个包,分别提供了 2D 和 3D 的绘图 API。前面介绍的 DojoX Charting 就是在这两个包的基础上开发的。Gfx 以及 Gfx 3D 是一组矢量绘图 API。对于原生的矢量图形,Gfx 能够支持 SVG、Canvas 和 VML,新版本中又增加了对 Silverlight 的支持。首先,我们来看看 DojoX Gfx 可以做什么。在 dojo 官方网站公布的开发包中有这么几个 Gfx 例子,见下图。可以看出,Gfx 对于简单的 2D 绘图已经绰绰有余了。

图 11.Gfx 绘图
图 11.Gfx 绘图
图 11.Gfx 绘图

下面我们就来使用 Gfx 开始绘图。Gfx 绘图的基本步骤可以简单的归为两步:打开一个画布(surface),然后“画”。在绘画之前,我们要例行公事,引入 dojox.gfx 包。然后通过 dojox.gfx 对象的 createSurface 方法建立画布。Surface 提供了很多画笔来进行作画,这里我们介绍几个比较常用的。

  • 线性画笔:使用方法为 surface.createLine(opt),参数包含 x1、y1、x2、y2 四个属性,分别表示起点横纵坐标和终点横纵坐标;
  • 矩形画笔:使用方法为 surface.createRect(opt),参数 opt 是一个 JSON 对象,包括了属性 x、y、width、height,分别表示矩形左上角的横纵坐标以及举行的宽和高;
  • 圆形画笔:使用方法为 surface.createCircle(opt),参数 opt 具有三个属性,分别是圆心横坐标 cx、圆心纵坐标 cy、圆半径 r;
  • 路径画笔:这个是最强大的画笔,可以绘制任意曲线和图形,当然也是使用最复杂的画笔,使用方法为 surface.createPath(path),path 是描绘一组路径的字符串,描绘规则可以参考 SVG 中关于矢量路径的介绍。

除以上介绍的几种外还有点、折线、文字等画笔,并且还提供了组功能,可以使一组图形一起响应事件等。下面就看个简单的例子来加深理解。

清单 18. Gfx 绘图代码
 dojo.require("dojox.gfx"); 
 surface = dojox.gfx.createSurface(dojo.byId("gfx_holder"), 700, 700); 
 surface.createRect({x: 260, y: 260, width: 50, height: 50}).setFill("#AAF"); 
 surface.createLine({x1: 100, y1: 400, x2: 400, y2: 350}) 
	 .setStroke({color:"#9F3",width:5}); 
 surface.createCircle({cx: 200, cy: 200, r: 50}) 
	 .setFill("#FEF").setStroke({color: "#F9A", width: 3}); 
 var path="M153 334 " + 
		"C153 334 151 334 151 334 C151 339 153 344 156 344 " + 
		"C164 344 171 339 171 334 C171 322 164 314 156 314 " + 
		"C142 314 131 322 131 334 C131 350 142 364 156 364 " + 
		"C175 364 191 350 191 334 C191 311 175 294 156 294 " + 
		"C131 294 111 311 111 334 C111 361 131 384 156 384 " + 
		"C186 384 211 361 211 334 C211 300 186 274 156 274"
 surface.createPath(path).setFill("rgb(FF,FF,FF)").setStroke({color:"red",width:3});

清单 18中的代码将在 ID 为 gfx_holder 的 html 标记内添加这个绘图。这段代码中的 setFill 方法和 setStroke 方法分别用来设置填充效果和笔触效果。代码运行结果如下图所示。

图 12. Gfx 简单绘图示例
图 12. Gfx 简单绘图示例
图 12. Gfx 简单绘图示例

Gfx3D 的工作原理是采用计算机图形学的原理将三维空间中的物体按照透视规则从三维坐标系转换成二维的坐标系然后通过 SVG 等矢量图显示出来。限于浏览器和 javascript 的性能,目前 Gfx3D 仅能绘制较为简单的 3D 物体和空间曲线,还不能绘制复杂的空间曲面以及进行纹理等渲染工作,但是 Gfx3D 足以满足大部分 Web 应用的需要了。Gfx3D 的使用大致分三个步骤:建立画布,在画布上建立视图,在视图上建立 3D 物体。视图上必不可少的要设置光源以及摄像机方位。设置光源可以通过 setLights 方法来设定,摄像机方位则需要使用 dojox.gf3d.maxtrix 对象的 cameraRotateX、cameraRotateY、cameraRotateZ、cameraTranslate 等方法来确定。清单 19在画布上绘制了一个立方体和一个圆柱体。

清单 19. Gfx 3D 绘图
 dojo.require("dojox.gfx3d"); 

 makeObjects = function(){ 
 var surface = dojox.gfx.createSurface("test", 500, 500); 
 var view = surface.createViewport();  // 建立视图
 view.setLights([{direction: {x: 0, y: 0, z: -10}, color: "white"}, 
			 {direction: {x: 10, y: 0, z: -10}, color: "#444"}], 
 {color: "white", intensity: 2}, "white"); 
 var m = dojox.gfx3d.matrix;  // 建立空间
 var l = view.createCube({bottom: {x: 0, y: 0, z: 0}, top: {x: 100, y: 100, z: 100}}) 
		 .setFill({type: "plastic", finish: "dull", color: "lime"});  // 绘制立方体
 view.createCylinder({})   // 绘制圆柱体
    .setTransform([m.translate(200, 100,200), m.rotateZg(60), m.rotateXg(-60)]) 
	 .setStroke("black") 
	 .setFill({type: "plastic", finish: "glossy", color: "red"}); 
 var camera = [m.cameraRotateXg(20), m.cameraRotateYg(20), 
 m.cameraTranslate(-200, -200, 0)]; // 设置摄像机方位
 view.applyCameraTransform(camera); 
 view.render(); 
 }; 
 dojo.addOnLoad(makeObjects);

运行,结果如下图所示。

图 13. Gfx-3D 简单绘图示例
图 13. Gfx-3D 简单绘图示例
图 13. Gfx-3D 简单绘图示例

其他 DojoX Widget

DojoX 的 widget 包中还提供了更多的小部件,非常的方便易用,本文的最后再来两个餐后甜点,拾色器和鱼眼。拾色器是我们经常会用到的小部件,用 DojoX 生成拾色器非常简单,仅需要 清单 20这一小段代码即可。

清单 20. 拾色器
 <div id="pickerToo" dojoType="dojox.widget.ColorPicker"
 animatePoint="true"
 showHsv="true"
 showRgb="true"
 webSafe="true"
 onChange="alert(this.value)"> 
 </div>

除了这段 javascript 代码,为了能正确的显示拾色器的样式,我们还需要导入它的 css 文件:dojox/widget/ColorPicker/ColorPicker.css。代码中,属性 animatePoint 来确定指针是否有滑动动画,默认为 true;属性 showHsv 表示是否显示 HSV 颜色模式数值;属性 showRgb 表示是否显示 RGB 颜色模式的数值;webSafe 表示是否显示 Web 安全色。清单 20显示效果如下:

图 14 .DojoX 拾色器
图 14 .DojoX 拾色器
图 14 .DojoX 拾色器

鱼眼的实现也很简单,我们需要制作一组图标,命名为 fe1.gif、fe2.gif、fe3.gif 一直到 fe7.gif。我们将它们和页面放在同一文件夹下。然后在网页中书写如 清单 21所示代码。

清单 21. 鱼眼
 <div dojoType="dojox.widget.FisheyeList"
 itemWidth="40" itemHeight="40"
 itemMaxWidth="150" itemMaxHeight="150"
 orientation="horizontal"
 effectUnits="2"
 itemPadding="10"
 attachEdge="center"
 labelEdge="bottom"
 id="fisheye1"> 
 <div dojoType="dojox.widget.FisheyeListItem" id="item1"
 onclick="alert('click on ' + this.label !');" label="Item 1" iconSrc="fe1.gif"> 
 </div> 
 <div dojoType="dojox.widget.FisheyeListItem" label="Item 2" iconSrc="fe2.gif"></div> 
 <div dojoType="dojox.widget.FisheyeListItem" label="Item 3" iconSrc="fe3.gif"></div> 
 <div dojoType="dojox.widget.FisheyeListItem" label="Item 4" iconSrc="fe4.gif"></div> 
 <div dojoType="dojox.widget.FisheyeListItem" label="Item 5" iconSrc="fe5.gif"></div> 
 <div dojoType="dojox.widget.FisheyeListItem" label="Item 6" iconSrc="fe6.gif"></div> 
 </div>

运行,鼠标移上去来看看效果。

图 15.DojoX 鱼眼
图 15 DojoX 鱼眼
图 15 DojoX 鱼眼

结束语

JavaScript 并不是万能的,同样,使用 JavaScript 开发的 Dojo 也不是万能的,但是为了满足众多开发者的需要,DojoX 提供了非常丰富的选择,并且提供了对 Flash、SilverLight、Google Gear 等组件的支持,使得 Dojo 爱好者们可以开发出更好更强大的 Web 应用。随着 DojoX 项目的不断完善和成熟,Dojo 将给开发者们带来更多的惊喜。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=450815
ArticleTitle=掌握 Dojo 工具包,第 8 部分: 明日之星 - DojoX
publish-date=11302009