Introduction
As web application technologies progress, more and more interactive elements are being presented to the user. A subset of this intractability within the browser includes SVG elements. Interactive SVG elements can be leveraged to help support a wide spectrum of features, but in this article, we are going to focus on an interactive SVG map example.
We’re going to implement an SVG map using two open-source libraries, SVG.js and Tippy.js. SVG.js is a lightweight library for manipulating and animating SVGs, and Tippy.js is a library supported by Popper.js that helps create comprehensive tooltips, popovers, and dropdowns. We are first going to establish the interactive SVG’s foundation with SVG.js, then use Tippy.js to help display data dynamically.
Initial Considerations and Prerequisites
Before we start to discuss SVG.js and implementing the foundation for our SVG interactive map, let’s discuss some initial considerations and prerequisites for this blog post:
Example CodePen
This entire post will reference a central CodePen which represents an example of SVG.js working in conjunction with Tippy.js to show information on Thailand’s provinces and their respective populations. You can view the embedded CodePen example or access it via this link. The CodePen example is authored by me; feel free to fork and/or share!
See the Pen
SVG.js + Tippy.js Example w/Thailand by Kyle Justice (@kylejustice)
on CodePen.
Establish an SVG Map and Data Source
When implementing an interactive SVG, we first need to define [or source] our SVG file which will represent the interactive elements. We won’t expand on creating SVGs in this blog, but there are many open-source SVG files available on the web. Wikimedia is one example that has SVG maps with a varying degree of licenses for use, but many are fully available to the public domain.
With initializing an SVG, at least with our CodePen example, we’ve placed IDs and Class names on each path that represents a province within Thailand. Each ID represents the province’s respective FIPS region code and the class is used to apply styling. For example:
<path id="TH62" class="svg-map__province" data-province-name="Phuket" xmlns="http://www.w3.org/2000/svg" d="{...}"></path>
To align with each individual province SVG path, we have a corresponding dataset contained in one JSON file. Each SVG path’s FIPS region ID correlates to an object containing the province’s data. For example, Phuket’s [TH62] JSON data would appear as:
"TH62": { "name": "Phuket", "nameThai": "ภูเก็ต", "population": 416582, "populationDensity": 762 },
There are all sorts of interesting sample JSON data sets available for open source use on the internet that could be utilized for additional SVG interactive implementations. For example, a listing of all airports, states of Australia, etc. Although, in a real-world example, it would be more likely to integrate with an API that provides the supporting dynamic data for the interactive SVG component if one of its values is to act as a medium for data.
Accessibility Considerations
Before implementing any features for web applications, it is first and foremost important to consider Accessibility first. Unfortunately, interactive SVG elements [particularly maps] provide some obstacles with regards to accessibility.
The keyboard tabbing order is likely the most critical challenge. If keyboard tabbing was allowed, it would likely be unclear to the user what the order of the different SVG elements is. Specifically, with the Thailand map example: which province would be tabbed to first? Second? And, last? Geography is usually chaotic, and ordering items on a map would likely be arbitrary. Not only would keyboard accessibility be challenging, but the screen-reader would likely be even more confusing. Not only would the ordering of items be questionable, but describing shapes and spacial relationships would also be near-impossible.
With this in mind, it is a better practice to provide an accessible alternative that is more straightforward to keyboard and screen-reader users, while hiding the interactive SVG elements from them. For example: present a search input field [or dropdown] before the interactive SVG and ensure the SVG elements are removed from the tabbing order and hidden from screen-readers.
Additionally, in another blog post, I talk about Accessibility in conjunction with Google Maps and providing an accessible alternative. If interested, feel free to read that as well!
Note: the CodePen example of Thailand does not provide an accessible alternative as it is more focused on the core example. But, when deploying to production, I highly recommend an accessible alternative to be present to support best practices.
Utilizing SVG.js
As we now have an SVG and corresponding data, we can now start to implement the foundation for the interactive SVG by initializing SVG.js. To include the library, the SVG.js Getting Started page is a great reference.
Initializing the Interactive SVG
We can create a new SVG.Svg document by calling SVG(). It is possible to reference already existing DOM elements, but in our CodePen example, we’re just getting the map of Thailand via an axios call and calling SVG() with the given SVG map markup:
Promise.all([axios.get(SVG_MAP_URL), axios.get(SVG_MAP_DATA_URL)]).then( function (results) { const svgResults = results[0]; let svgMap = svgResults.data; const svgDraw = SVG(svgMap).size("100%", "100%").addTo(SVG_MAP_ROOT_ID);
Additionally, in the above code example you’ll also notice we’re calling .size("100%", "100%")
and .addTo(SVG_MAP_ROOT_ID)
.
The size() method allows us to set the size of the element with a given width and height. With the Thailand example, we’re simply setting width and height to “100%” and allowing the CSS to maintain the width.
The addTo() method allows us to inject the interactive SVG into the DOM. This can be accomplished via different selectors, but in our example, we’re just using a unique root ID where we’re wanting to host the interactive SVG map of Thailand.
Initializing the SVG Paths
We’ve initialized the base Interactive SVG map and placed it within the DOM. But, now we need to iterate through each individual SVG path representing the respective Thai province to style them and define event handlers.
SVG Paths Iterating
To note, in the CodePen example, alongside using an API call to GET our SVG map of Thailand, we’re also making a concurrent GET to gather our JSON data for each province in Thailand (stored in svgData).
To iterate through each SVG path and gather the critical variables, we can perform via:
for (const province of svgDraw.find("path")) { let provinceId = province.attr("id"); let provinceData = svgData[provinceId];
SVG.js has a method find() which can accept a selector and return all matching SVG elements. We’re simply using the selector “path” and a simple for loop to iterate through each one.
Next, we can use SVG.js’s attr() method [as a getter] to return a given attribute value. In this case, we’re utilizing it to obtain the ID for each respective province. Given the ID, we’re then obtaining the province’s JSON data with the previously fetched data (svgData).
SVG Paths Styling
Additionally, we’re going to style each province’s SVG path respective to the degree of population density. The more densely populated provinces will be a darker color, while the least dense provinces will be a lighter color. We won’t go through each line, but there is a method within the CodePen example named getProvinceDensityIndex() that returns an index representing the province’s density group given its population density.
Using the province’s population density total and the defined method getProvinceDensityIndex(), we can construct the CSS class and add it to the SVG path:
let provinceDensityIndex = getProvinceDensityIndex( provinceData.populationDensity ); province.addClass(`svg-map__province--density-${provinceDensityIndex}`);
The SVG.js method addClass() allows us to pass a class name string to be added to the SVG path. In our Thailand CodePen example, the CSS class constructed has a modifier at the end representing the index of the province’s density group which each have distinct colors. Feel free to view the SCSS file within the CodePen example to see how the classes are being dynamically defined.
SVG Paths Click Handler
As we’ve just styled the SVG path we’re currently iterating on, we can now initialize the click handler. Upon a click event, our click handler will add a CSS class denoting an active province and remove the previously active province’s class accordingly:
var onClick = function () { let clickedProvinceId = this.attr("id"); if (currentProvinceId !== clickedProvinceId) { if (currentProvinceId) { let currentProvince = svgDraw.find(`#${currentProvinceId}`); if (currentProvince.hasClass(SVG_PATH_ACTIVE_CLASS)) { currentProvince.removeClass(SVG_PATH_ACTIVE_CLASS); } } this.addClass(SVG_PATH_ACTIVE_CLASS); } currentProvinceId = clickedProvinceId; }; province.on("click", onClick);
To listen for click events, we’re utilizing SVG.js’s on() method, leveraging on ‘click’. Although, an alternative could also be the click() method. Within, we have the context of this, representing the clicked on Thai province SVG path.
We have an earlier defined variable currentProvinceId
which holds the value of the currently selected province. In the above code, we’re comparing the currently selected province ID with the newly clicked province ID to remove/add the active CSS classes accordingly. The SVG.js methods addClass(), removeClass(), and hasClass() can be used to easily perform class operations by simply passing a string that represents the target class name.
And, finally, in the end, we’re ensuring the currentProvinceId
variable is updated to reflect the newly clicked Thai province.
Interesting Features
We’ve only utilized the basics of SVG.js, but there are more advanced topics and interesting features that can be used in a more comprehensive interactive SVG JavaScript implementation. While there are many interesting advanced topics for SVG.js, let’s briefly discuss Animations alongside Panning & Zooming:
Animating
It is possible to animate elements with regards to SVG.js. The method animate(), given parameters such as duration, delay, and when is able to then animate attributes passed via attr(). There are also additional methods that can be chained with animate() such as dmove(), rotate(), skew(), and others.
Easing is also possible with the ease() method. And, there’s even an open-source plugin that supports more easing equations called svg.easing.js.
These are only a small sample size of mentioned items for SVG animation. Feel free to review the documentation further and give animating a go!
Panning & Zooming
If your interactive SVG has smaller path elements and/or is required to be used on a mobile device, implementing panning & zooming is likely optimal. Fortunately, there is an open-source plugin that can add panning & zooming to an SVG.js instance called svg.panzoom.js.
In this plugin, the panZoom() method can be used from the SVG.js instance to initialize the panning & zooming behavior. Options can also be passed within this method call that customizes specific panning & zooming behavior.
Some notable options include panning, oneFingerPan, zoomMin, zoomMax. panning can toggle whether or not the user can pan the interactive SVG. oneFingerPan is also used to determine whether or not a touch device user can pan the interactive SVG with only one finger (typical experience is to use two fingers to pan). If click events are used, it is recommended to disable the oneFingerPan option, as this feature will obstruct the click capabilities on touch devices.
zoomMin and zoomMax are also quite helpful in maintaining a degree of minimum and maximum amount of zoom, respectfully. Without these two options, by default, the user can zoom out so far to where the interactive SVG is not visible anymore. Likewise, the user would be able to zoom in at such a high value to where they cannot tell where on the interactive SVG they are looking.
Displaying Data with Tippy.js
Now that our base interactive SVG is initialized, alongside event listeners that persist and reflect the currently selected Thai province, let’s show some data!
In our CodePen example, we’re going to use Tippy.js as a tool to show the population and population density information to the user when they click on a province. Tippy.js is a library that makes it more straightforward in implementing tooltips, popovers, dropdowns, etc.
Initializing the Tippy Instances
For each individual province of Thailand within the SVG map, we’re going to instantiate one Tippy.js instance. To do so, we can use Tippy.js’s tippy() constructor method. Additionally, let’s define a method called getTippyInstance()
where given the selected province’s ID and data object, we return the Tippy.js instance:
const getTippyInstance = (provinceId, provinceData) => { const getFormattedNum = (num) => { return Number.parseFloat(num).toLocaleString("en-US"); }; const tippyContent = `<see CodePen for markup>`; return tippy(`#${provinceId}`, { allowHTML: true, appendTo: document.body, content: tippyContent, duration: 0, followCursor: "initial", interactive: true, plugins: [followCursor], trigger: "manual" }); };
Feel free to refer to the Tippy.js documentation on the different options we’re passing. But, note that we’re having the Tippy.js object target the element ID corresponding to the province’s SVG path element.
Notably within the options, followCursor is set to “initial” alongside its additional plugin [followCursor]. This allows us to position the Tippy.js tooltip exactly where the user clicked on the SVG map. Without this, we would need to define some comprehensive logic to know where to position the tooltip. Some edge cases could require a high LOE if we don’t position where the click occurs. For example: consider the province of Trat. If we just had logic that positioned the tooltip in the center of the province, it might be valid for almost every province. But, Trat’s landmass is concentrated in the north with its islands scattered to the south (Ko Chang is beautiful by the way! 🌴). If we simply positioned the tooltip in the center, it would likely be positioned in the ocean.

We are also setting the interactive flag to true. This allows the user to interact with the content inside and potentially highlight/copy any textual data they’d like.
Additionally, we’re going to manually manage showing and hiding the Tippy.js instances. Thus, we’re setting trigger to “manual“.
With our getTippyInstance()
method, we then just call it within the for loop iterating through the SVG paths:
let svgTippyInstances = {}; for (const province of svgDraw.find("path")) { // ... svgTippyInstances[provinceId] = getTippyInstance(provinceId, provinceData);
Showing and Hiding the Tippy Instances
Our Tippy.js instances are initialized and stored within svgTippyInstances
. We’ve set the trigger option to “manual“, thus, now we can manage showing and hiding the tooltips within our click event handler:
var onClick = function () { let clickedProvinceId = this.attr("id"); // ... if (currentProvinceId) { svgTippyInstances[currentProvinceId][0].hide(); } svgTippyInstances[clickedProvinceId][0].show(); currentProvinceId = clickedProvinceId; }; province.on("click", onClick);
In our logic, we’re hiding the currently selected province’s Tippy.js instance, showing the clicked province’s Tippy.js instance. Then, ensuring the “currently selected” province ID is updated to the clicked province’s ID.
Tippy.js has show() and hide() methods that can respectively show and hide the Tippy tooltip instances. It’s as simple as that!
Interesting Features
We’ve only discussed a small sample size of functionality that Tippy.js offers. There are more features available, such as animations, built-in themes, AJAX tools, etc. Additionally, there are a myriad of plugins available for Tippy.js.
If you’re interested in Tippy.js, I highly encourage you to explore!
Conclusion
Implementing an interactive SVG can sound intimidating, but is somewhat straightforward when leveraging SVG.js. And, when showing data, Tippy.js can quickly be utilized to spin up and display tooltips.
And, from a use-case perspective, interactive SVG elements are likely most commonly utilized to represent geographical maps, but they aren’t limited to just that use. Interactive SVGs can also be used for data visualization, games, diagrams, etc. So feel free to get creative!
Safe journeys, and cheers!
Leave a Reply