Skip to content

Instantly share code, notes, and snippets.

@andreasplesch
Last active September 19, 2015 13:20
Show Gist options
  • Save andreasplesch/49b2130b15425c1eebc0 to your computer and use it in GitHub Desktop.
Save andreasplesch/49b2130b15425c1eebc0 to your computer and use it in GitHub Desktop.
d3 color scale from ncar ncl color table

I am interested in an easy way to generate a color table with a defined number of colors for use with this:

http://andreasplesch.github.io/x3dom/GeoElevationGrid_texture/terrain_spheresensor.xhtml

Here is a D3 way to use the many color maps NCAR's NCL provides. The original source file is read, so all of the parsing and then color scale (mapping) work takes place in the callback. The original tables directly map to D3 linear scales with a segmented definition. Hcl interpolation looks better for oversampling. The resampling is then simply an application of a new input domain to the piecewise color scale function. Since I use table cells to draw the color tables, there is a somewhat clever function to keep the table at a constant total width by inserting an extra width pixel every so many columns.

Next may be to offer all the color tables in some way (dropdown first) and let a user pick one to resample.

View live: http://bl.ocks.org/andreasplesch/49b2130b15425c1eebc0

<html>
<style>
table {
border-spacing: 0px;
border: 1px solid grey;
margin: 20px;
}
td {
padding-top: 40;
padding-left: 0;
border: 0px;
}
body {
font-family: monospace;
font-size: 14;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<h1>color table topo_15lev.rgb</h1>
original table
<table><tr id='originalmap'></tr></table>
<div>
Resample for a new table with the desired number of colors:
</div>
<label for='ncol_input'>number of colors:</label>
<input type='number' id='ncol_input' min='2' max='100' value='5' oninput='updateTable(value)'/>
<div>
Resampled color table:
</div>
<table><tr id='sampledmap'></tr></table>
As 1d pixel texture:
<div>
<span id='texheader'></span>
<span id='samplelist'></span>
</div>
<script>
var bar_width=500;
var cbar1 = d3.select('#originalmap');
var cbar2 = d3.select('#sampledmap');
var hexlist = d3.select('#samplelist');
var hexheader = d3.select('#texheader');
var samples = d3.select('#ncol_input').node().value;
var cscale;
// read file
d3.text('topo_15lev.rgb.dat', function (error, d) {
console.log('error: ' + error);
var ssv = d3.dsv(" ", "text/plain");
var cmap = ssv.parseRows(d, function (row, index) {
//always ignore first two rows
if (index <2) {return null;}
//remove empty elements;
row = row.filter(function(d){return d !== "";});
//console.log('row: ' + row);
//expect exactly three values for r,g,b
if (row.length !== 3) {return null;}
//check if all are numbers
if (row.some(function(d){return isNaN(d * 0);})) {return null;}
//console.log('row: ' + row);
//check if fractional or decimal, larger 1 must be decimal
if (row.some(function(d){return d > 1 ;})) {return arr2col(row);}
//use dot to decide corner cases; if dot fractional
if (row.some(function(d){return d.indexOf(".") !== -1 ;})){return arr2col(row, 255);}
//should be decimal
return arr2col(row);
});
//construct scale
//1/length stride leads to floating point precision issues
//go back to stride of 1
var crange1 = d3.range(0, cmap.length);
//var crange2 = d3.range(0, cmap.length, cmap.length/samples);
var crange2 = d3.range(0, samples).map(function(d){return d * cmap.length/samples;});
cscale = d3.scale.linear().domain(crange1).range(cmap).interpolate(d3.interpolateHcl);
//populate tables
updateCbar(cbar1, crange1, bar_width/cmap.length);
updateCbar(cbar2, crange2, bar_width/samples);
updateHexList(hexheader, hexlist, crange2);
});
function updateTable(samples) {
var cmaplength = cscale.range().length;
var crange2 = d3.range(0, samples).map(function(d){return d * cmaplength/samples;});
updateCbar(cbar2, crange2, bar_width/samples);
updateHexList(hexheader, hexlist, crange2);
}
function arr2col(arr, mul) {
if (mul == undefined) {mul = 1;}
return d3.rgb(arr[0] * mul, arr[1] * mul, arr[2] * mul);
}
function adjust_cell_size (position, cell_size) {
//position: column index of cell
//cell_size: average cell_size
//px_cell_size: truncated cell_size in pixels
//cell_gap: fractional gap to average target cell size
//adjust width by 1 in positions which need to even out total width
var px_cell_size = Math.floor(cell_size);
var cell_gap = cell_size - px_cell_size;
return ((position+1) * cell_gap)%1 < cell_gap ? px_cell_size+1 : px_cell_size;
};
function updateCbar (cbar, range, cell_size) {
var cells = cbar.selectAll('td').data(range)
.style({
'background-color': function(d) {return cscale(d);},
'padding-right': function(d, i) {
return adjust_cell_size(i, cell_size);}});
cells.enter().append('td')
.style({
'background-color': function(d) {return cscale(d);},
'padding-right': function(d, i) {
return adjust_cell_size(i, cell_size);}});
cells.exit().remove();
}
function updateHexList(header, container, range) {
header.text(range.length + " 1 3");
var spans = container.selectAll('span').data(range)
.text(function(d){return " " + cscale(d);});
spans.enter().append('span').text(function(d){return " " + cscale(d);});
spans.exit().remove();
}
</script>
</body>
</html>
# Converted from MeteoSwiss NCL library
# number of colors in table
ncolors = 16
# r g b
40 54 154
0 201 50
30 211 104
94 224 116
162 235 130
223 248 146
246 229 149
200 178 118
162 126 94
143 97 84
162 125 116
178 150 139
199 176 170
219 205 202
236 228 226
255 255 255
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment