Using RaphaelJS To Create A Map
The brief was actually quite simple. Create an interactive map of the United Kingdom split into regions that when hovered would show a small pop-up containing the name of the region, and when clicked would take you to a specific page. In this case it was to be a term archive page for a specific taxonomy inside WordPress, but it could really be anything (within reason).
I have received permission from the client I did the work for to use the map in this tutorial so here is a demo to show you what the finished product looks like.
Happy? Good. Now let’s start with what could be the most important part of this tutorial.
Prerequisites
You need to grab yourself an image of the map, item, or whatever it is you want to create with RaphaelJS. Not just any image though, it must be an SVG or a Scalable Vector Graphics file. For those that haven’t heard of an SVG before, it is basically an image that is created using Mathematical equations meaning it can be scaled to any size without degradation since all the computer has to do is recalculate the results to the equations.
This can be exceptionally confusing to some so here is a visual example of how vector graphics, like SVG, differ from raster graphics, like JPG or Bitmap.
As you can see, when you scale up a raster image you start to lose detail & the image starts to degrade quite badly. However the vector image stays extremely detailed and doesn’t degrade at all.
I can’t share the image I used to make the map, but there are a lot of SVG maps & other SVG images available from Stock Photo sites such as IStockPhoto.com.
HTML
Now we can move on to some actual code. First the HTML used.
1 2 |
<div id="map"> </div> |
Ahem. Yep, that’s it. No other HTML code is needed. The rest is all RaphaelJS.
RaphaelJS
First thing, we need to make sure jQuery is attached to the page. RaphaelJS doesn’t need jQuery, but it does help a lot to have the jQuery core functions available when using Raphael. I always use the Google CDN but you can attach it by any method you like. You also, obviously, need RaphaelJS attached to your page. You can download it from the RaphaelJS website. You will also need a small patch/plugin for RaphaelJS that creates a Pop-Up using vector graphics. I can’t remember where I found this but here is the code:
1 2 |
Raphael.fn.popup=function(x,y,text,size){txtattr={font:"12px Helvetica, Arial, sans-serif"} size=size||5;text=text||"";var res=this.set(),d=3;res.push(this.path().attr({fill:"#333333",stroke:"#333333"}));res.push(this.text(x,y,text).attr(txtattr).attr({fill:"#fff","font-family":"Helvetica, Arial"}));res.update=function(X,Y,WIDTH){X=X||x;Y=Y||y;var mmax=Math.max,mmin=Math.min,bb=this[1].getBBox(),w=bb.width/2,h=bb.height/2,dir=(X-bb.width<15)?3:1,X=(dir==1)?X:X+WIDTH,dx=[0,w+size*2,0,-w-size*2],dy=[-h*2-size*3,-h-size,0,-h-size],p=["M",X-dx[dir],Y-dy[dir],"l",-size,(dir==2)*-size,-mmax(w-size,0),0,"a",size,size,0,0,1,-size,-size,"l",0,-mmax(h-size,0),(dir==3)*-size,-size,(dir==3)*size,-size,0,-mmax(h-size,0),"a",size,size,0,0,1,size,-size,"l",mmax(w-size,0),0,size,!dir*-size,size,!dir*size,mmax(w-size,0),0,"a",size,size,0,0,1,size,size,"l",0,mmax(h-size,0),(dir==1)*size,size,(dir==1)*-size,size,0,mmax(h-size,0),"a",size,size,0,0,1,-size,size,"l",-mmax(w-size,0),0,"z"].join(","),xy=[{x:X,y:Y+size*2+h},{x:X-size*2-w,y:Y},{x:X,y:Y-size*2-h},{x:X+size*2+w,y:Y}][dir];xy.path=p;this.attr(xy);return this;};return res.update(x,y);}; |
It has been minified, all you need to do is paste it onto the bottom of your RaphaelJS file that you downloaded from the RaphaelJS website, or you can put it in another JS file and attach it separately. I leave it to you.
Okay. First we need to make a path file. This file will contain the path data to create our map. Where do you get this data from? Well your just open up the SVG file in your favorite text editor (I use Notepad++). SVG files are actually just XML files, since the SVG file format is XML based. If the image has had each part grouped together when it was created you should be able to easily find the different parts you need. Here is an example of how my SVG file looked.
The highlighted areas are the important parts. The first is the name of group, this helps you identify which part of the image this code draws. The second highlighted part is the path data which is what you are looking for. You will need to copy the information between the d=""
. Remember though that you need the data (d=””) from every path element inside that group to create that group. This may involve joining lots of path elements together. To do that just join them together & leave a single space between each one.
There is an alternative though. Eric in the comments to this tutorial has kindly pointed out that there is a small web app that will take your SVG file & pump out the needed RaphaelJS code to make it. Kinda cool!
Now let’s get to what you need to do with that code. The easiest way to build a large image such as a map is to store the path data in a separate file as an object.
So create a file called path.js and place this inside:
1 2 3 |
var paths = { } |
This is the start of your path data file. You should have your first piece of path data in clipboard, so let’s use it.
1 2 3 4 5 6 7 8 |
var paths = { item: { path: "Paste Path Data Here" }, item2: { path: "Paste Second Path Data Here" } } |
So in our example here item
would use the path data from the first group (<g id="">
) element in our SVG file, and item2
the second and so on. That would normally be it, but we want to have a popup display something & we want links that go somewhere when clicked. To do that we store our data inside this JS file. So here is an example:
1 2 3 4 5 6 7 |
var paths = { item: { info: "Something For The PopUp To Show", url: "http://link.com", path: "Path data still here" } } |
As you can see, info is the text the popup will show, URL is where the area of the map goes to when clicked, and path is still just the path data.
Now you just need to fill up the path file with all your path data. It can take a while but stick at it. Once you’ve done that we can continue on to our initialization code.
I keep the initialization code inside a file called init.js (original, I know) but you can easily just place it on the HTML page if you like. Let’s take a look at it before we go through it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
jQuery(function($){ var c = Raphael('map', 400, 502); c.safari(); var label = c.popup(0, 0, "").hide(); attr = { fill: '#898989', stroke: '#FFFFFF', 'stroke-width': 2, 'stroke-linejoin': 'round' } arr = new Array(); for (var item in paths) { var p = c.path(paths[item].path); arr[p.id] = item; p.attr(attr); p.hover(function(){ this.animate({ fill: '#8fbf27' }, 200); bbox = this.getBBox(); label.attr({text: paths[arr[this.id]].name}).update(bbox.x, bbox.y + bbox.height/2, bbox.width).toFront().show(); }, function(){ this.animate({ fill: attr.fill }, 200); label.hide(); }) .click(function(){ location.href = paths[arr[this.id]].url; }) } }); |
I know it looks complicated, but let’s try and go through it piece by piece and see if we can’t make sense of it. First off we’ll ignore the jQuery DOM ready, I’m using the No Conflict version since this was created for a WordPress based website.
1 2 3 |
var c = Raphael('map', 400, 502); c.safari(); var label = c.popup(0, 0, "").hide(); |
Okay. First we create the canvas or paper, the parameters are the ID of the element you would like the image to be created in, and the width & height of the image. The width & height can be found in the top of the SVG file (when it’s open in a text editor) or by opening it in your favorite vector illustration program such as InkScape or Illustrator. Next we run a special fix built into Raphael to counteract a rendering problem sometimes apparent in Safari, and finally we create a popup and hide it straight away. We will use the popup a little later.
1 2 |
attr = { fill: '#898989', stroke: '#FFFFFF', 'stroke-width': 2, 'stroke-linejoin': 'round' } arr = new Array(); |
These two lines are fairly simple. The first sets up our fill color, stroke color, stroke width & how the stroke lines should be joined. The second just creates an empty array to be used a little later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
for (var item in paths) { var p = c.path(paths[item].path); arr[p.id] = item; p.attr(attr); p.hover(function(){ this.animate({ fill: '#8fbf27' }, 200); bbox = this.getBBox(); label.attr({text: paths[arr[this.id]].info}).update(bbox.x, bbox.y + bbox.height/2, bbox.width).toFront().show(); }, function(){ this.animate({ fill: attr.fill }, 200); label.hide(); }) .click(function(){ location.href = paths[arr[this.id]].url; }) } }); |
Okay this last section can get complicated. First we start a loop that goes through each item inside our path object (remember from our path.js file). We then create a path using the path data, this is stored in a variable called p
. Now we store a reference to the current item inside that empty array using the ID of the current path as the key, this is to help us gain access to the data (info, url etc) we stored in the path.js file. Next we tell our newly created path what color it should be using the attributes we set earlier.
Next we add some event listeners to our path. The first is hover. On hover we animate our new color in, you could do anything you like at this point but this is the effect my client was after so I stuck with it. Next we grab the width & height information for the current path and store it in bbox
. Now we setup our popup, first we set the text using the data from our path.js file, then update the position using the paths ‘x’ position & it’s ‘y’ position plus half it’s height. This will make the popup appear to the left horizontally but central vertically when it is displayed on the map. We then set it to be in front of everything & to finally show. The last part of the hover is to restore the original color & hide to popup when the mouse leaves. The click function is optional and sends the browser to the URL you set inside the path.js file.
Despite the code not being very large, to those who haven’t used Javascript much I can imagine it is daunting to look at. I’ve tried to explain it as best I can without it becoming confusing, but I doubt I’ve answered every possible question you have. If you do have a question or you need me to explain something in a little more detail, please let me know via the comments below. Also I’m am currently available to do small jobs in WordPress and sometimes other small jobs (jQuery, PHP etc) please send me an email via the contact form or via admin [at] gmail [dot] com if you are interested, no large scale jobs at the moment please as I just don’t have the time available at the moment to commit to them.
115 Comments
Joy
Paul,
Awesome tutorial! Is there any way to change the color of the countries to different colors? For example if I hover over Ireland the color is blue and then hover over Scottland the color is red.
Thanks!
Paul Robinson
Hi joy,
The best way to do that would be to add a color into your Path file with the other attributes & just pull it out when creating each country and apply it to that part. So for example if you stored the color in a property called ‘color’, along with the url etc you might change the
p.hover
code to:Just remember the color will need to be a hex color value.
Manu
Hey great tutorial, thanks a lot.
– For those who still look for a circle instead of a path: Here you can easily convert a circle into a path: http://complexdan.com/svg-circleellipse-to-path-converter/
Helped me!
vijay
It was a great tutorial, Thank you very much it is working great for me.
I want to display the country name all time inside shape and in popup I will display description. please suggest me
Example in init.js:
name: “Something To Show inside shape all the time”,
info: “Something For The PopUp To Show”,
url: “http://link.com”,
path: “Path data still here”
Evan
Hi Paul,
I know it’s been forever ago since this tutorial was posted but it was incredibly helpful for me just recently, so thank you!
I actually had the same question as Matt, about wanting to fill each country with different colors and I managed to do so with the code you provided for him, using the
switch
. However, the thing I can’t figure out is how to leave the color of each country just as it was before the hover action. As it is right now: country1 is originally yellow, I hover and change it to blue and it doesn’t return to yellow after leaving the mouseover, it just takes the default color value of theswitch
. Do you think you could help me with that? Thank you again!Paul Robinson
Hi Evan,
Sorry for the delay in getting back to you.
Looking back the code I originally posted for Matt should do that. If placed in the correct places in the code it should initialise the map with the non-hover color, then show the hover color on hover and then return to the non-hover color. Could you post your code on Pastebin or something similar? If you can’t post it publicly then feel free to drop me an email via the contact form instead.
Also apologies for some of the layout mess in these comments. I’m busy building a new theme that will make things a lot nicer and more readable. It will be ready soon.
Ravindra Gaikwad
Hi Paul,
I am trying to implement a USA map showing regions like – north east, south east, middle atlantic, moutain, pacific etc instead of individual states. Can you please let me know how can this be achieved? Appreciate your help. Thanks.
Paul Robinson
Hi,
That’s a complex question, but the general steps would be the same. The trickiest part would be to find the map. If there isn’t a map available that is already split up in the way you’d like you’d need to grab a map of the USA and then use an vector editing program like Inkscape (or Illustrator) to add in the regions manually.
The rest of the code should be similar if not the same since the paths your take from the SVG determine what is drawn.
Hope that helps a little.