基于VML与HTML5 Canva实现的跨浏览器饼图与折线图

简介: 基于VML与HTML5 Canva实现的跨浏览器饼图与折线图

本来想自己做个库的,毅力不够啊,所以放出源代码

当前实现的框架功能有:

1. 支持IE6+以上版本,支持Chrome, 支持FireFox

2. 动画加载机制

3. tooltip支持

4. legend支持

5. 功能丰富的参数设置

当前支持的图形种类有两种:饼图与折线图,将来也许会不定期更新,不断完善!

希望还有点毅力和时间做话。不废话啦,直接上效果图:

折线图演示代码:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="js/fishchart.js"></script>
<title>Line Demo</title>
  <script>
  window.onload = function() {
    var container = document.getElementById("my_container");
    var seriesData = [{name:"本一", data:[330,348,355,345, 350, 348], color:"RGB(255,0,0)"},
                  {name:"本二", data:[300,326,328,320, 330, 325], color:"RGB(0,0,255)"},
                  {name:"本三", data:[248,288,277,290, 280, 290], color:"RGB(255,0,255)"}];
    var config = {
        type : "line",
        width : 800, 
        height: 400,
        series: seriesData,
        container: container,
        title:"江苏省2008~2013高考录取分数线",
        tooltip : {
          enable : true
        },
        animation :{
          enable: true
        },
          legend : {
            enable : true
          },
          
          yAxis :{
            tickSize: 10,
            title: "分数"
          },
          xAxis :{
            tickSize: 10,
            title: "年份",
            categories: ["2008", "2009", "2010", "2011", "2012", "2013"]
          }
    };
    fishChart.initSettings(config);
    fishChart.render();
  }
  </script>
</head>
<body>
<h1>VML Line Chart Demo</h1>
<div id="my_container" style="width:600px; height:400px;">
</div>
</body>
</html>

饼图演示代码:

<html>
<head>
<script src="js/fishchart.js"></script>
<title>My Demo 1</title>
  <script>
  window.onload = function() {
    var container = document.getElementById("my_container");
    var seriesData = [{name:"apples", value:150, color:"RGB(255,0,0)"},
                    {name:"orange", value:100, color:"RGB(255,255,0)"},
                    {name:"banana", value:80, color:"RGB(255,0,255)"},
                    {name:"peaches", value:60, color:"RGB(0,255,255)"},
                    {name:"strawberries", value:40, color:"RGB(0,127,255)"}]
    var config = {
        type : "pie",
        width : 600, 
        height: 400,
        series: seriesData,
        container: container,
        unit: "kg",
        title:"Fruit Sales",
        tooltip : {
          enable : true
        },
        animation :{
          enable: true
        },
          legend : {
            enable : true
          },
          text : {
            enable: true
          }
    };
    fishChart.initSettings(config);
    fishChart.render();
  }
  </script>
</head>
<body>
<h1>farchart - Pie Demo</h1>
<div id="my_container" style="width:600px; height:400px;">
</div>
</body>
</html>

JS库源代码:有兴趣者可以自己添加更多图表支持

// farchart farchart.js version 0.0.1, 28 August 2013 
 
// (c) 2013 by Jia ZhiGang. All Rights reserved. 
// Copyright (c) 2013 Jia ZhiGang (http://www.edinme.com)
//
// far chart is freely distributable under the terms of an GPL-style license.
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the farchart web site: http://www.edinme.com
function check_vmlCapability() {
  if(document.namespaces['v']==null) {
    var e=["shape","shapetype","group","background","path","formulas","handles","fill","stroke","shadow","textbox","textpath","imagedata","line","polyline","curve","roundrect","oval","rect","arc","image"],s=document.createStyleSheet(); 
    for(var i=0; i<e.length; i++) {s.addRule("v\\:"+e[i],"behavior: url(#default#VML); display: inline-block;");} document.namespaces.add("v","urn:schemas-microsoft-com:vml");
  } 
}
 
function msieversion()
{
   var ua = window.navigator.userAgent;
   var msie = ua.indexOf ( "MSIE " );
 
   if ( msie > 0 ) 
   {
     // If Internet Explorer, return version number
     return parseInt (ua.substring (msie+5, ua.indexOf (".", msie )));     
   }      
   else
   {
    // If another browser, return 0    
    return 0;
  }                
}
 
