Circle-Packing with Javascript and SVG — the Kusama Way

Mads Stoumann - Jul 22 '23 - - Dev Community

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player