|
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() |