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', 'numpy'] 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() """ Source: https://awesome-panel.org/resources/streaming_number_indicators/ """ from asyncio import create_task, get_event_loop, sleep import numpy as np import panel as pn ACCENT = "#00A170" OK_COLOR = ACCENT ERROR_COLOR = "#a10031" ALERT = 80 COLORS = [(ALERT, OK_COLOR), (100, ERROR_COLOR)] INITIAL_VALUE = ALERT - 3 N = 18 # Number of indicators # Can be removed when https://github.com/holoviz/panel/pull/6194 is released CSS_FIX = """ :host(.pn-loading) .pn-loading-msg, .pn-loading .pn-loading-msg { color: var(--panel-on-background-color, black) !important; } """ if not CSS_FIX in pn.config.raw_css: pn.config.raw_css.append(CSS_FIX) async def update_values(values): """Some random updating of values.""" while True: # Replace with your own code. new_value = np.copy(values.rx.value) new_value += np.random.randint(5, size=N) - 2 new_value[new_value < 0] = 0 new_value[new_value > 99] = 99 values.rx.value = new_value await sleep(1) @pn.cache # We use caching to share values across all sessions in a server context def get_values(): # We use Reactive Expressions https://param.holoviz.org/user_guide/Reactive_Expressions.html return pn.rx([INITIAL_VALUE] * N) @pn.cache # We use caching to only update values once across all sessions in a server context def create_update_values_task(): values = get_values() create_task(update_values(values)) def get_styles(value): if value <= ALERT: return {"border": f"1px solid {OK_COLOR}", "padding": "1em", "border-radius": "3px"} return {"border": f"1px solid {ERROR_COLOR}", "padding": "1em", "border-radius": "3px"} def create_indicator(index, values): title = f"Sensor {index}" value = values[index] return pn.indicators.Number( name=title, value=value, format="{value}%", colors=COLORS, margin=10, styles=pn.rx(get_styles)(value), width=165, ) def create_component(): values = get_values() indicators = tuple(create_indicator(i, values) for i in range(len(values.rx.value))) layout = pn.FlexBox(*indicators) return layout if pn.state.served or pn.state._is_pyodide: pn.extension() if get_event_loop().is_running(): # We can only start the stream if the event loop is running create_update_values_task() pn.template.FastListTemplate( site="Awesome Panel", site_url="https://awesome-panel.org", title="Streaming Number Indicators", accent=ACCENT, theme="dark", theme_toggle=False, main=[create_component()], 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()