利用vml製作統計圖全攻略(餅圖)[轉自CSDN]

來源:互聯網
上載者:User
提要:

    VML是Microsoft IE 5.0及其後續版本內嵌的向量圖形實現,雖然MS也提倡日後使用SVG,但是作為一個IE內嵌的標記語言,在某些時候的應用還是比較方便的。本文通過完整的描述一個統計餅圖的建立過程,來展現VML在Web方面的魅力。文章通過實現一個JavaScript類,讀者能夠完整地看到整個餅圖的製作過程。

    VML(Vector Markup Language)自從問世以來似乎都處於默默無聞的地步,直到現在為止,情況依然沒有改變。其實細心的一點你就可以發現,在Web方面,MS得很多產品還是內建使用了,最典型的就是Office的快取圖案,將word或者ppt文檔儲存成html,如果文檔內部使用了快取圖案,你就可以看到那些圖形使用過VML來表述的,另外一個典型的應用就是Visio的匯出到web這個工具。

    前段時間,James在CSDN發表了使用ASP產生統計圖的例子,我也認真拜讀了其中的代碼,並且也針對一些問題找他請教了,James的顏色感覺非常好,可惜我沒有那樣的功底,因此在代碼實現中我就使用了隨機顏色來實現,可能整體的介面看起來會稍微差勁一點。

    統計圖比較典型的是餅圖,柱狀圖,曲線圖,本文著重講解Pie的製作過程,文章採用了JavaScript實現了一個類,如果相關Javascript物件導向不是特別瞭解的,可以參考我另外的文章《物件導向的Javascript編程》和《再論物件導向的JavaScript編程》。

     

      暫且不考慮如何?,我們先看看代碼最終的使用如何。

      objPie=new VMLPie("600px","450px","人口統計圖"); //初始化寬度,高度,標題
        objPie.BorderWidth=3; //圖表邊框
        objPie.BorderColor="blue"; //圖表邊框顏色
        objPie.Width="800px"; //定義圖表寬度
        objPie.Height="600px"; //定義圖表高度       
        objPie.backgroundColor="#ffffff"; //定義背景顏色
        objPie.Shadow=true; //是否需要陰影 true為是 false為不要陰影
        //添加圖表資料
        //順序為名稱,值,描述
        objPie.AddData("北京",50,"北京的人口");
        objPie.AddData("上海",52,"上海的固定人口");
        objPie.AddData("天津",30,"天津的外地人口");
        objPie.AddData("西安",58,"西安城市人口");
        objPie.AddData("武漢",30,"武漢的外地人口");
        objPie.AddData("重慶",58,"重慶城市人口");
        result.innerHTML=objPie.Draw(); //產生VML資料

      這段代碼就是最終的調用,我封裝了一個VMLPie的JavaScript類,而本文的重點也就是詳細地描述類的具體實現過程,另外要使用VML必須作如下的聲明。

1.  HTML Tag的名字空間聲明

<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">

2.  <head>之間的style聲明

                <STYLE>
                          v\:* { BEHAVIOR: url(#default#VML) }
                          o\:* { BEHAVIOR: url(#default#VML) }
                          .shape { BEHAVIOR: url(#default#VML) }
                </STYLE>
完成了上述工作之後,代碼就能夠完整的工作了。

現在我們開始講述這個VMLPie的實現了,首先,我將那些實現的函數作一個簡單的說明。

//VMLPic主函數,提供建立一個VMLPie執行個體
function VMLPie(pWidth,pHeight,pCaption,pContainer){}
//開始畫圖,將圖形畫到指定的容器上面
VMLPie.prototype.Draw=function(){}
//畫餅圖的各個塊
VMLPie.prototype.CreatePie=function(){}
//介面功能:
//        放大或者縮小圖形
//參數說明:
//        iValue:放大的或者縮小的倍數,1為原圖大小,0.5原圖的50%,2為原圖的
//            兩倍,以此類推
VMLPie.prototype.Zoom=function (iValue){}
//介面功能:
//        添加餅圖資料
//參數說明:
//        sName:資料標籤名稱
//        sValue:資料值
//        sTooltipText:資料描述
VMLPie.prototype.AddData=function(sName,sValue,sTooltipText){}
//介面功能:
//        清除所有資料
VMLPie.prototype.Clear=function(){}
//以下四個函數是擴充使用,就是提供一些VML的互動
function HoverPie(el){}
function RestorePie(el){}
function LegendMouseOverEvent(){}
function LegendMouseOutEvent(){}

看完這些函數的說明,我想讀者對於整個類的結構有一個大致的想法思路了吧,可是對於VML的製作,可能還是沒有一個很清晰的思路吧,那麼,下面我就著重介紹幾個VML元素,我只是大致的介紹一些用法,具體的在 美洲豹的 《VML中文教程》有比較詳細地介紹,如果有興趣的話可以去參考參考。

1.    V:Group

作為VML其它元素的容器,其屬性coordsize定義其座標大小,內部的元素的位置都只是相對於group元素所定義的coordsize,假設coordsize定義為21600,21600,就是定義了21600 * 21600的畫布,如果內部有一個v:shape或者其它元素, shape.style.left=”2160px”,其實際位置只是在v:group的1/10寬度的位置。

2.    V:Rect

定義一個矩形元素,fillcolor表示填充的背景顏色,stokecolor表示邊框顏色,strokeweight表示邊框寬度

3.    V:Shape

VML提供的預設圖案元素,通過定義path可以定義出需要的任何形狀,至於path的用法,可以參考w3c的文檔。

4.    V:Fill

作為shape的子項目,用來設定shape的背景效果,通過type來設定填充的方法,具體用法如下

1)      solid:實心填充,通過color設定填充顏色

2)      gradient:線狀漸層,這個時候需要color和color2這兩個參數來設定漸層的開始顏色和結束顏色,Angle則設定漸層方向。

3)      gradientradial:圓心漸層,其他的使用方法和gradient類似

4)      tile:使用圖片平鋪,src設定圖片

5)      pattern:使用圖片作為一個圖章填充模式

6)      frame:使用圖片拉神填充

        另外Opacity則用來設定透明度

5.    V:Shadow

設定shape是否需要陰影,主要使用如下參數

ON:設定是否啟用陰影

Color:陰影的顏色

Offset:陰影的偏離位置

6.    V:TextBox

定義shape的文字地區

以上的這些元素是我在製作VMLPie需要使用到的,所以做了一個簡單的介紹,具體的可以參考豹子的《VML中文教程》,另外MSDN裡頭的Vector Markup Language(VML)的SDK Document是一個非常好的參考資料。W3c的note比較抽象,在做一些比較深入的開發的時候,也是一個非常重要的參考。

做完了一系列的準備工作,那麼現在應該開始真正介紹如何繪製Pie了吧。首先看看下面的一個,清楚地描述了VMLPie的元素結構。

為了更加清楚的顯示構成VML的文本結構,我使用XML的格式給出了如下的方式。

<v:group>
    <!--設定餅圖的背景及其標題 -->
  <v:rect>
          <v:textbox>VML餅圖</v:textbox>
  </v:rect>
    <!--設定餅圖的各個塊-->
  <v:shape path="...">     
  </v:shape>
  <!--圖例元素集合 -->
  <v:group>
          <!--設定圖例的顏色 -->
          <v:rect fillcolor="green"></v:rect>
          <v:rect>
                    <!-- 設定圖例文字-->
                    <v:textbox>重慶(50)</v:textbox>
          </v:rect>
  </v:group>
</v:group>
完成了製圖的基本思路之後,剩下的就是通過DOM逐步完成建立過程了,為了簡單起見,我們安裝函數的功能進行逐步分析。

1.            VMLPie

      this.Container=pContainer;
        this.Width= pWidth || "400px";
        this.Height=pHeight || "250px";
        this.Caption = pCaption || "VML Chart";
        this.backgroundColor="";
        this.Shadow=false;
        this.BorderWidth=0;
        this.BorderColor=null;
        this.all=new Array();
        this.id=document.uniqueID;
        this.RandColor=function(){                 
                return "rgb("+ parseInt( Math.random() * 255) +"," +parseInt( Math.random() * 255) +"," +parseInt( Math.random() * 255)+")";
        }
        this.VMLObject=null;
        this.LegendObject=null;

這個函數只是簡單的初始化了一些變數,將class可以使用的一些屬性在這裡做了聲明。RandColor函數提供了產生隨機顏色的作用,這個也就是我在前文提到的,對於餅圖的各個塊的顏色,我都採用隨機顏色,這樣帶來的一個問題就是會出現有些時候兩個顏色比較接近,如果隨能夠給我提供幾個基礎的顏色列表,我將感激不盡。

2.            VMLPie.prototype.AddData

添加圖表資料,在這裡我採用了prototype滯後載入的方式實現,如果讀者認為這樣不夠清晰,可以修改成this.RandColor那樣的內建形式。

實現代碼如下:

VMLPie.prototype.AddData=function(sName,sValue,sTooltipText){
    var oData=new Object();
    oData.Name=sName;
    oData.Value=sValue;
    oData.TooltipText=sTooltipText;
    var iCount=this.all.length;
    this.all[iCount]=oData;
}
這裡使用一個Object來儲存每一個資料項目,然後放到this.all這個數組中。

3.            VMLPie.prototype.Draw

整個類實現最關鍵的函數,也就是在這個函數和後續的CreatePie函數中,一步一步地實現了圖形的繪製工作。

//畫外框
var o=document.createElement("v:group");
o.id=this.id;
o.style.width=this.Width;
o.style.height=this.Height;
o.coordsize="21600,21600";
//添加一個背景層
var vRect=document.createElement("v:rect");
vRect.style.width="21600px"
vRect.style.height="21600px"
o.appendChild(vRect);
//添加標題   
var vCaption=document.createElement("v:textbox");
vCaption.style.fontSize="24px";               
vCaption.style.height="24px"
vCaption.preSize="24";
vCaption.style.fontWeight="bold";
vCaption.innerHTML=this.Caption;
vCaption.style.textAlign="center";

vRect.appendChild(vCaption);
//設定邊框大小
if(this.BorderWidth){
    vRect.strokeweight=this.BorderWidth;
}
//設定邊框顏色
if(this.BorderColor){
    vRect.strokecolor=this.BorderColor;
}
//設定背景顏色
if(this.backgroundColor){             
    vRect.fillcolor=this.backgroundColor;
}
//設定是否出現陰影
if(this.Shadow){
    var vShadow=document.createElement("v:shadow");
    vShadow.on="t";
    vShadow.type="single";
    vShadow.color="graytext";
    vShadow.offset="4px,4px";
    vRect.appendChild(vShadow);
}
this.VMLObject=o;

完成上述工作之後,已經構建出了一個canvas(畫布),完成了圖形外框及其標題的製作,剩下的也就是最最重要的工作調用CreatePie來畫出各個塊,並且作出圖例。
4.            VMLPie.prototype.CreatePie

CreatePie提供了一個參數,也就是餅圖製作的容器,我們通過Draw的上述代碼已經建立了一個V:Group元素,這個也就是餅圖繪製的容器了。

var mX=Math.pow(2,16) * 360;
//這個參數是劃圖形的關鍵
//AE x y width height startangle endangle
//x y表示圓心位置
//width height形狀的大小
//startangle endangle的計算方法如下
// 2^16 * 度數       
var vTotal=0;
var startAngle=0;
var endAngle=0;
var pieAngle=0;
var prePieAngle=0;           

//計算資料的總和
for(i=0;i<this.all.length;i++){
    vTotal+=this.all[i].Value;
}
//建立圖例容器
//這裡子項目的left,top或者width都是針對於容器設定的座標系統而言的
//例如
//圖表容器我設定了coordsize為 21600,21600,那麼objLengendGroup的位置都是相對於這個座標系統而言的
//和實際圖形顯示的大小沒有直接關係
var objLegendGroup=document.createElement("v:group");
with(objLegendGroup){
    style.left="17000px";
    style.top="4000px";
    style.width="4000px";
    style.height=1400 * this.all.length +"px";
    coordsize="21600,21600";                   
}
//做圖例的背景填充並且設定陰影
var objLegendRect=document.createElement("v:rect");
objLegendRect.fillcolor=" #EBF1F9";
objLegendRect.strokeweight=1;
with(objLegendRect){         
    //設定為21600,21600,就是保證完全覆蓋group客戶區
    style.width="21600px";
    style.height="21600px";                   
}           
//對於圖例加入陰影
var vShadow=document.createElement("v:shadow");
vShadow.on="t";               
vShadow.type="single";
vShadow.color="graytext";
vShadow.offset="4px,4px";             
objLegendRect.appendChild(vShadow);

//將其放到圖例的容器中
objLegendGroup.appendChild(objLegendRect);

this.LegendObject=objLegendGroup;
vGroup.appendChild(objLegendGroup);

這個時候,我們已經完成了各個地區位置的繪製,通過如上的代碼,我繪製了一個LegendGroup,將其作為圖例的顯示位置,另外主的V:group就作為pie的繪製容器,如果出於規範的考慮,也應該將Pie的各個shape放到一個group中,那樣在日後的編程式控制制中會更加方便一點。

下面的這段代碼也就是我要講述的,因為代碼比較關鍵,除了給出代碼,我還著重的說明各個語句的作用。

for(i=0;i<this.all.length;i++){ //順序的划出各個餅圖
        var vPieEl=document.createElement("v:shape");
        var vPieId=document.uniqueID;
        vPieEl.style.width="15000px";
        vPieEl.style.height="14000px";
        vPieEl.style.top="4000px";
        vPieEl.style.left="1000px";
        vPieEl.adj="0,0";
        vPieEl.coordsize="1500,1400";
        vPieEl.strokecolor="white";                       
        vPieEl.id=vPieId;
        vPieEl.style.zIndex=1;
        vPieEl.onmouseover="HoverPie(this)";
        vPieEl.onmouseout="RestorePie(this)";             
        pieAngle= this.all[i].Value / vTotal;
        startAngle+=prePieAngle;
        prePieAngle=pieAngle;
        endAngle=pieAngle;
        vPieEl.path="M 750 700 AE 750 700 750 700 " + parseInt(mX * startAngle) +" " + parseInt(mX * endAngle) +" xe";
        vPieEl.title=this.all[i].Name +"\n所佔比例:"+ endAngle * 100 +"%\n詳細描述:" +this.all[i].TooltipText;
        vPieEl._scale=parseInt( 360 * (startAngle + endAngle /2));
                                 
        var objFill=document.createElement("v:fill");
        objFill.rotate="t";                       
        objFill.focus="100%";
        objFill.type="gradient";                   
        objFill.angle=parseInt( 360 * (startAngle + endAngle /2)); 
       
        var objTextbox=document.createElement("v:textbox");
        objTextbox.innerHTML=this.all[i].Name +":" + this.all[i].Value;
        objTextbox.inset="5px 5px 5px 5px";
        objTextbox.style.width="100px";
        objTextbox.style.height="20px";   
       
        var vColor=this.RandColor();
        vPieEl.fillcolor=vColor; //設定顏色
        //開始畫圖例     
        p.innerText=vPieEl.outerHTML;
        var colorTip=document.createElement("v:rect");
       
        var iHeight=parseInt(21600 / (this.all.length * 2));
        var iWidth=parseInt(iHeight * parseInt(objLegendGroup.style.height) /parseInt(objLegendGroup.style.width) /1.5 );
       
        colorTip.style.height= iHeight;           
        colorTip.style.width=iWidth;     
        colorTip.style.top=iHeight * i * 2 + parseInt(iHeight /2);                   
        colorTip.style.left=parseInt(iWidth /2);
        colorTip.fillcolor=vColor;
        colorTip.element=vPieId;
        //colorTip.attachEvent("onmouse",LegendMouseOverEvent);
        colorTip.onmouseover="LegendMouseOverEvent()";
        colorTip.onmouseout="LegendMouseOutEvent()";
       
        var textTip=document.createElement("v:rect");
        textTip.style.top=parseInt(colorTip.style.top)- 500;
        textTip.style.left= iWidth * 2;           
        textTip.innerHTML="<v:textbox style=\"font-size:12px;font-weight:bold\">" + this.all[i].Name +"("+ this.all[i].Value+")</v:textbox>";
       
        objLegendGroup.appendChild(colorTip);
        objLegendGroup.appendChild(textTip);
       
        vGroup.appendChild(vPieEl);       
}
我們現在就開始來看如上代碼的實現。

1.                          首先建立一個v:shape,left,top,width,height分別設定為1000px,4000px,15000px;14000px;這個的數字是我根據大致的位置確定的。

2.                          設定每個shape的id,我採用了document.uniqueID,就是用DOM的方法提供的隨機id,本來我沒有打算設定這個ID,但是後來考慮到和legend的互動,所以就設定了每個shape的ID.

3.                          設定shape的onmouseover,onmouseout事件,開始的時候我是採用attachEvent的方式來實現的,後來發現無法起作用(到現在我也沒有找到原因),求出每個資料所佔的比例。

4.                          設定Path,這個也就是比較令人難以看懂的部分了,我首先求出了startAngle和endAngle,startAngle的意義是這樣的,假設有3個數,0.2,0.2,0.4,對於第二項來說,startAngle應該是 0.25,endAngle是0.25,對於第三項來說,startAngle是0.5,endAngle是0.5,總而言之,startAngle可以表示為前面資料所佔的比例,engAngle表示當前資料所佔的比例。Path有很多指令,對於其他指令,我就不多作解釋,而這裡使用的是M 750 700 AE 750 700 750 700 start end 。對於shape我重新定義了coordsize=”1500,1400”,m 750,700則表示移動到 750,700,就是移動到shape定義的中心,AE用來畫曲線,總共有6個參數,w3c的note描述如下center (x,y) size(w,h) start-angle, end-angle,前面四個參數不難理解,剩下的兩個參數我不是特別明白意思,但是在國外的一篇文章看到其中的演算法如下應該是 2^16 * 度數,對應於程式,就應該是startAngle * 2^16 * 360,因為我的startAngle是比例。

5.                          通過設定fillcolor來設定餅圖塊的顏色,同時設定title提供一些提示資訊.

6.                          對於圖例開始著色,同時添加圖例的文字說明.

7.                          因為所有的座標系統都是相對的,為了保證美觀,對於objLegendGroup我通過1400 * this.all.length +"px"來求得,而對於圖例的colortip,高度和寬度就是動態計算的。

到目前為止,通過程式已經建立了一個基於VML的Pie,剩下的就是輸出的問題了,前文我也提到過,我通過DOM方式建立的VML在顯示方面沒有任何問題,可是在動態互動方面,似乎不起作用,obj.VMLObject就是一個DOM對象,可是對於onmouseover,onmouseout等等的設定如果直接使用appendChild的方式的時候,根本無法起作用。而通過設定innerHTML來產生VML圖形的時候,那些滑鼠事件卻可以響應,有過這個方面編程的朋友麻煩告訴我具體的原因如何。

function HoverPie(el){}
function RestorePie(el){}
function LegendMouseOverEvent(){}
function LegendMouseOutEvent(){}
這四個函數就是提供了簡單的互動作用。具體的代碼如下:

function HoverPie(el){
        var v_length=500;
        var x_cur=Math.cos( el._scale * Math.PI /180);
        var y_cur=Math.sin (el._scale * Math.PI /180);     
        x=parseInt(x_cur * v_length);
        y=parseInt(y_cur * v_length);     
        el.style.top=4000 -y;
        el.style.left=1000 +x;
        el.strokecolor="black";

}
function RestorePie(el){
        el.style.top=4000;
        el.style.left=1000;
        el.strokecolor="white";
       
}
function LegendMouseOverEvent(){
       
        var eSrc=window.event.srcElement;
        var vPie=document.all(eSrc.element);       
        HoverPie(vPie);
}
function LegendMouseOutEvent(){
        var eSrc=window.event.srcElement;
        var vPie=document.all(eSrc.element);
        RestorePie(vPie);
}
HoverPie提供了簡單的圖形相對位移的功能.

5.            VMLPie.prototype.Zoom

提供了圖形的放大和縮小的功能,既然是作為向量圖形,那麼其放大和縮小就應該是沒有任何映像損失的,實現的原理很簡單,只需要修改group的座標系統就可以實現,這個也就是我一直強調的需要將shape都放到group中的主要原因,對於文字,指能夠使用和html Dom相同的方式進行設定。

VMLPie.prototype.Zoom=function (iValue){
        var vX=21600;
        var vY=21600;
        this.VMLObject=document.all(this.id);
        this.VMLObject.coordsize=parseInt(vX / iValue) +","+parseInt(vY /iValue);
        var texts=this.VMLObject.getElementsByTagName("TEXTBOX");
        for(var i=0;i<texts.length;i++){
                if (texts[i].preSize){
                          texts[i].style.fontSize= parseInt(texts[i].preSize) * iValue +"px";
                }
                else{
                          texts[i].style.fontSize= 12  * iValue + "px";
                }
        }
}
到目前為止,已經全部介紹完了VMLPie的全部製作過程,我是第一次寫這樣的文章,可能在很多東西的表述方面不是特別清楚,希望可以和大家共同探討,另外完整的原始碼我已經貼在CSDN的論壇裡頭,大家可以去下載。

http://expert.csdn.net/Expert/topic/2160/2160456.xml?temp=.4498102

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.