block by HarryStevens cc5af3c0f0bf434e6623a88b51c7cf51

Radial Gradient Voronoi

Full Screen

Combine a radial gradient and Voronoi diagram to make beautiful patterns.

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
    }
  </style>
</head>
<body>
  <script src="https://unpkg.com/geometric@2.5.0/build/geometric.min.js"></script>

  <script type="module">
    import { range } from "https://cdn.skypack.dev/d3-array@3";
    import { Delaunay } from "https://cdn.skypack.dev/d3-delaunay@6";
    import { randomUniform } from "https://cdn.skypack.dev/d3-random@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 width = window.innerWidth, height = window.innerHeight;
    
    // Generate some random data
    const xr = randomUniform(0, width);
    const yr = randomUniform(0, height);
    const data = range(200).map(() => [ xr(), yr() ]);
    
    const polygons = Array
      .from(
        Delaunay
          .from(data)
          .voronoi([0, 0, width, height])
          .cellPolygons(),
        (p, i) => Object.assign(p, { data: data[i] })
      )
      .map(d => {
        // To avoid a thin black line between each circle,
        // increase the size of each voronoi polygon by .99px
        const o = d.map(p => {
          return geometric.pointTranslate(
            p, 
            geometric.lineAngle([d.data, p]),
            0.99
          );
        });

        o.data = d.data;

        return o;
      });

    // Lists of colors to loop through
    const colorArray = [
      ["#fff7f3", "#fde0dd", "#fcc5c0", "#fa9fb5", "#f768a1", "#dd3497", "#ae017e", "#7a0177", "#49006a"],
      ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"],
      ["#ffffcc", "#ffeda0", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#bd0026", "#800026"]
    ];
    let currArray = 0;
    let colors = colorArray[currArray].map((d, i, e) => ({ color: d, pct: i / (e.length - 1) * 100 }));

    const svg = select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    const rect = svg.append("rect")
        .attr("width", width)
        .attr("height", height)
        .style("fill", colors[colors.length - 1].color);

    const defs = svg.append("defs");

    const gradient = defs.append("radialGradient")
        .attr("id", "gradient");

    updateColors();

    defs.selectAll("clipPath")
        .data(polygons)
      .enter().append("clipPath")
        .attr("id", (d, i) => `clip-path-${i}`)
      .append("polygon")
        .attr("points", d => d);

    svg.selectAll("circle")
        .data(polygons)
      .enter().append("circle")
        .attr("r", 50)
        .attr("cx", d => d.data[0])
        .attr("cy", d => d.data[1])
        .style("clip-path", (d, i) => `url(#clip-path-${i})`)
        .style("fill", "url(#gradient)");

    interval(updateColors, 2000);

    function updateColors(){
      colors = colorArray[currArray].map((d, i, e) => ({color: d, pct: i / (e.length - 1) * 100}));

      let stops = gradient.selectAll("stop")
          .data(colors);
      
      stops.transition().duration(1900)
          .attr("stop-color", d => d.color);

      stops.enter().append("stop")
          .attr("offset", d => `${d.pct}%`)
          .attr("stop-color", d => d.color);
        
      rect.transition().duration(1900)
          .style("fill", colors[colors.length - 1].color);

      ++currArray;

      if (currArray === colorArray.length) currArray = 0;
    }
  </script>

</body>
</html>