|
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', 'holoviews', 'hvplot', 'param', '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() |
|
|
|
#!/usr/bin/env python |
|
# coding: utf-8 |
|
|
|
# # The Easiest Way to Create an Interactive Dashboard in Python |
|
# |
|
# This notebook is an updated version of the original notebook supporting the blog post |
|
# |
|
# **[The Easiest Way to Create an Interactive Dashboard in Python](https://towardsdatascience.com/the-easiest-way-to-create-an-interactive-dashboard-in-python-77440f2511d1)**. Turn Pandas pipelines into a |
|
# dashboard using [\`param.rx\`](https://param.holoviz.org/user_guide/Reactive_Expressions.html) and [panel.ReactiveExpr](https://panel.holoviz.org/reference/panes/ReactiveExpr.html). |
|
# |
|
# by *Sophia Yang* and *Marc Skov Madsen*. |
|
# |
|
#  |
|
|
|
# ## Import and configure packages |
|
# |
|
# Please note that in **Colab** you will need to \`!pip install panel hvplot\`. In VS Code notebooks you will need to install \`!pip install panel hvplot jupyter_bokeh\`. |
|
|
|
# In[ ]: |
|
|
|
|
|
# !pip install panel==1.3.6 hvplot==0.9.1 # colab |
|
# !pip install panel==1.3.6 hvplot==0.9.1 jupyter_bokeh==3.0.7 # vscode |
|
|
|
|
|
# In[ ]: |
|
|
|
|
|
import param |
|
import panel as pn |
|
|
|
pn.extension('tabulator', sizing_mode="stretch_width") |
|
|
|
|
|
# In[ ]: |
|
|
|
|
|
import hvplot.pandas |
|
import holoviews as hv |
|
|
|
hv.extension('bokeh') |
|
|
|
|
|
# ## Define Color Palette |
|
|
|
# In[ ]: |
|
|
|
|
|
PALETTE = ["#ff6f69", "#ffcc5c", "#88d8b0", ] |
|
pn.Row( |
|
pn.layout.HSpacer(height=50, styles={"background": PALETTE[0]}), |
|
pn.layout.HSpacer(height=50, styles={"background": PALETTE[1]}), |
|
pn.layout.HSpacer(height=50, styles={"background": PALETTE[2]}), |
|
) |
|
|
|
|
|
# ## Load Data |
|
|
|
# In[ ]: |
|
|
|
|
|
from bokeh.sampledata.autompg import autompg_clean as df |
|
df.head(3) |
|
|
|
|
|
# ## Define DataFrame Pipeline |
|
|
|
# In[ ]: |
|
|
|
|
|
def get_data(df, cylinders=4, mfr=['ford','chevrolet'], yaxis="hp"): |
|
return ( |
|
df[ |
|
(df.cyl == cylinders) & |
|
(df.mfr.isin(mfr)) |
|
] |
|
.groupby(['origin', 'mpg'])[yaxis].mean() |
|
.to_frame() |
|
.reset_index() |
|
.sort_values(by='mpg') |
|
) |
|
|
|
get_data(df).head(3) |
|
|
|
|
|
# ## Make DataFrame Pipeline Interactive |
|
|
|
# Define [Panel widgets](https://panel.holoviz.org/reference/index.html#widgets) |
|
|
|
# In[ ]: |
|
|
|
|
|
cylinders = pn.widgets.IntSlider(name='Cylinders', start=4, end=8, step=2) |
|
mfr = pn.widgets.ToggleGroup( |
|
name='MFR', |
|
options=['ford', 'chevrolet', 'honda', 'toyota', 'audi'], |
|
value=['ford', 'chevrolet', 'honda', 'toyota', 'audi'], |
|
button_type='primary', button_style="outline") |
|
yaxis = pn.widgets.RadioButtonGroup( |
|
name='Y axis', |
|
options=['hp', 'weight'], |
|
button_type='primary', button_style="outline" |
|
) |
|
|
|
|
|
# Combine pipeline and widgets |
|
|
|
# In[ ]: |
|
|
|
|
|
ipipeline=param.rx(get_data)(df, cylinders, mfr, yaxis) |
|
|
|
ipipeline.head() |
|
|
|
|
|
# ## Pipe to Tabulator |
|
|
|
# In[ ]: |
|
|
|
|
|
itable = pn.widgets.Tabulator(ipipeline, pagination='remote', page_size=10) |
|
itable |
|
|
|
|
|
# Check out the [Tabulator Reference Guide](https://panel.holoviz.org/reference/widgets/Tabulator.html) for more inspiration. |
|
|
|
# ## Pipe to hvPlot and HoloViews |
|
|
|
# First we will create the interactive plot with [hvPlot](https://hvplot.holoviz.org/). |
|
|
|
# In[ ]: |
|
|
|
|
|
ihvplot = ipipeline.hvplot(x='mpg', y=yaxis, by='origin', color=PALETTE, line_width=6, height=400, responsive=True) |
|
ihvplot |
|
|
|
|
|
# The we will put it in a HoloViews pane |
|
|
|
# In[ ]: |
|
|
|
|
|
iplot = pn.pane.HoloViews(ihvplot) |
|
iplot |
|
|
|
|
|
# Check out the [HoloViews Reference Guide](https://panel.holoviz.org/reference/panes/HoloViews.html) for more inspiration |
|
|
|
# ## Layout using Template |
|
# |
|
# Here we use the [FastListTemplate](https://panel.holoviz.org/reference/templates/FastListTemplate.html#templates-gallery-fastlisttemplate). |
|
|
|
# In[ ]: |
|
|
|
|
|
template = pn.template.FastListTemplate( |
|
title='The easiest way to create a dashboard', |
|
sidebar=[cylinders, 'Manufacturers', mfr, 'Y axis' , yaxis], |
|
main=[iplot, itable], |
|
accent_base_color="#88d8b0", |
|
header_background="#88d8b0", |
|
# main_layout=None, # Use this if you want a gray sidebar and white main area |
|
) |
|
template.servable(); # Add semicolon because templates don't render well in a notebook |
|
|
|
|
|
# To *serve the notebook* run \`panel serve the_easiest_way_to_create_dashboard.ipynb\`. |
|
|
|
|
|
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() |