arranging
s around a polygon (HTML, css, .ejs)

I want to arrange some rectangular div components around a regular polygon. Basically one of the long sides of the divs will be coincident with a line segment around the polygon.

In the final code, I’ll use .ejs (since the number of sides of the polygon is dynamic, 3-10 sides). In my “quick and dirty” testing I’m doing a triangle in just HTML and CSS to get the math right.

I have a “very close” solution already and am wondering how to get it “exact” and am also wondering why my geometry intuition is so far off.

HTML and CSS:

div {
  position: absolute;
  left: 200px;
  top: 200px;
  width: 80px;
  height: 40px;
  background-color: skyblue;
}

.rotatedA {
  transform: translateY(-60px) translateX(-35px) rotate(300deg);
  background-color: blue;
}


.rotatedB {
  transform: translateY(-60px) translateX(35px)  rotate(60deg);
  background-color: red;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="basic.css">
  </head>
  <body>
    <div>Normal</div>
    <div class="rotatedA">Rotated</div>
    <div class="rotatedB">Rotated</div>
  </body>
</html>

The first attempt I rotated “A” by 60 and “B” by -60 and did a translateY equal to the div height. When that did not work I played around with it. On this last attempt (close but not perfect since the rotations won’t give an integer) it seems like the Y adjustment is 1.5x (item height + cos(60)) but the X adjustment is 1/2 of sin(60) (I don’t understand why).

Since my results aren’t going to be an integer number of pixels what is the correct way to do this? Also, I don’t understand why my geometry is so off (I could understand sin(60) but 1/2(sin(60)) doesn’t make sense to me

Answer

Here’s a mathematical way; the number and dimensions are read by the script, then the divs are arranged accordingly. I also made sure that the wrapper container has the correct dimensions so it can be used with other elements:

function arrange(wrapper) {
  wrapper.style.position = "relative";
  const rects = Array.from(wrapper.children);

  const n = rects.length;
  /* dimensions of a rectangle */
  const bb = rects[0].getBoundingClientRect();
  const a = bb.width;
  const h = bb.height;
  
  /* incircle radius of regular polygon */
  const r = a * 0.5 / Math.tan(Math.PI / n);

  /* radius of outer circle */
  const bigR = Math.sqrt((r + h) * (r + h) + a * a / 4);

  rects.forEach((rect, i) => {
    const angle = i * (360 / n);
    if (angle) rect.style.transform = `rotate(${angle}deg)`;
    rect.style.position = angle ? "absolute" : "relative";
    rect.style.marginBottom = bigR + r + "px";
    rect.style.transformOrigin = `${a/2}px ${-r}px`;
    rect.style.left = bigR - a / 2 + "px";
    rect.style.top = bigR + r + "px";
  });
  if (window.getComputedStyle(wrapper).display == "inline-block")
    wrapper.style.width = 2 * bigR + "px";
}

arrange(document.querySelector('#polygon'));
#polygon {
  border: 1px solid black;
  display: inline-block;
}

#polygon div {
  width: 80px;
  height: 20px;
  background-color: skyblue;
  text-align: center;
  padding: 5px;
}
<div id="polygon">
  <div>Normal</div>
  <div>Rotated</div>
  <div>Rotated</div>
  <div>Rotated</div>
  <div>Rotated</div>
  <div>Rotated</div>
  <div>Rotated</div>
</div>

The basic idea is to

  1. calculate the in-circle’s radius of the polygon based on the width of a rectangle
  2. set transform-origin accordingly centered and above the first rectangle
  3. arrange the others by rotating them
  4. (do more calculations so the wrapper element encompasses everything exactly)