A List Apart

Menu

A More Accessible Map

by Published in CSS, HTML, JavaScript, Accessibility50 Comments

As companies like Google and Yahoo! have simplified the process of placing information on a map by offering web services/APIs, the popularity and abundance of mapping applications on the web has increased dramatically. While these maps have had a positive effect on most users, what does it mean for people with accessibility needs?

Most online mapping applications do not address issues of web accessibility. For a visually impaired web user, these highly visual maps are essentially useless.

Is there a way to display text-based data on a map, keeping it accessible, useful and visually attractive? Yes: using an accessible CSS-based map in which the underlying map data is separated from the visual layout.

A different starting point

So what if, instead of starting from a map graphic and adding data to points located across the image, we start with the data itself and create a map based on the data?

First, let’s pick some data that has a geographic component so it can be placed on a map. For example, let’s use the 10 most populated cities in the world. Displayed as plain text, the list might look like this:

  1. Tokyo, Japan – 28,025,000 people
  2. Mexico City, Mexico – 18,131,000 people
  3. Mumbai, India – 18,042,000 people
  4. Sao Paulo, Brazil – 17,110,000 people
  5. New York City, USA - 16,626,000 people
  6. Shanghai, China – 14,173,000 people
  7. Lagos, Nigeria – 13,488,000 people
  8. Los Angeles, USA - 13,129,000 people
  9. Calcutta, India – 12,900,000 people
  10. Buenos Aires, Argentina – 12,431,000 people

Note: The data above is intended only as an example and may not be entirely accurate.

Not bad, but it would be nice to add some descriptive text to each of the cities to describe a little more about them. So, an example for just one city might be:

Shanghai, China
Shanghai, China is situated on the banks of the Yangtze River and has a population of 14,173,000, making it the sixth most populated city.

Organizing the data

Now that we’ve added some descriptive information to the city, let’s think about how to organize that data in HTML. A definition list (dl) would be a good way to organize this information because a dl has both definition term (dt) and definition description (dd) child elements. This means we can have the city’s name as a dt and the city description as a dd. Let’s see how that might look:

<dl>
<dt><a href="http://en.wikipedia.org/wiki/Shanghai%2C_China" 
class="location" id="shanghai">Shanghai, China</a></dt>
<dd>Shanghai, China is situated on the banks of the Yangtze 
River and has a population of 14,173,000, making it the
sixth most populated city.</dd>
...

Bringing the rest of our data into the definition list gives us the complete list of 10 cities.

Note that the anchor link within the dt was given a class and unique id. The id allows us to refer to each city individually, so we can locate it on the map. We’ll come back to this a little later.

Adding some style

Portion of the world map

So where are we now? We’ve created an accessible text-based list of items and their definitions. The data is what we want to convey to the user, regardless of whether they see it on a map or just as text, so we are in good shape. Now that we have the information organized, let’s make a point map out of it.

In a point map, there are three main items that are visually displayed: the map, the point(s) on the map, and data to display when the user clicks on or hovers over a point. We can very easily translate those visual items to the text-based dl we just created:

  • Map image = dl
  • Map point(s) = dt > a
  • Map point popup data = dd

The first step in making dl into a map is to add the background map image with a style. We will give the dl a class of “map” so that only those dls in a document get styled this way (in our example, as a world map).

dl.map {
background: url(worldmap.png) no-repeat;
border: 1px solid #999;
margin: 0px;
padding: 0px;
text-align:left;
width: 550px;
height: 275px;
position: relative;
}

The next step is to add a style for the dts that will allow the anchors within to be made into points on the map:

dl.map dt {
display: inline;
}

Next, we need to deal with the dd items which will be the data displayed when a point on the map is hovered over. We do not want the dd to display until the a within its associated dt is hovered, so by default we need to hide the dds. Since screen readers will ignore an HTML element with a style of display: none, we need to hide the data by positioning it far away from the browser’s view. The style for the dd will make the element look like a tooltip window:

dl.map dd {
background: #555;
border: 2px solid #222;
border-radius: 8px; /* CSS3 rounded corners */
-moz-border-radius: 8px; /* Mozilla rounded corners */
color: #fff;
padding: 4px;
width: 200px;
position: absolute;
left: -9999px;
z-index: 11;
}

Each of the dts contains an a which, by default, takes a user to the Wikipedia article corresponding to the city; this link is what will trigger the tooltip to display the dd. The link will be displayed as a point and the link text will be invisible, so again we need to move the text out of the browser’s view. Each point will also have a different location, but we’ll take care of that in the next step. The base style for the anchor links looks like this:

dl.map a.location {
background: url(point.png) no-repeat;
display: block;
outline: none;
text-decoration: none;
text-indent: -9999px;
width: 10px;
height: 10px;
position: absolute;
z-index: 10;
}
dl.map a.location:hover {
background: url(point-hover.png) no-repeat -1px -1px;
}

Finally, we need to add styles specific to each point on the map. These points are represented by the anchor links inside each of the dts. The placement of each point will be accomplished by assigning top and left values (since we already positioned them all absolutely in the above code snippet):

