examples inspector plugin (#1040)

* replace unnecessary elements from hello_world example and replace with py-tutor tag

* add py_tutor plugin

* port altair example

* add code for more granular tutor mode

* add support for including modules source in pytutor

* remove js dependencies in hello_world

* put antigravity on a diet ;)

* use py-tutor on antigravity example

* use py-tutor on d3 example

* use py-tutor on bokeh example

* use py-tutor on bokeh_interactive example

* fix issue when module_paths is undefined

* remove prism js dependency leftovers

* ooops, really remove prism js dependency leftovers

* port follium example to pytutor

* port pymarkdown and matplotlib example to pytutor

* port message_passing and numpy_convas_fractals examples to pytutor

* port the panel complex  examples to pytutor

* port the panel complex  examples to pytutor

* port last examples to py-tutor

* remove prism

* remore most debugging logs and replace log with info

* add new d3.py file

* add comments to connect method

* clean pyscript class from logs

* revert class pySrc attribute

* add check_tutor_generated_code to test code inspector plugin in examples

* add doctsting to PyTutor connect

* add check for tutor code inspection on all examples

* Update pyscriptjs/src/plugins/python/py_tutor.py

fix template indentation

Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>

* Update examples/todo-pylist.html

fix typo (stray = )

Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>

* fix pymarkdown example

Co-authored-by: Fabio Pliger <fpliger@anaconda.com>
Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>
This commit is contained in:
Fabio Pliger
2022-12-20 07:48:07 -08:00
committed by GitHub
parent d4120d2af3
commit c0f36aa047
24 changed files with 1055 additions and 2515 deletions

View File

@@ -5,9 +5,7 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
@@ -20,135 +18,61 @@
</nav>
<section class="pyscript">
<div id="altair"></div>
<py-config>
packages = [
"altair",
"pandas",
"vega_datasets"
]
</py-config>
<py-script>
import altair as alt
from vega_datasets import data
<py-tutor>
<py-config>
packages = [
"altair",
"pandas",
"vega_datasets"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
import altair as alt
from vega_datasets import data
source = data.movies.url
source = data.movies.url
pts = alt.selection(type="single", encodings=['x'])
pts = alt.selection(type="single", encodings=['x'])
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
display(alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
), target="altair")
</py-script>
display(alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
), target="altair")
</py-script>
</py-tutor>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"altair",
"pandas",
"vega_datasets"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import altair as alt
from vega_datasets import data
source = data.movies.url
pts = alt.selection(type="single", encodings=['x'])
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
display(alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
), target="altair")
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -5,9 +5,7 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
@@ -18,101 +16,21 @@
<a class="title" href="" style="color: #f0ab3c;">Antigravity</a>
</div>
</nav>
<section class="pyscript">
<py-config>
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
<py-script>
import antigravity
antigravity.fly()
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = ["./antigravity.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
import antigravity
antigravity.fly()
&lt;/py-script&gt;
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</div>
</section>
<py-tutor modules="antigravity.py">
<section class="pyscript">
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
<py-script>
import antigravity
antigravity.fly()
</py-script>
</section>
</py-tutor>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -15,101 +15,50 @@
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
</div>
</nav>
<section class="pyscript">
<div id="myplot"></div>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
</div>
</nav>
<py-tutor>
<section class="pyscript">
<div id="myplot"></div>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<py-config>
packages = [
"bokeh",
"numpy"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script id="main">
import json
import pyodide
<py-script id="main">
import json
import pyodide
from js import Bokeh, console, JSON
from js import Bokeh, console, JSON
from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDN
# create a new plot with default tools, using figure
p = figure(plot_width=400, plot_height=400)
# create a new plot with default tools, using figure
p = figure(plot_width=400, plot_height=400)
# add a circle renderer with x and y coordinates, size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
p_json = json.dumps(json_item(p, "myplot"))
# add a circle renderer with x and y coordinates, size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
p_json = json.dumps(json_item(p, "myplot"))
Bokeh.embed.embed_item(JSON.parse(p_json))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import json
import pyodide
from js import Bokeh, console, JSON
from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDN
# create a new plot with default tools, using figure
p = figure(plot_width=400, plot_height=400)
# add a circle renderer with x and y coordinates, size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
p_json = json.dumps(json_item(p, "myplot"))
Bokeh.embed.embed_item(JSON.parse(p_json))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
Bokeh.embed.embed_item(JSON.parse(p_json))
</py-script>
</section>
</py-tutor>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -8,8 +8,6 @@
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script type="text/javascript">
Bokeh.set_log_level("info");
@@ -27,18 +25,22 @@
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
</div>
</nav>
<section class="pyscript">
<h1>Bokeh Example</h1>
<div id="myplot"></div>
<py-tutor>
<section class="pyscript">
<h1>Bokeh Example</h1>
<div id="myplot"></div>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<py-config>
packages = [
"bokeh",
"numpy"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script id="main">
<py-script id="main">
import asyncio
import json
import pyodide
@@ -106,111 +108,8 @@ async def show(plot, target):
_link_docs(pydoc, jsdoc)
asyncio.ensure_future(show(row, 'myplot'))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import asyncio
import json
import pyodide
from js import Bokeh, console, JSON
from bokeh import __version__
from bokeh.document import Document
from bokeh.embed.util import OutputDocumentFor, standalone_docs_json_and_render_items
from bokeh.models import Slider, Div
from bokeh.layouts import Row
from bokeh.protocol.messages.patch_doc import process_document_events
# create a new plot with default tools, using figure
p = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude")
div = Div(text=f'Amplitude is: {p.value}')
def callback(attr, old, new):
div.text = f'Amplitude is: {new}'
p.on_change('value', callback)
row = Row(children=[p, div])
def doc_json(model, target):
with OutputDocumentFor([model]) as doc:
doc.title = ""
docs_json, _ = standalone_docs_json_and_render_items(
[model], suppress_callback_warning=True
)
doc_json = list(docs_json.values())[0]
root_id = doc_json['roots']['root_ids'][0]
return doc, json.dumps(dict(
target_id = target,
root_id = root_id,
doc = doc_json,
version = __version__,
))
def _link_docs(pydoc, jsdoc):
def jssync(event):
if getattr(event, 'setter_id', None) is not None:
return
events = [event]
json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events))
pydoc.apply_json_patch(json.loads(json_patch))
jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False))
def pysync(event):
json_patch, buffers = process_document_events([event], use_buffers=True)
buffer_map = {}
for (ref, buffer) in buffers:
buffer_map[ref['id']] = buffer
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js')
pydoc.on_change(pysync)
async def show(plot, target):
pydoc, model_json = doc_json(plot, target)
views = await Bokeh.embed.embed_item(JSON.parse(model_json))
jsdoc = views[0].model.document
_link_docs(pydoc, jsdoc)
asyncio.ensure_future(show(row, 'myplot'))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</py-script>
</section>
</py-tutor>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -7,8 +7,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.loading {
@@ -38,277 +36,89 @@
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a>
</div>
</nav>
<section class="pyscript">
<b>
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
</b>
<div style="display: flex; flex-direction: row">
<div>
<div style="text-align: center">JavaScript version</div>
<div id="js" style="width: 400px; height: 400px">
<div class="loading"></div>
<section class="pyscript">
<py-tutor modules="d3.py">
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./d3.py"]
</py-config>
</py-tutor>
<b>
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
</b>
<div style="display: flex; flex-direction: row">
<div>
<div style="text-align: center">JavaScript version</div>
<div id="js" style="width: 400px; height: 400px">
<div class="loading"></div>
</div>
</div>
<div>
<div style="text-align: center">PyScript version</div>
<div id="py" style="width: 400px; height: 400px">
<div class="loading"></div>
</div>
</div>
</div>
<div>
<div style="text-align: center">PyScript version</div>
<div id="py" style="width: 400px; height: 400px">
<div class="loading"></div>
</div>
</div>
</div>
<py-script src="d3.py"></py-script>
</section>
<script type="module">
<script type="module">
const fruits = [
{name: "🍊", count: 21},
{name: "🍇", count: 13},
{name: "🍏", count: 8},
{name: "🍌", count: 5},
{name: "🍐", count: 3},
{name: "🍋", count: 2},
{name: "🍎", count: 1},
{name: "🍉", count: 1},
]
const fruits = [
{name: "🍊", count: 21},
{name: "🍇", count: 13},
{name: "🍏", count: 8},
{name: "🍌", count: 5},
{name: "🍐", count: 3},
{name: "🍋", count: 2},
{name: "🍎", count: 1},
{name: "🍉", count: 1},
]
const fn = (d) => d.count
const data = d3.pie().value(fn)(fruits)
const fn = (d) => d.count
const data = d3.pie().value(fn)(fruits)
const arc = d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8)
const arc = d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8)
const js = d3.select("#js")
js.select(".loading").remove()
const js = d3.select("#js")
js.select(".loading").remove()
const svg = js
.append("svg")
.attr("viewBox", "-320 -320 640 640")
.attr("width", "400")
.attr("height", "400")
const svg = js
.append("svg")
.attr("viewBox", "-320 -320 640 640")
.attr("width", "400")
.attr("height", "400")
for (const d of data) {
svg.append("path")
.style("fill", "steelblue")
.attr("d", arc(d))
for (const d of data) {
svg.append("path")
.style("fill", "steelblue")
.attr("d", arc(d))
const text = svg.append("text")
.style("fill", "white")
.attr("transform", `translate(${arc.centroid(d).join(",")})`)
.attr("text-anchor", "middle")
const text = svg.append("text")
.style("fill", "white")
.attr("transform", `translate(${arc.centroid(d).join(",")})`)
.attr("text-anchor", "middle")
text.append("tspan")
.style("font-size", "24")
.attr("x", "0").text(d.data.name)
text.append("tspan")
.style("font-size", "24")
.attr("x", "0").text(d.data.name)
text.append("tspan")
.style("font-size", "18")
.attr("x", "0")
.attr("dy", "1.3em")
.text(d.value)
}
</script>
<py-script>
import js
from pyodide.ffi import create_proxy, to_js
d3 = js.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"]))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;script&gt;
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
const fruits = [
{name: "🍊", count: 21},
{name: "🍇", count: 13},
{name: "🍏", count: 8},
{name: "🍌", count: 5},
{name: "🍐", count: 3},
{name: "🍋", count: 2},
{name: "🍎", count: 1},
{name: "🍉", count: 1},
]
const fn = (d) => d.count
const data = d3.pie().value(fn)(fruits)
const arc = d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8)
const js = d3.select("#js")
js.select(".loading").remove()
const svg = js
.append("svg")
.attr("viewBox", "-320 -320 640 640")
.attr("width", "400")
.attr("height", "400")
for (const d of data) {
svg.append("path")
.style("fill", "steelblue")
.attr("d", arc(d))
const text = svg.append("text")
.style("fill", "white")
.attr("transform", `translate(${arc.centroid(d).join(",")})`)
.attr("text-anchor", "middle")
text.append("tspan")
.style("font-size", "24")
.attr("x", "0").text(d.data.name)
text.append("tspan")
.style("font-size", "18")
.attr("x", "0")
.attr("dy", "1.3em")
.text(d.value)
}
&lt;/script&gt;
&lt;py-script&gt;
from pyodide.ffi import create_proxy, to_js
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"]))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
text.append("tspan")
.style("font-size", "18")
.attr("x", "0")
.attr("dy", "1.3em")
.text(d.value)
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</script>
</body>
</html>

64
examples/d3.py Normal file
View File

@@ -0,0 +1,64 @@
import js
from pyodide.ffi import create_proxy, to_js
d3 = js.d3
fruits = [
{"name": "🍊", "count": 21},
{"name": "🍇", "count": 13},
{"name": "🍏", "count": 8},
{"name": "🍌", "count": 5},
{"name": "🍐", "count": 3},
{"name": "🍋", "count": 2},
{"name": "🍎", "count": 1},
{"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"])
)

View File

@@ -6,8 +6,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
@@ -21,14 +19,18 @@
<section class="pyscript">
<div id="folium"></div>
<py-config>
packages = [
"folium",
"pandas"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"folium",
"pandas"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
<py-script>
import folium
import json
import pandas as pd
@@ -60,77 +62,9 @@ folium.Choropleth(
folium.LayerControl().add_to(m)
display(m, target="folium")
</py-script>
</py-script>
</py-tutor>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div id="folium"&gt;&lt;/div&gt;
&lt;py-config&gt;
packages = [
"folium",
"pandas"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import folium
import json
import pandas as pd
from pyodide.http import open_url
url = (
"https://raw.githubusercontent.com/python-visualization/folium/master/examples/data"
)
state_geo = f"{url}/us-states.json"
state_unemployment = f"{url}/US_Unemployment_Oct2012.csv"
state_data = pd.read_csv(open_url(state_unemployment))
geo_json = json.loads(open_url(state_geo).read())
m = folium.Map(location=[48, -102], zoom_start=3)
folium.Choropleth(
geo_data=geo_json,
name="choropleth",
data=state_data,
columns=["State", "Unemployment"],
key_on="feature.id",
fill_color="YlGn",
fill_opacity=0.7,
line_opacity=0.2,
legend_name="Unemployment Rate (%)",
).add_to(m)
folium.LayerControl().add_to(m)
display(m, target="folium")
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -9,12 +9,9 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
@@ -24,48 +21,23 @@
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
</div>
</nav>
<section class="pyscript">
Hello world! <br>
This is the current date and time, as computed by Python:
<py-script>
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-script&gt;
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
<py-tutor>
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<section class="pyscript">
Hello world! <br>
This is the current date and time, as computed by Python:
<py-script>
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
</py-script>
</section>
</py-tutor>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -8,19 +8,23 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="../build/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<script defer src="../build/pyscript.js"></script>
<py-config>
packages = [
"markdown"
]
plugins = [
"../build/plugins/python/py_markdown.py"
]
</py-config>
</head>
<body>
<py-tutor>
<py-config>
packages = [
"markdown"
]
plugins = [
"../build/plugins/python/py_markdown.py",
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-md>#Hello world!</py-md>
</py-tutor>
</body>
</html>

View File

@@ -6,8 +6,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
@@ -21,13 +19,17 @@
<section class="pyscript">
<div id="mpl"></div>
<py-config>
packages = [
"matplotlib"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"matplotlib"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
<py-script>
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
@@ -61,78 +63,8 @@ fig1.colorbar(tpc)
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
display(fig1, target="mpl")
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div id="mpl"&gt;&lt;/div&gt;
&lt;py-config&gt;
packages = [
"matplotlib",
]
&lt;/py-config&gt;
&lt;py-script&gt;
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
# First create the x and y coordinates of the points.
n_angles = 36
n_radii = 8
min_radius = 0.25
radii = np.linspace(min_radius, 0.95, n_radii)
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi / n_angles
x = (radii * np.cos(angles)).flatten()
y = (radii * np.sin(angles)).flatten()
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
# Create the Triangulation; no triangles so Delaunay triangulation created.
triang = tri.Triangulation(x, y)
# Mask off unwanted triangles.
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
y[triang.triangles].mean(axis=1))
< min_radius)
fig1, ax1 = plt.subplots()
ax1.set_aspect('equal')
tpc = ax1.tripcolor(triang, z, shading='flat')
fig1.colorbar(tpc)
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
display(fig1, target="mpl")
&lt;/py-script&gt;
</code>
</pre>
</div>
</py-script>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -4,22 +4,28 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
</head>
<body>
<py-config>
packages = [
"numpy",
"networkx",
"matplotlib"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"numpy",
"networkx",
"matplotlib"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
<py-script>
import numpy as np
import networkx as nx
</py-script>
</py-script>
<p>Message passing with linear algebra: a demo.</p>
<p>Imagine we have a chain graph that looks like this:</p>
@@ -31,21 +37,22 @@ G = nx.Graph()
nodes = list(range(4))
G.add_edges_from(zip(nodes[0:-1], nodes[1:]))
print(G.edges())
</py-script></pre>
</py-script>
</pre>
<p>This chain graph has the following adjacency matrix:</p>
<pre>
<py-script>
<py-script>
adj_mat = np.eye(4, k=1)
print(f"A: {adj_mat}")
</py-script>
</pre>
</py-script>
</pre>
<p>And imagine that we have a message that lives on the graph:</p>
<pre>
<py-script>
<py-script>
message = np.array([1.0, 0.0, 0.0, 0.0])
print(f"message: {message}")
</py-script>
</pre>
</py-script>
</pre>
<p>Try out message passing below by doing any one of the following steps:</p>
<pre><code>message @ adj_mat</code></pre>
<pre><code>message @ adj_mat @ adj_mat</code></pre>
@@ -53,6 +60,7 @@ print(f"message: {message}")
<div>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</div>
</py-tutor>
</body>
</html>

View File

@@ -6,8 +6,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<style>
.loading {
display: inline-block;
@@ -86,25 +84,28 @@
</div>
</div>
</div>
<py-tutor>
<py-config type="json">
{
"packages": [
"numpy",
"sympy"
],
"fetch": [
{
"files": [
"./palettes.py",
"./fractals.py"
]
}
],
"plugins": [
"../build/plugins/python/py_tutor.py"
]
}
</py-config>
<py-config type="json">
{
"packages": [
"numpy",
"sympy"
],
"fetch": [
{
"files": [
"./palettes.py",
"./fractals.py"
]
}
]
}
</py-config>
<py-script>
<py-script>
from pyodide.ffi import to_js, create_proxy
import numpy as np
@@ -333,282 +334,8 @@ async def main():
)
asyncio.ensure_future(main())
</py-script>
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config type="json"&gt;
{
"packages": [
"numpy",
"sympy"
],
"fetch": [
{
"files": [
"./palettes.py",
"./fractals.py"
]
}
]
}
&lt;/py-config&gt;
&lt;py-script&gt;
from pyodide.ffi import to_js, create_proxy
import numpy as np
import sympy
from palettes import Magma256
from fractals import mandelbrot, julia, newton
from js import (
console,
document,
devicePixelRatio,
ImageData,
Uint8ClampedArray,
CanvasRenderingContext2D as Context2d,
requestAnimationFrame,
)
def prepare_canvas(width: int, height: int, canvas: Element) -> Context2d:
ctx = canvas.getContext("2d")
canvas.style.width = f"{width}px"
canvas.style.height = f"{height}px"
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
return ctx
def color_map(array: np.array, palette: np.array) -> np.array:
size, _ = palette.shape
index = (array/array.max()*(size - 1)).round().astype("uint8")
width, height = array.shape
image = np.full((width, height, 4), 0xff, dtype=np.uint8)
image[:, :, :3] = palette[index]
return image
def draw_image(ctx: Context2d, image: np.array) -> None:
data = Uint8ClampedArray.new(to_js(image.tobytes()))
width, height, _ = image.shape
image_data = ImageData.new(data, width, height)
ctx.putImageData(image_data, 0, 0)
width, height = 600, 600
async def draw_mandelbrot() -> None:
spinner = document.querySelector("#mandelbrot .loading")
canvas = document.querySelector("#mandelbrot canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Mandelbrot set ...")
console.time("mandelbrot")
iters = mandelbrot(width, height)
console.timeEnd("mandelbrot")
image = color_map(iters, Magma256)
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
async def draw_julia() -> None:
spinner = document.querySelector("#julia .loading")
canvas = document.querySelector("#julia canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Julia set ...")
console.time("julia")
iters = julia(width, height)
console.timeEnd("julia")
image = color_map(iters, Magma256)
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
def ranges():
x0_in = document.querySelector("#x0")
x1_in = document.querySelector("#x1")
y0_in = document.querySelector("#y0")
y1_in = document.querySelector("#y1")
xr = (float(x0_in.value), float(x1_in.value))
yr = (float(y0_in.value), float(y1_in.value))
return xr, yr
current_image = None
async def draw_newton() -> None:
spinner = document.querySelector("#newton .loading")
canvas = document.querySelector("#newton canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Newton set ...")
poly_in = document.querySelector("#poly")
coef_in = document.querySelector("#coef")
conv_in = document.querySelector("#conv")
iter_in = document.querySelector("#iter")
xr, yr = ranges()
# z**3 - 1
# z**8 + 15*z**4 - 16
# z**3 - 2*z + 2
expr = sympy.parse_expr(poly_in.value)
coeffs = [ complex(c) for c in reversed(sympy.Poly(expr, sympy.Symbol("z")).all_coeffs()) ]
poly = np.polynomial.Polynomial(coeffs)
coef = complex(sympy.parse_expr(coef_in.value))
console.time("newton")
iters, roots = newton(width, height, p=poly, a=coef, xr=xr, yr=yr)
console.timeEnd("newton")
if conv_in.checked:
n = poly.degree() + 1
k = int(len(Magma256)/n)
colors = Magma256[::k, :][:n]
colors[0, :] = [255, 0, 0] # red: no convergence
image = color_map(roots, colors)
else:
image = color_map(iters, Magma256)
global current_image
current_image = image
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
handler = create_proxy(lambda _event: draw_newton())
document.querySelector("#newton fieldset").addEventListener("change", handler)
canvas = document.querySelector("#newton canvas")
is_selecting = False
init_sx, init_sy = None, None
sx, sy = None, None
async def mousemove(event):
global is_selecting
global init_sx
global init_sy
global sx
global sy
def invert(sx, source_range, target_range):
source_start, source_end = source_range
target_start, target_end = target_range
factor = (target_end - target_start)/(source_end - source_start)
offset = -(factor * source_start) + target_start
return (sx - offset) / factor
bds = canvas.getBoundingClientRect()
event_sx, event_sy = event.clientX - bds.x, event.clientY - bds.y
ctx = canvas.getContext("2d")
pressed = event.buttons == 1
if is_selecting:
if not pressed:
xr, yr = ranges()
x0 = invert(init_sx, xr, (0, width))
x1 = invert(sx, xr, (0, width))
y0 = invert(init_sy, yr, (0, height))
y1 = invert(sy, yr, (0, height))
document.querySelector("#x0").value = x0
document.querySelector("#x1").value = x1
document.querySelector("#y0").value = y0
document.querySelector("#y1").value = y1
is_selecting = False
init_sx, init_sy = None, None
sx, sy = init_sx, init_sy
await draw_newton()
else:
ctx.save()
ctx.clearRect(0, 0, width, height)
draw_image(ctx, current_image)
sx, sy = event_sx, event_sy
ctx.beginPath()
ctx.rect(init_sx, init_sy, sx - init_sx, sy - init_sy)
ctx.fillStyle = "rgba(255, 255, 255, 0.4)"
ctx.strokeStyle = "rgba(255, 255, 255, 1.0)"
ctx.fill()
ctx.stroke()
ctx.restore()
else:
if pressed:
is_selecting = True
init_sx, init_sy = event_sx, event_sy
sx, sy = init_sx, init_sy
canvas.addEventListener("mousemove", create_proxy(mousemove))
import asyncio
async def main():
_ = await asyncio.gather(
draw_mandelbrot(),
draw_julia(),
draw_newton(),
)
asyncio.ensure_future(main())
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</py-tutor>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,8 +10,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
@@ -23,72 +21,31 @@
</div>
</nav>
<section class="pyscript">
<div id="simple_app"></div>
<div id="simple_app"></div>
<py-config>
packages = [
"bokeh",
"numpy",
"panel==0.14.1"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"bokeh",
"numpy",
"panel==0.14.1"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
import panel as pn
<py-script>
import panel as pn
slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')
slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')
def callback(new):
return f'Amplitude is: {new}'
def callback(new):
return f'Amplitude is: {new}'
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')
def callback(new):
return f'Amplitude is: {new}'
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
&lt;/py-script&gt;
</code>
</pre>
</div>
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
</py-script>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -41,8 +41,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -66,337 +64,163 @@
<div class="bk-root" id="plot"></div>
</div>
</div>
</div>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
import panel as pn
import pandas as pd
import param
<py-script>
import panel as pn
import pandas as pd
import param
from pyodide.http import open_url
from pyodide.http import open_url
MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"
MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"
class App(param.Parameterized):
class App(param.Parameterized):
data = param.DataFrame(precedence=-1)
data = param.DataFrame(precedence=-1)
view = param.DataFrame(precedence=-1)
view = param.DataFrame(precedence=-1)
arc_view = param.DataFrame(precedence=-1)
arc_view = param.DataFrame(precedence=-1)
radius = param.Integer(default=50, bounds=(20, 1000))
radius = param.Integer(default=50, bounds=(20, 1000))
elevation = param.Integer(default=10, bounds=(0, 50))
elevation = param.Integer(default=10, bounds=(0, 50))
hour = param.Integer(default=0, bounds=(0, 23))
hour = param.Integer(default=0, bounds=(0, 23))
speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)
speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)
play = param.Event(label='▷')
play = param.Event(label='▷')
def __init__(self, **params):
self.deck_gl = None
super().__init__(**params)
self.deck_gl = pn.pane.DeckGL(
dict(self.spec),
mapbox_api_key=MAPBOX_KEY,
throttle={'click': 10},
sizing_mode='stretch_both',
margin=0
)
self.deck_gl.param.watch(self._update_arc_view, 'click_state')
self._playing = False
self._cb = pn.state.add_periodic_callback(
self._update_hour, 1000//self.speed, start=False
)
def __init__(self, **params):
self.deck_gl = None
super().__init__(**params)
self.deck_gl = pn.pane.DeckGL(
dict(self.spec),
mapbox_api_key=MAPBOX_KEY,
throttle={'click': 10},
sizing_mode='stretch_both',
margin=0
)
self.deck_gl.param.watch(self._update_arc_view, 'click_state')
self._playing = False
self._cb = pn.state.add_periodic_callback(
self._update_hour, 1000//self.speed, start=False
)
@property
def spec(self):
return {
"initialViewState": {
"bearing": 0,
"latitude": 40.7,
"longitude": -73.9,
"maxZoom": 15,
"minZoom": 5,
"pitch": 40.5,
"zoom": 11
},
"layers": [self.hex_layer, self.arc_layer],
"mapStyle": "mapbox://styles/mapbox/dark-v9",
"views": [
{"@@type": "MapView", "controller": True}
]
}
@property
def spec(self):
return {
"initialViewState": {
"bearing": 0,
"latitude": 40.7,
"longitude": -73.9,
"maxZoom": 15,
"minZoom": 5,
"pitch": 40.5,
"zoom": 11
},
"layers": [self.hex_layer, self.arc_layer],
"mapStyle": "mapbox://styles/mapbox/dark-v9",
"views": [
{"@@type": "MapView", "controller": True}
]
}
@property
def hex_layer(self):
return {
"@@type": "HexagonLayer",
"autoHighlight": True,
"coverage": 1,
"data": self.data if self.view is None else self.view,
"elevationRange": [0, 100],
"elevationScale": self.elevation,
"radius": self.radius,
"extruded": True,
"getPosition": "@@=[pickup_x, pickup_y]",
"id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
}
@property
def hex_layer(self):
return {
"@@type": "HexagonLayer",
"autoHighlight": True,
"coverage": 1,
"data": self.data if self.view is None else self.view,
"elevationRange": [0, 100],
"elevationScale": self.elevation,
"radius": self.radius,
"extruded": True,
"getPosition": "@@=[pickup_x, pickup_y]",
"id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
}
@property
def arc_layer(self):
return {
"@@type": "ArcLayer",
"id": 'arc-layer',
"data": self.arc_view,
"pickable": True,
"getWidth": 1,
"getSourcePosition": "@@=[pickup_x, pickup_y]",
"getTargetPosition": "@@=[dropoff_x, dropoff_y]",
"getSourceColor": [0, 255, 0, 180],
"getTargetColor": [240, 100, 0, 180]
}
@property
def arc_layer(self):
return {
"@@type": "ArcLayer",
"id": 'arc-layer',
"data": self.arc_view,
"pickable": True,
"getWidth": 1,
"getSourcePosition": "@@=[pickup_x, pickup_y]",
"getTargetPosition": "@@=[dropoff_x, dropoff_y]",
"getSourceColor": [0, 255, 0, 180],
"getTargetColor": [240, 100, 0, 180]
}
def _update_hour(self):
self.hour = (self.hour+1) % 24
def _update_hour(self):
self.hour = (self.hour+1) % 24
@param.depends('view', watch=True)
def _update_arc_view(self, event=None):
data = self.data if self.view is None else self.view
if not self.deck_gl or not self.deck_gl.click_state:
self.arc_view = data.iloc[:0]
return
lon, lat = self.deck_gl.click_state['coordinate']
tol = 0.001
self.arc_view = data[
(df.pickup_x>=float(lon-tol)) &
(df.pickup_x<=float(lon+tol)) &
(df.pickup_y>=float(lat-tol)) &
(df.pickup_y<=float(lat+tol))
]
@param.depends('view', watch=True)
def _update_arc_view(self, event=None):
data = self.data if self.view is None else self.view
if not self.deck_gl or not self.deck_gl.click_state:
self.arc_view = data.iloc[:0]
return
lon, lat = self.deck_gl.click_state['coordinate']
tol = 0.001
self.arc_view = data[
(df.pickup_x>=float(lon-tol)) &
(df.pickup_x<=float(lon+tol)) &
(df.pickup_y>=float(lat-tol)) &
(df.pickup_y<=float(lat+tol))
]
@param.depends('hour', watch=True, on_init=True)
def _update_hourly_view(self):
self.view = self.data[self.data.hour==self.hour]
@param.depends('hour', watch=True, on_init=True)
def _update_hourly_view(self):
self.view = self.data[self.data.hour==self.hour]
@param.depends('speed', watch=True)
def _update_speed(self):
self._cb.period = 1000//self.speed
@param.depends('speed', watch=True)
def _update_speed(self):
self._cb.period = 1000//self.speed
@param.depends('play', watch=True)
def _play_pause(self):
if self._playing:
self._cb.stop()
self.param.play.label = '▷'
self.param.speed.precedence = -1
else:
self._cb.start()
self.param.play.label = '❚❚'
self.param.speed.precedence = 1
self._playing = not self._playing
@param.depends('play', watch=True)
def _play_pause(self):
if self._playing:
self._cb.stop()
self.param.play.label = '▷'
self.param.speed.precedence = -1
else:
self._cb.start()
self.param.play.label = '❚❚'
self.param.speed.precedence = 1
self._playing = not self._playing
@param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
def update_spec(self):
self.deck_gl.object = dict(self.spec)
@param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
def update_spec(self):
self.deck_gl.object = dict(self.spec)
url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
df = pd.read_csv(open_url(url))
app = App(data=df)
controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)
url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
df = pd.read_csv(open_url(url))
app = App(data=df)
controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)
app.deck_gl.servable(target='plot')
controls.servable(target='widgets');
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
import pandas as pd
import param
from pyodide.http import open_url
MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"
class App(param.Parameterized):
data = param.DataFrame(precedence=-1)
view = param.DataFrame(precedence=-1)
arc_view = param.DataFrame(precedence=-1)
radius = param.Integer(default=50, bounds=(20, 1000))
elevation = param.Integer(default=10, bounds=(0, 50))
hour = param.Integer(default=0, bounds=(0, 23))
speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)
play = param.Event(label='▷')
def __init__(self, **params):
self.deck_gl = None
super().__init__(**params)
self.deck_gl = pn.pane.DeckGL(
dict(self.spec),
mapbox_api_key=MAPBOX_KEY,
throttle={'click': 10},
sizing_mode='stretch_both',
margin=0
)
self.deck_gl.param.watch(self._update_arc_view, 'click_state')
self._playing = False
self._cb = pn.state.add_periodic_callback(
self._update_hour, 1000//self.speed, start=False
)
@property
def spec(self):
return {
"initialViewState": {
"bearing": 0,
"latitude": 40.7,
"longitude": -73.9,
"maxZoom": 15,
"minZoom": 5,
"pitch": 40.5,
"zoom": 11
},
"layers": [self.hex_layer, self.arc_layer],
"mapStyle": "mapbox://styles/mapbox/dark-v9",
"views": [
{"@@type": "MapView", "controller": True}
]
}
@property
def hex_layer(self):
return {
"@@type": "HexagonLayer",
"autoHighlight": True,
"coverage": 1,
"data": self.data if self.view is None else self.view,
"elevationRange": [0, 100],
"elevationScale": self.elevation,
"radius": self.radius,
"extruded": True,
"getPosition": "@@=[pickup_x, pickup_y]",
"id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
}
@property
def arc_layer(self):
return {
"@@type": "ArcLayer",
"id": 'arc-layer',
"data": self.arc_view,
"pickable": True,
"getWidth": 1,
"getSourcePosition": "@@=[pickup_x, pickup_y]",
"getTargetPosition": "@@=[dropoff_x, dropoff_y]",
"getSourceColor": [0, 255, 0, 180],
"getTargetColor": [240, 100, 0, 180]
}
def _update_hour(self):
self.hour = (self.hour+1) % 24
@param.depends('view', watch=True)
def _update_arc_view(self, event=None):
data = self.data if self.view is None else self.view
if not self.deck_gl or not self.deck_gl.click_state:
self.arc_view = data.iloc[:0]
return
lon, lat = self.deck_gl.click_state['coordinate']
tol = 0.001
self.arc_view = data[
(df.pickup_x>=float(lon-tol)) &
(df.pickup_x<=float(lon+tol)) &
(df.pickup_y>=float(lat-tol)) &
(df.pickup_y<=float(lat+tol))
]
@param.depends('hour', watch=True, on_init=True)
def _update_hourly_view(self):
self.view = self.data[self.data.hour==self.hour]
@param.depends('speed', watch=True)
def _update_speed(self):
self._cb.period = 1000//self.speed
@param.depends('play', watch=True)
def _play_pause(self):
if self._playing:
self._cb.stop()
self.param.play.label = '▷'
self.param.speed.precedence = -1
else:
self._cb.start()
self.param.play.label = '❚❚'
self.param.speed.precedence = 1
self._playing = not self._playing
@param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
def update_spec(self):
self.deck_gl.object = dict(self.spec)
url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
df = pd.read_csv(open_url(url))
app = App(data=df)
controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)
app.deck_gl.servable(target='plot')
controls.servable(target='widgets')
&lt;/py-script&gt;
</code>
</pre>
</div>
app.deck_gl.servable(target='plot')
controls.servable(target='widgets');
</py-script>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -42,12 +42,10 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
@@ -70,211 +68,98 @@
<div class="bk-root" id="table"></div>
</div>
</div>
</div>
<py-tutor>
<py-config>
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-config>
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
</py-config>
<py-script>
import altair as alt
import panel as pn
import pandas as pd
<py-script>
import altair as alt
import panel as pn
import pandas as pd
from sklearn.cluster import KMeans
from pyodide.http import open_url
from sklearn.cluster import KMeans
from pyodide.http import open_url
pn.config.sizing_mode = 'stretch_width'
pn.config.sizing_mode = 'stretch_width'
url = 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv'
penguins = pd.read_csv(open_url(url)).dropna()
cols = list(penguins.columns)[2:6]
url = 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv'
penguins = pd.read_csv(open_url(url)).dropna()
cols = list(penguins.columns)[2:6]
x = pn.widgets.Select(name='x', options=cols, value='bill_depth_mm').servable(target='x-widget')
y = pn.widgets.Select(name='y', options=cols, value='bill_length_mm').servable(target='y-widget')
n_clusters = pn.widgets.IntSlider(name='n_clusters', start=1, end=5, value=3).servable(target='n-widget')
x = pn.widgets.Select(name='x', options=cols, value='bill_depth_mm').servable(target='x-widget')
y = pn.widgets.Select(name='y', options=cols, value='bill_length_mm').servable(target='y-widget')
n_clusters = pn.widgets.IntSlider(name='n_clusters', start=1, end=5, value=3).servable(target='n-widget')
brush = alt.selection_interval(name='brush') # selection of type "interval"
brush = alt.selection_interval(name='brush') # selection of type "interval"
def get_clusters(n_clusters):
kmeans = KMeans(n_clusters=n_clusters)
est = kmeans.fit(penguins[cols].values)
df = penguins.copy()
df['labels'] = est.labels_.astype('str')
return df
def get_clusters(n_clusters):
kmeans = KMeans(n_clusters=n_clusters)
est = kmeans.fit(penguins[cols].values)
df = penguins.copy()
df['labels'] = est.labels_.astype('str')
return df
def get_chart(x, y, df):
centers = df.groupby('labels').mean()
return (
alt.Chart(df)
.mark_point(size=100)
.encode(
x=alt.X(x, scale=alt.Scale(zero=False)),
y=alt.Y(y, scale=alt.Scale(zero=False)),
shape='labels',
color='species'
).add_selection(brush).properties(width=800) +
alt.Chart(centers)
.mark_point(size=250, shape='cross', color='black')
.encode(x=x+':Q', y=y+':Q')
)
def get_chart(x, y, df):
centers = df.groupby('labels').mean()
return (
alt.Chart(df)
.mark_point(size=100)
.encode(
x=alt.X(x, scale=alt.Scale(zero=False)),
y=alt.Y(y, scale=alt.Scale(zero=False)),
shape='labels',
color='species'
).add_selection(brush).properties(width=800) +
alt.Chart(centers)
.mark_point(size=250, shape='cross', color='black')
.encode(x=x+':Q', y=y+':Q')
)
intro = pn.pane.Markdown("""
This app provides an example of **building a simple dashboard using
Panel**.\n\nIt demonstrates how to take the output of **k-means
clustering on the Penguins dataset** using scikit-learn,
parameterizing the number of clusters and the variables to
plot.\n\nThe plot and the table are linked, i.e. selecting on the plot
will filter the data in the table.\n\n The **`x` marks the center** of
the cluster.
""").servable(target='intro')
intro = pn.pane.Markdown("""
This app provides an example of **building a simple dashboard using
Panel**.\n\nIt demonstrates how to take the output of **k-means
clustering on the Penguins dataset** using scikit-learn,
parameterizing the number of clusters and the variables to
plot.\n\nThe plot and the table are linked, i.e. selecting on the plot
will filter the data in the table.\n\n The **`x` marks the center** of
the cluster.
""").servable(target='intro')
chart = pn.pane.Vega().servable(target='cluster-plot')
table = pn.widgets.Tabulator(pagination='remote', page_size=10).servable(target='table')
chart = pn.pane.Vega().servable(target='cluster-plot')
table = pn.widgets.Tabulator(pagination='remote', page_size=10).servable(target='table')
def update_table(event=None):
table.value = get_clusters(n_clusters.value)
def update_table(event=None):
table.value = get_clusters(n_clusters.value)
n_clusters.param.watch(update_table, 'value')
n_clusters.param.watch(update_table, 'value')
@pn.depends(x, y, n_clusters, watch=True)
def update_chart(*events):
chart.object = get_chart(x.value, y.value, table.value)
chart.selection.param.watch(update_filters, 'brush')
@pn.depends(x, y, n_clusters, watch=True)
def update_chart(*events):
chart.object = get_chart(x.value, y.value, table.value)
chart.selection.param.watch(update_filters, 'brush')
def update_filters(event=None):
filters = []
for k, v in (getattr(event, 'new') or {}).items():
filters.append(dict(field=k, type='>=', value=v[0]))
filters.append(dict(field=k, type='<=', value=v[1]))
table.filters = filters
def update_filters(event=None):
filters = []
for k, v in (getattr(event, 'new') or {}).items():
filters.append(dict(field=k, type='>=', value=v[0]))
filters.append(dict(field=k, type='<=', value=v[1]))
table.filters = filters
update_table()
update_chart()
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import altair as alt
import panel as pn
import pandas as pd
from sklearn.cluster import KMeans
from pyodide.http import open_url
pn.config.sizing_mode = 'stretch_width'
url = 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv'
penguins = pd.read_csv(open_url(url)).dropna()
cols = list(penguins.columns)[2:6]
x = pn.widgets.Select(name='x', options=cols, value='bill_depth_mm').servable(target='x-widget')
y = pn.widgets.Select(name='y', options=cols, value='bill_length_mm').servable(target='y-widget')
n_clusters = pn.widgets.IntSlider(name='n_clusters', start=1, end=5, value=3).servable(target='n-widget')
brush = alt.selection_interval(name='brush') # selection of type "interval"
def get_clusters(n_clusters):
kmeans = KMeans(n_clusters=n_clusters)
est = kmeans.fit(penguins[cols].values)
df = penguins.copy()
df['labels'] = est.labels_.astype('str')
return df
def get_chart(x, y, df):
centers = df.groupby('labels').mean()
return (
alt.Chart(df)
.mark_point(size=100)
.encode(
x=alt.X(x, scale=alt.Scale(zero=False)),
y=alt.Y(y, scale=alt.Scale(zero=False)),
shape='labels',
color='species'
).add_selection(brush).properties(width=800) +
alt.Chart(centers)
.mark_point(size=250, shape='cross', color='black')
.encode(x=x+':Q', y=y+':Q')
)
intro = pn.pane.Markdown("""
This app provides an example of **building a simple dashboard using
Panel**.\n\nIt demonstrates how to take the output of **k-means
clustering on the Penguins dataset** using scikit-learn,
parameterizing the number of clusters and the variables to
plot.\n\nThe plot and the table are linked, i.e. selecting on the plot
will filter the data in the table.\n\n The **`x` marks the center** of
the cluster.
""").servable(target='intro')
chart = pn.pane.Vega().servable(target='cluster-plot')
table = pn.widgets.Tabulator(pagination='remote', page_size=10).servable(target='table')
def update_table(event=None):
table.value = get_clusters(n_clusters.value)
n_clusters.param.watch(update_table, 'value')
@pn.depends(x, y, n_clusters, watch=True)
def update_chart(*events):
chart.object = get_chart(x.value, y.value, table.value)
chart.selection.param.watch(update_filters, 'brush')
def update_filters(event=None):
filters = []
for k, v in (getattr(event, 'new') or {}).items():
filters.append(dict(field=k, type='>=', value=v[0]))
filters.append(dict(field=k, type='<=', value=v[1]))
table.filters = filters
update_table()
update_chart()
&lt;/py-script&gt;
</code>
</pre>
</div>
update_table()
update_chart()
</py-script>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
<script>
$(document).ready(function () {
$('#sidebarCollapse').on('click', function () {

View File

@@ -33,8 +33,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -56,148 +54,68 @@
</div>
</div>
</div>
</div>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<py-tutor>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
</py-config>
<py-script>
import panel as pn
import numpy as np
import pandas as pd
<py-script>
import panel as pn
import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
df = pd.DataFrame(np.random.randn(10, 4), columns=list('ABCD')).cumsum()
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')
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).servable(target='table')
tabulator = pn.widgets.Tabulator(df, height=450, width=400).servable(target='table')
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
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)
tabulator.style.applymap(color_negative_red)
p = figure(height=450, width=600)
p = figure(height=450, width=600)
cds = ColumnDataSource(data=ColumnDataSource.from_df(df))
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')
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)
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)
cb = pn.state.add_periodic_callback(stream, 200)
pn.pane.Bokeh(p).servable(target='plot')
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
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).servable(target='table')
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)
pn.pane.Bokeh(p).servable(target='plot')
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
&lt;/py-script&gt;
</code>
</pre>
</div>
pn.pane.Bokeh(p).servable(target='plot')
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
</py-script>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,8 +10,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -24,100 +22,21 @@
</div>
</nav>
<section class="pyscript">
<h1><b>PyScript REPL</b></h1>
Tip: press Shift-ENTER to evaluate a cell
<br>
<py-config>
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</div>
<h1><b>PyScript REPL</b></h1>
Tip: press Shift-ENTER to evaluate a cell
<br>
<py-tutor modules="antigravity.py">
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</div>
</py-tutor>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = [&quot;./antigravity.py&quot;]
&lt;/py-config&gt;
&lt;div&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
&lt;/div&gt;
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,8 +10,6 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="repl.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
@@ -26,123 +24,24 @@
</nav>
<section class="pyscript">
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
<py-config>
packages = [
"bokeh",
"numpy"
]
<py-tutor modules="antigravity.py">
<py-config>
packages = [
"bokeh",
"numpy"
]
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./utils.py", "./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
<div id="output" class="p-4"></div>
</div>
[[fetch]]
files = ["./utils.py", "./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
<div id="output" class="p-4"></div>
</div>
</py-tutor>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
&lt;div id=&quot;output&quot; class=&quot;p-4&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -11,8 +11,6 @@
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -25,19 +23,24 @@
</div>
</nav>
<section class="pyscript">
<div class="font-mono">start time: <label id="outputDiv"></label></div>
<div id="outputDiv2" class="font-mono"></div>
<div id="outputDiv3" class="font-mono"></div>
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-script>
<div class="font-mono">start time: <label id="outputDiv"></label></div>
<div id="outputDiv2" class="font-mono"></div>
<div id="outputDiv3" class="font-mono"></div>
<py-tutor modules="utils.py">
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-script>
import utils
display(utils.now())
</py-script>
</py-script>
<py-script>
<py-script>
from utils import now
import asyncio
@@ -54,79 +57,8 @@ async def foo():
out3.clear()
pyscript.run_until_complete(foo())
</py-script>
</py-script>
</py-tutor>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div class=&quot;font-mono&quot;&gt;start time: &lt;label id=&quot;outputDiv&quot;&gt;&lt;/label&gt;&lt;/div&gt;
&lt;div id=&quot;outputDiv2&quot; class=&quot;font-mono&quot;&gt;&lt;/div&gt;
&lt;div id=&quot;outputDiv3&quot; class=&quot;font-mono&quot;&gt;&lt;/div&gt;
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
from utils import now
import asyncio
async def foo():
while True:
await asyncio.sleep(1)
output = now()
Element("outputDiv2").write(output)
out3 = Element("outputDiv3")
if output[-1] in ["0", "4", "8"]:
out3.write("It's espresso time!")
else:
out3.clear()
pyscript.run_until_complete(foo())
&lt;/py-script&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,8 +10,6 @@
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -25,146 +23,55 @@
</nav>
<section class="pyscript">
<h1>To Do List</h1>
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
<py-tutor modules="utils.py;pylist.py">
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./utils.py", "./pylist.py"]
</py-config>
<py-script>
from js import document
from pyodide.ffi.wrappers import add_event_listener
<py-script>
from js import document
from pyodide.ffi.wrappers import add_event_listener
def add_task(*ags, **kws):
# create a new dictionary representing the new task
new_task_content = Element("new-task-content")
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
def add_task(*ags, **kws):
# create a new dictionary representing the new task
new_task_content = Element("new-task-content")
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
# add a new task to the list and tell it to use the `content` key to show in the UI
# and to use the key `done` to sync the task status with a checkbox element in the UI
myList.add(task)
# add a new task to the list and tell it to use the `content` key to show in the UI
# and to use the key `done` to sync the task status with a checkbox element in the UI
myList.add(task)
# clear the inputbox element used to create the new task
new_task_content.clear()
# clear the inputbox element used to create the new task
new_task_content.clear()
def on_click(evt):
add_task()
def on_click(evt):
add_task()
def handle_keypress(evt):
if evt.key == "Enter":
add_task()
def handle_keypress(evt):
if evt.key == "Enter":
add_task()
add_event_listener(
document.getElementById("new-task-content"),
"keypress",
handle_keypress
)
add_event_listener(
document.getElementById("new-task-content"),
"keypress",
handle_keypress
)
</py-script>
<div class="py-box">
<input id="new-task-content" />
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
</div>
</py-script>
<div class="py-box">
<input id="new-task-content" />
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
</div>
<py-list id="myList"></py-list>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-register-widget src=&quot;./pylist.py&quot; name=&quot;py-list&quot; klass=&quot;PyList&quot;&gt;&lt;/py-register-widget&gt;
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
from js import document
from pyodide.ffi.wrappers import add_event_listener
def add_task(*ags, **kws):
# create a new dictionary representing the new task
new_task_content = Element("new-task-content")
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
# add a new task to the list and tell it to use the `content` key to show in the UI
# and to use the key `done` to sync the task status with a checkbox element in the UI
myList.add(task)
# clear the inputbox element used to create the new task
new_task_content.clear()
def on_click(evt):
add_task()
def handle_keypress(evt):
if evt.key == "Enter":
add_task()
add_event_listener(
document.getElementById("new-task-content"),
"keypress",
handle_keypress
)
&lt;/py-script&gt;
&lt;div class=&quot;py-box&quot;&gt;
&lt;input id=&quot;new-task-content&quot; /&gt;
&lt;button py-click=&quot;add_task()&quot; id=&quot;new-task-btn&quot; class=&quot;py-button&quot;&gt;Add Task!&lt;/button&gt;
&lt;/div&gt;
&lt;py-list id=&quot;myList&quot;&gt;&lt;/py-list&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
</code>
</pre>
<p>pylist.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import pyscript
from datetime import datetime as dt
class PyItem(pyscript.PyItemTemplate):
def on_click(self, evt=None):
self.data["done"] = not self.data["done"]
self.strike(self.data["done"])
self.select("input").element.checked = self.data["done"]
class PyList(pyscript.PyListTemplate):
item_class = PyItem
def add(self, item):
if isinstance(item, str):
item = {"content": item, "done": False, "created_at": dt.now()}
super().add(item, labels=["content"], state_key="done")
</code>
</pre>
</div>
<py-list id="myList"></py-list>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</py-tutor>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -11,8 +11,6 @@
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
@@ -25,184 +23,43 @@
</div>
</nav>
<section class="pyscript">
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-tutor modules="./utils.py;./todo.py">
<py-config>
plugins = [
"../build/plugins/python/py_tutor.py"
]
[[fetch]]
files = ["./utils.py", "./todo.py"]
</py-config>
<py-script src="./todo.py"> </py-script>
<py-script src="./todo.py"> </py-script>
<main>
<section>
<main>
<section>
<div class="text-center w-full mb-8">
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
</div>
<div>
<input id="new-task-content" class="py-input" type="text">
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
Add task
</button>
</div>
<div class="text-center w-full mb-8">
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
</div>
<div>
<input id="new-task-content" class="py-input" type="text">
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
Add task
</button>
</div>
<py-list id="myList"></py-list>
<div id="list-tasks-container" class="flex flex-col-reverse mt-4">
</div>
<py-list id="myList"></py-list>
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
<template id="task-template">
<section class="task py-li-element">
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox">
<p class="m-0 inline"></p>
</label>
<template id="task-template">
<section class="task py-li-element">
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox">
<p class="m-0 inline"></p>
</label>
</section>
</template>
</section>
</template>
</section>
</main>
</main>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script src="./todo.py"&gt;
&lt;/py-script&gt;
&lt;main&gt;
&lt;section&gt;
&lt;div class=&quot;text-center w-full mb-8&quot;&gt;
&lt;h1 class=&quot;text-3xl font-bold text-gray-800 uppercase tracking-tight&quot;&gt;To Do List&lt;/h1&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;input id=&quot;new-task-content&quot; class=&quot;py-input&quot; type=&quot;text&quot;&gt;
&lt;button id=&quot;new-task-btn&quot; class=&quot;py-button&quot; type=&quot;submit&quot; py-click=&quot;add_task()&quot;&gt;
Add task
&lt;/button&gt;
&lt;/div&gt;
&lt;py-list id=&quot;myList&quot;&gt;&lt;/py-list&gt;
&lt;div id=&quot;list-tasks-container&quot; class=&quot;flex flex-col-reverse mt-4&quot;&gt;
&lt;/div&gt;
&lt;template id=&quot;task-template&quot;&gt;
&lt;section class=&quot;task py-li-element&quot;&gt;
&lt;label for=&quot;flex items-center p-2 &quot;&gt;
&lt;input class=&quot;mr-2&quot; type=&quot;checkbox&quot;&gt;
&lt;p class=&quot;m-0 inline&quot;&gt;&lt;/p&gt;
&lt;/label&gt;
&lt;/section&gt;
&lt;/template&gt;
&lt;/section&gt;
&lt;/main&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
<p>todo.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
from utils import add_class, remove_class
tasks = []
# define the task template that will be use to render new templates to the page
task_template = Element("task-template").select(".task", from_content=True)
task_list = Element("list-tasks-container")
new_task_content = Element("new-task-content")
def add_task(*ags, **kws):
# ignore empty task
if not new_task_content.element.value:
return None
# create task
task_id = f"task-{len(tasks)}"
task = {
"id": task_id,
"content": new_task_content.element.value,
"done": False,
"created_at": dt.now(),
}
tasks.append(task)
# add the task element to the page as new node in the list by cloning from a
# template
task_html = task_template.clone(task_id)
task_html_content = task_html.select("p")
task_html_content.element.innerText = task["content"]
task_html_check = task_html.select("input")
task_list.element.appendChild(task_html.element)
def check_task(evt=None):
task["done"] = not task["done"]
if task["done"]:
add_class(task_html_content, "line-through")
else:
remove_class(task_html_content, "line-through")
new_task_content.clear()
task_html_check.element.onclick = check_task
def add_task_event(e):
if e.key == "Enter":
add_task()
new_task_content.element.onkeypress = add_task_event
</code>
</pre>
</div>
</section>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -0,0 +1,215 @@
import html
from pyscript import Plugin, js
js.console.warn(
"WARNING: This plugin is still in a very experimental phase and will likely change"
" and potentially break in the future releases. Use it with caution."
)
plugin = Plugin("PyTutorial")
# TODO: Part of the CSS is hidden in examples.css ---->> IMPORTANT: move it here!!
# TODO: Python files running and <py-script src="bla.py"> not in the config are not available...
# TODO: We can totally implement this in Python
PAGE_SCRIPT = """
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
"""
TEMPLATE_CODE_SECTION = """
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
{source}
</code>
</pre>
{modules_section}
</div>
"""
TEMPLATE_PY_MODULE_SECTION = """
<p>{module_name}</p>
<pre class="prism-code language-python">
<code class="language-python">
{source}
</code>
</pre>
"""
@plugin.register_custom_element("py-tutor")
class PyTutor:
def __init__(self, element):
self.element = element
def append_script_to_page(self):
"""
Append the JS script (PAGE_SCRIPT) to the page body in order to attach the
click and keydown events to show/hide the source code section on the page.
"""
el = js.document.createElement("script")
el.type = "text/javascript"
try:
el.appendChild(js.document.createTextNode(PAGE_SCRIPT))
except BaseException:
el.text = PAGE_SCRIPT
js.document.body.appendChild(el)
def add_prism(self):
# Add The CSS
link = js.document.createElement("link")
link.type = "text/css"
link.rel = "stylesheet"
js.document.head.appendChild(link)
link.href = "./assets/prism/prism.css"
# Add the JS file
script = js.document.createElement("script")
script.type = "text/javascript"
try:
script.appendChild(js.document.createTextNode(PAGE_SCRIPT))
except BaseException:
script.text = PAGE_SCRIPT
script.src = "./assets/prism/prism.js"
js.document.head.appendChild(script)
def _create_code_section(self, source, module_paths=None, parent=None):
"""
Get source and the path to modules to be displayed, create a new `code`
`section` where it's contents use TEMPLATE_CODE_SECTION with `source` and
`modules_paths` to display the information it needs.
Args:
source (str): source within a <py-tutor> tag that needs to be displaed
module_paths (list(str)): list of paths to modules that needs to be shown
parent(HTMLElement, optional): Element where the code section will be appended
to. I None is passed parent == document.body.
Defaults to None.
Returns:
(None)
"""
if not parent:
parent = js.document.body
js.console.info("Creating code introspection section.")
modules_section = self.create_modules_section(module_paths)
js.console.info("Creating new code section element.")
el = js.document.createElement("section")
el.classList.add("code")
el.innerHTML = TEMPLATE_CODE_SECTION.format(
source=source, modules_section=modules_section
)
parent.appendChild(el)
@classmethod
def create_modules_section(cls, module_paths=None):
"""Create the HTML content for all modules passed in `module_paths`. More specifically,
reads the content of each module and calls PyTytor.create_module_section
Args:
module_paths (list(str)): list of paths to modules that needs to be shown
Returns:
(str) HTML code with the content of each module in `module_path`, ready to be
attached to the DOM
"""
js.console.info(f"Module paths to parse: {module_paths}")
if not module_paths:
return ""
return "\n\n".join([cls.create_module_section(m) for m in module_paths])
@staticmethod
def create_module_section(module_path):
"""Create the HTML content for the module passed as `module_path`.
More specifically, reads the content of module and calls PyTytor.create_module_section
Args:
module_paths (list(str)): list of paths to modules that needs to be shown
Returns:
(str) HTML code with the content of each module in `module_path`, ready to be
attached to the DOM
"""
js.console.info(f"Creating module section: {module_path}")
with open(module_path) as fp:
content = fp.read()
return TEMPLATE_PY_MODULE_SECTION.format(
module_name=module_path, source=content
)
def create_page_code_section(self):
"""
Create all the code content to be displayed on a page. More specifically:
* get the HTML code within the <py-tutor> tag
* get the source code from all files specified in the py-tytor `modules` attribute
* create the HTML to be attached on the page using the content created in
the previous 2 items and apply them to TEMPLATE_CODE_SECTION
Returns:
(None)
"""
# Get the content of all the modules that were passed to be documented
module_paths = self.element.getAttribute("modules")
if module_paths:
js.console.info(f"Module paths detected: {module_paths}")
module_paths = str(module_paths).split(";")
# Get the inner HTML content of the py-tutor tag and document that
tutor_tag_innerHTML = html.escape(self.element.innerHTML)
self._create_code_section(tutor_tag_innerHTML, module_paths)
def connect(self):
"""
Handler meant to be called when the Plugin CE (Custom Element) is attached
to the page.
As so, it's the entry point that coordinates the whole plugin workflow and
is responsible for calling the right steps in order:
* identify what parts of the App (page) that are within the py-tutor tag
to be documented as well as any modules specified as attribute
* inject the button to show/hide button and related modal
* inject the JS code that attaches the click event to the button
* build the modal that shows/hides with the correct page/modules code
"""
# Create the core do show the source code on the page
self.create_page_code_section()
# append the script needed to show source first...
self.append_script_to_page()
# inject the prism JS library dependency
self.add_prism()

View File

@@ -303,6 +303,72 @@ class PyScriptTest:
text = "\n".join(loc.all_inner_texts())
raise AssertionError(f"Found {n} alert banners:\n" + text)
def check_tutor_generated_code(self, modules_to_check=None):
"""
Ensure that the source code viewer injected by the PyTutor plugin
is presend. Raise AssertionError if not found.
Args:
modules_to_check(str): iterable with names of the python modules
that have been included in the tutor config
and needs to be checked (if they are included
in the displayed source code)
Returns:
None
"""
# Given: a page that has a <py-tutor> tag
assert self.page.locator("py-tutor").count()
# EXPECT that"
#
# the page has the "view-code-button"
view_code_button = self.page.locator("#view-code-button")
vcb_count = view_code_button.count()
if vcb_count != 1:
raise AssertionError(
f"Found {vcb_count} code view button. Should have been 1!"
)
# the page has the code-section element
code_section = self.page.locator("#code-section")
code_section_count = code_section.count()
code_msg = (
f"One (and only one) code section should exist. Found: {code_section_count}"
)
assert code_section_count == 1, code_msg
pyconfig_tag = self.page.locator("py-config")
code_section_inner_html = code_section.inner_html()
# the code_section has the index.html section
assert "<p>index.html</p>" in code_section_inner_html
# the section has the tags highlighting the HTML code
assert (
'<pre class="prism-code language-html" tabindex="0">'
' <code class="language-html">' in code_section_inner_html
)
# if modules were included, these are also presented in the code section
if modules_to_check:
for module in modules_to_check:
assert f"{module}" in code_section_inner_html
# the section also includes the config
assert "&lt;</span>py-config</span>" in code_section_inner_html
# the contents of the py-config tag are included in the code section
assert pyconfig_tag.inner_html() in code_section_inner_html
# the code section to be invisible by default (by having the hidden class)
assert "code-section-hidden" in code_section.get_attribute("class")
# once the view_code_button is pressed, the code section becomes visible
view_code_button.click()
assert "code-section-visible" in code_section.get_attribute("class")
# ============== Helpers and utility functions ==============

View File

@@ -61,6 +61,7 @@ class TestExamples(PyScriptTest):
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
assert re.search(pattern, content)
self.assert_no_banners()
self.check_tutor_generated_code()
def test_simple_clock(self):
self.goto("examples/simple_clock.html")
@@ -79,6 +80,7 @@ class TestExamples(PyScriptTest):
else:
assert False, "Espresso time not found :("
self.assert_no_banners()
self.check_tutor_generated_code()
def test_altair(self):
self.goto("examples/altair.html")
@@ -97,6 +99,7 @@ class TestExamples(PyScriptTest):
assert save_as_png_link.is_visible()
assert see_source_link.is_visible()
self.assert_no_banners()
self.check_tutor_generated_code()
def test_antigravity(self):
self.goto("examples/antigravity.html")
@@ -120,6 +123,7 @@ class TestExamples(PyScriptTest):
re.match(ycoord_pattern, char.get_attribute("transform")).group("ycoord")
)
assert later_y_coord < starting_y_coord
self.check_tutor_generated_code(modules_to_check=["antigravity.py"])
def test_bokeh(self):
# XXX improve this test
@@ -128,6 +132,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "Bokeh Example"
wait_for_render(self.page, "*", '<div.*class=\\"bk\\".*>')
self.assert_no_banners()
self.check_tutor_generated_code()
def test_bokeh_interactive(self):
# XXX improve this test
@@ -136,6 +141,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "Bokeh Example"
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
self.assert_no_banners()
self.check_tutor_generated_code()
@pytest.mark.skip("flaky, see issue 759")
def test_d3(self):
@@ -152,6 +158,7 @@ class TestExamples(PyScriptTest):
# means that the chart rendered successfully and with the right text
assert "🍊21\n🍇13\n🍏8\n🍌5\n🍐3\n🍋2\n🍎1\n🍉1" in pyscript_chart.inner_text()
self.assert_no_banners()
self.check_tutor_generated_code(modules_to_check=["d3.py"])
def test_folium(self):
self.goto("examples/folium.html")
@@ -175,6 +182,7 @@ class TestExamples(PyScriptTest):
assert "" in zoom_out.inner_text()
zoom_out.click()
self.assert_no_banners()
self.check_tutor_generated_code()
def test_markdown_plugin(self):
# Given the example page with:
@@ -186,6 +194,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "PyMarkdown"
# ASSERT markdown is rendered to the corresponding HTML tag
wait_for_render(self.page, "*", "<h1>Hello world!</h1>")
self.check_tutor_generated_code()
def test_matplotlib(self):
self.goto("examples/matplotlib.html")
@@ -213,6 +222,7 @@ class TestExamples(PyScriptTest):
deviation = np.mean(np.abs(img_data - ref_data))
assert deviation == 0.0
self.assert_no_banners()
self.check_tutor_generated_code()
def test_numpy_canvas_fractals(self):
self.goto("examples/numpy_canvas_fractals.html")
@@ -260,6 +270,7 @@ class TestExamples(PyScriptTest):
# Confirm that changing the input values, triggered a new computation
assert self.console.log.lines[-1] == "Computing Newton set ..."
self.assert_no_banners()
self.check_tutor_generated_code()
def test_panel(self):
self.goto("examples/panel.html")
@@ -278,6 +289,7 @@ class TestExamples(PyScriptTest):
# Let's confirm that slider title changed
assert slider_title.inner_text() == "Amplitude: 5"
self.assert_no_banners()
self.check_tutor_generated_code()
def test_panel_deckgl(self):
# XXX improve this test
@@ -286,6 +298,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "PyScript/Panel DeckGL Demo"
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
self.assert_no_banners()
self.check_tutor_generated_code()
def test_panel_kmeans(self):
# XXX improve this test
@@ -294,6 +307,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "Pyscript/Panel KMeans Demo"
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
self.assert_no_banners()
self.check_tutor_generated_code()
def test_panel_stream(self):
# XXX improve this test
@@ -302,6 +316,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "PyScript/Panel Streaming Demo"
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
self.assert_no_banners()
self.check_tutor_generated_code()
def test_repl(self):
self.goto("examples/repl.html")
@@ -322,6 +337,7 @@ class TestExamples(PyScriptTest):
assert self.page.wait_for_selector("#my-repl-2-2", state="attached")
assert self.page.locator("#my-repl-2-2").text_content() == "4"
self.assert_no_banners()
self.check_tutor_generated_code(modules_to_check=["antigravity.py"])
def test_repl2(self):
self.goto("examples/repl2.html")
@@ -338,6 +354,7 @@ class TestExamples(PyScriptTest):
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
assert re.search(pattern, content)
self.assert_no_banners()
self.check_tutor_generated_code(modules_to_check=["antigravity.py"])
def test_todo(self):
self.goto("examples/todo.html")
@@ -366,6 +383,7 @@ class TestExamples(PyScriptTest):
in first_task.inner_html()
)
self.assert_no_banners()
self.check_tutor_generated_code(modules_to_check=["./utils.py", "./todo.py"])
def test_todo_pylist(self):
# XXX improve this test
@@ -374,6 +392,7 @@ class TestExamples(PyScriptTest):
assert self.page.title() == "Todo App"
wait_for_render(self.page, "*", "<input.*?id=['\"]new-task-content['\"].*?>")
self.assert_no_banners()
self.check_tutor_generated_code(modules_to_check=["utils.py", "pylist.py"])
@pytest.mark.xfail(reason="To be moved to collective and updated, see issue #686")
def test_toga_freedom(self):
@@ -392,6 +411,7 @@ class TestExamples(PyScriptTest):
result = self.page.locator("#toga_c_input")
assert "40.555" in result.input_value()
self.assert_no_banners()
self.check_tutor_generated_code()
def test_webgl_raycaster_index(self):
# XXX improve this test