Skip to content

Instantly share code, notes, and snippets.

@mehak-sachdeva
Last active October 16, 2019 15:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mehak-sachdeva/22dbd463d5358e4356e85fdb5223a242 to your computer and use it in GitHub Desktop.
Save mehak-sachdeva/22dbd463d5358e4356e85fdb5223a242 to your computer and use it in GitHub Desktop.
Interactive Map with Leaflet.js

##Data file to use

We will create the visualization using GeoJSON which is a format for encoding a variety of geographic data structure. It follows the following structure:

{
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [125.6, 10.1]
        },
        "properties": {
          "name": "Dinagat Islands"
        }
}

##Displaying the Basemap Tiles

var map = L.map('map').setView([37.8, -96], 5);

		L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/light-v9/tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWVoYWtzYWNoZGV2YSIsImEiOiJjaXF2YTNvYWIwMDA1ZmttZzBsNTM1NXV1In0.-SA7eLZOeeYkVPG7Jek2ug', {
			maxZoom: 18,
			id: 'mapbox.light',
			attribution: 'Mapbox'
		}).addTo(map);

For better readability and ease in navigation, we can also save out access_tokens specific to our accounts in a variable and access the variable within the L.tileLayer option as follows:

var map = L.map('map').setView([37.8, -96], 5);
		var mapAccessToken = 'pk.eyJ1IjoibWVoYWtzYWNoZGV2YSIsImEiOiJjaXF2YTNvYWIwMDA1ZmttZzBsNTM1NXV1In0.-SA7eLZOeeYkVPG7Jek2ug';

		L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/light-v9/tiles/256/{z}/{x}/{y}?access_token='+mapAccessToken, {
			maxZoom: 18,
			id: 'mapbox.light',
			attribution: 'Mapbox'
		}).addTo(map);

You can also create your custom basemaps in Mapbox and access them here or also bring in Stamen tiles instead. (Should I show a variation in the form of an image or something here, or will that be superfluous?)

##Adding Colors

We create a symbolic map with a gradient of colors reflecting the density spectrum of each state in the country. For this we create a function that defines the colors for each state according to our predefined brackets.


	function getColor(d) {
			return d > 1000 ? '#5e4fa2' :
			       d > 500  ? '#3288bd' :
			       d > 200  ? '#66c2a5' :
			       d > 100  ? '#abdda4' :
			       d > 50   ? '#e6f598' :
			       d > 20   ? '#ffffbf' :
			       d > 10   ? '#fee08b' :
			                  '#fdae61';
		}

Since this function takes in the density parameter, while defining the overall style function for the feature of our GeoJSON data, we access the feature.properties.density while calling the getColor function.

function style(feature) {
			return {
				weight: 2,
				opacity: 1,
				color: 'white',
				dashArray: '3',
				fillOpacity: 0.7,
				fillColor: getColor(feature.properties.density)
			};
		}

##Adding Interaction while Hover

We next create a function highlightFeature() that takes in the event object and highlights the target layer (i.e. the layer over which the user hovers) with the styling properties mentioned in the options. We use the setStyle(), a pre-defined method in Leaflet. We also make sure, that the highlights of the hovered feature do not clash with the existing layer features by the bringToFront() method. Since, this method has issues with Internet Explorer and Mobile Opera we want to make sure that we do not apply this on either of those platforms by this condition if (!L.Browser.ie && !L.Browser.opera).

function highlightFeature(e) {
			var layer = e.target;

			layer.setStyle({
				weight: 3,
				color: 'black',
				dashArray: '',
				fillOpacity: 0.7
			});

			if (!L.Browser.ie && !L.Browser.opera) {
				layer.bringToFront();
			}

			info.update(layer.feature.properties);
		}

To define what happens when the mouse is not hovering over any of the states, we define the following function:

var geojson;

		function resetHighlight(e) {
			geojson.resetStyle(e.target);
			info.update();
		}

The resetStyle method in leaflet helps reset the style of the target feature to the original GeoJSON style, particularly useful in post-hover cases like this. We also use the info.update() function within our functions defining interaction which is defined as follows:

var info = L.control();
info.update = function (props) {
			this._div.innerHTML = '<h4>US Population Density</h4>' +  (props ?
				'<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
				: 'Hover over a state');
		};