dl.map a#shanghai {
top: 80px;
left: 484px;
}
dl.map a#tokyo {
top: 105px;
left: 121px;
}
...

We’ve just created a basic map, so let’s look at where we are with our first example. This example places the points on the map, but does not display any information when a point is hovered over.

Display the data as a tooltip

Now that we have a map with points positioned, we need to display data when the points are hovered over. To display a tooltip we are going to have to use some JavaScript.

The JavaScript to show and hide the dd “tooltip” does so by modifying the style of the dd associated with the current a being hovered. A JavaScript function named showTooltip() places the dd back into the visible area of the browser by modifying its top and left style values, based on the location of the associated a point.

It would be very easy for us to use inline event handlers like onmouseover and onmouseout to accomplish this, but that wouldn’t be very unobtrusive. We want like our script to remain independent of our map and our markup. To do that we can have the JavaScript dynamically assign events to the anchor elements.

But before we dig into the JavaScript, let’s pause for a moment to talk about JavaScript objects. The JavaScript used in this technique makes use of object literal notation, so learning the basics of this will help you understand how it all works. Here is a basic example of an object literal:

var person = {
name: "Homer Simpson",
say: "Doh!",
talk: function(){
alert( this.say );
},
walk: function(){
// code
}
};

One of the benefits of using objects in JavaScript is that we can use methods and properties in a more sensible manner. For example, with the above code we can call a method like person.talk() or get a property like person.name. Objects also allow us to reuse method and property names without causing conflict. For instance, if we were to add an animal object to the file above, it could also have an walk method (animal.walk()) without conflicting with the person.walk() method.

The script used to assign the events in this technique is a little more complicated, but here is a shortened skeleton of what this example script looks like:

var mapMaker = {
offsetX:     -16,  // tooltip X offset
offsetY:     16,  // tooltip Y offset
init:        function(){
// sets up the maps
},
showTooltip: function(){
// shows the tooltip
},
hideTooltip: function(){
// hides the tooltip
},
addEvt: function( element, type, handler ){
// adds the events
}
};

Now let’s dig a bit deeper into some of the methods. For the sake of brevity we will not cover every method, but we will tackle the key ones.

The init() method is our constructor. This means the method is what we call to initialize our mapMaker object. It sets up the map by adding events to each of the anchor links inside the dts, making the tooltips possible. Let’s look at the init method:

init: function(){
var i  = 0;
var ii = 0;
var location;
var DLs = document.getElementsByTagName('dl');
while( DLs.length > i ){
if( DLs[i].className == 'map' ){
mapMaker.stripWhitespace( DLs[i] );
var DTs = DLs[i].getElementsByTagName( 'dt' );
while( DTs.length > ii ){
location = DTs[ii].firstChild;
mapMaker.addEvt( location, 'mouseover',
     mapMaker.showTooltip );
mapMaker.addEvt( location, 'mouseout',
     mapMaker.hideTooltip );
mapMaker.addEvt( location, 'focus', 
     mapMaker.showTooltip );
mapMaker.addEvt( location, 'blur', 
     mapMaker.hideTooltip );
ii++;
};
ii = 0;
}
i++;
};
},

The init method first collects all of the dl elements in the document by using document.getElementsByTagName( ‘dl’ ) and then loops through each, looking for one with a class of “map”. When it finds one, it collects its dt elements and loops through them, assigning event handlers (using the addEvt() method) to the anchor tag within. The events we’ve accounted for include mouseover (which calls the showTooltip() method) and mouseout (which calls the hideTooltip() method) and mirrors the same actions for the focus and blur events, respectively, to make the tooltip available for keyboard users.

Now let’s take a closer look at a portion of the showTooltip() method; this method displays the tooltip dd when a map point (dl.map > dt > a) is hovered. To display the correct tooltip, the function must know which dd is associated with each anchor. From the currently hovered anchor tag, this, we can move to its parent dt tag (this.parentNode) then to that dt’s dd (this.parentNode.nextSibling) which is the tooltip we want to display:

showTooltip: function(){
var a = this;
var tooltip = a.parentNode.nextSibling;
...

Once we know which dd element should be displayed we can set the different style attributes to place the dd tooltip into view and move it into the correct location next to the point:

  ...
// get width and height of background map
var mapWidth  = tooltip.parentNode.offsetWidth;
var mapHeight = tooltip.parentNode.offsetHeight;

// get width and height of the tooltip
var tooltipWidth = tooltip.offsetWidth;
var tooltipHeight = tooltip.offsetHeight;

// figure out where to place the tooltip based on the point
var newX = a.offsetLeft + mapMaker.offsetX;
var newY = a.offsetTop + mapMaker.offsetY;

// check if tooltip fits map width & adjust if necessary
if( ( newX + tooltipWidth ) > mapWidth ){
tooltip.style.left = newX - tooltipWidth - 24 + 'px';
} else {
tooltip.style.left = newX + 'px';
}

//check if tooltip fits map height 
if( ( newY + tooltipHeight ) > mapHeight ){
tooltip.style.top = newY - tooltipHeight - 14 + 'px';
} else {
tooltip.style.top = newY + 'px';
}
...

In the script above the dd tooltip style is changed to bring the element back into the visible area of the map, but the script also takes into account the size of the map and positions the tooltip so it does not go outside the background map image boundaries.

The last detail we need to worry about is running the script when the webpage has loaded. Sounds easy just call window.onload = mapMaker.init() right? Well you could do that, but it doesn’t allow other scripts to run on page load and it would be really nice to trigger this script to run after the page has loaded but before all of the images have downloaded. Thankfully, Dean Edwards has done the work for us on this one and you can check out his write-up of the technique for more information.

We have now reviewed the major parts of this map example’s JavaScript, but did not cover every single function in the script. The functions for adding events get fairly complicated and are outside the scope of this article; however, you can view the entire script here.

Now that we have added some JavaScript functionality let’s look at where we are with this second map example. We now have a visually pleasing map that provides more detail when the user hovers over a city. Of course, because this map is created from text-based data, the data displayed as a map can be read by a screen reader and convey the same meaning.

What about CSS on and JavaScript off?

We need to address users who have JavaScript turned off but use a browser with good CSS support. We want these users to get the lo-fi, but useable text version of the map, because without JavaScript enabled they won’t be able to see the tooltips.

To enable the tooltips and the map style, the JavaScript init() method can be tweaked to turn the map “on.” If the map is not “on,” it will simply display as text only. As we have given our map dl a class of “map,” we’ll need to add a second class: “on.” We’ll also need ot update our style sheet, adding .on to each style rule like this:

dl.map.on {
background: url(worldmap.png) no-repeat;
border: 1px solid #999;
margin: 0px;
padding: 0px;
text-align:left;
width: 550px;
height: 275px;
position: relative;
}

The above style will only affect dls that have are class-ified as both “map” and “on” (<dl class=“map on”>) and will not affect any dls with only a class of “map.” You can see this in action in our third example.

Getting fancy

Screenshot of the map tooltip

Now that we have a functional map, why don’t we add some images to the tooltip? This next example map shows images inside the tooltip. Or, maybe you would like to have links inside the tooltip. To do this, we need the tooltip to be “sticky” and not hide when the point is moused off. Here is an example of a “sticky” tooltip map (the JavaScript functions have oviously changed slightly to make the tooltip stick). Finally, this technique also allows for us to place multiple maps on a single page, so we have lots of options.

Where in the world?

Now back to those wonderful APIs. What if you want to use one place points on this world map by latitude and longitude?

Server side scripting examples of how to dynamically place points on the map are beyond the scope of this article, but here is some pseudo code to convert latitude and longitude points to pixels for the world map image used in our example:

/* For this example world map image in map coordinate 
system GCS_WGS_1984 */

// set latitude and longitude
pointLat  = // between -90.00 and 90.00
pointLong = // between -180.00 and 180.00;

// get map height and width
mapHeight = // map height (in pixels)
mapWidth  = // map width (in pixels)

// convert latitude to pixels
pointLat = ( ( pointLat * -1 ) + 90 ) * ( mapHeight / 180 );

// convert longitude to pixels
pointLong = ( pointLong + 180 ) * ( mapWidth / 360 );

/* you may also have to correct for the size 
of the point image placed on the map */
pointLat  = pointLat  - ( [point image height] / 2 );
pointLong = pointLong - ( [point image width] / 2);

If you are considering using server side scripting to dynamically place points on a map, you might also consider having the background image of the map be set by JavaScript. This would allow a dynamic page to assign different background map images to the dl based on the area or zoom level you would like to display.

A quick review

Let’s review what we have done:

  1. Started with a text-based list that has geographic components
  2. Turned that text list into an HTML definition list
  3. Styled the dl to look like a map
  4. Styled the dt elements into points located around the map using CSS
  5. Styled the dd elements to look like a tooltips, but moved them out of view
  6. Used JavaScript to dynamically assign mouse and keyboard events to the map points
  7. Enabled the dd “tooltips” to be styled back into view when the map points are hovered

What have we accomplished by doing this?

  1. Created a text-based map
  2. Enabled screen readers, text-based users, and search engines to “read” the map
  3. Created a pleasing visual map for sighted users
  4. Created an interactive map for mouse users
  5. Allowed non-mouse users to interact with the map with their keyboard

Where to go from here?

The example of a more accessible map presented here is just that, a more accessible map. It does not claim to be the best way, just a different way to approach the problem of creating accessible geographic-type content without having to create multiple pages in different formats. This example will not address all types of accessibility needs, nor be an ideal solution to all types of web based maps or GIS applications; however, a text-based webpage is always a great starting point for addressing accessibility concerns.

Compared to other commonly used mapping methods, this technique starts with the data and builds a map around it. Text content is separated from the map layout. When content can be separated from layout the content is generally more accessible. This means that an alternative accessible version may not need to be created.

What we have built in this article is just the start of what might be done on a map. These same concepts could be used to place shapes, or even a drill down map to display detailed data for more specific areas or locations. The important thing to remember is to start with the text-based information you want to convey. First make the data readable and useful as text, and then make it into a map.

50 Reader Comments

Load Comments