Skip to content

Instantly share code, notes, and snippets.

@couchand
Forked from mbostock/.block
Last active September 15, 2016 18:42
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save couchand/6537398 to your computer and use it in GitHub Desktop.
Save couchand/6537398 to your computer and use it in GitHub Desktop.
Adjustable ranges on color scale

An example illustrating a scale that allows the ranges to be adjusted by the user.

This example is based on this one.

This example demonstrates how to construct a key from a threshold scale, in the style of Ford Fessenden’s map of police stops involving force. A linear scale is used to set the x-position of each colored rectangle in the key. There is one rectangle per color in the threshold scale’s range, and one tick per value in the threshold scale’s domain. The linear scale’s domain sets the implied extent of the key, here spanning 0 to 100%.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
margin: 0;
overflow: hidden;
}
svg {
font: 10px sans-serif;
}
.caption {
font-weight: bold;
}
.key path {
display: none;
}
.key line {
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<script>
var scale = 'Blues',
scaleMax = 9;
var width = 960,
height = 500,
formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
var threshold = d3.scale.threshold()
.domain([.11, .22, .33, .50])
.range(colorbrewer.Blues[5]);
// A position encoding for the key only.
var x = d3.scale.linear()
.domain([0, 1])
.range([0, 240]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(13)
.tickValues(threshold.domain())
.tickFormat(function(d, i) { return i === threshold.domain().length-1 ? formatPercent(d) : formatNumber(100 * d); });
var others;
var drag = d3.behavior.drag()
.on('dragstart', function(d) {
others = [];
threshold.domain().forEach(function(v) {
if ( v == d ) return;
others.push(v);
});
})
.on('drag', function(d) {
var xMin = x.domain()[0], xMax = x.domain()[1];
var newValue = x.invert( d3.event.x );
newValue =
newValue < xMin ? xMin :
xMax < newValue ? xMax :
newValue;
var newDomain = others.slice();
newDomain.push( newValue );
newDomain.sort();
threshold.domain( newDomain );
xAxis.tickValues( newDomain );
update();
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(" + (width - 240) / 2 + "," + height / 2 + ")");
var rects = g.append("g");
function update() {
var rect = rects.selectAll(".range")
.data(threshold.range().map(function(color) {
var d = threshold.invertExtent(color);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}));
rect.enter().append("rect")
.attr("class", "range")
.attr("height", 8)
.on("dblclick", function() {
var newValue = x.invert( d3.mouse(this)[0] );
var newDomain = threshold.domain().slice();
newDomain.push( newValue );
if ( newDomain.length >= scaleMax ) return;
newDomain.sort();
threshold
.domain( newDomain )
.range(colorbrewer[scale][newDomain.length+1]);
xAxis.tickValues( newDomain );
update();
});
rect
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.style("fill", function(d) { return threshold(d[0]); });
g.call(xAxis)
.selectAll(".tick")
.style("cursor", "ew-resize")
.call(drag)
.append("rect")
.attr("x", -3)
.attr("width", 6)
.attr("height", 13)
.attr("fill-opacity", 0);
}
update();
g.append("text")
.attr("class", "caption")
.attr("y", -6)
.text("Percentage of stops that involved force");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment