Reflection vector calculation not working properly - javascript

I have set up a sort of a reflection vector simulator here: https://jsfiddle.net/ahvonenj/1re5n8jg/
I am using Vectorious for my vector calculations and objects.
The program kind of works:
I am casting rays from the middle of the canvas towards the mouse cursor. I can successfully detect ray intersections with walls and the point of the intersection on the wall (red dots). I am also able to get the middle point of the wall (blue dots). I THINK that when I normalize the middle point vector, that is the normal of the wall.
Here is the part that listens to mouse movement, casts the ray, gets the intersection and tried to calculate the reflection vectors ( I have added some comments regarding the actual calculations for the reflection vector and what I think I am calculating):
Global.$canvas.addEventListener('mousemove', function(e)
{
var ctx = Global.ctx;
var rect = canvas.getBoundingClientRect();
Global.Mouse = new Vector([e.clientX - rect.left, e.clientY - rect.top]);
var v1 = Vector.subtract(Global.Mouse, Global.Canvas.Center).normalize();
var mag = Vector.subtract(Global.Canvas.Center, Global.Mouse).magnitude();
var ray = Vector.scale(v1, mag * 3).add(Global.Canvas.Center);
ctx.clearRect(0, 0, 600, 600);
ctx.beginPath();
for(var i = 0; i < Global.Canvas.Walls.length; i++)
{
var wall = Global.Canvas.Walls[i];
if(rayIntersectsWith(wall, [Global.Canvas.Center, ray]))
{
// x_1' - x_0 = v - 2(v dot ñ)ñ
// Where ñ = Normal of the wall
// and v = Vector from center to the point of intersection with the wall
// http://mathworld.wolfram.com/Reflection.html
// This is the point on wall where the intersection happens
var point = rayIntersectionPoint(wall, [Global.Canvas.Center, ray])
// This is the full ray cast from center towards the mouse
var d = ray;
// This (probably) is the vector from center to the intersection point on the wall
var v = Vector.subtract(point, Global.Canvas.Center);
// This represents the wall vector, or a middle of the wall (blue dot on wall)
var wallVector = Vector.add(wall[0], wall[1]).scale(0.5);
// This is supposed to be the normal of the wall
var wallNormal = Vector.normalize(wallVector);
// This is supposed to be the 2(v dot ñ) part of the equation
var v_dot_n = Vector.dot(v, wallNormal) * 2;
// This is supposed to be the v_dot_n * ñ of the equation
var v_dot_n_scaled_by_n = Vector.scale(wallNormal, v_dot_n);
// This is supposed to be the v - v_dot_n_scale_by_n part of the equation
var dot_vector = Vector.subtract(v, v_dot_n_scaled_by_n);
console.log('w1', wall[0].x, wall[0].y, ", w2", wall[1].x, wall[1].y)
var bounceVector = dot_vector
console.log(wallVector.x, wallVector.y, wallVector.magnitude())
console.log(wallNormal.x, wallNormal.y, wallNormal.magnitude())
ctx.beginPath();
ctx.arc(wallVector.x, wallVector.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.arc(dot_vector.x, dot_vector.y, Global.isecRadius * 1.5, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = '#00FF00';
ctx.moveTo(wall[0].x, wall[0].y);
ctx.lineTo(wall[1].x, wall[1].y);
ctx.stroke();
console.log(bounceVector.x, bounceVector.y)
ctx.beginPath();
ctx.arc(bounceVector.x, bounceVector.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
ctx.moveTo(point.x, point.y);
ctx.lineTo(bounceVector.x, bounceVector.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(point.x, point.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'red';
}
}
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
ctx.moveTo(Global.Canvas.Center.x, Global.Canvas.Center.y);
ctx.lineTo(ray.x, ray.y);
ctx.stroke();
});
My calculations are based on equations found here: http://mathworld.wolfram.com/Reflection.html
But if you try the fiddle, you will notice that the reflection calculations barely work for the angled wall and currently do not work at all for the outside walls, so this is where I need some help.
The equation looks like this:
x_1'- x_0 = v - 2(v·ñ)ñ.
Where x_1´ - x_0 is the reflection vector from the intersection point of the wall to where ever it should be pointing. Probably the same length as v.
v is the vector from x_1 to the intersection point of the wall, so in my code it is named as v and goes from the center to the wall intersection.
ñ is the normal of the wall.
Rest of the equation should also be commented in my code and the variables named accordingly I hope.
I am a bit confused with the stuff subtracted from v in the equation. v dot ñ returns a scalar, which is then multiplied by 2. However ñ is a vector so 2(v dot ñ) must mean scaling vector ñ by the scalar value of 2(v dot ñ)?
Best guess I have been able to conclude from debugging this thing is that the wall normal is most likely note correctly calculated.

From my own Geom library I have.
// v1, v2 are Vec
// reflected vec for line hitting this
// argument line is the line to be reflected
// retVec is the returned resulting vector.
reflectAsVec : function(line, retVec = new Vec()){ // returns a vector...
v2.x = this.p2.x - this.p1.x;
v2.y = this.p2.y - this.p1.y;
v1.x = line.p2.x - line.p1.x;
v1.y = line.p2.y - line.p1.y;
var len = v1.dot(v2.norm()) * 2;
retVec.x = v2.x * len - v1.x;
retVec.y = v2.y * len - v1.y;
return retVec;
},
this is a line with two points p1,p2 defining the ends. Note the v2 is normalised for the last two lines calculating the reflected vec.
The function v2.norm() converts a Vec to a normalised vector as follows
// u is a number
norm : function(){ // normalises this to be a unit length.
u = Math.hypot(this.x,this.y);
this.x /= u;
this.y /= u;
return this; // returns this
},
And for v1.dot(vec)
dot : function(vec){ // get the dot product of this and {avec}
return this.x * vec.x + this.y * vec.y; // returns number
},
Once you have the reflected vector you can create the reflected line from the intercept point and the reflected vector.

Related

How to change strokeStyle used in an arc for each point?

I am trying to draw an object using numerous points and I am trying to assign a unique color to each point by assigning the color to strokeStyle. I am using HSL style of coloring.
It is only taking the first color or black.
Below is the code that I have tried.
const c = document.getElementById("myCanvas");
c.width=window.innerWidth;
c.height=window.innerHeight - 150;
let ctx = c.getContext("2d");
let cx = c.width/2, cy = c.height/2;
let n = 6, d = 71;
ctx.translate(cx,cy);
ctx.save();
ctx.beginPath();
for(let i = 0; i < 361; i++){
let k = i * d * Math.PI/180;
let r = 150 * Math.sin(n*k);
let x = r * Math.cos(k);
let y = r * Math.sin(k);
ctx.arc(x, y, 0.5, 0, 2 * Math.PI);
ctx.strokeStyle = "hsl("+Math.random() * 360 | 0+",100%,50%)"; // assign a random color to each point
}
ctx.stroke();
<canvas id="myCanvas"></canvas>
If you want to change the color for each point you need to begin a new path with every step of the loop. The arcs in your code are just small dots. If you want to see anything you need to draw lines between the previous point (last) and the new one.
In my code I've commented out the part where you draw the arc.
const c = document.getElementById("myCanvas");
c.width=window.innerWidth;
c.height=window.innerHeight;
let ctx = c.getContext("2d");
let cx = c.width/2, cy = c.height/2;
let n = 6, d = 71;
ctx.translate(cx,cy);
ctx.save();
let last = {x:0,y:0}
for(let i = 0; i < 361; i++){
let k = i * d * Math.PI/180;
let r = 150 * Math.sin(n*k);
let x = r * Math.cos(k);
let y = r * Math.sin(k);
ctx.beginPath();
ctx.moveTo(last.x,last.y);
ctx.lineTo(x,y)
ctx.strokeStyle = "hsl("+ Math.random() * 360 + ",100%,50%)";
ctx.stroke();
/*
ctx.beginPath();
ctx.arc(x, y, 0.5, 0, 2 * Math.PI);
ctx.stroke();*/
last={x,y}
}
<canvas id="myCanvas"></canvas>
The main reason is the ctx.stroke(); command. You are not actually drawing anything in the loop. Just constructing a very long path. At the point where you call the stroke() method it strokes the entire path with the current color. You need to find a way to break up the the paths into the segments you want and stroke each one independently. That is, call beginPath() when you want a new segment and stroke() when that segment is ready to be drawn.
This answer also provides some details.

How to use multiple click event on a canvas

I have drawn a circle with canvas using following code.
<div class="abc"><canvas id="myCanvas">HTML5 canvas tag.</canvas></div>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100,75,50,-0.1*Math.PI,1.7*Math.PI);
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke();
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
ctx2.beginPath();
ctx2.arc(100,75,50,1.7*Math.PI,-0.1*Math.PI);
ctx2.lineWidth = 15;
ctx2.strokeStyle = "#a9a9a9";
ctx2.stroke();
</script>
Here is the result on the web browser.
Now I need to call two different javascript function when the user clicks on red part and gray part of the circle.
On click of the red part it should call function1 and on click of gray part, it should call function2.
I've tried a lot but didn't succeed. I need some expert's help to implement it.
Letting the browser do (most of) the work
On click of the red part it should call function1 and on click of gray part, it should call function2.
You can use reusable path objects to store the different paths you want to test against and then use isPointInPath() with path and coordinates as arguments.
By checking each path for a hit you can assign a different call based on for example index.
And there is no need to do this:
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
This will simply reference the same context as a canvas can only have one - if requested more than once the same will be given (or null if different type).
How to use multiple click event on a canvas
You can share the click handler to do what you want as shown below -
For modern browsers you can use both Path2D objects to store path information you want to use later (I'll address older browsers in the second example).
Example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var p1 = new Path2D();
var p2 = new Path2D();
var paths = [p1, p2]; // store paths in array for check later
// store arc parts to respective path objects
p1.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
p2.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
// render the two path objects using a common context, but different style
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke(p1);
ctx.strokeStyle = "#a9a9a9";
ctx.stroke(p2);
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// iterate through path array to test each path for hits
for(var i = 0; i < paths.length; i++) {
if (ctx.isPointInStroke(paths[i], x, y)) { // check which path
console.log("Path " + (i+1) + " clicked");
break;
}
}
};
<canvas id="myCanvas"></canvas>
Browser compatibility
Older browsers however, won't support the isPointInStroke(), such as IE11.
For this scenario you can use simple math to figure out if the click is within the radius of the arcs:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 15;
ctx.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
ctx.strokeStyle = "#c32020";
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
ctx.strokeStyle = "#a9a9a9";
ctx.stroke();
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top,
diffX = 100 - x,
diffY = 75 - y,
len = diffX*diffX + diffY*diffY, // we don't need to square-root it:
r1 = 43*43, // it's faster to scale up radius (50-50% of linewidth)
r2 = 57*57; // radius + 50% of linewidth
// are we on the edge of the circle?
if (len >= r1 && len <= r2) {
// now find angle to see if we're in red or grey area
var angle = Math.atan2(diffY, diffX);
if (angle > 0.7 * Math.PI && angle < 0.9 * Math.PI) {
console.log("Grey part");
}
else {
console.log("Red part");
}
}
};
<canvas id="myCanvas"></canvas>
Note that a special case in the latter example, i.e. when the arc crosses the 0/360° point, you will want to split the checks [0, angle> and [angle, 360>.
You will need to add a click event to the canvas element and calculate if the red or gray is clicked.
Here's some code to get you started, but you'll still have to figure out what coordinates your circle area has.
var elem = document.getElementById('myCanvas'),
elemLeft = elem.offsetLeft,
elemTop = elem.offsetTop;
// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
var x = event.pageX,
y = event.pageY;
// use x and y to determine if the colored regions are clicked and execute code accordingly
}, false);
Code gotten from here: How do I add a simple onClick event handler to a canvas element?

Html canvas - rotate individual shapes on center axis

As you can see in the demo the L shape is getting cropped off the top of the screen and should be rotated 180 degrees and flush with the top left corner. I noticed two things that don't work as expected, the first is when I change ctx.translate(x, y) to ctx.moveTo(x, y) and increase the shape position to 100, 100 it moves more than 100px with translate, where as moveTo seems accurate. The second is that using a negative translate after ctx.stroke() has no affect on the shapes position.
var shape = {};
function draw(shape) {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
var x = shape.position.x + 0.5;
var y = shape.position.y + 0.5;
ctx.translate(x, y);
ctx.translate(shape.width * shape.scale/2, shape.height * shape.scale/2);
ctx.rotate(shape.orientation * Math.PI/180);
ctx.beginPath();
for (var i = 0; i < shape.points.length; i++) {
x = shape.points[i].x * shape.scale + shape.position.x + 0.5;
y = shape.points[i].y * shape.scale + shape.position.y + 0.5;
ctx.lineTo(x, y);
}
ctx.strokeStyle = '#fff';
ctx.stroke();
ctx.translate(-shape.width * shape.scale/2, -shape.height * shape.scale/2);
ctx.restore();
}
}
// L Shape
shape.points = [];
shape.points.push({ x:0, y:0 });
shape.points.push({ x:0, y:3 });
shape.points.push({ x:2, y:3 });
shape.points.push({ x:2, y:2 });
shape.points.push({ x:1, y:2 });
shape.points.push({ x:1, y:0 });
shape.points.push({ x:0, y:0 });
shape.position = {x: 0, y: 0};
shape.scale = 30;
shape.width = 3;
shape.height = 2;
shape.orientation = 180;
draw(shape);
#canvas {
background: #272B34; }
<canvas id="canvas" width="400" height="600"></canvas>
The easiest way to do 2D tranforms is via the setTransform function which takes 6 numbers, 2 vectors representing the direction and scale of the X and y axis, and one coordinate representing the new origin.
Unlike the other transform functions which are dependent of the current state setTransform is not effected by any transform done before it is called.
To set the transform for a matrix that has a square aspect (x and y scale are the same) and that the y axis is at 90 deg to the x ( no skewing) and a rotation is as follows
// x,y the position of the orign
function setMatrix(x,y,scale,rotate){
var xAx = Math.cos(rotate) * scale; // the x axis x
var xAy = Math.sin(rotate) * scale; // the x axis y
ctx.setTransform(xAx, xAy, -xAy, xAx, x, y);
}
//use
setMatrix(100,100,20,Math.PI / 4);
ctx.strokeRect(-2,-2,4,4); // draw a square centered at 100,100
// scaled 20 times
// and rotate clockwise 45 deg
Update
In response to the questions in the comments.
Why sin and cos?
Can you also explain why you used cos and sin for the axis?
I use Math.sin and Math.cos to calculate the X axis and thus the Y axis as well (because y is at 90 deg to x) because it is slightly quicker than adding the rotation as a separate transform.
When you use any of the transform functions apart from setTransform you are doing a matrix multiplication. The next snippet is the JS equivalent minimum calculations done when using ctx.rotate, ctx.scale, ctx.translate, or ctx.transform
// mA represent the 2D context current transform
mA = [1,0,0,1,0,0]; // default transform
// mB represents the transform to apply
mB = [0,1,-1,0,0,0]; // Rotate 90 degree clockwise
// m is the resulting matrix
m[0] = mA[0] * mB[0] + mA[2] * mB[1];
m[1] = mA[1] * mB[0] + mA[3] * mB[1];
m[2] = mA[0] * mB[2] + mA[2] * mB[3];
m[3] = mA[1] * mB[2] + mA[3] * mB[3];
m[4] = mA[0] * mB[0] + mA[2] * mB[1] + mA[4];
m[5] = mA[1] * mB[0] + mA[3] * mB[1] + mA[5];
As you can see there are 12 multiplications and 6 additions plus the need for memory to hold the intermediate values and if the call was to ctx.rotation the sin and cos of the angle would also be done. This is all done in native code in the JavaScript engine so is quicker than doing in JS, but side stepping the matrix multiplication by calculating the axis in JavaScript results in less work. Using setTransform simply replaces the current matrix and does not require a matrix multiplication to be performed.
The alternative to the answer's setMatrix function can be
function setMatrix(x,y,scale,rotate){
ctx.setTransform(scale,0,0,scale, x, y); // set current matrix
ctx.rotate(rotate); // multiply current matrix with rotation matrix
}
which does the same and does look cleaner, though is slower and when you want to do things like games where performance is very important often called functions should be as quick as possible.
To use the setMatrix function
So how would I use this for custom shapes like the L in my demo?
Replacing your draw function. BTW you should be getting the context outside any draw function.
// assumes ctx is the 2D context in scope for this function.
function draw(shape) {
var i = 0;
setMatrix(shape.position.x, shape.position.y, shape.scale, shape.orientation); // orientation is in radians
ctx.strokeStyle = '#fff';
ctx.beginPath();
ctx.moveTo(shape.points[i].x, shape.points[i++].y)
while (i < shape.points.length) {
ctx.lineTo(shape.points[i].x, shape.points[i++].y);
}
ctx.closePath(); // draw line from end to start
ctx.stroke();
}
In your code you have the line stored such that its origin (0,0) is at the top left. When defining shapes you should define it in terms of its local coordinates. This will define the point of rotation and scaling and represents the coordinate that will be at the transforms origin (position x,y).
Thus you should define your shape at its origin
function createShape(originX, originY, points){
var i;
const shape = [];
for(i = 0; i < points.length; i++){
shape.push({
x : points[i][0] - originX,
y : points[i][1] - originY,
});
}
return shape;
}
const shape = {};
shape.points = createShape(
1,1.5, // the local origin relative to the coords on next line
[[0,0],[0,3],[2,3],[2,2],[1,2],[1,0]] // shape coords
);

Issue on Drawing Multiple Circles on HTML5 Canvas

Can you please take a look at this demo and let me know how I can draw multiple circles in a canvas with different coordinate without repeating bunch of codes?
As you can see on Demo and following code
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
ctx.beginPath();
ctx.arc(150, 50, 5, 0, Math.PI * 2, true);
ctx.arc(20, 85, 5, 0, Math.PI * 2, true);
ctx.arc(160, 95, 5, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
I tried to have them under ctx but it is not correct so I tried to use for loop to create 50 points but I have issue on repeating and adding code like ctx.fill(); for all of them.
Can you please let me know how I can fix this?
Thanks
Constantly creating and closing new paths is not good advice.
You should batch together all fills / strokes of the same style, and execute them in a single draw call. The performance difference between these approaches becomes apparent very quickly with increasing polygon count.
The way to go about it is to move the pen and make the path construction call for each circle; stroke/fill in the end in one shot. However, there's a quirk involved here. When you have the point moved to the center and draw the circle, you'd still see a horizontal radius line, drawn from the center of the circle, to the circumference.
To avoid this artefact, instead of moving to the center, we move to the circumference. This skips the radius drawing. Essentially all these commands are for tracing a path and there's no way to describe a discontinuity without calling closePath; usually moveTo does it but HTML5 canvas API it doesn't. This is a simple workaround to counter that.
const pi2 = Math.PI * 2;
const radius = 5;
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i],
x = p.x,
y = p.y;
ctx.moveTo( x + radius, y ); // This was the line you were looking for
ctx.arc( x, y, radius, 0, pi2 );
}
// Finally, draw outside loop
ctx.stroke();
ctx.fill();
Also worth considering, is using transformations, and drawing everything relative to a local frame of reference.
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i];
ctx.save();
ctx.translate( p.x + radius, p.y );
ctx.moveTo( 0, 0 );
ctx.arc( 0, 0, radius, 0, pi2 );
ctx.restore();
}
ctx.stroke();
ctx.fill();
This is because you are not closing the path, either using fill() or closePath() will close the path so it does not try and connect all the items. fill() fills in the circles and closes the path so we can just use that. Also you need to use beginPath(), so that they are separate from each other. Here is your three circles:
var coords = [ [150,50], [20,85], [160,95] ];
for(var i = 0; i < coords.length; i++){
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
ctx.fill();
}
To not repeat a bunch of code and have unique coordinates store your X and Y position in an array and use a for loop to go through it.
Update:
A more efficient way to do which achieves the same effect this would be to only use a single path and use moveTo() instead of creating a new path when drawing each circle:
ctx.beginPath();
for(var i = 0; i < coords.length; i++){
ctx.moveTo(coords[i][0], coords[i][1]);
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
}
ctx.fill();
ctx.beginPath();
points.forEach(point => {
ctx.moveTo( point.x, point.y );
ctx.arc(point.x,point.y,1,0,Math.PI*2,false);
});
ctx.fill();
You can easily create several circles with a for loop. You really just need to draw an arc and fill it each time. Using your example, you could do something like this.
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
for (var i = 0; i < 3; i++) {
ctx.arc(50 * (i+1), 50 + 15 * i, 5, 0, Math.PI * 2, true);
ctx.fill();
}
Example Fiddle of lots of circles in different locations drawn in a loop.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext( '2d' );
var cx = canvas.width/2;
var cy = canvas.height/2;
ctx.fillStyle = "#00A308";
var total_circles = 50;
var radius = 100;
for(i = 0; i < total_circles; i++){
var angle = i * 2 * Math.PI/total_circles;
var x = cx + Math.cos(angle) * radius;
var y = cy + Math.sin(angle) * radius;
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}

