Skip to content

Commit

Permalink
Issue 1943 allow jscode in options (#2029)
Browse files Browse the repository at this point in the history
* First version

* Fix trailing commas

* Fix typing for GeoJson kwargs

* Update folium/utilities.py

Co-authored-by: Frank Anema <[email protected]>

* Update tests/plugins/test_grouped_layer_control.py

Co-authored-by: Frank Anema <[email protected]>

* Update tests/plugins/test_marker_cluster.py

Co-authored-by: Frank Anema <[email protected]>

* Update tests/test_folium.py

Co-authored-by: Frank Anema <[email protected]>

* Update tests/test_raster_layers.py

Co-authored-by: Frank Anema <[email protected]>

---------

Co-authored-by: Frank Anema <[email protected]>
  • Loading branch information
hansthen and Conengmo authored Nov 17, 2024
1 parent 2a2da39 commit f2ec195
Show file tree
Hide file tree
Showing 65 changed files with 213 additions and 313 deletions.
2 changes: 1 addition & 1 deletion folium/elements.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import List, Tuple

from branca.element import CssLink, Element, Figure, JavascriptLink, MacroElement
from jinja2 import Template

from folium.template import Template
from folium.utilities import JsCode


Expand Down
32 changes: 14 additions & 18 deletions folium/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,23 @@
from branca.colormap import ColorMap, LinearColormap, StepColormap
from branca.element import Element, Figure, Html, IFrame, JavascriptLink, MacroElement
from branca.utilities import color_brewer
from jinja2 import Template

from folium.elements import JSCSSMixin
from folium.folium import Map
from folium.map import FeatureGroup, Icon, Layer, Marker, Popup, Tooltip
from folium.template import Template
from folium.utilities import (
TypeJsonValue,
TypeLine,
TypePathOptions,
_parse_size,
camelize,
escape_backticks,
get_bounds,
get_obj_in_upper_tree,
image_to_url,
javascript_identifier_path_to_array_notation,
none_max,
none_min,
parse_options,
validate_locations,
)
from folium.vector_layers import Circle, CircleMarker, PolyLine, path_options
Expand Down Expand Up @@ -68,7 +66,7 @@ class RegularPolygonMarker(JSCSSMixin, Marker):
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = new L.RegularPolygonMarker(
{{ this.location|tojson }},
{{ this.options|tojson }}
{{ this.options|tojavascript }}
).addTo({{ this._parent.get_name() }});
{% endmacro %}
"""
Expand All @@ -95,7 +93,7 @@ def __init__(
self._name = "RegularPolygonMarker"
self.options = path_options(line=False, radius=radius, **kwargs)
self.options.update(
parse_options(
dict(
number_of_sides=number_of_sides,
rotation=rotation,
)
Expand Down Expand Up @@ -627,9 +625,7 @@ class GeoJson(Layer):
{%- if this.marker %}
pointToLayer: {{ this.get_name() }}_pointToLayer,
{%- endif %}
{%- for key, value in this.options.items() %}
{{ key }}: {{ value|tojson }},
{%- endfor %}
...{{this.options | tojavascript }}
});
function {{ this.get_name() }}_add (data) {
Expand Down Expand Up @@ -667,7 +663,7 @@ def __init__(
popup: Optional["GeoJsonPopup"] = None,
zoom_on_click: bool = False,
marker: Union[Circle, CircleMarker, Marker, None] = None,
**kwargs: TypeJsonValue,
**kwargs: Any,
):
super().__init__(name=name, overlay=overlay, control=control, show=show)
self._name = "GeoJson"
Expand All @@ -692,7 +688,7 @@ def __init__(
self.popup_keep_highlighted = popup_keep_highlighted

self.marker = marker
self.options = parse_options(**kwargs)
self.options = kwargs

self.data = self.process_data(data)

Expand Down Expand Up @@ -1267,7 +1263,7 @@ class GeoJsonTooltip(GeoJsonDetail):
{% macro script(this, kwargs) %}
{{ this._parent.get_name() }}.bindTooltip("""
+ GeoJsonDetail.base_template
+ """,{{ this.tooltip_options | tojson | safe }});
+ """,{{ this.tooltip_options | tojavascript }});
{% endmacro %}
"""
)
Expand All @@ -1293,7 +1289,7 @@ def __init__(
)
self._name = "GeoJsonTooltip"
kwargs.update({"sticky": sticky, "class_name": class_name})
self.tooltip_options = {camelize(key): kwargs[key] for key in kwargs.keys()}
self.tooltip_options = kwargs


class GeoJsonPopup(GeoJsonDetail):
Expand Down Expand Up @@ -1335,7 +1331,7 @@ class GeoJsonPopup(GeoJsonDetail):
{% macro script(this, kwargs) %}
{{ this._parent.get_name() }}.bindPopup("""
+ GeoJsonDetail.base_template
+ """,{{ this.popup_options | tojson | safe }});
+ """,{{ this.popup_options | tojavascript }});
{% endmacro %}
"""
)
Expand All @@ -1360,7 +1356,7 @@ def __init__(
)
self._name = "GeoJsonPopup"
kwargs.update({"class_name": self.class_name})
self.popup_options = {camelize(key): value for key, value in kwargs.items()}
self.popup_options = kwargs


class Choropleth(FeatureGroup):
Expand Down Expand Up @@ -1703,7 +1699,7 @@ class DivIcon(MacroElement):
_template = Template(
"""
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.divIcon({{ this.options|tojson }});
var {{ this.get_name() }} = L.divIcon({{ this.options|tojavascript }});
{{this._parent.get_name()}}.setIcon({{this.get_name()}});
{% endmacro %}
"""
Expand All @@ -1719,7 +1715,7 @@ def __init__(
):
super().__init__()
self._name = "DivIcon"
self.options = parse_options(
self.options = dict(
html=html,
icon_size=icon_size,
icon_anchor=icon_anchor,
Expand Down Expand Up @@ -1880,7 +1876,7 @@ class CustomIcon(Icon):
_template = Template(
"""
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.icon({{ this.options|tojson }});
var {{ this.get_name() }} = L.icon({{ this.options|tojavascript }});
{{ this._parent.get_name() }}.setIcon({{ this.get_name() }});
{% endmacro %}
"""
Expand All @@ -1898,7 +1894,7 @@ def __init__(
):
super(Icon, self).__init__()
self._name = "CustomIcon"
self.options = parse_options(
self.options = dict(
icon_url=image_to_url(icon_image),
icon_size=icon_size,
icon_anchor=icon_anchor,
Expand Down
10 changes: 4 additions & 6 deletions folium/folium.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@
from typing import Any, List, Optional, Sequence, Union

from branca.element import Element, Figure
from jinja2 import Template

from folium.elements import JSCSSMixin
from folium.map import Evented, FitBounds, Layer
from folium.raster_layers import TileLayer
from folium.template import Template
from folium.utilities import (
TypeBounds,
TypeJsonValue,
_parse_size,
parse_font_size,
parse_options,
temp_html_filepath,
validate_location,
)
Expand Down Expand Up @@ -204,9 +203,8 @@ class Map(JSCSSMixin, Evented):
{
center: {{ this.location|tojson }},
crs: L.CRS.{{ this.crs }},
{%- for key, value in this.options.items() %}
{{ key }}: {{ value|tojson }},
{%- endfor %}
...{{this.options|tojavascript}}
}
);
Expand Down Expand Up @@ -305,7 +303,7 @@ def __init__(
else:
self.zoom_control_position = False

self.options = parse_options(
self.options = dict(
max_bounds=max_bounds_array,
zoom=zoom_start,
zoom_control=False if self.zoom_control_position else zoom_control,
Expand Down
61 changes: 16 additions & 45 deletions folium/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@

import warnings
from collections import OrderedDict
from typing import Dict, List, Optional, Sequence, Tuple, Type, Union
from typing import List, Optional, Sequence, Union

from branca.element import Element, Figure, Html, MacroElement
from jinja2 import Template

from folium.elements import ElementAddToElement, EventHandler
from folium.template import Template
from folium.utilities import (
JsCode,
TypeBounds,
TypeJsonValue,
camelize,
escape_backticks,
parse_options,
validate_location,
Expand Down Expand Up @@ -109,7 +108,7 @@ class FeatureGroup(Layer):
"""
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.featureGroup(
{{ this.options|tojson }}
{{ this.options|tojavascript }}
);
{% endmacro %}
"""
Expand All @@ -126,7 +125,7 @@ def __init__(
super().__init__(name=name, overlay=overlay, control=control, show=show)
self._name = "FeatureGroup"
self.tile_name = name if name is not None else self.get_name()
self.options = parse_options(**kwargs)
self.options = kwargs


class LayerControl(MacroElement):
Expand Down Expand Up @@ -180,7 +179,7 @@ class LayerControl(MacroElement):
let {{ this.get_name() }} = L.control.layers(
{{ this.get_name() }}_layers.base_layers,
{{ this.get_name() }}_layers.overlays,
{{ this.options|tojson }}
{{ this.options|tojavascript }}
).addTo({{this._parent.get_name()}});
{%- if this.draggable %}
Expand All @@ -201,7 +200,7 @@ def __init__(
):
super().__init__()
self._name = "LayerControl"
self.options = parse_options(
self.options = dict(
position=position, collapsed=collapsed, autoZIndex=autoZIndex, **kwargs
)
self.draggable = draggable
Expand Down Expand Up @@ -263,7 +262,7 @@ class Icon(MacroElement):
"""
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.AwesomeMarkers.icon(
{{ this.options|tojson }}
{{ this.options|tojavascript }}
);
{{ this._parent.get_name() }}.setIcon({{ this.get_name() }});
{% endmacro %}
Expand Down Expand Up @@ -307,7 +306,7 @@ def __init__(
f"color argument of Icon should be one of: {self.color_options}.",
stacklevel=2,
)
self.options = parse_options(
self.options = dict(
marker_color=color,
icon_color=icon_color,
icon=icon,
Expand Down Expand Up @@ -356,7 +355,7 @@ class Marker(MacroElement):
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.marker(
{{ this.location|tojson }},
{{ this.options|tojson }}
{{ this.options|tojavascript }}
).addTo({{ this._parent.get_name() }});
{% endmacro %}
"""
Expand All @@ -374,7 +373,7 @@ def __init__(
super().__init__()
self._name = "Marker"
self.location = validate_location(location) if location is not None else None
self.options = parse_options(
self.options = dict(
draggable=draggable or None, autoPan=draggable or None, **kwargs
)
if icon is not None:
Expand Down Expand Up @@ -424,7 +423,7 @@ class Popup(Element):

_template = Template(
"""
var {{this.get_name()}} = L.popup({{ this.options|tojson }});
var {{this.get_name()}} = L.popup({{ this.options|tojavascript }});
{% for name, element in this.html._children.items() %}
{% if this.lazy %}
Expand Down Expand Up @@ -476,7 +475,7 @@ def __init__(

self.show = show
self.lazy = lazy
self.options = parse_options(
self.options = dict(
max_width=max_width,
autoClose=False if show or sticky else None,
closeOnClick=False if sticky else None,
Expand Down Expand Up @@ -526,22 +525,11 @@ class Tooltip(MacroElement):
`<div{% if this.style %} style={{ this.style|tojson }}{% endif %}>
{{ this.text }}
</div>`,
{{ this.options|tojson }}
{{ this.options|tojavascript }}
);
{% endmacro %}
"""
)
valid_options: Dict[str, Tuple[Type, ...]] = {
"pane": (str,),
"offset": (tuple,),
"direction": (str,),
"permanent": (bool,),
"sticky": (bool,),
"interactive": (bool,),
"opacity": (float, int),
"attribution": (str,),
"className": (str,),
}

def __init__(
self,
Expand All @@ -556,7 +544,7 @@ def __init__(
self.text = str(text)

kwargs.update({"sticky": sticky})
self.options = self.parse_options(kwargs)
self.options = kwargs

if style:
assert isinstance(
Expand All @@ -565,23 +553,6 @@ def __init__(
# noqa outside of type checking.
self.style = style

def parse_options(
self,
kwargs: Dict[str, TypeJsonValue],
) -> Dict[str, TypeJsonValue]:
"""Validate the provided kwargs and return options as json string."""
kwargs = {camelize(key): value for key, value in kwargs.items()}
for key in kwargs.keys():
assert (
key in self.valid_options
), "The option {} is not in the available options: {}.".format(
key, ", ".join(self.valid_options)
)
assert isinstance(
kwargs[key], self.valid_options[key]
), f"The option {key} must be one of the following types: {self.valid_options[key]}."
return kwargs


class FitBounds(MacroElement):
"""Fit the map to contain a bounding box with the
Expand Down Expand Up @@ -660,7 +631,7 @@ class FitOverlays(MacroElement):
}
});
if (bounds.isValid()) {
{{ this._parent.get_name() }}.{{ this.method }}(bounds, {{ this.options|tojson }});
{{ this._parent.get_name() }}.{{ this.method }}(bounds, {{ this.options|tojavascript }});
}
}
{{ this._parent.get_name() }}.on('overlayadd', customFlyToBounds);
Expand All @@ -682,7 +653,7 @@ def __init__(
self._name = "FitOverlays"
self.method = "flyToBounds" if fly else "fitBounds"
self.fit_on_map_load = fit_on_map_load
self.options = parse_options(padding=(padding, padding), max_zoom=max_zoom)
self.options = dict(padding=(padding, padding), max_zoom=max_zoom)


class CustomPane(MacroElement):
Expand Down
5 changes: 2 additions & 3 deletions folium/plugins/antpath.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from jinja2 import Template

from folium.elements import JSCSSMixin
from folium.template import Template
from folium.vector_layers import BaseMultiLocation, path_options


Expand Down Expand Up @@ -31,7 +30,7 @@ class AntPath(JSCSSMixin, BaseMultiLocation):
{% macro script(this, kwargs) %}
{{ this.get_name() }} = L.polyline.antPath(
{{ this.locations|tojson }},
{{ this.options|tojson }}
{{ this.options|tojavascript }}
).addTo({{this._parent.get_name()}});
{% endmacro %}
"""
Expand Down
Loading

0 comments on commit f2ec195

Please sign in to comment.