纯HTML5 Canvas实现的饼图

简介: 纯HTML5 Canvas实现的饼图

基本思路:

主要是利用HTML5 Canvas实现饼图绘制,绘制弧度的API主要是使用

context.arc与lineto两个API。

实现的功能有:

1. 支持标签Legend显示或者隐藏

2. 首次载入动画效果

3. 鼠标tooltip效果

4. 自定义饼图大小与是否添加文字

效果如下:


调用代码:

<html>
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=IE8">
<script src="js/fishcomponent.js"></script>
<script src="js/pie.js"></script>
<title>My Demo 1</title>
  <script>
  window.onload = function() {
    var canvas = document.getElementById("pie_canvas");
    var seriesData = [{name:"apples", value:150, color:"RGBA(255,0,0,1)"},
                  {name:"orange", value:100, color:"RGBA(255,255,0,1)"},
                  {name:"banana", value:80, color:"RGBA(255,0,255,1)"},
                  {name:"peaches", value:60, color:"RGBA(0,255,255,1)"},
                  {name:"strawberries", value:40, color:"RGBA(0,127,255,1)"}]
    var config = {
        width : 600, 
        height: 400,
        series: seriesData,
        canvas: canvas,
        unit: "kg",
        title:"Fruit Sales",
        tooltips : {
          enable : true
        },
        animation :{
          enable: true
        },
          legend : {
            enable : true
          },
          text : {
            enable: true
          },
    };
    pieChart.initSettings(config);
    pieChart.render();
  }
  </script>
</head>
<body>
<h1>Pie Chart Demo</h1>
<div id="my_container" style="width:600px; height:500px;">
  <canvas id="pie_canvas"></canvas>
</div>
<div id="btn-group">
  <button type="button" id="clear-button">Clear Plot</button>
  <button type="button" id="refresh-button">Draw Plot</button>
</div>
</body>
</html>

饼图JS的代码:

var pieChart = {
    width: 600,
    height: 400,
    series: [],
    unit: "kg",
    chartCanvas: null,
    selectable : true,
    title: "Pie Chart",
    legend : {
      enable : true
    },
    edge : {
      width: 50,
      height: 50
    },
    animation: {
      enable: true,
      animCanvas : null,
      hh: 1, // trick is here!! for animation play
      pctx: null
    },
    tooltips: {
      enable: true,
      tooltipCanvas : null,
      ttContext: null,
      index: -1
    },
    circle : {
      cx: 0,
      cy: 0,
      radius: 0
      
    },
    text : {
      enable: false,
      content:[]
    },
    
    initSettings: function (config) {
      this.chartCanvas = config.canvas;
      this.chartCanvas.width = config.width;
      this.chartCanvas.height = config.height;
        this.width = config.width;
        this.height = config.height;
        this.series = config.series;
        this.title = config.title;
        this.unit = config.unit;
        if(config.tooltips != undefined) {
          this.tooltips.enable = config.tooltips.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;
        }
    },
    
    render : function() {
      // 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);
      var ctx = null;
      if(this.animation.enable) {
        this.animation.animCanvas = document.createElement("canvas");
          this.animation.animCanvas.width = this.width;
          this.animation.animCanvas.height = this.height;
          ctx = this.animation.animCanvas.getContext("2d");
      } else {
        ctx = this.chartCanvas.getContext("2d");
        this.renderBorder(ctx);
      }
      
      if(this.circle.radius <= 0) {
        ctx.strokeText("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
      ctx.font = '18pt Calibri';
      ctx.fillText(this.title, this.width/2 - this.edge.width, 30);
      ctx.save();
      var deltaArc = 0;
      for(var i=0; i<nums; i++) {
        var precent = this.series[i].value/sum;
        this.renderPie(ctx, i, precent, deltaArc);
        deltaArc += 2*Math.PI * precent;
      }
      ctx.restore();
      
      // add blur shadow
      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();
      
      // render legend
      ctx.save();
      this.renderLegend(ctx, sum);
      ctx.restore();
      
      // play animation
      if(this.animation.enable) {
        var parent = this;
        this.animation.pctx = this.chartCanvas.getContext("2d");
        this.renderBorder(this.animation.pctx);
        setTimeout(function() {parent.playAnimation(parent);}, 1000/20);            
      }
    },
    
    showTooltips : function(loc, ctx) {
      if(!this.tooltips.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.tooltips.tooltipCanvas == null) {
          this.tooltips.tooltipCanvas = document.createElement("canvas");
          this.tooltips.ttContext = this.tooltips.tooltipCanvas.getContext("2d");
            this.tooltips.tooltipCanvas.width = 150;
            this.tooltips.tooltipCanvas.height = 100;
        } 
        
        // only draw once
        // if(index == this.tooltips.index){
        //  return;
        // }
        this.clearTooltips(ctx);
        
        this.tooltips.index = index;
        var m_context = this.tooltips.ttContext;
        m_context.save();
        m_context.clearRect(0, 0, this.tooltips.tooltipCanvas.width, this.tooltips.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);
        m_context.roundRect(2,2,this.tooltips.tooltipCanvas.width-4, this.tooltips.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.tooltips.tooltipCanvas.width)> this.width) {
        loc.x = loc.x - this.tooltips.tooltipCanvas.width;
      }
      if((loc.y - this.tooltips.tooltipCanvas.height) <= 0) {
        loc.y = loc.y + this.tooltips.tooltipCanvas.height;
      }
      ctx.drawImage(this.tooltips.tooltipCanvas, 0, 0, this.tooltips.tooltipCanvas.width, this.tooltips.tooltipCanvas.height, 
          loc.x, loc.y-this.tooltips.tooltipCanvas.height, this.tooltips.tooltipCanvas.width, this.tooltips.tooltipCanvas.height);  
      } else {
        this.tooltips.index = -1;
        this.clearTooltips(ctx);
      }
    },
    
    clearTooltips : function(ctx) {
    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); 
    },
    
    renderBorder : function(ctx) {
    ctx.save();
    ctx.fillStyle="white";
    ctx.strokeStyle="black";
    ctx.fillRect(0, 0, this.width, this.height);
    ctx.strokeRect(0, 0, this.width, this.height);
    ctx.restore();
    },
    
    renderPie : function(ctx, index, precent, deltaArc) {
      var endAngle = deltaArc + 2*Math.PI*precent;
      ctx.beginPath();
      ctx.arc(this.circle.cx, this.circle.cy, this.circle.radius, deltaArc, endAngle, false);
      ctx.moveTo(this.circle.cx, this.circle.cy);
      ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(deltaArc), this.circle.cy + this.circle.radius * Math.sin(deltaArc));
      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.fill();
      
      // render text content
      if(this.text.enable) {        
        var halfEndAngle = deltaArc + 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);
      }
    },
    
    renderLegend : 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;
        ctx.fillStyle = this.series[i].color;
        ctx.fillRect(pos - 40, 20*i+10, 10, 10);
          ctx.fillStyle = "black";
        ctx.fillText(tipStr, pos - 25, 20*i+20);
      }     
    },
    
    playAnimation : function(parent) {  
      if(parent.animation.hh < parent.height) {
        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 {
        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(parent.animation.enable && parent.legend.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)};
              
              parent.showTooltips(loc, (parent.animation.enable ? parent.animation.pctx : ctx));
              }, false);
          }
      }
    },
    
};

源代码可以直接使用

相关文章
|
5月前
|
移动开发 前端开发 HTML5
基于HTML5+Canvas绘制的鼠标跟随三角形碎片光标动画代码
基于HTML5+Canvas绘制的鼠标跟随三角形碎片光标动画特效代码,很有意思,一团三角形碎片跟随鼠标的移动,不冗长、不笨重,反而有一种很轻盈的感觉,非常不错
95 29
|
5月前
|
移动开发 前端开发 HTML5
Html5 Canvas绘制圆形仪表盘动画源码
Html5 Canvas绘制圆形仪表盘动画特效是一款基于HTML5 Canvas绘制的圆形百分比仪表盘动画特效。
53 1
|
6月前
|
Web App开发 移动开发 前端开发
html5 canvas五彩碎纸屑飘落动画特效
h5 canvas飘落纸片动画是一款实现五彩纸屑飘落的背景动画特效,基于canvas绘制的空中飘落的纸屑片动画特效,适用于网页动态背景效果代码。简单使用,欢迎下载!代码适用浏览器:搜狗、360、FireFox(建议)、Chrome、Safari、Opera、傲游、世界之窗,是一款不错的的特效插件,希望大家喜欢!
99 5
|
7月前
|
前端开发
基于canvas实现的彩色纸屑组成文字3d动画HTML源码
基于canvas实现的彩色纸屑组成文字3d动画HTML源码
72 0
基于canvas实现的彩色纸屑组成文字3d动画HTML源码
|
7月前
|
移动开发 前端开发 HTML5
HTML5 Canvas制作的粒子十秒倒计时源码
一段基于HTML5 Canvas制作的粒子爆炸,十秒数字倒计时,全屏倒计时动画效果,给人一种非常大气的视觉感
90 0
HTML5 Canvas制作的粒子十秒倒计时源码
|
7月前
|
前端开发 JavaScript
Canvas三维变化背景动画HTML源码
Canvas三维变化背景动画HTML源码
77 5
|
9月前
|
XML 移动开发 前端开发
HTML5 SVG和canvas的性能探讨
HTML5 中的 SVG(可缩放矢量图形)和 Canvas(画布)分别用于网页图形绘制。SVG 基于矢量图形,使用 XML 描述,适合静态或少量动态内容(如图标、图表),易于编辑且保持高分辨率;Canvas 则基于位图,通过 JavaScript 绘制,更适合快速更新大量图形的场景(如游戏、动态动画),但在复杂图形计算时可能遇到性能瓶颈。总体而言,SVG 适用于静态和少量动态内容,而 Canvas 更适合高频率更新和性能要求高的场景。
|
9月前
|
移动开发 前端开发 JavaScript
HTML5 Canvas详解及应用
HTML5 Canvas 允许通过 JavaScript 在网页上动态绘制图形、动画等视觉内容。首先在 HTML 中定义 `&lt;canvas&gt;` 元素,并通过 JavaScript 获取画布上下文进行绘制。常见方法包括绘制矩形、路径、圆形和文本,以及处理图像和创建动画效果。适用于游戏开发、数据可视化、图像编辑和动态图形展示等多种应用场景。需要注意性能优化、无状态绘制及自行处理事件等问题。
|
9月前
|
移动开发 前端开发 JavaScript
HTML5 + JavaScript绘制饼图+1
HTML5 + JavaScript绘制饼图+1
|
9月前
|
移动开发 前端开发 数据挖掘
用HTML5中的 画布(Canvas)在“圳品”信息系统网页上绘制显示饼图
用HTML5中的 画布(Canvas)在“圳品”信息系统网页上绘制显示饼图

热门文章

最新文章