JavaScript: How to get the center of a pie slice?

Hi I am trying to create a simple pie chart using the HTML canvas element.
I found a nice tutorial on the web and starting to learn how it is done.
Now I want to add some text and I need to know how to find the center of each slice in the pie where the text can be added.
Here is the loop code:
for ( i = 0; i < t.data.length; i++ )
{
label = t.data[i].label;
value = t.data[i].value;
color = t.data[i].color;
ctx.lineWidth = t.strokeLineWidth;
ctx.strokeStyle = "#FFFFFF";
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo( centerX, centerY );
ctx.arc( centerX, centerY, radius, lastEnd, lastEnd + ( Math.PI * 2 * ( value / total ) ), false );
ctx.lineTo( centerX, centerY );
ctx.fill();
ctx.stroke();
lastEnd += Math.PI * 2 * ( value / total );
}
Simply treat the slices as triangles, i.e. ignore the arc.
There are a bunch of different ways to define "the center". Have a look at the wikipedia article on how to do this. You'll probably want the intersection of the angle bisectors, which is called the incenter. This is the center of the inscribed circle, and it gives the most space for text.
The coordinates of the incenter is calculated as follows:
Let (xa,ya), (xb,yb), and (xc,yc) be the coordinates of the three corners of the triangle, and let a, b, and c be the lengths of the opposing sides. I.e., a = ||(xb,yb)-(xc,yc)||, etc. Finally, let P=a+b+c. Then the center is
((a xa + b xb + c xc)/P, (a ya + b yb + c yc)/P).
Cheers,
The math is more complicated than I can do quickly. You might try the rgraph library.
http://www.rgraph.net/docs/pie.html

Categories