HTML 5 Interactive Map Using SVG Path and Polygon

Update #1 (02/25/2013)

Lately, I discovered that the sample code HTML5 - Interactive Map using SVG Path/Data was no longer working correctly in Firefox 19.0. The mouseover event was totally off. Initially, I thought simply updating the KineticJS library will solve the issue but that was not the case. The reason was that some of the properties/methods were being removed and replaced in kinetic-v4.3.3. For instance, Layer.draw() is now Layer.drawScene(),setAlpha() is being replaced with setOpacity(), color name is no longer valid and we should use HTML color code, and etc. In kinetic-v3.9.7, we can set the text background color through the Kinetic. Text constructor. But in the latest KineticJS library, we have to group the Rectangle and Text object to create the text with the background color. I have updated the source code to use the latest library and everyone is welcome to download it.


Recently I was revisiting the article Silverlight - Creating Image Map with Hotspots that I wrote a couple of years ago using Silverlight and Expression Blend 3.0. I decided to redo it using HTML5. This brief tutorial demonstrates how to implement the interactive map using HTML5, KineticJS, and jQuery.

Getting Started

Shown in figure 1 are the files in the sample project. The images folder contains all the flag images and InteractiveMapData.js file contains all the map information. InteractiveMapPath.htm and InteractiveMapPoint.htm include a sample interactive map using the SVG path and point respectively. KineticJS is an HTML5 Canvas JavaScript library that extends the 2d context by enabling high-performance path detection and pixel detection for desktop and mobile applications.

How to create an interactive image?

You can use Expression Blend to create the SVG path; please refer to the article Silverlight - Creating Image Map with Hotspots for more information. In the InteractiveMapPath.htm example, I reused the path drawings from the previous tutorial. If you look at it closely the fill and stroke are a little bit off compared to the other example. I'm very positive this wouldn't happen if I redraw the path again. There are several free tools out there you can use to draw the path data but the one I found the most productive one is from the aviary. Here is a brief tutorial on how to use it to generate and utilize the path data:
  1. Click on Start a new Phoenix link creation under Launch Phoenix image.
  2. A new window will open, click on the Load an image file link.
  3. You can browse for the image on your PC or provide an absolute link to the picture and hit the upload button.
  4. Right-click the newly created layer and select "Push layer to raven":
  6. It will bring up another window with a pen tool similar to figure 3:
  7. Select the pen tool (highlighted in yellow) and start to draw the path on the image.
  8. When you are done drawing the path on the image, click on File, Export, Export to SVG, OK, save the file.
  9. You can open the .svg file using Notepad or other HTML editor to view and copy the path data.
Another option to create an interactive map/image is to use an SVG polygon. My favorite is the Online Image Map Editor because this one has an option to zoom in and it is very helpful when drawing a polygon on a small area.

How to add events to the image path?

We will use a KineticJS JavaScript library to hook up the event listeners to the shapes on the images. You can find many good examples of how to utilize the library from the HTML5 Canvas Tutorials. I will briefly go through the implementation. Both implementations in the attached example are about the same. The main difference is that one uses Kinetic.Polygon() and the other uses the Kinetic.Path() constructor.
Listing 1
  1. var path = new Kinetic.Path({  
  2.      data: c,  
  3.      fill: '#fff',  
  4.      stroke: '#555',  
  5.      strokeWidth: .5  
  6.  }); var shape = new Kinetic.Polygon({  
  7.      points: points,  
  8.      fill: '#fff',  
  9.      stroke: '#555',  
  10.      strokeWidth: .5  
  11.  }); 
The div elements inside the body tag are used to hold the image map and the menu. Initially, I was planning to use the HTML5 context menu but decided to drop it because the menu element wasn't supported by many browsers. I ended up using the jQuery to create the menu when the mouse is clicked.
Listing 2
  1. <div id="container">  
  2.     </div>  
  3.     <div id="contextMenu" style="display: none">  
  4.         <div id="contextMenuH">  
  5.         </div>  
  6.         <div id="contextMenuB">  
  7.    </div>  
  8. </div> 
