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!