Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add choropleth support #40

Merged
merged 20 commits into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d3fcd8e
Add basic chloropleth support
Feb 3, 2018
d5a5435
Center zoom on click coordinates; rename polygons layer to 'cholorple…
Feb 19, 2018
07bf5e5
Add line_color, line_stroke, and line_width properties to Chloropleth…
Feb 20, 2018
3a5b3e6
Add chloropleth label layer; add template block for styling legend keys
Feb 20, 2018
bfd6a04
Add line styling to example chloropleth notebook
Feb 27, 2018
6b47dbc
Merge branch 'master' into chloropleth-support
Feb 27, 2018
79fd36a
Update internal documentation in ChloroplethViz class; add chloroplet…
Feb 27, 2018
360ef08
Update spelling to mapbox standard: 'choropleth'
Feb 28, 2018
ac8c100
Add tests for ChoroplethViz (currently uses polygons.geojson)
Feb 28, 2018
f1bcd21
Formatting and headers on choropleth notebook
Mar 2, 2018
9fa3853
Add vector layer support options; requesting direction for reconcilin…
Mar 6, 2018
b11c006
Single ChoroplethViz class defines either vector-based or geojson-bas…
Mar 17, 2018
722d747
Add support for categorical data-driven styling with vector data-join…
Mar 23, 2018
cb9972b
Merge branch 'master' into chloropleth-support; update styleUrl to st…
Mar 23, 2018
07b2ca7
Attempt to add test for utils.rgb_tuple_from_str
Mar 23, 2018
8084c7c
Merge branch 'master' into chloropleth-support
ryanbaumann Mar 27, 2018
cc147a0
Additional test coverage for ChoroplethViz and color_map, rgb_tuple_f…
Mar 29, 2018
29a3c36
Per @ryanbaumann 's comments: revert color data-join style to the old…
Mar 29, 2018
6af2548
Test fix
Mar 29, 2018
d80e99e
Minor refactor to color_map to prioritize returning exact match color…
Mar 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions examples/chloropleth-viz-example.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Mapboxgl Python Library\n",
"\n",
"https://github.com/mapbox/mapboxgl-jupyter"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import sys\n",
"import os\n",
"\n",
"from mapboxgl.viz import *\n",
"from mapboxgl.utils import *\n",
"from mapboxgl.colors import *\n",
"\n",
"# Must be a public token, starting with `pk`\n",
"token = os.getenv('MAPBOX_ACCESS_TOKEN')\n",
"\n",
"with open('us-states.geojson', 'r') as f:\n",
" data = json.load(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Chloropleths with interpolated color assignment"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_color_stops = [\n",
" [0.0, 'rgb(255,255,204)'],\n",
" [100.0, 'rgb(255,237,160)'],\n",
" [500.0, 'rgb(253,141,60)'],\n",
" [2000.0, 'rgb(227,26,28)'],\n",
" [5000.0, 'rgb(189,0,38)'],\n",
" [10000.0,'rgb(128,0,38)']\n",
"]\n",
" \n",
" \n",
"viz = ChloroplethViz(data, opacity=0.8)\n",
"viz.color_property = 'density'\n",
"viz.color_stops = sample_color_stops\n",
"viz.color_function_type = 'interpolate'\n",
"viz.default_color = '#bada55'\n",
"viz.center = (-96, 37.8)\n",
"viz.zoom = 3\n",
"viz.below_layer = 'waterway-label'\n",
"viz.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Chloropleths with match-type color scheme"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"match_color_stops = [\n",
" ['Massachusetts', 'rgb(46,204,113)'],\n",
" ['Utah', 'rgb(231,76,60)'],\n",
" ['California', 'rgb(142,68,173)'],\n",
"]\n",
" \n",
"viz = ChloroplethViz(data, opacity=0.8)\n",
"viz.color_property = 'name'\n",
"viz.color_stops = match_color_stops\n",
"viz.color_function_type = 'match'\n",
"viz.color_default = 'rgba(52,73,94,0.5)'\n",
"viz.center = (-96, 37.8)\n",
"viz.zoom = 3\n",
"viz.below_layer = 'waterway-label'\n",
"viz.show()"
]
}
],
"metadata": {
"anaconda-cloud": {
"attach-environment": true,
"environment": "Root",
"summary": "Mapboxgl Python Data Visualization example"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
1 change: 1 addition & 0 deletions examples/us-states.geojson

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions mapboxgl/templates/chloropleth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{% extends "main.html" %}

{% block javascript %}
var legend = document.getElementById('legend');

mapboxgl.accessToken = '{{ accessToken }}';

var map = new mapboxgl.Map({
container: 'map',
style: '{{ styleUrl }}',
center: {{ center }},
zoom: {{ zoom }},
transformRequest: (url, resourceType)=> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to change this to the latest master branch version that only adds the URL parameter if the request is to a Mapbox API: https://github.com/mapbox/mapboxgl-jupyter/blob/master/mapboxgl/templates/circle.html#L11

Copy link
Collaborator Author

@akacarlyann akacarlyann Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed with 07bf5e5.

return {
url: [url.slice(0, url.indexOf("?")+1), "pluginName=PythonMapboxgl&", url.slice(url.indexOf("?")+1)].join('')
}
}
});

map.addControl(new mapboxgl.NavigationControl());

calcCircleColorLegend({{ colorStops }}, "{{ colorProperty }}");

map.on('style.load', function() {

// Add data source
map.addSource("data", {
Copy link
Contributor

@ryanbaumann ryanbaumann Feb 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the choropleth source is a vector tile? This would be very common when working with Pandas, for example, when you have a tabular set of data that has a geo identifier like postcode that you want to match to a vector tile polygon source with a feature property matching postcode. https://www.mapbox.com/mapbox-gl-js/example/data-join/.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to figure out a way to make a choropleth viz in mapboxgl use the same python class constructor whether a user has a local geojson file or has a vector tile source to match to.

"type": "geojson",
"data": {{ geojson_data }},
"buffer": 1,
"maxzoom": 14
});

// Add data layer
map.addLayer({
"id": "polygons",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the layer name more descriptive- choropleth-fill. Then a label layer could be choropleth-label

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed with d5a5435.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added "chloropleth-label" layer in 3a5b3e6.

"source": "data",
"type": "fill",
"paint": {
"fill-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}"),
"fill-opacity": {{ opacity }}
}
}, "{{belowLayer}}" );

// Create a popup
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});

