-1

I'm trying to generate a polygon with circles inside (JS canvas). Here's a sample expected output:enter image description here

It's basically a 4-sided polygon (square) with circles next to the vertices. Here is what I tried:

However, I don't get the expected outcome. Note: I want this to work for any sized polygon and not just a square. Also, stopping the draw() function to execute gives me a proper square. I believe there's a problem in the draw() function. Any help is appreciated :)

function draw(x, y, ctx){
    ctx.arc(x, y, 4, 0, Math.PI * 2);
    ctx.fillStyle = "#283149";
    ctx.fill(); // create circle
}
function createPolygon(n){
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext('2d');
    ctx.reset();
    var size = 60, Xcenter = 80, Ycenter = 80;
    ctx.beginPath();
    ctx.moveTo (Xcenter +  size * Math.cos(0), Ycenter +  size *  Math.sin(0));          
    
    for (var i = 1; i <= n; i++) {
        ctx.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
        draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter +  Math.sin(i * 2 * Math.PI / n), ctx);
    }
    ctx.fillStyle = "#00818A"
    ctx.fill();
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>
The Myth
  • 1,090
  • 1
  • 4
  • 16

2 Answers2

3

Focus on drawing the polygon first. Then you can add the circles relative to the points of the polygon.

const main = () => {
  const ctx = document.getElementById('poly').getContext('2d');
  Object.assign(ctx.canvas, { width: 350, height: 150 });
  Object.assign(ctx, { strokeStyle: 'red', lineWidth: 1 });
  drawPolygon(ctx, 60, 75, 6, 50, {
    nodeSize: 5,
    nodeInset: 10
  });
  drawPolygon(ctx, 180, 75, 4, 50, {
    nodeInset: 25,
    nodeSize: 10,
    rotation: Math.PI / 4
  });
  drawPolygon(ctx, 290, 75, 3, 50, {
    nodeSize: 4,
    rotation: Math.PI / 3
  });
};

const defaultPolygonOptions = {
  nodeInset: 0,
  nodeSize: 0,
  rotation: 0,
};

/**
 * @param {CanvasRenderingContext2D} ctx - Canvas 2D context
 * @param {Number} x - origin x-position
 * @param {Number} y - origin y-position
 * @param {Number} n - number of points
 * @param {Number} radius - radius of polygon
 * @param {Object} [options] - configuration options
 * @param {Number} [options.nodeInset] - insets for nodes
 * @param {Number} [options.nodeSize] - size of node
 * @param {Number} [options.rotation] - polygon rotation
 */
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
  const opts = { ...defaultPolygonOptions, ...options };
  ctx.beginPath();
  ctx.moveTo(
    x + radius * Math.cos(opts.rotation),
    y + radius * Math.sin(opts.rotation)
  );          
  for (let i = 1; i <= n; i += 1) {
    const angle = (i * (2 * Math.PI / n)) + opts.rotation;
    ctx.lineTo(
      x + radius * Math.cos(angle),
      y + radius * Math.sin(angle)
    );
  }
  ctx.stroke();

  if (!opts.nodeSize) return;
  const dist = radius - opts.nodeInset;
  for (let i = 1; i <= n; i += 1) {
    const angle = (i * (2 * Math.PI / n)) + opts.rotation;
    ctx.beginPath();         
    ctx.arc(
      x + dist * Math.cos(angle),
      y + dist * Math.sin(angle),
      opts.nodeSize,
      0,
      2 * Math.PI
    );
    ctx.stroke();
  }
};