Shown in listing 3 are the mouse events associated with each shape. The JavaScript is very straight forward. Here is a brief explanation of the events. The color will change from white to green when the mouse hovers over the shape. You can change it to your favorite color. On mouse out of the shape, set the color back to white and hide the tooltip. The tooltip with the country name will appear when moving the mouse along the shape. There is also logic to hide the menu when moving the mouse from one shape to another shape. And last but not least the mouse click event. We are using jQuery to position and generate the menu. The menu is composed of the country name, flag image and several links related to the selected country.
Listing 3
  1. *///mouseover the country  
  2. shape.on("mouseover", function () {  
  3.     this.setFill('#008000');  
  4.     this.setOpacity(0.5);  
  5.     shapesLayer.drawScene();  
  6. });  
  8. //mouseout the country  
  9. shape.on("mouseout", function () {  
  10.     this.setFill('#fff');  
  11.     shapesLayer.drawScene();  
  12.     tooltip.hide();  
  13.     tooltipLayer.drawScene();  
  14. });  
  16. //mousemove around the country path  
  17. shape.on("mousemove", function () {  
  18.     var mousePos = stage.getMousePosition();  
  19.     var x = mousePos.x + 5;  
  20.     var y = mousePos.y + 10;  
  21.     drawTooltip(tooltip, x, y, k);  
  23.     //keep track of previous key  
  24.     if (previousK !== k) {  
  25.         previousK = k;  
  26.         previousSelected = this;  
  28.         //hide the menu if different country path is selected  
  29.         $("[id$='contextMenu']").css({  
  30.             display: 'none'  
  31.         });  
  32.     }  
  33. });  
  35. //onclick the country path  
  36. shape.on("mousedown", function (e) {  
  37.     $("[id$='contextMenu']").css({  
  38.         display: 'inline',  
  39.         position: 'absolute',  
  40.         top: e.pageY,  
  41.         left: e.pageX + 5,  
  42.         opacity: .8  
  43.     });  
  45.     //menu header  
  46.     $("[id$='contextMenuH']").html('');  
  48.     //flag  
  49.     $('<img />').attr('src', area.flag).appendTo($("[id$='contextMenuH']"));  
  50.     $('<span />').html(k).appendTo($("[id$='contextMenuH']"));  
  52.     //build links  
  53.     $("[id$='contextMenuB']").html(''); //clear  
  55.     //countryReports  
  56.     $('<a target="_blank"></a>')  
  57. .attr('href', ' + k + '.htm')  
  58. .html('Country Reports').appendTo($("[id$='contextMenuB']"));  
  60.     //Economy  
  61.     $('<br/><a target="_blank"></a>')  
  62. .attr('href', '' + k + '.htm').html('Economy')  
  63. .appendTo($("[id$='contextMenuB']"));  
  65.     //The world Factbook  
  66.     $('<br/><a target="_blank"></a>')  
  67. .attr('href', ' + area.abbreviation + '.html')  
  68. .html('Factbook').appendTo($("[id$='contextMenuB']"));  
  70.     //Global Statistics  
  71.     $('<br/><a target="_blank"></a>')  
  72. .attr('href', ' + k + '.aspx')  
  73. .html('Global Statistics').appendTo($("[id$='contextMenuB']"));  
  75.     //Wiki  
  76.     $('<br/><a target="_blank"></a>')  
  77. .attr('href', '' + k).html('Wiki')  
  78. .appendTo($("[id$='contextMenuB']"));  
  79. }); 

Point of Interest

Polygon or Path? I think they both behave the same and didn't make much difference. I guess that depends on the tools that we have.


I hope someone will find this information useful and make your programming job easier. If you find any bugs or disagree with the contents or want to help improve this article, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it because I might have missed some important information in this article. Please send me an email if you want to help improve this article.
Tested on: Firefox 12.0, Apple Safari 4.0.4, Google Chrome 19.0
Watch this script in action