|
1 |
| -""" |
2 |
| -module containing the class TimeDynamicGeoJson, which can be used to |
3 |
| -create choropleth maps with a time slider. |
4 |
| -""" |
5 |
| -from jinja2 import Template |
6 |
| -from branca.element import JavascriptLink, Figure |
| 1 | +import json |
| 2 | + |
| 3 | +from branca.element import Figure, JavascriptLink |
| 4 | + |
7 | 5 | from folium.features import GeoJson
|
8 | 6 |
|
| 7 | +from jinja2 import Template |
| 8 | + |
9 | 9 |
|
10 |
| -class TimeDynamicGeoJson(GeoJson): |
11 |
| - """ |
12 |
| - Creates a GeoJson object for plotting into a Map. |
13 |
| -
|
14 |
| - Parameters |
15 |
| - ---------- |
16 |
| - data: file, dict or str. |
17 |
| - The GeoJSON data you want to plot. |
18 |
| - * If file, then data will be read in the file and fully |
19 |
| - embedded in Leaflet's JavaScript. |
20 |
| - * If dict, then data will be converted to JSON and embedded |
21 |
| - in the JavaScript. |
22 |
| - * If str, then data will be passed to the JavaScript as-is. |
23 |
| - style_function: function, default None |
24 |
| - A function mapping a GeoJson Feature to a style dict. |
25 |
| - name : string, default None |
26 |
| - The name of the Layer, as it will appear in LayerControls |
27 |
| - overlay : bool, default False |
28 |
| - Adds the layer as an optional overlay (True) or the base layer (False). |
29 |
| - control : bool, default True |
30 |
| - Whether the Layer will be included in LayerControls |
31 |
| - smooth_factor: float, default None |
32 |
| - How much to simplify the polyline on each zoom level. More means |
33 |
| - better performance and smoother look, and less means more accurate |
34 |
| - representation. Leaflet defaults to 1.0. |
35 |
| -
|
36 |
| - Examples |
37 |
| - -------- |
38 |
| - >>> # Providing file that shall be embedded. |
39 |
| - >>> GeoJson(open('foo.json')) |
40 |
| - >>> # Providing filename that shall not be embedded. |
41 |
| - >>> GeoJson('foo.json') |
42 |
| - >>> # Providing dict. |
43 |
| - >>> GeoJson(json.load(open('foo.json'))) |
44 |
| - >>> # Providing string. |
45 |
| - >>> GeoJson(open('foo.json').read()) |
46 |
| -
|
47 |
| - >>> # Provide a style_function that color all states green but Alabama. |
48 |
| - >>> style_function = lambda x: {'fillColor': '#0000ff' if |
49 |
| - ... x['properties']['name']=='Alabama' else |
50 |
| - ... '#00ff00'} |
51 |
| - >>> GeoJson(geojson, style_function=style_function) |
52 |
| -
|
53 |
| - """ |
54 |
| - def __init__(self, data, styledict, **kwargs): |
55 |
| - super(TimeDynamicGeoJson, self).__init__(data, **kwargs) |
56 |
| - assert isinstance(styledict, dict), 'styledict must be a dictionary' |
| 10 | +class TimeSliderChoropleth(GeoJson): |
| 11 | + def __init__(self, data, styledict, name=None, overlay=True, control=True, **kwargs): |
| 12 | + super(TimeSliderChoropleth, self).__init__(data, name=name, overlay=overlay, control=control) |
| 13 | + if not isinstance(styledict, dict): |
| 14 | + raise ValueError('styledict must be a dictionary, got {!r}'.format(styledict)) |
57 | 15 | for val in styledict.values():
|
58 |
| - assert isinstance(val, dict), 'each item in styledict must be a dictionary' |
| 16 | + if not isinstance(val, dict): |
| 17 | + raise ValueError('Each item in styledict must be a dictionary, got {!r}'.format(val)) |
| 18 | + |
59 | 19 |
|
60 |
| - self.styledict = styledict |
| 20 | + # Make set of timestamps. |
| 21 | + timestamps = set() |
| 22 | + for feature in styledict.values(): |
| 23 | + timestamps.update(set(feature.keys())) |
| 24 | + timestamps = sorted(list(timestamps)) |
61 | 25 |
|
62 |
| - # make set of timestamps |
63 |
| - self.timestamps = set() |
64 |
| - for feature in self.styledict.values(): |
65 |
| - self.timestamps.update(set(feature.keys())) |
66 |
| - self.timestamps = sorted(list(self.timestamps)) |
| 26 | + self.timestamps = json.dumps(timestamps) |
| 27 | + self.styledict = json.dumps(styledict, sort_keys=True, indent=2) |
67 | 28 |
|
68 | 29 | self._template = Template(u"""
|
69 | 30 | {% macro script(this, kwargs) %}
|
@@ -102,7 +63,9 @@ def __init__(self, data, styledict, **kwargs):
|
102 | 63 | if (current_timestamp in style){
|
103 | 64 | fillColor = style[current_timestamp]['color'];
|
104 | 65 | opacity = style[current_timestamp]['opacity'];
|
105 |
| - d3.selectAll('#feature-'+feature_id).attr('fill', fillColor).style('fill-opacity', opacity); |
| 66 | + d3.selectAll('#feature-'+feature_id |
| 67 | + ).attr('fill', fillColor) |
| 68 | + .style('fill-opacity', opacity); |
106 | 69 | }
|
107 | 70 | }
|
108 | 71 | }
|
@@ -155,21 +118,28 @@ def __init__(self, data, styledict, **kwargs):
|
155 | 118 | ).addTo({{this._parent.get_name()}}
|
156 | 119 | );
|
157 | 120 |
|
158 |
| - {{this.get_name()}}.setStyle(function(feature) {feature.properties.style;}); |
| 121 | + {{this.get_name()}}.setStyle(function(feature) {feature.properties.style;}); |
159 | 122 |
|
160 | 123 | {{ this.get_name() }}.eachLayer(function (layer) {
|
161 | 124 | layer._path.id = 'feature-' + layer.feature.id;
|
162 | 125 | });
|
163 | 126 |
|
164 |
| - d3.selectAll('path').attr('stroke', 'white').attr('stroke-width', 0.8).attr('stroke-dasharray', '5,5').attr('fill-opacity', 0); |
| 127 | + d3.selectAll('path') |
| 128 | + .attr('stroke', 'white') |
| 129 | + .attr('stroke-width', 0.8) |
| 130 | + .attr('stroke-dasharray', '5,5') |
| 131 | + .attr('fill-opacity', 0); |
165 | 132 | fill_map();
|
166 | 133 |
|
167 | 134 | {% endmacro %}
|
168 | 135 | """)
|
169 | 136 |
|
170 | 137 | def render(self, **kwargs):
|
171 |
| - super(TimeDynamicGeoJson, self).render(**kwargs) |
| 138 | + super(TimeSliderChoropleth, self).render(**kwargs) |
172 | 139 | figure = self.get_root()
|
173 | 140 | assert isinstance(figure, Figure), ('You cannot render this Element '
|
174 | 141 | 'if it is not in a Figure.')
|
175 |
| - figure.header.add_child(JavascriptLink('https://d3js.org/d3.v4.min.js')) # noqa |
| 142 | + figure.header.add_child( |
| 143 | + JavascriptLink('https://d3js.org/d3.v4.min.js'), |
| 144 | + name='d3v4' |
| 145 | + ) |
0 commit comments