77
88class TimeSliderChoropleth (JSCSSMixin , Layer ):
99 """
10- Creates a TimeSliderChoropleth plugin to append into a map with Map.add_child.
10+ Create a choropleth with a timeslider for timestamped data.
11+
12+ Visualize timestamped data, allowing users to view the choropleth at
13+ different timestamps using a slider.
1114
1215 Parameters
1316 ----------
@@ -16,6 +19,8 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
1619 styledict: dict
1720 A dictionary where the keys are the geojson feature ids and the values are
1821 dicts of `{time: style_options_dict}`
22+ highlight: bool, default False
23+ Whether to show a visual effect on mouse hover and click.
1924 name : string, default None
2025 The name of the Layer, as it will appear in LayerControls.
2126 overlay : bool, default False
@@ -34,65 +39,69 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
3439 _template = Template (
3540 """
3641 {% macro script(this, kwargs) %}
37- var timestamps = {{ this.timestamps|tojson }};
38- var styledict = {{ this.styledict|tojson }};
39- var current_timestamp = timestamps[{{ this.init_timestamp }}];
42+ {
43+ let timestamps = {{ this.timestamps|tojson }};
44+ let styledict = {{ this.styledict|tojson }};
45+ let current_timestamp = timestamps[{{ this.init_timestamp }}];
46+
47+ let slider_body = d3.select("body").insert("div", "div.folium-map")
48+ .attr("id", "slider_{{ this.get_name() }}");
49+ $("#slider_{{ this.get_name() }}").hide();
50+ // insert time slider label
51+ slider_body.append("output")
52+ .attr("width", "100")
53+ .style('font-size', '18px')
54+ .style('text-align', 'center')
55+ .style('font-weight', '500%')
56+ .style('margin', '5px');
4057 // insert time slider
41- d3.select("body").insert("p", ":first-child") .append("input")
58+ slider_body .append("input")
4259 .attr("type", "range")
4360 .attr("width", "100px")
4461 .attr("min", 0)
4562 .attr("max", timestamps.length - 1)
4663 .attr("value", {{ this.init_timestamp }})
47- .attr("id", "slider")
4864 .attr("step", "1")
4965 .style('align', 'center');
5066
51- // insert time slider output BEFORE time slider (text on top of slider)
52- d3.select("body").insert("p", ":first-child").append("output")
53- .attr("width", "100")
54- .attr("id", "slider-value")
55- .style('font-size', '18px')
56- .style('text-align', 'center')
57- .style('font-weight', '500%');
58-
59- var datestring = new Date(parseInt(current_timestamp)*1000).toDateString();
60- d3.select("output#slider-value").text(datestring);
67+ let datestring = new Date(parseInt(current_timestamp)*1000).toDateString();
68+ d3.select("#slider_{{ this.get_name() }} > output").text(datestring);
6169
62- fill_map = function(){
70+ let fill_map = function(){
6371 for (var feature_id in styledict){
6472 let style = styledict[feature_id]//[current_timestamp];
6573 var fillColor = 'white';
6674 var opacity = 0;
6775 if (current_timestamp in style){
6876 fillColor = style[current_timestamp]['color'];
6977 opacity = style[current_timestamp]['opacity'];
70- d3.selectAll('#feature-'+feature_id
78+ d3.selectAll('#{{ this.get_name() }}- feature-'+feature_id
7179 ).attr('fill', fillColor)
7280 .style('fill-opacity', opacity);
7381 }
7482 }
7583 }
7684
77- d3.select("#slider ").on("input", function() {
85+ d3.select("#slider_{{ this.get_name() }} > input ").on("input", function() {
7886 current_timestamp = timestamps[this.value];
7987 var datestring = new Date(parseInt(current_timestamp)*1000).toDateString();
80- d3.select("output#slider-value ").text(datestring);
88+ d3.select("#slider_{{ this.get_name() }} > output ").text(datestring);
8189 fill_map();
8290 });
8391
92+ let onEachFeature;
8493 {% if this.highlight %}
85- {{this.get_name()}}_onEachFeature = function onEachFeature (feature, layer) {
94+ onEachFeature = function(feature, layer) {
8695 layer.on({
8796 mouseout: function(e) {
8897 if (current_timestamp in styledict[e.target.feature.id]){
8998 var opacity = styledict[e.target.feature.id][current_timestamp]['opacity'];
90- d3.selectAll('#feature-'+e.target.feature.id).style('fill-opacity', opacity);
99+ d3.selectAll('#{{ this.get_name() }}- feature-'+e.target.feature.id).style('fill-opacity', opacity);
91100 }
92101 },
93102 mouseover: function(e) {
94103 if (current_timestamp in styledict[e.target.feature.id]){
95- d3.selectAll('#feature-'+e.target.feature.id).style('fill-opacity', 1);
104+ d3.selectAll('#{{ this.get_name() }}- feature-'+e.target.feature.id).style('fill-opacity', 1);
96105 }
97106 },
98107 click: function(e) {
@@ -103,8 +112,9 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
103112 {% endif %}
104113
105114 var {{ this.get_name() }} = L.geoJson(
106- {{ this.data|tojson }}
107- ).addTo({{ this._parent.get_name() }});
115+ {{ this.data|tojson }},
116+ {onEachFeature: onEachFeature}
117+ );
108118
109119 {{ this.get_name() }}.setStyle(function(feature) {
110120 if (feature.properties.style !== undefined){
@@ -115,11 +125,13 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
115125 }
116126 });
117127
118- function onOverlayAdd(e) {
128+ let onOverlayAdd = function (e) {
119129 {{ this.get_name() }}.eachLayer(function (layer) {
120- layer._path.id = 'feature-' + layer.feature.id;
130+ layer._path.id = '{{ this.get_name() }}- feature-' + layer.feature.id;
121131 });
122132
133+ $("#slider_{{ this.get_name() }}").show();
134+
123135 d3.selectAll('path')
124136 .attr('stroke', 'white')
125137 .attr('stroke-width', 0.8)
@@ -128,13 +140,16 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
128140
129141 fill_map();
130142 }
131- {{ this._parent.get_name() }}.on('overlayadd', onOverlayAdd);
132-
133- onOverlayAdd(); // fill map as layer is loaded
134-
135- {%- if not this.show %}
136- {{ this.get_name() }}.remove();
143+ {{ this.get_name() }}.on('add', onOverlayAdd);
144+ {{ this.get_name() }}.on('remove', function() {
145+ $("#slider_{{ this.get_name() }}").hide();
146+ })
147+
148+ {%- if this.show %}
149+ {{ this.get_name() }}.addTo({{ this._parent.get_name() }});
150+ $("#slider_{{ this.get_name() }}").show();
137151 {%- endif %}
152+ }
138153 {% endmacro %}
139154 """
140155 )
@@ -145,6 +160,7 @@ def __init__(
145160 self ,
146161 data ,
147162 styledict ,
163+ highlight : bool = False ,
148164 name = None ,
149165 overlay = True ,
150166 control = True ,
@@ -153,6 +169,7 @@ def __init__(
153169 ):
154170 super ().__init__ (name = name , overlay = overlay , control = control , show = show )
155171 self .data = GeoJson .process_data (GeoJson ({}), data )
172+ self .highlight = highlight
156173
157174 if not isinstance (styledict , dict ):
158175 raise ValueError (
@@ -165,13 +182,13 @@ def __init__(
165182 ) # noqa
166183
167184 # Make set of timestamps.
168- timestamps = set ()
185+ timestamps_set = set ()
169186 for feature in styledict .values ():
170- timestamps .update (set (feature .keys ()))
187+ timestamps_set .update (set (feature .keys ()))
171188 try :
172- timestamps = sorted (timestamps , key = int )
189+ timestamps = sorted (timestamps_set , key = int )
173190 except (TypeError , ValueError ):
174- timestamps = sorted (timestamps )
191+ timestamps = sorted (timestamps_set )
175192
176193 self .timestamps = timestamps
177194 self .styledict = styledict
0 commit comments