/
echarts.py
192 lines (156 loc) · 6.78 KB
/
echarts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
from __future__ import annotations
import json
import sys
from collections import defaultdict
from typing import (
TYPE_CHECKING, Any, Callable, ClassVar, List, Mapping, Optional,
)
import param
from bokeh.models import CustomJS
from pyviz_comms import JupyterComm
from ..util import lazy_load
from ..viewable import Viewable
from .base import ModelPane
if TYPE_CHECKING:
from bokeh.document import Document
from bokeh.model import Model
from pyviz_comms import Comm
class ECharts(ModelPane):
"""
ECharts panes allow rendering echarts.js dictionaries and pyecharts plots.
Reference: https://panel.holoviz.org/reference/panes/ECharts.html
:Example:
>>> pn.extension('echarts')
>>> ECharts(some_echart_dict_or_pyecharts_object, height=480, width=640)
"""
object = param.Parameter(default=None, doc="""
The Echarts object being wrapped. Can be an Echarts dictionary or a pyecharts chart""")
options = param.Parameter(default=None, doc="""
An optional dict of options passed to Echarts.setOption. Allows to fine-tune the rendering behavior.
For example, you might want to use `options={ "replaceMerge": ['series'] })` when updating
the `objects` with a value containing a smaller number of series.
""")
renderer = param.ObjectSelector(default="canvas", objects=["canvas", "svg"], doc="""
Whether to render as HTML canvas or SVG""")
theme = param.ObjectSelector(default="default", objects=["default", "light", "dark"], doc="""
Theme to apply to plots.""")
priority: ClassVar[float | bool | None] = None
_rename: ClassVar[Mapping[str, str | None]] = {"object": "data"}
_rerender_params: ClassVar[List[str]] = []
_updates: ClassVar[bool] = True
def __init__(self, object=None, **params):
super().__init__(object, **params)
self._py_callbacks = defaultdict(lambda: defaultdict(list))
self._js_callbacks = defaultdict(list)
@classmethod
def applies(cls, obj: Any, **params) -> float | bool | None:
if isinstance(obj, dict):
return 0
elif cls.is_pyecharts(obj):
return 0.8
return None
@classmethod
def is_pyecharts(cls, obj):
if 'pyecharts' in sys.modules:
import pyecharts
return isinstance(obj, pyecharts.charts.chart.Chart)
return False
def _process_event(self, event):
callbacks = self._py_callbacks.get(event.type, {})
for cb in callbacks.get(None, []):
cb(event)
if event.query is None:
return
for cb in callbacks.get(event.query, []):
cb(event)
def _get_js_events(self, ref):
js_events = defaultdict(list)
for event, specs in self._js_callbacks.items():
for (query, code, args) in specs:
models = {
name: viewable._models[ref][0] for name, viewable in args.items()
if ref in viewable._models
}
js_events[event].append({'query': query, 'callback': CustomJS(code=code, args=models)})
return dict(js_events)
def _process_param_change(self, params):
props = super()._process_param_change(params)
if 'data' not in props:
return props
data = props['data'] or {}
if not isinstance(data, dict):
w, h = data.width, data.height
props['data'] = data = json.loads(data.dump_options())
if not self.height and h:
props['height'] = int(h.replace('px', ''))
if not self.width and w:
props['width'] = int(w.replace('px', ''))
else:
props['data'] = data
if data.get('responsive'):
props['sizing_mode'] = 'stretch_both'
return props
def _get_properties(self, document: Document):
props = super()._get_properties(document)
props['event_config'] = {
event: list(queries) for event, queries in self._py_callbacks.items()
}
return props
def _get_model(
self, doc: Document, root: Optional[Model] = None,
parent: Optional[Model] = None, comm: Optional[Comm] = None
) -> Model:
self._bokeh_model = lazy_load(
'panel.models.echarts', 'ECharts', isinstance(comm, JupyterComm), root
)
model = super()._get_model(doc, root, parent, comm)
self._register_events('echarts_event', model=model, doc=doc, comm=comm)
return model
def on_event(self, event: str, callback: Callable, query: str | None = None):
"""
Register anevent handler which triggers when the specified event is triggered.
Reference: https://apache.github.io/echarts-handbook/en/concepts/event/
Arguments
---------
event: str
The name of the event to register a handler on, e.g. 'click'.
callback: str | CustomJS
The event handler to be executed when the event fires.
query: str | None
A query that determines when the event fires.
"""
self._py_callbacks[event][query].append(callback)
event_config = {event: list(queries) for event, queries in self._py_callbacks.items()}
for ref, (model, _) in self._models.items():
self._apply_update({}, {'event_config': event_config}, model, ref)
def js_on_event(self, event: str, callback: str | CustomJS, query: str | None = None, **args):
"""
Register a Javascript event handler which triggers when the
specified event is triggered. The callback can be a snippet
of Javascript code or a bokeh CustomJS object making it possible
to manipulate other models in response to an event.
Reference: https://apache.github.io/echarts-handbook/en/concepts/event/
Arguments
---------
event: str
The name of the event to register a handler on, e.g. 'click'.
code: str
The event handler to be executed when the event fires.
query: str | None
A query that determines when the event fires.
args: Viewable
A dictionary of Viewables to make available in the namespace
of the object.
"""
self._js_callbacks[event].append((query, callback, args))
for ref, (model, _) in self._models.items():
js_events = self._get_js_events(ref)
self._apply_update({}, {'js_events': js_events}, model, ref)
def setup_js_callbacks(root_view, root_model):
if 'panel.models.echarts' not in sys.modules:
return
ref = root_model.ref['id']
for pane in root_view.select(ECharts):
if ref in pane._models:
pane._models[ref][0].js_events = pane._get_js_events(ref)
Viewable._preprocessing_hooks.append(setup_js_callbacks)