block by harrystevens 2d51280d0f268f8e9dda1b9f72bbc798

Isometric Grid Transition

Full Screen

Transition to an isometric projection and back again.

See also Isometric Grid Slider.

index.html

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        margin: 0;
      }
      svg {
        display: table;
        margin: 0 auto;
      }
      line {
        stroke-width: 4px;
      }
      path {
        fill: none;
      }
      .cell {
        stroke: #eee;
      }
      .outline {
        stroke: #000;
        stroke-width: 2px;
      }
    </style>
  </head>
  <body>
    <script type="module">
      import { cross, range } from "https://cdn.skypack.dev/d3-array@3";
      import { geoEquirectangular, geoPath } from "https://cdn.skypack.dev/d3-geo@3";
      import { randomUniform } from "https://cdn.skypack.dev/d3-random@3";
      import { interpolateSinebow } from "https://cdn.skypack.dev/d3-scale-chromatic@3";
      import { select } from "https://cdn.skypack.dev/d3-selection@3";
      import { interval } from "https://cdn.skypack.dev/d3-timer@3";
      import { transition } from "https://cdn.skypack.dev/d3-transition@3";

      const { cos, PI, round, sin } = Math;

      const i = 0.4;
      const r = range(-1, 1, i);
      const cells = {
        type: "FeatureCollection",
        features: cross(r, r)
          .map(([lon, lat]) => ({
            type: "Feature",
            geometry: {
              type: "Polygon",
              coordinates: [
                [
                  [lon, lat],
                  [lon, lat + i],
                  [lon + i, lat + i],
                  [lon + i, lat],
                  [lon, lat]
                ]
              ]
            }
          }))
      };

      const x = n => round(cos(n));
      const y = n => round(sin(n));
      const random = randomUniform(-1, 1);
      const data = Array.from({ length: 250 }).map(d => ([random(), random()]));
      const square = [ range(1, PI * 2.5, PI * 0.25).map(n => [x(n), y(n)]) ];

      const margin = {left: 1, right: 1, top: 30, bottom: 1};
      const size = 400;
      const projection = geoEquirectangular()
        .reflectY(true)
        .fitSize([size, size], cells);
      const path = geoPath(projection);

      const svg = select("body").append("svg")
          .attr("width", size + margin.left + margin.right)
          .attr("height", size + margin.top + margin.bottom);

      const g = svg.append("g")
          .attr("transform", `translate(${[margin.left, margin.top]})`);

      const cell = g.selectAll(".cell")
          .data(cells.features)
        .join("path")
          .attr("class", "cell")
          .attr("d", path);

      const outline = g.append("path")
          .attr("class", "outline")
          .datum({
            type: "Polygon",
            coordinates: square
          })
          .attr("d", path);

      const line = g.selectAll("line")
          .data(data)
        .join("line")
          .attr("stroke", (d, i, e) => interpolateSinebow(i / e.length))
          .attr("y2", -margin.top + 1)
          .attr("transform", d => `translate(${projection(d)})`);

      let rotation = [0, 0, 0];
      draw(rotation);
      interval(() => {
        rotation = rotation[0] ? [0, 0, 0] : [30, -60, 0];
        draw(rotation)
      }, 2e3);

      function draw(rotation){
        projection
            .rotate(rotation)
            .fitSize([size, size], cells);

        cell
          .transition()
          .duration(1e3)
            .attr("d", path);

        outline
          .transition()
          .duration(1e3)
            .attr("d", path);

        line
          .transition()
          .duration(1e3)
            .attr("transform", d => `translate(${projection(d)})`);
      }
    </script>
  </body>
</html>