I am seeking help with creating a map scale bar for use with a D3.js zoomable map. I often create maps for my job using D3.js. I never placed a scale bar on the maps I built in the past but I recently gotten requests to include a scale bar on the maps. I searched the web for some type of example to build off of, but I couldn't find anything. I toyed around with d3.scale.linear() but I'm unable to create anything useful.
What I'm trying to create is a map scale bar that adjust it's numbers accordingly when zooming in and out with d3.behavior.zoom(). Any help with this issue (a working model or something to build off of) will greatly be appreciated. Thank you in advance.
I made a D3.js plugin called d3-geo-scale-bar that should accomplish what you're looking for.
Here's the GitHub repo: https://github.com/HarryStevens/d3-geo-scale-bar
Here's a basic demo: https://bl.ocks.org/HarryStevens/8c8d3a489aa1372e14b8084f94b32464
And here's a demo with pan and zoom: https://bl.ocks.org/HarryStevens/64fc5f1a4489abe78433b7d19510f864
Here's an Observable notebook demo: https://observablehq.com/d/75249011244aafd6
There are several issues you need to address in a web scale bar:
I designed a scale for a map a while back that used the formula here for physical distance between to geographic coordinates. But what two points do you use to measure distance? I used two points that when connected formed a line through the center of the map frame (horizontally and not to the edges for a landscape oriented map) the scale near the center of the map is likely most representative of the map scale as a whole. To get the geographic location of a point using svg coordinates, use
projection.invert([x,y]) to get your longitude and latitude.
With the two points, you should then be able to calculate physical distance and svg coordinate distance, giving you the information needed for a scale. This might look like:
var scaleWidth = width / 4; var p1 = projection.invert([width/2 - scaleWidth/2, height / 2]); var p2 = projection.invert([width/2 + scaleWidth/2, height / 2]); var geoDistance = getDistance(p1,p2);
getDistance is a function that returns the great circle distance between two geographic points, and the geographic distance this function returns is represented by
width/4 pixels on the map. Now, for the simplest scale, you can draw a rectangle
width/4 pixels wide and label it as being the distance returned by
Note that if you choose the same two svg points every time as your reference points for the scale, the scale bar may show a un-round length. So, you'll need to modify the distance until you get a sufficiently round number, and then draw the scale bar in a corner (unless a scale bar 1.954 km wide is ok, this will be faster too).
To zoom, just redraw (and recalculate the distance between two central points, zoom factor will not help you in this) and/or transition the scale to represent the new physical distance between the reference points (recalculating them as needed to make a round number).
Here's a bl.ock of the scale in action, and an image of it below:
©2020 All rights reserved.