Added new choropleth map tutorial
This commit is contained in:
parent
f847e07c06
commit
6b4d64a320
@ -38,6 +38,8 @@
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="dist/leaflet.ie.css" /><![endif]-->
|
||||
<script src="{{ root }}dist/leaflet.js"></script>
|
||||
|
||||
{% if page.css %}<style>{{ page.css }}</style>{% endif %}
|
||||
|
||||
<script>
|
||||
CM_ATTR = 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
|
||||
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
|
||||
|
@ -93,15 +93,15 @@ input.title {font-size:1.5em;}
|
||||
textarea {width:390px;height:250px;padding:5px;}
|
||||
form.inline {line-height:3;}
|
||||
form.inline p {margin-bottom:0;}
|
||||
.error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
|
||||
.error, .alert, .notice, .success/*, .info*/ {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
|
||||
.error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
|
||||
.notice {background:#fff6bf;color:#514721;border-color:#ffd324;}
|
||||
.success {background:#e6efc2;color:#264409;border-color:#c6d880;}
|
||||
.info {background:#d5edf8;color:#205791;border-color:#92cae4;}
|
||||
/*.info {background:#d5edf8;color:#205791;border-color:#92cae4;}*/
|
||||
.error a, .alert a {color:#8a1f11;}
|
||||
.notice a {color:#514721;}
|
||||
.success a {color:#264409;}
|
||||
.info a {color:#205791;}
|
||||
/*.info a {color:#205791;}*/
|
||||
|
||||
/* grid.css */
|
||||
.container {width:950px;margin:0 auto;}
|
||||
@ -262,4 +262,4 @@ hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margi
|
||||
hr.space {background:#fff;color:#fff;visibility:hidden;}
|
||||
.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
|
||||
.clearfix, .container {display:block;}
|
||||
.clear {clear:both;}
|
||||
.clear {clear:both;}
|
||||
|
BIN
docs/images/choropleth.png
Normal file
BIN
docs/images/choropleth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
@ -35,6 +35,13 @@ In this pretty tutorial, you'll learn how to easily define your own icons for us
|
||||
|
||||
In this tutorial, you'll learn how to create and interact with map vectors created from [GeoJSON][5] objects.
|
||||
|
||||
***
|
||||
[<img src="docs/images/choropleth.png" class="example-img bordered-img" />][7]
|
||||
|
||||
### [Interactive Choropleth Map][7]
|
||||
|
||||
A case study of creating a colorful interactive choropleth map of US States Population Density with GeoJSON and some custom controls. News websites will love this.
|
||||
|
||||
***
|
||||
[<img src="docs/images/layers-control.png" class="example-img bordered-img" />][6]
|
||||
|
||||
@ -62,3 +69,4 @@ If you find that an important tutorial is missing here, let us know!
|
||||
[4]: examples/geojson.html
|
||||
[5]: http://geojson.org/
|
||||
[6]: examples/layers-control.html
|
||||
[7]: examples/choropleth.html
|
||||
|
169
examples/choropleth-example.html
Normal file
169
examples/choropleth-example.html
Normal file
@ -0,0 +1,169 @@
|
||||
<!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="../dist/leaflet.css" />
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="../dist/leaflet.ie.css" /><![endif]-->
|
||||
|
||||
<style>
|
||||
#map {
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 6px 8px;
|
||||
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: #777;
|
||||
}
|
||||
|
||||
.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="../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], 4);
|
||||
|
||||
var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
|
||||
attribution: 'Map data © 2011 OpenStreetMap contributors, Imagery © 2011 CloudMade',
|
||||
key: 'BC9A493B41014CAABB98F0471D759707',
|
||||
styleId: 22677
|
||||
}).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 ? '#800026' :
|
||||
d > 500 ? '#BD0026' :
|
||||
d > 200 ? '#E31A1C' :
|
||||
d > 100 ? '#FC4E2A' :
|
||||
d > 50 ? '#FD8D3C' :
|
||||
d > 20 ? '#FEB24C' :
|
||||
d > 10 ? '#FED976' :
|
||||
'#FFEDA0';
|
||||
}
|
||||
|
||||
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.bringToFront().setStyle({
|
||||
weight: 5,
|
||||
color: '#666',
|
||||
dashArray: '',
|
||||
fillOpacity: 0.7
|
||||
});
|
||||
|
||||
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 © <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 ? '–' + to : '+'));
|
||||
}
|
||||
|
||||
div.innerHTML = labels.join('<br>');
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
383
examples/choropleth.md
Normal file
383
examples/choropleth.md
Normal file
@ -0,0 +1,383 @@
|
||||
---
|
||||
layout: tutorial
|
||||
title: Interactive Choropleth Map
|
||||
css: "#map {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 6px 8px;
|
||||
font: 14px/18px 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: #777;
|
||||
}
|
||||
|
||||
.legend {
|
||||
text-align: left;
|
||||
line-height: 18px;
|
||||
color: #555;
|
||||
}
|
||||
.legend i {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
}"
|
||||
---
|
||||
|
||||
## Interactive Choropleth Map
|
||||
|
||||
This is a case study of creating a colorful interactive choropleth map of US States Population Density with the help of [GeoJSON](geojson.html) and some [custom controls](../reference.html#icontrol) (that will hopefully convince all the remaining major news and government websites that do not use Leaflet yet to start doing so).
|
||||
|
||||
The tutorial was inspired by the [Texas Tribune US Senate Runoff Results map](http://www.texastribune.org/about/staff/ryan-murphy/) (also powered by Leaflet), created by [Ryan Murphy](http://www.texastribune.org/about/staff/ryan-murphy/).
|
||||
|
||||
<div id="map" class="map"></div>
|
||||
|
||||
[View example on a separate page →](choropleth-example.html)
|
||||
|
||||
### Data Source
|
||||
|
||||
We'll be creating a visualization of population density per US state. As the amount of data (state shapes and the density value for each state) is not very big, the most convenient and simple way to store and then display it is [GeoJSON](geojson.html).
|
||||
|
||||
Each feature of our GeoJSON data ([us-states.js](us-states.js)) will look like this:
|
||||
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Alabama",
|
||||
"density": 94.65
|
||||
},
|
||||
"geometry": ...
|
||||
...
|
||||
}
|
||||
|
||||
The GeoJSON with state shapes was kindly shared by [Mike Bostock](http://bost.ocks.org/mike) of [D3](http://d3js.org/) fame, extended with density values from [this Wikipedia article](http://en.wikipedia.org/wiki/List_of_U.S._states_by_population_density) based on July 1st 2011 data from [US Census Bureau](http://www.census.gov/) and assigned to `statesData` JS variable.
|
||||
|
||||
### Basic States Map
|
||||
|
||||
Lets display our states data on a map with CloudMade tiles of style 22677 for nice grayscale tiles that look perfect as a background for visualizations:
|
||||
|
||||
var map = L.map('map').setView([37.8, -96], 4);
|
||||
|
||||
L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
|
||||
key: ...,
|
||||
attribution: ...,
|
||||
styleId: 22677
|
||||
}).addTo(map);
|
||||
|
||||
L.geoJson(statesData).addTo(map);
|
||||
|
||||
<div id="map2" style="height: 300px"></div>
|
||||
|
||||
|
||||
### Adding Some Color
|
||||
|
||||
Now we need to color the states according to their population density. Choosing nice colors for a map can be tricky, but there's a great tool that can help with it --- [ColorBrewer](http://colorbrewer2.org/). Using the values we got from it, we create a function that returns a color based on population density:
|
||||
|
||||
function getColor(d) {
|
||||
return d > 1000 ? '#800026' :
|
||||
d > 500 ? '#BD0026' :
|
||||
d > 200 ? '#E31A1C' :
|
||||
d > 100 ? '#FC4E2A' :
|
||||
d > 50 ? '#FD8D3C' :
|
||||
d > 20 ? '#FEB24C' :
|
||||
d > 10 ? '#FED976' :
|
||||
'#FFEDA0';
|
||||
}
|
||||
|
||||
Next we define a styling function for our GeoJSON layer so that its `fillColor` depends on `feature.properties.density` property, also adjusting the appearance a bit and adding a nice touch with dashed stroke.
|
||||
|
||||
function style(feature) {
|
||||
return {
|
||||
fillColor: getColor(feature.properties.density),
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
color: 'white',
|
||||
dashArray: '3',
|
||||
fillOpacity: 0.7
|
||||
};
|
||||
}
|
||||
|
||||
L.geoJson(statesData, {style: style}).addTo(map);
|
||||
|
||||
Looks much better now!
|
||||
|
||||
<div id="map3" style="height: 300px"></div>
|
||||
|
||||
|
||||
### Adding Interaction
|
||||
|
||||
Now lets make the states highlighted visually in some way when they are hovered with a mouse. First we'll define an event listener for layer `mouseover` event:
|
||||
|
||||
function highlightFeature(e) {
|
||||
var layer = e.target;
|
||||
|
||||
layer.bringToFront().setStyle({
|
||||
weight: 5,
|
||||
color: '#666',
|
||||
dashArray: '',
|
||||
fillOpacity: 0.7
|
||||
});
|
||||
}
|
||||
|
||||
Here we get access to the layer that was hovered through `e.target`, set a thick grey border on the layer as our highlight effect, also bringing it to the front so that the border doesn't clash with nearby states.
|
||||
|
||||
Next we'll define what happens on `mouseout`:
|
||||
|
||||
function resetHighlight(e) {
|
||||
geojson.resetStyle(e.target);
|
||||
}
|
||||
|
||||
The handy `geojson.resetStyle` method will reset the layer style to its default state (defined by our `style` function). For this to work, make sure our GeoJSON layer is accessible through the `geojson` variable by defining it before our listeners and assigning the layer to it later:
|
||||
|
||||
var geojson;
|
||||
// ... our listeners
|
||||
geojson = L.geoJson(...);
|
||||
|
||||
As an additional touch, lets define a `click` listener that zooms to the state:
|
||||
|
||||
function zoomToFeature(e) {
|
||||
map.fitBounds(e.target.getBounds());
|
||||
}
|
||||
|
||||
Now we'll use the `onEachFeature` option to add the listeners on our state layers:
|
||||
|
||||
function onEachFeature(feature, layer) {
|
||||
layer.on({
|
||||
mouseover: highlightFeature,
|
||||
mouseout: resetHighlight,
|
||||
click: zoomToFeature
|
||||
});
|
||||
}
|
||||
|
||||
geojson = L.geoJson(statesData, {
|
||||
style: style,
|
||||
onEachFeature: onEachFeature
|
||||
}).addTo(map);
|
||||
|
||||
This makes the states highlight nicely on hover and gives us the ability to add other interactions inside our listeners.
|
||||
|
||||
### Custom Info Control
|
||||
|
||||
We could use the usual popups on click to show information about different states, but we'll choose a different route --- showing it on state hover inside a [custom control](../reference.html#icontrol).
|
||||
|
||||
Here's the code for our control:
|
||||
|
||||
var info = L.control();
|
||||
|
||||
info.onAdd = function (map) {
|
||||
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
|
||||
this.update();
|
||||
return this._div;
|
||||
};
|
||||
|
||||
// method that we will use to update the control based on feature properties passed
|
||||
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);
|
||||
|
||||
We need to update the control when the user hovers over a state, so we'll also modify our listeners as follows:
|
||||
|
||||
function highlightFeature(e) {
|
||||
...
|
||||
info.update(layer.feature.properties);
|
||||
}
|
||||
|
||||
function resetHighlight(e) {
|
||||
...
|
||||
info.update();
|
||||
}
|
||||
|
||||
The control also needs some CSS styles to look nice:
|
||||
|
||||
{: .css}
|
||||
.info {
|
||||
padding: 6px 8px;
|
||||
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: #777;
|
||||
}
|
||||
|
||||
### Custom Legend Control
|
||||
|
||||
Creating a control with a legend is easier, since it is static and doesn't change on state hover. JavaScript code:
|
||||
|
||||
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 = [];
|
||||
|
||||
// loop through our density intervals and generate a label with a colored square for each interval
|
||||
for (var i = 0; i < grades.length; i++) {
|
||||
div.innerHTML +=
|
||||
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
|
||||
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
|
||||
}
|
||||
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
|
||||
CSS styles for the control (we also reuse the `info` class defined earlier):
|
||||
|
||||
{: .css}
|
||||
.legend {
|
||||
line-height: 18px;
|
||||
color: #555;
|
||||
}
|
||||
.legend i {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
Enjoy the result on [the top of this page](#map) or on a [separate page](choropleth-example.html).
|
||||
|
||||
<script src="us-states.js"></script>
|
||||
<script>
|
||||
var map2 = L.map('map2').setView([37.8, -96], 4);
|
||||
L.tileLayer(CM_URL, {attribution: CM_ATTR, styleId: 22677}).addTo(map2);
|
||||
L.geoJson(statesData).addTo(map2);
|
||||
|
||||
|
||||
var map3 = L.map('map3').setView([37.8, -96], 4);
|
||||
L.tileLayer(CM_URL, {attribution: CM_ATTR, styleId: 22677}).addTo(map3);
|
||||
L.geoJson(statesData, {style: style}).addTo(map3);
|
||||
|
||||
|
||||
var map = L.map('map').setView([37.8, -96], 4);
|
||||
|
||||
L.tileLayer(CM_URL, {attribution: CM_ATTR, styleId: 22677}).addTo(map);
|
||||
|
||||
// control that shows state info on hover
|
||||
var InfoControl = L.Control.extend({
|
||||
|
||||
onAdd: function (map) {
|
||||
this._div = L.DomUtil.create('div', 'info');
|
||||
this.update();
|
||||
return this._div;
|
||||
},
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
var info = new InfoControl();
|
||||
|
||||
info.addTo(map);
|
||||
|
||||
|
||||
// get color depending on population density value
|
||||
function getColor(d) {
|
||||
return d > 1000 ? '#800026' :
|
||||
d > 500 ? '#BD0026' :
|
||||
d > 200 ? '#E31A1C' :
|
||||
d > 100 ? '#FC4E2A' :
|
||||
d > 50 ? '#FD8D3C' :
|
||||
d > 20 ? '#FEB24C' :
|
||||
d > 10 ? '#FED976' :
|
||||
'#FFEDA0';
|
||||
}
|
||||
|
||||
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.bringToFront().setStyle({
|
||||
weight: 5,
|
||||
color: '#666',
|
||||
dashArray: '',
|
||||
fillOpacity: 0.7
|
||||
});
|
||||
|
||||
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 © <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 ? '–' + to : '+'));
|
||||
}
|
||||
|
||||
div.innerHTML = labels.join('<br>');
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
|
||||
</script>
|
54
examples/us-states.js
Normal file
54
examples/us-states.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user