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.
+
+
+
+
+
+
+
+
+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 :)
+
+ Interactive Streaming Table and Bokeh plot using Panel
+
+ WARNING: This examples takes a little longer to load. So be patient :)
+
+
Simple demo showing Panel widgets interating with parts of the page
WARNING: This examples takes a little longer to load. So be patient :)
-
+
+ Minimal d3 demo demonstrating how to create a visualization
+
+
A Python REPL (Read Eval Print Loop).
-
+
A Python REPL (Read Eval Print Loop) with slightly better formatting.
A static demo of the <py-script> tag
-
+
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 {