DEV Community

Cover image for Circle-Packing with Javascript and SVG — the Kusama Way
Mads Stoumann
Mads Stoumann

Posted on

Circle-Packing with Javascript and SVG — the Kusama Way

I love the works of Japanese artist Yayoi Kusama, and have — for a long time — wanted to make an interactive poster of her famous dots.

Although my father is a retired Math-teacher, I simply cannot grasp the super-complicated "circle-packing" algorithms, so I ended up doing something more simple:

function kusamaDots(
  numDots,
  minRadius,
  maxRadius,
  width,
  height
) {
  const dots = [];

  function createDot() {
    const x = Math.floor(Math.random() * width);
    const y = Math.floor(Math.random() * height);
    const radius =
      Math.random() * (maxRadius - minRadius) + minRadius;
    const dot = { radius, x, y };
    if (!dots.some((c) => intersects(dot, c))) {
      return dot;
    }
    return null;
  }

  while (dots.length < numDots) {
    const dot = createDot();
    if (dot !== null) dots.push(dot);
  }
  return dots;
}
Enter fullscreen mode Exit fullscreen mode

Parameters are:

  1. numDots. Total number of dots.
  2. minRadius. Minimum radius of a dot.
  3. maxRadius. maximum radius of a dot.
  4. width of the "canvas" (SVG viewBox).
  5. height of the same.

The createDot method creates a random x and y-position within the boundaries of the viewBox. It sets a random radius between the values of minRadius and maxRadius.

It creates a dot-object, and then it checks whether the dot intersects with any other dots (that have been created already). For this, we need a small helper-method:

function intersects(first, second) {
  const dx = first.x - second.x;
  const dy = first.y - second.y;
  const distance = Math.sqrt(dx * dx + dy * dy);
  const sumOfRadii = first.radius + second.radius;
  return distance <= sumOfRadii;
}
Enter fullscreen mode Exit fullscreen mode

If the dot does not intersect with any other dots, it's returned.

After that, a while-loop runs until numDots have been reached.

To output the dots, we simply create <circle>s in SVG:

const dots = kusamaDots(
  e.target.valueAsNumber,
  10,
  150,
  1000,
  1000
);
svg.innerHTML = dots
  .map(
    (dot) =>
      `<circle r="${dot.radius}" cx="${dot.x}" cy="${dot.y}"></circle>`
  )
  .join("")
Enter fullscreen mode Exit fullscreen mode

CSS

The CSS is fairly simple. The frame and responsive texts are similar to The Moon in 10241 Dots.

The most important part is a custom property used for the dot color (as fill)!

However, I decided to do a custom range slider and a color-picker as part of the poster, thus making it an interactive poster:

Kusama Poster Final

Can you spot them?!

Drag the slider and click the color-picker to make your own poster:

Modified version


Demo

Top comments (4)

Collapse
 
idosius profile image
Ido Schacham

Well done once again! Am very happy to subscribe to your posts.

I've also had to face the circle packing challenge a few months ago - but with balls in/on a cylinder in 3D.

I realized that the best solution would be some kind of data structure that represents the remaining available surface coordinates, and remove a chunk of that surface every time a ball is added.

Sadly, turns out this was very CPU consuming, at least with the libraries that I found. I ended up implementing something similar to the approach that you took, which is kind of silly, and not optimal, but good enough.

Collapse
 
madsstoumann profile image
Mads Stoumann

Thanks — happy to hear you like the posts! I went with the simplest "packing"-solution in the end, which fits Yayoi Kusamas style well, but is not ideal for grouping (and sorting) circles — like the more complex packing-algorithms.

Collapse
 
ant_f_dev profile image
Anthony Fung

Thanks for sharing. Something about mixing code with visual arts makes it feel more fun!

Collapse
 
madsstoumann profile image
Mads Stoumann

Indeed it’s fun!