We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Echarts not updated when removing a serie
#### Software version info
- python 3.10.12
- panel 1.2.1
- bokeh 3.2.1
- MacOS Ventura 13.5
- Chrome Version 115.0.5790.170 arm64

#### Description 
I have a param `ListSelector`, used to build a panel `CheckBoxGroup`, and an `echart`
The number of series of the echart varies according to the number of values selected in the `CheckBoxGroup`.

When I click new values, a new serie is added, and the plot is properly updated.
But when I unclick an existing value, the plot isn't updated.

More details : 
- in both cases, the echart series are updated. see the reproduction code below.
- having a look at the messages passing through the Websocket, I know that "ModelChanged" messages are received by the browser, *with updated series*. Check screenshots below.

- It's really only *removing* a serie that is failing. Adding series always work. This is illustrated in the video below. For instance, running my reproduction code, run the following sequence : 
  - check 2 (it's updated)
  - check 4 (it's updated)
  - uncheck 4 (it's not updated)
  - check8 (it's updated as expected)

#### Complete, minimal, self-contained example code that reproduces the issue

```python 
import panel as pn
import param

class DemoEchartsBug(pn.viewable.Viewer):

    numbers = param.ListSelector(default=[],
                                objects=[2, 4, 8])

    def __init__(self, **params):
        super().__init__(**params)

        self.cb_group = pn.widgets.CheckBoxGroup.from_param(self.param.numbers)
    
    @param.depends("numbers")
    def plot(self):

        echart_index = list(range(10))
        
        echart_series = [
            {
                "type": "bar",
                "name": str(num),
                "data": [i * num for i in range(10)],
            }
            for num in self.numbers
        ]

        echart_config = {
            "xAxis": [{"type": "category",  "data": echart_index}],
            "yAxis": [{"type": "value"}],
            "series": echart_series,
        }

        echart_pane = pn.pane.ECharts(echart_config,
                                      width=800,
                                      height=400)

        return pn.Column(
                pn.pane.Markdown(f"# numbers : {self.numbers}"),
                echart_pane
        )

    def __panel__(self):

        return pn.Column(
            pn.pane.Markdown(" # Bug with ECharts"),
            self.cb_group,
            pn.layout.Divider(),
            self.plot
        )
```

#### Stack traceback and/or browser JavaScript console output
No console output

#### Screen recording 

https://github.com/holoviz/panel/assets/756464/9b4cfdf5-1414-4b1f-ad7f-c9f88981e0bd



#### Screenshots of the messages in the websocket

- Step 1 : Before removing a serie. There are two series in the echart config
![step 1 - before removing a serie](https://github.com/holoviz/panel/assets/756464/d2e45c03-0850-4211-9c13-21c2c1cde410)

- Step 2 : After removing a serie. There is one serie in the echart config, as it should be. 
![step 2 - after removing a serie](https://github.com/holoviz/panel/assets/756464/a877930c-0898-4d75-9642-e77c88ea6054)



--- END ISSUE ---

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

Path: `panel/models/echarts.py`
Content:
```
1 """
2 Defines custom bokeh model to render ECharts plots.
3 """
4 from bokeh.core.properties import (
5     Any, Dict, Enum, List, Nullable, String,
6 )
7 from bokeh.events import ModelEvent
8 from bokeh.models import LayoutDOM
9 
10 from ..config import config
11 from ..io.resources import bundled_files
12 from ..util import classproperty
13 
14 
15 class EChartsEvent(ModelEvent):
16 
17     event_name = 'echarts_event'
18 
19     def __init__(self, model, type=None, data=None, query=None):
20         self.type = type
21         self.data = data
22         self.query = query
23         super().__init__(model=model)
24 
25 
26 class ECharts(LayoutDOM):
27     """
28     A Bokeh model that wraps around an ECharts plot and renders it
29     inside a Bokeh.
30     """
31 
32     __javascript_raw__ = [
33         f"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min.js",
34         f"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min.js"
35     ]
36 
37     @classproperty
38     def __javascript__(cls):
39         return bundled_files(cls)
40 
41     @classproperty
42     def __js_skip__(cls):
43         return {
44             'echarts': cls.__javascript__[:1]
45         }
46 
47     __js_require__ = {
48         'paths': {
49             "echarts":  f"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min",
50             "echarts-gl": f"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min"
51         },
52         'exports': {}
53     }
54 
55     data = Nullable(Dict(String, Any))
56 
57     event_config = Dict(String, Any)
58 
59     js_events = Dict(String, List(Any))
60 
61     renderer = Enum("canvas", "svg")
62 
63     theme = Enum("default", "light", "dark")
64
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/panel/models/echarts.py b/panel/models/echarts.py
--- a/panel/models/echarts.py
+++ b/panel/models/echarts.py
@@ -54,6 +54,8 @@
 
     data = Nullable(Dict(String, Any))
 
+    options = Nullable(Dict(String, Any))
+
     event_config = Dict(String, Any)
 
     js_events = Dict(String, List(Any))
diff --git a/panel/pane/echarts.py b/panel/pane/echarts.py
--- a/panel/pane/echarts.py
+++ b/panel/pane/echarts.py
@@ -38,6 +38,12 @@
     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""")

{"golden_diff": "diff --git a/panel/models/echarts.py b/panel/models/echarts.py\n--- a/panel/models/echarts.py\n+++ b/panel/models/echarts.py\n@@ -54,6 +54,8 @@\n \n     data = Nullable(Dict(String, Any))\n \n+    options = Nullable(Dict(String, Any))\n+\n     event_config = Dict(String, Any)\n \n     js_events = Dict(String, List(Any))\ndiff --git a/panel/pane/echarts.py b/panel/pane/echarts.py\n--- a/panel/pane/echarts.py\n+++ b/panel/pane/echarts.py\n@@ -38,6 +38,12 @@\n     object = param.Parameter(default=None, doc=\"\"\"\n         The Echarts object being wrapped. Can be an Echarts dictionary or a pyecharts chart\"\"\")\n \n+    options = param.Parameter(default=None, doc=\"\"\"\n+        An optional dict of options passed to Echarts.setOption. Allows to fine-tune the rendering behavior.\n+        For example, you might want to use `options={ \"replaceMerge\": ['series'] })` when updating\n+        the `objects` with a value containing a smaller number of series.\n+        \"\"\")\n+\n     renderer = param.ObjectSelector(default=\"canvas\", objects=[\"canvas\", \"svg\"], doc=\"\"\"\n        Whether to render as HTML canvas or SVG\"\"\")\n", "issue": "Echarts not updated when removing a serie\n#### Software version info\r\n- python 3.10.12\r\n- panel 1.2.1\r\n- bokeh 3.2.1\r\n- MacOS Ventura 13.5\r\n- Chrome Version 115.0.5790.170 arm64\r\n\r\n#### Description \r\nI have a param `ListSelector`, used to build a panel `CheckBoxGroup`, and an `echart`\r\nThe number of series of the echart varies according to the number of values selected in the `CheckBoxGroup`.\r\n\r\nWhen I click new values, a new serie is added, and the plot is properly updated.\r\nBut when I unclick an existing value, the plot isn't updated.\r\n\r\nMore details : \r\n- in both cases, the echart series are updated. see the reproduction code below.\r\n- having a look at the messages passing through the Websocket, I know that \"ModelChanged\" messages are received by the browser, *with updated series*. Check screenshots below.\r\n\r\n- It's really only *removing* a serie that is failing. Adding series always work. This is illustrated in the video below. For instance, running my reproduction code, run the following sequence : \r\n  - check 2 (it's updated)\r\n  - check 4 (it's updated)\r\n  - uncheck 4 (it's not updated)\r\n  - check8 (it's updated as expected)\r\n\r\n#### Complete, minimal, self-contained example code that reproduces the issue\r\n\r\n```python \r\nimport panel as pn\r\nimport param\r\n\r\nclass DemoEchartsBug(pn.viewable.Viewer):\r\n\r\n    numbers = param.ListSelector(default=[],\r\n                                objects=[2, 4, 8])\r\n\r\n    def __init__(self, **params):\r\n        super().__init__(**params)\r\n\r\n        self.cb_group = pn.widgets.CheckBoxGroup.from_param(self.param.numbers)\r\n    \r\n    @param.depends(\"numbers\")\r\n    def plot(self):\r\n\r\n        echart_index = list(range(10))\r\n        \r\n        echart_series = [\r\n            {\r\n                \"type\": \"bar\",\r\n                \"name\": str(num),\r\n                \"data\": [i * num for i in range(10)],\r\n            }\r\n            for num in self.numbers\r\n        ]\r\n\r\n        echart_config = {\r\n            \"xAxis\": [{\"type\": \"category\",  \"data\": echart_index}],\r\n            \"yAxis\": [{\"type\": \"value\"}],\r\n            \"series\": echart_series,\r\n        }\r\n\r\n        echart_pane = pn.pane.ECharts(echart_config,\r\n                                      width=800,\r\n                                      height=400)\r\n\r\n        return pn.Column(\r\n                pn.pane.Markdown(f\"# numbers : {self.numbers}\"),\r\n                echart_pane\r\n        )\r\n\r\n    def __panel__(self):\r\n\r\n        return pn.Column(\r\n            pn.pane.Markdown(\" # Bug with ECharts\"),\r\n            self.cb_group,\r\n            pn.layout.Divider(),\r\n            self.plot\r\n        )\r\n```\r\n\r\n#### Stack traceback and/or browser JavaScript console output\r\nNo console output\r\n\r\n#### Screen recording \r\n\r\nhttps://github.com/holoviz/panel/assets/756464/9b4cfdf5-1414-4b1f-ad7f-c9f88981e0bd\r\n\r\n\r\n\r\n#### Screenshots of the messages in the websocket\r\n\r\n- Step 1 : Before removing a serie. There are two series in the echart config\r\n![step 1 - before removing a serie](https://github.com/holoviz/panel/assets/756464/d2e45c03-0850-4211-9c13-21c2c1cde410)\r\n\r\n- Step 2 : After removing a serie. There is one serie in the echart config, as it should be. \r\n![step 2 - after removing a serie](https://github.com/holoviz/panel/assets/756464/a877930c-0898-4d75-9642-e77c88ea6054)\r\n\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport json\nimport sys\n\nfrom collections import defaultdict\nfrom typing import (\n    TYPE_CHECKING, Any, Callable, ClassVar, List, Mapping, Optional,\n)\n\nimport param\n\nfrom bokeh.models import CustomJS\nfrom pyviz_comms import JupyterComm\n\nfrom ..util import lazy_load\nfrom ..viewable import Viewable\nfrom .base import ModelPane\n\nif TYPE_CHECKING:\n    from bokeh.document import Document\n    from bokeh.model import Model\n    from pyviz_comms import Comm\n\n\nclass ECharts(ModelPane):\n    \"\"\"\n    ECharts panes allow rendering echarts.js dictionaries and pyecharts plots.\n\n    Reference: https://panel.holoviz.org/reference/panes/ECharts.html\n\n    :Example:\n\n    >>> pn.extension('echarts')\n    >>> ECharts(some_echart_dict_or_pyecharts_object, height=480, width=640)\n    \"\"\"\n\n    object = param.Parameter(default=None, doc=\"\"\"\n        The Echarts object being wrapped. Can be an Echarts dictionary or a pyecharts chart\"\"\")\n\n    renderer = param.ObjectSelector(default=\"canvas\", objects=[\"canvas\", \"svg\"], doc=\"\"\"\n       Whether to render as HTML canvas or SVG\"\"\")\n\n    theme = param.ObjectSelector(default=\"default\", objects=[\"default\", \"light\", \"dark\"], doc=\"\"\"\n       Theme to apply to plots.\"\"\")\n\n    priority: ClassVar[float | bool | None] = None\n\n    _rename: ClassVar[Mapping[str, str | None]] = {\"object\": \"data\"}\n\n    _rerender_params: ClassVar[List[str]] = []\n\n    _updates: ClassVar[bool] = True\n\n    def __init__(self, object=None, **params):\n        super().__init__(object, **params)\n        self._py_callbacks = defaultdict(lambda: defaultdict(list))\n        self._js_callbacks = defaultdict(list)\n\n    @classmethod\n    def applies(cls, obj: Any, **params) -> float | bool | None:\n        if isinstance(obj, dict):\n            return 0\n        elif cls.is_pyecharts(obj):\n            return 0.8\n        return None\n\n    @classmethod\n    def is_pyecharts(cls, obj):\n        if 'pyecharts' in sys.modules:\n            import pyecharts\n            return isinstance(obj, pyecharts.charts.chart.Chart)\n        return False\n\n    def _process_event(self, event):\n        callbacks = self._py_callbacks.get(event.type, {})\n        for cb in callbacks.get(None, []):\n            cb(event)\n        if event.query is None:\n            return\n        for cb in callbacks.get(event.query, []):\n            cb(event)\n\n    def _get_js_events(self, ref):\n        js_events = defaultdict(list)\n        for event, specs in self._js_callbacks.items():\n            for (query, code, args) in specs:\n                models = {\n                    name: viewable._models[ref][0] for name, viewable in args.items()\n                    if ref in viewable._models\n                }\n                js_events[event].append({'query': query, 'callback': CustomJS(code=code, args=models)})\n        return dict(js_events)\n\n    def _process_param_change(self, params):\n        props = super()._process_param_change(params)\n        if 'data' not in props:\n            return props\n        data = props['data'] or {}\n        if not isinstance(data, dict):\n            w, h = data.width, data.height\n            props['data'] = data = json.loads(data.dump_options())\n            if not self.height and h:\n                props['height'] = int(h.replace('px', ''))\n            if not self.width and w:\n                props['width'] = int(w.replace('px', ''))\n        else:\n            props['data'] = data\n        if data.get('responsive'):\n            props['sizing_mode'] = 'stretch_both'\n        return props\n\n    def _get_properties(self, document: Document):\n        props = super()._get_properties(document)\n        props['event_config'] = {\n            event: list(queries) for event, queries in self._py_callbacks.items()\n        }\n        return props\n\n    def _get_model(\n        self, doc: Document, root: Optional[Model] = None,\n        parent: Optional[Model] = None, comm: Optional[Comm] = None\n    ) -> Model:\n        self._bokeh_model = lazy_load(\n            'panel.models.echarts', 'ECharts', isinstance(comm, JupyterComm), root\n        )\n        model = super()._get_model(doc, root, parent, comm)\n        self._register_events('echarts_event', model=model, doc=doc, comm=comm)\n        return model\n\n    def on_event(self, event: str, callback: Callable, query: str | None = None):\n        \"\"\"\n        Register anevent handler which triggers when the specified event is triggered.\n\n        Reference: https://apache.github.io/echarts-handbook/en/concepts/event/\n\n        Arguments\n        ---------\n        event: str\n            The name of the event to register a handler on, e.g. 'click'.\n        callback: str | CustomJS\n            The event handler to be executed when the event fires.\n        query: str | None\n            A query that determines when the event fires.\n        \"\"\"\n        self._py_callbacks[event][query].append(callback)\n        event_config = {event: list(queries) for event, queries in self._py_callbacks.items()}\n        for ref, (model, _) in self._models.items():\n            self._apply_update({}, {'event_config': event_config}, model, ref)\n\n    def js_on_event(self, event: str, callback: str | CustomJS, query: str | None = None, **args):\n        \"\"\"\n        Register a Javascript event handler which triggers when the\n        specified event is triggered. The callback can be a snippet\n        of Javascript code or a bokeh CustomJS object making it possible\n        to manipulate other models in response to an event.\n\n        Reference: https://apache.github.io/echarts-handbook/en/concepts/event/\n\n        Arguments\n        ---------\n        event: str\n            The name of the event to register a handler on, e.g. 'click'.\n        code: str\n            The event handler to be executed when the event fires.\n        query: str | None\n            A query that determines when the event fires.\n        args: Viewable\n            A dictionary of Viewables to make available in the namespace\n            of the object.\n        \"\"\"\n        self._js_callbacks[event].append((query, callback, args))\n        for ref, (model, _) in self._models.items():\n            js_events = self._get_js_events(ref)\n            self._apply_update({}, {'js_events': js_events}, model, ref)\n\n\ndef setup_js_callbacks(root_view, root_model):\n    if 'panel.models.echarts' not in sys.modules:\n        return\n    ref = root_model.ref['id']\n    for pane in root_view.select(ECharts):\n        if ref in pane._models:\n            pane._models[ref][0].js_events = pane._get_js_events(ref)\n\nViewable._preprocessing_hooks.append(setup_js_callbacks)\n", "path": "panel/pane/echarts.py"}, {"content": "\"\"\"\nDefines custom bokeh model to render ECharts plots.\n\"\"\"\nfrom bokeh.core.properties import (\n    Any, Dict, Enum, List, Nullable, String,\n)\nfrom bokeh.events import ModelEvent\nfrom bokeh.models import LayoutDOM\n\nfrom ..config import config\nfrom ..io.resources import bundled_files\nfrom ..util import classproperty\n\n\nclass EChartsEvent(ModelEvent):\n\n    event_name = 'echarts_event'\n\n    def __init__(self, model, type=None, data=None, query=None):\n        self.type = type\n        self.data = data\n        self.query = query\n        super().__init__(model=model)\n\n\nclass ECharts(LayoutDOM):\n    \"\"\"\n    A Bokeh model that wraps around an ECharts plot and renders it\n    inside a Bokeh.\n    \"\"\"\n\n    __javascript_raw__ = [\n        f\"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min.js\",\n        f\"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min.js\"\n    ]\n\n    @classproperty\n    def __javascript__(cls):\n        return bundled_files(cls)\n\n    @classproperty\n    def __js_skip__(cls):\n        return {\n            'echarts': cls.__javascript__[:1]\n        }\n\n    __js_require__ = {\n        'paths': {\n            \"echarts\":  f\"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min\",\n            \"echarts-gl\": f\"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min\"\n        },\n        'exports': {}\n    }\n\n    data = Nullable(Dict(String, Any))\n\n    event_config = Dict(String, Any)\n\n    js_events = Dict(String, List(Any))\n\n    renderer = Enum(\"canvas\", \"svg\")\n\n    theme = Enum(\"default\", \"light\", \"dark\")\n", "path": "panel/models/echarts.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport json\nimport sys\n\nfrom collections import defaultdict\nfrom typing import (\n    TYPE_CHECKING, Any, Callable, ClassVar, List, Mapping, Optional,\n)\n\nimport param\n\nfrom bokeh.models import CustomJS\nfrom pyviz_comms import JupyterComm\n\nfrom ..util import lazy_load\nfrom ..viewable import Viewable\nfrom .base import ModelPane\n\nif TYPE_CHECKING:\n    from bokeh.document import Document\n    from bokeh.model import Model\n    from pyviz_comms import Comm\n\n\nclass ECharts(ModelPane):\n    \"\"\"\n    ECharts panes allow rendering echarts.js dictionaries and pyecharts plots.\n\n    Reference: https://panel.holoviz.org/reference/panes/ECharts.html\n\n    :Example:\n\n    >>> pn.extension('echarts')\n    >>> ECharts(some_echart_dict_or_pyecharts_object, height=480, width=640)\n    \"\"\"\n\n    object = param.Parameter(default=None, doc=\"\"\"\n        The Echarts object being wrapped. Can be an Echarts dictionary or a pyecharts chart\"\"\")\n\n    options = param.Parameter(default=None, doc=\"\"\"\n        An optional dict of options passed to Echarts.setOption. Allows to fine-tune the rendering behavior.\n        For example, you might want to use `options={ \"replaceMerge\": ['series'] })` when updating\n        the `objects` with a value containing a smaller number of series.\n        \"\"\")\n\n    renderer = param.ObjectSelector(default=\"canvas\", objects=[\"canvas\", \"svg\"], doc=\"\"\"\n       Whether to render as HTML canvas or SVG\"\"\")\n\n    theme = param.ObjectSelector(default=\"default\", objects=[\"default\", \"light\", \"dark\"], doc=\"\"\"\n       Theme to apply to plots.\"\"\")\n\n    priority: ClassVar[float | bool | None] = None\n\n    _rename: ClassVar[Mapping[str, str | None]] = {\"object\": \"data\"}\n\n    _rerender_params: ClassVar[List[str]] = []\n\n    _updates: ClassVar[bool] = True\n\n    def __init__(self, object=None, **params):\n        super().__init__(object, **params)\n        self._py_callbacks = defaultdict(lambda: defaultdict(list))\n        self._js_callbacks = defaultdict(list)\n\n    @classmethod\n    def applies(cls, obj: Any, **params) -> float | bool | None:\n        if isinstance(obj, dict):\n            return 0\n        elif cls.is_pyecharts(obj):\n            return 0.8\n        return None\n\n    @classmethod\n    def is_pyecharts(cls, obj):\n        if 'pyecharts' in sys.modules:\n            import pyecharts\n            return isinstance(obj, pyecharts.charts.chart.Chart)\n        return False\n\n    def _process_event(self, event):\n        callbacks = self._py_callbacks.get(event.type, {})\n        for cb in callbacks.get(None, []):\n            cb(event)\n        if event.query is None:\n            return\n        for cb in callbacks.get(event.query, []):\n            cb(event)\n\n    def _get_js_events(self, ref):\n        js_events = defaultdict(list)\n        for event, specs in self._js_callbacks.items():\n            for (query, code, args) in specs:\n                models = {\n                    name: viewable._models[ref][0] for name, viewable in args.items()\n                    if ref in viewable._models\n                }\n                js_events[event].append({'query': query, 'callback': CustomJS(code=code, args=models)})\n        return dict(js_events)\n\n    def _process_param_change(self, params):\n        props = super()._process_param_change(params)\n        if 'data' not in props:\n            return props\n        data = props['data'] or {}\n        if not isinstance(data, dict):\n            w, h = data.width, data.height\n            props['data'] = data = json.loads(data.dump_options())\n            if not self.height and h:\n                props['height'] = int(h.replace('px', ''))\n            if not self.width and w:\n                props['width'] = int(w.replace('px', ''))\n        else:\n            props['data'] = data\n        if data.get('responsive'):\n            props['sizing_mode'] = 'stretch_both'\n        return props\n\n    def _get_properties(self, document: Document):\n        props = super()._get_properties(document)\n        props['event_config'] = {\n            event: list(queries) for event, queries in self._py_callbacks.items()\n        }\n        return props\n\n    def _get_model(\n        self, doc: Document, root: Optional[Model] = None,\n        parent: Optional[Model] = None, comm: Optional[Comm] = None\n    ) -> Model:\n        self._bokeh_model = lazy_load(\n            'panel.models.echarts', 'ECharts', isinstance(comm, JupyterComm), root\n        )\n        model = super()._get_model(doc, root, parent, comm)\n        self._register_events('echarts_event', model=model, doc=doc, comm=comm)\n        return model\n\n    def on_event(self, event: str, callback: Callable, query: str | None = None):\n        \"\"\"\n        Register anevent handler which triggers when the specified event is triggered.\n\n        Reference: https://apache.github.io/echarts-handbook/en/concepts/event/\n\n        Arguments\n        ---------\n        event: str\n            The name of the event to register a handler on, e.g. 'click'.\n        callback: str | CustomJS\n            The event handler to be executed when the event fires.\n        query: str | None\n            A query that determines when the event fires.\n        \"\"\"\n        self._py_callbacks[event][query].append(callback)\n        event_config = {event: list(queries) for event, queries in self._py_callbacks.items()}\n        for ref, (model, _) in self._models.items():\n            self._apply_update({}, {'event_config': event_config}, model, ref)\n\n    def js_on_event(self, event: str, callback: str | CustomJS, query: str | None = None, **args):\n        \"\"\"\n        Register a Javascript event handler which triggers when the\n        specified event is triggered. The callback can be a snippet\n        of Javascript code or a bokeh CustomJS object making it possible\n        to manipulate other models in response to an event.\n\n        Reference: https://apache.github.io/echarts-handbook/en/concepts/event/\n\n        Arguments\n        ---------\n        event: str\n            The name of the event to register a handler on, e.g. 'click'.\n        code: str\n            The event handler to be executed when the event fires.\n        query: str | None\n            A query that determines when the event fires.\n        args: Viewable\n            A dictionary of Viewables to make available in the namespace\n            of the object.\n        \"\"\"\n        self._js_callbacks[event].append((query, callback, args))\n        for ref, (model, _) in self._models.items():\n            js_events = self._get_js_events(ref)\n            self._apply_update({}, {'js_events': js_events}, model, ref)\n\n\ndef setup_js_callbacks(root_view, root_model):\n    if 'panel.models.echarts' not in sys.modules:\n        return\n    ref = root_model.ref['id']\n    for pane in root_view.select(ECharts):\n        if ref in pane._models:\n            pane._models[ref][0].js_events = pane._get_js_events(ref)\n\nViewable._preprocessing_hooks.append(setup_js_callbacks)\n", "path": "panel/pane/echarts.py"}, {"content": "\"\"\"\nDefines custom bokeh model to render ECharts plots.\n\"\"\"\nfrom bokeh.core.properties import (\n    Any, Dict, Enum, List, Nullable, String,\n)\nfrom bokeh.events import ModelEvent\nfrom bokeh.models import LayoutDOM\n\nfrom ..config import config\nfrom ..io.resources import bundled_files\nfrom ..util import classproperty\n\n\nclass EChartsEvent(ModelEvent):\n\n    event_name = 'echarts_event'\n\n    def __init__(self, model, type=None, data=None, query=None):\n        self.type = type\n        self.data = data\n        self.query = query\n        super().__init__(model=model)\n\n\nclass ECharts(LayoutDOM):\n    \"\"\"\n    A Bokeh model that wraps around an ECharts plot and renders it\n    inside a Bokeh.\n    \"\"\"\n\n    __javascript_raw__ = [\n        f\"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min.js\",\n        f\"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min.js\"\n    ]\n\n    @classproperty\n    def __javascript__(cls):\n        return bundled_files(cls)\n\n    @classproperty\n    def __js_skip__(cls):\n        return {\n            'echarts': cls.__javascript__[:1]\n        }\n\n    __js_require__ = {\n        'paths': {\n            \"echarts\":  f\"{config.npm_cdn}/echarts@5.4.1/dist/echarts.min\",\n            \"echarts-gl\": f\"{config.npm_cdn}/echarts-gl@2.0.9/dist/echarts-gl.min\"\n        },\n        'exports': {}\n    }\n\n    data = Nullable(Dict(String, Any))\n\n    options = Nullable(Dict(String, Any))\n\n    event_config = Dict(String, Any)\n\n    js_events = Dict(String, List(Any))\n\n    renderer = Enum(\"canvas\", \"svg\")\n\n    theme = Enum(\"default\", \"light\", \"dark\")\n", "path": "panel/models/echarts.py"}]}