Like axes in D3.js, scale bars created with the module d3-geo-scale-bar play nicely with transitions, so you can smoothly change the bar if your projection changes. In the map below, as the state changes, the scale bar selects a default distance based on the projection, but you can always set it manually with scaleBar.distance.
This example uses Noah Veltman’s lovely library flubber to smoothly transition from one state’s boundary to another.
<!DOCTYPE html>
<html>
<head>
<style>
body {
display: table;
margin: 0 auto;
}
svg {
overflow: visible;
}
.outline {
stroke: black;
fill: #eee;
}
.text {
font-family: sans-serif;
paint-order: stroke fill;
stroke: white;
stroke-linecap: round;
stroke-linejoin: round;
stroke-opacity: .9;
stroke-width: 4px;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/d3-geo-scale-bar@1"></script>
<script src="https://unpkg.com/flubber@0.3.0"></script>
<script>
let size;
const projection = d3.geoMercator();
const path = d3.geoPath(projection);
const scaleBar = d3.geoScaleBar()
.units(d3.geoScaleMiles)
.left(0)
.top(1)
.projection(projection);
const svg = d3.select("body").append("svg");
const outline = svg.append("path")
.attr("class", "outline");
const text = svg.append("text")
.attr("class", "text")
.attr("dy", 16);
const bar = svg.append("g")
.attr("transform", `translate(5, -20)`)
.append("g");
d3.json("geoJsonUsa.json")
.then(usa => {
let i = 0, state = usa.features[i], prevPath = path(state);
draw();
update();
d3.interval(_ => {
update();
}, 1500);
addEventListener("resize", draw);
function draw(){
size = Math.min(innerWidth, 500);
projection.fitSize([size, size], state);
scaleBar.size([size, size]);
svg
.attr("width", size)
.attr("height", size);
bar.call(scaleBar);
}
function update(){
i = i === usa.features.length - 1 ? 0 : i + 1;
state = usa.features[i];
projection.fitSize([size, size], state);
const currPath = path(state),
interpolator = flubber.interpolate(prevPath, currPath);
outline
.transition().duration(1000)
.attrTween("d", _ => interpolator);
text.text(state.properties.name);
bar
.transition().duration(1000)
.call(scaleBar);
prevPath = currPath;
}
});
</script>
</body>
</html>