// Show the popup on mouseover
map.on('mousemove', 'polygons', function(e) {
map.getCanvas().style.cursor = 'pointer';

let f = e.features[0];
let popup_html = '<div>';

for (key in f.properties) {
popup_html += '<li><b> ' + key + '</b>: ' + f.properties[key] + ' </li>'
}

popup_html += '</div>'
popup.setLngLat(e.lngLat)
.setHTML(popup_html)
.addTo(map);
});

map.on('mouseleave', 'polygons', function() {
map.getCanvas().style.cursor = '';
popup.remove();
});

// Fly to on click
map.on('click', 'polygons', function(e) {
map.flyTo({
center: e.features[0].geometry.coordinates,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akacarlyann on click for choropleth, we'll want to center the map on the to the mouse pointer click location since the polygon could span a very large area.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed with d5a5435.

zoom: map.getZoom() + 1
});
});


});
{% endblock %}
36 changes: 36 additions & 0 deletions mapboxgl/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,39 @@ def add_unique_template_variables(self, options):
clusterRadius=self.clusterRadius,
clusterMaxZoom=self.clusterMaxZoom
))


class ChloroplethViz(MapViz):
"""Create a heatmap viz"""

def __init__(self,
data,
color_property=None,
color_stops=None,
color_default='grey',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add support for outline-color and outline-width properties here to control the style of the border around the choropleth.

I recommend handing these options by creating a second choropleth-line layer, using the line type in Mapbox GL style spec, where the line-color and line-width can be controlled easily.

Copy link
Collaborator Author

@akacarlyann akacarlyann Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed with 07bf5e5.

color_function_type='interpolate',
*args,
**kwargs):
"""Construct a Mapviz object

:param weight_property: property to determine heatmap weight. EX. "population"

"""
super(ChloroplethViz, self).__init__(data, *args, **kwargs)

self.template = 'chloropleth'
self.color_property = color_property
self.color_stops = color_stops
self.color_default = color_default
self.color_function_type = color_function_type

def add_unique_template_variables(self, options):
"""Update map template variables specific to heatmap visual"""
options.update(dict(
geojson_data=json.dumps(self.data, ensure_ascii=False),
colorProperty=self.color_property,
colorType=self.color_function_type,
colorStops=self.color_stops,
defaultColor=self.color_default
))