Skip to content

Instantly share code, notes, and snippets.

@starcalibre
Created January 25, 2016 08:21
Show Gist options
  • Save starcalibre/6cccfa843ed254aa0a0d to your computer and use it in GitHub Desktop.
Save starcalibre/6cccfa843ed254aa0a0d to your computer and use it in GitHub Desktop.
Dynamic Colour Scale Legend
.idea/
*.iml

The SVG linearGradient def in tandem with an axis can be used to create legends for continuous colour scales.

This is an example of generating the def programatically, enabling for dynamic creation and updating of colour scales. Different colour scales can be chosen from the drop-down menu and the plot and legend updates accordingly. In this we're using diverging scales for the range [-3, 3], but this code can be adapted to other types of colour scales.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle {
stroke-width: 1;
stroke: white;
}
#legend-svg {
vertical-align: top;
}
#controls {
padding-top: 10px;
}
</style>
</head>
<body>
<div id="controls">
<label for="scale-select">Select Colour Scale: </label>
<select id="scale-select">
<option value="puOr11">puOr11</option>
<option value="spectral8">spectral8</option>
<option value="redBlackGreen">redBlackGreen</option>
<option value="viridis">viridis</option>
</select>
</div>
<div id="container">
<svg id="main-svg"></svg>
<svg id="legend-svg"></svg>
</div>
</body>
<script src="./scales.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js" charset="utf-8"></script>
<script>
var fullWidth = 600;
var fullHeight = 400;
var margin = { top: 20, bottom: 20, left: 30, right: 20 };
var width = fullWidth - margin.left - margin.right;
var height = fullHeight - margin.top - margin.bottom;
var numberPoints = 500;
var rangeScale = d3.scale.linear()
.domain([-200, 200])
.range([-3, 3]);
var randomX = d3.random.normal(0, 50);
var randomY = d3.random.normal(0, 50);
var data = d3.range(numberPoints).map(function(d, i) {
var x = randomX();
var y = randomY();
return {
x: x,
y: y,
z: rangeScale(x + y),
i: i
};
});
var xRange = d3.extent(data, function(d) { return d.x });
var yRange = d3.extent(data, function(d) { return d.y });
var xScale = d3.scale.linear()
.domain([xRange[0] - 10, xRange[1] + 10])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([yRange[0] - 10, yRange[1] + 10])
.range([height, 0]);
var svg = d3.select('#main-svg')
.attr('width', fullWidth)
.attr('height', fullHeight)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' +
margin.top + ')');
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var xAxisSvg = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
var yAxisSvg = svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
svg.selectAll('circle').data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return xScale(d.x);
})
.attr('cy', function(d) {
return yScale(d.y);
})
.attr('r', 5);
// add the legend now
var legendFullHeight = fullHeight;
var legendFullWidth = 50;
var legendMargin = { top: 20, bottom: 20, left: 5, right: 20 };
// use same margins as main plot
var legendWidth = legendFullWidth - legendMargin.left - legendMargin.right;
var legendHeight = legendFullHeight - legendMargin.top - legendMargin.bottom;
var legendSvg = d3.select('#legend-svg')
.attr('width', legendFullWidth)
.attr('height', legendFullHeight)
.append('g')
.attr('transform', 'translate(' + legendMargin.left + ',' +
legendMargin.top + ')');
updateColourScale(scales['puOr11']);
// attach event listener to control
d3.select('#scale-select').on('change', function() {
var val = d3.select(this).node().value;
updateColourScale(scales[val]);
});
// update the colour scale, restyle the plot points and legend
function updateColourScale(scale) {
// create colour scale
var colorScale = d3.scale.linear()
.domain(linspace(-3, 3, scale.length))
.range(scale);
// style points
d3.selectAll('circle')
.attr('fill', function(d) {
return colorScale(d.z);
});
// clear current legend
legendSvg.selectAll('*').remove();
// append gradient bar
var gradient = legendSvg.append('defs')
.append('linearGradient')
.attr('id', 'gradient')
.attr('x1', '0%') // bottom
.attr('y1', '100%')
.attr('x2', '0%') // to top
.attr('y2', '0%')
.attr('spreadMethod', 'pad');
// programatically generate the gradient for the legend
// this creates an array of [pct, colour] pairs as stop
// values for legend
var pct = linspace(0, 100, scale.length).map(function(d) {
return Math.round(d) + '%';
});
var colourPct = d3.zip(pct, scale);
colourPct.forEach(function(d) {
gradient.append('stop')
.attr('offset', d[0])
.attr('stop-color', d[1])
.attr('stop-opacity', 1);
});
legendSvg.append('rect')
.attr('x1', 0)
.attr('y1', 0)
.attr('width', legendWidth)
.attr('height', legendHeight)
.style('fill', 'url(#gradient)');
// create a scale and axis for the legend
var legendScale = d3.scale.linear()
.domain([-3, 3])
.range([legendHeight, 0]);
var legendAxis = d3.svg.axis()
.scale(legendScale)
.orient("right")
.tickValues(d3.range(-3, 4))
.tickFormat(d3.format("d"));
legendSvg.append("g")
.attr("class", "legend axis")
.attr("transform", "translate(" + legendWidth + ", 0)")
.call(legendAxis);
}
function linspace(start, end, n) {
var out = [];
var delta = (end - start) / (n - 1);
var i = 0;
while(i < (n - 1)) {
out.push(start + (i * delta));
i++;
}
out.push(end);
return out;
}
</script>
</html>
var scales = {
'puOr11': ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'],
'spectral8': ['#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#e6f598', '#abdda4', '#66c2a5', '#3288bd'],
'redBlackGreen': ['#ff0000', '#AA0000', '#550000', '#005500', '#00AA00', '#00ff00'],
'viridis': ["#440154","#440256","#450457","#450559","#46075a","#46085c","#460a5d","#460b5e","#470d60","#470e61","#471063","#471164","#471365","#481467","#481668","#481769","#48186a","#481a6c","#481b6d","#481c6e","#481d6f","#481f70","#482071","#482173","#482374","#482475","#482576","#482677","#482878","#482979","#472a7a","#472c7a","#472d7b","#472e7c","#472f7d","#46307e","#46327e","#46337f","#463480","#453581","#453781","#453882","#443983","#443a83","#443b84","#433d84","#433e85","#423f85","#424086","#424186","#414287","#414487","#404588","#404688","#3f4788","#3f4889","#3e4989","#3e4a89","#3e4c8a","#3d4d8a","#3d4e8a","#3c4f8a","#3c508b","#3b518b","#3b528b","#3a538b","#3a548c","#39558c","#39568c","#38588c","#38598c","#375a8c","#375b8d","#365c8d","#365d8d","#355e8d","#355f8d","#34608d","#34618d","#33628d","#33638d","#32648e","#32658e","#31668e","#31678e","#31688e","#30698e","#306a8e","#2f6b8e","#2f6c8e","#2e6d8e","#2e6e8e","#2e6f8e","#2d708e","#2d718e","#2c718e","#2c728e","#2c738e","#2b748e","#2b758e","#2a768e","#2a778e","#2a788e","#29798e","#297a8e","#297b8e","#287c8e","#287d8e","#277e8e","#277f8e","#27808e","#26818e","#26828e","#26828e","#25838e","#25848e","#25858e","#24868e","#24878e","#23888e","#23898e","#238a8d","#228b8d","#228c8d","#228d8d","#218e8d","#218f8d","#21908d","#21918c","#20928c","#20928c","#20938c","#1f948c","#1f958b","#1f968b","#1f978b","#1f988b","#1f998a","#1f9a8a","#1e9b8a","#1e9c89","#1e9d89","#1f9e89","#1f9f88","#1fa088","#1fa188","#1fa187","#1fa287","#20a386","#20a486","#21a585","#21a685","#22a785","#22a884","#23a983","#24aa83","#25ab82","#25ac82","#26ad81","#27ad81","#28ae80","#29af7f","#2ab07f","#2cb17e","#2db27d","#2eb37c","#2fb47c","#31b57b","#32b67a","#34b679","#35b779","#37b878","#38b977","#3aba76","#3bbb75","#3dbc74","#3fbc73","#40bd72","#42be71","#44bf70","#46c06f","#48c16e","#4ac16d","#4cc26c","#4ec36b","#50c46a","#52c569","#54c568","#56c667","#58c765","#5ac864","#5cc863","#5ec962","#60ca60","#63cb5f","#65cb5e","#67cc5c","#69cd5b","#6ccd5a","#6ece58","#70cf57","#73d056","#75d054","#77d153","#7ad151","#7cd250","#7fd34e","#81d34d","#84d44b","#86d549","#89d548","#8bd646","#8ed645","#90d743","#93d741","#95d840","#98d83e","#9bd93c","#9dd93b","#a0da39","#a2da37","#a5db36","#a8db34","#aadc32","#addc30","#b0dd2f","#b2dd2d","#b5de2b","#b8de29","#bade28","#bddf26","#c0df25","#c2df23","#c5e021","#c8e020","#cae11f","#cde11d","#d0e11c","#d2e21b","#d5e21a","#d8e219","#dae319","#dde318","#dfe318","#e2e418","#e5e419","#e7e419","#eae51a","#ece51b","#efe51c","#f1e51d","#f4e61e","#f6e620","#f8e621","#fbe723","#fde725"]
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment