zoukankan      html  css  js  c++  java
  • 利用vml制作统计图全攻略(饼图)[转自CSDN]

    提要:

        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
  • 相关阅读:
    yii1.0 yii2.0 ar
    php 函数
    整除理论,1.1数的整除性定理总结
    设M=5^2003+7^2004+9^2005+11^2006,求证8|M。(整除理论,1.1.8)
    已知整数m,n,p,q适合(m-p)|(mn+pq)证明:(m-p)|(mq+np)(整除理论1.1.5)
    证明:一个整数a若不能被6整除,则a2+24必能被24整除。(整除理论,1.1.4)
    ural 1073.Square Country(动态规划)
    10个男孩和n个女孩共买了n2+8n+2本书,已知他们每人买的书本的数量是相同的,且女孩人数多于南海人数,问女孩人数是多少?(整除原理1.1.3)
    设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak,证明:11|n的充分必要条件是11|T(n);(整除理论1.1.2))
    设n是奇数,证明:16|(n4+4n2+11)(整除原理1.1.1)
  • 原文地址:https://www.cnblogs.com/cuihongyu3503319/p/617820.html
Copyright © 2011-2022 走看看