Constellation Knowledge

Jason’s original concept of a new dashboard interface for navigating the Khan Academy’s exercises has come to life (you’ll need an account).

My “Addition 1” skills are improving.

"Why did you do this?"

Since we started preparing to test out the Khan Academy’s exercises inside a couple forward-thinking schools, we knew we needed an interface that clearly shows students which exercises they should be working on and where progress has been made.

We also wanted to make the whole experience a bit more Mario 64fun.

We’re using a Google Maps-like interface for plenty of reasons, not the least of which is that Google has spent a lot of time trying to figure out how to build interfaces that help people navigate two(-ish)-dimensional maps. Piggy-backing on their work for our two(-ish)-dimensional map of knowledge concepts seems like a big win.

So far we have absolutely zero proof that we’ve accomplished (or failed at) any of this. But we will have lots of evidence or counterevidence soon(!), when students start using using this interface to navigate their math class.

All I know is that I keep fighting off my video gamer’s urge to turn this…

…into this, which must be a good sign.

"Before you guys have to revert all your changes because you underestimated your users’ astrophobia, how does this work?”

I’m going to share the technical details of using the Google Maps API (v3) to implement the map part.

The Custom Map

When you create a new google.maps.Map object, you can specify a number of preconfigured map types (like ROADMAP, SATELLITE, etc). None of these worked for us since we needed a blank slate on which to draw our own map. The Maps API does support creating completely custom maps, but even their tutorial has all types of “This is an advanced topic,” “We didn’t fully document this process,” “This is more difficult than it sounds”-type warnings.

Luckily, Google has given us a customizable google.maps.ImageMapType which implements all of the hairy details of a custom map and only requires that you implement one function which basically boils down to, “Given a specific latitude, longitude, and zoom level, what image should the map display?” PERFECT. This is exactly what we needed. I started off by always returning some /images/black_tile.png URL to just get a big black blank slate of a map.

    var knowledgeMapType = new google.maps.ImageMapType(this.options);'knowledge', knowledgeMapType);'knowledge');


    getTileUrl: function(coord, zoom) {
        return "/images/black_tile.png"

Setting up controls

The API is really pleasant to use. When setting up your map, there are are options like streetViewControl: false for all types of control settings and positions. We turned off Street View, restricted zoom to a few particular levels, added the pan and zoom control in the upper left, and made a few other tweaks. A primary reason for choosing Google Maps as the tool to represent our knowledge map was getting well-behaved pan, zoom, and more in an extremely stable cross-browser package. = new google.maps.Map(document.getElementById("map-canvas"), {
        mapTypeControl: false,
        streetViewControl: false,
        scrollwheel: false

Placing and labeling nodes

Each node represents one module of the Khan Academy exercises. Google makes it easy to throw markers with customized icons on their maps at specific lat/long coordinates. However, there’s no simple built-in way to label the icons with text. I used a little external utility, markerwithlabel.js, to get this functionality.

    var marker = new MarkerWithLabel({
        position: node.latLng,
        icon: this.getMarkerIcon(iconUrl, zoom),
        flat: true,
        labelAnchor: this.getLabelAnchor(zoom),
        labelClass: this.getLabelClass(labelClass, zoom)

Going into space

Once the map really started to come together we wanted more than a plain black background. We’d be missing out on one of the coolest pieces of Google Maps if we didn’t take advantage of some cool tile zooming behavior. We totally lucked out when realizing that we could use Google Sky. It’s really easy to make that “turn Latitude/Longitude/Zoom into a tile URL” function I mentioned above grab the appropriate Google Sky tiles.

    getTileUrl: function(coord, zoom) {
        // Sky tiles example from
        return KnowledgeMap.getHorizontallyRepeatingTileUrl(coord, zoom, 
                function(coord, zoom) {
                  return "" +
                     coord.x + "_" + coord.y + '_' + zoom + '.jpg';

Now when students are zooming in and out of their knowledge map they’re also zooming in and out of actual Hubble photos of outer space. Google’s APIs gave us all of this power in about 5 minutes.

Saving the user’s last coordinates

If the knowledge map is going to help students understand where they are in the collection of Khan Academy exercises, it needs to remember their last position. On the server we have a URL that can save each user’s current latitude, longitude, and zoom level. I started off by listening for our map’s center_changed and zoom_changed events to detect any change in position and send off a fire’n’forget request to save state on the server. Bad: these events fire constantly while dragging around the map. Good: listening for the idle event instead, which fires when map movement has settled, works perfectly.

    google.maps.event.addListener(, "idle", function(){KnowledgeMap.saveMapCoords();});


    saveMapCoords: function() {
        var center =;
        $.post("/savemapcoords", {
            "lng": center.lng(),
        }); // Fire and forget

Preventing students from getting lost in space

There’s a lot more Google Sky than there are Khan Academy modules (for now), so we needed to restrict the movement of the map to protect users from getting lost at random positions in space that are nowhere near the module nodes. In this case┬áthe constantly-firing center_changed event is perfect. We listen for changes and update the center of the map whenever it has been dragged “out of bounds.”

    google.maps.event.addListener(, "center_changed", function(){KnowledgeMap.onCenterChange();});


    onCenterChange: function() {
        var center =;
        if (this.approvedBounds.contains(center)) {
        else {
            // Restrict to approved boundary

Changing the size of icons and labels on zoom

Markers (and MarkerWithLabels) don’t normally change sizes when the map zooms. To accomplish this, we listen to the zoom_changed event, update the icon and label properties appropriately for each zoom level, and force the markers to redraw. I know there are other options here instead of using markers, but markers make a lot of sense when laying out nodes programmatically. This also means we get to add cool zoom-y easter eggs like Constellation View.

    google.maps.event.addListener(, "zoom_changed", function(){KnowledgeMap.onZoomChange();});


   onZoomChange: function() {
        var zoom =;
        for (var key in this.dictNodes)
            var node = this.dictNodes[key];
            var marker = node.marker;
            marker.setIcon(this.getMarkerIcon(node.iconUrl, zoom));
            marker.labelAnchor = this.getLabelAnchor(zoom);
            marker.labelClass = this.getLabelClass(marker.labelClass, zoom);
            marker.label.setStyles(); // Redraw marker's label

Please take a look

Most of the work is in knowledgemap.js. You’ll notice that the snippets above have been simplified. I’m 100% positive there are more elegant ways to do most of this stuff. Let me know when you find them.

We had fun working on this UI, but there’s so much to do in so many other areas of the Khan Academy that we can’t get stuck in any one spot for long. Sal and Shantanu have gotten lots of great feedback from teachers and parents already, and it’s clear that they’ll need better reporting tools soon. Moving quickly is one of our most important goals.

"I caught you playing a video game!"

Rebecca walked into my room yesterday while I was working on this UI, and she totally called me out for playing a video game in my second week of work. I had a huge smile on my face for minutes.

11/23/10 — 6:03pm Permalink
  1. bjk5 posted this