Question

I have a pie chart in canvas and I wanted to plot random points in each sector of that pie.

I have got the area of each sector. using the arc sector

var arcsector = Math.PI * (2 * sector / total);
var startAngle = (lastend - offset) * (radius/Math.PI);
var endAngle = (lastend + arcsector - offset) * (radius/Math.PI);
var sectorAngle = arcsector * (radius/Math.PI);
var sectorArea = .5 * (sectorAngle*Math.PI/180) * (radius*radius);

How can I randomly plot points within that area?

Was it helpful?

Solution

A pie is a part of a circle, which, with your notations, starts at startAngle and ends at endAngle.

Most simple way to get a random point is to build a random angle (between startAngle and endAngle) and a random radius, then you have your point with those lines :

 var randAngle  = startAngle + Math.random()*( endAngle - startAngle );
 var randRadius = Math.random()*radius;
 var randX = centerX + randRadius * Math.cos(randAngle);
 var randY = centerY + randRadius * Math.sin(randAngle);
 ctx.fillRect ( randX, randY, 1, 1 ) ;

repeat the number of times required !

OTHER TIPS

The simple approach is to:

  • Create a temporary arc shape on path
  • Create a random point
  • Hit-test the point against the shape and plot if inside

You can create a temporary arc path by doing something like this (adjust to match your situation) (and no need to stroke/fill):

ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();

Then create random points within the bounds of that arc, or just use a very basic approach (which is probably fast enough in most case unless you would need a lot of points) - and the spread is even compared to using a radius based approach:

var randomX = cx + radius * 2 * Math.random() - radius;
var randomY = cy + radius * 2 * Math.random() - radius;

and finally hit-test:

if (ctx.isPointInPath(randomX, randomY)) {
    // plot point, count etc.
}

FIDDLE

Update

An even more efficient way to generate random points in the arc shape (and spread them more even) is to draw directly to an off-screen canvas without using any bound checking and no cos/sin operations, which are expensive, and finally composite that on top of your arc shape (or use arc as clip).

// create off-screen canvas
var ocanvas = document.createElement('canvas');
var octx = ocanvas.getContext('2d');
var d;
d = ocanvas.width = ocanvas.height = 300;

octx.fillStyle = '#fff';

while(count) {
    var randomX = d * Math.random();
    var randomY = d * Math.random();
    octx.fillRect(randomX - 1, randomY - 1, 2, 2);
    count--;
}

// composite random points with main arc    
ctx.globalCompositeOperation = 'source-atop';
ctx.drawImage(ocanvas, 0, 0);
ctx.globalCompositeOperation = 'source-over';

It can be optimized further by having the off-screen canvas represent only the bounds of the arc shape.

FIDDLE

Demo: http://jsfiddle.net/jv6nP/3/

it's not perfect that points are at border and thus their radius being bigger than zero makes them overlap onto other parts of pie. And this also results in them going over black border.

var can = $('#can')[0].getContext('2d'), 
    border=2, 
    x=100, 
    y=75, 
    r=60, 
    sRadius= 0,
    leadAngle=null, 
    points= [], 
    dotRadius=2,
    data = {
        water:[30,'#5CC5FA'],
        earth:[60,'#F0A71F'],
        air:[10,'#26EDE3']
    };



function reDraw(){

    //making border...
    can.beginPath();
    can.arc(x,y,r+border,0,2*Math.PI);
    can.fillStyle='black';
    can.fill();


var newAngle=null;
for (var k in data) { //making piechart..

    leadAngle = (2*Math.PI)*(data[k][0]/100);
    newAngle = sRadius+leadAngle;

    calPoints(sRadius,leadAngle,k);

    can.beginPath();
    can.arc(x,y,r,sRadius,newAngle);
    can.lineTo(x,y);
    can.fillStyle=data[k][1];
    can.fill();
    sRadius= newAngle;
}

//calculating points..
function calPoints(s,e,name) {
    if (name!='water') return;
    var py,px,rAngle,rRad;
    for (var i=0; i<15; i++) {
         rAngle=s+Math.random()*(e);
         rRad = Math.random()*r;
         px = (Math.cos(rAngle) * rRad)+x;
         py = (Math.sin(rAngle) * rRad)+y;
         points.push([px,py]);
    }
}


//plotting dots from data...
points.forEach(function(v){
        can.beginPath();
        can.arc(v[0],v[1],dotRadius,0,2*Math.PI);
        can.fillStyle='fff';
        can.fill();
});

    points=[];
    requestAnimationFrame(reDraw);

}

reDraw();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top