Repository: yagoferrer/map-tools
Pull request #178 (2015-02-26 07:14:18)

Gert/template changes
Each different commit fix a different issue.  Fix fo #177, #168


Patch:
diff --git a/dist/map-tools.js b/dist/map-tools.js
new file mode 100644
index 0000000..b62c7d3
--- /dev/null
+++ b/dist/map-tools.js
@@ -0,0 +1,2738 @@
+/* map-tools.js 0.4.0 MIT License. 2015 Yago Ferrer <yago.ferrer@gmail.com> */
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/* global: window */
+/*jslint node: true */
+"use strict";
+window.GMP = require('map-tools/index')(window);
+
+},{"map-tools/index":12}],2:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+var topojson = require('topojson');
+var utils = require('map-tools/utils');
+var crossfilter = require('crossfilter');
+
+
+module.exports = function (global, that) {
+
+  var addFilter = require('map-tools/addFilter')(global, that);
+
+  /**
+   * Adds GeoJSON Feature Options like: style
+   * @param features
+   * @param options
+   * @private
+   */
+  function addFeatureOptions(features, options) {
+
+
+    var feature, x;
+    for (x in features) {
+      if (features.hasOwnProperty(x)) {
+        feature = features[x];
+
+        var data = feature.k;
+        feature.k.uid = utils.createUid();
+
+        Object.defineProperty(feature, 'data', {
+          value: data,
+          enumerable: true,
+          writable: false,
+          configurable: false
+        });
+
+        if (options) {
+          if (options.filters) {
+            // Only add filters if not defined.
+            if (!global.GMP.maps[that.id].json.filter) {
+              addFilter('json', options.filters);
+            }
+
+            global.GMP.maps[that.id].json.crossfilter.add([feature.data]);
+          }
+
+          if (options.style) {
+            that.instance.data.overrideStyle(feature, options.style);
+          }
+        }
+        global.GMP.maps[that.id].json.all[feature.data.uid] = feature;
+      }
+    }
+  }
+
+
+  /**
+   * Adds a Topo JSON file into a Map
+   * @param data The parsed JSON File
+   * @param options
+   */
+  function addTopoJson(data, options) {
+    var item, geoJson, features,  x;
+    for (x in options) {
+      if (options.hasOwnProperty(x)) {
+        item = options[x];
+        geoJson = topojson.feature(data, data.objects[item.object]);
+        features = that.instance.data.addGeoJson(geoJson);
+        addFeatureOptions(features, item);
+        global.GMP.maps[that.id].json.groups[item.object] = features;
+      }
+    }
+    return features;
+  }
+
+  function addGeoJson(data, options) {
+    var features = that.instance.data.addGeoJson(data, options);
+    addFeatureOptions(features, options);
+    return features;
+  }
+
+  return {
+    addGeoJson: addGeoJson,
+    addTopoJson: addTopoJson
+  };
+};
+
+},{"crossfilter":18,"map-tools/addFilter":3,"map-tools/utils":16,"topojson":19}],3:[function(require,module,exports){
+var crossfilter = require('crossfilter');
+
+module.exports = function(global, that) {
+
+  function defaultDimension(item) {
+    return function(d) {return d[item];};
+  }
+
+  function addFilter(type, filters) {
+
+    global.GMP.maps[that.id][type].crossfilter = global.GMP.maps[that.id][type].crossfilter || crossfilter([]);
+    global.GMP.maps[that.id][type].filter = global.GMP.maps[that.id][type].filter || {};
+    var dimension, item;
+
+    if (typeof filters === 'string') {
+      filters = [filters];
+    }
+
+    for (dimension in filters) {
+
+      item = filters[dimension];
+      if (typeof item === 'string') {
+        global.GMP.maps[that.id][type].filter[item] = global.GMP.maps[that.id][type].crossfilter.dimension(defaultDimension(item));
+      } else {
+        global.GMP.maps[that.id][type].filter[Object.keys(item)[0]] = global.GMP.maps[that.id][type].crossfilter.dimension(item[Object.keys(item)[0]]);
+      }
+
+    }
+  }
+
+  return addFilter;
+
+};
+
+},{"crossfilter":18}],4:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+var utils = require('map-tools/utils');
+
+module.exports = function (global, that) {
+
+  var gmaps = require('map-tools/gmaps.js')(global);
+
+
+  /**
+   * Creates a new Google Map Instance
+   * @param args Arguments to instantiate a Google Maps
+   *
+   */
+
+  function getElement(args) {
+
+    if (args.el) {
+      return global.document.querySelector(args.el);
+    }
+
+    if (args.id) {
+      return global.document.getElementById(args.id);
+    }
+
+  }
+
+  function create(args, cb) {
+
+    cb = cb || function () {};
+
+    var mapOptions = gmaps.mapOptions(args);
+
+    args.id = args.id || args.el.substring(1);
+    that.id = args.id;
+    that.options = args;
+    that.instance = new global.google.maps.Map(getElement(args), mapOptions);
+
+    global.GMP.maps[that.id].instance = that.instance;
+
+    global.google.maps.event.addListenerOnce(that.instance, 'idle', function (){
+      cb(false, that.instance);
+    });
+  }
+  /**
+   * Validates GMP Options
+   * @param options to validate
+   * @param cb Only used when something goes wrong
+   * @returns {boolean} true/false
+   */
+  function validOptions(options, cb) {
+    if (!options || (options && typeof options !== 'object')) {
+      cb(new Error('You must pass a valid first parameter: options'));
+      return false;
+    }
+
+    if (!options.id && !options.el) {
+      cb(new Error('You must pass an "id" or a "el" property values'));
+      return false;
+    }
+
+    if (!options.lat || !options.lng) {
+      cb(new Error('You must pass valid "lat" (latitude) and "lng" (longitude) values'));
+      return false;
+    }
+
+    return true;
+  }
+
+  function load(options, cb) {
+
+    if (validOptions(options, cb)) {
+
+      var id = options.id || options.el.substring(1);
+
+      global.GMP.maps = global.GMP.maps || {};
+      global.GMP.maps[id] = {
+        create: function () {
+          create(this.arguments, cb);
+        },
+        arguments: options
+      };
+
+      // Set Global Structure
+      global.GMP.maps[id].markers = global.GMP.maps[id].markers || {};
+      global.GMP.maps[id].json = global.GMP.maps[id].json || {all: {}, groups: {}};
+
+      that.markers = global.GMP.maps[id].markers;
+      that.json = global.GMP.maps[id].json;
+
+
+      if (options.async !== false || options.sync === true) {
+        gmaps.load(id, options);
+      } else {
+        global.GMP.maps[id].create();
+      }
+    }
+  }
+
+  return {
+    load: load
+  };
+
+};
+
+},{"map-tools/gmaps.js":10,"map-tools/utils":16}],5:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+var utils = require('map-tools/utils');
+
+module.exports = function (global, that) {
+  var bubble = require('map-tools/bubble')(global);
+  var addFilter = require('map-tools/addFilter')(global, that);
+
+  function addMarker(marker, options) {
+    var i;
+    marker.map = that.instance;
+    marker.position = new global.google.maps.LatLng(marker.lat, marker.lng);
+
+    var group = marker.group || false;
+
+    if (options && options.group) {
+      group = options.group || group;
+    }
+
+    // Adds options set via 2nd parameter. Overwrites any Marker options already set.
+    if (options) {
+      for (i in options) {
+        if (options.hasOwnProperty(i)) {
+          if (!options.filters) { // Filters is a special property that we don't need to add to the Marker.
+            marker[i] = options[i];
+          }
+        }
+      }
+    }
+
+    // Adds additional options from the Group and overwrites any Marker options already set.
+    if (group && that.markers && that.markers.groupOptions && that.markers.groupOptions[group]) {
+
+      for (var j in that.markers.groupOptions[group]) {
+        marker[j] = that.markers.groupOptions[group][j];
+      }
+    }
+
+    marker.data = marker.data || {};
+
+    if (marker.data) {
+      marker.data.uid = utils.createUid();
+    }
+
+    if (options && options.filters) {
+      // Only add filters if not defined.
+      if (!global.GMP.maps[that.id].markers.filter) {
+        addFilter('markers', options.filters);
+      }
+      that.markers.crossfilter.add([marker.data]);
+    }
+
+    var instance = new global.google.maps.Marker(marker);
+
+    if (marker.move) {
+      instance.setAnimation(global.google.maps.Animation[marker.move.toUpperCase()]);
+    }
+
+    if (marker.bubble) {
+      bubble.create(instance, marker.bubble, that.instance);
+    }
+
+
+    // Adds Marker Reference to specific Group
+    if (group) {
+      that.markers.groups = global.GMP.maps[that.id].markers.groups || {};
+      that.markers.groups[group] = global.GMP.maps[that.id].markers.groups[group] || [];
+      that.markers.groups[group].push(instance);
+    }
+
+
+    // Adds Marker Reference of each Marker to "markers"
+    that.markers.all = global.GMP.maps[that.id].markers.all || {};
+    that.markers.all[marker.data.uid] = instance;
+
+
+    return instance;
+  }
+
+
+  /**
+   * Adds Markers to the Map
+   * @param args Array or Markers
+   * @param options things like groups etc
+   * @returns {Array} all the instances of the markers.
+   */
+  function addMarkers(args, options) {
+    if (args.length && args.length >= 1) {
+      var markers = [];
+      var marker;
+      for (var i in args) {
+        marker = addMarker(args[i], options);
+        markers.push(marker);
+
+      }
+
+      return markers;
+    }
+
+    return addMarker(args, options);
+  };
+
+  return addMarkers;
+
+};
+
+},{"map-tools/addFilter":3,"map-tools/bubble":7,"map-tools/utils":16}],6:[function(require,module,exports){
+/*jslint node: true */
+"use strict"
+
+var utils = require('map-tools/utils');
+var config = require('map-tools/config');
+
+module.exports = function(global, that) {
+
+	function getPositionKey(pos) {
+		return pos.toUpperCase().match(/\S+/g).join('_');
+	}
+
+	function hy2cmml(k) {
+		return k.replace(/-(.)/g, function(m, g) {
+			return g.toUpperCase();
+		});
+	}
+
+	function HTMLParser(aHTMLString){
+		var container = window.document.createElement('div');
+
+		container.innerHTML = aHTMLString;
+
+		return container;
+	}
+
+	function addPanel(options) {
+
+		var position,
+			panel,
+			e,
+			rule;
+
+		// default position
+		options.position = options.position || config.panelPosition;
+
+		if (options.templateURL) {
+			var request = new XMLHttpRequest();
+			request.open("GET", options.templateURL, false);
+			request.send(null);
+			panel = HTMLParser(request.responseText);
+
+		}
+
+		// template options
+		if (typeof options.template === 'string') {
+			panel = HTMLParser(options.template);
+		}
+
+		// positioning options
+		if (options.position) {
+			// convert to google ControlPosition map position keys
+			options.position = getPositionKey(options.position);
+			position = google.maps.ControlPosition[options.position];
+		}
+
+		// style options
+		if (typeof options.style === 'object') {
+			for (rule in options.style) {
+				var ruleKey = hy2cmml(rule);
+				panel.style[ruleKey] = options.style[rule];
+			}
+		}
+
+		// event handler
+		if (options.events) {
+
+			for (e in options.events) {
+				var keys = e.match(/\S+/g);
+
+
+				var	event = keys.splice(-1); //event type
+				var selector = keys.join(' '); // selector string
+				var	elements = panel.querySelectorAll(selector);
+
+				[].forEach.call(elements, function(elm) {
+					google.maps.event.addDomListener( elm, event, options.events[e] );
+				});
+
+			}
+		}
+
+
+		that.instance.controls[position].push(panel);
+		return panel;
+	}
+
+	return addPanel;
+};
+
+},{"map-tools/config":8,"map-tools/utils":16}],7:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+module.exports = function (global) {
+
+  function create(marker, options, map) {
+    var event = options.event || 'click';
+
+    options.content = options.content.replace(/\{(\w+)\}/g, function (m, variable) {
+      return marker.data[variable] || '';
+    });
+
+    marker.bubble.instance = new global.google.maps.InfoWindow(options);
+
+    global.google.maps.event.addListener(marker, event, function () {
+      marker.bubble.instance.open(map, marker);
+    });
+  }
+
+  return {
+    create: create
+  };
+};
+
+
+},{}],8:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+module.exports = {
+  version: '3.18', // Released: May 15, 2014
+  zoom: 8,
+  customMapOptions: ['id', 'lat', 'lng', 'type'],
+  customMarkerOptions: ['lat', 'lng', 'move', 'bubble'],
+  panelPosition: 'TOP_LEFT'
+};
+
+},{}],9:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+module.exports = function (global, that) {
+
+  function updateMarker(marker, options) {
+
+    if (options.custom) {
+      if (options.custom.move) {
+        marker.setAnimation(global.google.maps.Animation[options.custom.move.toUpperCase()]);
+      }
+
+      if (options.custom.lat && options.custom.lng) {
+        marker.setPosition(new global.google.maps.LatLng(options.custom.lat, options.custom.lng));
+      }
+
+      if (options.custom.bubble && options.custom.bubble.content) {
+        marker.bubble.instance.setContent(options.custom.bubble.content);
+      }
+    }
+
+    if (options.defaults) {
+      marker.setOptions(options.defaults);
+    }
+
+    return marker;
+  }
+  /**
+   * Transforms flat keys to Setters. For example visible becomes: setVisible.
+   * @param options
+   * @returns {{count: number, setterKey: *, setters: {}}}
+   * @private
+   */
+  function findAndUpdateMarker(marker, options) {
+    if (marker.data && marker.data.uid) {
+      return updateMarker(marker, options);
+    }
+
+    if (marker.uid && global.GMP.maps[that.id].markers.all[marker.uid]) {
+      return updateMarker(global.GMP.maps[that.id].markers.all[marker.uid], options);
+    }
+  }
+
+
+  return findAndUpdateMarker;
+};
+
+},{}],10:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+
+var config = require('map-tools/config');
+var utils = require('map-tools/utils');
+
+module.exports = function (global) {
+
+  /**
+   * Injects Google API Javascript File and adds a callback to load the Google Maps Async.
+   * @type {{load: Function}}
+   * @private
+   *
+   * @returns the element appended
+   */
+  function load(id, args) {
+    var version = args.version || config.version;
+    var script = global.document.createElement('script');
+    script.type = 'text/javascript';
+    script.src = '//maps.googleapis.com/maps/api/js?v=' + version + '&callback=GMP.maps.' + id + '.create';
+    return global.document.body.appendChild(script);
+  }
+
+  function mapOptions(args) {
+    // To clone Arguments excluding customMapOptions
+    var mapOptions = utils.clone(args, config.customMapOptions);
+    mapOptions.zoom = args.zoom || config.zoom;
+    if (args.lat && args.lng) {
+      mapOptions.center = new global.google.maps.LatLng(args.lat, args.lng);
+    }
+    if (args.type) {
+      mapOptions.mapTypeId = global.google.maps.MapTypeId[args.type] || false;
+    }
+
+    return mapOptions;
+  }
+
+  return {
+    load: load,
+    mapOptions: mapOptions
+  };
+};
+
+},{"map-tools/config":8,"map-tools/utils":16}],11:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+var utils = require('map-tools/utils');
+var config = require('map-tools/config');
+
+module.exports = function (global, that) {
+  var findAndUpdateMarker = require('map-tools/findUpdateMarker')(global, that);
+
+  /**
+   * Adds a New Group
+   * @param name Name of the Group
+   * @param options That Apply to all the Group
+   */
+  function addGroup(name, options) {
+    global.GMP.maps[that.id].markers = global.GMP.maps[that.id].markers || {};
+    global.GMP.maps[that.id].markers.groups = global.GMP.maps[that.id].markers.groups || [];
+    global.GMP.maps[that.id].markers.groupOptions = global.GMP.maps[that.id].markers.groupOptions || {};
+    global.GMP.maps[that.id].markers.groupOptions[name] = options;
+  }
+
+  /**
+   * Updates all the Markers of a Group to have specific Properties
+   * @param name
+   * @param options
+   */
+  function updateGroup(name, options) {
+    var result = [], instance, item;
+    var preparedOptions =  utils.prepareOptions(options, config.customMarkerOptions);
+    if (global.GMP.maps[that.id].markers.groups && global.GMP.maps[that.id].markers.groups[name]) {
+      for (item in global.GMP.maps[that.id].markers.groups[name]) {
+        if (global.GMP.maps[that.id].markers.groups[name].hasOwnProperty(item)) {
+          instance = findAndUpdateMarker(global.GMP.maps[that.id].markers.groups[name][item], preparedOptions);
+          result.push(instance);
+        }
+      }
+    }
+    return result;
+  }
+
+  return {
+    addGroup: addGroup,
+    updateGroup: updateGroup
+  };
+
+};
+
+},{"map-tools/config":8,"map-tools/findUpdateMarker":9,"map-tools/utils":16}],12:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+module.exports = function (global) {
+
+  /**
+   * Creates a new GMaps Plus instance
+   * @param options
+   * @constructor
+   */
+  function GMP(options, cb) {
+    var that = this;
+
+    this.addMarker = require('map-tools/addMarker')(global, that);
+    this.addTopoJson = require('map-tools/addFeature')(global, that).addTopoJson;
+    this.addGeoJson = require('map-tools/addFeature')(global, that).addGeoJson;
+    this.updateFeature = require('map-tools/updateFeature')(global, that);
+    this.addPanel = require('map-tools/addPanel')(global, that);
+    this.updateMarker = require('map-tools/updateMarker')(global, that);
+    this.addGroup = require('map-tools/groups')(global, that).addGroup;
+    this.updateGroup = require('map-tools/groups')(global, that).updateGroup;
+    this.updateMap = require('map-tools/updateMap')(global, that);
+
+    var map = require('map-tools/addMap')(global, that);
+
+    global.onload = map.load(options, cb); // Wait until the DOM is ready before attempting to load the Map
+
+    return this;
+  }
+
+  // a GMP Instance
+  GMP.prototype.instance = false;
+
+  return GMP;
+};
+
+},{"map-tools/addFeature":2,"map-tools/addMap":4,"map-tools/addMarker":5,"map-tools/addPanel":6,"map-tools/groups":11,"map-tools/updateFeature":13,"map-tools/updateMap":14,"map-tools/updateMarker":15}],13:[function(require,module,exports){
+/*jslint node: true */
+"use strict"
+
+var utils = require('map-tools/utils');
+
+
+module.exports = function(global, that) {
+
+  function updateStyle (f, style) {
+	  if (typeof style === 'function') {
+		  var styleOptions = style.call(f)
+		  return that.instance.data.overrideStyle(f, styleOptions);
+	  }
+    that.instance.data.overrideStyle(f, style);
+  }
+
+  function update(args, options){
+    var type = Object.prototype.toString.call(args);
+
+    if (type === '[object Array]') {
+      var feature, x;
+      for (x in args) {
+        feature = args[x];
+        findAndUpdate(feature, options);
+      }
+    }
+
+    if (type === '[object Object]') {
+      findAndUpdate(args, options);
+    }
+  };
+
+  function findAndUpdate(args, options) {
+
+    if (args.data && args.data.uid) {
+      return updateFeature(args, options);
+    }
+
+    if (args.uid && global.GMP.maps[that.id].json && global.GMP.maps[that.id].json.all[args.uid]) {
+      return updateFeature(global.GMP.maps[that.id].json.all[args.uid], options);
+    }
+  }
+
+
+  function updateFeature(feature, options) {
+    if( options.style ) {
+      updateStyle(feature, options.style);
+    }
+  }
+
+
+
+  return update;
+};
+
+},{"map-tools/utils":16}],14:[function(require,module,exports){
+"use strict";
+module.exports = function (global, that) {
+
+  var gmaps = require('map-tools/gmaps.js')(global);
+
+  function updateMap(args) {
+    var mapOptions = gmaps.mapOptions(args);
+    return that.instance.setOptions(mapOptions);
+  }
+
+  return updateMap;
+};
+
+},{"map-tools/gmaps.js":10}],15:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+var utils = require('map-tools/utils');
+var config = require('map-tools/config');
+
+module.exports = function (global, that) {
+
+  var findAndUpdateMarker = require('map-tools/findUpdateMarker')(global, that);
+
+  function updateMarker(args, options) {
+    var type = Object.prototype.toString.call(args);
+    var preparedOptions = utils.prepareOptions(options, config.customMarkerOptions);
+
+    if (type === '[object Object]') {
+      return findAndUpdateMarker(args, preparedOptions);
+    }
+    if (type === '[object Array]') {
+      var marker, results = [], instance, x;
+      for (x in args) {
+        if (args.hasOwnProperty(x)) {
+          marker = args[x];
+          instance = findAndUpdateMarker(marker, preparedOptions);
+          results.push(instance);
+        }
+      }
+      return results;
+    }
+  }
+
+  return updateMarker;
+
+};
+
+},{"map-tools/config":8,"map-tools/findUpdateMarker":9,"map-tools/utils":16}],16:[function(require,module,exports){
+/*jslint node: true */
+"use strict";
+function clone(o, exceptionKeys) {
+  var out, v, key;
+  out = Array.isArray(o) ? [] : {};
+  for (key in o) {
+    if (o.hasOwnProperty(key)) {
+      if (!exceptionKeys || (exceptionKeys && exceptionKeys.indexOf(key) === -1)) {
+        v = o[key];
+        out[key] = (typeof v === "object") ? clone(v) : v;
+      }
+    }
+  }
+  return out;
+}
+
+function createUid() {
+  return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    var r, v;
+    r = Math.random() * 16 | 0;
+    v = c === 'x' ? r : r & 0x3 | 0x8;
+    return v.toString(16);
+  });
+}
+
+function prepareOptions(options, custom) {
+  var result = {}, option;
+  for (option in options) {
+    if (options.hasOwnProperty(option)) {
+      if (custom.indexOf(option) > -1) {
+        result.custom = result.custom || {};
+        result.custom[option] = options[option];
+      } else {
+        result.defaults = result.defaults || {};
+        result.defaults[option] = options[option];
+      }
+    }
+  }
+  return result;
+}
+
+module.exports = {
+  clone: clone,
+  createUid: createUid,
+  prepareOptions: prepareOptions
+};
+
+},{}],17:[function(require,module,exports){
+(function(exports){
+crossfilter.version = "1.3.11";
+function crossfilter_identity(d) {
+  return d;
+}
+crossfilter.permute = permute;
+
+function permute(array, index) {
+  for (var i = 0, n = index.length, copy = new Array(n); i < n; ++i) {
+    copy[i] = array[index[i]];
+  }
+  return copy;
+}
+var bisect = crossfilter.bisect = bisect_by(crossfilter_identity);
+
+bisect.by = bisect_by;
+
+function bisect_by(f) {
+
+  // Locate the insertion point for x in a to maintain sorted order. The
+  // arguments lo and hi may be used to specify a subset of the array which
+  // should be considered; by default the entire array is used. If x is already
+  // present in a, the insertion point will be before (to the left of) any
+  // existing entries. The return value is suitable for use as the first
+  // argument to `array.splice` assuming that a is already sorted.
+  //
+  // The returned insertion point i partitions the array a into two halves so
+  // that all v < x for v in a[lo:i] for the left side and all v >= x for v in
+  // a[i:hi] for the right side.
+  function bisectLeft(a, x, lo, hi) {
+    while (lo < hi) {
+      var mid = lo + hi >>> 1;
+      if (f(a[mid]) < x) lo = mid + 1;
+      else hi = mid;
+    }
+    return lo;
+  }
+
+  // Similar to bisectLeft, but returns an insertion point which comes after (to
+  // the right of) any existing entries of x in a.
+  //
+  // The returned insertion point i partitions the array into two halves so that
+  // all v <= x for v in a[lo:i] for the left side and all v > x for v in
+  // a[i:hi] for the right side.
+  function bisectRight(a, x, lo, hi) {
+    while (lo < hi) {
+      var mid = lo + hi >>> 1;
+      if (x < f(a[mid])) hi = mid;
+      else lo = mid + 1;
+    }
+    return lo;
+  }
+
+  bisectRight.right = bisectRight;
+  bisectRight.left = bisectLeft;
+  return bisectRight;
+}
+var heap = crossfilter.heap = heap_by(crossfilter_identity);
+
+heap.by = heap_by;
+
+function heap_by(f) {
+
+  // Builds a binary heap within the specified array a[lo:hi]. The heap has the
+  // property such that the parent a[lo+i] is always less than or equal to its
+  // two children: a[lo+2*i+1] and a[lo+2*i+2].
+  function heap(a, lo, hi) {
+    var n = hi - lo,
+        i = (n >>> 1) + 1;
+    while (--i > 0) sift(a, i, n, lo);
+    return a;
+  }
+
+  // Sorts the specified array a[lo:hi] in descending order, assuming it is
+  // already a heap.
+  function sort(a, lo, hi) {
+    var n = hi - lo,
+        t;
+    while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo);
+    return a;
+  }
+
+  // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous
+  // slice of array a[lo:lo+n]. This method can also be used to update the heap
+  // incrementally, without incurring the full cost of reconstructing the heap.
+  function sift(a, i, n, lo) {
+    var d = a[--lo + i],
+        x = f(d),
+        child;
+    while ((child = i << 1) <= n) {
+      if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++;
+      if (x <= f(a[lo + child])) break;
+      a[lo + i] = a[lo + child];
+      i = child;
+    }
+    a[lo + i] = d;
+  }
+
+  heap.sort = sort;
+  return heap;
+}
+var heapselect = crossfilter.heapselect = heapselect_by(crossfilter_identity);
+
+heapselect.by = heapselect_by;
+
+function heapselect_by(f) {
+  var heap = heap_by(f);
+
+  // Returns a new array containing the top k elements in the array a[lo:hi].
+  // The returned array is not sorted, but maintains the heap property. If k is
+  // greater than hi - lo, then fewer than k elements will be returned. The
+  // order of elements in a is unchanged by this operation.
+  function heapselect(a, lo, hi, k) {
+    var queue = new Array(k = Math.min(hi - lo, k)),
+        min,
+        i,
+        x,
+        d;
+
+    for (i = 0; i < k; ++i) queue[i] = a[lo++];
+    heap(queue, 0, k);
+
+    if (lo < hi) {
+      min = f(queue[0]);
+      do {
+        if (x = f(d = a[lo]) > min) {
+          queue[0] = d;
+          min = f(heap(queue, 0, k)[0]);
+        }
+      } while (++lo < hi);
+    }
+
+    return queue;
+  }
+
+  return heapselect;
+}
+var insertionsort = crossfilter.insertionsort = insertionsort_by(crossfilter_identity);
+
+insertionsort.by = insertionsort_by;
+
+function insertionsort_by(f) {
+
+  function insertionsort(a, lo, hi) {
+    for (var i = lo + 1; i < hi; ++i) {
+      for (var j = i, t = a[i], x = f(t); j > lo && f(a[j - 1]) > x; --j) {
+        a[j] = a[j - 1];
+      }
+      a[j] = t;
+    }
+    return a;
+  }
+
+  return insertionsort;
+}
+// Algorithm designed by Vladimir Yaroslavskiy.
+// Implementation based on the Dart project; see lib/dart/LICENSE for details.
+
+var quicksort = crossfilter.quicksort = quicksort_by(crossfilter_identity);
+
+quicksort.by = quicksort_by;
+
+function quicksort_by(f) {
+  var insertionsort = insertionsort_by(f);
+
+  function sort(a, lo, hi) {
+    return (hi - lo < quicksort_sizeThreshold
+        ? insertionsort
+        : quicksort)(a, lo, hi);
+  }
+
+  function quicksort(a, lo, hi) {
+    // Compute the two pivots by looking at 5 elements.
+    var sixth = (hi - lo) / 6 | 0,
+        i1 = lo + sixth,
+        i5 = hi - 1 - sixth,
+        i3 = lo + hi - 1 >> 1,  // The midpoint.
+        i2 = i3 - sixth,
+        i4 = i3 + sixth;
+
+    var e1 = a[i1], x1 = f(e1),
+        e2 = a[i2], x2 = f(e2),
+        e3 = a[i3], x3 = f(e3),
+        e4 = a[i4], x4 = f(e4),
+        e5 = a[i5], x5 = f(e5);
+
+    var t;
+
+    // Sort the selected 5 elements using a sorting network.
+    if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t;
+    if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
+    if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t;
+    if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
+    if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t;
+    if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t;
+    if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t;
+    if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
+    if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
+
+    var pivot1 = e2, pivotValue1 = x2,
+        pivot2 = e4, pivotValue2 = x4;
+
+    // e2 and e4 have been saved in the pivot variables. They will be written
+    // back, once the partitioning is finished.
+    a[i1] = e1;
+    a[i2] = a[lo];
+    a[i3] = e3;
+    a[i4] = a[hi - 1];
+    a[i5] = e5;
+
+    var less = lo + 1,   // First element in the middle partition.
+        great = hi - 2;  // Last element in the middle partition.
+
+    // Note that for value comparison, <, <=, >= and > coerce to a primitive via
+    // Object.prototype.valueOf; == and === do not, so in order to be consistent
+    // with natural order (such as for Date objects), we must do two compares.
+    var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2;
+    if (pivotsEqual) {
+
+      // Degenerated case where the partitioning becomes a dutch national flag
+      // problem.
+      //
+      // [ |  < pivot  | == pivot | unpartitioned | > pivot  | ]
+      //  ^             ^          ^             ^            ^
+      // left         less         k           great         right
+      //
+      // a[left] and a[right] are undefined and are filled after the
+      // partitioning.
+      //
+      // Invariants:
+      //   1) for x in ]left, less[ : x < pivot.
+      //   2) for x in [less, k[ : x == pivot.
+      //   3) for x in ]great, right[ : x > pivot.
+      for (var k = less; k <= great; ++k) {
+        var ek = a[k], xk = f(ek);
+        if (xk < pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          ++less;
+        } else if (xk > pivotValue1) {
+
+          // Find the first element <= pivot in the range [k - 1, great] and
+          // put [:ek:] there. We know that such an element must exist:
+          // When k == less, then el3 (which is equal to pivot) lies in the
+          // interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
+          // Note that in the latter case invariant 2 will be violated for a
+          // short amount of time. The invariant will be restored when the
+          // pivots are put into their final positions.
+          while (true) {
+            var greatValue = f(a[great]);
+            if (greatValue > pivotValue1) {
+              great--;
+              // This is the only location in the while-loop where a new
+              // iteration is started.
+              continue;
+            } else if (greatValue < pivotValue1) {
+              // Triple exchange.
+              a[k] = a[less];
+              a[less++] = a[great];
+              a[great--] = ek;
+              break;
+            } else {
+              a[k] = a[great];
+              a[great--] = ek;
+              // Note: if great < k then we will exit the outer loop and fix
+              // invariant 2 (which we just violated).
+              break;
+            }
+          }
+        }
+      }
+    } else {
+
+      // We partition the list into three parts:
+      //  1. < pivot1
+      //  2. >= pivot1 && <= pivot2
+      //  3. > pivot2
+      //
+      // During the loop we have:
+      // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned  | > pivot2  | ]
+      //  ^            ^                        ^              ^             ^
+      // left         less                     k              great        right
+      //
+      // a[left] and a[right] are undefined and are filled after the
+      // partitioning.
+      //
+      // Invariants:
+      //   1. for x in ]left, less[ : x < pivot1
+      //   2. for x in [less, k[ : pivot1 <= x && x <= pivot2
+      //   3. for x in ]great, right[ : x > pivot2
+      for (var k = less; k <= great; k++) {
+        var ek = a[k], xk = f(ek);
+        if (xk < pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          ++less;
+        } else {
+          if (xk > pivotValue2) {
+            while (true) {
+              var greatValue = f(a[great]);
+              if (greatValue > pivotValue2) {
+                great--;
+                if (great < k) break;
+                // This is the only location inside the loop where a new
+                // iteration is started.
+                continue;
+              } else {
+                // a[great] <= pivot2.
+                if (greatValue < pivotValue1) {
+                  // Triple exchange.
+                  a[k] = a[less];
+                  a[less++] = a[great];
+                  a[great--] = ek;
+                } else {
+                  // a[great] >= pivot1.
+                  a[k] = a[great];
+                  a[great--] = ek;
+                }
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Move pivots into their final positions.
+    // We shrunk the list from both sides (a[left] and a[right] have
+    // meaningless values in them) and now we move elements from the first
+    // and third partition into these locations so that we can store the
+    // pivots.
+    a[lo] = a[less - 1];
+    a[less - 1] = pivot1;
+    a[hi - 1] = a[great + 1];
+    a[great + 1] = pivot2;
+
+    // The list is now partitioned into three partitions:
+    // [ < pivot1   | >= pivot1 && <= pivot2   |  > pivot2   ]
+    //  ^            ^                        ^             ^
+    // left         less                     great        right
+
+    // Recursive descent. (Don't include the pivot values.)
+    sort(a, lo, less - 1);
+    sort(a, great + 2, hi);
+
+    if (pivotsEqual) {
+      // All elements in the second partition are equal to the pivot. No
+      // need to sort them.
+      return a;
+    }
+
+    // In theory it should be enough to call _doSort recursively on the second
+    // partition.
+    // The Android source however removes the pivot elements from the recursive
+    // call if the second partition is too large (more than 2/3 of the list).
+    if (less < i1 && great > i5) {
+      var lessValue, greatValue;
+      while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less;
+      while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great;
+
+      // Copy paste of the previous 3-way partitioning with adaptions.
+      //
+      // We partition the list into three parts:
+      //  1. == pivot1
+      //  2. > pivot1 && < pivot2
+      //  3. == pivot2
+      //
+      // During the loop we have:
+      // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned  | == pivot2 ]
+      //              ^                      ^              ^
+      //            less                     k              great
+      //
+      // Invariants:
+      //   1. for x in [ *, less[ : x == pivot1
+      //   2. for x in [less, k[ : pivot1 < x && x < pivot2
+      //   3. for x in ]great, * ] : x == pivot2
+      for (var k = less; k <= great; k++) {
+        var ek = a[k], xk = f(ek);
+        if (xk <= pivotValue1 && xk >= pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          less++;
+        } else {
+          if (xk <= pivotValue2 && xk >= pivotValue2) {
+            while (true) {
+              var greatValue = f(a[great]);
+              if (greatValue <= pivotValue2 && greatValue >= pivotValue2) {
+                great--;
+                if (great < k) break;
+                // This is the only location inside the loop where a new
+                // iteration is started.
+                continue;
+              } else {
+                // a[great] < pivot2.
+                if (greatValue < pivotValue1) {
+                  // Triple exchange.
+                  a[k] = a[less];
+                  a[less++] = a[great];
+                  a[great--] = ek;
+                } else {
+                  // a[great] == pivot1.
+                  a[k] = a[great];
+                  a[great--] = ek;
+                }
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // The second partition has now been cleared of pivot elements and looks
+    // as follows:
+    // [  *  |  > pivot1 && < pivot2  | * ]
+    //        ^                      ^
+    //       less                  great
+    // Sort the second partition using recursive descent.
+
+    // The second partition looks as follows:
+    // [  *  |  >= pivot1 && <= pivot2  | * ]
+    //        ^                        ^
+    //       less                    great
+    // Simply sort it by recursive descent.
+
+    return sort(a, less, great + 1);
+  }
+
+  return sort;
+}
+
+var quicksort_sizeThreshold = 32;
+var crossfilter_array8 = crossfilter_arrayUntyped,
+    crossfilter_array16 = crossfilter_arrayUntyped,
+    crossfilter_array32 = crossfilter_arrayUntyped,
+    crossfilter_arrayLengthen = crossfilter_arrayLengthenUntyped,
+    crossfilter_arrayWiden = crossfilter_arrayWidenUntyped;
+
+if (typeof Uint8Array !== "undefined") {
+  crossfilter_array8 = function(n) { return new Uint8Array(n); };
+  crossfilter_array16 = function(n) { return new Uint16Array(n); };
+  crossfilter_array32 = function(n) { return new Uint32Array(n); };
+
+  crossfilter_arrayLengthen = function(array, length) {
+    if (array.length >= length) return array;
+    var copy = new array.constructor(length);
+    copy.set(array);
+    return copy;
+  };
+
+  crossfilter_arrayWiden = function(array, width) {
+    var copy;
+    switch (width) {
+      case 16: copy = crossfilter_array16(array.length); break;
+      case 32: copy = crossfilter_array32(array.length); break;
+      default: throw new Error("invalid array width!");
+    }
+    copy.set(array);
+    return copy;
+  };
+}
+
+function crossfilter_arrayUntyped(n) {
+  var array = new Array(n), i = -1;
+  while (++i < n) array[i] = 0;
+  return array;
+}
+
+function crossfilter_arrayLengthenUntyped(array, length) {
+  var n = array.length;
+  while (n < length) array[n++] = 0;
+  return array;
+}
+
+function crossfilter_arrayWidenUntyped(array, width) {
+  if (width > 32) throw new Error("invalid array width!");
+  return array;
+}
+function crossfilter_filterExact(bisect, value) {
+  return function(values) {
+    var n = values.length;
+    return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)];
+  };
+}
+
+function crossfilter_filterRange(bisect, range) {
+  var min = range[0],
+      max = range[1];
+  return function(values) {
+    var n = values.length;
+    return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)];
+  };
+}
+
+function crossfilter_filterAll(values) {
+  return [0, values.length];
+}
+function crossfilter_null() {
+  return null;
+}
+function crossfilter_zero() {
+  return 0;
+}
+function crossfilter_reduceIncrement(p) {
+  return p + 1;
+}
+
+function crossfilter_reduceDecrement(p) {
+  return p - 1;
+}
+
+function crossfilter_reduceAdd(f) {
+  return function(p, v) {
+    return p + +f(v);
+  };
+}
+
+function crossfilter_reduceSubtract(f) {
+  return function(p, v) {
+    return p - f(v);
+  };
+}
+exports.crossfilter = crossfilter;
+
+function crossfilter() {
+  var crossfilter = {
+    add: add,
+    remove: removeData,
+    dimension: dimension,
+    groupAll: groupAll,
+    size: size
+  };
+
+  var data = [], // the records
+      n = 0, // the number of records; data.length
+      m = 0, // a bit mask representing which dimensions are in use
+      M = 8, // number of dimensions that can fit in `filters`
+      filters = crossfilter_array8(0), // M bits per record; 1 is filtered out
+      filterListeners = [], // when the filters change
+      dataListeners = [], // when data is added
+      removeDataListeners = []; // when data is removed
+
+  // Adds the specified new records to this crossfilter.
+  function add(newData) {
+    var n0 = n,
+        n1 = newData.length;
+
+    // If there's actually new data to add…
+    // Merge the new data into the existing data.
+    // Lengthen the filter bitset to handle the new records.
+    // Notify listeners (dimensions and groups) that new data is available.
+    if (n1) {
+      data = data.concat(newData);
+      filters = crossfilter_arrayLengthen(filters, n += n1);
+      dataListeners.forEach(function(l) { l(newData, n0, n1); });
+    }
+
+    return crossfilter;
+  }
+
+  // Removes all records that match the current filters.
+  function removeData() {
+    var newIndex = crossfilter_index(n, n),
+        removed = [];
+    for (var i = 0, j = 0; i < n; ++i) {
+      if (filters[i]) newIndex[i] = j++;
+      else removed.push(i);
+    }
+
+    // Remove all matching records from groups.
+    filterListeners.forEach(function(l) { l(0, [], removed); });
+
+    // Update indexes.
+    removeDataListeners.forEach(function(l) { l(newIndex); });
+
+    // Remove old filters and data by overwriting.
+    for (var i = 0, j = 0, k; i < n; ++i) {
+      if (k = filters[i]) {
+        if (i !== j) filters[j] = k, data[j] = data[i];
+        ++j;
+      }
+    }
+    data.length = j;
+    while (n > j) filters[--n] = 0;
+  }
+
+  // Adds a new dimension with the specified value accessor function.
+  function dimension(value) {
+    var dimension = {
+      filter: filter,
+      filterExact: filterExact,
+      filterRange: filterRange,
+      filterFunction: filterFunction,
+      filterAll: filterAll,
+      top: top,
+      bottom: bottom,
+      group: group,
+      groupAll: groupAll,
+      dispose: dispose,
+      remove: dispose // for backwards-compatibility
+    };
+
+    var one = ~m & -~m, // lowest unset bit as mask, e.g., 00001000
+        zero = ~one, // inverted one, e.g., 11110111
+        values, // sorted, cached array
+        index, // value rank ↦ object id
+        newValues, // temporary array storing newly-added values
+        newIndex, // temporary array storing newly-added index
+        sort = quicksort_by(function(i) { return newValues[i]; }),
+        refilter = crossfilter_filterAll, // for recomputing filter
+        refilterFunction, // the custom filter function in use
+        indexListeners = [], // when data is added
+        dimensionGroups = [],
+        lo0 = 0,
+        hi0 = 0;
+
+    // Updating a dimension is a two-stage process. First, we must update the
+    // associated filters for the newly-added records. Once all dimensions have
+    // updated their filters, the groups are notified to update.
+    dataListeners.unshift(preAdd);
+    dataListeners.push(postAdd);
+
+    removeDataListeners.push(removeData);
+
+    // Incorporate any existing data into this dimension, and make sure that the
+    // filter bitset is wide enough to handle the new dimension.
+    m |= one;
+    if (M >= 32 ? !one : m & (1 << M) - 1) {
+      filters = crossfilter_arrayWiden(filters, M <<= 1);
+    }
+    preAdd(data, 0, n);
+    postAdd(data, 0, n);
+
+    // Incorporates the specified new records into this dimension.
+    // This function is responsible for updating filters, values, and index.
+    function preAdd(newData, n0, n1) {
+
+      // Permute new values into natural order using a sorted index.
+      newValues = newData.map(value);
+      newIndex = sort(crossfilter_range(n1), 0, n1);
+      newValues = permute(newValues, newIndex);
+
+      // Bisect newValues to determine which new records are selected.
+      var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1], i;
+      if (refilterFunction) {
+        for (i = 0; i < n1; ++i) {
+          if (!refilterFunction(newValues[i], i)) filters[newIndex[i] + n0] |= one;
+        }
+      } else {
+        for (i = 0; i < lo1; ++i) filters[newIndex[i] + n0] |= one;
+        for (i = hi1; i < n1; ++i) filters[newIndex[i] + n0] |= one;
+      }
+
+      // If this dimension previously had no data, then we don't need to do the
+      // more expensive merge operation; use the new values and index as-is.
+      if (!n0) {
+        values = newValues;
+        index = newIndex;
+        lo0 = lo1;
+        hi0 = hi1;
+        return;
+      }
+
+      var oldValues = values,
+          oldIndex = index,
+          i0 = 0,
+          i1 = 0;
+
+      // Otherwise, create new arrays into which to merge new and old.
+      values = new Array(n);
+      index = crossfilter_index(n, n);
+
+      // Merge the old and new sorted values, and old and new index.
+      for (i = 0; i0 < n0 && i1 < n1; ++i) {
+        if (oldValues[i0] < newValues[i1]) {
+          values[i] = oldValues[i0];
+          index[i] = oldIndex[i0++];
+        } else {
+          values[i] = newValues[i1];
+          index[i] = newIndex[i1++] + n0;
+        }
+      }
+
+      // Add any remaining old values.
+      for (; i0 < n0; ++i0, ++i) {
+        values[i] = oldValues[i0];
+        index[i] = oldIndex[i0];
+      }
+
+      // Add any remaining new values.
+      for (; i1 < n1; ++i1, ++i) {
+        values[i] = newValues[i1];
+        index[i] = newIndex[i1] + n0;
+      }
+
+      // Bisect again to recompute lo0 and hi0.
+      bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1];
+    }
+
+    // When all filters have updated, notify index listeners of the new values.
+    function postAdd(newData, n0, n1) {
+      indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); });
+      newValues = newIndex = null;
+    }
+
+    function removeData(reIndex) {
+      for (var i = 0, j = 0, k; i < n; ++i) {
+        if (filters[k = index[i]]) {
+          if (i !== j) values[j] = values[i];
+          index[j] = reIndex[k];
+          ++j;
+        }
+      }
+      values.length = j;
+      while (j < n) index[j++] = 0;
+
+      // Bisect again to recompute lo0 and hi0.
+      var bounds = refilter(values);
+      lo0 = bounds[0], hi0 = bounds[1];
+    }
+
+    // Updates the selected values based on the specified bounds [lo, hi].
+    // This implementation is used by all the public filter methods.
+    function filterIndexBounds(bounds) {
+      var lo1 = bounds[0],
+          hi1 = bounds[1];
+
+      if (refilterFunction) {
+        refilterFunction = null;
+        filterIndexFunction(function(d, i) { return lo1 <= i && i < hi1; });
+        lo0 = lo1;
+        hi0 = hi1;
+        return dimension;
+      }
+
+      var i,
+          j,
+          k,
+          added = [],
+          removed = [];
+
+      // Fast incremental update based on previous lo index.
+      if (lo1 < lo0) {
+        for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) {
+          filters[k = index[i]] ^= one;
+          added.push(k);
+        }
+      } else if (lo1 > lo0) {
+        for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) {
+          filters[k = index[i]] ^= one;
+          removed.push(k);
+        }
+      }
+
+      // Fast incremental update based on previous hi index.
+      if (hi1 > hi0) {
+        for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) {
+          filters[k = index[i]] ^= one;
+          added.push(k);
+        }
+      } else if (hi1 < hi0) {
+        for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) {
+          filters[k = index[i]] ^= one;
+          removed.push(k);
+        }
+      }
+
+      lo0 = lo1;
+      hi0 = hi1;
+      filterListeners.forEach(function(l) { l(one, added, removed); });
+      return dimension;
+    }
+
+    // Filters this dimension using the specified range, value, or null.
+    // If the range is null, this is equivalent to filterAll.
+    // If the range is an array, this is equivalent to filterRange.
+    // Otherwise, this is equivalent to filterExact.
+    function filter(range) {
+      return range == null
+          ? filterAll() : Array.isArray(range)
+          ? filterRange(range) : typeof range === "function"
+          ? filterFunction(range)
+          : filterExact(range);
+    }
+
+    // Filters this dimension to select the exact value.
+    function filterExact(value) {
+      return filterIndexBounds((refilter = crossfilter_filterExact(bisect, value))(values));
+    }
+
+    // Filters this dimension to select the specified range [lo, hi].
+    // The lower bound is inclusive, and the upper bound is exclusive.
+    function filterRange(range) {
+      return filterIndexBounds((refilter = crossfilter_filterRange(bisect, range))(values));
+    }
+
+    // Clears any filters on this dimension.
+    function filterAll() {
+      return filterIndexBounds((refilter = crossfilter_filterAll)(values));
+    }
+
+    // Filters this dimension using an arbitrary function.
+    function filterFunction(f) {
+      refilter = crossfilter_filterAll;
+
+      filterIndexFunction(refilterFunction = f);
+
+      lo0 = 0;
+      hi0 = n;
+
+      return dimension;
+    }
+
+    function filterIndexFunction(f) {
+      var i,
+          k,
+          x,
+          added = [],
+          removed = [];
+
+      for (i = 0; i < n; ++i) {
+        if (!(filters[k = index[i]] & one) ^ !!(x = f(values[i], i))) {
+          if (x) filters[k] &= zero, added.push(k);
+          else filters[k] |= one, removed.push(k);
+        }
+      }
+      filterListeners.forEach(function(l) { l(one, added, removed); });
+    }
+
+    // Returns the top K selected records based on this dimension's order.
+    // Note: observes this dimension's filter, unlike group and groupAll.
+    function top(k) {
+      var array = [],
+          i = hi0,
+          j;
+
+      while (--i >= lo0 && k > 0) {
+        if (!filters[j = index[i]]) {
+          array.push(data[j]);
+          --k;
+        }
+      }
+
+      return array;
+    }
+
+    // Returns the bottom K selected records based on this dimension's order.
+    // Note: observes this dimension's filter, unlike group and groupAll.
+    function bottom(k) {
+      var array = [],
+          i = lo0,
+          j;
+
+      while (i < hi0 && k > 0) {
+        if (!filters[j = index[i]]) {
+          array.push(data[j]);
+          --k;
+        }
+        i++;
+      }
+
+      return array;
+    }
+
+    // Adds a new group to this dimension, using the specified key function.
+    function group(key) {
+      var group = {
+        top: top,
+        all: all,
+        reduce: reduce,
+        reduceCount: reduceCount,
+        reduceSum: reduceSum,
+        order: order,
+        orderNatural: orderNatural,
+        size: size,
+        dispose: dispose,
+        remove: dispose // for backwards-compatibility
+      };
+
+      // Ensure that this group will be removed when the dimension is removed.
+      dimensionGroups.push(group);
+
+      var groups, // array of {key, value}
+          groupIndex, // object id ↦ group id
+          groupWidth = 8,
+          groupCapacity = crossfilter_capacity(groupWidth),
+          k = 0, // cardinality
+          select,
+          heap,
+          reduceAdd,
+          reduceRemove,
+          reduceInitial,
+          update = crossfilter_null,
+          reset = crossfilter_null,
+          resetNeeded = true,
+          groupAll = key === crossfilter_null;
+
+      if (arguments.length < 1) key = crossfilter_identity;
+
+      // The group listens to the crossfilter for when any dimension changes, so
+      // that it can update the associated reduce values. It must also listen to
+      // the parent dimension for when data is added, and compute new keys.
+      filterListeners.push(update);
+      indexListeners.push(add);
+      removeDataListeners.push(removeData);
+
+      // Incorporate any existing data into the grouping.
+      add(values, index, 0, n);
+
+      // Incorporates the specified new values into this group.
+      // This function is responsible for updating groups and groupIndex.
+      function add(newValues, newIndex, n0, n1) {
+        var oldGroups = groups,
+            reIndex = crossfilter_index(k, groupCapacity),
+            add = reduceAdd,
+            initial = reduceInitial,
+            k0 = k, // old cardinality
+            i0 = 0, // index of old group
+            i1 = 0, // index of new record
+            j, // object id
+            g0, // old group
+            x0, // old key
+            x1, // new key
+            g, // group to add
+            x; // key of group to add
+
+        // If a reset is needed, we don't need to update the reduce values.
+        if (resetNeeded) add = initial = crossfilter_null;
+
+        // Reset the new groups (k is a lower bound).
+        // Also, make sure that groupIndex exists and is long enough.
+        groups = new Array(k), k = 0;
+        groupIndex = k0 > 1 ? crossfilter_arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity);
+
+        // Get the first old key (x0 of g0), if it exists.
+        if (k0) x0 = (g0 = oldGroups[0]).key;
+
+        // Find the first new key (x1), skipping NaN keys.
+        while (i1 < n1 && !((x1 = key(newValues[i1])) >= x1)) ++i1;
+
+        // While new keys remain…
+        while (i1 < n1) {
+
+          // Determine the lesser of the two current keys; new and old.
+          // If there are no old keys remaining, then always add the new key.
+          if (g0 && x0 <= x1) {
+            g = g0, x = x0;
+
+            // Record the new index of the old group.
+            reIndex[i0] = k;
+
+            // Retrieve the next old key.
+            if (g0 = oldGroups[++i0]) x0 = g0.key;
+          } else {
+            g = {key: x1, value: initial()}, x = x1;
+          }
+
+          // Add the lesser group.
+          groups[k] = g;
+
+          // Add any selected records belonging to the added group, while
+          // advancing the new key and populating the associated group index.
+          while (!(x1 > x)) {
+            groupIndex[j = newIndex[i1] + n0] = k;
+            if (!(filters[j] & zero)) g.value = add(g.value, data[j]);
+            if (++i1 >= n1) break;
+            x1 = key(newValues[i1]);
+          }
+
+          groupIncrement();
+        }
+
+        // Add any remaining old groups that were greater than all new keys.
+        // No incremental reduce is needed; these groups have no new records.
+        // Also record the new index of the old group.
+        while (i0 < k0) {
+          groups[reIndex[i0] = k] = oldGroups[i0++];
+          groupIncrement();
+        }
+
+        // If we added any new groups before any old groups,
+        // update the group index of all the old records.
+        if (k > i0) for (i0 = 0; i0 < n0; ++i0) {
+          groupIndex[i0] = reIndex[groupIndex[i0]];
+        }
+
+        // Modify the update and reset behavior based on the cardinality.
+        // If the cardinality is less than or equal to one, then the groupIndex
+        // is not needed. If the cardinality is zero, then there are no records
+        // and therefore no groups to update or reset. Note that we also must
+        // change the registered listener to point to the new method.
+        j = filterListeners.indexOf(update);
+        if (k > 1) {
+          update = updateMany;
+          reset = resetMany;
+        } else {
+          if (!k && groupAll) {
+            k = 1;
+            groups = [{key: null, value: initial()}];
+          }
+          if (k === 1) {
+            update = updateOne;
+            reset = resetOne;
+          } else {
+            update = crossfilter_null;
+            reset = crossfilter_null;
+          }
+          groupIndex = null;
+        }
+        filterListeners[j] = update;
+
+        // Count the number of added groups,
+        // and widen the group index as needed.
+        function groupIncrement() {
+          if (++k === groupCapacity) {
+            reIndex = crossfilter_arrayWiden(reIndex, groupWidth <<= 1);
+            groupIndex = crossfilter_arrayWiden(groupIndex, groupWidth);
+            groupCapacity = crossfilter_capacity(groupWidth);
+          }
+        }
+      }
+
+      function removeData() {
+        if (k > 1) {
+          var oldK = k,
+              oldGroups = groups,
+              seenGroups = crossfilter_index(oldK, oldK);
+
+          // Filter out non-matches by copying matching group index entries to
+          // the beginning of the array.
+          for (var i = 0, j = 0; i < n; ++i) {
+            if (filters[i]) {
+              seenGroups[groupIndex[j] = groupIndex[i]] = 1;
+              ++j;
+            }
+          }
+
+          // Reassemble groups including only those groups that were referred
+          // to by matching group index entries.  Note the new group index in
+          // seenGroups.
+          groups = [], k = 0;
+          for (i = 0; i < oldK; ++i) {
+            if (seenGroups[i]) {
+              seenGroups[i] = k++;
+              groups.push(oldGroups[i]);
+            }
+          }
+
+          if (k > 1) {
+            // Reindex the group index using seenGroups to find the new index.
+            for (var i = 0; i < j; ++i) groupIndex[i] = seenGroups[groupIndex[i]];
+          } else {
+            groupIndex = null;
+          }
+          filterListeners[filterListeners.indexOf(update)] = k > 1
+              ? (reset = resetMany, update = updateMany)
+              : k === 1 ? (reset = resetOne, update = updateOne)
+              : reset = update = crossfilter_null;
+        } else if (k === 1) {
+          if (groupAll) return;
+          for (var i = 0; i < n; ++i) if (filters[i]) return;
+          groups = [], k = 0;
+          filterListeners[filterListeners.indexOf(update)] =
+          update = reset = crossfilter_null;
+        }
+      }
+
+      // Reduces the specified selected or deselected records.
+      // This function is only used when the cardinality is greater than 1.
+      function updateMany(filterOne, added, removed) {
+        if (filterOne === one || resetNeeded) return;
+
+        var i,
+            k,
+            n,
+            g;
+
+        // Add the added values.
+        for (i = 0, n = added.length; i < n; ++i) {
+          if (!(filters[k = added[i]] & zero)) {
+            g = groups[groupIndex[k]];
+            g.value = reduceAdd(g.value, data[k]);
+          }
+        }
+
+        // Remove the removed values.
+        for (i = 0, n = removed.length; i < n; ++i) {
+          if ((filters[k = removed[i]] & zero) === filterOne) {
+            g = groups[groupIndex[k]];
+            g.value = reduceRemove(g.value, data[k]);
+          }
+        }
+      }
+
+      // Reduces the specified selected or deselected records.
+      // This function is only used when the cardinality is 1.
+      function updateOne(filterOne, added, removed) {
+        if (filterOne === one || resetNeeded) return;
+
+        var i,
+            k,
+            n,
+            g = groups[0];
+
+        // Add the added values.
+        for (i = 0, n = added.length; i < n; ++i) {
+          if (!(filters[k = added[i]] & zero)) {
+            g.value = reduceAdd(g.value, data[k]);
+          }
+        }
+
+        // Remove the removed values.
+        for (i = 0, n = removed.length; i < n; ++i) {
+          if ((filters[k = removed[i]] & zero) === filterOne) {
+            g.value = reduceRemove(g.value, data[k]);
+          }
+        }
+      }
+
+      // Recomputes the group reduce values from scratch.
+      // This function is only used when the cardinality is greater than 1.
+      function resetMany() {
+        var i,
+            g;
+
+        // Reset all group values.
+        for (i = 0; i < k; ++i) {
+          groups[i].value = reduceInitial();
+        }
+
+        // Add any selected records.
+        for (i = 0; i < n; ++i) {
+          if (!(filters[i] & zero)) {
+            g = groups[groupIndex[i]];
+            g.value = reduceAdd(g.value, data[i]);
+          }
+        }
+      }
+
+      // Recomputes the group reduce values from scratch.
+      // This function is only used when the cardinality is 1.
+      function resetOne() {
+        var i,
+            g = groups[0];
+
+        // Reset the singleton group values.
+        g.value = reduceInitial();
+
+        // Add any selected records.
+        for (i = 0; i < n; ++i) {
+          if (!(filters[i] & zero)) {
+            g.value = reduceAdd(g.value, data[i]);
+          }
+        }
+      }
+
+      // Returns the array of group values, in the dimension's natural order.
+      function all() {
+        if (resetNeeded) reset(), resetNeeded = false;
+        return groups;
+      }
+
+      // Returns a new array containing the top K group values, in reduce order.
+      function top(k) {
+        var top = select(all(), 0, groups.length, k);
+        return heap.sort(top, 0, top.length);
+      }
+
+      // Sets the reduce behavior for this group to use the specified functions.
+      // This method lazily recomputes the reduce values, waiting until needed.
+      function reduce(add, remove, initial) {
+        reduceAdd = add;
+        reduceRemove = remove;
+        reduceInitial = initial;
+        resetNeeded = true;
+        return group;
+      }
+
+      // A convenience method for reducing by count.
+      function reduceCount() {
+        return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
+      }
+
+      // A convenience method for reducing by sum(value).
+      function reduceSum(value) {
+        return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
+      }
+
+      // Sets the reduce order, using the specified accessor.
+      function order(value) {
+        select = heapselect_by(valueOf);
+        heap = heap_by(valueOf);
+        function valueOf(d) { return value(d.value); }
+        return group;
+      }
+
+      // A convenience method for natural ordering by reduce value.
+      function orderNatural() {
+        return order(crossfilter_identity);
+      }
+
+      // Returns the cardinality of this group, irrespective of any filters.
+      function size() {
+        return k;
+      }
+
+      // Removes this group and associated event listeners.
+      function dispose() {
+        var i = filterListeners.indexOf(update);
+        if (i >= 0) filterListeners.splice(i, 1);
+        i = indexListeners.indexOf(add);
+        if (i >= 0) indexListeners.splice(i, 1);
+        i = removeDataListeners.indexOf(removeData);
+        if (i >= 0) removeDataListeners.splice(i, 1);
+        return group;
+      }
+
+      return reduceCount().orderNatural();
+    }
+
+    // A convenience function for generating a singleton group.
+    function groupAll() {
+      var g = group(crossfilter_null), all = g.all;
+      delete g.all;
+      delete g.top;
+      delete g.order;
+      delete g.orderNatural;
+      delete g.size;
+      g.value = function() { return all()[0].value; };
+      return g;
+    }
+
+    // Removes this dimension and associated groups and event listeners.
+    function dispose() {
+      dimensionGroups.forEach(function(group) { group.dispose(); });
+      var i = dataListeners.indexOf(preAdd);
+      if (i >= 0) dataListeners.splice(i, 1);
+      i = dataListeners.indexOf(postAdd);
+      if (i >= 0) dataListeners.splice(i, 1);
+      i = removeDataListeners.indexOf(removeData);
+      if (i >= 0) removeDataListeners.splice(i, 1);
+      m &= zero;
+      return filterAll();
+    }
+
+    return dimension;
+  }
+
+  // A convenience method for groupAll on a dummy dimension.
+  // This implementation can be optimized since it always has cardinality 1.
+  function groupAll() {
+    var group = {
+      reduce: reduce,
+      reduceCount: reduceCount,
+      reduceSum: reduceSum,
+      value: value,
+      dispose: dispose,
+      remove: dispose // for backwards-compatibility
+    };
+
+    var reduceValue,
+        reduceAdd,
+        reduceRemove,
+        reduceInitial,
+        resetNeeded = true;
+
+    // The group listens to the crossfilter for when any dimension changes, so
+    // that it can update the reduce value. It must also listen to the parent
+    // dimension for when data is added.
+    filterListeners.push(update);
+    dataListeners.push(add);
+
+    // For consistency; actually a no-op since resetNeeded is true.
+    add(data, 0, n);
+
+    // Incorporates the specified new values into this group.
+    function add(newData, n0) {
+      var i;
+
+      if (resetNeeded) return;
+
+      // Add the added values.
+      for (i = n0; i < n; ++i) {
+        if (!filters[i]) {
+          reduceValue = reduceAdd(reduceValue, data[i]);
+        }
+      }
+    }
+
+    // Reduces the specified selected or deselected records.
+    function update(filterOne, added, removed) {
+      var i,
+          k,
+          n;
+
+      if (resetNeeded) return;
+
+      // Add the added values.
+      for (i = 0, n = added.length; i < n; ++i) {
+        if (!filters[k = added[i]]) {
+          reduceValue = reduceAdd(reduceValue, data[k]);
+        }
+      }
+
+      // Remove the removed values.
+      for (i = 0, n = removed.length; i < n; ++i) {
+        if (filters[k = removed[i]] === filterOne) {
+          reduceValue = reduceRemove(reduceValue, data[k]);
+        }
+      }
+    }
+
+    // Recomputes the group reduce value from scratch.
+    function reset() {
+      var i;
+
+      reduceValue = reduceInitial();
+
+      for (i = 0; i < n; ++i) {
+        if (!filters[i]) {
+          reduceValue = reduceAdd(reduceValue, data[i]);
+        }
+      }
+    }
+
+    // Sets the reduce behavior for this group to use the specified functions.
+    // This method lazily recomputes the reduce value, waiting until needed.
+    function reduce(add, remove, initial) {
+      reduceAdd = add;
+      reduceRemove = remove;
+      reduceInitial = initial;
+      resetNeeded = true;
+      return group;
+    }
+
+    // A convenience method for reducing by count.
+    function reduceCount() {
+      return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
+    }
+
+    // A convenience method for reducing by sum(value).
+    function reduceSum(value) {
+      return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
+    }
+
+    // Returns the computed reduce value.
+    function value() {
+      if (resetNeeded) reset(), resetNeeded = false;
+      return reduceValue;
+    }
+
+    // Removes this group and associated event listeners.
+    function dispose() {
+      var i = filterListeners.indexOf(update);
+      if (i >= 0) filterListeners.splice(i);
+      i = dataListeners.indexOf(add);
+      if (i >= 0) dataListeners.splice(i);
+      return group;
+    }
+
+    return reduceCount();
+  }
+
+  // Returns the number of records in this crossfilter, irrespective of any filters.
+  function size() {
+    return n;
+  }
+
+  return arguments.length
+      ? add(arguments[0])
+      : crossfilter;
+}
+
+// Returns an array of size n, big enough to store ids up to m.
+function crossfilter_index(n, m) {
+  return (m < 0x101
+      ? crossfilter_array8 : m < 0x10001
+      ? crossfilter_array16
+      : crossfilter_array32)(n);
+}
+
+// Constructs a new array of size n, with sequential values from 0 to n - 1.
+function crossfilter_range(n) {
+  var range = crossfilter_index(n, n);
+  for (var i = -1; ++i < n;) range[i] = i;
+  return range;
+}
+
+function crossfilter_capacity(w) {
+  return w === 8
+      ? 0x100 : w === 16
+      ? 0x10000
+      : 0x100000000;
+}
+})(typeof exports !== 'undefined' && exports || this);
+
+},{}],18:[function(require,module,exports){
+module.exports = require("./crossfilter").crossfilter;
+
+},{"./crossfilter":17}],19:[function(require,module,exports){
+!function() {
+  var topojson = {
+    version: "1.6.18",
+    mesh: function(topology) { return object(topology, meshArcs.apply(this, arguments)); },
+    meshArcs: meshArcs,
+    merge: function(topology) { return object(topology, mergeArcs.apply(this, arguments)); },
+    mergeArcs: mergeArcs,
+    feature: featureOrCollection,
+    neighbors: neighbors,
+    presimplify: presimplify
+  };
+
+  function stitchArcs(topology, arcs) {
+    var stitchedArcs = {},
+        fragmentByStart = {},
+        fragmentByEnd = {},
+        fragments = [],
+        emptyIndex = -1;
+
+    // Stitch empty arcs first, since they may be subsumed by other arcs.
+    arcs.forEach(function(i, j) {
+      var arc = topology.arcs[i < 0 ? ~i : i], t;
+      if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
+        t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
+      }
+    });
+
+    arcs.forEach(function(i) {
+      var e = ends(i),
+          start = e[0],
+          end = e[1],
+          f, g;
+
+      if (f = fragmentByEnd[start]) {
+        delete fragmentByEnd[f.end];
+        f.push(i);
+        f.end = end;
+        if (g = fragmentByStart[end]) {
+          delete fragmentByStart[g.start];
+          var fg = g === f ? f : f.concat(g);
+          fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
+        } else {
+          fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
+        }
+      } else if (f = fragmentByStart[end]) {
+        delete fragmentByStart[f.start];
+        f.unshift(i);
+        f.start = start;
+        if (g = fragmentByEnd[start]) {
+          delete fragmentByEnd[g.end];
+          var gf = g === f ? f : g.concat(f);
+          fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
+        } else {
+          fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
+        }
+      } else {
+        f = [i];
+        fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
+      }
+    });
+
+    function ends(i) {
+      var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
+      if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
+      else p1 = arc[arc.length - 1];
+      return i < 0 ? [p1, p0] : [p0, p1];
+    }
+
+    function flush(fragmentByEnd, fragmentByStart) {
+      for (var k in fragmentByEnd) {
+        var f = fragmentByEnd[k];
+        delete fragmentByStart[f.start];
+        delete f.start;
+        delete f.end;
+        f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
+        fragments.push(f);
+      }
+    }
+
+    flush(fragmentByEnd, fragmentByStart);
+    flush(fragmentByStart, fragmentByEnd);
+    arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });
+
+    return fragments;
+  }
+
+  function meshArcs(topology, o, filter) {
+    var arcs = [];
+
+    if (arguments.length > 1) {
+      var geomsByArc = [],
+          geom;
+
+      function arc(i) {
+        var j = i < 0 ? ~i : i;
+        (geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
+      }
+
+      function line(arcs) {
+        arcs.forEach(arc);
+      }
+
+      function polygon(arcs) {
+        arcs.forEach(line);
+      }
+
+      function geometry(o) {
+        if (o.type === "GeometryCollection") o.geometries.forEach(geometry);
+        else if (o.type in geometryType) geom = o, geometryType[o.type](o.arcs);
+      }
+
+      var geometryType = {
+        LineString: line,
+        MultiLineString: polygon,
+        Polygon: polygon,
+        MultiPolygon: function(arcs) { arcs.forEach(polygon); }
+      };
+
+      geometry(o);
+
+      geomsByArc.forEach(arguments.length < 3
+          ? function(geoms) { arcs.push(geoms[0].i); }
+          : function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); });
+    } else {
+      for (var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push(i);
+    }
+
+    return {type: "MultiLineString", arcs: stitchArcs(topology, arcs)};
+  }
+
+  function mergeArcs(topology, objects) {
+    var polygonsByArc = {},
+        polygons = [],
+        components = [];
+
+    objects.forEach(function(o) {
+      if (o.type === "Polygon") register(o.arcs);
+      else if (o.type === "MultiPolygon") o.arcs.forEach(register);
+    });
+
+    function register(polygon) {
+      polygon.forEach(function(ring) {
+        ring.forEach(function(arc) {
+          (polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
+        });
+      });
+      polygons.push(polygon);
+    }
+
+    function exterior(ring) {
+      return cartesianRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]) > 0; // TODO allow spherical?
+    }
+
+    polygons.forEach(function(polygon) {
+      if (!polygon._) {
+        var component = [],
+            neighbors = [polygon];
+        polygon._ = 1;
+        components.push(component);
+        while (polygon = neighbors.pop()) {
+          component.push(polygon);
+          polygon.forEach(function(ring) {
+            ring.forEach(function(arc) {
+              polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
+                if (!polygon._) {
+                  polygon._ = 1;
+                  neighbors.push(polygon);
+                }
+              });
+            });
+          });
+        }
+      }
+    });
+
+    polygons.forEach(function(polygon) {
+      delete polygon._;
+    });
+
+    return {
+      type: "MultiPolygon",
+      arcs: components.map(function(polygons) {
+        var arcs = [];
+
+        // Extract the exterior (unique) arcs.
+        polygons.forEach(function(polygon) {
+          polygon.forEach(function(ring) {
+            ring.forEach(function(arc) {
+              if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
+                arcs.push(arc);
+              }
+            });
+          });
+        });
+
+        // Stitch the arcs into one or more rings.
+        arcs = stitchArcs(topology, arcs);
+
+        // If more than one ring is returned,
+        // at most one of these rings can be the exterior;
+        // this exterior ring has the same winding order
+        // as any exterior ring in the original polygons.
+        if ((n = arcs.length) > 1) {
+          var sgn = exterior(polygons[0][0]);
+          for (var i = 0, t; i < n; ++i) {
+            if (sgn === exterior(arcs[i])) {
+              t = arcs[0], arcs[0] = arcs[i], arcs[i] = t;
+              break;
+            }
+          }
+        }
+
+        return arcs;
+      })
+    };
+  }
+
+  function featureOrCollection(topology, o) {
+    return o.type === "GeometryCollection" ? {
+      type: "FeatureCollection",
+      features: o.geometries.map(function(o) { return feature(topology, o); })
+    } : feature(topology, o);
+  }
+
+  function feature(topology, o) {
+    var f = {
+      type: "Feature",
+      id: o.id,
+      properties: o.properties || {},
+      geometry: object(topology, o)
+    };
+    if (o.id == null) delete f.id;
+    return f;
+  }
+
+  function object(topology, o) {
+    var absolute = transformAbsolute(topology.transform),
+        arcs = topology.arcs;
+
+    function arc(i, points) {
+      if (points.length) points.pop();
+      for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, p; k < n; ++k) {
+        points.push(p = a[k].slice());
+        absolute(p, k);
+      }
+      if (i < 0) reverse(points, n);
+    }
+
+    function point(p) {
+      p = p.slice();
+      absolute(p, 0);
+      return p;
+    }
+
+    function line(arcs) {
+      var points = [];
+      for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
+      if (points.length < 2) points.push(points[0].slice());
+      return points;
+    }
+
+    function ring(arcs) {
+      var points = line(arcs);
+      while (points.length < 4) points.push(points[0].slice());
+      return points;
+    }
+
+    function polygon(arcs) {
+      return arcs.map(ring);
+    }
+
+    function geometry(o) {
+      var t = o.type;
+      return t === "GeometryCollection" ? {type: t, geometries: o.geometries.map(geometry)}
+          : t in geometryType ? {type: t, coordinates: geometryType[t](o)}
+          : null;
+    }
+
+    var geometryType = {
+      Point: function(o) { return point(o.coordinates); },
+      MultiPoint: function(o) { return o.coordinates.map(point); },
+      LineString: function(o) { return line(o.arcs); },
+      MultiLineString: function(o) { return o.arcs.map(line); },
+      Polygon: function(o) { return polygon(o.arcs); },
+      MultiPolygon: function(o) { return o.arcs.map(polygon); }
+    };
+
+    return geometry(o);
+  }
+
+  function reverse(array, n) {
+    var t, j = array.length, i = j - n; while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
+  }
+
+  function bisect(a, x) {
+    var lo = 0, hi = a.length;
+    while (lo < hi) {
+      var mid = lo + hi >>> 1;
+      if (a[mid] < x) lo = mid + 1;
+      else hi = mid;
+    }
+    return lo;
+  }
+
+  function neighbors(objects) {
+    var indexesByArc = {}, // arc index -> array of object indexes
+        neighbors = objects.map(function() { return []; });
+
+    function line(arcs, i) {
+      arcs.forEach(function(a) {
+        if (a < 0) a = ~a;
+        var o = indexesByArc[a];
+        if (o) o.push(i);
+        else indexesByArc[a] = [i];
+      });
+    }
+
+    function polygon(arcs, i) {
+      arcs.forEach(function(arc) { line(arc, i); });
+    }
+
+    function geometry(o, i) {
+      if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); });
+      else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
+    }
+
+    var geometryType = {
+      LineString: line,
+      MultiLineString: polygon,
+      Polygon: polygon,
+      MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); }
+    };
+
+    objects.forEach(geometry);
+
+    for (var i in indexesByArc) {
+      for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
+        for (var k = j + 1; k < m; ++k) {
+          var ij = indexes[j], ik = indexes[k], n;
+          if ((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
+          if ((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
+        }
+      }
+    }
+
+    return neighbors;
+  }
+
+  function presimplify(topology, triangleArea) {
+    var absolute = transformAbsolute(topology.transform),
+        relative = transformRelative(topology.transform),
+        heap = minAreaHeap();
+
+    if (!triangleArea) triangleArea = cartesianTriangleArea;
+
+    topology.arcs.forEach(function(arc) {
+      var triangles = [],
+          maxArea = 0,
+          triangle;
+
+      // To store each point’s effective area, we create a new array rather than
+      // extending the passed-in point to workaround a Chrome/V8 bug (getting
+      // stuck in smi mode). For midpoints, the initial effective area of
+      // Infinity will be computed in the next step.
+      for (var i = 0, n = arc.length, p; i < n; ++i) {
+        p = arc[i];
+        absolute(arc[i] = [p[0], p[1], Infinity], i);
+      }
+
+      for (var i = 1, n = arc.length - 1; i < n; ++i) {
+        triangle = arc.slice(i - 1, i + 2);
+        triangle[1][2] = triangleArea(triangle);
+        triangles.push(triangle);
+        heap.push(triangle);
+      }
+
+      for (var i = 0, n = triangles.length; i < n; ++i) {
+        triangle = triangles[i];
+        triangle.previous = triangles[i - 1];
+        triangle.next = triangles[i + 1];
+      }
+
+      while (triangle = heap.pop()) {
+        var previous = triangle.previous,
+            next = triangle.next;
+
+        // If the area of the current point is less than that of the previous point
+        // to be eliminated, use the latter's area instead. This ensures that the
+        // current point cannot be eliminated without eliminating previously-
+        // eliminated points.
+        if (triangle[1][2] < maxArea) triangle[1][2] = maxArea;
+        else maxArea = triangle[1][2];
+
+        if (previous) {
+          previous.next = next;
+          previous[2] = triangle[2];
+          update(previous);
+        }
+
+        if (next) {
+          next.previous = previous;
+          next[0] = triangle[0];
+          update(next);
+        }
+      }
+
+      arc.forEach(relative);
+    });
+
+    function update(triangle) {
+      heap.remove(triangle);
+      triangle[1][2] = triangleArea(triangle);
+      heap.push(triangle);
+    }
+
+    return topology;
+  };
+
+  function cartesianRingArea(ring) {
+    var i = -1,
+        n = ring.length,
+        a,
+        b = ring[n - 1],
+        area = 0;
+
+    while (++i < n) {
+      a = b;
+      b = ring[i];
+      area += a[0] * b[1] - a[1] * b[0];
+    }
+
+    return area * .5;
+  }
+
+  function cartesianTriangleArea(triangle) {
+    var a = triangle[0], b = triangle[1], c = triangle[2];
+    return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]));
+  }
+
+  function compareArea(a, b) {
+    return a[1][2] - b[1][2];
+  }
+
+  function minAreaHeap() {
+    var heap = {},
+        array = [],
+        size = 0;
+
+    heap.push = function(object) {
+      up(array[object._ = size] = object, size++);
+      return size;
+    };
+
+    heap.pop = function() {
+      if (size <= 0) return;
+      var removed = array[0], object;
+      if (--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
+      return removed;
+    };
+
+    heap.remove = function(removed) {
+      var i = removed._, object;
+      if (array[i] !== removed) return; // invalid request
+      if (i !== --size) object = array[size], (compareArea(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
+      return i;
+    };
+
+    function up(object, i) {
+      while (i > 0) {
+        var j = ((i + 1) >> 1) - 1,
+            parent = array[j];
+        if (compareArea(object, parent) >= 0) break;
+        array[parent._ = i] = parent;
+        array[object._ = i = j] = object;
+      }
+    }
+
+    function down(object, i) {
+      while (true) {
+        var r = (i + 1) << 1,
+            l = r - 1,
+            j = i,
+            child = array[j];
+        if (l < size && compareArea(array[l], child) < 0) child = array[j = l];
+        if (r < size && compareArea(array[r], child) < 0) child = array[j = r];
+        if (j === i) break;
+        array[child._ = i] = child;
+        array[object._ = i = j] = object;
+      }
+    }
+
+    return heap;
+  }
+
+  function transformAbsolute(transform) {
+    if (!transform) return noop;
+    var x0,
+        y0,
+        kx = transform.scale[0],
+        ky = transform.scale[1],
+        dx = transform.translate[0],
+        dy = transform.translate[1];
+    return function(point, i) {
+      if (!i) x0 = y0 = 0;
+      point[0] = (x0 += point[0]) * kx + dx;
+      point[1] = (y0 += point[1]) * ky + dy;
+    };
+  }
+
+  function transformRelative(transform) {
+    if (!transform) return noop;
+    var x0,
+        y0,
+        kx = transform.scale[0],
+        ky = transform.scale[1],
+        dx = transform.translate[0],
+        dy = transform.translate[1];
+    return function(point, i) {
+      if (!i) x0 = y0 = 0;
+      var x1 = (point[0] - dx) / kx | 0,
+          y1 = (point[1] - dy) / ky | 0;
+      point[0] = x1 - x0;
+      point[1] = y1 - y0;
+      x0 = x1;
+      y0 = y1;
+    };
+  }
+
+  function noop() {}
+
+  if (typeof define === "function" && define.amd) define(topojson);
+  else if (typeof module === "object" && module.exports) module.exports = topojson;
+  else this.topojson = topojson;
+}();
+
+},{}]},{},[1]);
diff --git a/dist/map-tools.min.js b/dist/map-tools.min.js
new file mode 100644
index 0000000..f5dc0e7
--- /dev/null
+++ b/dist/map-tools.min.js
@@ -0,0 +1,2 @@
+/* map-tools.js 0.4.0 MIT License. 2015 Yago Ferrer <yago.ferrer@gmail.com> */
+!function r(t,n,e){function o(i,u){if(!n[i]){if(!t[i]){var s="function"==typeof require&&require;if(!u&&s)return s(i,!0);if(a)return a(i,!0);var f=new Error("Cannot find module '"+i+"'");throw f.code="MODULE_NOT_FOUND",f}var c=n[i]={exports:{}};t[i][0].call(c.exports,function(r){var n=t[i][1][r];return o(n?n:r)},c,c.exports,r,t,n,e)}return n[i].exports}for(var a="function"==typeof require&&require,i=0;i<e.length;i++)o(e[i]);return o}({1:[function(r){"use strict";window.GMP=r("map-tools/index")(window)},{"map-tools/index":12}],2:[function(r,t){"use strict";{var n=r("topojson"),e=r("map-tools/utils");r("crossfilter")}t.exports=function(t,o){function a(r,n){var a,i;for(i in r)if(r.hasOwnProperty(i)){a=r[i];var u=a.k;a.k.uid=e.createUid(),Object.defineProperty(a,"data",{value:u,enumerable:!0,writable:!1,configurable:!1}),n&&(n.filters&&(t.GMP.maps[o.id].json.filter||s("json",n.filters),t.GMP.maps[o.id].json.crossfilter.add([a.data])),n.style&&o.instance.data.overrideStyle(a,n.style)),t.GMP.maps[o.id].json.all[a.data.uid]=a}}function i(r,e){var i,u,s,f;for(f in e)e.hasOwnProperty(f)&&(i=e[f],u=n.feature(r,r.objects[i.object]),s=o.instance.data.addGeoJson(u),a(s,i),t.GMP.maps[o.id].json.groups[i.object]=s);return s}function u(r,t){var n=o.instance.data.addGeoJson(r,t);return a(n,t),n}var s=r("map-tools/addFilter")(t,o);return{addGeoJson:u,addTopoJson:i}}},{crossfilter:18,"map-tools/addFilter":3,"map-tools/utils":16,topojson:19}],3:[function(r,t){var n=r("crossfilter");t.exports=function(r,t){function e(r){return function(t){return t[r]}}function o(o,a){r.GMP.maps[t.id][o].crossfilter=r.GMP.maps[t.id][o].crossfilter||n([]),r.GMP.maps[t.id][o].filter=r.GMP.maps[t.id][o].filter||{};var i,u;"string"==typeof a&&(a=[a]);for(i in a)u=a[i],"string"==typeof u?r.GMP.maps[t.id][o].filter[u]=r.GMP.maps[t.id][o].crossfilter.dimension(e(u)):r.GMP.maps[t.id][o].filter[Object.keys(u)[0]]=r.GMP.maps[t.id][o].crossfilter.dimension(u[Object.keys(u)[0]])}return o}},{crossfilter:18}],4:[function(r,t){"use strict";r("map-tools/utils");t.exports=function(t,n){function e(r){return r.el?t.document.querySelector(r.el):r.id?t.document.getElementById(r.id):void 0}function o(r,o){o=o||function(){};var a=u.mapOptions(r);r.id=r.id||r.el.substring(1),n.id=r.id,n.options=r,n.instance=new t.google.maps.Map(e(r),a),t.GMP.maps[n.id].instance=n.instance,t.google.maps.event.addListenerOnce(n.instance,"idle",function(){o(!1,n.instance)})}function a(r,t){return!r||r&&"object"!=typeof r?(t(new Error("You must pass a valid first parameter: options")),!1):r.id||r.el?r.lat&&r.lng?!0:(t(new Error('You must pass valid "lat" (latitude) and "lng" (longitude) values')),!1):(t(new Error('You must pass an "id" or a "el" property values')),!1)}function i(r,e){if(a(r,e)){var i=r.id||r.el.substring(1);t.GMP.maps=t.GMP.maps||{},t.GMP.maps[i]={create:function(){o(this.arguments,e)},arguments:r},t.GMP.maps[i].markers=t.GMP.maps[i].markers||{},t.GMP.maps[i].json=t.GMP.maps[i].json||{all:{},groups:{}},n.markers=t.GMP.maps[i].markers,n.json=t.GMP.maps[i].json,r.async!==!1||r.sync===!0?u.load(i,r):t.GMP.maps[i].create()}}var u=r("map-tools/gmaps.js")(t);return{load:i}}},{"map-tools/gmaps.js":10,"map-tools/utils":16}],5:[function(r,t){"use strict";var n=r("map-tools/utils");t.exports=function(t,e){function o(r,o){var a;r.map=e.instance,r.position=new t.google.maps.LatLng(r.lat,r.lng);var s=r.group||!1;if(o&&o.group&&(s=o.group||s),o)for(a in o)o.hasOwnProperty(a)&&(o.filters||(r[a]=o[a]));if(s&&e.markers&&e.markers.groupOptions&&e.markers.groupOptions[s])for(var f in e.markers.groupOptions[s])r[f]=e.markers.groupOptions[s][f];r.data=r.data||{},r.data&&(r.data.uid=n.createUid()),o&&o.filters&&(t.GMP.maps[e.id].markers.filter||u("markers",o.filters),e.markers.crossfilter.add([r.data]));var c=new t.google.maps.Marker(r);return r.move&&c.setAnimation(t.google.maps.Animation[r.move.toUpperCase()]),r.bubble&&i.create(c,r.bubble,e.instance),s&&(e.markers.groups=t.GMP.maps[e.id].markers.groups||{},e.markers.groups[s]=t.GMP.maps[e.id].markers.groups[s]||[],e.markers.groups[s].push(c)),e.markers.all=t.GMP.maps[e.id].markers.all||{},e.markers.all[r.data.uid]=c,c}function a(r,t){if(r.length&&r.length>=1){var n,e=[];for(var a in r)n=o(r[a],t),e.push(n);return e}return o(r,t)}var i=r("map-tools/bubble")(t),u=r("map-tools/addFilter")(t,e);return a}},{"map-tools/addFilter":3,"map-tools/bubble":7,"map-tools/utils":16}],6:[function(r,t){"use strict";var n=(r("map-tools/utils"),r("map-tools/config"));t.exports=function(r,t){function e(r){return r.toUpperCase().match(/\S+/g).join("_")}function o(r){return r.replace(/-(.)/g,function(r,t){return t.toUpperCase()})}function a(r){var t=window.document.createElement("div");return t.innerHTML=r,t}function i(r){var i,u,s,f;if(r.position=r.position||n.panelPosition,r.templateURL){var c=new XMLHttpRequest;c.open("GET",r.templateURL,!1),c.send(null),u=a(c.responseText)}if("string"==typeof r.template&&(u=a(r.template)),r.position&&(r.position=e(r.position),i=google.maps.ControlPosition[r.position]),"object"==typeof r.style)for(f in r.style){var l=o(f);u.style[l]=r.style[f]}if(r.events)for(s in r.events){var p=s.match(/\S+/g),d=p.splice(-1),m=p.join(" "),v=u.querySelectorAll(m);[].forEach.call(v,function(t){google.maps.event.addDomListener(t,d,r.events[s])})}return t.instance.controls[i].push(u),u}return i}},{"map-tools/config":8,"map-tools/utils":16}],7:[function(r,t){"use strict";t.exports=function(r){function t(t,n,e){var o=n.event||"click";n.content=n.content.replace(/\{(\w+)\}/g,function(r,n){return t.data[n]||""}),t.bubble.instance=new r.google.maps.InfoWindow(n),r.google.maps.event.addListener(t,o,function(){t.bubble.instance.open(e,t)})}return{create:t}}},{}],8:[function(r,t){"use strict";t.exports={version:"3.18",zoom:8,customMapOptions:["id","lat","lng","type"],customMarkerOptions:["lat","lng","move","bubble"],panelPosition:"TOP_LEFT"}},{}],9:[function(r,t){"use strict";t.exports=function(r,t){function n(t,n){return n.custom&&(n.custom.move&&t.setAnimation(r.google.maps.Animation[n.custom.move.toUpperCase()]),n.custom.lat&&n.custom.lng&&t.setPosition(new r.google.maps.LatLng(n.custom.lat,n.custom.lng)),n.custom.bubble&&n.custom.bubble.content&&t.bubble.instance.setContent(n.custom.bubble.content)),n.defaults&&t.setOptions(n.defaults),t}function e(e,o){return e.data&&e.data.uid?n(e,o):e.uid&&r.GMP.maps[t.id].markers.all[e.uid]?n(r.GMP.maps[t.id].markers.all[e.uid],o):void 0}return e}},{}],10:[function(r,t){"use strict";var n=r("map-tools/config"),e=r("map-tools/utils");t.exports=function(r){function t(t,e){var o=e.version||n.version,a=r.document.createElement("script");return a.type="text/javascript",a.src="//maps.googleapis.com/maps/api/js?v="+o+"&callback=GMP.maps."+t+".create",r.document.body.appendChild(a)}function o(t){var o=e.clone(t,n.customMapOptions);return o.zoom=t.zoom||n.zoom,t.lat&&t.lng&&(o.center=new r.google.maps.LatLng(t.lat,t.lng)),t.type&&(o.mapTypeId=r.google.maps.MapTypeId[t.type]||!1),o}return{load:t,mapOptions:o}}},{"map-tools/config":8,"map-tools/utils":16}],11:[function(r,t){"use strict";var n=r("map-tools/utils"),e=r("map-tools/config");t.exports=function(t,o){function a(r,n){t.GMP.maps[o.id].markers=t.GMP.maps[o.id].markers||{},t.GMP.maps[o.id].markers.groups=t.GMP.maps[o.id].markers.groups||[],t.GMP.maps[o.id].markers.groupOptions=t.GMP.maps[o.id].markers.groupOptions||{},t.GMP.maps[o.id].markers.groupOptions[r]=n}function i(r,a){var i,s,f=[],c=n.prepareOptions(a,e.customMarkerOptions);if(t.GMP.maps[o.id].markers.groups&&t.GMP.maps[o.id].markers.groups[r])for(s in t.GMP.maps[o.id].markers.groups[r])t.GMP.maps[o.id].markers.groups[r].hasOwnProperty(s)&&(i=u(t.GMP.maps[o.id].markers.groups[r][s],c),f.push(i));return f}var u=r("map-tools/findUpdateMarker")(t,o);return{addGroup:a,updateGroup:i}}},{"map-tools/config":8,"map-tools/findUpdateMarker":9,"map-tools/utils":16}],12:[function(r,t){"use strict";t.exports=function(t){function n(n,e){var o=this;this.addMarker=r("map-tools/addMarker")(t,o),this.addTopoJson=r("map-tools/addFeature")(t,o).addTopoJson,this.addGeoJson=r("map-tools/addFeature")(t,o).addGeoJson,this.updateFeature=r("map-tools/updateFeature")(t,o),this.addPanel=r("map-tools/addPanel")(t,o),this.updateMarker=r("map-tools/updateMarker")(t,o),this.addGroup=r("map-tools/groups")(t,o).addGroup,this.updateGroup=r("map-tools/groups")(t,o).updateGroup,this.updateMap=r("map-tools/updateMap")(t,o);var a=r("map-tools/addMap")(t,o);return t.onload=a.load(n,e),this}return n.prototype.instance=!1,n}},{"map-tools/addFeature":2,"map-tools/addMap":4,"map-tools/addMarker":5,"map-tools/addPanel":6,"map-tools/groups":11,"map-tools/updateFeature":13,"map-tools/updateMap":14,"map-tools/updateMarker":15}],13:[function(r,t){"use strict";r("map-tools/utils");t.exports=function(r,t){function n(r,n){if("function"==typeof n){var e=n.call(r);return t.instance.data.overrideStyle(r,e)}t.instance.data.overrideStyle(r,n)}function e(r,t){var n=Object.prototype.toString.call(r);if("[object Array]"===n){var e,a;for(a in r)e=r[a],o(e,t)}"[object Object]"===n&&o(r,t)}function o(n,e){return n.data&&n.data.uid?a(n,e):n.uid&&r.GMP.maps[t.id].json&&r.GMP.maps[t.id].json.all[n.uid]?a(r.GMP.maps[t.id].json.all[n.uid],e):void 0}function a(r,t){t.style&&n(r,t.style)}return e}},{"map-tools/utils":16}],14:[function(r,t){"use strict";t.exports=function(t,n){function e(r){var t=o.mapOptions(r);return n.instance.setOptions(t)}var o=r("map-tools/gmaps.js")(t);return e}},{"map-tools/gmaps.js":10}],15:[function(r,t){"use strict";var n=r("map-tools/utils"),e=r("map-tools/config");t.exports=function(t,o){function a(r,t){var o=Object.prototype.toString.call(r),a=n.prepareOptions(t,e.customMarkerOptions);if("[object Object]"===o)return i(r,a);if("[object Array]"===o){var u,s,f,c=[];for(f in r)r.hasOwnProperty(f)&&(u=r[f],s=i(u,a),c.push(s));return c}}var i=r("map-tools/findUpdateMarker")(t,o);return a}},{"map-tools/config":8,"map-tools/findUpdateMarker":9,"map-tools/utils":16}],16:[function(r,t){"use strict";function n(r,t){var e,o,a;e=Array.isArray(r)?[]:{};for(a in r)r.hasOwnProperty(a)&&(!t||t&&-1===t.indexOf(a))&&(o=r[a],e[a]="object"==typeof o?n(o):o);return e}function e(){return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(r){var t,n;return t=16*Math.random()|0,n="x"===r?t:3&t|8,n.toString(16)})}function o(r,t){var n,e={};for(n in r)r.hasOwnProperty(n)&&(t.indexOf(n)>-1?(e.custom=e.custom||{},e.custom[n]=r[n]):(e.defaults=e.defaults||{},e.defaults[n]=r[n]));return e}t.exports={clone:n,createUid:e,prepareOptions:o}},{}],17:[function(r,t,n){!function(r){function t(r){return r}function n(r,t){for(var n=0,e=t.length,o=new Array(e);e>n;++n)o[n]=r[t[n]];return o}function e(r){function t(t,n,e,o){for(;o>e;){var a=e+o>>>1;r(t[a])<n?e=a+1:o=a}return e}function n(t,n,e,o){for(;o>e;){var a=e+o>>>1;n<r(t[a])?o=a:e=a+1}return e}return n.right=n,n.left=t,n}function o(r){function t(r,t,n){for(var o=n-t,a=(o>>>1)+1;--a>0;)e(r,a,o,t);return r}function n(r,t,n){for(var o,a=n-t;--a>0;)o=r[t],r[t]=r[t+a],r[t+a]=o,e(r,1,a,t);return r}function e(t,n,e,o){for(var a,i=t[--o+n],u=r(i);(a=n<<1)<=e&&(e>a&&r(t[o+a])>r(t[o+a+1])&&a++,!(u<=r(t[o+a])));)t[o+n]=t[o+a],n=a;t[o+n]=i}return t.sort=n,t}function a(r){function t(t,e,o,a){var i,u,s,f,c=new Array(a=Math.min(o-e,a));for(u=0;a>u;++u)c[u]=t[e++];if(n(c,0,a),o>e){i=r(c[0]);do(s=r(f=t[e])>i)&&(c[0]=f,i=r(n(c,0,a)[0]));while(++e<o)}return c}var n=o(r);return t}function i(r){function t(t,n,e){for(var o=n+1;e>o;++o){for(var a=o,i=t[o],u=r(i);a>n&&r(t[a-1])>u;--a)t[a]=t[a-1];t[a]=i}return t}return t}function u(r){function t(r,t,o){return(A>o-t?e:n)(r,t,o)}function n(n,e,o){var a,i=(o-e)/6|0,u=e+i,s=o-1-i,f=e+o-1>>1,c=f-i,l=f+i,p=n[u],d=r(p),m=n[c],v=r(m),g=n[f],h=r(g),y=n[l],M=r(y),b=n[s],x=r(b);d>v&&(a=p,p=m,m=a,a=d,d=v,v=a),M>x&&(a=y,y=b,b=a,a=M,M=x,x=a),d>h&&(a=p,p=g,g=a,a=d,d=h,h=a),v>h&&(a=m,m=g,g=a,a=v,v=h,h=a),d>M&&(a=p,p=y,y=a,a=d,d=M,M=a),h>M&&(a=g,g=y,y=a,a=h,h=M,M=a),v>x&&(a=m,m=b,b=a,a=v,v=x,x=a),v>h&&(a=m,m=g,g=a,a=v,v=h,h=a),M>x&&(a=y,y=b,b=a,a=M,M=x,x=a);var P=m,k=v,G=y,O=M;n[u]=p,n[c]=n[e],n[f]=g,n[l]=n[o-1],n[s]=b;var E=e+1,w=o-2,j=O>=k&&k>=O;if(j)for(var A=E;w>=A;++A){var L=n[A],S=r(L);if(k>S)A!==E&&(n[A]=n[E],n[E]=L),++E;else if(S>k)for(;;){var U=r(n[w]);{if(!(U>k)){if(k>U){n[A]=n[E],n[E++]=n[w],n[w--]=L;break}n[A]=n[w],n[w--]=L;break}w--}}}else for(var A=E;w>=A;A++){var L=n[A],S=r(L);if(k>S)A!==E&&(n[A]=n[E],n[E]=L),++E;else if(S>O)for(;;){var U=r(n[w]);{if(!(U>O)){k>U?(n[A]=n[E],n[E++]=n[w],n[w--]=L):(n[A]=n[w],n[w--]=L);break}if(w--,A>w)break}}}if(n[e]=n[E-1],n[E-1]=P,n[o-1]=n[w+1],n[w+1]=G,t(n,e,E-1),t(n,w+2,o),j)return n;if(u>E&&w>s){for(var _,U;(_=r(n[E]))<=k&&_>=k;)++E;for(;(U=r(n[w]))<=O&&U>=O;)--w;for(var A=E;w>=A;A++){var L=n[A],S=r(L);if(k>=S&&S>=k)A!==E&&(n[A]=n[E],n[E]=L),E++;else if(O>=S&&S>=O)for(;;){var U=r(n[w]);{if(!(O>=U&&U>=O)){k>U?(n[A]=n[E],n[E++]=n[w],n[w--]=L):(n[A]=n[w],n[w--]=L);break}if(w--,A>w)break}}}}return t(n,E,w+1)}var e=i(r);return t}function s(r){for(var t=new Array(r),n=-1;++n<r;)t[n]=0;return t}function f(r,t){for(var n=r.length;t>n;)r[n++]=0;return r}function c(r,t){if(t>32)throw new Error("invalid array width!");return r}function l(r,t){return function(n){var e=n.length;return[r.left(n,t,0,e),r.right(n,t,0,e)]}}function p(r,t){var n=t[0],e=t[1];return function(t){var o=t.length;return[r.left(t,n,0,o),r.left(t,e,0,o)]}}function d(r){return[0,r.length]}function m(){return null}function v(){return 0}function g(r){return r+1}function h(r){return r-1}function y(r){return function(t,n){return t+ +r(n)}}function M(r){return function(t,n){return t-r(n)}}function b(){function r(r){var t=O,n=r.length;return n&&(b=b.concat(r),j=_(j,O+=n),S.forEach(function(e){e(r,t,n)})),c}function e(){for(var r=x(O,O),t=[],n=0,e=0;O>n;++n)j[n]?r[n]=e++:t.push(n);A.forEach(function(r){r(0,[],t)}),U.forEach(function(t){t(r)});for(var o,n=0,e=0;O>n;++n)(o=j[n])&&(n!==e&&(j[e]=o,b[e]=b[n]),++e);for(b.length=e;O>e;)j[--O]=0}function i(r){function e(t,e,o){B=t.map(r),W=Z(P(o),0,o),B=n(B,W);var a,i=$(B),u=i[0],s=i[1];if(X)for(a=0;o>a;++a)X(B[a],a)||(j[W[a]+e]|=Q);else{for(a=0;u>a;++a)j[W[a]+e]|=Q;for(a=s;o>a;++a)j[W[a]+e]|=Q}if(!e)return Y=B,H=W,nt=u,void(et=s);var f=Y,c=H,l=0,p=0;for(Y=new Array(O),H=x(O,O),a=0;e>l&&o>p;++a)f[l]<B[p]?(Y[a]=f[l],H[a]=c[l++]):(Y[a]=B[p],H[a]=W[p++]+e);for(;e>l;++l,++a)Y[a]=f[l],H[a]=c[l];for(;o>p;++p,++a)Y[a]=B[p],H[a]=W[p]+e;i=$(Y),nt=i[0],et=i[1]}function i(r,t,n){rt.forEach(function(r){r(B,W,t,n)}),B=W=null}function s(r){for(var t,n=0,e=0;O>n;++n)j[t=H[n]]&&(n!==e&&(Y[e]=Y[n]),H[e]=r[t],++e);for(Y.length=e;O>e;)H[e++]=0;var o=$(Y);nt=o[0],et=o[1]}function f(r){var t=r[0],n=r[1];if(X)return X=null,J(function(r,e){return e>=t&&n>e}),nt=t,et=n,K;var e,o,a,i=[],u=[];if(nt>t)for(e=t,o=Math.min(nt,n);o>e;++e)j[a=H[e]]^=Q,i.push(a);else if(t>nt)for(e=nt,o=Math.min(t,et);o>e;++e)j[a=H[e]]^=Q,u.push(a);if(n>et)for(e=Math.max(t,et),o=n;o>e;++e)j[a=H[e]]^=Q,i.push(a);else if(et>n)for(e=Math.max(nt,n),o=et;o>e;++e)j[a=H[e]]^=Q,u.push(a);return nt=t,et=n,A.forEach(function(r){r(Q,i,u)}),K}function c(r){return null==r?T():Array.isArray(r)?F(r):"function"==typeof r?q(r):L(r)}function L(r){return f(($=l(G,r))(Y))}function F(r){return f(($=p(G,r))(Y))}function T(){return f(($=d)(Y))}function q(r){return $=d,J(X=r),nt=0,et=O,K}function J(r){var t,n,e,o=[],a=[];for(t=0;O>t;++t)!(j[n=H[t]]&Q)^!!(e=r(Y[t],t))&&(e?(j[n]&=V,o.push(n)):(j[n]|=Q,a.push(n)));A.forEach(function(r){r(Q,o,a)})}function z(r){for(var t,n=[],e=et;--e>=nt&&r>0;)j[t=H[e]]||(n.push(b[t]),--r);return n}function N(r){for(var t,n=[],e=nt;et>e&&r>0;)j[t=H[e]]||(n.push(b[t]),--r),e++;return n}function I(r){function n(t,n,e,o){function a(){++B===D&&(y=C(y,R<<=1),T=C(T,R),D=k(R))}var c,l,p,d,v,g,h=F,y=x(B,D),M=z,P=I,G=B,E=0,w=0;for(K&&(M=P=m),F=new Array(B),B=0,T=G>1?_(T,O):x(O,D),G&&(p=(l=h[0]).key);o>w&&!((d=r(t[w]))>=d);)++w;for(;o>w;){for(l&&d>=p?(v=l,g=p,y[E]=B,(l=h[++E])&&(p=l.key)):(v={key:d,value:P()},g=d),F[B]=v;!(d>g||(T[c=n[w]+e]=B,j[c]&V||(v.value=M(v.value,b[c])),++w>=o));)d=r(t[w]);a()}for(;G>E;)F[y[E]=B]=h[E++],a();if(B>E)for(E=0;e>E;++E)T[E]=y[T[E]];c=A.indexOf(W),B>1?(W=i,X=s):(!B&&Z&&(B=1,F=[{key:null,value:P()}]),1===B?(W=u,X=f):(W=m,X=m),T=null),A[c]=W}function e(){if(B>1){for(var r=B,t=F,n=x(r,r),e=0,o=0;O>e;++e)j[e]&&(n[T[o]=T[e]]=1,++o);for(F=[],B=0,e=0;r>e;++e)n[e]&&(n[e]=B++,F.push(t[e]));if(B>1)for(var e=0;o>e;++e)T[e]=n[T[e]];else T=null;A[A.indexOf(W)]=B>1?(X=s,W=i):1===B?(X=f,W=u):X=W=m}else if(1===B){if(Z)return;for(var e=0;O>e;++e)if(j[e])return;F=[],B=0,A[A.indexOf(W)]=W=X=m}}function i(r,t,n){if(r!==Q&&!K){var e,o,a,i;for(e=0,a=t.length;a>e;++e)j[o=t[e]]&V||(i=F[T[o]],i.value=z(i.value,b[o]));for(e=0,a=n.length;a>e;++e)(j[o=n[e]]&V)===r&&(i=F[T[o]],i.value=N(i.value,b[o]))}}function u(r,t,n){if(r!==Q&&!K){var e,o,a,i=F[0];for(e=0,a=t.length;a>e;++e)j[o=t[e]]&V||(i.value=z(i.value,b[o]));for(e=0,a=n.length;a>e;++e)(j[o=n[e]]&V)===r&&(i.value=N(i.value,b[o]))}}function s(){var r,t;for(r=0;B>r;++r)F[r].value=I();for(r=0;O>r;++r)j[r]&V||(t=F[T[r]],t.value=z(t.value,b[r]))}function f(){var r,t=F[0];for(t.value=I(),r=0;O>r;++r)j[r]&V||(t.value=z(t.value,b[r]))}function c(){return K&&(X(),K=!1),F}function l(r){var t=q(c(),0,F.length,r);return J.sort(t,0,t.length)}function p(r,t,n){return z=r,N=t,I=n,K=!0,S}function d(){return p(g,h,v)}function P(r){return p(y(r),M(r),v)}function G(r){function t(t){return r(t.value)}return q=a(t),J=o(t),S}function E(){return G(t)}function w(){return B}function L(){var r=A.indexOf(W);return r>=0&&A.splice(r,1),r=rt.indexOf(n),r>=0&&rt.splice(r,1),r=U.indexOf(e),r>=0&&U.splice(r,1),S}var S={top:l,all:c,reduce:p,reduceCount:d,reduceSum:P,order:G,orderNatural:E,size:w,dispose:L,remove:L};tt.push(S);var F,T,q,J,z,N,I,R=8,D=k(R),B=0,W=m,X=m,K=!0,Z=r===m;return arguments.length<1&&(r=t),A.push(W),rt.push(n),U.push(e),n(Y,H,0,O),d().orderNatural()}function R(){var r=I(m),t=r.all;return delete r.all,delete r.top,delete r.order,delete r.orderNatural,delete r.size,r.value=function(){return t()[0].value},r}function D(){tt.forEach(function(r){r.dispose()});var r=S.indexOf(e);return r>=0&&S.splice(r,1),r=S.indexOf(i),r>=0&&S.splice(r,1),r=U.indexOf(s),r>=0&&U.splice(r,1),E&=V,T()}var Y,H,B,W,X,K={filter:c,filterExact:L,filterRange:F,filterFunction:q,filterAll:T,top:z,bottom:N,group:I,groupAll:R,dispose:D,remove:D},Q=~E&-~E,V=~Q,Z=u(function(r){return B[r]}),$=d,rt=[],tt=[],nt=0,et=0;return S.unshift(e),S.push(i),U.push(s),E|=Q,(w>=32?!Q:E&(1<<w)-1)&&(j=C(j,w<<=1)),e(b,0,O),i(b,0,O),K}function s(){function r(r,t){var n;if(!d)for(n=t;O>n;++n)j[n]||(s=f(s,b[n]))}function t(r,t,n){var e,o,a;if(!d){for(e=0,a=t.length;a>e;++e)j[o=t[e]]||(s=f(s,b[o]));for(e=0,a=n.length;a>e;++e)j[o=n[e]]===r&&(s=c(s,b[o]))}}function n(){var r;for(s=l(),r=0;O>r;++r)j[r]||(s=f(s,b[r]))}function e(r,t,n){return f=r,c=t,l=n,d=!0,p}function o(){return e(g,h,v)}function a(r){return e(y(r),M(r),v)}function i(){return d&&(n(),d=!1),s}function u(){var n=A.indexOf(t);return n>=0&&A.splice(n),n=S.indexOf(r),n>=0&&S.splice(n),p}var s,f,c,l,p={reduce:e,reduceCount:o,reduceSum:a,value:i,dispose:u,remove:u},d=!0;return A.push(t),S.push(r),r(b,0,O),o()}function f(){return O}var c={add:r,remove:e,dimension:i,groupAll:s,size:f},b=[],O=0,E=0,w=8,j=L(0),A=[],S=[],U=[];return arguments.length?r(arguments[0]):c}function x(r,t){return(257>t?L:65537>t?S:U)(r)}function P(r){for(var t=x(r,r),n=-1;++n<r;)t[n]=n;return t}function k(r){return 8===r?256:16===r?65536:4294967296}b.version="1.3.11",b.permute=n;var G=b.bisect=e(t);G.by=e;var O=b.heap=o(t);O.by=o;var E=b.heapselect=a(t);E.by=a;var w=b.insertionsort=i(t);w.by=i;var j=b.quicksort=u(t);j.by=u;var A=32,L=s,S=s,U=s,_=f,C=c;"undefined"!=typeof Uint8Array&&(L=function(r){return new Uint8Array(r)},S=function(r){return new Uint16Array(r)},U=function(r){return new Uint32Array(r)},_=function(r,t){if(r.length>=t)return r;var n=new r.constructor(t);return n.set(r),n},C=function(r,t){var n;switch(t){case 16:n=S(r.length);break;case 32:n=U(r.length);break;default:throw new Error("invalid array width!")}return n.set(r),n}),r.crossfilter=b}("undefined"!=typeof n&&n||this)},{}],18:[function(r,t){t.exports=r("./crossfilter").crossfilter},{"./crossfilter":17}],19:[function(r,t){!function(){function r(r,t){function n(t){var n,e=r.arcs[0>t?~t:t],o=e[0];return r.transform?(n=[0,0],e.forEach(function(r){n[0]+=r[0],n[1]+=r[1]})):n=e[e.length-1],0>t?[n,o]:[o,n]}function e(r,t){for(var n in r){var e=r[n];delete t[e.start],delete e.start,delete e.end,e.forEach(function(r){o[0>r?~r:r]=1}),u.push(e)}}var o={},a={},i={},u=[],s=-1;return t.forEach(function(n,e){var o,a=r.arcs[0>n?~n:n];a.length<3&&!a[1][0]&&!a[1][1]&&(o=t[++s],t[s]=n,t[e]=o)}),t.forEach(function(r){var t,e,o=n(r),u=o[0],s=o[1];if(t=i[u])if(delete i[t.end],t.push(r),t.end=s,e=a[s]){delete a[e.start];var f=e===t?t:t.concat(e);a[f.start=t.start]=i[f.end=e.end]=f}else a[t.start]=i[t.end]=t;else if(t=a[s])if(delete a[t.start],t.unshift(r),t.start=u,e=i[u]){delete i[e.end];var c=e===t?t:e.concat(t);a[c.start=e.start]=i[c.end=t.end]=c}else a[t.start]=i[t.end]=t;else t=[r],a[t.start=u]=i[t.end=s]=t}),e(i,a),e(a,i),t.forEach(function(r){o[0>r?~r:r]||u.push([r])}),u}function e(t,n,e){function o(r){var t=0>r?~r:r;(c[t]||(c[t]=[])).push({i:r,g:f})}function a(r){r.forEach(o)}function i(r){r.forEach(a)}function u(r){"GeometryCollection"===r.type?r.geometries.forEach(u):r.type in l&&(f=r,l[r.type](r.arcs))}var s=[];if(arguments.length>1){var f,c=[],l={LineString:a,MultiLineString:i,Polygon:i,MultiPolygon:function(r){r.forEach(i)}};u(n),c.forEach(arguments.length<3?function(r){s.push(r[0].i)}:function(r){e(r[0].g,r[r.length-1].g)&&s.push(r[0].i)})}else for(var p=0,d=t.arcs.length;d>p;++p)s.push(p);return{type:"MultiLineString",arcs:r(t,s)}}function o(t,e){function o(r){r.forEach(function(t){t.forEach(function(t){(i[t=0>t?~t:t]||(i[t]=[])).push(r)})}),s.push(r)}function a(r){return p(u(t,{type:"Polygon",arcs:[r]}).coordinates[0])>0}var i={},s=[],f=[];return e.forEach(function(r){"Polygon"===r.type?o(r.arcs):"MultiPolygon"===r.type&&r.arcs.forEach(o)}),s.forEach(function(r){if(!r._){var t=[],n=[r];for(r._=1,f.push(t);r=n.pop();)t.push(r),r.forEach(function(r){r.forEach(function(r){i[0>r?~r:r].forEach(function(r){r._||(r._=1,n.push(r))})})})}}),s.forEach(function(r){delete r._}),{type:"MultiPolygon",arcs:f.map(function(e){var o=[];if(e.forEach(function(r){r.forEach(function(r){r.forEach(function(r){i[0>r?~r:r].length<2&&o.push(r)})})}),o=r(t,o),(n=o.length)>1)for(var u,s=a(e[0][0]),f=0;f<n;++f)if(s===a(o[f])){u=o[0],o[0]=o[f],o[f]=u;break}return o})}}function a(r,t){return"GeometryCollection"===t.type?{type:"FeatureCollection",features:t.geometries.map(function(t){return i(r,t)})}:i(r,t)}function i(r,t){var n={type:"Feature",id:t.id,properties:t.properties||{},geometry:u(r,t)};return null==t.id&&delete n.id,n}function u(r,t){function n(r,t){t.length&&t.pop();for(var n,e=c[0>r?~r:r],o=0,a=e.length;a>o;++o)t.push(n=e[o].slice()),f(n,o);0>r&&s(t,a)}function e(r){return r=r.slice(),f(r,0),r}function o(r){for(var t=[],e=0,o=r.length;o>e;++e)n(r[e],t);return t.length<2&&t.push(t[0].slice()),t}function a(r){for(var t=o(r);t.length<4;)t.push(t[0].slice());return t}function i(r){return r.map(a)}function u(r){var t=r.type;return"GeometryCollection"===t?{type:t,geometries:r.geometries.map(u)}:t in l?{type:t,coordinates:l[t](r)}:null}var f=g(r.transform),c=r.arcs,l={Point:function(r){return e(r.coordinates)},MultiPoint:function(r){return r.coordinates.map(e)},LineString:function(r){return o(r.arcs)},MultiLineString:function(r){return r.arcs.map(o)},Polygon:function(r){return i(r.arcs)},MultiPolygon:function(r){return r.arcs.map(i)}};return u(t)}function s(r,t){for(var n,e=r.length,o=e-t;o<--e;)n=r[o],r[o++]=r[e],r[e]=n}function f(r,t){for(var n=0,e=r.length;e>n;){var o=n+e>>>1;r[o]<t?n=o+1:e=o}return n}function c(r){function t(r,t){r.forEach(function(r){0>r&&(r=~r);var n=o[r];n?n.push(t):o[r]=[t]})}function n(r,n){r.forEach(function(r){t(r,n)})}function e(r,t){"GeometryCollection"===r.type?r.geometries.forEach(function(r){e(r,t)}):r.type in i&&i[r.type](r.arcs,t)}var o={},a=r.map(function(){return[]}),i={LineString:t,MultiLineString:n,Polygon:n,MultiPolygon:function(r,t){r.forEach(function(r){n(r,t)})}};r.forEach(e);for(var u in o)for(var s=o[u],c=s.length,l=0;c>l;++l)for(var p=l+1;c>p;++p){var d,m=s[l],v=s[p];(d=a[m])[u=f(d,v)]!==v&&d.splice(u,0,v),(d=a[v])[u=f(d,m)]!==m&&d.splice(u,0,m)}return a}function l(r,t){function n(r){a.remove(r),r[1][2]=t(r),a.push(r)}var e=g(r.transform),o=h(r.transform),a=v();return t||(t=d),r.arcs.forEach(function(r){for(var i,u,s=[],f=0,c=0,l=r.length;l>c;++c)u=r[c],e(r[c]=[u[0],u[1],1/0],c);for(var c=1,l=r.length-1;l>c;++c)i=r.slice(c-1,c+2),i[1][2]=t(i),s.push(i),a.push(i);for(var c=0,l=s.length;l>c;++c)i=s[c],i.previous=s[c-1],i.next=s[c+1];for(;i=a.pop();){var p=i.previous,d=i.next;i[1][2]<f?i[1][2]=f:f=i[1][2],p&&(p.next=d,p[2]=i[2],n(p)),d&&(d.previous=p,d[0]=i[0],n(d))}r.forEach(o)}),r}function p(r){for(var t,n=-1,e=r.length,o=r[e-1],a=0;++n<e;)t=o,o=r[n],a+=t[0]*o[1]-t[1]*o[0];return.5*a}function d(r){var t=r[0],n=r[1],e=r[2];return Math.abs((t[0]-e[0])*(n[1]-t[1])-(t[0]-n[0])*(e[1]-t[1]))}function m(r,t){return r[1][2]-t[1][2]}function v(){function r(r,t){for(;t>0;){var n=(t+1>>1)-1,o=e[n];if(m(r,o)>=0)break;e[o._=t]=o,e[r._=t=n]=r}}function t(r,t){for(;;){var n=t+1<<1,a=n-1,i=t,u=e[i];if(o>a&&m(e[a],u)<0&&(u=e[i=a]),o>n&&m(e[n],u)<0&&(u=e[i=n]),i===t)break;e[u._=t]=u,e[r._=t=i]=r}}var n={},e=[],o=0;return n.push=function(t){return r(e[t._=o]=t,o++),o},n.pop=function(){if(!(0>=o)){var r,n=e[0];return--o>0&&(r=e[o],t(e[r._=0]=r,0)),n}},n.remove=function(n){var a,i=n._;if(e[i]===n)return i!==--o&&(a=e[o],(m(a,n)<0?r:t)(e[a._=i]=a,i)),i},n}function g(r){if(!r)return y;var t,n,e=r.scale[0],o=r.scale[1],a=r.translate[0],i=r.translate[1];return function(r,u){u||(t=n=0),r[0]=(t+=r[0])*e+a,r[1]=(n+=r[1])*o+i}}function h(r){if(!r)return y;var t,n,e=r.scale[0],o=r.scale[1],a=r.translate[0],i=r.translate[1];return function(r,u){u||(t=n=0);var s=(r[0]-a)/e|0,f=(r[1]-i)/o|0;r[0]=s-t,r[1]=f-n,t=s,n=f}}function y(){}var M={version:"1.6.18",mesh:function(r){return u(r,e.apply(this,arguments))},meshArcs:e,merge:function(r){return u(r,o.apply(this,arguments))},mergeArcs:o,feature:a,neighbors:c,presimplify:l};"function"==typeof define&&define.amd?define(M):"object"==typeof t&&t.exports?t.exports=M:this.topojson=M}()},{}]},{},[1]);
\ No newline at end of file
diff --git a/examples/add.control.html b/examples/add.control.html
index cc6b60b..8abd920 100644
--- a/examples/add.control.html
+++ b/examples/add.control.html
@@ -5,9 +5,11 @@
     <title>Document</title>
     <script src="../dist/map-tools.js"></script>
     <link href="vendors/prism/prism.css" rel="stylesheet" />
+    <link rel="stylesheet" href="../examples/style/basic.css"/>
     <script src="vendors/prism/prism.js"></script>
 </head>
 <style>
+
     html, body, #mymap {
         height: 100%;
         margin: 0px;
@@ -18,15 +20,6 @@
         background: rgba(0,0,0,.08) !important;
     }
 
-    #mymap {
-      float: left;
-      width: 600px;
-      height: 500px;
-    }
-
-    pre {
-      float: left;
-    }
 </style>
 <body>
 <script>
@@ -36,82 +29,32 @@
         lng: 2.1833
     }, function (err, instance) {
         if (!err) {
-            addControl();
+            addPanel();
         }
     });
 
-    function addControl() {
+    function addPanel() {
 
-        map.addControl({
-            template:   '<div>' +
-                        '<button id="clickMe" class="test-button">ctrl 1</button>' +
-                        '<button class="test-button">ctrl 2</button>' +
-                        '</div>',
-            position:'top center',
-            style: {
-                'background-color': '#fff',
-                'border': '2px solid #fff',
-                'border-radius': '3px',
-                'box-shadow': '0 2px 6px rgba(0,0,0,.3)',
-                'cursor': 'pointer',
-                'marginBottom': '22px',
-                'text-align': 'center'
-            },
-            events: {
-                '.test-button click' : function (e) {
-                    alert('Clicked on custom control');
-                }
-            }
-        });
-
-        map.addControl({
+        map.addPanel({
             templateURL: 'templates/custom.panel.html',
-            position:'right bottom',
+            position:'left bottom',
             events: {
                 '.menu li click' : function (e) {
                     console.log('clicked on ', e.target);
                     e.target.classList.toggle('active');
-                    }
-            }
-        });
-    }
-</script>
-<div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-          map.addControl({
-            template:   '<div>' +
-  '<button id="clickMe" class="test-button">ctrl 1</button>' +
-  '<button class="test-button">ctrl 2</button>' +
-  '</div>',
-            position:'top center',
-            style: {
-                'background-color': '#fff',
-                'border': '2px solid #fff',
-                'border-radius': '3px',
-                'box-shadow': '0 2px 6px rgba(0,0,0,.3)',
-                'cursor': 'pointer',
-                'marginBottom': '22px',
-                'text-align': 'center'
-            },
-            events: {
-                '.test-button click' : function (e) {
-                    alert('Clicked on custom control');
                 }
             }
         });
 
-        map.addControl({
-            templateURL: 'templates/custom.panel.html',
-            position:'right bottom',
-            events: {
-                '.menu li click' : function (e) {
-                    console.log('clicked on ', e.target);
-                    e.target.classList.toggle('active');
-                    }
-            }
+        map.addPanel({
+            templateURL: 'code.snippet/add.control.snippet.html',
+            position:'right center'
         });
-</code>
-</pre>
+        Prism.highlightAll();
+
+    }
+</script>
+
+<div id="mymap"></div>
 </body>
 </html>
diff --git a/examples/add.group.html b/examples/add.group.html
index fe522bd..f2359aa 100644
--- a/examples/add.group.html
+++ b/examples/add.group.html
@@ -4,149 +4,121 @@ Icons generated using: http://mapicons.nicolasmollet.com/
 <!doctype html>
 <html lang="en">
 <head>
-  <meta charset="UTF-8">
-  <title>Document</title>
-  <link href="vendors/prism/prism.css" rel="stylesheet" />
-  <script src="vendors/prism/prism.js"></script>
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
-  <script src="../dist/map-tools.js"></script>
-</head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
+	<meta charset="UTF-8">
+	<title>Document</title>
+	<link href="vendors/prism/prism.css" rel="stylesheet"/>
+	<link rel="stylesheet" href="../examples/style/basic.css"/>
+	<script src="vendors/prism/prism.js"></script>
+	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 
-  pre {
-    float: left;
-  }
+	<script src="../dist/map-tools.js"></script>
+	<style>
 
-
-</style>
+	</style>
+</head>
 <body>
 <script>
-  var map = new GMP({
-    id: 'mymap',
-    lat: 41.3833,
-    lng: 2.1833,
-    zoom: 12
-  }, function (err, instance) {
-    if (!err) {
-      addMarkers();
-    }
-  });
-
-  function addMarkers() {
-
-    map.addGroup('places', {visible: false});
-
-    var places = [{
-      lat: 41.387220,
-      lng: 2.169983,
-      title: 'Plaça Catalunya',
-      icon: 'images/citysquare.png'
-    },
-      {
-        lat: 41.4036,
-        lng: 2.1744,
-        title: 'Sagrada Familia',
-        icon: 'images/icon-sevilla.png'
-      },
-      {
-        lat: 41.395571,
-        lng: 2.161961,
-        title: 'Casa Milà',
-        icon: 'images/palace-2.png'
-      },
-      {
-        lat: 41.385231,
-        lng: 2.180893,
-        title: 'Picasso Museum',
-        icon: 'images/art-museum-2.png'
-      }
-    ];
-
-    map.addMarker(places, {group: 'places'});
-
-    map.addGroup('parks', {visible: true, icon: 'images/forest2.png'});
-
-    var parks = [
-      {
-        lat: 41.414724,
-        lng: 2.152646,
-        title: 'Parc Guell'
-      },
-      {
-        lat: 41.389483,
-        lng: 2.186650,
-        title: 'Ciütadella Park'
-      },
-      {
-        lat: 41.420384,
-        lng: 2.167424,
-        title: 'Park del Guinardó'
-      }
-    ]
-
-    map.addMarker(parks, {group: 'parks'});
-
-  }
-
-  var placesVisible = false;
-  var parksVisible  = true;
-
-  $(function(){
-
-    $('.places').click(function(e) {
-      e.preventDefault();
-      placesVisible = !placesVisible;
-      map.updateGroup('places', {visible: placesVisible});
-    });
-
-    $('.parks').click(function(e) {
-      e.preventDefault();
-      parksVisible = !parksVisible;
-      map.updateGroup('parks', {visible: parksVisible});
-    });
-  });
+	var map = new GMP({
+		id: 'mymap',
+		lat: 41.3833,
+		lng: 2.1833,
+		zoom: 12
+	}, function (err, instance) {
+		if (!err) {
+			addMarkers();
+
+
+			map.addPanel({
+				template: '<div class="actions">' +
+				'<div class="places">Click to toggle Markers visibility Group: Places</div>' +
+				'<div class="parks">Click to toggle Markers visibility Group: Parks</div>' +
+				'</div>',
+				position: 'right center',
+				events: {
+					'.places click': function (e) {
+						e.preventDefault();
+						placesVisible = !placesVisible;
+						map.updateGroup('places', {visible: placesVisible});
+					},
+
+					'.parks click': function (e) {
+						e.preventDefault();
+						parksVisible = !parksVisible;
+						map.updateGroup('parks', {visible: parksVisible});
+					}
+				}
+			});
+
+
+			map.addPanel({
+				templateURL: 'code.snippet/add.group.snippet.html',
+				position: 'right center'
+			});
+			Prism.highlightAll();
+		}
+	});
+
+	function addMarkers() {
+
+		map.addGroup('places', {visible: false});
+
+		var places = [{
+			lat: 41.387220,
+			lng: 2.169983,
+			title: 'Plaça Catalunya',
+			icon: 'images/citysquare.png'
+		},
+			{
+				lat: 41.4036,
+				lng: 2.1744,
+				title: 'Sagrada Familia',
+				icon: 'images/icon-sevilla.png'
+			},
+			{
+				lat: 41.395571,
+				lng: 2.161961,
+				title: 'Casa Milà',
+				icon: 'images/palace-2.png'
+			},
+			{
+				lat: 41.385231,
+				lng: 2.180893,
+				title: 'Picasso Museum',
+				icon: 'images/art-museum-2.png'
+			}
+		];
+
+		map.addMarker(places, {group: 'places'});
+
+		map.addGroup('parks', {visible: true, icon: 'images/forest2.png'});
+
+		var parks = [
+			{
+				lat: 41.414724,
+				lng: 2.152646,
+				title: 'Parc Guell'
+			},
+			{
+				lat: 41.389483,
+				lng: 2.186650,
+				title: 'Ciütadella Park'
+			},
+			{
+				lat: 41.420384,
+				lng: 2.167424,
+				title: 'Park del Guinardó'
+			}
+		]
+
+		map.addMarker(parks, {group: 'parks'});
+
+	}
+
+	var placesVisible = false;
+	var parksVisible = true;
+
 
 </script>
-<div><h4><a href class="places">Click to toggle Markers visibility Group: Places</a></h4></div>
-<div><h4><a href class="parks">Click to toggle Markers visibility Group: Parks</a></h4></div>
 <div id="mymap"></div>
-
-<pre>
-<code class="language-javascript">
-   map.addGroup('parks', {visible: true, icon: 'images/forest2.png'});
-
-    var parks = [
-      {
-        lat: 41.414724,
-        lng: 2.152646,
-        title: 'Parc Guell'
-      },
-      {
-        lat: 41.389483,
-        lng: 2.186650,
-        title: 'Ciütadella Park'
-      },
-      {
-        lat: 41.420384,
-        lng: 2.167424,
-        title: 'Park del Guinardó'
-      }
-    ]
-
-    map.addMarker(parks, {group: 'parks'});
-</code>
-</pre>
-
 </body>
 </html>
diff --git a/examples/add.marker.html b/examples/add.marker.html
index 8f231a5..02cc578 100644
--- a/examples/add.marker.html
+++ b/examples/add.marker.html
@@ -4,26 +4,10 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-</style>
 <body>
 <script>
   var map = new GMP({
@@ -33,6 +17,13 @@
   }, function (err, instance) {
     if (!err) {
       addMarker();
+
+	    // snippet panel
+	    map.addPanel({
+		    templateURL: 'code.snippet/add.marker.snippet.html',
+		    position:'right center'
+	    });
+	    Prism.highlightAll();
     }
   });
 
@@ -44,27 +35,8 @@
     });
   }
 </script>
-<div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-    var map = new GMP({
-    id: 'mymap',
-    lat: 41.3833,
-    lng: 2.1833
-  }, function (err, instance) {
-    if (!err) {
-      addMarker();
-    }
-  });
 
-  function addMarker() {
-    map.addMarker({
-      lat: 41.3833,
-      lng: 2.1833,
-      title: 'Barcelona'
-    });
-  }
-</code>
-</pre>
+<div id="mymap" class="side"></div>
+
 </body>
 </html>
diff --git a/examples/add.multiple.markers.html b/examples/add.multiple.markers.html
index 96f4e7b..bf0ef44 100644
--- a/examples/add.multiple.markers.html
+++ b/examples/add.multiple.markers.html
@@ -4,27 +4,10 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-
-</style>
 <body>
 <script>
   var map = new GMP({
@@ -34,6 +17,14 @@
   }, function (err, instance) {
     if (!err) {
       addMarkers();
+
+	    // snippet panel
+	    map.addPanel({
+		    templateURL: 'code.snippet/add.multiple.markers.snippet.html',
+		    position:'right center'
+	    });
+	    Prism.highlightAll();
+
     }
   });
 
@@ -51,31 +42,6 @@
   }
 </script>
 <div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-    var map = new GMP({
-    id: 'mymap',
-    lat: 41.3833,
-    lng: 2.1833
-  }, function (err, instance) {
-    if (!err) {
-      addMarkers();
-    }
-  });
 
-  function addMarkers() {
-    map.addMarker([{
-      lat: 41.3833,
-      lng: 2.1833,
-      title: 'Barcelona'
-    },
-      {
-        lat: 42.5000,
-        lng: 1.5167,
-        title: 'Andorra'
-      }]);
-  }
-</code>
-</pre>
 </body>
 </html>
diff --git a/examples/animate.markers.html b/examples/animate.markers.html
index e5e5a86..56bbef9 100644
--- a/examples/animate.markers.html
+++ b/examples/animate.markers.html
@@ -4,26 +4,10 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-</style>
 <body>
 <script>
   var map = new GMP({
@@ -34,6 +18,13 @@
   }, function (err, instance) {
     if (!err) {
       addMarkers();
+
+	    // snippet panel
+	    map.addPanel({
+		    templateURL: 'code.snippet/animate.markers.snippet.html',
+		    position:'right center'
+	    });
+	    Prism.highlightAll();
     }
   });
 
@@ -68,36 +59,5 @@
   }
 </script>
 <div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-      map.addMarker([{
-      lat: 41.387220,
-      lng: 2.169983,
-      title: 'Plaça Catalunya',
-      move: 'bounce'
-
-    },
-      {
-        lat: 41.4036,
-        lng: 2.1744,
-        title: 'Sagrada Familia',
-        move: 'bounce'
-      },
-      {
-        lat: 41.395571,
-        lng: 2.161961,
-        title: 'Casa Milà',
-        move: 'drop'
-      },
-      {
-        lat: 41.385231,
-        lng: 2.180893,
-        title: 'Picasso Museum',
-        move: 'drop'
-      }
-
-    ]);
-</code>
-</pre>
 </body>
 </html>
diff --git a/examples/bubble.html b/examples/bubble.html
index 3dee957..1a1dd59 100644
--- a/examples/bubble.html
+++ b/examples/bubble.html
@@ -4,27 +4,11 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-</style>
 <body>
 <script>
 
@@ -35,6 +19,26 @@
   }, function (err, instance) {
     if (!err) {
       addMarker();
+
+	    map.addPanel({
+		    template: '<div class="actions">' +
+		    '<div class="update">Update Bubble text to: "Click Me!"</div>' +
+		    '</div>',
+		    position: 'right center',
+		    events: {
+			    '.update click': function (e) {
+				    map.updateMarker(marker, {bubble: {content: '<p>Click Me!</p>'}});
+				    e.preventDefault();
+			    }
+		    }
+	    });
+
+
+	    map.addPanel({
+		    templateURL: 'code.snippet/add.bubble.snippet.html',
+		    position:'right center'
+	    });
+	    Prism.highlightAll();
     }
   });
 
@@ -55,33 +59,7 @@
     });
   }
 
-  $(function() {
-    $('.update').click(function(e) {
-      map.updateMarker(marker, {bubble: {content: '<p>Click Me!</p>'}});
-      e.preventDefault();
-
-    });
-  })
 </script>
-<h4><a href="#" class="update">Update Bubble text to: "Click Me!"</a></h4>
 <div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-   marker = map.addMarker({
-      lat: 41.3833,
-      lng: 2.1833,
-      title: 'Barcelona',
-      bubble: {
-        event: 'mouseover',
-        content: '<p>{city}</p>'
-      },
-      data: {
-        city: 'Barcelona!'
-      }
-    });
-    ...
-    map.updateMarker(marker, {bubble: {content: '<p>Click Me!</p>'}});
-</code>
-</pre>
 </body>
 </html>
diff --git a/examples/code.snippet/add.bubble.snippet.html b/examples/code.snippet/add.bubble.snippet.html
new file mode 100644
index 0000000..af4383b
--- /dev/null
+++ b/examples/code.snippet/add.bubble.snippet.html
@@ -0,0 +1,22 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	marker = map.addMarker({
+		lat: 41.3833,
+		lng: 2.1833,
+		title: 'Barcelona',
+		bubble: {
+			event: 'mouseover',
+			content: '<p>{city}</p>'
+		},
+		data: {
+			city: 'Barcelona!'
+		}
+	});
+	...
+	map.updateMarker(marker, {
+		bubble: {content: '<p>Click Me!</p>'}
+	});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/add.control.snippet.html b/examples/code.snippet/add.control.snippet.html
new file mode 100644
index 0000000..0b124c5
--- /dev/null
+++ b/examples/code.snippet/add.control.snippet.html
@@ -0,0 +1,21 @@
+<div class="map-panel" >
+  <pre class="language-javascript">
+		<code class="language-javascript">
+			map.addPanel({
+        templateURL: 'templates/custom.panel.html',
+        position:'left bottom',
+        events: {
+          '.menu li click' : function (e) {
+              console.log('clicked on ', e.target);
+              e.target.classList.toggle('active');
+            }
+          }
+				});
+
+			map.addPanel({
+		    templateURL: 'templates/snippet.panel.html',
+		    position:'right center'
+			});
+</code>
+	</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/add.group.snippet.html b/examples/code.snippet/add.group.snippet.html
new file mode 100644
index 0000000..6622fd0
--- /dev/null
+++ b/examples/code.snippet/add.group.snippet.html
@@ -0,0 +1,29 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	map.addGroup('parks', {
+			visible: true,
+			icon: 'images/forest2.png'
+	});
+
+	var parks = [{
+			lat: 41.414724,
+			lng: 2.152646,
+			title: 'Parc Guell'
+		},
+		{
+			lat: 41.389483,
+			lng: 2.186650,
+			title: 'Ciütadella Park'
+		},
+		{
+			lat: 41.420384,
+			lng: 2.167424,
+			title: 'Park del Guinardó'
+		}
+	]
+
+	map.addMarker(parks, {group: 'parks'});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/add.marker.snippet.html b/examples/code.snippet/add.marker.snippet.html
new file mode 100644
index 0000000..03b8bdb
--- /dev/null
+++ b/examples/code.snippet/add.marker.snippet.html
@@ -0,0 +1,23 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	var map = new GMP({
+		id: 'mymap',
+		lat: 41.3833,
+		lng: 2.1833
+		}, function (err, instance) {
+			if (!err) {
+				addMarker();
+			}
+	});
+
+	function addMarker() {
+		map.addMarker({
+			lat: 41.3833,
+			lng: 2.1833,
+			title: 'Barcelona'
+		});
+	}
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/add.multiple.markers.snippet.html b/examples/code.snippet/add.multiple.markers.snippet.html
new file mode 100644
index 0000000..985cb6e
--- /dev/null
+++ b/examples/code.snippet/add.multiple.markers.snippet.html
@@ -0,0 +1,28 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	var map = new GMP({
+		id: 'mymap',
+		lat: 41.3833,
+		lng: 2.1833
+		}, function (err, instance) {
+			if (!err) {
+				addMarkers();
+			}
+	});
+
+	function addMarkers() {
+		map.addMarker([{
+			lat: 41.3833,
+			lng: 2.1833,
+			title: 'Barcelona'
+		},
+		{
+			lat: 42.5000,
+			lng: 1.5167,
+			title: 'Andorra'
+			}]);
+	}
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/animate.markers.snippet.html b/examples/code.snippet/animate.markers.snippet.html
new file mode 100644
index 0000000..7151b7a
--- /dev/null
+++ b/examples/code.snippet/animate.markers.snippet.html
@@ -0,0 +1,31 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	map.addMarker([{
+			lat: 41.387220,
+			lng: 2.169983,
+			title: 'Plaça Catalunya',
+			move: 'bounce'
+		},
+		{
+			lat: 41.4036,
+			lng: 2.1744,
+			title: 'Sagrada Familia',
+			move: 'bounce'
+		},
+		{
+			lat: 41.395571,
+			lng: 2.161961,
+			title: 'Casa Milà',
+			move: 'drop'
+		},
+		{
+			lat: 41.385231,
+			lng: 2.180893,
+			title: 'Picasso Museum',
+			move: 'drop'
+		}
+	]);
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/crossfilter.snippet.html b/examples/code.snippet/crossfilter.snippet.html
new file mode 100644
index 0000000..c162967
--- /dev/null
+++ b/examples/code.snippet/crossfilter.snippet.html
@@ -0,0 +1,35 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	map.addMarker([
+		{
+			lat: 41.3833,
+			lng: 2.1833,
+			data: {
+				name: 'Barcelona',
+				population: 1621000
+			}
+		},
+		{
+			lat: 40.419795,
+			lng: -3.710436,
+			data: {
+				name: 'Madrid',
+				population: 3234000
+			}
+		},
+		{
+			lat: 41.648821,
+			lng: -0.889086,
+			data: {
+				name: 'Zaragoza',
+				population: 679624
+			}
+		}
+	], {filters: ['population']});
+	}
+	...
+	map.updateMarker(map.markers.filter.population.top(1), {move: 'bounce'});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/geojson.snippet.html b/examples/code.snippet/geojson.snippet.html
new file mode 100644
index 0000000..1195611
--- /dev/null
+++ b/examples/code.snippet/geojson.snippet.html
@@ -0,0 +1,10 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	var file = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson';
+	$.getJSON(file, function (data) {
+	map.addGeoJson(data);
+	});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/simple.map.snippet.html b/examples/code.snippet/simple.map.snippet.html
new file mode 100644
index 0000000..15bd4f1
--- /dev/null
+++ b/examples/code.snippet/simple.map.snippet.html
@@ -0,0 +1,14 @@
+<div class="map-panel" >
+  <pre class="language-javascript">
+		<code class="language-javascript">
+var map = new GMP({
+			id: 'mymap',
+			lat: 41.3833,
+			lng: 2.1833,
+			type: 'ROADMAP'
+			}, function (err, instance) {
+				console.log('Map Loaded!', instance);
+			});
+		</code>
+	</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/topojson.snippet.html b/examples/code.snippet/topojson.snippet.html
new file mode 100644
index 0000000..34cdb62
--- /dev/null
+++ b/examples/code.snippet/topojson.snippet.html
@@ -0,0 +1,30 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	$.getJSON('./json/us-country.json', function (data) {
+				map.addTopoJson(data, [{
+					object: 'us_country',
+					style: {
+						strokeColor: '#2c2c2c',
+						strokeWeight: 0.5,
+						fillOpacity: 0
+					},
+					filters: ['NAME']
+				}]);
+		});
+	...
+	var feature1 = map.json.filter.NAME.filter('Colorado')
+									.top(1)[0];
+	var feature2 = map.json.filter.NAME.filter('Nevada')
+									.top(1)[0];
+	var options = {
+		style:{
+			fillOpacity: 0.4,
+			fillColor:'black',
+			strokeColor: 'black'
+		}
+	};
+	map.updateFeature([feature1,feature2], options);
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/update.map.snippet.html b/examples/code.snippet/update.map.snippet.html
new file mode 100644
index 0000000..fb39b51
--- /dev/null
+++ b/examples/code.snippet/update.map.snippet.html
@@ -0,0 +1,23 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	// Update Terrain
+	map.updateMap({
+			type: 'TERRAIN',
+			mapTypeControl: false,
+			zoomControl: false,
+			streetViewControl: false,
+			panControl: false
+	});
+
+	// Update RoadMap
+	map.updateMap({
+			type: 'ROADMAP',
+			mapTypeControl: true,
+			zoomControl: true,
+			streetViewControl: true,
+			panControl: true
+	});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/code.snippet/update.marker.snippet.html b/examples/code.snippet/update.marker.snippet.html
new file mode 100644
index 0000000..1d7ab48
--- /dev/null
+++ b/examples/code.snippet/update.marker.snippet.html
@@ -0,0 +1,9 @@
+<div class="map-panel" >
+<pre class="language-javascript">
+<code class="language-javascript">
+	// crossfilter results
+	var marker = map.markers.filter.cityName.filterExact('barcelona').top(1);
+	map.updateMarker(marker, {move: 'drop'});
+</code>
+</pre>
+</div>
\ No newline at end of file
diff --git a/examples/crossfilter.html b/examples/crossfilter.html
index 5e211b3..69c51ee 100644
--- a/examples/crossfilter.html
+++ b/examples/crossfilter.html
@@ -4,27 +4,11 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  #mymap {
-    float: left;
-    width: 500px;
-    height: 500px;
-  }
-
-  pre {
-    float: right;
-  }
-</style>
 <body>
 <script>
 
@@ -36,6 +20,26 @@
   }, function (err, instance) {
     if (!err) {
       addMarker();
+
+	    map.addPanel({
+		    template: '<div class="actions">' +
+		    '<div class="top">Top Populated City</div>' +
+		    '</div>',
+		    position: 'right center',
+		    events: {
+			    '.top click': function (e) {
+				    map.updateMarker(map.markers.filter.population.top(1), {move: 'bounce'});
+				    e.preventDefault();
+			    }
+		    }
+	    });
+
+			// snippet panel
+	    map.addPanel({
+		    templateURL: 'code.snippet/add.group.snippet.html',
+		    position:'right center'
+	    });
+	    Prism.highlightAll();
     }
   });
 
@@ -68,50 +72,7 @@
     ], {filters: ['population']});
   }
 
-  $(function() {
-    $('.top').click(function(e) {
-        map.updateMarker(map.markers.filter.population.top(1), {move: 'bounce'});
-        e.preventDefault();
-    });
-
-  });
-
-
 </script>
-<h4><a href="#" class="top">Top Populated City</a></h4>
 <div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-   map.addMarker([
-      {
-        lat: 41.3833,
-        lng: 2.1833,
-        data: {
-          name: 'Barcelona',
-          population: 1621000
-        }
-      },
-      {
-        lat: 40.419795,
-        lng: -3.710436,
-        data: {
-          name: 'Madrid',
-          population: 3234000
-        }
-      },
-      {
-        lat: 41.648821,
-        lng: -0.889086,
-        data: {
-          name: 'Zaragoza',
-          population: 679624
-        }
-      }
-    ], {filters: ['population']});
-  }
-  ...
-  map.updateMarker(map.markers.filter.population.top(1), {move: 'bounce'});
-</code>
-</pre>
 </body>
 </html>
diff --git a/examples/geojson.html b/examples/geojson.html
index 90a6978..d207b49 100644
--- a/examples/geojson.html
+++ b/examples/geojson.html
@@ -1,23 +1,8 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <style type="text/css">
-    html, body, #mymap {
-      height: 100%;
-      margin: 0;
-    }
-
-    #mymap {
-      float: left;
-      width: 600px;
-      height: 500px;
-    }
-
-    pre {
-      float: left;
-    }
-  </style>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
@@ -35,16 +20,17 @@
     $.getJSON('http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', function (data) {
       map.addGeoJson(data);
     });
+
+	  // snippet panel
+	  map.addPanel({
+		  templateURL: 'code.snippet/geojson.snippet.html',
+		  position:'right center'
+	  });
+	  Prism.highlightAll();
+
   });
 
 </script>
-<pre>
-<code class="language-javascript">
-var file = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson';
-$.getJSON(file, function (data) {
-  map.addGeoJson(data);
-  });
-</code>
-</pre>
+
 </body>
 </html>
diff --git a/examples/simple.map.html b/examples/simple.map.html
index 9905752..da68ee0 100644
--- a/examples/simple.map.html
+++ b/examples/simple.map.html
@@ -4,26 +4,11 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
 
-  pre {
-    float: left;
-  }
-
-</style>
 <body>
 <script>
   var map = new GMP({
@@ -33,20 +18,16 @@
     type: 'ROADMAP'
   }, function (err, instance) {
     console.log('Map Loaded!', instance);
+
+	  // snippet panel
+	  map.addPanel({
+		  templateURL: 'code.snippet/simple.map.snippet.html',
+		  position:'right center'
+	  });
+	  Prism.highlightAll();
+
   });
 </script>
-<div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-  var map = new GMP({
-    id: 'mymap',
-    lat: 41.3833,
-    lng: 2.1833,
-    type: 'ROADMAP'
-  }, function (err, instance) {
-    console.log('Map Loaded!', instance);
-  });
-</code>
-</pre>
+<div id="mymap" ></div>
 </body>
 </html>
diff --git a/examples/style/basic.css b/examples/style/basic.css
new file mode 100644
index 0000000..7a2ea77
--- /dev/null
+++ b/examples/style/basic.css
@@ -0,0 +1,47 @@
+html, body, #mymap {
+    height: 100%;
+    margin: 0px;
+    padding: 0px;
+}
+
+div.map-panel{
+    margin-right: 25px;
+    background: #f5f2f0;
+    border-radius: 2px;
+    box-shadow: 0 2px 6px rgba(0,0,0,0.3);
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    font-size: 12px;
+    line-height: 16px;
+    outline: none;
+    overflow: hidden;
+    z-index: 0;
+    -webkit-transition: background-color 200ms cubic-bezier(0.52,0,0.48,1);
+    -moz-transition: background-color 200ms cubic-bezier(0.52,0,0.48,1);
+    -ms-transition: background-color 200ms cubic-bezier(0.52,0,0.48,1);
+    -o-transition: background-color 200ms cubic-bezier(0.52,0,0.48,1);
+    transition: background-color 200ms cubic-bezier(0.52,0,0.48,1);
+}
+
+.actions {
+    margin: 25px;
+    marginBottom: 22px;
+
+}
+.actions div {
+    cursor: pointer;
+    color: rgba(0,0,0,.8);
+    line-height: 0.5;
+    margin: 0.25em .25em;
+    padding: .5em .8em;
+    border-radius: .2857rem;
+    font-size: 1rem;
+    background-color: #fff;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, .3);
+}
+
+.actions div:hover {
+    color: rgba(255, 153, 0,.8);
+
+}
diff --git a/examples/topojson.html b/examples/topojson.html
index 2045c89..7fa1d19 100644
--- a/examples/topojson.html
+++ b/examples/topojson.html
@@ -1,32 +1,15 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <style type="text/css">
-    html, body, #mymap {
-      height: 100%;
-      margin: 0;
-    }
 
-    #mymap {
-      float: left;
-      width: 700px;
-      height: 500px;
-    }
-
-    pre {
-      float: left;
-    }
-  </style>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+  <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 </head>
 <body>
 
-<h4><a href="#" class="single">Update Single Feature (Select Nevada & Colorado)</a></h4>
-<h4><a href="#" class="choro">Show  Census choropleth Data </a></h4>
-
 <div id="mymap"></div>
 <script>
 
@@ -50,64 +33,54 @@
       }]);
     });
 
-  });
+	  map.addPanel({
+		  template: '<div class="actions">' +
+		  '<div class="single">Update Single Feature (Select Nevada & Colorado)</div>' +
+		  '<div class="choro">Show CensusArea Data (Chropleth) </div>' +
+		  '</div>',
+		  position: 'right center',
+		  events: {
+			  '.single click': function (e) {
+				  var feature1 = map.json.filter.NAME.filter('Colorado').top(1)[0];
+				  var feature2 = map.json.filter.NAME.filter('Nevada').top(1)[0];
+				  map.updateFeature([feature1,feature2], {style: {fillOpacity: 0.4, fillColor:'black', strokeColor: 'black'}});
+				  e.preventDefault();
+			  },
 
+			  '.choro click': function (e) {
+				  map.updateFeature(Object.keys(GMP.maps.mymap.json.all).map(function(k) { return GMP.maps.mymap.json.all[k] }), {style: color});
+				  e.preventDefault();
+			  }
+		  }
+	  });
 
-  $(function() {
 
-    $('.single').click(function (e) {
-      var feature1 = map.json.filter.NAME.filter('Colorado').top(1)[0];
-      var feature2 = map.json.filter.NAME.filter('Nevada').top(1)[0];
-      map.updateFeature([feature1,feature2], {style: {fillOpacity: 0.4, fillColor:'black', strokeColor: 'black'}});
-      e.preventDefault();
-    });
+	  // snippet panel
+	  map.addPanel({
+		  templateURL: 'code.snippet/topojson.snippet.html',
+		  position:'right center'
+	  });
+	  Prism.highlightAll();
 
-    $('.choro').click(function (e) {
-      var color = function() {
-        // Simple choropleth function based on the CENSUSAREA data
-        var value = (this.data.CENSUSAREA/570640.95);
-        var alpha = 5;
-        var h = 210,
-        s = 100,
-        l = (Math.pow((1 - value), alpha) * 50)+50; // this is just to increase the difference on the data for much different color
 
-       return {
-          fillColor: 'hsl('+h+','+s+'%,'+l+'%)',
-          fillOpacity:0.7
-        }
+  });
 
-      };
-      map.updateFeature(Object.keys(GMP.maps.mymap.json.all).map(function(k) { return GMP.maps.mymap.json.all[k] }), {style: color});
-      e.preventDefault();
-    });
 
-  });
-</script>
 
-<pre>
-<code class="language-javascript">
-$.getJSON('./json/us-country.json', function (data) {
-map.addTopoJson(data, [{object: 'us_country',
-        style: {
-          strokeColor: '#2c2c2c',
-          strokeWeight: 0.5,
-          fillOpacity: 0
-        },
-        filters: ['NAME']
-      }]);
-    });
-  ...
-  var feature1 = map.json.filter.NAME.filter('Colorado').top(1)[0];
-  var feature2 = map.json.filter.NAME.filter('Nevada').top(1)[0];
-  var options = {
-    style:{
-      fillOpacity: 0.4,
-      fillColor:'black',
-      strokeColor: 'black'
-    }
+  var color = function() {
+	  // Simple choropleth function based on the CENSUSAREA data
+	  var value = (this.data.CENSUSAREA/570640.95);
+	  var alpha = 5;
+	  var h = 210,
+			  s = 100,
+			  l = (Math.pow((1 - value), alpha) * 50)+50; // this is just to increase the difference on the data for much different color
+
+	  return {
+		  fillColor: 'hsl('+h+','+s+'%,'+l+'%)',
+		  fillOpacity:0.7
+	  }
+
   };
-  map.updateFeature([feature1,feature2], options);
-</code>
-</pre>
+</script>
 </body>
 </html>
diff --git a/examples/update.map.html b/examples/update.map.html
index 9760789..d995139 100644
--- a/examples/update.map.html
+++ b/examples/update.map.html
@@ -4,89 +4,61 @@
   <meta charset="UTF-8">
   <title>Document</title>
   <link href="vendors/prism/prism.css" rel="stylesheet" />
+    <link rel="stylesheet" href="../examples/style/basic.css"/>
   <script src="vendors/prism/prism.js"></script>
   <script src="../dist/map-tools.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 </head>
-<style>
-  html, body, .mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
-
-  .mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-</style>
 <body>
 <script>
   var map = new GMP({
-    el: '.mymap',
+    el: '#mymap',
     lat: 41.3833,
     lng: 2.1833
   }, function (err, instance) {
 
-  });
-
+	  map.addPanel({
+		  template: '<div class="actions">' +
+		  '<div class="update-terrain">Update Map View to: TERRAIN and Disable: all controls</div>' +
+		  '<div class="update-roadmap">Update Map View to: ROADMAP and Enable: all controls</div>' +
+		  '</div>',
+		  position: 'right center',
+		  events: {
+			  '.update-terrain click': function (e) {
+				  e.preventDefault();
+				  map.updateMap(
+						  {
+							  type: 'TERRAIN',
+							  mapTypeControl: false,
+							  zoomControl: false,
+							  streetViewControl: false,
+							  panControl: false
+						  });
+			  },
 
-  $(function() {
+			  '.update-roadmap click': function (e) {
+				  e.preventDefault();
+				  map.updateMap({
+					  type: 'ROADMAP',
+					  mapTypeControl: true,
+					  zoomControl: true,
+					  streetViewControl: true,
+					  panControl: true
+				  });
+			  }
+		  }
+	  });
 
-    $('.update-terrain').click(function(e) {
-      e.preventDefault();
-      map.updateMap(
-        {
-          type: 'TERRAIN',
-          mapTypeControl: false,
-          zoomControl: false,
-          streetViewControl: false,
-          panControl: false
-        });
-    });
-
-    $('.update-roadmap').click(function(e) {
-      e.preventDefault();
-      map.updateMap({
-        type: 'ROADMAP',
-        mapTypeControl: true,
-        zoomControl: true,
-        streetViewControl: true,
-        panControl: true
-      });
-    });
+		// snippet panel
+	  map.addPanel({
+		  templateURL: 'code.snippet/update.map.snippet.html',
+		  position:'right center'
+	  });
+	  Prism.highlightAll();
 
   });
 
 </script>
-<h4><a href class="update-terrain">Update Map View to: TERRAIN and Disable: all controls</a></h4>
-<h4><a href class="update-roadmap">Update Map View to: ROADMAP and Enable: all controls</a></h4>
-<div class="mymap"></div>
-<pre>
-<code class="language-javascript">
-  // Update Terrain
-  map.updateMap({
-  type: 'TERRAIN',
-  mapTypeControl: false,
-  zoomControl: false,
-  streetViewControl: false,
-  panControl: false
-  });
-
-  // Update RoadMap
-  map.updateMap({
-  type: 'ROADMAP',
-  mapTypeControl: true,
-  zoomControl: true,
-  streetViewControl: true,
-  panControl: true
-  });
-</code>
-</pre>
+<div id="mymap"></div>
 </body>
 </html>
diff --git a/examples/update.marker.html b/examples/update.marker.html
index b30e2d4..684e93e 100644
--- a/examples/update.marker.html
+++ b/examples/update.marker.html
@@ -1,94 +1,84 @@
 <!doctype html>
 <html lang="en">
 <head>
-  <meta charset="UTF-8">
-  <title>Document</title>
-  <link href="vendors/prism/prism.css" rel="stylesheet" />
-  <script src="vendors/prism/prism.js"></script>
-  <script src="../dist/map-tools.js"></script>
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
+	<meta charset="UTF-8">
+	<title>Document</title>
+	<link href="vendors/prism/prism.css" rel="stylesheet" />
+	<link rel="stylesheet" href="../examples/style/basic.css"/>
+	<script src="vendors/prism/prism.js"></script>
+	<script src="../dist/map-tools.js"></script>
+	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 </head>
-<style>
-  html, body, #mymap {
-    height: 100%;
-    margin: 0px;
-    padding: 0px;
-  }
 
-  #mymap {
-    float: left;
-    width: 600px;
-    height: 500px;
-  }
-
-  pre {
-    float: left;
-  }
-
-</style>
 <body>
 
-<h4><a href="#" class="update-crossfilter">Update Marker by using Crossfilter</a></h4>
-<h4><a href="#" class="update-reference">Update Marker by using it's own reference</a></h4>
-<h4><a href="#" class="update-reference-location">Update Marker to another location</a></h4>
 <script>
 
-  var positions = [{lat: 41.3833, lng:2.1833}, {lat: 42.263177, lng: 3.178188}];
+	var positions = [{lat: 41.3833, lng:2.1833}, {lat: 42.263177, lng: 3.178188}];
 
-  var map = new GMP({
-    id: 'mymap',
-    lat: 41.3833,
-    lng: 2.1833
-  }, function (err, instance) {
-    if (!err) {
-      addMarker();
-    }
-  });
+	var map = new GMP({
+		id: 'mymap',
+		lat: 41.3833,
+		lng: 2.1833
+	}, function (err, instance) {
+		if (!err) {
+			addMarker();
 
-  var markerInstance;
+			map.addPanel({
+				template: '<div class="actions">' +
+				'<div class="update-crossfilter">Update Marker by using Crossfilter</div>' +
+				'<div class="update-reference">Update Marker by using it\'s own reference</div>' +
+				'<div class="update-reference-location">Update Marker to another location</div>' +
+				'</div>',
+				position: 'right center',
+				events: {
+					'.update-crossfilter click': function (e) {
+						var marker = map.markers.filter.cityName.filterExact('barcelona').top(1);
+						map.updateMarker(marker, {move: 'drop'});
+						e.preventDefault();
+					},
 
-  function addMarker() {
-    markerInstance = map.addMarker({
-      lat: 41.3833,
-      lng: 2.1833,
-      title: 'Barcelona',
-      data: {
-        cityName: 'barcelona'
-      }
-    }, {
-      filters: ['cityName']
-    });
-  }
+					'.update-reference click': function (e) {
+						map.updateMarker([markerInstance], {move: 'drop'});
+						e.preventDefault();
+					},
 
-  $(function() {
-    $('.update-crossfilter').click(function(e) {
-      var marker = map.markers.filter.cityName.filterExact('barcelona').top(1);
-      map.updateMarker(marker, {move: 'drop'});
-      e.preventDefault();
-    });
+					'.update-reference-location click': function (e) {
+						map.updateMarker([markerInstance], positions[+!toggle]);
+						toggle = !toggle;
+						e.preventDefault();
+					}
+				}
+			});
 
-    $('.update-reference').click(function(e) {
-      map.updateMarker([markerInstance], {move: 'drop'});
-      e.preventDefault();
-    });
+			var toggle = false
 
+			// snippet panel
+			map.addPanel({
+				templateURL: 'code.snippet/update.marker.snippet.html',
+				position:'right center'
+			});
+			Prism.highlightAll();
+		}
+	});
 
-    var toggle = false;
-    $('.update-reference-location').click(function(e) {
-      map.updateMarker([markerInstance], positions[+!toggle]);
-      toggle = !toggle;
-      e.preventDefault();
-    });
+	var markerInstance;
 
-  });
+	function addMarker() {
+		markerInstance = map.addMarker({
+			lat: 41.3833,
+			lng: 2.1833,
+			title: 'Barcelona',
+			data: {
+				cityName: 'barcelona'
+			}
+		}, {
+			filters: ['cityName']
+		});
+	}
 
 </script>
 <div id="mymap"></div>
-<pre>
-<code class="language-javascript">
-  var marker = map.markers.filter.cityName.filterExact('barcelona').top(1);
-  map.updateMarker(marker, {move: 'drop'});
-</code>
-</pre>
+
 </body>
 </html>
diff --git a/lib/map-tools/topojson.js b/lib/map-tools/addFeature.js
similarity index 100%
rename from lib/map-tools/topojson.js
rename to lib/map-tools/addFeature.js
diff --git a/lib/map-tools/addControl.js b/lib/map-tools/addPanel.js
similarity index 100%
rename from lib/map-tools/addControl.js
rename to lib/map-tools/addPanel.js
diff --git a/lib/map-tools/index.js b/lib/map-tools/index.js
index 48f95d8..da4573b 100644
--- a/lib/map-tools/index.js
+++ b/lib/map-tools/index.js
@@ -11,10 +11,10 @@ module.exports = function (global) {
     var that = this;
 
     this.addMarker = require('map-tools/addMarker')(global, that);
-    this.addTopoJson = require('map-tools/topojson')(global, that).addTopoJson;
-    this.addGeoJson = require('map-tools/topojson')(global, that).addGeoJson;
+    this.addTopoJson = require('map-tools/addFeature')(global, that).addTopoJson;
+    this.addGeoJson = require('map-tools/addFeature')(global, that).addGeoJson;
     this.updateFeature = require('map-tools/updateFeature')(global, that);
-    this.addControl = require('map-tools/addControl')(global, that);
+    this.addPanel = require('map-tools/addPanel')(global, that);
     this.updateMarker = require('map-tools/updateMarker')(global, that);
     this.addGroup = require('map-tools/groups')(global, that).addGroup;
     this.updateGroup = require('map-tools/groups')(global, that).updateGroup;


Test patch:
diff --git a/test/functional/addControl.Spec.js b/test/functional/addControl.Spec.js
index 812c3ee..3616125 100644
--- a/test/functional/addControl.Spec.js
+++ b/test/functional/addControl.Spec.js
@@ -1,10 +1,10 @@
-describe('when calling addControl()', function () {
+describe('when calling addPanel()', function () {
 	"use strict";
 
 	it('should add a single panel on the specified position', function () {
 		var map = new GMP({async: false, id: 'mymap', lat: 41.3833, lng: 2.1833});
 
-		var result = map.addControl({
+		var result = map.addPanel({
 			template: '<div>' +
 			'<button id="clickMe" class="test-button">ctrl 1</button></div>',
 			position: 'top center'
@@ -17,7 +17,7 @@ describe('when calling addControl()', function () {
 	it('should add a single panel with the specified style options', function () {
 		var map = new GMP({async: false, id: 'mymap', lat: 41.3833, lng: 2.1833});
 
-		var panel = map.addControl({
+		var panel = map.addPanel({
 			template: '<div><button id="clickMe">ctrl 1</button></div>',
 			style: {
 				'background-color': '#fff',
@@ -34,7 +34,7 @@ describe('when calling addControl()', function () {
 		var map = new GMP({async: false, id: 'mymap', lat: 41.3833, lng: 2.1833});
 		var spy = sinon.spy();
 
-		var panel = map.addControl({
+		var panel = map.addPanel({
 			template: '<div><button id="clickMe">ctrl 1</button></div>',
 			events: {
 				'#clickMe click' : spy
@@ -55,7 +55,7 @@ describe('when calling addControl()', function () {
 
     var map = new GMP({async: false, id: 'mymap', lat: 41.3833, lng: 2.1833});
 
-    map.addControl({
+    map.addPanel({
       templateURL: 'control-template.html',
       position: 'top center'
     });