MarcSkovMadsen's picture
Upload 2 files
19c6661
raw
history blame
7.08 kB
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*.
#
# ![Data App](https://github.com/sophiamyang/hvplot_interactive/blob/main/assets/easy-dataframe-dashboards.gif?raw=true)
# ## 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()