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>
<div class="outer">
  <div class="inner">
    <svg class="pink-cube" viewBox="0 0 300 230">

      <line opacity=".5" x1="80" x2="100" y1="150" y2="150" fill="none" stroke-width="1" stroke-linecap="round" stroke="var(--lightColor)" stroke-dasharray="2.5" />
      <line opacity=".5" x1="186" x2="204" y1="90" y2="90" fill="none" stroke-width="1" stroke-linecap="round" stroke="var(--lightColor)" stroke-dasharray="2.5" />
      <use xlink:href="#cube" x="121" y="112"></use>
      <use xlink:href="#cube" x="100" y="124"></use>
      <use xlink:href="#cube" x="142" y="124"></use>
      <use xlink:href="#cube" x="121" y="136"></use>
      <use class="m-left" xlink:href="#cube" x="79" y="136"></use>
      <use xlink:href="#cube" x="163" y="136"></use>
      <use xlink:href="#cube" x="142" y="148"></use>
      <use xlink:href="#cube" x="100" y="148"></use>
      <use xlink:href="#cube" x="121" y="160"></use>
      <use xlink:href="#cube" x="121" y="88"></use>
      <use xlink:href="#cube" x="100" y="100"></use>
      <use xlink:href="#cube" x="142" y="100"></use>
      <use xlink:href="#cube" x="121" y="112"></use>
      <use xlink:href="#cube" x="79" y="112"></use>
      <use class="m-right" xlink:href="#cube" x="163" y="112"></use>
      <use class="m-right" xlink:href="#cube" x="142" y="124"></use>
      <use xlink:href="#cube" x="100" y="124"></use>
      <use xlink:href="#cube" x="121" y="136"></use>
      <use xlink:href="#cube" x="121" y="64"></use>
      <use class="m-up" xlink:href="#cube"...

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

// fill an SVG with 27 cubes forming one big cube that groups into 3 cube-groups according to the provided values
function createDynamicCubeLayout(svgSelector, values) {
   // cube positions with grouping and placement order
  const cubePositions = [
    { x: 121, y: 48, group: 'middle', order: 3 },
    { x: 121, y: 24, group: 'middle', order: 3 },
    { x: 121, y: 0, group: 'middle', order: 3 },
    { x: 100, y: 60, group: 'left', order: 3 },
    { x: 100, y: 36, group: 'left', order: 3 },
    { x: 100, y: 12, group: 'left', order: 3 },
    { x: 142, y: 60, group: 'right', order: 2 },
    { x: 142, y: 36, group: 'right', order: 2 },
    { x: 142, y: 12, group: 'right', order: 2 },
    { x: 163, y: 72, group: 'right', order: 3 },
    { x: 163, y: 48, group: 'right', order: 3 },
    { x: 163, y: 24, group: 'right', order: 3 },
    { x: 79, y: 72, group: 'left', order: 1 },
    { x: 79, y: 48, group: 'left', order: 1 },
    { x: 79, y: 24, group: 'left', order: 1 },
    { x: 121, y: 72, group: 'middle', order: 2 },
    { x: 121, y: 48, group: 'middle', order: 2 },
    { x: 121, y: 24, group: 'middle', order: 2 },
    { x: 100, y: 84, group: 'left', order: 2 },
    { x: 100, y: 60, group: 'left', order: 2 },
    { x: 100, y: 36, group: 'left', order: 2 },
    { x: 142, y: 84, group: 'right', order: 1 },
    { x: 142, y: 60, group: 'right', order: 1 },
    { x: 142, y: 36, group: 'right', order: 1 },
    { x: 121, y: 96, group: 'middle', order: 1 },
    { x: 121, y: 72, group: 'middle', order: 1 },
    { x: 121, y: 48, group: 'middle', order: 1 },
  ];

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

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

  // clear existing SVG content
  const svg = document.querySelector(svgSelector);
  svg.innerHTML = '';

  // append cubes based on original order
  cubePositions.forEach(pos => {
   ...