Uses a custom d3 projection to dislpay contours generated for a 2D array of values
defined on grid with coordinates xgrid
and ygrid
.
Last active
September 15, 2017 20:38
-
-
Save grahampullan/20db83b0170b052b64d717152501f57d to your computer and use it in GitHub Desktop.
d3 contour on an arbitrary structured grid
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<svg width="500" height="500" stroke="#fff" stroke-width="1."></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/d3-contour.v1.min.js"></script> | |
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<script> | |
// Structured (n * m) grid of data. Point coordinates are (xgrid, ygrid) | |
var n = 101, m = 101; | |
var values = new Array(n * m); | |
var xgrid = new Array(n * m); | |
var ygrid = new Array(n * m); | |
// Populate plot data | |
for (var j = 0., k = 0; j < m; ++j) { | |
for (var i = 0.; i < n; ++i, ++k) { | |
xgrid[k] = i; | |
ygrid[k] = 25*Math.sin(i*Math.PI/50) + j; | |
values[k] = Math.pow(xgrid[k]-50,2) + Math.pow(ygrid[k]-50,2); | |
} | |
} | |
var svg = d3.select("svg"), | |
width = +svg.attr("width"), | |
height = +svg.attr("height"); | |
// set x and y scale to maintain 1:1 aspect ratio | |
var domainAspectRatio = (d3.max(ygrid)-d3.min(ygrid))/(d3.max(xgrid)-d3.min(xgrid)); | |
var rangeAspectRatio = height / width; | |
if (rangeAspectRatio > domainAspectRatio) { | |
var xscale = d3.scaleLinear() | |
.domain(d3.extent(xgrid)) | |
.range([0,width]); | |
var yscale = d3.scaleLinear() | |
.domain(d3.extent(ygrid)) | |
.range([domainAspectRatio*width,0]); | |
} else { | |
var xscale = d3.scaleLinear() | |
.domain(d3.extent(xgrid)) | |
.range([0,height/domainAspectRatio]); | |
var yscale = d3.scaleLinear() | |
.domain(d3.extent(ygrid)) | |
.range([height,0]); | |
} | |
// configure a projection to map the contour coordinates returned by | |
// d3.contours (px,py) to the input data (xgrid,ygrid) | |
var projection = d3.geoTransform({ | |
point: function(px, py) { | |
var xfrac, yfrac, xnow, ynow; | |
var xidx, yidx, idx0, idx1, idx2, idx3; | |
// remove the 0.5 offset that comes from d3-contour | |
px=px-0.5; | |
py=py-0.5; | |
// clamp to the limits of the xgrid and ygrid arrays (removes "bevelling" from outer perimeter of contours) | |
if ( px < 0) { px = 0;} // px < 0 ? px = 0 : px; | |
if ( py < 0) { py = 0;} // py < 0 ? py = 0 : py; | |
if ( px > (n-1) ) { px = n-1; } // px > (n-1) ? px = n-1 : px; | |
if ( py > (m-1) ) { py = m-1; } // py > (m-1) ? py = m-1 : py; | |
// xidx and yidx are the array indices of the "bottom left" corner | |
// of the cell in which the point (px,py) resides | |
xidx = Math.floor(px); | |
yidx = Math.floor(py); | |
if ( xidx == (n-1) ) { xidx = n-2; } // xidx == (n-1) ? xidx = n-2 : xidx; | |
if ( yidx == (m-1) ) { yidx = m-2; } // yidx == (m-1) ? yidx = m-2 : yidx; | |
// xfrac and yfrac give the coordinates, between 0 and 1, | |
// of the point within the cell | |
xfrac = px-xidx; | |
yfrac = py-yidx; | |
// indices of the 4 corners of the cell | |
idx0 = xidx + yidx*n; | |
idx1 = idx0 + 1; | |
idx2 = idx0 + n; | |
idx3 = idx2 + 1; | |
// bilinear interpolation to find projected coordinates (xnow,ynow) | |
// of the current contour coordinate | |
xnow = (1-xfrac)*(1-yfrac)*xgrid[idx0] + xfrac*(1-yfrac)*xgrid[idx1] + yfrac*(1-xfrac)*xgrid[idx2] + xfrac*yfrac*xgrid[idx3]; | |
ynow = (1-xfrac)*(1-yfrac)*ygrid[idx0] + xfrac*(1-yfrac)*ygrid[idx1] + yfrac*(1-xfrac)*ygrid[idx2] + xfrac*yfrac*ygrid[idx3]; | |
this.stream.point(xscale(xnow), yscale(ynow)); | |
} | |
}); | |
// array of threshold values | |
var thresholds = d3.range(d3.min(values),d3.max(values),(d3.max(values)- d3.min(values))/21); | |
// color scale | |
var color = d3.scaleLinear() | |
.domain(d3.extent(thresholds)) | |
.interpolate(function() { return d3.interpolateRdBu; }); | |
// initialise contours | |
var contours = d3.contours() | |
.size([n, m]) | |
.smooth(true) | |
.thresholds(thresholds); | |
// make and project the contours | |
svg.selectAll("path") | |
.data(contours(values)) | |
.enter().append("path") | |
.attr("d", d3.geoPath(projection)) | |
.attr("fill", function(d) { return color(d.value); }); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment