Issue on Drawing Multiple Circles on HTML5 Canvas - javascript

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();
}

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.

Draw points on a circle's edge

I want draw something like this in javascript:
Example:
The bigger circle: r = 5
What do I want to do? to position circles by forming a circle
My questions are (I would like to know):
How much points can I draw on a circle if I know the ray,
the distance between each circle and the radius of each circle?
How can I find the position of each circle (to draw automatically)?
Like the question 1 but the circles do not have the same radius.
Thank you!
Displace points around a circle edge equidistantly
Using HTML Canvas and a bit of trigonometry
Create a reusable circles() function and pass the desired arguments for Number of circles, Size, Radius, Color:
const ctx = document.getElementById("canvas").getContext("2d");
const cvsSize = 400;
ctx.canvas.width = cvsSize;
ctx.canvas.height = cvsSize;
function circles(tot, rad, dist, color) {
const arc = Math.PI * 2 / tot; // Arc in Radians
let ang = 0; // Start at angle 0 (East)
for (let i = 0; i < tot; i++) {
const x = dist * Math.cos(ang) + (cvsSize / 2);
const y = dist * Math.sin(ang) + (cvsSize / 2);
ctx.beginPath();
ctx.arc(x, y, rad, 0, Math.PI * 2, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
ang += arc;
}
}
// Circles, Radius, Distance, Color
circles(3, 5, 10, "#f0b");
circles(10, 8, 50, "#0bf");
circles(17, 10, 90, "#bf0");
circles(21, 15, 140, "#b0f");
<canvas id="canvas"></canvas>

Canvas figures moves to the right side

Here is the code:
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>
This code works fine. BUT each of my polgon appears at wrong place. For example I call
polygon(context, 120,120,50,12);
and
polygon(context, 120,220,50,12);
and the second polygon appears at x=220, y=220
I mean, they moves in the right side, but they should appear one under another with the same x coordinates.
After drawing the shape you need to translate it back to the original position so the next shape is drawn from the same relative location as the first.
ctx.translate(-x, -y);
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
polygon(context, 120,220,50,12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
ctx.translate(-x, -y);
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>
Reset the translation matrix to the identity matrix before drawing each shape:
context.setTransform(1, 0, 0, 1, 0, 0);
I think it happens because of:
ctx.translate(x, y);
If you look closely at HTML canvas translate() Method Definition and Usage you will see that translate() method remaps the (0,0) position of the canvas instead of setting starting point for your drawing. So if you do function call like this:
ctx.translate(120, 120);
ctx.translate(120, 220);
You actually moving registration point of canvas twice. First time it will be moved by (120,120) and later it will be moved by (120,220), so your first polygon will be drawed correctly but the second will be drawed on position (240,340), because coordinates of both starting points will eventually be summed.
You are using the same 2d context over and over for your polygons. The context will save the state of your translations, so consecutive translations add up. You could "revert" the effect of translations by translating with same-negative-values at the end of each polygon call.
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
polygon(context, 120, 220, 50, 12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
ctx.translate(-1 * x, -1 * y);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>

How to divide the Circles into 24/7 equal parts using canvas?

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Growing Circles</title>
</head>
<body>
<canvas id="c" width="960" height="720"></canvas>
</body>
</html>
JavaScript
var canvas = document.getElementById( "c" ),
ctx = canvas.getContext( "2d" );
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc( 500, 350, 60, 0, 2 * Math.PI, false );
ctx.fillStyle = "#4DA54D";
ctx.fill();
ctx.strokeStyle = "DarkRed";
ctx.stroke();
ctx.beginPath();
ctx.arc( 500, 350, 120, 0, 2 * Math.PI, false );
ctx.strokeStyle = "OliveDrab";
ctx.stroke();
ctx.beginPath();
ctx.arc( 500, 350, 180, 0, 2 * Math.PI, false );
ctx.strokeStyle = "#530053";
ctx.stroke();
ctx.beginPath();
ctx.arc( 500, 350, 240, 0, 2 * Math.PI, false );
ctx.strokeStyle = "#208181";
ctx.stroke();
ctx.beginPath();
ctx.arc( 500, 350, 300, 0, 2 * Math.PI, false );
ctx.strokeStyle = "#CC7A00";
ctx.stroke();
ctx.beginPath();
ctx.arc( 500, 350, 360, 0, 2 * Math.PI, false );
ctx.strokeStyle = "#205450";
ctx.stroke();
I want to divide This demo into 24 hours timeline by using lines. I tried few but not up-to mark.It is going very big in code!
Is there any other way to solve this in small piece of code like using slice as in Tried demo ?
Tried Demo
My Requirement is to do as This demo.
I want to have This demo to be sliced 24/7 where 24 represent hours and 7 for days.
Further Requirement :: Even i must be able to access the particular arc which i want depending on the day and hour !
Finally i want to have a look like this Image I will pass the arguments(Day, hour,Color) and then that particular segment should change the color, as i shown in the Image.
This is how i tried to print the numbers ..
function drawnumbers(){
for(var i=1; i< hours+1;i++){
context.font="18px sans-serif";
context.fillText(+i,20+i*30,20+i*30);
}
}but i want them on the outer circle as in png Image
Although Harsh has already provided a very useful answer, it relates to the wireframe drawing which you depicted.
I thought it would also be useful to show you how you could achieve the drawing of the individual segments.
I think you have asked for a little too much in your PNG as we would practically be creating your project for you, but with my answer and Harsh's answer I believe you can get what you want:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
// centre or center for US :) the drawing
var x = canvas.width / 2;
var y = canvas.height / 2;
// number of days
var days = 7;
// number of hours
var hours = 24;
// one segment represents an hour so divide degrees by hours
var segmentWidth = 360 / hours;
// begin at 0 and end at one segment width
var startAngle = 0;
var endAngle = segmentWidth;
// how thick you want a segment
var segmentDepth = 30;
function init() {
for (var i = 1; i <= days; i++) {
drawSegments(i * segmentDepth);
}
}
function drawSegments(radius) {
for (var i = 0; i < hours; i++) {
context.beginPath();
context.arc(x, y, radius,
(startAngle * Math.PI / 180), (endAngle * Math.PI / 180), false);
context.lineWidth = segmentDepth;
context.strokeStyle = '#' +
(Math.random() * 0xFFFFFF << 0).toString(16);
context.stroke();
// increase per segment
startAngle += segmentWidth;
endAngle += segmentWidth;
}
}
// start drawing our chart
init();
See my http://jsfiddle.net/U2tPJ/6/ demo.
Man, you are doing PROGRAMMING, so why are you afraid of using the tools? Use loops and variables.
Dial
var canvas = document.getElementById( "c" ),
ctx = canvas.getContext( "2d" ),
strokes = ["DarkRed", "OliveDrab", "#530053", "#208181", "#CC7A00", "#205450"];
ctx.lineWidth = 3;
for(var i=0; i<7; i++) {
ctx.beginPath();
ctx.arc( 500, 350, 420-60*i, 0, 2 * Math.PI, false );
if(i==6) {
ctx.fillStyle = "#4DA54D";
ctx.fill();
}
ctx.strokeStyle = strokes[i];
ctx.stroke();
}
// Now do the same for the 24 spokes (Use mathematical formulae)
// this is just from the top of my head, may need some adjustment
angle = 360/25; // you have 24 spokes, so 25 sectors (first and last one overlap or are the same)
for(var j=0; j<24; j++) {
ctx.beginPath();
ctx.moveTo(500, 350);
ctx.lineTo(500+420*Math.sin(angle*j), 350-420*Math.cos(angle*j));
ctx.strokeStyle = "blue";
ctx.stroke();
}
Labels
For printing labels on the dial:
angle = 360/23;
for(var i=0; i<23; i++) {
x = <centerX> + <radius> * Math.sin(angle*i*Math.PI/180) // convert from radian to angle
y = <centerY> - <radius> * Math.cos(angle*i*Math.PI/180)
context.fillText(i+1, x, y);
}

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