| importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"); | |
| function sendPatch(patch, buffers, msg_id) { | |
| self.postMessage({ | |
| type: 'patch', | |
| patch: patch, | |
| buffers: buffers | |
| }) | |
| } | |
| async function startApplication() { | |
| console.log("Loading pyodide!"); | |
| self.postMessage({type: 'status', msg: 'Loading pyodide'}) | |
| self.pyodide = await loadPyodide(); | |
| self.pyodide.globals.set("sendPatch", sendPatch); | |
| console.log("Loaded!"); | |
| await self.pyodide.loadPackage("micropip"); | |
| const env_spec = ['https://cdn.holoviz.org/panel/wheels/bokeh-3.3.2-py3-none-any.whl', 'https://cdn.holoviz.org/panel/1.3.6/dist/wheels/panel-1.3.6-py3-none-any.whl', 'pyodide-http==0.2.1', 'hvplot', 'pandas'] | |
| for (const pkg of env_spec) { | |
| let pkg_name; | |
| if (pkg.endsWith('.whl')) { | |
| pkg_name = pkg.split('/').slice(-1)[0].split('-')[0] | |
| } else { | |
| pkg_name = pkg | |
| } | |
| self.postMessage({type: 'status', msg: `Installing ${pkg_name}`}) | |
| try { | |
| await self.pyodide.runPythonAsync(` | |
| import micropip | |
| await micropip.install('${pkg}'); | |
| `); | |
| } catch(e) { | |
| console.log(e) | |
| self.postMessage({ | |
| type: 'status', | |
| msg: `Error while installing ${pkg_name}` | |
| }); | |
| } | |
| } | |
| console.log("Packages loaded!"); | |
| self.postMessage({type: 'status', msg: 'Executing code'}) | |
| const code = ` | |
| import asyncio | |
| from panel.io.pyodide import init_doc, write_doc | |
| init_doc() | |
| """The purpose of this app is to test that a **multi-page Dashboard Layout** similar to the | |
| [bootstrap dashboard template](https://getbootstrap.com/docs/4.3/examples/dashboard/) | |
| from [getboostrap.com](https://getbootstrap.com/) can be implemented in | |
| [Panel](https://panel.pyviz.org/). | |
| """ | |
| import hvplot.pandas # pylint: disable=unused-import | |
| import pandas as pd | |
| import panel as pn | |
| BOOTSTRAP_DASHBOARD_CHART_URL="https://awesomepanel.blob.core.windows.net/resources/bootstrap_dashboard/bootstrap_dashboard_chart.csv" | |
| BOOTSTRAP_DASHBOARD_TABLE_URL="https://awesomepanel.blob.core.windows.net/resources/bootstrap_dashboard/bootstrap_dashboard_table.csv" | |
| COLOR="#0072B5" | |
| @pn.cache | |
| def _get_chart_data(): | |
| return pd.read_csv(BOOTSTRAP_DASHBOARD_CHART_URL) | |
| @pn.cache | |
| def _get_table_data(): | |
| return pd.read_csv(BOOTSTRAP_DASHBOARD_TABLE_URL) | |
| def _holoviews_chart(): | |
| """## Dashboard Orders Chart generated by HoloViews""" | |
| data = _get_chart_data() | |
| line_plot = data.hvplot.line( | |
| x="Day", | |
| y="Orders", | |
| height=500, | |
| line_color=COLOR, | |
| line_width=6, | |
| ) | |
| scatter_plot = data.hvplot.scatter(x="Day", y="Orders", height=300,).opts( | |
| marker="o", | |
| size=10, | |
| color=COLOR, | |
| ) | |
| fig = line_plot * scatter_plot | |
| gridstyle = { | |
| "grid_line_color": "black", | |
| "grid_line_width": 0.1, | |
| } | |
| fig = fig.opts( | |
| responsive=True, | |
| toolbar=None, | |
| yticks=list( | |
| range( | |
| 12000, | |
| 26000, | |
| 2000, | |
| ) | |
| ), | |
| ylim=( | |
| 12000, | |
| 26000, | |
| ), | |
| gridstyle=gridstyle, | |
| show_grid=True, | |
| ) | |
| return fig | |
| app = pn.extension("tabulator", sizing_mode="stretch_width") | |
| pn.template.FastListTemplate( | |
| site="Awesome Panel", site_url="https://awesome-panel.org", title="Bootstrap Dashboard", | |
| main=[ | |
| pn.Column( | |
| pn.pane.Markdown("## Dashboard"), | |
| _holoviews_chart()), | |
| pn.Column(pn.pane.Markdown("## Section Title"), | |
| pn.widgets.Tabulator(_get_table_data(), layout='fit_data_stretch')), | |
| ], main_max_width="800px", main_layout=None, | |
| ).servable() | |
| await write_doc() | |
| ` | |
| try { | |
| const [docs_json, render_items, root_ids] = await self.pyodide.runPythonAsync(code) | |
| self.postMessage({ | |
| type: 'render', | |
| docs_json: docs_json, | |
| render_items: render_items, | |
| root_ids: root_ids | |
| }) | |
| } catch(e) { | |
| const traceback = `${e}` | |
| const tblines = traceback.split('\n') | |
| self.postMessage({ | |
| type: 'status', | |
| msg: tblines[tblines.length-2] | |
| }); | |
| throw e | |
| } | |
| } | |
| self.onmessage = async (event) => { | |
| const msg = event.data | |
| if (msg.type === 'rendered') { | |
| self.pyodide.runPythonAsync(` | |
| from panel.io.state import state | |
| from panel.io.pyodide import _link_docs_worker | |
| _link_docs_worker(state.curdoc, sendPatch, setter='js') | |
| `) | |
| } else if (msg.type === 'patch') { | |
| self.pyodide.globals.set('patch', msg.patch) | |
| self.pyodide.runPythonAsync(` | |
| state.curdoc.apply_json_patch(patch.to_py(), setter='js') | |
| `) | |
| self.postMessage({type: 'idle'}) | |
| } else if (msg.type === 'location') { | |
| self.pyodide.globals.set('location', msg.location) | |
| self.pyodide.runPythonAsync(` | |
| import json | |
| from panel.io.state import state | |
| from panel.util import edit_readonly | |
| if state.location: | |
| loc_data = json.loads(location) | |
| with edit_readonly(state.location): | |
| state.location.param.update({ | |
| k: v for k, v in loc_data.items() if k in state.location.param | |
| }) | |
| `) | |
| } | |
| } | |
| startApplication() |