diff --git a/pyscriptjs/examples/d3.html b/pyscriptjs/examples/d3.html new file mode 100644 index 0000000..fcae36f --- /dev/null +++ b/pyscriptjs/examples/d3.html @@ -0,0 +1,169 @@ + + + d3: JavaScript & PyScript visualizations side-by-side + + + + + + + + + + + Based on Learn D3: Shapes tutorial. + +
+
+
JavaScript version
+
+
+
+
+
+
PyScript version
+
+
+
+
+
+ + + + + + +from pyodide import create_proxy, to_js +from esm import d3 + +fruits = [ + dict(name="🍊", count=21), + dict(name="πŸ‡", count=13), + dict(name="🍏", count=8), + dict(name="🍌", count=5), + dict(name="🍐", count=3), + dict(name="πŸ‹", count=2), + dict(name="🍎", count=1), + dict(name="πŸ‰", count=1), +] + +fn = create_proxy(lambda d, *_: d["count"]) +data = d3.pie().value(fn)(to_js(fruits)) + +arc = (d3.arc() + .innerRadius(210) + .outerRadius(310) + .padRadius(300) + .padAngle(2 / 300) + .cornerRadius(8)) + +py = d3.select("#py") +py.select(".loading").remove() + +svg = (py + .append("svg") + .attr("viewBox", "-320 -320 640 640") + .attr("width", "400") + .attr("height", "400")) + +for d in data: + d_py = d.to_py() + + (svg.append("path") + .style("fill", "steelblue") + .attr("d", arc(d))) + + text = (svg.append("text") + .style("fill", "white") + .attr("transform", f"translate({arc.centroid(d).join(',')})") + .attr("text-anchor", "middle")) + + (text.append("tspan") + .style("font-size", "24") + .attr("x", "0") + .text(d_py["data"]["name"])) + + (text.append("tspan") + .style("font-size", "18") + .attr("x", "0") + .attr("dy", "1.3em") + .text(d_py["value"])) + + + + diff --git a/pyscriptjs/examples/index.html b/pyscriptjs/examples/index.html index 986da26..02d1eaa 100644 --- a/pyscriptjs/examples/index.html +++ b/pyscriptjs/examples/index.html @@ -30,22 +30,31 @@ WARNING: This examples takes a little longer to load. So be patient :)

+

Streaming Demo in Panel

+

Interactive Streaming Table and Bokeh plot using Panel + + WARNING: This examples takes a little longer to load. So be patient :) +

+

Simple Panel Demo

Simple demo showing Panel widgets interating with parts of the page WARNING: This examples takes a little longer to load. So be patient :)

- +

Simple d3 visualization

+

Minimal d3 demo demonstrating how to create a visualization

+ +

REPL

A Python REPL (Read Eval Print Loop).

- +

REPL2

A Python REPL (Read Eval Print Loop) with slightly better formatting.

Simple script

A static demo of the <py-script> tag

- +

Simple script 2

A dynamic demo of the <py-script> tag

diff --git a/pyscriptjs/examples/panel_stream.html b/pyscriptjs/examples/panel_stream.html new file mode 100644 index 0000000..f49a719 --- /dev/null +++ b/pyscriptjs/examples/panel_stream.html @@ -0,0 +1,128 @@ + + + + + PyScript/Panel Streaming Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - bokeh + - numpy + - pandas + - scikit-learn + + +
+ + +
+
+
+
+
+
+
+
+
+
+ +import asyncio +import micropip + +await micropip.install(['panel==0.13.0rc11', 'altair']) + +import panel as pn +import numpy as np +import pandas as pd + +from bokeh.models import ColumnDataSource +from bokeh.plotting import figure +from panel.io.pyodide import show + +df = pd.DataFrame(np.random.randn(10, 4), columns=list('ABCD')).cumsum() + +rollover = pn.widgets.IntInput(name='Rollover', value=15) +follow = pn.widgets.Checkbox(name='Follow', value=True, align='end') + +tabulator = pn.widgets.Tabulator(df, height=450, width=400) + +def color_negative_red(val): + """ + Takes a scalar and returns a string with + the css property `'color: red'` for negative + strings, black otherwise. + """ + color = 'red' if val < 0 else 'green' + return 'color: %s' % color + +tabulator.style.applymap(color_negative_red) + +p = figure(height=450, width=600) + +cds = ColumnDataSource(data=ColumnDataSource.from_df(df)) + +p.line('index', 'A', source=cds, line_color='red') +p.line('index', 'B', source=cds, line_color='green') +p.line('index', 'C', source=cds, line_color='blue') +p.line('index', 'D', source=cds, line_color='purple') + +def stream(): + data = df.iloc[-1] + np.random.randn(4) + tabulator.stream(data, rollover=rollover.value, follow=follow.value) + value = {k: [v] for k, v in tabulator.value.iloc[-1].to_dict().items()} + value['index'] = [tabulator.value.index[-1]] + cds.stream(value) + +cb = pn.state.add_periodic_callback(stream, 200) + +controls = pn.Row(cb.param.period, rollover, follow, width=400) + +await show(controls, 'controls') +await show(tabulator, 'table') +await show(p, 'plot') + + + diff --git a/pyscriptjs/src/components/pyenv.ts b/pyscriptjs/src/components/pyenv.ts index f67b808..c2f964c 100644 --- a/pyscriptjs/src/components/pyenv.ts +++ b/pyscriptjs/src/components/pyenv.ts @@ -39,7 +39,11 @@ export class PyEnv extends HTMLElement { let env = []; let paths = []; + this.environment = jsyaml.load(this.code); + if (this.environment === undefined) + return + for (let entry of this.environment) { if (typeof entry == "string" ){ env.push(entry); diff --git a/pyscriptjs/src/components/pyscript.ts b/pyscriptjs/src/components/pyscript.ts index c435e3f..c3df6b0 100644 --- a/pyscriptjs/src/components/pyscript.ts +++ b/pyscriptjs/src/components/pyscript.ts @@ -45,6 +45,11 @@ function htmlDecode(input) { return doc.documentElement.textContent; } +// TODO: use type declaractions +type PyodideInterface = { + registerJsModule(name: string, module: object): void +} + class Script { source: string; state: string; @@ -241,13 +246,46 @@ export class PyScript extends HTMLElement { // pkg.do_something(); } - async evaluate() { + protected async _register_esm(pyodide: PyodideInterface): Promise { + const imports: {[key: string]: unknown} = {} + for (const node of document.querySelectorAll("script[type='importmap']")) { + const importmap = (() => { + try { + return JSON.parse(node.textContent) + } catch { + return null + } + })() + + if (importmap?.imports == null) + continue + + for (const [name, url] of Object.entries(importmap.imports)) { + if (typeof name != "string" || typeof url != "string") + continue + + try { + // XXX: pyodide doesn't like Module(), failing with + // "can't read 'name' of undefined" at import time + imports[name] = {...await import(url)} + } catch { + console.error(`failed to fetch '${url}' for '${name}'`) + } + } + } + + pyodide.registerJsModule("esm", imports) + } + + async evaluate(): Promise { console.log('evaluate'); + if (this.source){ this.loadFromFile(this.source) }else{ - let pyodide = await pyodideReadyPromise; + const pyodide = await pyodideReadyPromise; + await this._register_esm(pyodide) // debugger try { function ltrim(code: string): string {