In several recent articles on Data Visualization using a Thematic World Map with color shades assigned to countries based on their value for a specific property in a world data set I have introduced the use of d3.js library for browser based visualization using SVG in combination with the GeoJSON data format in which many geographical definitions are available, from countries and states to cities, lakes and forests. I have shown how we can easily make the map switch between different properties and how we can add some degree of interactivity (allowing countries to be selected by clicking on them and changing the rotation or horizontal / east-west translation of the map.
In this article we will continue on with interactivity. We will discuss zooming in (and then out again) in two different ways: programmatically and user initiated. We will also discuss panning (or dragging) the map (when it is larger than the view box through which we look at it. Panning in d3 is a part of zooming and we get it more or less for free.
The final topic in this article is: zooming in on all selected countries – adjusting the zoom and translation whenever the selection changes.
User Initiated Zooming and Panning
D3 comes with rich support for zooming and panning (dragging, translating) SVG elements. It is clear that users have come to expect that graphs can be moved around and zoomed in upon – to the extent that I find myself sometimes making stretching and pinching finger movements on chart in the newspaper or some magazine.
In order to make the world map zoomable. we have to do a few simple things:
- define a zoom behavior – a simple object that defines what should happen when the zoom event occurs; part of this behavior are the definition of the maximum and minimum zoom factor (how large of small can the map become) and how far the translation of the map can be done (aka how far the user can drag the map around)
- attach the zoom behavior in such a way that every object (and every pixel) you want the user to be able to zoom on and drag with is enabled
In comparison to the code discussed in the earlier articles, I have now defined the height and width of the map using constants (1). I have also defined the maximum scaling factor (2): given the resolution of the GeoJSON data it does not make sense to allow users to zoom beyond factor 5.
I make use of a trick to ensure all pixels in the map are zoom-enabled: I create a group (3) that contains all country shapes and also an invisible rectangle. The zoom behavior will be associated with the group (and be inherited by all its children). However, a group is not represented by pixels. That where the invisible rectangle comes in (4): it makes sure that every pixel on the screen is part of an actual SVG object that has zoom behavior.
With this set up in place, adding the zoom behavior is quite straightforward.
- the zoomBehavior is associated with the countriesGroup
- the zoomBehavior is defined, using 1..maxScaleFactor as the zoom range and 0,0 to mapWidth, mapHeight as the translation (or panning/fragging) range (the latter means that the entire area between 0,0 and mapWidth, mapHeight will always be covered with a bit of map. When the zoom event occurs, function handleZoom is to be invoked
- Function handleZoom will simply apply the transformation associated with the zoom event (a combination of scale and translate) to all path elements in the countriesGroup (the shapes of all countries). Note: this is where you can implement funny things like a country that does not participate in zooming)
After zooming and panning, the map could present for example this focus view on South Asia:
Note that the legend is not enlarged, nor is the H1 title element. Zoom only applied to the countriesGroup element and all is children.
Country selection and associated styling is included in zooming.
The highlighted borders are as much thicker as the map is larger. We could consider applying different CSS styles – with differing stroke-width values – for different zoom factors.
Programmatic Zoom – Buttons for zooming in and out
In addition to zooming and panning through the built in zoom event that is triggered by a user’s actions, we can also cause zoom (and translate) in a programmatic way. As a simple example, let’s add zoom-controls – buttons that allow zooming in and out (respecting the zoom range from 1..5.
This will look like this:
The code for this:
And when the button has been clicked upon twice:
The functions zoomIn() and zoomOut() that make the actual zoom happen are as simple as can be:
They make use of the zoomBehavior we defined earlier on and apply a transition to it, multiplying the existing scale factor with either 0.667 (zoom out) or 1.5 (zoom in). The zoomBehavior knows it can not scale beyond 5 or below 1 and it respects those boundaries.
Similar to programmatic zooming is panning. Using the zoomBehavior, we can easily move around our viewpoint of the map within the constraints set by the translate extent. Here I have added four buttons to move our vantage point over the map.
The code for creating these four buttons:
The code for function panMap():
The translateBy function on the zoom behavior calculates the new transform from the current one combined with the translation of the horizontal and vertical distances (one of which is always 0). This function respects the translate extent that was defined on the zoomBehavior.
Note: to reset the zoom and translation – and return the map to how it looked before we did any zooming and panning – we can invoke function reset():
The duration(800) call takes care of turning the transition from the current scale factor and translation back to the beginning into a smooth animation that lasts for 800 ms.
Zoom In on Selected Countries
A special form of programmatic zoom action is the following: when a user selects a country, zoom in on that country (shape). When multiple countries are selected, zoom in on all the country shapes;. And when a country is deselected, adjust the zoom accordingly.
This zoom is triggered whenever countries are selected and deselected, And it must take into account the rectangle that fully encloses the selected country or countries. The center of this rectangle defines the required translation target. And the size of that rectangle relative to the total map viewbox size determines the appropriate scale factor.
For example, with Namibia, Tanzania and Madagascar selected, the automatic zoom we want to implement should bring the area indicated by the rectangle in the next figure into focus.
After we are done with this particular feature, the map is zoomed in like this:
The following code will zoom (pan and scale) the map based on the selected countries:
- find all GeoJSON features for the currently selected countries
- process all features and find the lowest and highest X and Y coordinate for any of them; the bounds function on geoGenerator returns the four extremes (min and max for X and Y) for the corners of the rectangle that fully encloses the shape
- determine the scale factor: how much smaller than the entire map is the enclosing rectangle? I have decided that the scale factor cannot be larger than 4.
- construct a transform that first translates to the center, then applies the new scale factor and finally translates to the center of the rectangle
- apply the transformation to the countriesGroup
This function is to be invoked whenever the selection of countries changes. All such changes are currently taken care of by function handleCountryClick, so that is where zoomToSelectedCountries should be invoked:
In this article I have leveraged the rich support for zooming and panning in D3.js. The articles describes both user initiated zooming and panning as well as programmatic zooming and panning. By adding transitions to the transformations, we can provide the user with smooth animations that make the step between two states easier to absorb.
GitHub Repository with the code for this article.
My earlier articles on World Map data visualization:
2. Presenting the World in Data using World Map Visualization – https://technology.amis.nl/frontend/presenting-the-world-in-data-using-world-map-visualization/ – bring together a straightforward approach to data visualization (from article one) and a rich data set from Kaggle that provides many interesting details for all countries in the world, related to education, health, economy, demography, climate and more.
3. Interactive Data Visualization in World Map–Zoom, Translate, Legend – https://technology.amis.nl/tech/interactive-data-visualization-in-world-map-zoom-translate-legend/ – a closer look at adding some interactivity to the world map. In particular: changing the rotation of the map, select countries (by clicking on them), show country details popup window and show legend – color scale (mapping heatmap colors to numerical values)