var fishChart = {
  type: "null", // global flag, default
    width: 600,
    height: 400,
    series: [],
    unit: "kg",
    container: null,
  title: {
    text: "Far Chart",
    x: -100 // center offset
  },
    isIE: true,
    legend : {
      enable : true
    },
    edge : {
      width: 50, // for pie
      height: 50, // for pie
    left: 50, // for other plot
    upper: 40,
    bottom: 40,
    right: 60
    },
  yAxis: {
    title: "Y Axis",
    vgap: 0,
    plotHeight: 0,
    min: 0,
    max: 0,
    tickSize: 10,
    padding: 5
  },
  xAxis: {
    categories: [],
    tickSize : 10,
    autoTick:true,
    xgap : 0,
    min:0,
    max:0,
    title:"X Axis"
  },
    animation: {
      enable: true,
      hh: 1,
    animCanvas:null,
    pctx: null
    },
    tooltip: {
      enable: true,
      tooltipCanvas : null,
      ttContext: null,
      index: -1
    },
    circle : { // for pie
      cx: 0,
      cy: 0,
      radius: 0
      
    },
    text : { // for pie
      enable: false,
      content:[]
    },
    
  point : { // for line and scatter plot
    enable : true,
    radius : 4
  },
    
    initSettings: function (config) {
      this.type = config.type;
      this.container = config.container;
        this.width = config.width;
        this.height = config.height;
        this.series = config.series;
        this.title.text = config.title;
        this.unit = config.unit;
        
        // tool-tip, animation, legend setting data
        if(config.tooltip != undefined) {
          this.tooltip.enable = config.tooltip.enable;          
        }
        if(config.animation != undefined) {
          this.animation.enable = config.animation.enable;          
        }
        if(config.legend != undefined) {
          this.legend.enable = config.legend.enable;          
        }
        if(config.text != undefined) {
          this.text.enable = config.text.enable;
        }
        
        // edge setting data
        if(config.edge != undefined && config.edge.right != undefined) {
          this.edge.right = config.edge.right;          
        }
        if(config.edge != undefined && config.edge.left != undefined) {
          this.edge.left = config.edge.left;          
        }
        if(config.edge != undefined && config.edge.bottom != undefined) {
          this.edge.bottom = config.edge.bottom;          
        }
        if(config.edge != undefined && config.edge.upper != undefined) {
          this.edge.upper = config.edge.upper;          
        }
        
        // xAxis setting
        if(config.xAxis != undefined) {         
          this.xAxis.title= config.xAxis.title;
          this.xAxis.tickSize = config.xAxis.tickSize;
        }
        
        if(config.xAxis != undefined && config.xAxis.categories != undefined) {
          this.xAxis.categories = config.xAxis.categories;
        }
        
        // yAxis setting
        if(config.yAxis != undefined) {
          this.yAxis.title = config.yAxis.title;
          this.yAxis.tickSize = config.yAxis.tickSize;          
        } 
        
        // decide whether render plot using HTML5 Canvas or VML
        if(msieversion() == 0) {
          this.chartCanvas = document.createElement("canvas");
          this.chartCanvas.id = "fc_canvas";
          this.container.appendChild(this.chartCanvas);
          this.chartCanvas.width = config.width;
          this.chartCanvas.height = config.height;
          this.isIE = false;
        } else {
          check_vmlCapability();
          this.isIE = true;
        }
    },
    
    render : function() {     
      var ctx = this.getCanvasContext();
      this.renderBorder(ctx);
      if(this.type === "pie") {
          // initialization circle
          this.circle.cx = this.width/2;
          this.circle.cy = this.height/2;
          this.circle.radius = Math.min(this.width/2, this.height/2) - Math.max(this.edge.width, this.edge.height);
          if(this.circle.radius <= 0) {
            console.log("Can not reader the chart, Circle is too small.");
            return;
          }
          
          // draw each arc according to data series 
          var sum = 0;
          var nums = this.series.length;
          for(var i=0; i<nums; i++) {
            sum += this.series[i].value;
          }
          
          // draw title
          this.customFillText(ctx, this.width/2 - this.edge.width, (this.isIE ? 10 : 30), this.circle.radius, 40, this.title.text, "white", "white", "black", '18pt Calibri');
          
          if(this.isIE) {
            this.renderPieShadow(ctx);
          }
          
          // render bar
          var start = 0;
          for(var i=0; i<nums; i++) {
            var precent = this.series[i].value/sum;
            this.renderPie(ctx, i, start, precent);
            if(this.isIE) {
              start += precent;
            } else {
              start += 2*Math.PI * precent;             
            }
          }
          
          // post add blur shadow
          if(!this.isIE) {
            this.renderPieShadow(ctx);
          }
          
          // render legend
          this.renderPieLegend(ctx, sum);
          
      } 
      // line plot
      else if(this.type == "line")
      {
        
        // draw title
        this.customFillText(ctx, (this.width/2 + this.title.x), (this.isIE ? 2 : this.edge.upper/2), this.width/2, 0, this.title.text, "white", "white", "black", '18pt Calibri');
        // draw XY Axis
        this.renderXYAxis(ctx);
        // draw lines
          var nums = this.series.length;
          for(var i=0; i<nums; i++) {
            this.renderOneLineCurve(ctx, i);
          }
          
        
      }
      
      // render legend
      this.renderLegend(ctx);
      
      // enable tool-tip
      if(this.tooltip.enable && !this.isIE && !this.animation.enable) {
        var parent = this;
        this.chartCanvas.addEventListener('mousemove', function(event) {
          var x = event.pageX;
          var y = event.pageY;
          var canvas = event.target;
          var bbox = canvas.getBoundingClientRect();
          var loc = { x: x - bbox.left * (canvas.width  / bbox.width),
              y: y - bbox.top  * (canvas.height / bbox.height)};
          parent.showTooltips(loc, ctx);
        }, false);              
      }
      
      // enable animation
      if(this.animation.enable) {
        var parent = this;
        if(this.isIE) {
          ctx.insertBefore(this.animation.animCanvas);  
        }
        setTimeout(function() {parent.playAnimation(parent);}, 1000/20);        
      }
    },
    
    renderOneLineCurve : function(ctx, index) {
      var size = this.series[index].data.length;
      var plotwidth = this.width - this.edge.left - this.edge.right;
    var deltax = plotwidth/size;
    var xpos = this.edge.left;
    
    if(this.isIE) {
      var vpolyLine = document.createElement("v:polyline");
      vpolyLine.strokecolor=this.series[index].color;
      vpolyLine.id = "vml-fline-" + index;
      vpolyLine.filled="false"; // default is true, will fill area of close path
      var points = "";
      for(var i=0; i<size; i++) {
        var value = this.series[index].data[i];
        var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap);
        
        // highlight the data point by circle.
        if(this.point.enable) {
          var hvoval = document.createElement("v:oval");
          hvoval.style.left = (xpos + Math.floor(deltax/2) - this.point.radius);
          hvoval.style.top = (this.height - this.edge.bottom - deltay - this.point.radius);
          hvoval.style.width = (this.point.radius * 2) + "px";
          hvoval.style.height = (this.point.radius * 2) + "px";
          hvoval.strokecolor = this.series[index].color;
          hvoval.fillcolor = this.series[index].color;
          hvoval.title = this.xAxis.categories[i] + " " + this.series[index].name + " : " +  + value; // tool-tip
          ctx.insertBefore(hvoval);
        }
        // - end it       
        
        points += (xpos + Math.floor(deltax/2)) + "," + (this.height - this.edge.bottom - deltay) + " ";    
        xpos += deltax;
      }
      vpolyLine.strokeweight="1px";
      vpolyLine.points=points;
      ctx.insertBefore(vpolyLine);
    } 
    else 
    {
      ctx.save();
      ctx.beginPath();
      ctx.lineWidth = 1;
      for(var i=0; i<size; i++) {
        var value = this.series[index].data[i];
        var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap);
        ctx.strokeStyle=this.series[index].color;
        if(i == 0) {
          ctx.moveTo(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay);
        } else {
          ctx.lineTo(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay);
        }
        
        xpos += deltax;
      }     
      ctx.stroke();
      ctx.restore();
      
      // draw dot circle
      if(!this.point.enable)
        return;
      ctx.save();
      var xpos = this.edge.left;
      for(var i=0; i<size; i++) {
        var value = this.series[index].data[i];
        var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap);
        ctx.beginPath();
        ctx.arc(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay, this.point.radius, 0, 2*Math.PI, false);
        ctx.closePath();
        ctx.fillStyle=this.series[index].color;
        ctx.fill();
        xpos += deltax;
      }
      ctx.restore();
    }
    },
    
    renderPieLegend : function(ctx, sum) {
      if(!this.legend.enable) return;
      var nums = this.series.length;
      ctx.font = '10pt Calibri';
      var pos = (this.width/2 > (this.circle.radius+50)) ? 50 : (this.circle.cx - this.circle.radius);
      for(var i=0; i<nums; i++) {
        var x = this.series[i].value/sum;
        x = Math.round (x*100) / 100;
        var tipStr =  this.series[i].name + ": " + (x * 100).toFixed(0) + "%";
        this.series[i].precent = tipStr;
        this.renderRect(ctx, (pos - 40), (20*i+10), 10, 10, this.series[i].color, this.series[i].color, 0);
        this.customFillText(ctx, (pos - 25), (this.isIE ? (20*i + 1) : (20*i+20)), 100, 0, tipStr, "white", "white", "black", "10pt Calibri");
      } 
    },
    
    renderPieShadow  : function(ctx) {
      if(this.isIE) {
          var voval = document.createElement("v:oval");
          voval.style.left=(this.circle.cx - this.circle.radius) + "px";
          voval.style.top=(this.circle.cy - this.circle.radius) + "px";
          voval.style.width=this.circle.radius*2 + "px";
          voval.style.height=this.circle.radius*2 + "px";
          voval.style.position="absolute";
          voval.strokecolor="RGB(150,150,150)";
          voval.strokeweight="4pt";
          ctx.insertBefore(voval);
      } else {
          ctx.save();
          ctx.shadowColor = "black";
          ctx.shadowOffsetX = 0;
          ctx.shadowOffsetY = 0;
          ctx.shadowBlur = 10;
          ctx.beginPath();
          ctx.arc(this.circle.cx, this.circle.cy, this.circle.radius, 0, Math.PI * 2, false);
          ctx.closePath();
          ctx.lineWidth = 1;
          ctx.strokeStyle = "RGBA(127,127,127,1)";
          ctx.stroke();
          ctx.restore();
      }
    },
    
    renderPie : function(ctx, index, startp, precent) {
      if(this.isIE) {
          var start = Math.round (startp*100); 
          var end = Math.round (precent*100) + start;
          var part = document.createElement("v:shape");
          part.style.width=Math.min(this.width, this.height) + "px";
          part.style.height=Math.min(this.width, this.height) + "px";
          part.style.position="absolute";
          part.id = "vml-pie-part-" + index;
          var path = "";
          for (var i= start; i <= end; i++) {
            x = Math.floor((Math.cos(i*Math.PI/50)*this.circle.radius)+this.circle.cx);
            y = Math.floor((Math.sin(i*Math.PI/50)*this.circle.radius)+this.circle.cy);
            xs = x.toString();
            ys = y.toString();
            path +=  xs + ", " + ys + ", ";
          }
          part.strokecolor=this.series[index].color;
          part.strokeweight="0pt";
          part.fillcolor = this.series[index].color;
          part.path= "m " + this.circle.cx + "," + this.circle.cy +" l " + path + this.circle.cx + "," + this.circle.cy + " x e";
          ctx.insertBefore(part);
          
          // render text content
        if(this.text.enable) {        
          var textLine = document.createElement("v:line");
          var fx = Math.floor((Math.cos((start + (end - start)/2)*Math.PI/50)*this.circle.radius)+this.circle.cx);
          var fy = Math.floor((Math.sin((start + (end - start)/2)*Math.PI/50)*this.circle.radius)+this.circle.cy);
          var tx = (fx < this.circle.cx) ? (fx - this.edge.width) : (fx + this.edge.width);
          textLine.from = fx + "," + fy;
          textLine.to = tx + "," + fy;
          ctx.insertBefore(textLine);
          
          var textPos = (fx < this.circle.cx) ? (fx - this.edge.width*2) : (fx + this.edge.width);          
          this.customFillText(ctx, (textPos), (fy-20), 100, 0, this.series[index].name + ": " + (precent * 100).toFixed(0) + "%", "white", "white", "black", "10pt Calibri");
        }
        
        if(this.tooltip.enable) {
          var _parent = this;
          part.title = "Index: " + (index + 1) + "\n" + this.series[index].name + ": " + this.series[index].value + this.unit + "\n" + 
                this.series[index].name + ": " + (precent * 100).toFixed(0) + "%" + "\n";
          part.attachEvent('onmouseover', function (event) {
            var srcElement = event.srcElement;
            srcElement.strokecolor= "white";
            srcElement.strokeweight="3pt";
          });
          part.attachEvent('onmouseout', function (event) { 
            var srcElement = event.srcElement;
            srcElement.strokecolor=_parent.series[index].color;
            srcElement.strokeweight="0pt";
          });
        }
        
      } else {        
        var endAngle = startp + 2*Math.PI*precent;
        ctx.save();
        ctx.beginPath();
        ctx.arc(this.circle.cx, this.circle.cy, this.circle.radius, startp, endAngle, false);
        ctx.moveTo(this.circle.cx, this.circle.cy);
        ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(startp), this.circle.cy + this.circle.radius * Math.sin(startp));
        ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(endAngle), this.circle.cy + this.circle.radius * Math.sin(endAngle));
        ctx.lineTo(this.circle.cx, this.circle.cy);
        ctx.closePath();
        ctx.fillStyle = this.series[index].color;
        ctx.strokeStyle = this.series[index].color;
        ctx.fill();
        ctx.stroke();
        ctx.restore();
        
          // render text content
          if(this.text.enable) {        
            var halfEndAngle = startp + Math.PI*precent;
            var hx = this.circle.cx + this.circle.radius * Math.cos(halfEndAngle);
            var hy = this.circle.cy + this.circle.radius * Math.sin(halfEndAngle);
            ctx.beginPath();
            ctx.moveTo(hx, hy);
            var linePos = (hx < this.circle.cx) ? (hx - this.edge.width) : (hx + this.edge.width);
            ctx.lineTo(linePos, hy);
            ctx.closePath();
            ctx.strokeStyle="black";
            ctx.stroke();
            var textPos = (hx < this.circle.cx) ? (hx - this.edge.width*2) : (hx + this.edge.width);
            precent = Math.round (precent*100) / 100;
            var size = this.text.content.length;
            var tipStr = (size > index) ? this.text.content[index] : this.series[index].name + ": " + (precent * 100).toFixed(0) + "%";
            ctx.font = '10pt Calibri';
            ctx.fillStyle="black";
            ctx.fillText(tipStr, textPos, hy);
          }
      }
    },
    
    showLineTooltips : function(loc, ctx) {
      if(!this.tooltip.enable) {
        return;
      }
      var size = this.series[0].data.length;
      var plotwidth = this.width - this.edge.left - this.edge.right;
    var deltax = (plotwidth/ size);
      var xunit = ((loc.x - this.edge.left)/deltax); //: */loc.x - this.edge.left;
      var deltay = this.height - this.edge.bottom - loc.y;
      if(xunit > 0 && deltay > 0) {
        var value = deltay/this.yAxis.ygap + this.yAxis.min;
        var xindex = Math.floor(xunit);
        var xpoint = xunit - xindex;
        if(xpoint < 0.55 && xpoint >0.45) {
          var num = this.series.length;
          var distance = [];
          for(var i=0; i<num; i++) {
            var dis = this.series[i].data[xindex] - value;
            distance[i] = Math.abs(dis);
          }
          var min = distance[0];
          var yindex = 0;
          for(var i=0; i<num; i++) {
            if(min > distance[i]) {
              min = distance[i];
              yindex = i;
            }
          }
          
          if(this.series[yindex].data[xindex] > Math.floor(value - 3) && this.series[yindex].data[xindex] < Math.floor(value + 3) ) {
            this.renderLineTooltips(ctx, yindex, xindex, loc);
          } 
          // clear tool tip
          else {
            this.clearTooltips(ctx);
          }
        } 
        else {
          this.clearTooltips(ctx);
        }
      } 
      else {
        this.clearTooltips(ctx);
      }
    },
    
    renderLineTooltips : function(ctx, yindex, xindex, loc) {
    // show tool tip
    this.clearTooltips(ctx);
    if(this.tooltip.tooltipCanvas == null) {
      this.tooltip.tooltipCanvas = document.createElement("canvas");
      this.tooltip.ttContext = this.tooltip.tooltipCanvas.getContext("2d");
        this.tooltip.tooltipCanvas.width = 150;
        this.tooltip.tooltipCanvas.height = 100;
    }
    var m_context = this.tooltip.ttContext;
    m_context.save();
    m_context.clearRect(0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height);
    m_context.lineWidth = 2;
    m_context.strokeStyle = this.series[yindex].color;
    m_context.fillStyle="RGBA(255,255,255,0.7)";
    this.getRoundRect(m_context, 2,2,this.tooltip.tooltipCanvas.width-4, this.tooltip.tooltipCanvas.height-4, 5, true, true);
    m_context.font="14px Arial";
    m_context.fillStyle="RGBA(0,0,0,1)";
    if(this.type == "line") {
      m_context.fillText((this.xAxis.title + ": " + this.xAxis.categories[xindex]), 5, 20);
      m_context.fillText(this.series[yindex].name + ": " + this.series[yindex].data[xindex], 5, 40);      
    } else {
      m_context.fillText(this.series[yindex].name, 5, 20);
      m_context.fillText(this.xAxis.title + ":" + this.series[yindex].data[xindex][0], 5, 40);
      m_context.fillText(this.yAxis.title + ":" + this.series[yindex].data[xindex][1], 5, 60);
    }
    m_context.restore();
    
    // make tool-tip rectangle is always visible 
    if((loc.x + this.tooltip.tooltipCanvas.width)> this.width) {
      loc.x = loc.x - this.tooltip.tooltipCanvas.width;
    }
    if((loc.y - this.tooltip.tooltipCanvas.height) <= 0) {
      loc.y = loc.y + this.tooltip.tooltipCanvas.height;
    }
    ctx.drawImage(this.tooltip.tooltipCanvas, 0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height, 
        loc.x, loc.y-this.tooltip.tooltipCanvas.height, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height);
    },
    
    showPieTooltips : function(loc, ctx) {
      if(!this.tooltip.enable) {
        return;
      }
      var dx = loc.x - this.width/2;
      var dy = loc.y - this.height/2;
      var dis = Math.floor(Math.sqrt(dx * dx + dy * dy));
      if(dis <= this.circle.radius) {
        // draw tool tip text
        var angle = Math.atan2(dy,dx);
        if(angle <= 0) {
          // if[-Math.PI, 0], make it[Math.PI, 2*Math.PI]
          angle = angle + 2*Math.PI;
        }
        
          var sum = 0;
          var nums = this.series.length;
          for(var s=0; s<nums; s++) {
            sum += this.series[s].value;
          }
          
          var deltaArc = 0;
          var index = 0;
          for(var i=0; i<nums; i++) {
            var precent = this.series[i].value/sum;
            deltaArc += 2*Math.PI * precent;
            if(angle<=deltaArc) {
              index = i;
              break;
            }
          }
        if(this.tooltip.tooltipCanvas == null) {
          this.tooltip.tooltipCanvas = document.createElement("canvas");
          this.tooltip.ttContext = this.tooltip.tooltipCanvas.getContext("2d");
            this.tooltip.tooltipCanvas.width = 150;
            this.tooltip.tooltipCanvas.height = 100;
        } 
        
        // only draw once
        // if(index == this.tooltips.index){
        //  return;
        // }
        this.clearTooltips(ctx);
        
        this.tooltip.index = index;
        var m_context = this.tooltip.ttContext;
        m_context.save();
        m_context.clearRect(0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height);
        m_context.lineWidth = 2;
        m_context.strokeStyle = this.series[index].color;
        m_context.fillStyle="RGBA(255,255,255,0.7)";
        // m_context.strokeRect(2, 2, this.tooltips.tooltipCanvas.width-4, this.tooltips.tooltipCanvas.height-4);
        // m_context.fillRect(2,2,this.tooltips.tooltipCanvas.width-4, this.tooltips.tooltipCanvas.height-4);
        this.getRoundRect(m_context, 2,2,this.tooltip.tooltipCanvas.width-4, this.tooltip.tooltipCanvas.height-4, 5, true, true);
      m_context.font="14px Arial";
      m_context.fillStyle="RGBA(0,0,0,1)";
      m_context.fillText("Index: " + (index + 1), 5, 20);
      m_context.fillText(this.series[index].name + ": " + this.series[index].value + this.unit, 5, 40);
      m_context.fillText(this.series[index].precent, 5, 60);
      m_context.restore();
      
      // make tool-tip rectangle is always visible 
      if((loc.x + this.tooltip.tooltipCanvas.width)> this.width) {
        loc.x = loc.x - this.tooltip.tooltipCanvas.width;
      }
      if((loc.y - this.tooltip.tooltipCanvas.height) <= 0) {
        loc.y = loc.y + this.tooltip.tooltipCanvas.height;
      }
      ctx.drawImage(this.tooltip.tooltipCanvas, 0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height, 
          loc.x, loc.y-this.tooltip.tooltipCanvas.height, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height); 
      } else {
        this.tooltip.index = -1;
        this.clearTooltips(ctx);
      }
    },
    
    redrawPie : function(ctx) {
      if(this.animation.enable) {
        ctx.clearRect(0,0,this.width, this.height);
        this.renderBorder(ctx);
        ctx.drawImage(this.animation.animCanvas, 0, 0, this.width, this.height, 0, 0, this.width, this.height); 
      } else {
          // draw each arc according to data series 
          var sum = 0;
          var nums = this.series.length;
          for(var i=0; i<nums; i++) {
            sum += this.series[i].value;
          }
          
          // draw title
          this.customFillText(ctx, this.width/2 - this.edge.width, (this.isIE ? 10 : 30), this.circle.radius, 40, this.title, "white", "white", "black", '18pt Calibri');
          
          if(this.isIE) {
            this.renderPieShadow(ctx);
          }
          
          // render bar
          var start = 0;
          for(var i=0; i<nums; i++) {
            var precent = this.series[i].value/sum;
            this.renderPie(ctx, i, start, precent);
            if(this.isIE) {
              start += precent;
            } else {
              start += 2*Math.PI * precent;             
            }
          }
          
          // post add blur shadow
          if(!this.isIE) {
            this.renderPieShadow(ctx);
          }
          
          // render legend
          this.renderPieLegend(ctx, sum);
      }
    },
    
    renderXYAxis : function(ctx) {
      this.drawXYTitles(ctx);
      if(this.type == "line") {
          // draw ticks and markers
          var nums = this.series.length;
          if(nums == 0) return;
        
        var min = 0;
        var max = 0;
        min = this.series[0].data[0];
        max = this.series[0].data[0];
        for(var s = 0; s < nums; s++) {
          var size = this.series[s].data.length;
          for(var i=0; i<size; i++) {
            min = Math.min(min, this.series[s].data[i]);
            max = Math.max(max, this.series[s].data[i]);
          }
        }       
          
          // calculate the text gap in Y Axis
          var delta = max - min;
          this.yAxis.padding = delta/10;
          this.yAxis.min = min - this.yAxis.padding;
          this.yAxis.max = max + this.yAxis.padding;
          var sum = this.yAxis.max - this.yAxis.min;
          var plotHeight = this.height - this.edge.upper - this.edge.bottom;
          var ygap = plotHeight/sum;
          this.yAxis.plotheight = plotHeight;
          this.yAxis.ygap = ygap;
          var index = Math.floor(this.yAxis.min);
          var first = true;
        // draw Y Axis and dash line
          for(var ypos=0; ypos<(plotHeight + this.edge.upper/2); ypos+=ygap*this.yAxis.tickSize)
          {
            this.customFillText(ctx, this.isIE ? (this.edge.left/3) : this.edge.left/2, this.isIE ? (this.height - this.edge.bottom - Math.floor(ypos)-15) : this.height - this.edge.bottom - Math.floor(ypos), 
                40, 0, "" + index, "white", "white", "black", "10px Arial");
            index = index + this.yAxis.tickSize;
            
            // draw dash line
            if(!first) {
              this.dashedLineTo(ctx, this.edge.left, this.height - this.edge.bottom - Math.floor(ypos), this.width - this.edge.right, this.height - this.edge.bottom - Math.floor(ypos), 5);                            
            }
            first = false;
          }
          
          // draw X Axis sub title
        var csize = this.xAxis.categories.length;
        var plotwidth = this.width - this.edge.left - this.edge.right;
        delta = plotwidth/ csize;
        var xpos = this.edge.left;
        for(var x=0; x<csize; x++) {
          this.customFillText(ctx, (xpos + delta/2), this.isIE ? (this.height - (this.edge.bottom*0.75) - 15):this.height - (this.edge.bottom*0.75), 
              50, 0, this.xAxis.categories[x], "white", "white", "black", "10px Arial");
          xpos += delta;
        }
          
      } else if(this.type == "scatter") {
        
      } else if(this.type == "column") {
        
      } else if(this.type == "bar") {
        
      }
    },
    
    drawXYTitles : function(ctx) {
      if(this.isIE) {
          // draw X Axis and title
          var xLine = document.createElement("v:line");
          xLine.from = this.edge.left + "," + (this.height-this.edge.bottom);
          xLine.to = (this.width - this.edge.right) + "," + (this.height-this.edge.bottom);
          ctx.insertBefore(xLine);
          this.customFillText(ctx, this.width/2, this.height-this.edge.bottom/2, (this.width/2 - this.edge.right), 0, this.xAxis.title, "white", "white", "black", "12px Arial");
 
          // draw Y title, VML rotate text is very trick thing,
        // the center point is itself half of width and height, very bad way
        var yTitleRect = document.createElement("v:rect");
        yTitleRect.style.left= Math.floor(-this.height/2 + 10) + "px";
        yTitleRect.style.top= (this.height - this.edge.bottom) + "px";
        yTitleRect.style.width=Math.floor(this.height) + "px";
        yTitleRect.style.height="0px";
        yTitleRect.fillcolor = "white";
        yTitleRect.strokecolor="white";
        yTitleRect.strokeweight="0pt";
        
        var ytitleTextBox = document.createElement("v:textbox");
        ytitleTextBox.style.font = "12px Arial";
        ytitleTextBox.innerText = this.yAxis.title;
        yTitleRect.style.rotation = "90"; // only make the text vertical, bad!!!
        yTitleRect.insertBefore(ytitleTextBox);   
        ctx.insertBefore(yTitleRect);
        
      } else {
        // draw X Axis and title
        ctx.save();
        this.onePixelLineTo(ctx, this.edge.left, this.height-this.edge.bottom, this.width - this.edge.right, this.height-this.edge.bottom, "white", false);   
        ctx.restore();
        ctx.font="12px Arial";
        ctx.fillText(this.xAxis.title, this.width/2, this.height-this.edge.bottom/4);
        
        // draw Y title
        ctx.save();
        ctx.font="12px Arial";
        ctx.translate(this.width/2, this.height/2);
        ctx.rotate(-Math.PI/2);
        ctx.fillText(this.yAxis.title, 20, -(this.width/2 - 20));
        ctx.restore();        
      }
    },
    
    renderLegend : function(ctx) {
      if(!this.legend.enable || this.type == "pie") {
        return;
      }
      // TODO:zhigang, legend here
    },
    
    renderBorder : function(ctx) {
      this.renderRect(ctx, 0, 0, this.width, this.height, "white", "black", 1);
    },
    
    playAnimation : function(parent) {
      
      if(parent.animation.hh < parent.height) {
        if(parent.isIE) {
          var node = document.getElementById("fishchart_mask_rect");
          node.style.top= this.animation.hh + "px";
          node.style.height= (this.height - this.animation.hh) + "px";
          parent.animation.hh = parent.animation.hh + 10;
          setTimeout(function() {parent.playAnimation(parent);}, 1000/20);
        } else {
          parent.animation.pctx.save();
          parent.animation.pctx.globalAlpha=0.5;
          parent.animation.pctx.clearRect(0,0,parent.width, parent.height);
          parent.renderBorder(parent.animation.pctx);
          parent.animation.pctx.drawImage(parent.animation.animCanvas, 0, 0, parent.width, this.animation.hh, 0, 0, parent.width, this.animation.hh);
          parent.animation.hh = parent.animation.hh + 10;
          parent.animation.pctx.restore();              
          setTimeout(function() {parent.playAnimation(parent);}, 1000/20);          
        }
      } else {
        // remove xxxxx or change the alpha value
        if(parent.isIE) {         
            var node = document.getElementById("fishchart_mask_rect");
            if(node != null) {          
              parent.animation.pctx.removeChild(node);        
            }
            
        } else {
          parent.animation.pctx.clearRect(0,0,parent.width, parent.height);
          parent.renderBorder(parent.animation.pctx);
          parent.animation.pctx.drawImage(parent.animation.animCanvas, 0, 0, parent.width, parent.height, 0, 0, parent.width, parent.height);         
        }
        
        // enable tool-tip functionality
          if(!this.isIE && parent.animation.enable && parent.tooltip.enable) {
            parent.chartCanvas.addEventListener('mousemove', function(event) {
              var x = event.pageX;
              var y = event.pageY;
              var canvas = event.target;
              var bbox = canvas.getBoundingClientRect();
              var loc = { x: x - bbox.left * (canvas.width  / bbox.width),
                  y: y - bbox.top  * (canvas.height / bbox.height)};
              console.log("ddddddddddddddddddddd");
              parent.showTooltips(loc, (parent.animation.enable ? parent.animation.pctx : ctx));
              }, false);
          }
      }
    },
    
    clearTooltips : function(ctx) {
    if(this.animation.enable) {
      ctx.clearRect(0,0,this.width, this.height);
      this.renderBorder(ctx);
      ctx.drawImage(this.animation.animCanvas, 0, 0, this.width, this.height, 0, 0, this.width, this.height);       
    } else {      
      this.redrawPlot(ctx);
    }
    },
    
    redrawPlot : function(ctx) {
      if(this.type === "pie") {
        this.redrawPie(ctx);
      } else if(this.type == "line") {
        ctx.clearRect(0,0,this.width, this.height);
        // draw title
        this.customFillText(ctx, (this.width/2 + this.title.x), (this.isIE ? 2 : this.edge.upper/2), this.width/2, 0, this.title.text, "white", "white", "black", '18pt Calibri');
        // draw XY Axis
        this.renderXYAxis(ctx);
        // draw lines
          var nums = this.series.length;
          for(var i=0; i<nums; i++) {
            this.renderOneLineCurve(ctx, i);
          }
      }
    },
    
    showTooltips : function(loc, ctx) {
      if(this.type === "pie") {
        this.showPieTooltips(loc, ctx);
      } else if(this.type === "line") {
        this.showLineTooltips(loc, ctx);
      }
    },
    
    getCanvasContext : function() {
      var ctx = null;
      if(this.isIE) {
        // create group
        ctx = document.createElement("v:group");
        var min_size = Math.min(this.width, this.height);
        ctx.style.width = min_size;
        ctx.style.height = min_size;
        
        // default the group will be center inside the DIV element
        ctx.coordsize = min_size + ' ' + min_size;
        this.container.insertBefore(ctx);
        if(this.animation.enable) {
          this.animation.animCanvas = document.createElement("v:rect");
          this.animation.animCanvas.id = "fishchart_mask_rect";
          this.animation.animCanvas.style.left= "0px";
          this.animation.animCanvas.style.top= "0px";
          this.animation.animCanvas.style.width= this.width + "px";
          this.animation.animCanvas.style.height= this.height + "px";
          this.animation.animCanvas.fillcolor = "white";
          this.animation.animCanvas.strokecolor = "white";
          this.animation.animCanvas.strokeweight= "0pt";
          var ttfill = document.createElement("v:fill");
          ttfill.opacity = "100%";
          this.animation.animCanvas.insertBefore(ttfill); 
          this.animation.pctx = ctx;
        }
      } else {
          if(this.animation.enable) {
            this.animation.animCanvas = document.createElement("canvas");
              this.animation.animCanvas.width = this.width;
              this.animation.animCanvas.height = this.height;
              this.animation.pctx = this.chartCanvas.getContext("2d"); // parent ctx
              ctx = this.animation.animCanvas.getContext("2d");
          } else {
            ctx = this.chartCanvas.getContext("2d");
          }
      }
      return ctx;
    },
    
    getRoundRect : function(ctx, x, y, width, height, radius, fill, stroke) {
    if (typeof stroke == "undefined") {
      stroke = true;
    }
    if (typeof radius === "undefined") {
      radius = 5;
    }
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y+ height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
    if (stroke) {
      ctx.stroke();
    }
    if (fill) {
      ctx.fill();
    }
    },
    
    renderRect : function (ctx, x, y, width, height, fillColor, strokeColor, lineWidth) 
    {
      if(this.isIE) {
        var vrect = document.createElement("v:rect");
        vrect.style.left= x + "px";
        vrect.style.top= y + "px";
        vrect.style.width= width + "px";
        vrect.style.height= height + "px";
        vrect.fillcolor = fillColor;
        vrect.strokecolor=strokeColor;
        vrect.strokeweight= lineWidth + "pt";
        ctx.insertBefore(vrect);
      } else {
        ctx.save();
        ctx.fillStyle= fillColor;
        ctx.strokeStyle= strokeColor;
        ctx.fillRect(x, y, width, height);
        ctx.strokeRect(x, y, width, height);
        ctx.restore();
      }
    },
    
    customFillText : function (ctx, x, y, width, height, textContent, fillColor, strokeColor, fontColor, fontSize)
    {
      if(this.isIE) {
        var textRect = document.createElement("v:rect");
        textRect.style.left=(x) + "px";
        textRect.style.top=(y) + "px";
        textRect.style.width=width + "px";
        textRect.style.height=height + "px";
        textRect.fillcolor = fillColor;
        textRect.strokecolor = strokeColor;
        textRect.strokeweight="0pt";
        var textBox = document.createElement("v:textbox");
        textBox.style.font = fontSize;// "10pt Calibri";
        textBox.innerText = textContent;
        
      // make stroke and fill attribute transparent is 100%
      var ttfill = document.createElement("v:fill");
      ttfill.opacity = "0%";
      var ttstroke = document.createElement("v:stroke");
      ttstroke.opacity = "0%";
      
      // append to group element
      textRect.insertBefore(ttstroke);
      textRect.insertBefore(ttfill);      
        textRect.insertBefore(textBox);
        ctx.insertBefore(textRect);       
      } else {
        ctx.save();
        ctx.font = fontSize;//'10pt Calibri';
        ctx.fillStyle=fontColor;
        ctx.fillText(textContent, x, y);
        ctx.restore();
      }
    },
    
    onePixelLineTo : function(ctx, fromX, fromY, toX, toY, backgroundColor, vertical) {
      var currentStrokeStyle = this.strokeStyle;
      ctx.beginPath();
      ctx.moveTo(fromX, fromY);
      ctx.lineTo(toX, toY);
      ctx.closePath();
      ctx.lineWidth=2;
      ctx.stroke();
      ctx.beginPath();
      if(vertical) {
        ctx.moveTo(fromX+1, fromY);
        ctx.lineTo(toX+1, toY);
      } else {
        ctx.moveTo(fromX, fromY+1);
        ctx.lineTo(toX, toY+1);
      }
      ctx.closePath();
      ctx.lineWidth=2;
      ctx.strokeStyle=backgroundColor;
      ctx.stroke();
      ctx.strokeStyle = currentStrokeStyle;
    },
    
    dashedLineTo : function (ctx, fromX, fromY, toX, toY, pattern) {
    if(this.isIE) {
      var vdashline = document.createElement("v:line");
      var vdashlineStroke = document.createElement("v:stroke");
      vdashline.from = fromX + "," + fromY;
      vdashline.to = toX + "," + toY;
      vdashlineStroke.dashstyle="dash";
      vdashline.insertBefore(vdashlineStroke);
      ctx.insertBefore(vdashline);                
    } else {
        ctx.save();
        ctx.lineWidth = 1;
        ctx.translate(0.5,0.5);
      // default interval distance -> 5px
        if (typeof pattern === "undefined") {
          pattern = 5;
        }
  
        // calculate the delta x and delta y
        var dx = (toX - fromX);
        var dy = (toY - fromY);
        var distance = Math.floor(Math.sqrt(dx*dx + dy*dy));
        var dashlineInteveral = (pattern <= 0) ? distance : (distance/pattern);
        var deltay = Math.floor((dy/distance) * pattern);
        var deltax = Math.floor((dx/distance) * pattern);
        
        // draw dash line
        ctx.beginPath();
        for(var dl=0; dl<dashlineInteveral; dl++) {
          if(dl%2) {
            ctx.lineTo(fromX + dl*deltax, fromY + dl*deltay);
          } else {            
            ctx.moveTo(fromX + dl*deltax, fromY + dl*deltay);           
          }         
        }
        ctx.stroke();
        ctx.restore();
    }
    }
    
};

相关文章
|
2月前
|
Web App开发
在 HTML 中禁用 Chrome 浏览器的 Google 翻译功能
在 html 标签中添加 translate=“no” 属性,浏览器将不会翻译整个页面。
142 0
|
1月前
|
Web App开发
在HTML中用meta控制浏览器默认模式
在HTML文档的`&lt;head&gt;`标签中,通过添加不同的`&lt;meta&gt;`标签可指定浏览器默认使用的内核。使用`&lt;meta name=&quot;renderer&quot; content=&quot;webkit&quot;&gt;
28 1
|
1月前
|
移动开发 前端开发 JavaScript
纯HTML5 Canvas实现的饼图
纯HTML5 Canvas实现的饼图
22 6
|
12天前
|
JavaScript 前端开发 物联网
文本,Vue实现打印的方式,打印机的种类有多少,浏览器打印html,右键,2打印插件,3指令打印,vue-print-nb
文本,Vue实现打印的方式,打印机的种类有多少,浏览器打印html,右键,2打印插件,3指令打印,vue-print-nb
|
1月前
|
移动开发 前端开发 JavaScript
浏览器端图表渲染技术SVG, VML HTML Canvas
浏览器端图表渲染技术SVG, VML HTML Canvas
16 0
|
2月前
|
前端开发 JavaScript
浏览器通过构建DOM树来解析HTML代码
【4月更文挑战第30天】浏览器通过构建DOM树来解析HTML代码
35 1
|
2月前
|
搜索推荐 前端开发 UED
html页面实现自动适应手机浏览器(一行代码搞定)
html页面实现自动适应手机浏览器(一行代码搞定)
114 0
|
8天前
|
前端开发 UED
使用HTML和CSS创建响应式表格
在网页设计中,表格是一种组织和展示数据的有效方式。本文档将指导你如何使用HTML和CSS来创建一个既美观又响应式的表格,以便在不同设备和屏幕尺寸上都能良好显示。我们将涵盖基础的HTML表格结构,样式美化以及如何实现响应式布局,使得表格内容在小屏设备上也能清晰阅读
|
12天前
|
前端开发 JavaScript
文本,wangEditor5展示HTML无样式,wangEditor5如何看源码,Ctrl + U看CSS文件,代码高亮,Prism.js可以实现,解决方法,参考网页源代码的写法
文本,wangEditor5展示HTML无样式,wangEditor5如何看源码,Ctrl + U看CSS文件,代码高亮,Prism.js可以实现,解决方法,参考网页源代码的写法
|
14天前
|
前端开发 JavaScript
【HTML+CSS+JavaScript】Animated Navigation
【HTML+CSS+JavaScript】Animated Navigation
9 0