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
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); |