This creates a custom control to update the information of each state as the user hovers over it. We also use an info.onAdd function that adds the information on the map as the layers are loaded.

info.onAdd = function (map) {
			this._div = L.DomUtil.create('div', 'info');
			this.update();
			return this._div;
		};

The L.DomUtil.create() creates a DOM node (div element here) and assigns it to the class 'info'. This element is added at the time of map loading on the map using info.addTo(map);

To add a little more custom interactivity, we also define a function zoomToFeature that zooms into the bounds of the state which is clicked on by the user.

	function zoomToFeature(e) {
			map.fitBounds(e.target.getBounds());
		}

This is achieved by the fitBounds() method of altering the map state by assigning the bounds recieved by using the getBounds() method on the target layer.

##Putting it all together!

Next, we use the onEachFeature option that will be called on each created feature layer to define what happens on mouseover, mouseout and click events. And finally, adding the GeoJSON with the defined style and onEachFeature option, we add the layer and its interactivity to the map using addTo(Map).

function onEachFeature(feature, layer) {
			layer.on({
				mouseover: highlightFeature,
				mouseout: resetHighlight,
				click: zoomToFeature
			});
		}

		geojson = L.geoJson(statesData, {
			style: style,
			onEachFeature: onEachFeature
		}).addTo(map);

##Adding Legend to the Map

Next we construct and add the legend to the map.

	var legend = L.control({position: 'bottomright'});

		legend.onAdd = function (map) {

			var div = L.DomUtil.create('div', 'info legend'),
				grades = [0, 10, 20, 50, 100, 200, 500, 1000],
				labels = [],
				from, to;

			for (var i = 0; i < grades.length; i++) {
				from = grades[i];
				to = grades[i + 1];

				labels.push(
					'<i style="background:' + getColor(from + 1) + '"></i> ' +
					from + (to ? '&ndash;' + to : '+'));
			}

			div.innerHTML = labels.join('<br>');
			return div;
		};

		legend.addTo(map);

In this function, the DomUtil option creates a div element within the info legend class and assigns the range to the grade array. Using the loop, we assign the grade brackets to the colors by the getColor function. Finally, the labels.join() option adds format to the legend labels and it is finally added to the map with addTo(map).

<!DOCTYPE html>
<html>
<head>
<title>Leaflet Layers Control Example</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://npmcdn.com/leaflet@1.0.0-rc.3/dist/leaflet.css" />
<style>
#map {
width: 1420px;
height: 760px;
align: center;
}
.info {
padding: 20px 12px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: 'black';
}
.legend {
text-align: left;
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://npmcdn.com/leaflet@1.0.0-rc.3/dist/leaflet.js"></script>
<script type="text/javascript" src="us-states.js"></script>
<script type="text/javascript">
var map = L.map('map').setView([37.8, -96], 5);
var mapAccessToken = 'pk.eyJ1IjoibWVoYWtzYWNoZGV2YSIsImEiOiJjaXF2YTNvYWIwMDA1ZmttZzBsNTM1NXV1In0.-SA7eLZOeeYkVPG7Jek2ug';
L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/256/{z}/{x}/{y}?access_token='+mapAccessToken, {
maxZoom: 18,
id: 'mapbox.light',
attribution: 'Mapbox'
}).addTo(map);
// control that shows state info on hover
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>US Population Density</h4>' + (props ?
'<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
: 'Hover over a state');
};
info.addTo(map);
// get color depending on population density value
function getColor(d) {
return d > 1000 ? '#5e4fa2' :
d > 500 ? '#3288bd' :
d > 200 ? '#66c2a5' :
d > 100 ? '#abdda4' :
d > 50 ? '#e6f598' :
d > 20 ? '#ffffbf' :
d > 10 ? '#fee08b' :
'#fdae61';
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.density)
};
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 3,
color: 'black',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
var geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
geojson = L.geoJson(statesData, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
map.attributionControl.addAttribution('Population data &copy; <a href="http://census.gov/">US Census Bureau</a>');
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 10, 20, 50, 100, 200, 500, 1000],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
labels.push(
'<i style="background:' + getColor(from + 1) + '"></i> ' +
from + (to ? '&ndash;' + to : '+'));
}
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment