Reputation: 111
I'm building map with d3js. I can render a USA state map from this geojson file easily, and plot point data over it as needed (not shown). But I would like to clip the visualization using a bounding box of long/lat values.
I'm not even sure at what stage or on what object to do the clipping, generally. There is some minimalistic documentation about methods for clipping projections on the d3 API reference (for instance here). I am uncertain if clipping the projection would be helpful here.
Image of my map and the map output I would like - from Wikle et al. 2019 - are attached. My HTML skeleton is minimal, it calls up the js script, and provides a division for the svg ("svg1").
The code at the end of this message generates this map:
Filtering out Alaska and Hawaii is not a problem and not part of this question. I would like an image like the following (please ignore the point data, I am only curious about clipping the state paths down to size):
Here is my js code:
window.addEventListener("load", main)
function main(){
// base svg and svg props
const width = 400, height = 400;
let facetWidth = width;
let facetHeight = height;
let svg1 = d3.select("#plot").append('svg')
.style("width", width)
.style("height", height)
.attr("id", "svg1");
// projection and generator
let projection = d3.geoAlbersUsa();
let geoGenerator = d3.geoPath().projection(projection);
// state drawing function:
function renderStates(data) {
projection.fitSize([facetWidth, facetHeight], data);
svg1.append('g')
.attr('id','statePaths')
.attr('id','statePaths')
.selectAll('path')
.data(data.features)
.join('path')
.attr('d', geoGenerator)
.attr('fill', '#ffffff')
.attr('stroke', '#000')
};
Any advice would be appreciated. I am still very new to rendering maps with d3js.
Upvotes: 3
Views: 706
Reputation: 111
I'll post the answer to my own question, in case it can be useful, as there doesn't seem to be a lot of explicit examples for clipping with d3js outside of the observables community. Their examples are a bit specialized to their platform (though they are a great help!).
To clip the above down to an area of interest, I used the projection.postclip
function, using d3.v7. My code is now too large to include here, but the following is the general pattern of use:
// set the standard d3js projection stuff:
let projection = d3.geoEquirectangular()
.translate([width/2, height/2])
.scale(1000)
.center([-89.04423584210527, 38.66203010526314])
;
// get your geographic coordinates that you would like to use as a bounding box.
// In my case I am grabbing them from array called `stations`
// using d3 accessor functions:
let xMinLL = d3.min(stations, d => +d.lon);
let yMinLL = d3.min(stations, d => +d.lat);
let xMaxLL = d3.max(stations, d => +d.lon);
let yMaxLL = d3.max(stations, d => +d.lat);
// assign our pixel coordinates. Remember that y is flipped, up is down.
[xMinPix, yMaxPix] = projection([xMinLL, yMinLL]);
[xMaxPix, yMinPix] = projection([xMaxLL, yMaxLL]);
// update your projection with the pixel coordinates:
const buff = 5;
projection.postclip(d3.geoClipRectangle(xMinPix - buff,
yMinPix - buff,
xMaxPix + buff,
yMaxPix + buff));
Upvotes: 3