block by veltman f24fba4f6549639cacfd4d0a50e9d4b8

Argyle

Full Screen

Infinite argyle with random ColorBrewer and d3 4.0 color scales.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <style>

    path {
      stroke: none;
    }

    line {
      stroke: #444;
      stroke-width: 1px;
      stroke-linecap: round;
      opacity: 0.8;
    }

    @media (-webkit-min-device-pixel-ratio: 1.5),
           (min-resolution: 1.5dppx) {
      line {
        stroke-width: 0.6px;
        opacity: 0.95;
      }
    }

  </style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="argyle.js"></script>
<script>

var angle = Math.PI / 6,
    rx = 30,
    ry = rx / Math.tan(angle),
    diagonal = Math.sqrt(rx * rx + ry * ry);
    width = 960,
    height = Math.ceil(500 / ry) * ry;

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

var diamond = d3.symbol()
  .type(d3.symbolDiamond)
  .size(ry * rx * 2);

// Array of [row, column] positions
var positions = d3.merge(d3.range(Math.ceil(height / ry) + 1).map(function(r){
  return d3.range(Math.ceil(width / (rx * 2)) + 1).map(function(c){
    return [r, c];
  });
}));

var g = svg.selectAll(".diamond")
  .data(positions)
  .enter()
  .append("g")
    .attr("transform", function(d){

      // Odd-r coordinates
      var x = (d[0] % 2 ? rx : 0) + d[1] * rx * 2,
          y = d[0] * ry;

      return "translate(" + x + " " + y + ")";

    });

var background = g.append("path")
  .attr("d", diamond);

g.append("line")
  .attr("x1", rx / 2)
  .attr("x2", -rx / 2);

g.append("line")
  .attr("x1", -rx / 2)
  .attr("x2", rx / 2);

// Separate lines + calculated dasharray to prevent cuts
var stitch = g.selectAll("line")
  .attr("y1", -ry / 2)
  .attr("y2", ry / 2)
  .attr("stroke-dasharray",(diagonal / 30) + "," + (diagonal / 30))
  .attr("stroke-dashoffset",(-diagonal / 60));

recolor(true);

function recolor(first) {

  var pattern = argyle();

  var t = d3.transition()
    .delay(first ? 0 : 1000)
    .duration(first ? 0 : 1000)
    .on("end", recolor);

  background.transition(t)
    .styleTween("fill", function(d){
      return d3.interpolateLab(getComputedStyle(this).getPropertyValue("fill"), pattern.color(d));
    });

  stitch.transition(t)
    .styleTween("stroke", function(d){
      return d3.interpolateLab(getComputedStyle(this).getPropertyValue("stroke"), pattern.stitch[pattern.color(d)]);
    });

}

d3.select(self.frameElement).style("height", height + "px");

</script>

argyle.js

