crossfiltering_holoviews / crossfiltering_holoviews_plotly.js
MarcSkovMadsen's picture
Upload 8 files
1ff22c2
raw
history blame contribute delete
No virus
6.23 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', 'pandas', 'plotly']
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()
"""*Linked Brushing* is a very powerful technique. It's also often called
*Linked Selections* or *Crossfiltering*.
This example is inspired by the HoloViews [Linked Brushing Reference Guide]\
(http://holoviews.org/user_guide/Linked_Brushing.html) and the Plotly blog post
[Introducing Dash HoloViews]\
(https://medium.com/plotly/introducing-dash-holoviews-6a05c088ebe5).
This example uses the *Iris* dataset.
"""
from typing import Tuple
import holoviews as hv
import panel as pn
from holoviews import opts
from panel.template import FastListTemplate
import plotly.io as pio
import pandas as pd
@pn.cache
def get_iris_data():
return pd.read_csv("https://cdn.awesome-panel.org/resources/crossfiltering_holoviews/iris.csv.gz")
ACCENT = "#F08080"
CSS = """
.main .card-margin.stretch_both {
height: calc(100vh - 125px) !important;
}
"""
def _plotly_hooks(plot, element):
"""Used by HoloViews to give plots plotly plots special treatment"""
fig = plot.state
fig["layout"]["dragmode"] = "select"
fig["config"]["displayModeBar"] = True
if isinstance(element, hv.Histogram):
# Constrain histogram selection direction to horizontal
fig["layout"]["selectdirection"] = "h"
def get_linked_plots() -> Tuple:
"""Returns a tuple (scatter, hist) of linked plots
See http://holoviews.org/user_guide/Linked_Brushing.html
"""
dataset = hv.Dataset(get_iris_data())
scatter = hv.Scatter(dataset, kdims=["sepal_length"], vdims=["sepal_width"])
hist = hv.operation.histogram(dataset, dimension="petal_width", normed=False)
# pylint: disable=no-value-for-parameter
selection_linker = hv.selection.link_selections.instance()
# pylint: disable=no-member
scatter = selection_linker(scatter).opts(
opts.Scatter(color=ACCENT, size=10, hooks=[_plotly_hooks], width=700, height=400),
)
hist = selection_linker(hist).opts(
opts.Histogram(color=ACCENT, hooks=[_plotly_hooks], width=700, height=400)
)
return scatter, hist
def create_app():
"""Returns the app in a nice FastListTemplate"""
if pn.config.theme == "dark":
pio.templates.default = "plotly_dark"
else:
pio.templates.default = "plotly_white"
scatter, hist = get_linked_plots()
scatter_panel = pn.pane.HoloViews(scatter, sizing_mode="stretch_both", backend="plotly")
hist_panel = pn.pane.HoloViews(hist, sizing_mode="stretch_both", backend="plotly")
def reset(event):
scatter, hist = get_linked_plots()
scatter_panel.object=scatter
hist_panel.object=hist
reset_button = pn.widgets.Button(name="RESET PLOTS", on_click=reset, description="Resets the plots. Plotly does not have a built in way to do this.")
template = FastListTemplate(
site="Awesome Panel",
site_url="https://awesome-panel.org",
title="Crossfiltering with HoloViews and Plotly",
accent=ACCENT,
main=[
# We need to wrap in Columns to get them to stretch properly
pn.Column(reset_button, scatter_panel, pn.layout.Spacer(height=20), hist_panel, height=870, sizing_mode="stretch_width"),
],
main_max_width="850px",
)
return template
pn.extension("plotly", raw_css=[CSS])
hv.extension("plotly")
create_app().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()