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

Use newer vega versions for altair compatibility #959

Merged
96 changes: 67 additions & 29 deletions folium/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ def render(self, **kwargs):
'if it is not in a Figure.')

figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.3.0/leaflet-dvf.markers.min.js'), # noqa
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.3.0/leaflet-dvf.markers.min.js'),
# noqa
Copy link
Member

Choose a reason for hiding this comment

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

Please keep this all in one line.

name='dvf_js')


Expand Down Expand Up @@ -241,12 +242,14 @@ class VegaLite(Element):

def __init__(self, data, width=None, height=None,
Copy link
Member

Choose a reason for hiding this comment

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

You're only updating VegaLite it seems, why not Vega also?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, will do

left='0%', top='0%', position='relative'):
super(VegaLite, self).__init__()
super(self.__class__, self).__init__()
Copy link
Member

Choose a reason for hiding this comment

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

Please keep this PR clean and don't change this here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I somehow got a strange error at some point, when I used VegaLite instead of self.__class__, but I can revert that.

Copy link
Member

Choose a reason for hiding this comment

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

What was the error?

self._name = 'VegaLite'
self.data = data.to_json() if hasattr(data, 'to_json') else data
if isinstance(self.data, text_type) or isinstance(data, binary_type):
self.data = json.loads(self.data)

self.json = json.dumps(self.data)

# Size Parameters.
self.width = _parse_size(self.data.get('width', '100%') if
width is None else width)
Expand All @@ -258,22 +261,12 @@ def __init__(self, data, width=None, height=None,

def render(self, **kwargs):
"""Renders the HTML representation of the element."""
self.json = json.dumps(self.data)
vegalite_major_version = self._get_vegalite_major_versions(self.data)

self._parent.html.add_child(Element(Template("""
<div id="{{this.get_name()}}"></div>
""").render(this=self, kwargs=kwargs)), name=self.get_name())

self._parent.script.add_child(Element(Template("""
var embedSpec = {
mode: "vega-lite",
spec: {{this.json}}
};
vg.embed(
{{this.get_name()}}, embedSpec, function(error, result) {}
);
""").render(this=self)), name=self.get_name())

figure = self.get_root()
assert isinstance(figure, Figure), ('You cannot render this Element '
'if it is not in a Figure.')
Expand All @@ -288,21 +281,62 @@ def render(self, **kwargs):
</style>
""").render(this=self, **kwargs)), name=self.get_name())

figure.header.add_child(
JavascriptLink('https://d3js.org/d3.v3.min.js'),
name='d3')
if vegalite_major_version == '1':
self._embed_vegalite_v1(figure)
elif vegalite_major_version == '2':
self._embed_vegalite_v2(figure)
elif vegalite_major_version == '3':
self._embed_vegalite_v3(figure)
else:
# Version 2 is assumed as the default, if no version is given in the schema.
self._embed_vegalite_v2(figure)

def _get_vegalite_major_versions(self, spec):
try:
schema = spec['$schema']
except KeyError:
major_version = None
else:
major_version = schema.split('/')[-1].split('.')[0].lstrip('v')
Copy link
Member

Choose a reason for hiding this comment

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

1-line and less code 😄
Thanks!


figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega/2.6.5/vega.min.js'), # noqa
name='vega')
return major_version

figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.min.js'), # noqa
name='vega-lite')
def _embed_vegalite_v3(self, figure):
self._vega_embed()

figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.min.js'), # noqa
name='vega-embed')
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega@4'), name='vega')
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-lite@3'), name='vega-lite')
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed')

def _embed_vegalite_v2(self, figure):
self._vega_embed()

figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega@3'), name='vega')
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-lite@2'), name='vega-lite')
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed')

def _vega_embed(self):
self._parent.script.add_child(Element(Template("""
vegaEmbed({{this.get_name()}}, {{this.json}})
.then(function(result) {})
.catch(console.error);
""").render(this=self)), name=self.get_name())

def _embed_vegalite_v1(self, figure):
self._parent.script.add_child(Element(Template("""
var embedSpec = {
mode: "vega-lite",
spec: {{this.json}}
};
vg.embed(
{{this.get_name()}}, embedSpec, function(error, result) {}
);
""").render(this=self)), name=self.get_name())

figure.header.add_child(JavascriptLink('https://d3js.org/d3.v3.min.js'), name='d3')
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega/2.6.5/vega.js'), name='vega') # noqa
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.js'), name='vega-lite') # noqa
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.js'), name='vega-embed') # noqa


class GeoJson(Layer):
Expand Down Expand Up @@ -444,7 +478,8 @@ def style_data(self):

for feature in self.data['features']:
feature.setdefault('properties', {}).setdefault('style', {}).update(self.style_function(feature)) # noqa
feature.setdefault('properties', {}).setdefault('highlight', {}).update(self.highlight_function(feature)) # noqa
feature.setdefault('properties', {}).setdefault('highlight', {}).update(
self.highlight_function(feature)) # noqa
return json.dumps(self.data, sort_keys=True)

def _get_self_bounds(self):
Expand Down Expand Up @@ -559,11 +594,13 @@ def style_data(self):
a corresponding JSON output.

"""

def recursive_get(data, keys):
if len(keys):
return recursive_get(data.get(keys[0]), keys[1:])
else:
return data

geometries = recursive_get(self.data, self.object_path.split('.'))['geometries'] # noqa
for feature in geometries:
feature.setdefault('properties', {}).setdefault('style', {}).update(self.style_function(feature)) # noqa
Expand Down Expand Up @@ -700,8 +737,8 @@ def __init__(self, fields, aliases=None, labels=True,
' the same length.'
assert isinstance(labels, bool), 'labels requires a boolean value.'
assert isinstance(localize, bool), 'localize must be bool.'
assert 'permanent' not in kwargs, 'The `permanent` option does not ' \
'work with GeoJsonTooltip.'
assert 'permanent' not in kwargs, 'The `permanent` option does not ' \
'work with GeoJsonTooltip.'

self.fields = fields
self.aliases = aliases
Expand Down Expand Up @@ -851,7 +888,7 @@ class Choropleth(FeatureGroup):
... highlight=True)
"""

def __init__(self, geo_data, data=None, columns=None, key_on=None, # noqa
def __init__(self, geo_data, data=None, columns=None, key_on=None, # noqa
bins=6, fill_color='blue', nan_fill_color='black',
fill_opacity=0.6, nan_fill_opacity=None, line_color='black',
line_weight=1, line_opacity=1, name=None, legend_name='',
Expand Down Expand Up @@ -1197,6 +1234,7 @@ class ColorLine(FeatureGroup):
A ColorLine object that you can `add_to` a Map.

"""

def __init__(self, positions, colors, colormap=None, nb_steps=12,
weight=None, opacity=None, **kwargs):
super(ColorLine, self).__init__(**kwargs)
Expand Down
83 changes: 83 additions & 0 deletions tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,89 @@ def test_color_line():
m._repr_html_()


def test_get_vegalite_major_version():
spec_v2 = {'$schema': 'https://vega.github.io/schema/vega-lite/v2.6.0.json',
'config': {'view': {'height': 300, 'width': 400}},
'data': {'name': 'data-aac17e868e23f98b5e0830d45504be45'},
'datasets': {'data-aac17e868e23f98b5e0830d45504be45': [{'folium usage': 0,
'happiness': 1.0},
{'folium usage': 1,
'happiness': 2.718281828459045},
{'folium usage': 2,
'happiness': 7.38905609893065},
{'folium usage': 3,
'happiness': 20.085536923187668},
{'folium usage': 4,
'happiness': 54.598150033144236},
{'folium usage': 5,
'happiness': 148.4131591025766},
{'folium usage': 6,
'happiness': 403.4287934927351},
{'folium usage': 7,
'happiness': 1096.6331584284585},
{'folium usage': 8,
'happiness': 2980.9579870417283},
{'folium usage': 9,
'happiness': 8103.083927575384}]},
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
'y': {'field': 'happiness', 'type': 'quantitative'}},
'mark': 'point'}

