Issue on Drawing Multiple Circles on HTML5 Canvas

24,325

Solution 1

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

Solution 2

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

Solution 3

  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();
Share:
24,325
Suffii
Author by

Suffii

Updated on November 07, 2020

Comments

  • Suffii
    Suffii over 3 years

    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

  • Suffii
    Suffii almost 10 years
    Hi Zack , thanks for reply but can also let me know how I can set border for each circle? and is there any way to use custom cordinates for each of the circles? thanks again
  • Suffii
    Suffii almost 10 years
    Thanks Spencer, can you also please let me know how I can set stroke for dots?
  • Spencer Wieczorek
    Spencer Wieczorek almost 10 years
    Do you mean like using ctx.stroke() other than ctx.fill()?
  • Suffii
    Suffii almost 10 years
    Not sure but your code not working now! did you have a chance to test it?
  • Spencer Wieczorek
    Spencer Wieczorek almost 10 years
    I did, here is the fiddle: jsfiddle.net/56jhq/2 . I didn't include things such as getting the ctx in the example code.
  • Tom Burris
    Tom Burris over 7 years
    Adria's Answer below is better, more Paths = wasted time.
  • Gajen
    Gajen almost 7 years
    @SpencerWieczorek, can we also add click event in each shape created?
  • Spencer Wieczorek
    Spencer Wieczorek almost 7 years
    @Gajen No but you can add it to the canvas it self and check to see if the mouse position intersects a circle. For example here is a modification of the fiddle that uses Circle Objects and the distance formula for checking intersections. (Note it shows in the console log)
  • Micha Schwab
    Micha Schwab almost 6 years
    This is great advice. Demo: jsfiddle.net/michaschwab/Lmuyqb27/3
  • R Balasubramanian
    R Balasubramanian over 5 years
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.
  • João Menighin
    João Menighin over 5 years
    This has better performance than the accepted answer. It calls fil() only once
  • thedarklord47
    thedarklord47 over 5 years
    This is a much better answer as it allows drawing in batches. I was confused because there seems to be an implicit moveTo performed under the hood in rect (or some other mechanism that makes an explicit call unnecessary), where as without this explicit moveTo, all arc calls are joined together when drawn.
  • Spencer Wieczorek
    Spencer Wieczorek over 5 years
    @thedarklord47 I went ahead and added a more performant version to the accepted answer. While this is indeed a better approach I'd like to note that my answer was mainly based on the issue the OP had and what they wanted to do: "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?".
  • gap
    gap almost 5 years
    @Adria any sense of which is more performant, approach #1 or #2 ?