Javascript canvas arc is not drawing according to angle - javascript

Hi I am having a problem with drawing a graph. The situation is there are number of days remaining and I need to draw and arc
var eAngle = tax * 0.0172;
var c = document.getElementById("tax");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100,100,70,1.5 , Math.PI * eAngle, true);
ctx.stroke();
Also how can I write some text in the middle of that arc circle.

I have adjusted the code a little bit. First of, we need to know the angle every day in your year gets. I'm going to say that would be a full circle (Math.PI * 2) divided by 365 (or 366 when its one-of-those-years). Then it is a matter of correctly understanding the arc function:
arc(x, y, radius, angle (0 = x-axis to the right), end angle (starting x-axis where 1 radian is a full circle), counterclockwise (true is counterclockwise drawing from start until the end angle, and false or ommitting is the regular clockwise angle);
In the end, this will work:
context.arc(100, 100, 50, -Math.PI / 2, -Math.PI / 2 + day * (Math.PI * 2 / 365), false);
Where, in order, the arguments are: x-position, y-position, radius, -90 degrees (to start from the y-axis instead of the x), whatever this outputs when you input a day plus the offset of the previous argument.
To draw text on top of this., you can simply use the canvas fillText function.
var dayArc = Math.PI * 2 / 365;
var dayN = 0;
var offset = -Math.PI / 2;
var canvas = document.getElementById("tax")
var context = canvas.getContext('2d');
setInterval(function(){
dayN = dayN > 364 ? 0 : dayN+1;
canvas.width = 500;
context.beginPath();
context.arc(100, 100, 50, offset, offset + dayN * dayArc, false);
context.stroke();
context.textAlign = 'center';
context.font = "24px 'Helvetica Neue', sans-serif";
context.fillText((365-dayN) + '/365', 100, 110);
}, 1000 / 60);
<canvas id="tax" widht="500" height="500"></canvas>

Okay. So I would split up the calculations, to make it clearer what's going on. We know that a full circle is Math.PI * 2 radians. So we keep that separate.
Since we only want a fraction of a circle, representing the number of days out of a year, we keep a separate variable, say, fractionOfCircle, and then multiply them for our final result.
var fractionOfCircle = days / 365;
var canvas = document.getElementById("tax");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(100, 100, 70, 1.5, Math.PI * 2 * fractionOfCircle, true);
ctx.stroke();
To put a text within the circle, my first approach would be to overlay an HTML element, like a div, and position the text accordingly.

Related

Why are multiple rotate() needed to place every number on the right spot? - Canvas Clock Numbers

Following this tutorial which shows how to make an analog clock using HTML canvas, I've had a hard time understanding what is going on when placing numbers on the clock face.
The code is here, and the following is the part that I'd like to ask.
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
for(num = 1; num < 13; num++){
ang = num * Math.PI / 6;
ctx.rotate(ang);
ctx.translate(0, -radius * 0.85);
ctx.rotate(-ang);
ctx.fillText(num.toString(), 0, 0);
ctx.rotate(ang);
ctx.translate(0, radius * 0.85);
ctx.rotate(-ang);
}
}
In a for loop, the first ctx.rotate(ang) sets the number on the place it's supposed to be.
The next rotate ctx.rotate(-ang) puts the number back to upright because it's tilted. (although I don't know why it works like this not putting the number back to the first position.)
Then, after ctx.fillText(…) shows the number up, it seems to do the same again.
Why are these two rotate() needed? Do they work differently from the ones in the upper? If do, how?
What this code tries to do is to go back to its previous position, the center of the canvas.
Think of the context as a sheet of paper that you can rotate and move (translate), with a fixed pen over it.
First they do rotate that sheet of paper so that tracing a vertical line will go in the desired direction.
Then they move the sheet of paper vertically, so that the pen is at the correct position. However here, the sheet of paper is still rotated, so if they were to draw the text horizontally from here, the drawing would be oblique.
So they do rotate again in the other way for the text to be at correct angle.
They draw the text.
Now they want to go back to point 1 to be able to draw the next tick. For this they do the same route but in the other way: rotate back the sheet of paper to the desired angle so that they can move vertically to the center.
Move vertically to the center
Finally rotate back so that the sheet of paper is in its original orientation for the next tick.
However you should not do this. rotate() may end up having rounding issues, so doing rotate(angle); rotate(-angle) can not come back to the initial orientation, but to some slightly rotated state, which can be catastrophic for your application since now when you'll try to draw pixel perfect lines, you won't be able and you will kill the whole performances of your app.
Instead use the absolute setTransform method to go back to the original position:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
radius = radius * 0.90
drawNumbers(ctx, radius);
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
for(num = 1; num < 13; num++){
ang = num * Math.PI / 6;
// go (back) to center
ctx.setTransform(1, 0, 0, 1, radius, radius);
ctx.rotate(ang);
ctx.translate(0, -radius * 0.85);
ctx.rotate(-ang);
ctx.fillText(num.toString(), 0, 0);
}
// reset to identity matrix;
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
canvas {
background-color: white;
}
<canvas id="canvas" width="400" height="400">
</canvas>
Here is another implementation without using rotate.
Instead I calculate the x, y with a bit of trigonometry.
The starting angle is var ang = Math.PI;
Then in the loop we decrease it ang -= Math.PI / 6;
Calculating the position is easy once you know the formula:
let x = radius * Math.sin(ang)
let y = radius * Math.cos(ang)
Below is a fully functional example
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.font = "16px arial";
ctx.textAlign = "center";
var radius = 60
var ang = Math.PI;
for (let num = 1; num < 13; num++) {
ang -= Math.PI / 6;
let x = radius * Math.sin(ang)
let y = radius * Math.cos(ang)
ctx.fillText(num.toString(), x, y);
ctx.beginPath()
ctx.arc(x, y - 6, 12, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath()
ctx.arc(x, y - 6, 45, -ang-2,-ang);
ctx.stroke();
}
<canvas id="canvas" width="160" height="160"></canvas>
I personally never been a fan of using rotate, for a small static canvas image might be fine, but as we move to more complex animations with multiple object, when I have to debug with multiple rotation it quickly becomes painful and quite hard to follow.

Label connected to circle, curved side on leaflet map

I'm using CSS to try and create a label (which is a popup that always remains on the map) attached to a circle. The following link will lead to the image of what I'm trying to do: Image. In order to achieve this I've been using the following code:
$(popup._container.firstChild).css({
background: "-webkit-radial-gradient(-29px" + percentZoom + ", circle closest-corner, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 58px, white 59px)"
});
Before, I was calculating the percentZoom depending on the radius of the circle and the zoom where the map is now.
var percent = (50 * presentCircleRadius) / 300000 //when the radius is 300000 the percentage should be 50%
var percentZoom = (percent * zoom) / 6; // then calculate it the exact zoom that should be used depending on the zoom. Being 6 the default one.
This didn't work or it had many issues when I zoomed in on the map (considering that the circle doesn't really change but the curvature seems to becoming flatter).
I tried using canvas as well to get the result that I wanted it, but I had issues. I was using two arches to build the top part and the bottom part, then thought about using two rectangles to create the two parts to the right of the circle. The problem with this it's that the circle is transparent and it's meant to start on the edge of it, if I used this solution the rectangle would appear in the middle of the circle.
var canvas = document.getElementById('myCanvas1');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 1.1 * Math.PI;
var endAngle = 1.9 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, 1.6 * Math.PI, 0 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'black';
context.stroke();
context.beginPath();
context.arc(x, y, radius, 0 * Math.PI, 0.4 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.lineWidth = "10";
context.strokeStyle = "blue";
context.rect(x, y - radius, 150, radius);
context.stroke();
<canvas id="myCanvas1" width="578" height="250"></canvas>
So I thought of using lines instead of rectangles to create the right part of the label: fiddle, the problem with this solution is, as mention before, as you zoom the curvature will change and I found no way to calculate exactly where the lines on the top and on the bottom should start.
Is there a way to do what I want to do: Make it so that the label follows the curvature of the circle as you zoom in and out and if so how can I make it so considering that there might be more than one circle per zoom with different radius?

plotting random points in circle sector

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?
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 !
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();

How to clear part of canvas?

Here is an example!
I am trying to reset the green arc inside drawValueArc() so that each time you click the change button, the green arc is removed and redrawn. How can I remove it without removing the entire canvas? Also, as an aside, I have noticed that Math.random() * 405 * Math.PI / 180 doesn't actually always result in an arc that fits inside the gray arc, sometimes it is larger than the gray arc, why is this?
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cx = 150;
var cy = 150;
var startRadians = 135 * Math.PI / 180;
var endRadians = 405 * Math.PI / 180;
//main arc
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, endRadians, false);
ctx.strokeStyle="rgb(220,220,220)";
ctx.lineWidth = 38;
ctx.stroke();
$('#setRandomValue').click(function(){
drawValueArc(Math.random() * 405 * Math.PI / 180);
});
function drawValueArc(val){
//ctx.clearRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, val, false);
ctx.strokeStyle = "green";
ctx.lineWidth = 38;
ctx.stroke();
}
Drawing past boundary
The problem you are facing is in first instance the fact you are drawing before and after a 0-degree on the circle. This can be complicated to handle as you need to split in two draws: one for the part up to 0 (360) and one 0 to the remaining part.
There is a simple trick you can use to make this easier to deal with and that is to deal with all angles from 0 and use an offset when you draw.
Demo using redraw base (I moved it to jsfiddle as jsbin did not work for me):
http://jsfiddle.net/3dGLR/
Demo using off-screen canvas
http://jsfiddle.net/AbdiasSoftware/Dg9Jj/
First, some optimizations and settings for the offset:
var startRadians = 0; //just deal with angles
var endRadians = 300;
var deg2rad = Math.PI / 180; //pre-calculate this to save some cpu cycles
var offset = 122; //adjust this to modify rotation
We will now let the main function, drawArc() do all calculations for us so we can focus on the numbers - here we also offset the values:
function drawArc(color, start, end) {
ctx.beginPath();
ctx.arc(cx, cy, 58,
(startRadians + offset) * deg2rad,
(end + offset) * deg2rad, false);
ctx.strokeStyle = color;
ctx.lineWidth = 38;
ctx.stroke();
}
Clearing the previous arc
There are several techniques to clear the previous drawn arc:
You can draw the base arc to an off-screen canvas and use drawImage() to erase the old.
You can do as in the following example, just re-draw it with the base color
As with 2. but subtracting the green arc and draw the base color from the end of the green arc to the end of the base arc.
clearing the whole canvas with fillRect or clearRect.
1 and 3 are the fastest, while 4 is the slowest.
With out re-factored function (drawArc) it's as easy as this:
function drawValueArc(val) {
drawArc("rgb(220,220,220)", startRadians, endRadians);
drawArc("green", startRadians, val);
}
As everything now is 0-based concerning start we really don't need to give any other argument than 0 to the drawArc instead of startRadians. Use the new offset to offset the start position and adjust the endRadians to where you want it to stop.
As you can see in the demo, using this technique keeps everything in check without the need to draw in split.
Tip: if you notice green artifacts on the edges: this is due to anti-alias. Simply reduce the line width for the green color by 2 pixels (see demo 2, off-screen canvas).

clearing circular regions from HTML5 Canvas

It appears the only way to clear a region from a canvas is to use the clearRect() command - I need to clear a circle (I am masking out areas from a filled canvas, point lights in this specific case) and despite all attempts it does not seem possible.
I tried drawing a circle with an alpha value of 0 but simply nothing would appear unless the alpha was higher (which is counter to the point :P) - I assume because a contex.fill() draws it as an add rather than a replace.
Any suggestions on how I might be able to (quickly) clear circles for mask purposes?
Use .arc to create a circular stroke and then use .clip() to make that the current clipping region.
Then you can use .clearRect() to erase the whole canvas, but only the clipped area will change.
If you're making a game or something where squeezing every bit of performance matters, have a look at how I made this answer: Canvas - Fill a rectangle in all areas that are fully transparent
Specifically, the edit of the answer that leads to this: http://jsfiddle.net/a2Age/2/
The huge plusses here:
No use of paths (slow)
No use of clips (slow)
No need for save/restore (since there's no way to reset a clipping region without clearing all state(1), it means you must use save/restore also)
(1) I actually complained about this and resetClip() has been put in the offical spec because of it, but it will be a while before browsers implement it.
Code
var ctx = document.getElementById('canvas1').getContext('2d'),
ambientLight = 0.1,
intensity = 1,
radius = 100,
amb = 'rgba(0,0,0,' + (1 - ambientLight) + ')';
addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0, 0, 500, 500);
function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
xOff = xOff || 0;
yOff = yOff || 0;
var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
g.addColorStop(0, amb);
ctx.fillStyle = g;
ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
}
canvas {
border: 1px solid black;
background-image: url('http://placekitten.com/500/500');
}
<canvas id="canvas1" width="500" height="500"></canvas>
Given the requirements, these answers are fine. But lets say you're like me and you have additional requirements:
You want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing.
You want to see the background underneath the shape instead of clearing the background.
For the first requirement, the solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape.
Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
You have a few options.
Firstly, here's a function we'll use to fill a circle.
var fillCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
};
clip()
var clearCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.clip();
context.clearRect(x - radius - 1, y - radius - 1,
radius * 2 + 2, radius * 2 + 2);
};
See this on jsFiddle.
globalCompositeOperation
var clearCircle = function(x, y, radius)
{
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
See this on jsFiddle.
Both gave the desired result on screen, however the performance wasn't sufficient in my case as I was drawing and clearing a lot of circles each frame for an effect. In the end I found a different way to get a similar effect to what I wanted by just drawing thicker lines on an arc, but the above may still be useful to someone having different performance requirements.
Use canvas.getContext("2d").arc(...) to draw a circle over the area with the background colour?
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.arc(x, y, r, 0, 2*Math.PI, false);
context.fillStyle = "#FFFFFF";
context.fill();
Where x = left position, y = right position, r = radius, and ctx = your canvas:
function clearCircle( x , y , r ){
for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
var angle = ( i / Math.round( Math.PI * r )) * 360;
ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
}
}

Categories