Skip to content

Commit 761cc3e

Browse files
JarnoRFBConengmo
authored andcommitted
Detect VegaLite version (#959)
This allows multiple versions of vegalite to be used. Use v2 as default vegalite version.
1 parent f8ac8dc commit 761cc3e

File tree

2 files changed

+150
-29
lines changed

2 files changed

+150
-29
lines changed

folium/features.py

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def render(self, **kwargs):
112112
'if it is not in a Figure.')
113113

114114
figure.header.add_child(
115-
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.3.0/leaflet-dvf.markers.min.js'), # noqa
115+
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.3.0/leaflet-dvf.markers.min.js'),
116+
# noqa
116117
name='dvf_js')
117118

118119

@@ -241,12 +242,14 @@ class VegaLite(Element):
241242

242243
def __init__(self, data, width=None, height=None,
243244
left='0%', top='0%', position='relative'):
244-
super(VegaLite, self).__init__()
245+
super(self.__class__, self).__init__()
245246
self._name = 'VegaLite'
246247
self.data = data.to_json() if hasattr(data, 'to_json') else data
247248
if isinstance(self.data, text_type) or isinstance(data, binary_type):
248249
self.data = json.loads(self.data)
249250

251+
self.json = json.dumps(self.data)
252+
250253
# Size Parameters.
251254
self.width = _parse_size(self.data.get('width', '100%') if
252255
width is None else width)
@@ -258,22 +261,12 @@ def __init__(self, data, width=None, height=None,
258261

259262
def render(self, **kwargs):
260263
"""Renders the HTML representation of the element."""
261-
self.json = json.dumps(self.data)
264+
vegalite_major_version = self._get_vegalite_major_versions(self.data)
262265

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

267-
self._parent.script.add_child(Element(Template("""
268-
var embedSpec = {
269-
mode: "vega-lite",
270-
spec: {{this.json}}
271-
};
272-
vg.embed(
273-
{{this.get_name()}}, embedSpec, function(error, result) {}
274-
);
275-
""").render(this=self)), name=self.get_name())
276-
277270
figure = self.get_root()
278271
assert isinstance(figure, Figure), ('You cannot render this Element '
279272
'if it is not in a Figure.')
@@ -288,21 +281,62 @@ def render(self, **kwargs):
288281
</style>
289282
""").render(this=self, **kwargs)), name=self.get_name())
290283

291-
figure.header.add_child(
292-
JavascriptLink('https://d3js.org/d3.v3.min.js'),
293-
name='d3')
284+
if vegalite_major_version == '1':
285+
self._embed_vegalite_v1(figure)
286+
elif vegalite_major_version == '2':
287+
self._embed_vegalite_v2(figure)
288+
elif vegalite_major_version == '3':
289+
self._embed_vegalite_v3(figure)
290+
else:
291+
# Version 2 is assumed as the default, if no version is given in the schema.
292+
self._embed_vegalite_v2(figure)
293+
294+
def _get_vegalite_major_versions(self, spec):
295+
try:
296+
schema = spec['$schema']
297+
except KeyError:
298+
major_version = None
299+
else:
300+
major_version = schema.split('/')[-1].split('.')[0].lstrip('v')
294301

295-
figure.header.add_child(
296-
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega/2.6.5/vega.min.js'), # noqa
297-
name='vega')
302+
return major_version
298303

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

303-
figure.header.add_child(
304-
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.min.js'), # noqa
305-
name='vega-embed')
307+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega@4'), name='vega')
308+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-lite@3'), name='vega-lite')
309+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed')
310+
311+
def _embed_vegalite_v2(self, figure):
312+
self._vega_embed()
313+
314+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega@3'), name='vega')
315+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-lite@2'), name='vega-lite')
316+
figure.header.add_child(JavascriptLink('https://cdn.jsdelivr.net/npm/vega-embed@3'), name='vega-embed')
317+
318+
def _vega_embed(self):
319+
self._parent.script.add_child(Element(Template("""
320+
vegaEmbed({{this.get_name()}}, {{this.json}})
321+
.then(function(result) {})
322+
.catch(console.error);
323+
""").render(this=self)), name=self.get_name())
324+
325+
def _embed_vegalite_v1(self, figure):
326+
self._parent.script.add_child(Element(Template("""
327+
var embedSpec = {
328+
mode: "vega-lite",
329+
spec: {{this.json}}
330+
};
331+
vg.embed(
332+
{{this.get_name()}}, embedSpec, function(error, result) {}
333+
);
334+
""").render(this=self)), name=self.get_name())
335+
336+
figure.header.add_child(JavascriptLink('https://d3js.org/d3.v3.min.js'), name='d3')
337+
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega/2.6.5/vega.js'), name='vega') # noqa
338+
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-lite/1.3.1/vega-lite.js'), name='vega-lite') # noqa
339+
figure.header.add_child(JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/vega-embed/2.2.0/vega-embed.js'), name='vega-embed') # noqa
306340

307341

308342
class GeoJson(Layer):
@@ -444,7 +478,8 @@ def style_data(self):
444478

445479
for feature in self.data['features']:
446480
feature.setdefault('properties', {}).setdefault('style', {}).update(self.style_function(feature)) # noqa
447-
feature.setdefault('properties', {}).setdefault('highlight', {}).update(self.highlight_function(feature)) # noqa
481+
feature.setdefault('properties', {}).setdefault('highlight', {}).update(
482+
self.highlight_function(feature)) # noqa
448483
return json.dumps(self.data, sort_keys=True)
449484

450485
def _get_self_bounds(self):
@@ -559,11 +594,13 @@ def style_data(self):
559594
a corresponding JSON output.
560595
561596
"""
597+
562598
def recursive_get(data, keys):
563599
if len(keys):
564600
return recursive_get(data.get(keys[0]), keys[1:])
565601
else:
566602
return data
603+
567604
geometries = recursive_get(self.data, self.object_path.split('.'))['geometries'] # noqa
568605
for feature in geometries:
569606
feature.setdefault('properties', {}).setdefault('style', {}).update(self.style_function(feature)) # noqa
@@ -700,8 +737,8 @@ def __init__(self, fields, aliases=None, labels=True,
700737
' the same length.'
701738
assert isinstance(labels, bool), 'labels requires a boolean value.'
702739
assert isinstance(localize, bool), 'localize must be bool.'
703-
assert 'permanent' not in kwargs, 'The `permanent` option does not ' \
704-
'work with GeoJsonTooltip.'
740+
assert 'permanent' not in kwargs, 'The `permanent` option does not ' \
741+
'work with GeoJsonTooltip.'
705742

706743
self.fields = fields
707744
self.aliases = aliases
@@ -851,7 +888,7 @@ class Choropleth(FeatureGroup):
851888
... highlight=True)
852889
"""
853890

854-
def __init__(self, geo_data, data=None, columns=None, key_on=None, # noqa
891+
def __init__(self, geo_data, data=None, columns=None, key_on=None, # noqa
855892
bins=6, fill_color='blue', nan_fill_color='black',
856893
fill_opacity=0.6, nan_fill_opacity=None, line_color='black',
857894
line_weight=1, line_opacity=1, name=None, legend_name='',
@@ -1197,6 +1234,7 @@ class ColorLine(FeatureGroup):
11971234
A ColorLine object that you can `add_to` a Map.
11981235
11991236
"""
1237+
12001238
def __init__(self, positions, colors, colormap=None, nb_steps=12,
12011239
weight=None, opacity=None, **kwargs):
12021240
super(ColorLine, self).__init__(**kwargs)

tests/test_features.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,89 @@ def test_color_line():
111111
m._repr_html_()
112112

113113

114+
def test_get_vegalite_major_version():
115+
spec_v2 = {'$schema': 'https://vega.github.io/schema/vega-lite/v2.6.0.json',
116+
'config': {'view': {'height': 300, 'width': 400}},
117+
'data': {'name': 'data-aac17e868e23f98b5e0830d45504be45'},
118+
'datasets': {'data-aac17e868e23f98b5e0830d45504be45': [{'folium usage': 0,
119+
'happiness': 1.0},
120+
{'folium usage': 1,
121+
'happiness': 2.718281828459045},
122+
{'folium usage': 2,
123+
'happiness': 7.38905609893065},
124+
{'folium usage': 3,
125+
'happiness': 20.085536923187668},
126+
{'folium usage': 4,
127+
'happiness': 54.598150033144236},
128+
{'folium usage': 5,
129+
'happiness': 148.4131591025766},
130+
{'folium usage': 6,
131+
'happiness': 403.4287934927351},
132+
{'folium usage': 7,
133+
'happiness': 1096.6331584284585},
134+
{'folium usage': 8,
135+
'happiness': 2980.9579870417283},
136+
{'folium usage': 9,
137+
'happiness': 8103.083927575384}]},
138+
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
139+
'y': {'field': 'happiness', 'type': 'quantitative'}},
140+
'mark': 'point'}
141+
142+
vegalite_v2 = folium.features.VegaLite(spec_v2)
143+
144+
assert vegalite_v2._get_vegalite_major_versions(spec_v2) == '2'
145+
146+
spec_v1 = {'$schema': 'https://vega.github.io/schema/vega-lite/v1.3.1.json',
147+
'data': {'values': [{'folium usage': 0, 'happiness': 1.0},
148+
{'folium usage': 1, 'happiness': 2.718281828459045},
149+
{'folium usage': 2, 'happiness': 7.38905609893065},
150+
{'folium usage': 3, 'happiness': 20.085536923187668},
151+
{'folium usage': 4, 'happiness': 54.598150033144236},
152+
{'folium usage': 5, 'happiness': 148.4131591025766},
153+
{'folium usage': 6, 'happiness': 403.4287934927351},
154+
{'folium usage': 7, 'happiness': 1096.6331584284585},
155+
{'folium usage': 8, 'happiness': 2980.9579870417283},
156+
{'folium usage': 9, 'happiness': 8103.083927575384}]},
157+
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
158+
'y': {'field': 'happiness', 'type': 'quantitative'}},
159+
'height': 300,
160+
'mark': 'point',
161+
'width': 400}
162+
163+
vegalite_v1 = folium.features.VegaLite(spec_v1)
164+
165+
assert vegalite_v1._get_vegalite_major_versions(spec_v1) == '1'
166+
167+
spec_no_version = {'config': {'view': {'height': 300, 'width': 400}},
168+
'data': {'name': 'data-aac17e868e23f98b5e0830d45504be45'},
169+
'datasets': {'data-aac17e868e23f98b5e0830d45504be45': [{'folium usage': 0,
170+
'happiness': 1.0},
171+
{'folium usage': 1,
172+
'happiness': 2.718281828459045},
173+
{'folium usage': 2,
174+
'happiness': 7.38905609893065},
175+
{'folium usage': 3,
176+
'happiness': 20.085536923187668},
177+
{'folium usage': 4,
178+
'happiness': 54.598150033144236},
179+
{'folium usage': 5,
180+
'happiness': 148.4131591025766},
181+
{'folium usage': 6,
182+
'happiness': 403.4287934927351},
183+
{'folium usage': 7,
184+
'happiness': 1096.6331584284585},
185+
{'folium usage': 8,
186+
'happiness': 2980.9579870417283},
187+
{'folium usage': 9,
188+
'happiness': 8103.083927575384}]},
189+
'encoding': {'x': {'field': 'folium usage', 'type': 'quantitative'},
190+
'y': {'field': 'happiness', 'type': 'quantitative'}},
191+
'mark': 'point'}
192+
193+
vegalite_no_version = folium.features.VegaLite(spec_no_version)
194+
195+
assert vegalite_no_version._get_vegalite_major_versions(spec_no_version) is None
196+
114197
# GeoJsonTooltip GeometryCollection
115198
def test_geojson_tooltip():
116199
m = folium.Map([30.5, -97.5], zoom_start=10)

0 commit comments

Comments
 (0)