MarcSkovMadsen commited on
Commit
d1729bb
1 Parent(s): 845cea2

Update crossfiltering_holoviews_bokeh.js

Browse files
Files changed (1) hide show
  1. crossfiltering_holoviews_bokeh.js +205 -1
crossfiltering_holoviews_bokeh.js CHANGED
@@ -68,7 +68,211 @@ from panel.template import FastListTemplate
68
 
69
  @pn.cache
70
  def get_iris_data():
71
- return pd.read_csv("https://cdn.awesome-panel.org/resources/crossfiltering_holoviews/iris.csv.gz")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
 
74
  ACCENT = "#F08080"
 
68
 
69
  @pn.cache
70
  def get_iris_data():
71
+ return pd.read_csv("importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js");
72
+
73
+ function sendPatch(patch, buffers, msg_id) {
74
+ self.postMessage({
75
+ type: 'patch',
76
+ patch: patch,
77
+ buffers: buffers
78
+ })
79
+ }
80
+
81
+ async function startApplication() {
82
+ console.log("Loading pyodide!");
83
+ self.postMessage({type: 'status', msg: 'Loading pyodide'})
84
+ self.pyodide = await loadPyodide();
85
+ self.pyodide.globals.set("sendPatch", sendPatch);
86
+ console.log("Loaded!");
87
+ await self.pyodide.loadPackage("micropip");
88
+ 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']
89
+ for (const pkg of env_spec) {
90
+ let pkg_name;
91
+ if (pkg.endsWith('.whl')) {
92
+ pkg_name = pkg.split('/').slice(-1)[0].split('-')[0]
93
+ } else {
94
+ pkg_name = pkg
95
+ }
96
+ self.postMessage({type: 'status', msg: `Installing ${pkg_name}`})
97
+ try {
98
+ await self.pyodide.runPythonAsync(`
99
+ import micropip
100
+ await micropip.install('${pkg}');
101
+ `);
102
+ } catch(e) {
103
+ console.log(e)
104
+ self.postMessage({
105
+ type: 'status',
106
+ msg: `Error while installing ${pkg_name}`
107
+ });
108
+ }
109
+ }
110
+ console.log("Packages loaded!");
111
+ self.postMessage({type: 'status', msg: 'Executing code'})
112
+ const code = `
113
+
114
+ import asyncio
115
+
116
+ from panel.io.pyodide import init_doc, write_doc
117
+
118
+ init_doc()
119
+
120
+ """*Linked Brushing* is a very powerful technique. It's also often called
121
+ *Linked Selections* or *Crossfiltering*.
122
+
123
+ This example is inspired by the HoloViews [Linked Brushing Reference Guide]\
124
+ (http://holoviews.org/user_guide/Linked_Brushing.html) and the Plotly blog post
125
+ [Introducing Dash HoloViews]\
126
+ (https://medium.com/plotly/introducing-dash-holoviews-6a05c088ebe5).
127
+
128
+ This example uses the *Iris* dataset.
129
+ """
130
+ from typing import Tuple
131
+
132
+ import holoviews as hv
133
+ import panel as pn
134
+ from holoviews import opts
135
+ from panel.template import FastListTemplate
136
+ import plotly.io as pio
137
+ import pandas as pd
138
+
139
+ @pn.cache
140
+ def get_iris_data():
141
+ return pd.read_csv("https://github.com/awesome-panel/awesome-panel/raw/refs/heads/main/docs/resources/crossfiltering_holoviews/iris.csv.gz")
142
+
143
+
144
+ ACCENT = "#F08080"
145
+
146
+ CSS = """
147
+ .main .card-margin.stretch_both {
148
+ height: calc(100vh - 125px) !important;
149
+ }
150
+ """
151
+
152
+ def _plotly_hooks(plot, element):
153
+ """Used by HoloViews to give plots plotly plots special treatment"""
154
+ fig = plot.state
155
+
156
+ fig["layout"]["dragmode"] = "select"
157
+ fig["config"]["displayModeBar"] = True
158
+ if isinstance(element, hv.Histogram):
159
+ # Constrain histogram selection direction to horizontal
160
+ fig["layout"]["selectdirection"] = "h"
161
+
162
+
163
+ def get_linked_plots() -> Tuple:
164
+ """Returns a tuple (scatter, hist) of linked plots
165
+
166
+ See http://holoviews.org/user_guide/Linked_Brushing.html
167
+ """
168
+
169
+ dataset = hv.Dataset(get_iris_data())
170
+
171
+ scatter = hv.Scatter(dataset, kdims=["sepal_length"], vdims=["sepal_width"])
172
+ hist = hv.operation.histogram(dataset, dimension="petal_width", normed=False)
173
+
174
+ # pylint: disable=no-value-for-parameter
175
+ selection_linker = hv.selection.link_selections.instance()
176
+ # pylint: disable=no-member
177
+ scatter = selection_linker(scatter).opts(
178
+ opts.Scatter(color=ACCENT, size=10, hooks=[_plotly_hooks], width=700, height=400),
179
+ )
180
+ hist = selection_linker(hist).opts(
181
+ opts.Histogram(color=ACCENT, hooks=[_plotly_hooks], width=700, height=400)
182
+ )
183
+
184
+ return scatter, hist
185
+
186
+
187
+ def create_app():
188
+ """Returns the app in a nice FastListTemplate"""
189
+ if pn.config.theme == "dark":
190
+ pio.templates.default = "plotly_dark"
191
+ else:
192
+ pio.templates.default = "plotly_white"
193
+ scatter, hist = get_linked_plots()
194
+ scatter_panel = pn.pane.HoloViews(scatter, sizing_mode="stretch_both", backend="plotly")
195
+ hist_panel = pn.pane.HoloViews(hist, sizing_mode="stretch_both", backend="plotly")
196
+
197
+ def reset(event):
198
+ scatter, hist = get_linked_plots()
199
+ scatter_panel.object=scatter
200
+ hist_panel.object=hist
201
+
202
+ 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.")
203
+
204
+ template = FastListTemplate(
205
+ site="Awesome Panel",
206
+ site_url="https://awesome-panel.org",
207
+ title="Crossfiltering with HoloViews and Plotly",
208
+ accent=ACCENT,
209
+ main=[
210
+ # We need to wrap in Columns to get them to stretch properly
211
+ pn.Column(reset_button, scatter_panel, pn.layout.Spacer(height=20), hist_panel, height=870, sizing_mode="stretch_width"),
212
+ ],
213
+ main_max_width="850px",
214
+ )
215
+ return template
216
+
217
+ pn.extension("plotly", raw_css=[CSS])
218
+ hv.extension("plotly")
219
+ create_app().servable()
220
+
221
+
222
+ await write_doc()
223
+ `
224
+
225
+ try {
226
+ const [docs_json, render_items, root_ids] = await self.pyodide.runPythonAsync(code)
227
+ self.postMessage({
228
+ type: 'render',
229
+ docs_json: docs_json,
230
+ render_items: render_items,
231
+ root_ids: root_ids
232
+ })
233
+ } catch(e) {
234
+ const traceback = `${e}`
235
+ const tblines = traceback.split('\n')
236
+ self.postMessage({
237
+ type: 'status',
238
+ msg: tblines[tblines.length-2]
239
+ });
240
+ throw e
241
+ }
242
+ }
243
+
244
+ self.onmessage = async (event) => {
245
+ const msg = event.data
246
+ if (msg.type === 'rendered') {
247
+ self.pyodide.runPythonAsync(`
248
+ from panel.io.state import state
249
+ from panel.io.pyodide import _link_docs_worker
250
+
251
+ _link_docs_worker(state.curdoc, sendPatch, setter='js')
252
+ `)
253
+ } else if (msg.type === 'patch') {
254
+ self.pyodide.globals.set('patch', msg.patch)
255
+ self.pyodide.runPythonAsync(`
256
+ state.curdoc.apply_json_patch(patch.to_py(), setter='js')
257
+ `)
258
+ self.postMessage({type: 'idle'})
259
+ } else if (msg.type === 'location') {
260
+ self.pyodide.globals.set('location', msg.location)
261
+ self.pyodide.runPythonAsync(`
262
+ import json
263
+ from panel.io.state import state
264
+ from panel.util import edit_readonly
265
+ if state.location:
266
+ loc_data = json.loads(location)
267
+ with edit_readonly(state.location):
268
+ state.location.param.update({
269
+ k: v for k, v in loc_data.items() if k in state.location.param
270
+ })
271
+ `)
272
+ }
273
+ }
274
+
275
+ startApplication()")
276
 
277
 
278
  ACCENT = "#F08080"