8000 Add support for altair and vega-lite v5 (#4488) · holoviz/panel@097a570 · GitHub
[go: up one dir, main page]

Skip to content

Commit 097a570

Browse files
philippjfrmaximlt
authored andcommitted
Add support for altair and vega-lite v5 (#4488)
1 parent b4805e3 commit 097a570

File tree

3 files changed

+129
-30
lines changed

3 files changed

+129
-30
lines changed

examples/reference/panes/Vega.ipynb

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"outputs": [],
4949
"source": [
5050
"vegalite = {\n",
51-
" \"$schema\": \"https://vega.github.io/schema/vega-lite/v3.json\",\n",
51+
" \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.json\",\n",
5252
" \"data\": {\"url\": \"https://raw.githubusercontent.com/vega/vega/master/docs/data/barley.json\"},\n",
5353
" \"mark\": \"bar\",\n",
5454
" \"encoding\": {\n",
@@ -92,7 +92,7 @@
9292
"outputs": [],
9393
"source": [
9494
"vgl_pane.object = {\n",
95-
" \"$schema\": \"https://vega.github.io/schema/vega-lite/v3.json\",\n",
95+
" \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.json\",\n",
9696
" \"data\": {\n",
9797
" \"url\": \"https://raw.githubusercontent.com/vega/vega/master/docs/data/disasters.csv\"\n",
9898
" },\n",
@@ -109,11 +109,16 @@
109109
" },\n",
110110
" \"encoding\": {\n",
111111
" \"x\": {\n",
112-
" \"field\": \"Year\",\n",
113-
" \"type\": \"nominal\",\n",
114-
" \"axis\": {\"labelAngle\": 90}\n",
112+
" \"field\": \"Year\",\n",
113+
" \"type\": \"quantitative\",\n",
114+
" \"axis\": {\"labelAngle\": 90},\n",
115+
" \"scale\": {\"zero\": False}\n",
116+
" },\n",
117+
" \"y\": {\n",
118+
" \"field\": \"Entity\",\n",
119+
" \"type\": \"nominal\",\n",
120+
" \"axis\": {\"title\": \"\"}\n",
115121
" },\n",
116-
" \"y\": {\"field\": \"Entity\", \"type\": \"nominal\", \"axis\": {\"title\": \"\"}},\n",
117122
" \"size\": {\n",
118123
" \"field\": \"Deaths\",\n",
119124
" \"type\": \"quantitative\",\n",
@@ -525,32 +530,31 @@
525530
"source": [
526531
"from vega_datasets import data\n",
527532
"\n",
528-
"\n",
529-
"df = data.seattle_temps()[:100]\n",
533+
"temps = data.seattle_temps()[:300]\n",
530534
"\n",
531535
"brush = alt.selection_interval(name='brush')\n",
532536
"\n",
533-
"chart = alt.Chart(df).mark_circle().encode(\n",
537+
"chart = alt.Chart(temps).mark_circle().encode(\n",
534538
" x='date:T',\n",
535-
" y='temp:Q',\n",
539+
" y=alt.Y('temp:Q', scale={'zero': False}),\n",
536540
" color=alt.condition(brush, alt.value('coral'), alt.value('lightgray'))\n",
541+
").properties(\n",
542+
" width=500\n",
537543
").add_selection(\n",
538544
" brush\n",
539545
")\n",
540546
"\n",
541547
"def filtered_table(selection):\n",
542548
" if not selection:\n",
543549
" return '## No selection'\n",
544-
" print(selection)\n",
545550
" query = ' & '.join(\n",
546551
" f'\"{pd.to_datetime(values[0], unit=\"ms\")}\" <= `{col}` <= \"{pd.to_datetime(values[1], unit=\"ms\")}\"'\n",
547-
" if pd.api.types.is_datetime64_any_dtype(df[col]) else f'{values[0]} <= `{col}` <= {values[1]}'\n",
552+
" if pd.api.types.is_datetime64_any_dtype(temps[col]) else f'{values[0]} <= `{col}` <= {values[1]}'\n",
548553
" for col, values in selection.items()\n",
549554
" )\n",
550-
" print(query)\n",
551555
" return pn.Column(\n",
552556
" f'Query: {query}',\n",
553-
" pn.pane.DataFrame(df.query(query), width=600, height=300)\n",
557+
" pn.pane.DataFrame(temps.query(query), width=600, height=300)\n",
554558
" )\n",
555559
"\n",
556560
"\n",

panel/pane/vega.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import re
34
import sys
45

56
from typing import (
@@ -40,33 +41,59 @@ def ds_as_cds(dataset):
4041

4142
_containers = ['hconcat', 'vconcat', 'layer']
4243

44+
SCHEMA_REGEX = re.compile('^v(\d+)\.\d+\.\d+.json')
45+
4346
def _isin(obj, attr):
4447
if isinstance(obj, dict):
4548
return attr in obj
4649
else:
4750
return hasattr(obj, attr)
4851

49-
def _get_type(spec):
50-
if isinstance(spec, dict):
51-
return spec.get('type', 'interval')
52+
def _get_type(spec, version):
53+
if version >= 5:
54+
if isinstance(spec, dict):
55+
return spec.get('select', {}).get('type', 'interval')
56+
else:
57+
return getattr(spec.select, 'type', 'interval')
5258
else:
53-
return getattr(spec, 'type', 'interval')
54-
59+
if isinstance(spec, dict):
60+
return spec.get('type', 'interval')
61+
else:
62+
return getattr(spec, 'type', 'interval')
5563

56-
def _get_selections(obj):
64+
def _get_schema_version(obj, default_version: int = 5) -> int:
65+
if Vega.is_altair(obj):
66+
schema = obj.to_dict().get('$schema', '')
67+
else:
68+
schema = obj.get('$schema', '')
69+
version = schema.split('/')[-1]
70+
match = SCHEMA_REGEX.fullmatch(version)
71+
if match is None or not match.groups():
72+
return default_version
73+
return int(match.groups()[0])
74+
75+
def _get_selections(obj, version=None):
76+
if version is None:
77+
version = _get_schema_version(obj)
78+
key = 'params' if version >= 5 else 'selection'
5779
selections = {}
58-
if _isin(obj, 'selection'):
80+
if _isin(obj, key):
81+
params = obj[key]
82+
if version >= 5 and isinstance(params, list):
83+
params = {
84+
p.name if hasattr(p, 'name') else p['name']: p for p in params
85+
if getattr(p, 'param_type', None) == 'selection' or _isin(p, 'select')
86+
}
5987
try:
6088
selections.update({
61-
name: _get_type(spec)
62-
for name, spec in obj['selection'].items()
89+
name: _get_type(spec, version) for name, spec in params.items()
6390
})
6491
except (AttributeError, TypeError):
6592
pass
6693
for c in _containers:
6794
if _isin(obj, c):
6895
for subobj in obj[c]:
69-
selections.update(_get_selections(subobj))
96+
selections.update(_get_selections(subobj, version=version))
7097
return selections
7198

7299

panel/tests/pane/test_vega.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
altair_available = pytest.mark.skipif(alt is None, reason="requires altair")
1212

13-
1413
import numpy as np
1514

1615
import panel as pn
@@ -21,6 +20,7 @@
2120
blank_schema = {'$schema': ''}
2221

2322
vega4_config = {'view': {'continuousHeight': 300, 'continuousWidth': 400}}
23+
vega5_config = {'view': {'continuousHeight': 300, 'continuousWidth': 300}}
2424

2525
vega_example = {
2626
'config': {
@@ -38,6 +38,62 @@
3838
'$schema': 'https://vega.github.io/schema/vega-lite/v3.2.1.json'
3939
}
4040

41+
vega4_selection_example = {
42+
'config': {'view': {'continuousWidth': 300, 'continuousHeight': 300}},
43+
'data': {'url': 'https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json'},
44+
'mark': {'type': 'point'},
45+
'encoding': {
46+
'color': {
47+
'condition': {
48+
'selection': 'brush',
49+
'field': 'Species',
50+
'type': 'nominal'
51+
},
52+
'value': 'lightgray'},
53+
'x': {
54+
'field': 'Beak Length (mm)',
55+
'scale': {'zero': False},
56+
'type': 'quantitative'
57+
},
58+
'y': {
59+
'field': 'Beak Depth (mm)',
60+
'scale': {'zero': False},
61+
'type': 'quantitative'}
62+
},
63+
'height': 250,
64+
'selection': {'brush': {'type': 'interval'}},
65+
'width': 250,
66+
'$schema': 'https://vega.github.io/schema/vega-lite/v4.17.0.json'
67+
}
68+
69+
vega5_selection_example = {
70+
'config': {'view': {'continuousWidth': 300, 'continuousHeight': 300}},
71+
'data': {'url': 'https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json'},
72+
'mark': {'type': 'point'},
73+
'encoding': {
74+
'color': {
75+
'condition': {
76+
'param': 'brush',
77+
'field': 'Species',
78+
'type': 'nominal'
79+
},
80+
'value': 'lightgray'},
81+
'x': {
82+
'field': 'Beak Length (mm)',
83+
'scale': {'zero': False},
84+
'type': 'quantitative'
85+
},
86+
'y': {
87+
'field': 'Beak Depth (mm)',
88+
'scale': {'zero': False},
89+
'type': 'quantitative'}
90+
},
91+
'height': 250,
92+
'params': [{'name': 'brush', 'select': {'type': 'interval'}}],
93+
'width': 250,
94+
'$schema': 'https://vega.github.io/schema/vega-lite/v5.6.1.json'
95+
}
96+
4197
vega_inline_example = {
4298
'config': {
4399
'view': {'width': 400, 'height': 300},
@@ -190,6 +246,14 @@ def test_vega_pane_inline(document, comm):
190246
assert pane._models == {}
191247

192248

249+
def test_vega_lite_4_selection_spec(document, comm):
250+
vega = Vega(vega4_selection_example)
251+
assert vega._selections == {'brush': 'interval'}
252+
253+
def test_vega_lite_5_selection_spec(document, comm):
254+
vega = Vega(vega5_selection_example)
255+
assert vega._selections == {'brush': 'interval'}
256+
193257
def altair_example():
194258
import altair as alt
195259
data = alt.Data(values=[{'x': 'A', 'y': 5},
@@ -203,22 +267,23 @@ def altair_example():
203267
)
204268
return chart
205269

206-
207270
@altair_available
208271
def test_get_vega_pane_type_from_altair():
209272
assert PaneBase.get_pane_type(altair_example()) is Vega
210273

211-
212274
@altair_available
213275
def test_altair_pane(document, comm):
214-
pane = pn.panel(altair_example())
276+
pane = Vega(altair_example())
215277

216278
# Create pane
217279
model = pane.get_root(document, comm=comm)
218280
assert isinstance(model, VegaPlot)
219281

220282
expected = dict(vega_example, data={})
221-
if altair_version >= Version('4.0.0'):
283+
if altair_version >= Version('5.0.0rc1'):
284+
expected['mark'] = {'type': 'bar'}
285+
expected['config'] = vega5_config
286+
elif altair_version >= Version('4.0.0'):
222287
expected['config'] = vega4_config
223288
assert dict(model.data, **blank_schema) == dict(expected, **blank_schema)
224289

@@ -231,7 +296,10 @@ def test_altair_pane(document, comm):
231296
chart.data.values[0]['x'] = 'C'
232297
pane.object = chart
233298
point_example = dict(vega_example, data={}, mark='point')
234-
if altair_version >= Version('4.0.0'):
299+
if altair_version >= Version('5.0.0rc1'):
300+
point_example['mark'] = {'type': 'point'}
301+
point_example['config'] = vega5_config
302+
elif altair_version >= Version('4.0.0'):
235303
point_example['config'] = vega4_config
236304
assert dict(model.data, **blank_schema) == dict(point_example, **blank_schema)
237305
cds_data = model.data_sources['data'].data

0 commit comments

Comments
 (0)
0