Skip to content

Commit eaa5285

Browse files
authored
Add IDOM pane (#2004)
1 parent 54b7654 commit eaa5285

18 files changed

+934
-8
lines changed

.github/workflows/docs.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
doit develop_install ${{ env.CHANS_DEV}} -o doc -o examples
5353
pip install pydeck sphinxcontrib-napoleon pyecharts
5454
conda install -c conda-forge "nodejs=15.3.0"
55+
pip install idom
5556
- name: opengl
5657
run: |
5758
sudo apt-get install libglu1-mesa

.github/workflows/test.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
conda activate test-environment
6868
conda list
6969
doit develop_install ${{ env.CHANS_DEV}} -o examples -o recommended -o tests -o build
70-
pip install pyecharts
70+
pip install pyecharts idom
7171
bokeh sampledata
7272
echo "-----"
7373
git describe

examples/reference/panes/IDOM.ipynb

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"import idom\n",
10+
"\n",
11+
"import panel as pn\n",
12+
"\n",
13+
"pn.extension()"
14+
]
15+
},
16+
{
17+
"cell_type": "markdown",
18+
"metadata": {},
19+
"source": [
20+
"The ``IDOM`` pane renders any [IDOM component](http://idom-docs.herokuapp.com/) both in the notebook and in a deployed server. IDOM defines an API for defining and controlling interactive HTML components directly from Python. Note that in the notebook the IDOM support for loading external modules relies on Panel's Jupyter serverextension. To check if this is enabled you can run:\n",
21+
"\n",
22+
" jupyter serverextension list\n",
23+
" \n",
24+
"You should see:\n",
25+
"\n",
26+
" panel.io.jupyter_server_extension enabled\n",
27+
" - Validating...\n",
28+
" panel.io.jupyter_server_extension OK\n",
29+
"\n",
30+
"If you don't see this but have installed panel you can manually enable the server extension with:\n",
31+
"\n",
32+
" jupyter serverextension enable --sys-prefix panel.io.jupyter_server_extension\n",
33+
" \n",
34+
"which will install it where your Python is installed or to place it in the Jupyter config in your home directory:\n",
35+
"\n",
36+
" jupyter serverextension enable --py panel\n",
37+
"\n",
38+
"#### Parameters:\n",
39+
"\n",
40+
"For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n",
41+
"\n",
42+
"* **``object``** (object): The IDOM component being displayed\n",
43+
"\n",
44+
"##### Display\n",
45+
"\n",
46+
"* **``default_layout``** (pn.layout.Panel, default=Row): Layout to wrap the plot and widgets in\n",
47+
"\n",
48+
"___"
49+
]
50+
},
51+
{
52+
"cell_type": "markdown",
53+
"metadata": {},
54+
"source": [
55+
"The `panel` function will automatically convert any ``idom.component`` into a displayable panel, while keeping all of its interactive features:"
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"metadata": {},
62+
"outputs": [],
63+
"source": [
64+
"@idom.component\n",
65+
"def ClickCount():\n",
66+
" count, set_count = idom.hooks.use_state(0)\n",
67+
"\n",
68+
" return idom.html.button(\n",
69+
" {\"onClick\": lambda event: set_count(count + 1)},\n",
70+
" [f\"Click count: {count}\"],\n",
71+
" )\n",
72+
"\n",
73+
"pn.panel(ClickCount, width=300)"
74+
]
75+
},
76+
{
77+
"cell_type": "markdown",
78+
"metadata": {},
79+
"source": [
80+
"This makes it possible to generate even complex interactive components directly from Python, e.g. here we will create a ToDo list:"
81+
]
82+
},
83+
{
84+
"cell_type": "code",
85+
"execution_count": null,
86+
"metadata": {},
87+
"outputs": [],
88+
"source": [
89+
"@idom.component\n",
90+
"def Todo():\n",
91+
" items, set_items = idom.hooks.use_state([])\n",
92+
"\n",
93+
" async def add_new_task(event):\n",
94+
" if event[\"key\"] == \"Enter\":\n",
95+
" set_items(items + [event[\"value\"]])\n",
96+
"\n",
97+
" tasks = []\n",
98+
"\n",
99+
" for index, text in enumerate(items):\n",
100+
"\n",
101+
" async def remove_task(event, index=index):\n",
102+
" set_items(items[:index] + items[index + 1 :])\n",
103+
"\n",
104+
" task_text = idom.html.td(idom.html.p(text))\n",
105+
" delete_button = idom.html.td({\"onClick\": remove_task}, idom.html.button([\"x\"]))\n",
106+
" tasks.append(idom.html.tr(task_text, delete_button))\n",
107+
"\n",
108+
" task_input = idom.html.input({\"onKeyDown\": add_new_task})\n",
109+
" task_table = idom.html.table(tasks)\n",
110+
"\n",
111+
" return idom.html.div(task_input, task_table)\n",
112+
"\n",
113+
"pn.pane.IDOM(Todo)"
114+
]
115+
},
116+
{
117+
"cell_type": "markdown",
118+
"metadata": {},
119+
"source": [
120+
"If you have a live server backing your session, whether that is a notebook server or a Bokeh/Panel server deployment you can also use external Javascript components which will be compiled before first use. See the [idom documentation](https://idom-docs.herokuapp.com/docs/javascript-components.html) for more details on using external components. Note that to ensure that the JS modules are installed in the correct place you should use `pn.pane.IDOM.install` rather than simply using `idom.install`:"
121+
]
122+
},
123+
{
124+
"cell_type": "code",
125+
"execution_count": null,
126+
"metadata": {},
127+
"outputs": [],
128+
"source": [
129+
"victory = pn.pane.IDOM.install(\"victory\", fallback=\"loading...\")\n",
130+
"\n",
131+
"victory_com = idom.component(\n",
132+
" lambda: victory.VictoryBar({\"style\": {\"parent\": {\"width\": \"500px\"}}}),\n",
133+
")\n",
134+
"\n",
135+
"pn.pane.IDOM(victory_com)"
136+
]
137+
},
138+
{
139+
"cell_type": "markdown",
140+
"metadata": {},
141+
"source": [
142+
"In order to work with Panel components seamlessly the `IDOM` pane also provides a `use_param` method which allows us to use the current parameter value much like we would when using `pn.depends`:"
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": null,
148+
"metadata": {},
149+
"outputs": [],
150+
"source": [
151+
"aw = pn.widgets.IntSlider(name='a', start=0, end=20, value=1)\n",
152+
"bw = pn.widgets.IntSlider(name='b', start=0, end=20, value=1)\n",
153+
"\n",
154+
"@idom.component\n",
155+
"def view():\n",
156+
" a = pn.pane.IDOM.use_param(aw)\n",
157+
" b = pn.pane.IDOM.use_param(bw.param.value) # equivalent to passing in the widget\n",
158+
" return idom.html.div({}, f'{a}+{b}={a+b}')\n",
159+
"\n",
160+
"pn.Row(aw, bw, view)"
161+
]
162+
}
163+
],
164+
"metadata": {
165+
"kernelspec": {
166+
"display_name": "Python 3",
167+
"language": "python",
168+
"name": "python3"
169+
},
170+
"language_info": {
171+
"codemirror_mode": {
172+
"name": "ipython",
173+
"version": 3
174+
},
175+
"file_extension": ".py",
176+
"mimetype": "text/x-python",
177+
"name": "python",
178+
"nbconvert_exporter": "python",
179+
"pygments_lexer": "ipython3",
180+
"version": "3.8.2"
181+
},
182+
"widgets": {
183+
"application/vnd.jupyter.widget-state+json": {
184+
"state": {},
185+
"version_major": 2,
186+
"version_minor": 0
187+
}
188+
}
189+
},
190+
"nbformat": 4,
191+
"nbformat_minor": 4
192+
}

panel/dist/idom/package.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "idom-panel-app",
3+
"description": "A client application for IDOM implemented in Preact",
4+
"version": "0.1.0",
5+
"author": "Philipp Rudiger",
6+
"main": "index.js",
7+
"license": "BSD-3",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/holoviz/panel"
11+
},
12+
"scripts": {
13+
"build": "snowpack && snowpack build",
14+
"format": "prettier --write ./src",
15+
"test": "echo \"Error: no test specified\" && exit 1"
16+
},
17+
"devDependencies": {
18+
"prettier": "^2.2.1",
19+
"snowpack": "2.12.1"
20+
},
21+
"dependencies": {
22+
"preact": "^10.5.12",
23+
"htm": "^3.0.4"
24+
},
25+
"snowpack": {
26+
"install": [
27+
"htm",
28+
"preact"
29+
],
30+
"alias": {
31+
"react": "preact/compat",
32+
"react-dom": "preact/compat"
33+
},
34+
"installOptions": {
35+
"polyfillNode": true
36+
}
37+
}
38+
}

panel/io/server.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ class DocHandler(BkDocHandler):
161161
@authenticated
162162
async def get(self, *args, **kwargs):
163163
session = await self.get_session()
164+
r = self.request
165+
prefix = '/'.join(r.uri.split('/')[:-1])
166+
state.root_url = f"{r.protocol}://{r.host}{prefix}"
164167
resources = Resources.from_bokeh(self.application.resources())
165168
page = server_html_page_for_session(
166169
session, resources=resources, title=session.document.title,
@@ -593,7 +596,8 @@ def get_server(panel, port=0, address=None, websocket_origin=None,
593596
server = Server(apps, port=port, **opts)
594597
if verbose:
595598
address = server.address or 'localhost'
596-
print("Launching server at http://%s:%s" % (address, server.port))
599+
url = f"http://{address}:{server.port}{server.prefix}"
600+
print(f"Launching server at {url}")
597601

598602
state._servers[server_id] = (server, panel, [])
599603

panel/io/state.py

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class _state(param.Parameterized):
4040
Object with encrypt and decrypt methods to support encryption
4141
of secret variables including OAuth information.""")
4242

43+
root_url = param.String(default=None, doc="""
44+
The root URL of the running server.""")
45+
4346
session_info = param.Dict(default={'total': 0, 'live': 0,
4447
'sessions': OrderedDict()}, doc="""
4548
Tracks information and statistics about user sessions.""")

panel/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
files.
66
"""
77

8+
from .idom import IDOM # noqa
89
from .ipywidget import IPyWidget # noqa
910
from .layout import Card # noqa
1011
from .location import Location # noqa

0 commit comments

Comments
 (0)