##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 ? '–' + 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)
.