大象目前從事的是工程管理方面的開(kāi)發(fā),在土木工程建設(shè)行業(yè)有一種時(shí)空線形圖,它是用實(shí)線,虛線,圖形等方式,來(lái)描述出工程進(jìn)度計(jì)劃中的數(shù)據(jù)信息的方法。呈現(xiàn)出來(lái)的一般是報(bào)表形式,多采用Excel來(lái)實(shí)現(xiàn)。
對(duì)于客戶來(lái)說(shuō),手動(dòng)繪制時(shí)空線形圖是比較麻煩的。因?yàn)橐嬀€,畫圖片。如果知道甘特圖或使用過(guò)Microsoft Project的朋友應(yīng)該很快就能明白,對(duì)于一個(gè)工程的進(jìn)度計(jì)劃來(lái)說(shuō),它包含有很多的任務(wù)項(xiàng),而這些任務(wù)項(xiàng)都需要在線形圖上表示出來(lái),因此手動(dòng)做的話工作量是很大的。現(xiàn)在有了系統(tǒng),就可以把這一任務(wù)交給計(jì)算機(jī)來(lái)完成,客戶只需要輸入數(shù)據(jù)即可。
系統(tǒng)中的時(shí)空線形圖最早是采用JavaScript腳本編寫的,后來(lái)由我改成了Flex版。先說(shuō)下實(shí)現(xiàn)的思路。因?yàn)橹皇秋@示和打印,沒(méi)有其它的要與用戶交互的需求。所以后臺(tái)數(shù)據(jù)是直接以XML流文件的形式,通過(guò)異步加載獲得。然后再利用Flex強(qiáng)大的XML解析能力處理這些數(shù)據(jù),最后就是根據(jù)這些數(shù)據(jù)使用Flash的API畫圖。呵呵,是不是很簡(jiǎn)單? 先看幾張截圖大致了解下。
本文的AS代碼是基于ActionScript 3.0,下面進(jìn)行詳細(xì)說(shuō)明。
1、獲得數(shù)據(jù)
我采用URLLoader.load方法實(shí)現(xiàn)。通過(guò)傳遞請(qǐng)求地址,注冊(cè)監(jiān)聽(tīng)器,然后在回調(diào)函數(shù)中就可以獲得XML數(shù)據(jù)了。當(dāng)然你也可以采用RemoteObject方式。
public function LoadXML():void {
var loader:URLLoader = new URLLoader();
loader.load(new URLRequest("/baseAction.do?method=ajaxData")); //這個(gè)地址可以直接是一個(gè)xml文件
loader.addEventListener(Event.COMPLETE, handleComplete);
}
private function handleComplete(event:Event):void {
var xml:XML = new XML(event.target.data);
……
}
這是個(gè)異步加載過(guò)程,當(dāng)數(shù)據(jù)在后臺(tái)獲取完成,并轉(zhuǎn)換為XML格式向前臺(tái)發(fā)送后,注冊(cè)的Event.COMPLETE事件類型就會(huì)觸發(fā)回調(diào)函數(shù)handleComplete,然后我們就能得到數(shù)據(jù)并將它轉(zhuǎn)型為XML對(duì)象。var loader:URLLoader = new URLLoader();
loader.load(new URLRequest("/baseAction.do?method=ajaxData")); //這個(gè)地址可以直接是一個(gè)xml文件
loader.addEventListener(Event.COMPLETE, handleComplete);
}
private function handleComplete(event:Event):void {
var xml:XML = new XML(event.target.data);
……
}
2、布局
我們現(xiàn)在有了數(shù)據(jù),下一步就是布局。本項(xiàng)目中的時(shí)空線形圖上有這些基本信息:序號(hào)、工程名稱、工期、標(biāo)段劃分和圖例。這其中的工期是一個(gè)時(shí)間跨度,對(duì)應(yīng)進(jìn)度計(jì)劃的最早開(kāi)始日期和最晚結(jié)束日期。我們?nèi)〉氖悄?/span>+月的形式來(lái)表示。而圖例則是對(duì)線形圖中的任務(wù)項(xiàng)列出對(duì)應(yīng)的說(shuō)明。大象當(dāng)時(shí)為了方便,使用Flex的Grid、GridRow、GridItem來(lái)生成布局框架,要是考慮性能方面的原因,應(yīng)該換成其它方法實(shí)現(xiàn),因?yàn)檫@種方式速度會(huì)有點(diǎn)慢,好在沒(méi)有其它的交互情況。那么先讓大象把做法講完,性能調(diào)優(yōu)的話題已經(jīng)超出本文的范圍了。
這里就是要不停的生成單元格,由單元格組成行,再由多行組成Grid,這跟html中的table、tr、td是一個(gè)道理。
var grid:Grid = new Grid(); //Grid只需要一個(gè)
var orderRow:GridRow = new GridRow(); //序號(hào)行
var projectRow:GridRow = new GridRow(); //工程名稱行
……
創(chuàng)建了行,我們同時(shí)還要向里面添加單元格,也即GridItem。這時(shí)就要根據(jù)得到的數(shù)據(jù)來(lái)處理了,是一個(gè)循環(huán)。var orderRow:GridRow = new GridRow(); //序號(hào)行
var projectRow:GridRow = new GridRow(); //工程名稱行
var spaces:* = xml.spaces.space; //可以直接用.符號(hào)來(lái)獲取XML文件的節(jié)點(diǎn)
for each(var space:Object in spaces){
orderRow.addChild(createTitleColItem(space.order,space.id)); //循環(huán)加載序號(hào)
projectRow.addChild(createVerTitleColItem(space.projectName); //循環(huán)加載工程名稱
}
createTitleColItem和createVerTitleColItem里面封裝了單元格的創(chuàng)建,寫成方法方便調(diào)用。for each(var space:Object in spaces){
orderRow.addChild(createTitleColItem(space.order,space.id)); //循環(huán)加載序號(hào)
projectRow.addChild(createVerTitleColItem(space.projectName); //循環(huán)加載工程名稱
}
工期的日期時(shí)間和標(biāo)段劃分也是同理,對(duì)于工期來(lái)說(shuō),數(shù)據(jù)都顯示在最左側(cè)一欄,其余的單元格都為空,這樣做是當(dāng)完成框架布局后,在這空白的區(qū)域繪圖。
好了,到這里布局完成,將grid加入到上一層容器,我用的是Canvas并且將它的寬度和高度設(shè)置和grid一樣,然后在Canvas外面再加上一層容器,比如VBox。然后給VBox一個(gè)合適的寬高(比較好的做法是取當(dāng)前顯示器的寬高,否則頁(yè)面顯示會(huì)有點(diǎn)問(wèn)題)。讓它比Canvas要小(一般來(lái)講,Canvas會(huì)比VBox大很多),這時(shí)就會(huì)出現(xiàn)滾動(dòng)條,拖動(dòng)滾動(dòng)條進(jìn)行查看。
3、計(jì)算坐標(biāo)
在這個(gè)應(yīng)用當(dāng)中,最重要的應(yīng)該就是計(jì)算坐標(biāo)了,不管是畫線還是畫圖,都需要坐標(biāo)來(lái)定位。那到底怎么計(jì)算坐標(biāo)呢?其實(shí)也不難,通過(guò)ID與日期就能把它們算出來(lái)。
①、X坐標(biāo)
注意在設(shè)置第一行序號(hào)的時(shí)候,createTitleColItem(space.order,space.id)這個(gè)方法的第二個(gè)參數(shù)就是一個(gè)ID,每個(gè)序號(hào)項(xiàng)的GridItem的id屬性都會(huì)保存space.id值,而任務(wù)數(shù)據(jù)項(xiàng)中也有一個(gè)這樣的ID,所以就可以通過(guò)查找ID來(lái)計(jì)算出它的水平位置來(lái)。
private function getX(position:String,orderChilds:Array):int{
var x:int = title_width; //最左側(cè)的標(biāo)題列寬
for(var i:int=1;i<orderChilds.length-1;i++){
if((child[i] as GridItem).id==position) break;
x += col_width; //普通列寬
}
return x;
}
position是任務(wù)項(xiàng)ID,orderChilds 是orderRow.getChildren()得到第一行的所有列返回的數(shù)組。任務(wù)項(xiàng)中會(huì)有兩個(gè)position,起始和終止。通過(guò)這兩個(gè)值可以求出x1與x2坐標(biāo)。var x:int = title_width; //最左側(cè)的標(biāo)題列寬
for(var i:int=1;i<orderChilds.length-1;i++){
if((child[i] as GridItem).id==position) break;
x += col_width; //普通列寬
}
return x;
}
②、Y坐標(biāo)
y坐標(biāo)的是通過(guò)任務(wù)項(xiàng)包含的開(kāi)始日期和結(jié)束日期與整個(gè)計(jì)劃的最晚日期進(jìn)行比較計(jì)算出來(lái)的。
private function getY(date:Date):int{
var rowSize:int = 5; //行數(shù),序號(hào)和工程名稱占據(jù)了五行
rowSize += Math.abs(dateDiff("m",date,this.endDate));
var day:int = date.getDate();
day = day > 30 ? 0 : 30-day;
return rowSize*title_height+Math.round((day%30/30)*title _height);
}
在應(yīng)用里我將行高都設(shè)為相同的值,工程名稱這行是四倍的行高。生成的布局框架里,日期時(shí)間是倒排序,它們之間的間隔是一個(gè)月。dateDiff就是計(jì)算當(dāng)前日期與最晚日期(this.endDate)之間相隔多少個(gè)月,而每相隔一個(gè)月,就是一行。因此通過(guò)這種方式計(jì)算出y坐標(biāo)。var rowSize:int = 5; //行數(shù),序號(hào)和工程名稱占據(jù)了五行
rowSize += Math.abs(dateDiff("m",date,this.endDate));
var day:int = date.getDate();
day = day > 30 ? 0 : 30-day;
return rowSize*title_height+Math.round((day%30/30)*title _height);
}
我補(bǔ)充說(shuō)明一下,因?yàn)樽罱K展現(xiàn)結(jié)果的是Canvas容器,所以坐標(biāo)的計(jì)算都是相對(duì)Canvas的,因此這個(gè)x和y的坐標(biāo)是相對(duì)Canvas內(nèi)的絕對(duì)坐標(biāo)。
4、繪圖
繪圖分為畫線和畫圖形。如果是畫線,則可以直接利用Flash的graphics來(lái)畫圖。畫圖形要麻煩點(diǎn),得先取得圖片,再用圖片來(lái)畫圖。不管是畫線還是畫圖形,都是和數(shù)據(jù)有關(guān)的。數(shù)據(jù)中會(huì)有一個(gè)類型,是說(shuō)明到底這個(gè)任務(wù)項(xiàng)是線還是圖。如果是線,會(huì)有一個(gè)顏色值,為了防止沒(méi)有設(shè)置顏色值,我們應(yīng)該給定一個(gè)默認(rèn)的顏色值。如果是圖,會(huì)有個(gè)圖片的相對(duì)地址,錄數(shù)據(jù)的時(shí)候會(huì)上傳圖片,不過(guò)為了防止沒(méi)有上傳圖片,我們應(yīng)該準(zhǔn)備一個(gè)默認(rèn)圖片。
①、畫線
var line:UIComponent = new UIComponent();
line.graphics.lineStyle(thickNum,color,1);//設(shè)置粗細(xì)、顏色、透明度
line.graphics.moveTo(x1,y1); //從某個(gè)坐標(biāo)開(kāi)始
line.graphics.lineTo(x2,y2); //畫到某個(gè)坐標(biāo)
②、畫圖line.graphics.lineStyle(thickNum,color,1);//設(shè)置粗細(xì)、顏色、透明度
line.graphics.moveTo(x1,y1); //從某個(gè)坐標(biāo)開(kāi)始
line.graphics.lineTo(x2,y2); //畫到某個(gè)坐標(biāo)
public function load (url:String,callback:Function,options:*):void{
var loader:Loader = new Loader();
loader.load(new URLRequest(encodeURI(url))); //圖片地址
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(e:Event):void {callback(e.currentTarget.content,options);});
}
load(url,paintRect,{"x1":x1,"y1":y1,"x2":x2,"y2":y2});
public function paintRect(source:*,options:*):void{
var shape:UIComponent = new UIComponent();
var rect_width:int = Math.abs(options.x2–options.x1); //圖形寬度
var rect_height:int = Math.abs(options.y2–options.y1); //圖形高度
var bitmap:BitmapData = new BitmapData(source.width, source.height); //用圖片源的寬高定義一個(gè)位圖對(duì)象
bitmap.draw(source); //將圖片源在位圖上繪制出來(lái)
shape.graphics.beginBitmapFill(bitmap); //用位圖填充繪圖區(qū)域
shape.graphics.drawRect(options.x1, options.x2,rect_width,rect_height); //定義矩形繪圖區(qū)域,這塊區(qū)域?qū)⒂脠D片填充
shape.graphics.endFill(); //應(yīng)用填充
}
load方法就是用來(lái)獲取圖片。注冊(cè)監(jiān)聽(tīng)器,這里我們還是使用Event.COMPLETE事件類型,當(dāng)圖片加載完成后,會(huì)調(diào)用回調(diào)函數(shù),并將參數(shù)也一起傳給回調(diào)函數(shù)。畫圖工作是在回調(diào)函數(shù)中進(jìn)行。另外有一點(diǎn)要說(shuō)明的是,Loader類是用來(lái)加載SWF文件或圖像(JPG、PNG 或 GIF)文件,而URLLoader 類則是加載文本或二進(jìn)制數(shù)據(jù),請(qǐng)大家使用的時(shí)候要注意這點(diǎn)區(qū)別。var loader:Loader = new Loader();
loader.load(new URLRequest(encodeURI(url))); //圖片地址
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(e:Event):void {callback(e.currentTarget.content,options);});
}
load(url,paintRect,{"x1":x1,"y1":y1,"x2":x2,"y2":y2});
public function paintRect(source:*,options:*):void{
var shape:UIComponent = new UIComponent();
var rect_width:int = Math.abs(options.x2–options.x1); //圖形寬度
var rect_height:int = Math.abs(options.y2–options.y1); //圖形高度
var bitmap:BitmapData = new BitmapData(source.width, source.height); //用圖片源的寬高定義一個(gè)位圖對(duì)象
bitmap.draw(source); //將圖片源在位圖上繪制出來(lái)
shape.graphics.beginBitmapFill(bitmap); //用位圖填充繪圖區(qū)域
shape.graphics.drawRect(options.x1, options.x2,rect_width,rect_height); //定義矩形繪圖區(qū)域,這塊區(qū)域?qū)⒂脠D片填充
shape.graphics.endFill(); //應(yīng)用填充
}
另外這里不管是畫線還是畫圖都用的是UIComponent類,這是因?yàn)樗幸粋€(gè)toolTip屬性,當(dāng)鼠標(biāo)移至圖或線上時(shí),會(huì)顯示你設(shè)置的信息。如果用Sprite則沒(méi)有這屬性。UIComponent是Flex定義的,而Sprite則是Flash本身的。
畫圖例的方法是一樣的,畫好的圖需要加入到父容器中顯示出來(lái)。到此整個(gè)做法就講完了,其實(shí)也不是很復(fù)雜,關(guān)鍵是要理清思路。因?yàn)樯婕暗缴虡I(yè)項(xiàng)目和保密原則,請(qǐng)?jiān)彺笙蟛荒軐⒋嗽创a拿出來(lái)給大家分享,不過(guò)我已經(jīng)提供了部分代碼,而且這些代碼是整個(gè)應(yīng)用中關(guān)鍵點(diǎn)。只要大家能從中得到一點(diǎn)幫助,那么我就很滿足了。
本文為菠蘿大象原創(chuàng),如要轉(zhuǎn)載請(qǐng)注明出處。