vegalite_v2 = folium.features.VegaLite(spec_v2)

assert vegalite_v2._get_vegalite_major_versions(spec_v2) == '2'

spec_v1 = {'$schema': 'https://vega.github.io/schema/vega-lite/v1.3.1.json',
'data': {'values': [{'folium usage': 0, 'happiness': 1.0},
{'folium usage': 1, 'happiness': 2.718281828459045},
{'folium usage': 2, 'happiness': 7.38905609893065},
{'folium usage': 3, 'happiness': 20.085536923187668},
{'folium usage': 4, 'happiness': 54.598150033144236},
{'folium usage': 5, 'happiness': 148.4131591025766},
{'folium usage': 6, 'happiness': 403.4287934927351},
{'folium usage': 7, 'happiness': 1096.6331584284585},
{'folium usage': 8, 'happiness': 2980.9579870417283},
{'folium usage': 9, 'happiness': 8103.083927575384}]},
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
'y': {'field': 'happiness', 'type': 'quantitative'}},
'height': 300,
'mark': 'point',
'width': 400}

vegalite_v1 = folium.features.VegaLite(spec_v1)

assert vegalite_v1._get_vegalite_major_versions(spec_v1) == '1'

spec_no_version = {'config': {'view': {'height': 300, 'width': 400}},
'data': {'name': 'data-aac17e868e23f98b5e0830d45504be45'},
'datasets': {'data-aac17e868e23f98b5e0830d45504be45': [{'folium usage': 0,
'happiness': 1.0},
{'folium usage': 1,
'happiness': 2.718281828459045},
{'folium usage': 2,
'happiness': 7.38905609893065},
{'folium usage': 3,
'happiness': 20.085536923187668},
{'folium usage': 4,
'happiness': 54.598150033144236},
{'folium usage': 5,
'happiness': 148.4131591025766},
{'folium usage': 6,
'happiness': 403.4287934927351},
{'folium usage': 7,
'happiness': 1096.6331584284585},
{'folium usage': 8,
'happiness': 2980.9579870417283},
{'folium usage': 9,
'happiness': 8103.083927575384}]},
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
'y': {'field': 'happiness', 'type': 'quantitative'}},
'mark': 'point'}

vegalite_no_version = folium.features.VegaLite(spec_no_version)

assert vegalite_no_version._get_vegalite_major_versions(spec_no_version) is None

# GeoJsonTooltip GeometryCollection
def test_geojson_tooltip():
m = folium.Map([30.5, -97.5], zoom_start=10)
Expand Down