MarcSkovMadsen commited on
Commit
bcf92b0
·
1 Parent(s): a0e7cc8

Delete index.jss

Browse files
Files changed (1) hide show
  1. index.jss +0 -629
index.jss DELETED
@@ -1,629 +0,0 @@
1
- importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js");
2
-
3
- function sendPatch(patch, buffers, msg_id) {
4
- self.postMessage({
5
- type: 'patch',
6
- patch: patch,
7
- buffers: buffers
8
- })
9
- }
10
-
11
- async function startApplication() {
12
- console.log("Loading pyodide!");
13
- self.postMessage({type: 'status', msg: 'Loading pyodide'})
14
- self.pyodide = await loadPyodide();
15
- self.pyodide.globals.set("sendPatch", sendPatch);
16
- console.log("Loaded!");
17
- await self.pyodide.loadPackage("micropip");
18
- 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', 'pandas']
19
- for (const pkg of env_spec) {
20
- let pkg_name;
21
- if (pkg.endsWith('.whl')) {
22
- pkg_name = pkg.split('/').slice(-1)[0].split('-')[0]
23
- } else {
24
- pkg_name = pkg
25
- }
26
- self.postMessage({type: 'status', msg: `Installing ${pkg_name}`})
27
- try {
28
- await self.pyodide.runPythonAsync(`
29
- import micropip
30
- await micropip.install('${pkg}');
31
- `);
32
- } catch(e) {
33
- console.log(e)
34
- self.postMessage({
35
- type: 'status',
36
- msg: `Error while installing ${pkg_name}`
37
- });
38
- }
39
- }
40
- console.log("Packages loaded!");
41
- self.postMessage({type: 'status', msg: 'Executing code'})
42
- const code = `
43
-
44
- import asyncio
45
-
46
- from panel.io.pyodide import init_doc, write_doc
47
-
48
- init_doc()
49
-
50
- #!/usr/bin/env python
51
-
52
- import panel as pn
53
- import pandas as pd
54
-
55
- from bokeh.plotting import figure
56
- from bokeh.layouts import layout
57
- from bokeh.models import (
58
- ColumnDataSource,
59
- Range1d,
60
- Slider,
61
- Button,
62
- TextInput,
63
- LabelSet,
64
- Circle,
65
- Div,
66
- )
67
-
68
- class StumpyBokehDashboard:
69
- def __init__(self):
70
- self.sizing_mode = "stretch_both"
71
- self.window = 0
72
- self.m = None
73
-
74
- self.df = None
75
- self.ts_cds = None
76
- self.quad_cds = None
77
- self.pattern_match_cds = None
78
- self.dist_cds = None
79
- self.circle_cds = None
80
-
81
- self.ts_plot = None
82
- self.mp_plot = None
83
- self.pm_plot = None
84
- self.logo_div = None
85
- self.heroku_div = None
86
-
87
- self.slider = None
88
- self.play_btn = None
89
- self.txt_inp = None
90
- self.pattern_btn = None
91
- self.match_btn = None
92
- self.reset_btn = None
93
- self.idx = None
94
- self.min_distance_idx = None
95
-
96
- self.animation = pn.state.add_periodic_callback(
97
- self.update_animate, 50, start=False
98
- )
99
-
100
- def get_df_from_file(self):
101
- raw_df = pd.read_csv(
102
- "https://raw.githubusercontent.com/seanlaw/stumpy-live-demo/master/raw.csv"
103
- )
104
-
105
- mp_df = pd.read_csv(
106
- "https://raw.githubusercontent.com/seanlaw/stumpy-live-demo/master/matrix_profile.csv"
107
- )
108
-
109
- self.window = raw_df.shape[0] - mp_df.shape[0] + 1
110
- self.m = raw_df.shape[0] - mp_df.shape[0] + 1
111
- self.min_distance_idx = mp_df["distance"].argmin()
112
-
113
- df = pd.merge(raw_df, mp_df, left_index=True, how="left", right_index=True)
114
-
115
- return df.reset_index()
116
-
117
- def get_ts_dict(self, df):
118
- return self.df.to_dict(orient="list")
119
-
120
- def get_circle_dict(self, df):
121
- return self.df[["index", "y"]].to_dict(orient="list")
122
-
123
- def get_quad_dict(self, df, pattern_idx=0, match_idx=None):
124
- if match_idx is None:
125
- match_idx = df.loc[pattern_idx, "idx"].astype(int)
126
- quad_dict = dict(
127
- pattern_left=[pattern_idx],
128
- pattern_right=[pattern_idx + self.window - 1],
129
- pattern_top=[max(df["y"])],
130
- pattern_bottom=[0],
131
- match_left=[match_idx],
132
- match_right=[match_idx + self.window - 1],
133
- match_top=[max(df["y"])],
134
- match_bottom=[0],
135
- vert_line_left=[pattern_idx - 5],
136
- vert_line_right=[pattern_idx + 5],
137
- vert_line_top=[max(df["distance"])],
138
- vert_line_bottom=[0],
139
- hori_line_left=[0],
140
- hori_line_right=[max(df["index"])],
141
- hori_line_top=[df.loc[pattern_idx, "distance"] - 0.01],
142
- hori_line_bottom=[df.loc[pattern_idx, "distance"] + 0.01],
143
- )
144
- return quad_dict
145
-
146
- def get_custom_quad_dict(self, df, pattern_idx=0, match_idx=None):
147
- if match_idx is None:
148
- match_idx = df.loc[pattern_idx, "idx"].astype(int)
149
- quad_dict = dict(
150
- pattern_left=[pattern_idx],
151
- pattern_right=[pattern_idx + self.window - 1],
152
- pattern_top=[max(df["y"])],
153
- pattern_bottom=[0],
154
- match_left=[match_idx],
155
- match_right=[match_idx + self.window - 1],
156
- match_top=[max(df["y"])],
157
- match_bottom=[0],
158
- vert_line_left=[match_idx - 5],
159
- vert_line_right=[match_idx + 5],
160
- vert_line_top=[max(df["distance"])],
161
- vert_line_bottom=[0],
162
- hori_line_left=[0],
163
- hori_line_right=[max(df["index"])],
164
- hori_line_top=[df.loc[match_idx, "distance"] - 0.01],
165
- hori_line_bottom=[df.loc[match_idx, "distance"] + 0.01],
166
- )
167
- return quad_dict
168
-
169
- def get_pattern_match_dict(self, df, pattern_idx=0, match_idx=None):
170
- if match_idx is None:
171
- match_idx = df["idx"].loc[pattern_idx].astype(int)
172
- pattern_match_dict = dict(
173
- index=list(range(self.window)),
174
- pattern=df["y"].loc[pattern_idx : pattern_idx + self.window - 1],
175
- match=df["y"].loc[match_idx : match_idx + self.window - 1],
176
- )
177
-
178
- return pattern_match_dict
179
-
180
- def get_ts_plot(self, color="black"):
181
- """
182
- Time Series Plot
183
- """
184
- ts_plot = figure(
185
- toolbar_location="above",
186
- sizing_mode=self.sizing_mode,
187
- title="Raw Time Series or Sequence",
188
- tools=["reset"],
189
- )
190
- q = ts_plot.quad(
191
- "pattern_left",
192
- "pattern_right",
193
- "pattern_top",
194
- "pattern_bottom",
195
- source=self.quad_cds,
196
- name="pattern_quad",
197
- color="#54b847",
198
- )
199
- q.visible = False
200
- q = ts_plot.quad(
201
- "match_left",
202
- "match_right",
203
- "match_top",
204
- "match_bottom",
205
- source=self.quad_cds,
206
- name="match_quad",
207
- color="#696969",
208
- alpha=0.5,
209
- )
210
- q.visible = False
211
- l = ts_plot.line(x="index", y="y", source=self.ts_cds, color=color)
212
- ts_plot.x_range = Range1d(
213
- 0, max(self.df["index"]), bounds=(0, max(self.df["x"]))
214
- )
215
- ts_plot.y_range = Range1d(0, max(self.df["y"]), bounds=(0, max(self.df["y"])))
216
-
217
- c = ts_plot.circle(
218
- x="index", y="y", source=self.circle_cds, size=0, line_color="white"
219
- )
220
- c.selection_glyph = Circle(line_color="white")
221
- c.nonselection_glyph = Circle(line_color="white")
222
-
223
- return ts_plot
224
-
225
- def get_dist_dict(self, df, pattern_idx=0):
226
- dist = df["distance"]
227
- max_dist = dist.max()
228
- min_dist = dist.min()
229
- x_offset = self.df.shape[0] - self.window / 2
230
- y_offset = max_dist / 2
231
- distance = dist.loc[pattern_idx]
232
- text = distance.round(1).astype(str)
233
- gauge_dict = dict(x=[0 + x_offset], y=[0 + y_offset], text=[text])
234
-
235
- return gauge_dict
236
-
237
- def get_mp_plot(self):
238
- """
239
- Matrix Profile Plot
240
- """
241
- mp_plot = figure(
242
- x_range=self.ts_plot.x_range,
243
- toolbar_location=None,
244
- sizing_mode=self.sizing_mode,
245
- title="Matrix Profile (All Minimum Distances)",
246
- )
247
- q = mp_plot.quad(
248
- "vert_line_left",
249
- "vert_line_right",
250
- "vert_line_top",
251
- "vert_line_bottom",
252
- source=self.quad_cds,
253
- name="pattern_start",
254
- color="#54b847",
255
- )
256
- q.visible = False
257
- q = mp_plot.quad(
258
- "hori_line_left",
259
- "hori_line_right",
260
- "hori_line_top",
261
- "hori_line_bottom",
262
- source=self.quad_cds,
263
- name="match_dist",
264
- color="#696969",
265
- alpha=0.5,
266
- )
267
- q.visible = False
268
- mp_plot.line(x="index", y="distance", source=self.ts_cds, color="black")
269
- # mp_plot.x_range = Range1d(0, self.df.shape[0]-self.window+1, bounds=(0, self.df.shape[0]-self.window+1))
270
- mp_plot.x_range = Range1d(
271
- 0, self.df.shape[0] + 1, bounds=(0, self.df.shape[0] + 1)
272
- )
273
- mp_plot.y_range = Range1d(
274
- 0, max(self.df["distance"]), bounds=(0, max(self.df["distance"]))
275
- )
276
-
277
- label = LabelSet(
278
- x="x",
279
- y="y",
280
- text="text",
281
- source=self.dist_cds,
282
- text_align="center",
283
- name="gauge_label",
284
- text_color="black",
285
- text_font_size="30pt",
286
- )
287
- mp_plot.add_layout(label)
288
-
289
- return mp_plot
290
-
291
- def get_pm_plot(self):
292
- """
293
- Pattern-Match Plot
294
- """
295
- pm_plot = figure(
296
- toolbar_location=None,
297
- sizing_mode=self.sizing_mode,
298
- title="Pattern Match Overlay",
299
- )
300
- l = pm_plot.line(
301
- "index",
302
- "pattern",
303
- source=self.pattern_match_cds,
304
- name="pattern_line",
305
- color="#54b847",
306
- line_width=2,
307
- )
308
- l.visible = False
309
- l = pm_plot.line(
310
- "index",
311
- "match",
312
- source=self.pattern_match_cds,
313
- name="match_line",
314
- color="#696969",
315
- alpha=0.5,
316
- line_width=2,
317
- )
318
- l.visible = False
319
-
320
- return pm_plot
321
-
322
- def get_logo_div(self):
323
- """
324
- STUMPY logo
325
- """
326
-
327
- logo_div = Div(
328
- text="<a href='https://stumpy.readthedocs.io/en/latest/'><img src='https://raw.githubusercontent.com/TDAmeritrade/stumpy/main/docs/images/stumpy_logo_small.png' style='width:100%'></a>", sizing_mode="stretch_width"
329
- )
330
-
331
- return logo_div
332
-
333
- def get_heroku_div(self):
334
- """
335
- STUMPY Heroku App Link
336
- """
337
-
338
- heroku_div = Div(text="http://tiny.cc/stumpy-demo")
339
-
340
- return heroku_div
341
-
342
- def get_slider(self, value=0):
343
- slider = Slider(
344
- start=0.0,
345
- end=max(self.df["index"]) - self.window,
346
- value=value,
347
- step=1,
348
- title="Subsequence",
349
- sizing_mode=self.sizing_mode,
350
- )
351
- return slider
352
-
353
- def get_play_button(self):
354
- play_btn = Button(label="► Play")
355
- play_btn.on_click(self.animate)
356
- return play_btn
357
-
358
- def get_text_input(self):
359
- txt_inp = TextInput(sizing_mode=self.sizing_mode)
360
- return txt_inp
361
-
362
- def get_buttons(self):
363
- pattern_btn = Button(label="Show Motif", sizing_mode=self.sizing_mode)
364
- match_btn = Button(label="Show Nearest Neighbor", sizing_mode=self.sizing_mode)
365
- reset_btn = Button(label="Reset", sizing_mode=self.sizing_mode, button_type="primary")
366
- return pattern_btn, match_btn, reset_btn
367
-
368
- def update_plots(self, attr, new, old):
369
- self.quad_cds.data = self.get_quad_dict(self.df, self.slider.value)
370
- self.pattern_match_cds.data = self.get_pattern_match_dict(
371
- self.df, self.slider.value
372
- )
373
- self.dist_cds.data = self.get_dist_dict(self.df, self.slider.value)
374
-
375
- def custom_update_plots(self, attr, new, old):
376
- self.quad_cds.data = self.get_custom_quad_dict(
377
- self.df, self.pattern_idx, self.slider.value
378
- )
379
- self.pattern_match_cds.data = self.get_pattern_match_dict(
380
- self.df, self.pattern_idx, self.slider.value
381
- )
382
- self.dist_cds.data = self.get_dist_dict(self.df, self.slider.value)
383
- dist = self.df["distance"].loc[self.slider.value]
384
-
385
- def show_hide_pattern(self):
386
- pattern_quad = self.ts_plot.select(name="pattern_quad")[0]
387
- pattern_start = self.mp_plot.select(name="pattern_start")[0]
388
- pattern_line = self.pm_plot.select(name="pattern_line")[0]
389
- if pattern_quad.visible:
390
- pattern_start.visible = False
391
- pattern_line.visible = False
392
- pattern_quad.visible = False
393
- self.pattern_btn.label = "Show Motif"
394
- else:
395
- pattern_start.visible = True
396
- pattern_line.visible = True
397
- pattern_quad.visible = True
398
- self.pattern_btn.label = "Hide Motif"
399
-
400
- def show_hide_match(self):
401
- match_quad = self.ts_plot.select(name="match_quad")[0]
402
- match_dist = self.mp_plot.select(name="match_dist")[0]
403
- match_line = self.pm_plot.select(name="match_line")[0]
404
- if match_quad.visible:
405
- match_dist.visible = False
406
- match_line.visible = False
407
- match_quad.visible = False
408
- self.match_btn.label = "Show Nearest Neighbor"
409
- else:
410
- match_dist.visible = True
411
- match_line.visible = True
412
- match_quad.visible = True
413
- self.match_btn.label = "Hide Nearest Neighbor"
414
-
415
- def update_slider(self, attr, old, new):
416
- self.slider.value = int(self.txt_inp.value)
417
-
418
- def animate(self):
419
- if self.play_btn.label == "► Play":
420
- self.play_btn.label = "❚❚ Pause"
421
- self.animation.start()
422
- else:
423
- self.play_btn.label = "► Play"
424
- self.animation.stop()
425
-
426
- def update_animate(self, shift=50):
427
- if self.window < self.m: # Probably using box select
428
- start = self.slider.value
429
- end = start + shift
430
- if self.df.loc[start:end, "distance"].min() <= 15:
431
- self.slider.value = self.df.loc[start:end, "distance"].idxmin()
432
- self.animate()
433
- elif self.slider.value + shift <= self.slider.end:
434
- self.slider.value = self.slider.value + shift
435
- else:
436
- self.slider.value = 0
437
- elif self.slider.value + shift <= self.slider.end:
438
- self.slider.value = self.slider.value + shift
439
- else:
440
- self.slider.value = 0
441
-
442
- def reset(self):
443
- self.sizing_mode = "stretch_both"
444
- self.window = self.m
445
-
446
- self.default_idx = self.min_distance_idx
447
- self.df = self.get_df_from_file()
448
- self.ts_cds.data = self.get_ts_dict(self.df)
449
- self.mp_plot.y_range.end = max(self.df["distance"])
450
- self.mp_plot.title.text = "Matrix Profile (All Minimum Distances)"
451
- self.mp_plot.y_range.bounds = (0, max(self.df["distance"]))
452
- self.quad_cds.data = self.get_quad_dict(self.df, pattern_idx=self.default_idx)
453
- self.pattern_match_cds.data = self.get_pattern_match_dict(
454
- self.df, pattern_idx=self.default_idx
455
- )
456
- self.dist_cds.data = self.get_dist_dict(self.df, pattern_idx=self.default_idx)
457
- self.circle_cds.data = self.get_circle_dict(self.df)
458
- # Remove callback and add old callback
459
- if self.custom_update_plots in self.slider._callbacks["value"]:
460
- self.slider.remove_on_change("value", self.custom_update_plots)
461
- self.slider.on_change("value", self.update_plots)
462
- self.slider.end = self.df.shape[0] - self.window
463
- self.slider.value = self.default_idx
464
-
465
- def get_data(self):
466
- self.df = self.get_df_from_file()
467
- self.default_idx = self.min_distance_idx
468
- self.ts_cds = ColumnDataSource(self.get_ts_dict(self.df))
469
- self.quad_cds = ColumnDataSource(
470
- self.get_quad_dict(self.df, pattern_idx=self.default_idx)
471
- )
472
- self.pattern_match_cds = ColumnDataSource(
473
- self.get_pattern_match_dict(self.df, pattern_idx=self.default_idx)
474
- )
475
- self.dist_cds = ColumnDataSource(
476
- self.get_dist_dict(self.df, pattern_idx=self.default_idx)
477
- )
478
- self.circle_cds = ColumnDataSource(self.get_circle_dict(self.df))
479
-
480
- def get_plots(self, ts_plot_color="black"):
481
- self.ts_plot = self.get_ts_plot(color=ts_plot_color)
482
- self.mp_plot = self.get_mp_plot()
483
- self.pm_plot = self.get_pm_plot()
484
-
485
- def get_widgets(self):
486
- self.slider = self.get_slider(value=self.default_idx)
487
- self.play_btn = self.get_play_button()
488
- self.txt_inp = self.get_text_input()
489
- self.pattern_btn, self.match_btn, self.reset_btn = self.get_buttons()
490
- self.logo_div = self.get_logo_div()
491
- self.heroku_div = self.get_heroku_div()
492
-
493
- def set_callbacks(self):
494
- self.slider.on_change("value", self.update_plots)
495
- self.pattern_btn.on_click(self.show_hide_pattern)
496
- self.show_hide_pattern()
497
- self.match_btn.on_click(self.show_hide_match)
498
- self.show_hide_match()
499
- self.reset_btn.on_click(self.reset)
500
- self.txt_inp.on_change("value", self.update_slider)
501
-
502
- def get_layout(self):
503
- self.get_data()
504
- self.get_plots()
505
- self.get_widgets()
506
- self.set_callbacks()
507
-
508
- l = layout(
509
- [
510
- [self.ts_plot],
511
- [self.mp_plot],
512
- [self.pm_plot],
513
- [self.slider],
514
- [self.pattern_btn, self.match_btn, self.play_btn, self.logo_div],
515
- ],
516
- sizing_mode=self.sizing_mode,
517
- )
518
-
519
- return l
520
-
521
- def get_raw_layout(self):
522
- self.get_data()
523
- self.get_plots(ts_plot_color="#54b847")
524
-
525
- l = layout([[self.ts_plot], [self.mp_plot]], sizing_mode=self.sizing_mode)
526
-
527
- return l
528
-
529
-
530
- dashboard = StumpyBokehDashboard()
531
-
532
- def get_components(dashboard: StumpyBokehDashboard=dashboard):
533
- dashboard.get_data()
534
- dashboard.get_plots()
535
- dashboard.get_widgets()
536
- dashboard.set_callbacks()
537
-
538
- logo = dashboard.logo_div
539
- settings = layout(
540
- dashboard.pattern_btn,
541
- dashboard.match_btn,
542
- dashboard.play_btn,
543
- dashboard.slider,
544
- height=150,
545
- sizing_mode="stretch_width",
546
- )
547
- main = layout(
548
- [
549
- [dashboard.ts_plot],
550
- [dashboard.mp_plot],
551
- [dashboard.pm_plot],
552
- ],
553
- sizing_mode=dashboard.sizing_mode,
554
- )
555
- return logo, settings, main
556
-
557
- pn.extension(template="fast")
558
- pn.state.template.param.update(
559
- site_url="https://awesome-panel.org",
560
- site="Awesome Panel",
561
- title="Stumpy Timeseries Analysis",
562
- favicon="https://raw.githubusercontent.com/MarcSkovMadsen/awesome-panel-assets/320297ccb92773da099f6b97d267cc0433b67c23/favicon/ap-1f77b4.ico",
563
- header_background="#459db9",
564
- theme_toggle=False,
565
- )
566
-
567
- logo, settings, main = get_components()
568
-
569
- pn.Column(
570
- logo,
571
- settings, sizing_mode="stretch_width",
572
- ).servable(target="sidebar")
573
- pn.panel(main, sizing_mode="stretch_both", max_height=800).servable(target="main")
574
-
575
-
576
- await write_doc()
577
- `
578
-
579
- try {
580
- const [docs_json, render_items, root_ids] = await self.pyodide.runPythonAsync(code)
581
- self.postMessage({
582
- type: 'render',
583
- docs_json: docs_json,
584
- render_items: render_items,
585
- root_ids: root_ids
586
- })
587
- } catch(e) {
588
- const traceback = `${e}`
589
- const tblines = traceback.split('\n')
590
- self.postMessage({
591
- type: 'status',
592
- msg: tblines[tblines.length-2]
593
- });
594
- throw e
595
- }
596
- }
597
-
598
- self.onmessage = async (event) => {
599
- const msg = event.data
600
- if (msg.type === 'rendered') {
601
- self.pyodide.runPythonAsync(`
602
- from panel.io.state import state
603
- from panel.io.pyodide import _link_docs_worker
604
-
605
- _link_docs_worker(state.curdoc, sendPatch, setter='js')
606
- `)
607
- } else if (msg.type === 'patch') {
608
- self.pyodide.globals.set('patch', msg.patch)
609
- self.pyodide.runPythonAsync(`
610
- state.curdoc.apply_json_patch(patch.to_py(), setter='js')
611
- `)
612
- self.postMessage({type: 'idle'})
613
- } else if (msg.type === 'location') {
614
- self.pyodide.globals.set('location', msg.location)
615
- self.pyodide.runPythonAsync(`
616
- import json
617
- from panel.io.state import state
618
- from panel.util import edit_readonly
619
- if state.location:
620
- loc_data = json.loads(location)
621
- with edit_readonly(state.location):
622
- state.location.param.update({
623
- k: v for k, v in loc_data.items() if k in state.location.param
624
- })
625
- `)
626
- }
627
- }
628
-
629
- startApplication()