andrew.hedges.name / blog

Add an interactive legend to a MarkerManager managed Google Map

November 22, 2009, 11:22 am · 9 comments · Filed under: Google, JavaScript, Web Development

There’s plenty of help out there for adding a legend to a Google Map. There are resources for working with MarkerManager as well. I can’t believe this is new ground, but there didn’t seem to be anything related to getting an interactive legend to play nice with MarkerManager. Now there is!

On a recent site, the client specified the (quite reasonable) dual requirements of showing different pins at different zoom levels and having a legend on the map that could be used to toggle pins of different types.

Having used the excellent, open source MarkerManager library before, I immediately thought of it for managing the zoom levels. I hadn’t implemented an interactive legend before, so I went a-Googling and found documentation and examples for adding custom map controls. So far, so good.

I stumbled around a bit about how to get the MarkerManager class to filter pins by both zoom level and type until I buckled down and just read the source of the class. Turns out, the solution was pretty simple (which is always a good sign).

In the end, I created a custom map control (LegendControl) that adds and removes groups of pins to and from the MarkerManager instance’s internal grid. It seems so obvious in hindsight!

E.g.

In case you’re confused about what problem I’m trying to solve, interact with the map below. Notice how you get different pins by zooming in and out and how you can turn whole classes of pins on and off.

SoC

Implementing MarkerManager to show and hide large numbers of pins by zoom level is a snap. And, it’s relatively trivial to write a couple of functions to show/hide individual pins by type. In fact, my client supplied me with functions they’d used before on other sites.

I decided from the outset to build the legend as a proper map control because, well, that’s what it is. I’m glad I did because it enforced separation of concerns. The resulting code is more tidy than if I’d just started hacking away.

I won’t go into detail about creating custom controls. You’re welcome to check out the script yourself to see how it’s done.

Now for the How

If you go to the map in a new window and view source, you’ll see a bunch of stuff that’s typical in a Google Maps implementation. For example, the following is the function I use to set up the map, itself:

function initMap() {
  var map, mgr;
  
  // set up your basic map
  map = new GMap2(document.getElementById('map'));
  map.setCenter(new GLatLng(-36.848, 174.756), 13);
  map.addControl(new GLargeMapControl());
  map.addControl(new GScaleControl());
  map.addControl(new GMapTypeControl());
  
  // create markermanager instance
  mgr = new MarkerManager(map);
  
  // add markers to MarkerManager and set up markerGroups
  initMarkers(mgr);
  
  // add legend
  map.addControl(new LegendControl({
    mgr          : mgr,
    markerGroups : markerGroups,
    legendValues : legendValues
  }));
}

See that last bit? That creates our legend control. But, I guess you figured that out, right?

Unfortunately, the way it’s currently written, LegendControl requires certain variables to be in the global scope. Yucky, I know. I’m open to feedback about how to tweak things to better encapsulate the control by passing in the necessary objects and options.

The variables that need to be in the global scope are as follows:

map
Instance of GMap2
mgr
Instance of MarkerManager
legendValues
Array of object literals, each with the following properties: type, description, and pinSrc
markerGroups
Object literal with arrays of markers keyed by type
Update: I’ve refactored the code to eliminate the need for variables to be in the global scope. Amazing what a good night’s sleep can do!

The other only trickiness required by LegendControl is that your markers (instances of GMarker) must have 2 properties in addition to the ones the class adds by default. These are zoomMin and zoomMax and they correspond to the minimum and maximum zoom levels at which the marker should be displayed.

So, by way of example, my function for creating individual markers looks like the following:

/**
 * Create a marker
 * @param object obj Object literal specifying marker attributes
 * @return GMarker
 */
function createMarker(obj) {
  var marker;
  
  marker = new GMarker(new GLatLng(obj.lat, obj.lng), {
    title   : obj.name,
    icon    : MapIconMaker.createMarkerIcon({
      width      : PIN_WIDTH,
      height     : PIN_HEIGHT,
      primaryColor : colors[obj.type]
    })
  });
  
  marker.zoomMin = obj.zoomMin;
  marker.zoomMax = obj.zoomMax;
  
  GEvent.addListener(marker, 'click', function() {
    marker.openInfoWindowHtml('
' + obj.name + '
'); }); return marker; }

Again, go to the map in a new window and view source to see the full implementation. It will make more sense than me spending another 500 words trying to explain it. :-)

I hope you found this helpful. As I said above, any suggestions for reducing the need for variables in the global scope are appreciated.

Update 9 Feb 2010: There has been a bit of interest in Legend Control, so I have put it up on Google Code under the MIT license. The script is free to use, modify, and distribute in both personal and commercial projects. Enjoy!

Short URL to this article:

9 comments


Hi,

Your example sounds great, but I am not able to actually see the icons from IE7.

I am looking for a way to create a legend within the map frame with the icon used on the map and the description of the icon.

Currently, we use the MyPane.prototype = new GControl; described in the link above to create a legend. We run our map based on an xml file that is parsed and one of the maps contains references to the icon (A, B, …). It would be great to be able to allow users to add new xml data via form (handled already), but then to just add a new letter C with the information on what C means, then to automatically add a legend without having to modify script. The legend should look something like this.
Legend:
A (icon) 2005
B (icon) 2006
Your thoughts would be great. Seeing your example in action once the code errors (if any) are corrected.

@Dwayne,

I just tested my example on IE7 and it did work. Here’s a direct link to the map page:

http://andrew.hedges.name/e/google-maps/legend.html

The pins are generated from the Google Charts API, so they might just have been experiencing a lag that caused them to time-out. Could you try substituting icon images from a more trusted source and see if you are still unable to see the pins?

This is great. I am wondering if you have ever tried using the mapiconmaker version 1.1 to add text in the center balloon. This would be helpful for color-blind and users that have a hard time differentiating color to determine differences in icons. I am not a big fan of too many custom images for icons as it can make the map very compact.

Nice job. You should think about how to advertise this more, as I have been searching for a while and this is one if not The best approach I’ve seen so far.

I really dig this. The show/hide box could be very useful. Can’t wait to try it!

Hey Andrew, is there any license to use your developed script?

@Dwayne,

The script is liberally licensed, so it’s free to use both personally and commercially. Thanks for reminding me. I’ll add a license statement to that effect to the script when I have a chance.

Keeping the doc block in place for attribution purposes would be appreciated, as would not linking directly to the file on my server.

Glad you find it useful!

In addition to making the markers above, I would like to add a drop down box to re-center, zoom, and remove markers that fall outside that region (e.g., state). Have you seen anything like this before? I only ask, as this is pretty good code that I haven’t seen elsewhere on multiple Google searches. I’m not sure this is actually possible.

BTW… using the mapiconmaker v1.1 you can add the labels that I suggested above (I think). I haven’t had a chance to work on this yet, but will over then next several weeks.

I will also try to integrate the coding using an XML data file, and will send to you when complete (if interested).

I will definitely pull the script off and put it on our server. Thanks for sharing your hard work.

This really has nothing to do with the map you created, by the way that is a awesome code.
My dilema is that I am creating markers on the map and each marker is for a office location…That was easy, but then my client wanted the legend to have the location name next to each marker. When I do this the description not only shows up on the legend it shows up on the map, which causes confusion because it shows two (google map has Baltimore on its map and the description “Baltimore” that is in the legend. Is there a way to just hav ethe marker descriptions show up in the legend and not duplicate on the map itself?

This script appears to have stopped working, the check boxes to toggle the category’s are gone…

Comments close automatically after 15 days.
Still have something to say? Drop me a line!