Skip to content

Instantly share code, notes, and snippets.

@eflowbeach
Forked from couchand/README.md
Created December 18, 2015 18:05
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 eflowbeach/c2e8d7d19cbe50f7cb43 to your computer and use it in GitHub Desktop.
Save eflowbeach/c2e8d7d19cbe50f7cb43 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