Skip to content

Programming Guide

Florian Ledermann edited this page Feb 10, 2016 · 23 revisions

Getting started

mapmap.js turns an SVG element into an interactive map. Currently, the SVG element must be present in the HTML code, and a reference to it or a selector expression must be passed to the mapmap() function. Furthermore, as SVG elements don't have an inherent size, you need to set the width and height attributes of the SVG element to specify the aspect ratio and abstract resolution of your map. You can use CSS to override these and define the actual dimensions of your map.

In order to correctly position HTML-based overlays (like legends or mouseover information) on the map, the SVG element must be contained inside a positioned element, i.e. an element with position: absolute, relative or fixed.

A typical HTML setup would look like:

<style type="text/css">
.mapWrapper {position: relative;}
</style>

<!-- ... -->

<div class="mapWrapper">
<svg id="mapEl" width="800" height="400"></svg>
</div>

The most simple map you can create will just render some GeoJSON:

var map = mapmap('#mapEl')
    .geometry('mygeodata.geojson');

General concepts & conventions

Method chaining

mapmap.js supports method chaining, supporting a concise and declarative programming style. This means that most methods of the map object return this, allowing you to subsequently call another method on the map.

Methods that return values are named getXyz to distinguish them from chainable methods.

Options

Most methods in the mapmap API take only a minimum amount of parameters -- usually the ones that must always be specified -- and an additional options parameter to pass optional parameters. The options parameter is a plain JavaScript object, and the API Documentation lists the available options and their default values.

// initialize a map with German localization
var map = mapmap('#mapEl', { language: 'de' });

In some cases, there is a default option, so if the options is not an object but a string or function it is assigned to the default option. This is indicated in the API documentation if the options parameter is named like keyOrOptions or similar - meaning that if `keyOrOptions is not an plain object, it will be assigned to the key option.

map.geometry('mygeodata.geojson', 'iso');

is equivalent to

map.geometry('mygeodata.geojson', { key: 'iso' });

Asynchronous resource loading

Usually, browser-based applications require an asynchronous programming model using callback functions if external data should be loaded. If multiple data files must be loaded, this can lead to complex code to ensure all resources have been loaded before proceeding with their processing. mapmap.js handles the loading of geometry and data internally and exposes a pseudo-synchronous API for primary functionality that ensures all resources have been loaded before any operations are performed on them.

// The map will be drawn and the interaction handler installed
// *after* the geometry file has loaded
map.geometry('admin.topojson', 'iso')
   .choropleth('population')
   .on('click', mapmap.zoom());

If you need to execute code after all resources specified so far, you can use the .then() method, providing a callback function.

map.geometry('admin.topojson', 'iso')
   .choropleth('population')
   .then(function(){
      console.log("Map loaded!");
   });

The .then() method also allows the map object to be used as a Thenable.

Metadata specification

The properties of individual fields or groups of fields can be described by passing metadata specifiers to the map. The meta() method takes a plain JS object, listing a number of metadata selectors (filednames with wildcard support) and associated metadata specifiers.

Metadata selectors

A metadata selector is a string containing a fieldname, or a regular expression for more complex matching. String selectors may use the * wildcard to replace an arbitrary number of characters or the ? wildcard to replace a single character. By using wildcards, you can assign identical metadata to similar fields - e.g. pop_* would assign the metadata specification to all fields starting with "pop_".

All metadata specifiers that match a given filed name will be applied to the field in order of their specificity. Specificity is is defined as the length of the string without wildcards, or the length of the regular expression. By exploiting specificity, you can apply metadata specifications in a cascading manner, using wildcards to express properties for a group of fields and then specifying per-field overrides (e.g. the human-readable label for the field) on an individual basis.

map.meta({
	'unemployment_*': { // applies to all unemployment fields
		label: "Overall Unemployment Rate",
		valueLabel: "Unemployment Rate",
		numberFormat: '%',
		color: colorbrewer.YlOrRd[6]
	},
	'umemployment_m': { // this is more specific, so will override above
		label: "Male Unemployment, 2014"
	},
	'umemployment_f': {
		label: "Female Unemployment, 2014"
	}
});

Labels & text formatting

TO DO. See API documentation for a list of available metadata attributes.

Quantitative scales

TO DO. See API documentation for a list of available metadata attributes.

Ordinal scales

TO DO. See API documentation for a list of available metadata attributes.

Undefined values

TO DO. See API documentation for a list of available metadata attributes.

Data processing with Map/Reduce

Data from CSV or JSON files can be joined with features specified in GeoJSON even if the structure or field names do not match. Simple joins can be performed by specifying the field names that should be used as primary keys to identify matching entities.

// Join geometry identified by the key 'iso' with
// data from a CSV where the key field is called 'code'
map.geometry('districts.geojson', 'iso')
   .data('population.csv', 'code')
   .choropleth('pop_count');

To support more complex transformations in a modular fashion, mapmap utilizes the MapReduce programming model. MapReduce is a two-step process: First, each entity that should be part of the result is mapped to a key. Subsequently, all entities sharing identical keys are processed and reduced to a single entity. Many data transformations needed in thematic mapping (e.g. filtering, aggregation, splitting, transforming keys) can be efficiently implemented using the MapReduce paradigm.

Both steps in MapReduce are implemented as functions, that are called for each item separately - map is called with each data item, reduce with each key emitted by map and an Array of items mapped to that key. Both functions receive an emit() function as additional parameter, which is used to emit the result(s).

For example, here is some code that aggregates population data for administrative districts from their municipalities.

// This assumes the data is available only for municipalities
// so we have to sum it up
map.geometry('districts.geojson', 'iso')
	.data('population.csv', {
        map: function(d, emit) {
            // only emit units on municipality level
            if (d.code.length == 5) {
                emit(d.code.substring(0,3), d);
            }
        },
        reduce: function(key, values, emit) {
            // sum up population count from municipalities
            var result = {
                pop_count: 0
            };
            for (var i=0; i<values.length; i++) {
                result.pop_count += values[i].pop_count;
            }
            emit(key, result);
        }
    });

Mapmap uses the datadata library internally, which provides the MapReduce implementation and some helper functions for utilizing it. For example, using datadata helpers, the above code can be written like this:

// Mapmap exposes a reference to the datadata library
var dd = mapmap.datadata;

map.geometry('districts.geojson', 'iso')
	.data('population.csv', {
        map: dd.map.key('code', function(c) {
            return c.length == 5 && c.substring(0,3);
        }),
        reduce: dd.emit.sum('pop_count')
    });

Visual attributes & symbolization

TO DO

Map view, coordinate systems & anchors

TO DO

Interaction

TO DO

[Documentation in progress... stay tuned...]