Proof of concept of grayscale image recolored directly via D3js color scale. Wikimaps colors used.
Source: The source image is a global heightmap from the Shuttle Radar Topography Mission, released as part of NASA’s Blue Marble collection at 8km resolution. The topography data is stored in a simple 130KB black&white, 8-bit PNG. In it, darker values represent lower elevations (sea floor), lighter values represent higher elevations (mountains).
Colors are read out of the image using the Canvas API.
Data properties:
[0,255]
: there are 8 bits per channel, values range thus is [0-255]
. 0
: the deepest depression below sea level.14
: is the Sea level.Formerly, Mike Bostock used the 5th, 50th and 95th percentiles for land elevation as 15, 35 and 132 respectively; quantiles being an effective way to maximize contrast while remapping colors, similar to auto-tone features popular in image editors.
The percentiles are used as the domain of a diverging linear scale; red values are below the median elevation, and blue values are above. Interpolating in HCL colorspace improves perception.
Stylesheet: this dataviz follow the Wikipedia Maps Conventions for Topographic maps.
var color = d3.scale.linear()
.domain([0,14,15,40,100,200])
.range([
"#71ABD8", //-10000m dark blue
"#D8F2FE", // 0m light-blue
"#94BF8B", // 1m green
"#EFEBC0", // 300m yellow
"#AA8753", // 3000m brown
"#FFFFFF"]) //~6000m white
.interpolate(d3.interpolateHcl);
Binary images can store lots of data efficiently. Also, see Mike Bostock’s :
Note: Semantically, traditional rainbow color scale are to avoid since rainbow color scales are harmful. To do better, let’s use perceptually-optimized scales.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="../js/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var color = d3.scale.linear()
.domain([0,14,15,40,100,200])
.range(["#71ABD8", "#D8F2FE", "#94BF8B", "#EFEBC0", "#AA8753", "#FFFFFF"])
.interpolate(d3.interpolateHcl);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
getImage("readme.png", function(image) {
context.drawImage(image, 0, 0, width, height);
image = context.getImageData(0, 0, width, height);
// Rescale the colors.
for (var c, i = 0, n = width * height * 4, d = image.data; i < n; i += 4) {
c = d3.rgb(color(d[i]));
d[i + 0] = c.r;
d[i + 1] = c.g;
d[i + 2] = c.b;
}
context.putImageData(image, 0, 0);
});
function getImage(path, callback) {
var image = new Image;
image.onload = function() { callback(image); };
image.src = path;
}
</script>