From 08f9d43ab24d7b0f805d12a5e45861aebb318860 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Tue, 11 Sep 2018 22:29:43 +0200 Subject: [PATCH 1/6] Use newer vega versions for altair compatibility Newer versions of vega, vega-lite and vega-embed are used. Additionally, the JavaScript for embedding is adapted. jsdelivr is used instead of cloudflare, since it allows to specify the newest minor version very nicely and it is recommended by the vega-lite docs https://vega.github.io/vega-lite/usage/embed.html#cdn --- folium/features.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/folium/features.py b/folium/features.py index 9cffef8b5..ecb377573 100644 --- a/folium/features.py +++ b/folium/features.py @@ -232,7 +232,7 @@ class VegaLite(Element): def __init__(self, data, width=None, height=None, left='0%', top='0%', position='relative'): - super(VegaLite, self).__init__() + super(self.__class__, self).__init__() 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): @@ -256,13 +256,10 @@ def render(self, **kwargs): """).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) {} - ); + const spec = {{this.json}}; + vegaEmbed({{this.get_name()}}, spec) + .then(function(result) {}) + .catch(console.error); """).render(this=self)), name=self.get_name()) figure = self.get_root() @@ -280,19 +277,19 @@ def render(self, **kwargs): """).render(this=self, **kwargs)), name=self.get_name()) figure.header.add_child( - JavascriptLink('https://d3js.org/d3.v3.min.js'), + JavascriptLink('https://d3js.org/d3.v5.min.js'), name='d3') figure.header.add_child( - JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega/2.6.5/vega.min.js'), # noqa + JavascriptLink('https://cdn.jsdelivr.net/npm/vega@3'), name='vega') figure.header.add_child( - JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.min.js'), # noqa + JavascriptLink('https://cdn.jsdelivr.net/npm/vega-lite@2'), name='vega-lite') figure.header.add_child( - JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.min.js'), # noqa + JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed') From 9b1c12bba1e0d7923d178bd2a41933cafe11f01a Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Fri, 16 Nov 2018 17:53:52 +0100 Subject: [PATCH 2/6] Allow multiple versions of vegalite to be used --- folium/features.py | 99 ++++++++++++++++++++++++++++++++++++------ tests/test_features.py | 54 +++++++++++++++++++++++ 2 files changed, 139 insertions(+), 14 deletions(-) diff --git a/folium/features.py b/folium/features.py index 18b657aef..1599360be 100644 --- a/folium/features.py +++ b/folium/features.py @@ -8,6 +8,7 @@ from __future__ import (absolute_import, division, print_function) import json +import os import warnings from branca.colormap import LinearColormap, StepColormap @@ -112,7 +113,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 name='dvf_js') @@ -208,6 +210,10 @@ def render(self, **kwargs): name='vega_parse') +def get_vega_versions(spec): + pass + + class VegaLite(Element): """ Creates a Vega-Lite chart element. @@ -260,17 +266,12 @@ 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("""
""").render(this=self, kwargs=kwargs)), name=self.get_name()) - self._parent.script.add_child(Element(Template(""" - const spec = {{this.json}}; - vegaEmbed({{this.get_name()}}, spec) - .then(function(result) {}) - .catch(console.error); - """).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.') @@ -285,9 +286,48 @@ def render(self, **kwargs): """).render(this=self, **kwargs)), name=self.get_name()) + 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: + self._embed_vegalite_v2(figure) + + def _get_vegalite_major_versions(self, spec): + schema = spec['$schema'] + version = os.path.splitext(os.path.split(schema)[1])[0].lstrip('v') + major_version = version.split('.')[0] + return major_version + + def _embed_vegalite_v3(self, figure): + self._parent.script.add_child(Element(Template(""" + const spec = {{this.json}}; + vegaEmbed({{this.get_name()}}, spec) + .then(function(result) {}) + .catch(console.error); + """).render(this=self)), name=self.get_name()) + figure.header.add_child( - JavascriptLink('https://d3js.org/d3.v5.min.js'), - name='d3') + 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._parent.script.add_child(Element(Template(""" + const spec = {{this.json}}; + vegaEmbed({{this.get_name()}}, spec) + .then(function(result) {}) + .catch(console.error); + """).render(this=self)), name=self.get_name()) figure.header.add_child( JavascriptLink('https://cdn.jsdelivr.net/npm/vega@3'), @@ -301,6 +341,33 @@ def render(self, **kwargs): JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed') + 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'), # noqa + name='vega') + + figure.header.add_child( + JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.js'), # noqa + name='vega-lite') + + figure.header.add_child( + JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.js'), # noqa + name='vega-embed') + class GeoJson(Layer): """ @@ -441,7 +508,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): @@ -556,11 +624,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 @@ -697,8 +767,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 @@ -834,7 +904,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='', @@ -1180,6 +1250,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) diff --git a/tests/test_features.py b/tests/test_features.py index af670f524..4a5b924eb 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -105,3 +105,57 @@ def test_color_line(): opacity=1) m.add_child(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' From 8d8b43122f5b579dc1c23a8123374dbb49e1acd0 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Fri, 16 Nov 2018 18:33:02 +0100 Subject: [PATCH 3/6] Use v2 as default vegalite version --- folium/features.py | 32 +++++++++++++++++--------------- tests/test_features.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/folium/features.py b/folium/features.py index 1599360be..e7ffe4889 100644 --- a/folium/features.py +++ b/folium/features.py @@ -296,18 +296,17 @@ def render(self, **kwargs): self._embed_vegalite_v2(figure) def _get_vegalite_major_versions(self, spec): - schema = spec['$schema'] - version = os.path.splitext(os.path.split(schema)[1])[0].lstrip('v') - major_version = version.split('.')[0] + try: + schema = spec['$schema'] + version = os.path.splitext(os.path.split(schema)[1])[0].lstrip('v') + major_version = version.split('.')[0] + except KeyError: + major_version = None + return major_version def _embed_vegalite_v3(self, figure): - self._parent.script.add_child(Element(Template(""" - const spec = {{this.json}}; - vegaEmbed({{this.get_name()}}, spec) - .then(function(result) {}) - .catch(console.error); - """).render(this=self)), name=self.get_name()) + self.vega_embed() figure.header.add_child( JavascriptLink('https://cdn.jsdelivr.net/npm/vega@4'), @@ -322,12 +321,7 @@ def _embed_vegalite_v3(self, figure): name='vega-embed') def _embed_vegalite_v2(self, figure): - self._parent.script.add_child(Element(Template(""" - const spec = {{this.json}}; - vegaEmbed({{this.get_name()}}, spec) - .then(function(result) {}) - .catch(console.error); - """).render(this=self)), name=self.get_name()) + self.vega_embed() figure.header.add_child( JavascriptLink('https://cdn.jsdelivr.net/npm/vega@3'), @@ -341,6 +335,14 @@ def _embed_vegalite_v2(self, figure): JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed') + def vega_embed(self): + self._parent.script.add_child(Element(Template(""" + const spec = {{this.json}}; + vegaEmbed({{this.get_name()}}, spec) + .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 = { diff --git a/tests/test_features.py b/tests/test_features.py index 4a5b924eb..7fd042d11 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -159,3 +159,33 @@ def test_get_vegalite_major_version(): 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 From f930c6710b11c5696fe9b236daa0811b994cc857 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Wed, 12 Dec 2018 09:15:04 +0100 Subject: [PATCH 4/6] Online JS links and remove function stub --- folium/features.py | 62 ++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 46 deletions(-) diff --git a/folium/features.py b/folium/features.py index e7ffe4889..602d118a3 100644 --- a/folium/features.py +++ b/folium/features.py @@ -210,10 +210,6 @@ def render(self, **kwargs): name='vega_parse') -def get_vega_versions(spec): - pass - - class VegaLite(Element): """ Creates a Vega-Lite chart element. @@ -253,6 +249,8 @@ def __init__(self, data, width=None, height=None, 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) @@ -264,8 +262,6 @@ 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(""" @@ -293,6 +289,7 @@ def render(self, **kwargs): 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): @@ -306,36 +303,20 @@ def _get_vegalite_major_versions(self, spec): return major_version def _embed_vegalite_v3(self, figure): - self.vega_embed() - - figure.header.add_child( - JavascriptLink('https://cdn.jsdelivr.net/npm/vega@4'), - name='vega') + self._vega_embed() - 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') + 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() + 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@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') - 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): + def _vega_embed(self): self._parent.script.add_child(Element(Template(""" const spec = {{this.json}}; vegaEmbed({{this.get_name()}}, spec) @@ -354,21 +335,10 @@ def _embed_vegalite_v1(self, figure): ); """).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'), # noqa - name='vega') - - figure.header.add_child( - JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.js'), # noqa - name='vega-lite') - - figure.header.add_child( - JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.js'), # noqa - name='vega-embed') + 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): From f7e4a5c4e6216dc5775b70409a8903cbba45d501 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Wed, 12 Dec 2018 14:29:11 +0100 Subject: [PATCH 5/6] Get major vegalite version without os module --- folium/features.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/folium/features.py b/folium/features.py index 8707b21c4..9a3ce3bd4 100644 --- a/folium/features.py +++ b/folium/features.py @@ -8,7 +8,6 @@ from __future__ import (absolute_import, division, print_function) import json -import os import warnings from branca.colormap import LinearColormap, StepColormap @@ -295,10 +294,10 @@ def render(self, **kwargs): def _get_vegalite_major_versions(self, spec): try: schema = spec['$schema'] - version = os.path.splitext(os.path.split(schema)[1])[0].lstrip('v') - major_version = version.split('.')[0] except KeyError: major_version = None + else: + major_version = schema.split('/')[-1].split('.')[0].lstrip('v') return major_version From 37dc3525068931eac3826772db925da72ef3f479 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Thu, 13 Dec 2018 17:36:26 +0100 Subject: [PATCH 6/6] Fix error due to redeclaration of js variable --- folium/features.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/folium/features.py b/folium/features.py index 9a3ce3bd4..def108819 100644 --- a/folium/features.py +++ b/folium/features.py @@ -317,8 +317,7 @@ def _embed_vegalite_v2(self, figure): def _vega_embed(self): self._parent.script.add_child(Element(Template(""" - const spec = {{this.json}}; - vegaEmbed({{this.get_name()}}, spec) + vegaEmbed({{this.get_name()}}, {{this.json}}) .then(function(result) {}) .catch(console.error); """).render(this=self)), name=self.get_name())