(function(){

  var runs = 0,
      scheme;

  var colorSchemes = d3.shuffle([
    ["#e0f3db", "#a8ddb5", "#43a2ca"], // GnBu
    ["#ece2f0", "#a6bddb", "#1c9099"], // PuBuGn
    ["#fde0dd", "#fa9fb5", "#c51b8a"], // RdPu
    ["#f7fcb9", "#addd8e", "#31a354"], // YlGn
    ["#edf8b1", "#7fcdbb", "#2c7fb8"], // YlGnBlue
    ["#ffeda0", "#feb24c", "#f03b20"], // YlOrRd
    ["#d8b365", "#f6e8c3", "#c7eae5", "#5ab4ac"], // BrBG
    ["#fc8d59", "#ffffbf", "#99d594"], // Spectral3
    ["#d7191c", "#fdae61", "#abdda4", "#2b83ba"], // Spectral4
    ["#fc8d59", "#ffffbf", "#91bfdb"], // RdYlBu
    ["#fc8d59", "#fee090", "#e0f3f8", "#91bfdb"], // RdYlBu
    d3.interpolateViridis,
    d3.interpolatePlasma,
    d3.interpolateWarm,
    d3.interpolateCool,
    d3.interpolateRainbow
  ]);

  var greys = ["#222", "#666", "#ccc", "#f7f7f7"];

  var patterns = [standard, checker, wave, bowtie, columns];

  function getPattern() {

    var r = Math.random(),
        totalColors = r < 0.1 ? 5 : r < 0.5 ? 4 : 3,
        colors = scheme.slice(0),
        stitchColors;

    // Pad with greys
    if (colors.length < totalColors) {

      d3.shuffle(greys);
      colors = colors.concat(greys.slice(0, totalColors - colors.length));

    } else {

      colors = d3.shuffle(colors).slice(0, totalColors);

      // Mix in a grey sometimes anyway
      if (Math.random() < 0.3) {
        colors[0] = choose(greys);
      }

    }

    stitchColors = getStitchColors(colors);

    d3.shuffle(colors);

    return {
      stitch: stitchColors,
      color: choose(patterns)(colors),
    };

  }

  // Set stitch coloring based on highest contrast ratio
  // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
  function getStitchColors(colors) {

    var l = colors.map(relativeLuminance),
        stitchColors = {},
        splice = colors.length === 5 || (colors.length === 4 && Math.random() < 0.35),
        primary,
        ratios;

    // Calculate contrast ratios for other colors
    ratios = colors.map(function(color, i){

      return colors.map(function(d, j){
        return (Math.max(l[i], l[j]) + 0.05) / (Math.min(l[i], l[j]) + 0.05);
      });

    });

    // Color with highest average contrast
    primary = d3.scan(ratios, function(a, b) { return 1 / d3.mean(a) - 1 / d3.mean(b); });

    // Dedicated stitch color
    if (splice) {

      colors.forEach(function(color, i){
        if (i !== primary) {
          stitchColors[color] = colors[primary];
        }
      });

      colors.splice(primary, 1);

    // Background colors as stitch colors on each other
    } else {

      colors.forEach(function(color, i){

        var secondary;

        if (i === primary) {

          secondary = d3.scan(ratios[i], function(a, b) { return 1 / a - 1 / b; });

          stitchColors[color] = colors[secondary];

        } else {
          stitchColors[color] = colors[primary];
        }

      });

    }

    return stitchColors;

  }

  // sRGB relative luminance
  // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
  function relativeLuminance(color) {

    var rgb = d3.rgb(color);

    var weights = ["r", "g", "b"].map(function(key){

      var val = rgb[key] / 255;

      return val <= 0.03928 ?
        val / 12.92 :
        Math.pow((val + 0.055) / 1.055, 2.4);

    });

    return weights[0] * 0.2126 + weights[1] * 0.7152 + weights[2] * 0.0722;

  }

  function nextColorScheme() {

    var offset,
        numColors;

    // Move first to last
    colorSchemes.push(scheme = colorSchemes.shift());

    // For d3 scales, get random evenly spaced colors
    if (typeof scheme === "function") {

      offset = Math.random();
      numColors = choose([3, 4]);

      scheme = d3.range(numColors).map(function(d){
        return scheme((offset + d / (numColors)) % 1);
      });

    }

  }

  function standard(colors){

    return function(d){
      return d[0] % 2 ?
        colors[0] :
        colors[1 + d[1] % (colors.length - 1)];
    };

  }

  function checker(colors){

    return function(d) {
      return d[0] % 2 ?
        colors[0] :
        colors[1 + ((d[1] + d[0] / 2) % (colors.length - 1))];
    };

  }

  function wave(colors){

    return function(d){

      return d[0] % 2 ?
        colors[0] :
        d[0] % 4 ?
          colors[2 + Math.floor(d[0] / 4) % (colors.length - 2)] :
          colors[1];

    };

  }

  function bowtie(colors){

    if (colors.length < 4) {
      return standard(colors);
    }

    return function(d){
      return d[0] % 2 ?
        colors[d[1] % 2] :
        colors[2 + (d[0] / 2) % 2];
    };

  }

  function columns(colors) {

    if (colors.length < 4) {
      return checker(colors);
    }

    return function(d) {
      return d[0] % 2 ?
        colors[d[1] % 2] :
        colors[2 + d[1] % 2];
    };

  }

  function choose(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
  }

  window.argyle = function(){

    if (runs++ % 3 === 0) {
      nextColorScheme();
    }

    return getPattern();

  };

})();