block by curran 04cd8c28e06facc55bd7

Texture Scales

Full Screen

A test of textures.js that shows one approach for creating a “texture scale” that combines individual scales for pattern, size, and color.

See also this discussion on GitHub: https://github.com/riccardoscalco/textures/issues/7

index.html

<!DOCTYPE html>
<html>
  <head>
    <script src="https://riccardoscalco.github.io/textures/textures.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <meta charset="utf-8">
    <title>Texture Test</title>
  </head>
  <body>
    <div id="example"></div>
    <script src="createTextureScale.js"></script>
    <script src="main.js"></script>
  </body>
</html>

createTextureScale.js

// Creates a texture scale that combines pattern, color, and size.
function createTextureScale(){
  var patternScale,
      colorScale,
      sizeScale,
      patternAccessor,
      colorAccessor,
      sizeAccessor,
      texturesCache = {};

  function my(d){

    // Extract data values using accessors.
    var patternValue = patternAccessor(d),
        colorValue = colorAccessor(d),
        sizeValue = sizeAccessor(d),

        // Use data values to look up the texture.
        key = [patternValue, colorValue, sizeValue].join(","),
        texture = texturesCache[key],

        pattern, color, size;
    
    // Create a new texture the first time each unique
    // (texture, color, size) combination is encountered.
    if(!texture){

      // Evaluate scaled values for pattern, color, and size.
      pattern = patternScale(patternValue);
      color = colorScale(colorValue);
      size = sizeScale(sizeValue);

      // Create the base texture with the pattern generator.
      texture = pattern();

      // Apply color.
      texture = colorizeTexture(texture, color);

      // Apply size.
      texture = texture.size(size);

      // Initialize the texture.
      svg.call(texture);

      // Store the texture for future reuse.
      texturesCache[key] = texture;
    }

    // Return the url so this function can be passed directly into the fill attribute.
    return texture.url();
  }

  // Makes the given texture appear as the given color.
  function colorizeTexture(texture, color){

    // Use stroke, present on all patterns.
    var texture = texture.stroke(color);

    // Use fill, present only on some textures (e.g. "circles", not "lines").
    if(texture.fill){
      texture.fill(color);
    }

    return texture;
  }

  // API design inspired by http://bost.ocks.org/mike/chart/
  my.patternScale = function(value){
    if (!arguments.length) return patternScale;
    patternScale = value;
    return my;
  };

  my.colorScale = function(value){
    if (!arguments.length) return colorScale;
    colorScale = value;
    return my;
  };

  my.sizeScale = function(value){
    if (!arguments.length) return sizeScale;
    sizeScale = value;
    return my;
  };

  my.patternAccessor = function(value){
    if (!arguments.length) return patternAccessor;
    patternAccessor = value;
    return my;
  };

  my.colorAccessor = function(value){
    if (!arguments.length) return colorAccessor;
    colorAccessor = value;
    return my;
  };

  my.sizeAccessor = function(value){
    if (!arguments.length) return sizeAccessor;
    sizeAccessor = value;
    return my;
  };

  return my;
}

main.js

// This program tests the script "createTextureScale", which
// creates a texture scale by combining scales for pattern, color, and size.
var width = 500,
    height = 500,

    // An n X n grid of circles will be created.
    n = 12,
    x = d3.scale.linear().domain([0, n]).range([0, width]),
    y = d3.scale.linear().domain([0, n]).range([0, height]),
    radius = 20,
    transitionDuration = 1000,
    svg = d3.select("#example").append("svg")
      .attr("width", width)
      .attr("height", height)

      // Center the SVG with respect to default width of bl.ocks.
      .style("position", "absolute")
      .style("left", 960 / 2 - width / 2),

    
    // Create a scale that encapsulates patterns.
    patternScale = d3.scale.ordinal()
      .domain(["A", "B", "C"])

      // Patterns (base textures) need to be generated multiple times,
      // once for each (size, color) pair they are paired with.
      // Therefore these need to be functions, not direct textures.
      .range([
        function(){ return textures.lines(); },
        function(){ return textures.circles(); },
        function(){ return textures.paths().d("squares"); }
      ]),

    // Create a scale that encapsulates colors.
    // Colors from http://colorbrewer2.org/
    colorScale = d3.scale.ordinal()
      .domain(["X", "Y", "Z"])
      .range(["#1b9e77", "#d95f02", "#7570b3"]),

    // Create a scale that encapsulates size.
    sizeScale = d3.scale.linear()
      .domain([0, 1])
      .range([5, 20])
    
    // Create a combined scale for pattern, size, and color.
    textureScale = createTextureScale()
      .patternScale(patternScale)
      .colorScale(colorScale)
      .sizeScale(sizeScale)
      .patternAccessor(function(d){ return d.pattern; })
      .colorAccessor(function(d){ return d.color; })
      .sizeAccessor(function(d){ return d.size; });

// Initialize the data grid.
var data = [];
for(var i = 0; i < n; i++){
  for(var j = 0; j < n; j++){
    data.push({
      x: i,
      y: j,
      pattern: i < n / 3 ? "A" : i < (n * 2 / 3) ? "B" : "C",
      color: j < n / 3 ? "X" : j < (n * 2 / 3) ? "Y" : "Z",
      size: j / n
    });
  }
}

// Create the marks.
var marks = svg.selectAll(".mark").data(data)
      .enter().append("circle")
    
      // The "mark" class is necessary, because
      // selectAll("circle") conflicts with the circle texture.
      .attr("class", "mark")
  
      .attr("cx", function(d){ return x(d.x) + radius; })
      .attr("cy", function(d){ return y(d.y) + radius; })
  
      // Use the color scale for the stroke around each circle.
      .style("stroke", function(d){ return colorScale(d.color); })
  
      // Use the combined texture & color scale to define the texture.
      .style("fill", textureScale);

// Periodically set a random radius on each circle.
function randomizeSize(){
  marks.transition().duration(transitionDuration)
    .attr("r", function(){ return Math.random() * radius; });
}
randomizeSize();
setInterval(randomizeSize, transitionDuration);