main();
canvas { background: #FF0; }
<canvas id="poly"></canvas>

Inspired by: How to draw polygons on an HTML5 canvas?

Animation

Here is a basic animation that is FPS throttled.

const ctx = document.getElementById('poly').getContext('2d');
let state = {
  magnitude: 1,
  origin: { x: 0, y: 0 },
  points: 4,
  radius: 60,
  rotation: Math.PI / 4,
  nodeInset: 16,
  nodeSize: 8,
  nodeRotation: Math.PI / 4,
};

const main = () => {
  init();
  requestAnimationFrame(update);
};

const init = () => {
  Object.assign(ctx.canvas, { width: 160, height: 160 });
  Object.assign(ctx, { strokeStyle: 'green', lineWidth: 1 });
  Object.assign(state, {
    origin: {
      x: ctx.canvas.width / 2,
      y: ctx.canvas.height / 2
    }
  });
};

// https://stackoverflow.com/a/65829199/1762224
const FPS = 30;
let lastTimestamp = 0;

const update = (timestamp) => {
  let inset = state.nodeInset + state.magnitude;
  if (inset > state.radius) {
    inset = state.radius;
    state.magnitude *= -1;
  } else if (inset < state.nodeSize) {
    inset = state.nodeSize;
    state.magnitude *= -1;
  }
  state.nodeInset = inset;
  state.nodeRotation += (Math.PI / 36);
  
  requestAnimationFrame(update);
  if (timestamp - lastTimestamp < 1000 / FPS) return;
  
  redraw();
  lastTimestamp = timestamp;
};

const redraw = () => {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  drawPolygon(ctx, state.origin.x, state.origin.y, state.points, state.radius, {
    nodeInset: state.nodeInset,
    nodeSize: state.nodeSize,
    nodeRotation: state.nodeRotation,
    rotation: state.rotation
  });
}

const defaultPolygonOptions = {
  nodeInset: 0,
  nodeSize: 0,
  rotation: 0,
};

/**
 * @param {CanvasRenderingContext2D} ctx - Canvas 2D context
 * @param {Number} x - origin x-position
 * @param {Number} y - origin y-position
 * @param {Number} n - number of points
 * @param {Number} radius - radius of polygon
 * @param {Object} [options] - configuration options
 * @param {Number} [options.nodeInset] - insets for nodes
 * @param {Number} [options.nodeSize] - size of node
 * @param {Number} [options.nodeRotation] - rotation of nodes
 * @param {Number} [options.rotation] - polygon rotation
 */
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
  const opts = { ...defaultPolygonOptions, ...options };
  ctx.beginPath();
  ctx.moveTo(
    x + radius * Math.cos(opts.rotation),
    y + radius * Math.sin(opts.rotation)
  );          
  for (let i = 1; i <= n; i += 1) {
    const angle = (i * (2 * Math.PI / n)) + opts.rotation;
    ctx.lineTo(
      x + radius * Math.cos(angle),
      y + radius * Math.sin(angle)
    );
  }
  ctx.stroke();

  if (!opts.nodeSize) return;
  const dist = radius - opts.nodeInset;
  for (let i = 1; i <= n; i += 1) {
    const angle = (i * (2 * Math.PI / n)) + opts.nodeRotation;
    ctx.beginPath();         
    ctx.arc(
      x + dist * Math.cos(angle),
      y + dist * Math.sin(angle),
      opts.nodeSize,
      0,
      2 * Math.PI
    );
    ctx.stroke();
  }
};

main();
canvas { background: #222; }
<canvas id="poly"></canvas>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • Awesome approach :) – The Myth Dec 06 '22 at 06:06
  • also is there a way I could make the circles moving towards each other at a constant speed. Like a circular orbit. I'm trying to create an animation for a "physics problem" – The Myth Dec 06 '22 at 06:12
  • @TheMyth added a rudimentary animation at the end. – Mr. Polywhirl Dec 06 '22 at 13:57
  • Great, but I don't want it to escape the square and also how do I identify that all 4 circles are in the center? – The Myth Dec 06 '22 at 14:46
  • @TheMyth this should be a separate question. – Mr. Polywhirl Dec 06 '22 at 15:48
  • Yes, I did create one [here](https://stackoverflow.com/questions/74698303/inward-circular-orbit-canvas) – The Myth Dec 06 '22 at 20:39
  • your code has a flaw. When, I keep on updating the values of the magnitude, the rotation increases. Like, if it is `10` first then `1`, the rotation will increase and not decrease. An example can be tested on this website: https://themyth1710.github.io/Particle-Theory/ – The Myth Dec 11 '22 at 08:41
-1

You have to close paths, so you have to use two loops

function draw(x, y, ctx) {
  ctx.beginPath();
  ctx.arc(x, y, 4, 0, Math.PI * 2);
  ctx.fillStyle = "#283149";
  ctx.fill(); // create circle
  ctx.closePath()
}

function createPolygon(n) {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext('2d');
  ctx.reset();
  var size = 60,
    Xcenter = 80,
    Ycenter = 80;

  ctx.beginPath();
  ctx.moveTo(Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));

  for (let i = 1; i <= n; i++) {
    ctx.lineTo(Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
  }




  ctx.fillStyle = "#00818A"
  ctx.fill();
  ctx.closePath()

  for (let i = 1; i <= n; i++) {
    draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter + Math.sin(i * 2 * Math.PI / n), ctx);
  }
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>
Konrad
  • 21,590
  • 4
  • 28
  • 64