Skip to content

Instantly share code, notes, and snippets.

@curran
Last active January 28, 2021 19:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/04cd8c28e06facc55bd7 to your computer and use it in GitHub Desktop.
Save curran/04cd8c28e06facc55bd7 to your computer and use it in GitHub Desktop.
Texture Scales

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: riccardoscalco/textures#7

// 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;
}
<!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>
// 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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment