JSFiddle - React, Tailwind, and code Playground

by Graham Dixon

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*3 cube and seperate into 3 groups [left, middle, right] 
function generateCube(n, values = [1, 1, 1], className="blue-cube") {
  // fill with cubes before printing to screen
  const cubes = [];

  // 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;

  // 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);

  // 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
    });

    // we set the 3d depth from the one x/y vector using layer
    for (let layer = 0; layer < n; layer++) {
      // calc the layer offset to simulate depth
      const layerOffset = ((layer * ((1 - cubeHeight) + verticalSpacing - 1)));
      
      // calc the x pos using the hoz and width vals
      const x = startX + (col * (cubeWidth + horizontalSpacing)) + (depth * horizontalSpacing);
      // do the same with the vert and height vals for y but also consider the layers vertical offsets
      const y = startY + (row * (cubeHeight + verticalSpacing)) + (depth * verticalSpacing) - layerOffset;

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

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

  // group according to cubes position
  const...