使用画布创建极地面积图 [英] Creating Polar Area Chart using Canvas

查看:212
本文介绍了使用画布创建极地面积图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用canvas在这里创建极地图:



http://jsfiddle.net/wm7pwL2w/2/



代码:

  var myColor = [#ff0,#00f,#002,#003,#004]; 
var myData = [10,30,20,60,40];
var myRadius = [120,80,40,70,40];

function getTotal(){
var myTotal = 0;
for(var j = 0; j< myData.length; j ++){
myTotal + =(typeof myData [j] =='number')? myData [j]:0;
}
return myTotal;
}

function plotData(){
var canvas;
var ctx;
var lastend = 0;
var myTotal = getTotal();

canvas = document.getElementById(canvas);
ctx = canvas.getContext(2d);
ctx.clearRect(0,0,canvas.width,canvas.height);

for(var i = 0; i< myData.length; i ++){
ctx.fillStyle = myColor [i];
ctx.beginPath();
ctx.moveTo(200,150);
ctx.arc(200,150,myRadius [i],lastend,lastend +(Math.PI * 2 *(myData [i] / myTotal)),false);
controller.log(myRadius [i]);
ctx.lineTo(200,150);
ctx.fill();
lastend + = Math.PI * 2 *(myData [i] / myTotal);
}
}

plotData();

更新:
要清除这些,想要实现:



< >



这种风格:





(这是一个简单的饼图)
我不能用我当前的实现实现第二部分(爆炸切片)。

解决方案

这将使用对象模型,以及保持图表和切片之间的父子关系。这样我可以使用图表模型,它渲染所有的孩子,我可以扩展切片对象做更强大的东西。在这个例子中我不支持文本,但这应该很容易从这里添加。



好,首先让我们构建一个父对象 - 图表本身:

 函数Chart(x,y){
this.x = x; //公开这些值,所以我们可以从外面改变它们
this.y = y; //以及原型(见下文)
this.total = 0;
this.slices = [];
}

它非常基本,它还没有包含我们需要的所有功能。我们可以直接将该函数构建到这个对象中,但是如果我们使用Chart对象的几个实例,在每个实例之间共享该内存空间会更聪明,所以我们要使用原型模型。



先让一个函数添加一个 Slice 对象:

  Chart.prototype.addSlice = function(data,radius,color,offset){

var slice = new Slice(data,radius,color,offset);

this.slices.push(slice); //将slice添加到内部数组
this.total + = data; // update total value

return this; //只是为了调用链接
};

这里我们看到它创建了一个 Slice 对象(见下面),然后将它添加到slice数组,更新总和并返回自身,以便我们链接它。



Slice object(child)在这里是相当简单的,但通过保持它作为一个对象而不是一个文本对象或数组,我们可以扩展它以后强大的功能,如果我们希望对父(或)可以让父调用每个切片本身的渲染向量来渲染自己,而不是在父级渲染。此外,对象在现代浏览器中编译得很好:

  function Slice(data,radius,color,offset){
this.data = data; // self-expl。
this.radius = radius
this.color = color;
this.offset = offset || 0; //如果没有给出则默认为0
}

我们支持一个偏移值(从中心),如果没有给出,它默认为0.



现在我们需要一个函数来遍历每个切片并渲染



奇怪的事情发生在这里:

  Chart.prototype.render = function(){

var i = 0,s,//迭代器,切片对象
angle,angleHalf,/​​/ angle based on data and total
currentAngle = 0,//当前角度为渲染目的
pi2 = 2 * Math.PI; // cache PI * 2

//在slice数组中的每个切片上迭代(参见addSlice())
for(; s = this.slices [i ++];){
angle = s.data / this.total * pi2; // calc。这个切片的角度
angleHalf = angle * .5; // calc。中心半角

ctx.translate(this.x,this.y); // move to pivot point
ctx.rotate(currentAngle); //旋转到累积角度

//爆炸发生在这里...
ctx.translate(s.offset * Math.cos(angleHalf),// translate so slice
s.offset * Math.sin(angleHalf)); // explodes using center

ctx.beginPath(); // draw slice(外部笔画在这里不显示)
ctx.moveTo(0,0);
ctx.arc(0,0,s.radius,0,angle);
ctx.fillStyle = s.color;
ctx.fill();

ctx.setTransform(1,0,0,1,0,0); //重置所有变换
currentAngle + = angle; // accumulate angle of slice
}
};

就是这样。转换的顺序很重要:




  • 先转换为旋转中心



  • $ b 现在,我们可以这样创建图表和切片:

      var myChart = new Chart(canvas.width * .5,canvas。高度* .5); 

    //添加一些切片到图表
    myChart.addSlice(10,120,'#ff0')
    .addSlice(30,80,'#00f')
    。addSlice(20,40,'#002')
    .addSlice(60,70,'#003')
    .addSlice(40,40,'#004');

    对于每次添加,数据值累积为一个总值。这个总值然后变成用于找出每个切片应当有多大角度的值:

      angle = s.data / this.total * pi2; // calc。角度为这个切片

    这里我们首先得到总数的百分比:

      s.data / this.total 

    此百分比用于整圈(2 x PI):

      pst *(2 * PI) 

    因此,无论我们添加多少个切片,我们都将动态调整它们相对于彼此的角度, 。



    现在,只需调用:

      myChart.render ; 

    来呈现所有。



    调整,甚至为偏移量设置动画,我们可以创建效用函数,例如在下面的实际代码中,或者直接为数组中的每个切片设置偏移:

      myChart.slices [sliceIndex] .offset = value; 

    requestAnimationFrame 你可以用各种偏移量来动画它,你需要担心的是一维值(任何人关心正弦波爆炸?)。



    如何定义参数和对象的方法取决于你,但是你应该能够根据需要扩展和改进。



    希望这有助于!



     

      #canvas {display:inline-block}  

     < canvas id = canvaswidth = 360 height = 180>< / canvas>< button id =oPlus> Offset +< / button>< button id =oMinus> Offset-< / button> ; button id =oRnd>随机< / button>  


    I am trying to create Polar Area Chart using canvas here :

    http://jsfiddle.net/wm7pwL2w/2/

    Code:

    var myColor = ["#ff0", "#00f", "#002", "#003", "#004"];
    var myData = [10, 30, 20, 60, 40];
    var myRadius = [120, 80, 40, 70, 40];
    
    function getTotal() {
        var myTotal = 0;
        for (var j = 0; j < myData.length; j++) {
            myTotal += (typeof myData[j] == 'number') ? myData[j] : 0;
        }
        return myTotal;
    }
    
    function plotData() {
        var canvas;
        var ctx;
        var lastend = 0;
        var myTotal = getTotal();
    
        canvas = document.getElementById("canvas");
        ctx = canvas.getContext("2d");
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    
        for (var i = 0; i < myData.length; i++) {
            ctx.fillStyle = myColor[i];
            ctx.beginPath();
            ctx.moveTo(200, 150);
            ctx.arc(200, 150, myRadius[i], lastend, lastend + (Math.PI * 2 * (myData[i] / myTotal)), false);
            console.log(myRadius[i]);
            ctx.lineTo(200, 150);
            ctx.fill();
            lastend += Math.PI * 2 * (myData[i] / myTotal);
        }
    }
    
    plotData();
    

    Update: To clear things out this is what I want to implement :

    <>

    This style with this :

    (This is a simple pie chart) I am not able to implement the second part(exploding the slices) with my current implementation.

    解决方案

    For this I would use an object model as well as keeping a parent-child relationship between the chart and the slices. This way I can work with just the chart model having it render all the children, and I could expand the slice object to do more powerful stuff. I did not support text in this example but this should be easy to add from here.

    Ok, first lets built a parent object - the chart itself:

    function Chart(x, y) {        
      this.x = x;       // expose these values so we can alter them from outside
      this.y = y;       // as well as within the prototypes (see below)
      this.total = 0;
      this.slices = [];
    }
    

    Its pretty basic and it does not yet contain all the functionality we need. We could build the function directly into this object but in case we use several instances of the Chart object it would be smarter to share that memory-space between each instance, so we're gonna use the prototype model instead.

    Lets first make a function to add a Slice object:

    Chart.prototype.addSlice = function(data, radius, color, offset) {
    
      var slice = new Slice(data, radius, color, offset);
    
      this.slices.push(slice);  // add slice to internal array
      this.total += data;       // update total value
    
      return this;              // just to make call chain-able
    };
    

    Here we see it creates a Slice object (see below), then adds it to the slice array, updates the total sum and returns itself so we can chain it.

    The Slice object (child) is fairly simple here, but by keeping it as an object rather than a literal object or an array we can expand it later with powerful functionality if we want with little to no modification of parent (you could have parent call a render vector on each slice itself to render itself instead of doing it in parent). Besides from that, objects compile well within modern browsers:

    function Slice(data, radius, color, offset) {
      this.data = data;           // self-expl.
      this.radius = radius
      this.color = color;  
      this.offset = offset || 0;  // default to 0 if non is given
    }
    

    That's about it. We support an offset value (from center) which defaults to 0 if not given.

    All we need to do now is to have a function that iterates over each slice and render them to canvas at offset, angle, color and so forth.

    The magic happens here:

    Chart.prototype.render = function() {
    
      var i = 0, s,             // iterator, slice object
          angle, angleHalf,     // angle based on data and total
          currentAngle = 0,     // current angle for render purpose
          pi2 = 2 * Math.PI;    // cache PI*2
    
      // iterate over each slice in the slice array (see addSlice())
      for(; s = this.slices[i++];) {
          angle = s.data / this.total * pi2; // calc. angle for this slice
          angleHalf = angle * .5;            // calc. half angle for center
    
          ctx.translate(this.x, this.y);     // move to pivot point
          ctx.rotate(currentAngle);          // rotate to accumulated angle
    
          // The "explosion" happens here...
          ctx.translate(s.offset * Math.cos(angleHalf),  // translate so slice
                        s.offset * Math.sin(angleHalf)); // explodes using center
    
          ctx.beginPath();             // draw slice (outer stroke not shown here)
          ctx.moveTo(0, 0);
          ctx.arc(0, 0, s.radius, 0, angle);
          ctx.fillStyle = s.color;
          ctx.fill();
    
          ctx.setTransform(1, 0, 0, 1, 0, 0);// reset all transforms
          currentAngle += angle;             // accumulate angle of slice
      }
    };
    

    That´s it. The order of the transforms is important:

    • First translate to center of rotation
    • Rotate around that center
    • Offset translate based on that rotation + half angle (in this case)

    Now we can create the charts and the slices this way:

    var myChart = new Chart(canvas.width * .5, canvas.height * .5);
    
    // add some slices to the chart
    myChart.addSlice(10, 120, '#ff0')
           .addSlice(30,  80, '#00f')
           .addSlice(20,  40, '#002')
           .addSlice(60,  70, '#003')
           .addSlice(40,  40, '#004');
    

    For each add the data value is accumulated to a total value. This total value then becomes the value used to find how large the angle should be for each slice:

    angle = s.data / this.total * pi2; // calc. angle for this slice
    

    Here we first get a percentage of total:

    s.data / this.total
    

    this percentage is used of the full circle (2 x PI):

    pst * (2 * PI);
    

    So no matter how many slices we add we will dynamically adjust their angles relative to each other and the total.

    Now, simply call:

    myChart.render();
    

    to render it all.

    To adjust, and even animate the offsets, we can create utility functions such as in the live code below, or simply set an offset directly for each slice in the array:

    myChart.slices[sliceIndex].offset = value;
    

    Put it in a loop with requestAnimationFrame and you can animate it with various offsets and all you need to worry about is 1-dimensional values (anyone care for a sinus wave explosion?).

    How you define the parameters and methods for the objects is up to you, but with this you should be able to expand and refine as needed.

    Hope this helps!

    // Main object (parent of slices)
    function Chart(x, y) {
        
      this.x = x;
      this.y = y;
      this.total = 0;
      
      this.slices = [];
    }
    
    // shared function to all chart instances to add a slice to itself
    Chart.prototype.addSlice = function(data, radius, color, offset) {
      var slice = new Slice(data, radius, color, offset);
      this.slices.push(slice);
      this.total += data;
      return this;
    };
    
    // shared function to all chart instances to render itself
    Chart.prototype.render = function() {
    
      var i = 0, s,
          angle, angleHalf,
          currentAngle = 0,
          pi2 = 2 * Math.PI;
    
      ctx.lineWidth = 7;
      ctx.strokeStyle = '#79f';
      
      for(; s = this.slices[i++];) {
          angle = s.data / this.total * pi2;
          angleHalf = angle * .5;
          
          ctx.translate(this.x, this.y);
          ctx.rotate(currentAngle);
          ctx.translate(s.offset * Math.cos(angleHalf), s.offset * Math.sin(angleHalf));
        
          ctx.beginPath();
          ctx.moveTo(0, 0);
          ctx.arc(0, 0, s.radius, 0, angle);
          ctx.fillStyle = s.color;
          ctx.fill();
    
          ctx.beginPath();
          ctx.arc(0, 0, s.radius, 0, angle);
          ctx.stroke();
        
          ctx.setTransform(1, 0, 0, 1, 0, 0);
          currentAngle += angle;
      }
    
      return this;
    };
    
    // utility method to add offset to all child-slices.
    // offset can be added to each individual slice as well
    Chart.prototype.addOffsetToAll = function(offset) {
      for(var i = 0, s; s = this.slices[i++];) s.offset += offset;
      return this;
    };
    
    // Child object, slice to be added to parent internally
    function Slice(data, radius, color, offset) {
      this.data = data;
      this.radius = radius
      this.color = color;  
      this.offset = offset || 0;
    }
    
    
    // MAIN CODE HERE
    
    var canvas = document.getElementById('canvas'),
        ctx = canvas.getContext('2d'),
    
        // create a chart instance with center at the center of canvas
        myChart = new Chart(canvas.width * .5, canvas.height * .5),
        offset = 0; // for adjusting offset later
    
    // add some slices to the chart
    myChart.addSlice(10, 120, '#ff0')
           .addSlice(30,  80, '#00f')
           .addSlice(20,  40, '#f72')
           .addSlice(60,  70, '#003')
           .addSlice(25,  80, '#555')
           .addSlice(40,  40, '#052');
    
    // render function to clear canvas, update offsets and render again
    function render() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    
      myChart.addOffsetToAll(offset)
             .render();
    }
    
    // initial render
    render();
    
    // handle buttons
    document.getElementById('oPlus').addEventListener('click', function() {
      offset = 2;
      render();
    }, false);
    
    document.getElementById('oMinus').addEventListener('click', function() {
      offset = -2;
      render();
    }, false);
    
    // this is how you can adjust each individual slice
    document.getElementById('oRnd').addEventListener('click', function() {
      
      for(var i = 0, s; s = myChart.slices[i++];) s.offset = 15 * Math.random();
      offset = 0;
      render();
    }, false);

    #canvas {display:inline-block}

    <canvas id="canvas" width=360 height=180></canvas>
    <button id="oPlus">Offset+</button>
    <button id="oMinus">Offset-</button>
    <button id="oRnd">Random</button>

    这篇关于使用画布创建极地面积图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