JSFiddle - React, Tailwind, and code Playground

by Graham Dixon

HTML

<svg class="hidden">
  <defs>
    <g id="cube" class="cube-unit">
      <rect width="21" height="24" fill="var(--lightColor)" stroke="var(--strokeColor)" transform="skewY(30)" />
      <rect width="21" height="24" fill="var(--darkColor)" stroke="var(--strokeColor)" transform="skewY(-30) translate(21 24.3)" />
      <rect width="21" height="21" fill="var(--mainColor)" stroke="var(--strokeColor)" transform="scale(1.41,.81) rotate(45) translate(0 -21)" />
    </g>
  </defs>
</svg>

CSS

body {
  font-family: "Roboto", sans-serif;
  margin: 0 auto;
  text-align: center;
}

.hidden {
  display: none;
}

.outer {
  display: flex;
  width: 100%;
}

.inner {
  width: 50%;
  flex-direction: column;
}

.cube-unit {
  fill-opacity: 0.9;
  stroke-miterlimit: 0;
}

.blue-cube, .blue-cube-generated {
  --mainColor: #009cde;
  --strokeColor: #0079ad;
  --lightColor: #00affa;
  --darkColor: #008bc7;
}

.pink-cube, .pink-cube-generated {
  --mainColor: #de0063;
  --strokeColor: #ad004e;
  --lightColor: #fa0070;
  --darkColor: #c7005a;
}

.m-left,
.m-right {
  transform: translateX(0px);
}

.m-up,
.m-down {
  transform: translateY(0px);
}

.m-left {
  --translate: -50px;
}

.m-right {
  --translate: 50px;
}

svg * {
  transition: 0.2s transform;
}

svg:hover .m-left,
svg:hover .m-right {
  transform: translateX(var(--translate));
}

svg:hover .m-up,
svg:hover .m-down {
  transform: translateY(var(--translate));
}

JavaScript

// generate a n**n cube and seperate into 3 groups [left, middle, right] 
function generateCube(n, values) {
  // define dimensions and spacing
  const cubeWidth = 21;
  const cubeHeight = -12;
  const horizontalSpacing = 21;
  const verticalSpacing = 12;

  // define starting position
  const startX = 32;
  const startY = 64;

  // create a new svg
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

  // produce the matrix and rotate for layout positions
  const cubeMatrix = generateSquareMatrix(n);
  const cubePositions = rotateMatrix45Deg(cubeMatrix);

  // rotate in the opposite direction to gather groupings
  const groupPositions = rotateMatrix45Deg(cubeMatrix, 1);

  // fill with cubes before printing to screen
  const cubes = [];

  // create and position cubes
  cubePositions.flat().forEach((dets) => {
    // extract row/col
    const {
      x: row,
      y: col
    } = dets;

    // get depth from position in the cubePositions array
    const depth = cubePositions.findIndex((rows, indx) => {
      return rows.indexOf(dets) !== -1
    });

    for (let layer = 0; layer < n; layer++) {
      const x = startX + (col * (cubeWidth + horizontalSpacing)) + (depth * horizontalSpacing);
      const y = startY + (row * (cubeHeight + verticalSpacing)) + (depth * verticalSpacing) - ((layer * ((1 - cubeHeight) + verticalSpacing - 1)));

      // record the cube
      cubes.push({
        screenX: x,
        screenY: y,
        x: row,
        y: col,
        order: layer
      });
    }
  });

  // adjust allocations by weighting
  const adjustedAllocations = adjustAllocations(values, n);

  // group according to cubes position
  const [leftGroup, middleGroup, rightGroup] = distributeCubes(adjustedAllocations, cubeMatrix, cubes);

  // print the cubes
  cubes.forEach((pos) => {
    // Create a new cube element (assuming SVG)
    const cube = document.createElementNS("http://www.w3.org/2000/svg", "use");
   ...