MarcSkovMadsen's picture
Upload 3 files
7f0359e verified
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()