I'm filtering out a subset of US counties with D3 and topojson. I can successfully filter out the counties and draw them to an SVG element, but how do I change the map bounds to fit the new selected counties? It's rendering the map as if the bounds were all US counties vs. only the filtered ones.
var width=960,
height=600,
centered;
const svgElement = d3.select(ref.current)
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", [0,0,width,height]);
var path = d3.geoPath();
d3.json("https://d3js.org/us-10m.v2.json").then((d) => {
d.objects.counties.geometries = d.objects.counties.geometries.filter(function(item) {
return props.data.indexOf(item.id) > -1
})
var counties = topojson.feature(d, d.objects.counties).features;
svgElement.append("g")
.attr("class", "filled-territory")
.selectAll("path")
.data(counties)
.enter()
.append("path")
.attr("d", path)
});
Where props.data
is an array of county IDs such as -
[
"45001",
"45003",
"45007",
"45083",
"45087",
"45091"
]
I'm able to get it to display like so, but I can't figure out how to have the path fill the SVG:
This particular file uses a coordinate system of pixels (it is projected geographic data), it fills an extent between [0,0] and [960,600]. Because it uses this coordinate system, often we can use the default projection of d3.geoPath, a null projection that treats coordinates in your json as pixels. However, the geoPath alone does not provide for a way to center or scale your features by itself.
D3 geographic projections come with a method called fitSize (also fitExtent or others) which take a geojson object and set the projection scale and translate properties to center that geojson object across the specified extent. Since we already have projected data, we can use d3.geoIdentity() which provides some convenient projection methods like fitSize but otherwise doesn't project our data as it is already projected.
In order to do use fitSize we can use the following:
// get a geojson feature collection object:
var geojson = topojson.feature(d, d.objects.counties)
// create a geoidentity "projection" and center the geojson:
var projection = d3.geoIdentity()
.fitSize([width,height],geojson)
// set the path's projection:
path.projection(projection);
// Now access the array of features in the collection for use with .data():
var counties = geojson.features;
Which looks like:
var data = [
"45001",
"45003",
"45007",
"45083",
"45087",
"45091"
]
var width=500,
height=500;
const svgElement = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geoPath();
d3.json("https://d3js.org/us-10m.v2.json").then((d) => {
d.objects.counties.geometries = d.objects.counties.geometries.filter(function(item) {
return data.indexOf(item.id) > -1
})
var geojson = topojson.feature(d, d.objects.counties)
var projection = d3.geoIdentity()
.fitSize([width,height],geojson)
path.projection(projection);
var counties = geojson.features;
svgElement.append("g")
.attr("class", "filled-territory")
.selectAll("path")
.data(counties)
.enter()
.append("path")
.attr("d", path)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js
"></script>