mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
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:
@@ -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,12 +18,16 @@
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="altair"></div>
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"pandas",
|
||||
"vega_datasets"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import altair as alt
|
||||
@@ -70,85 +72,7 @@
|
||||
size="independent"
|
||||
), target="altair")
|
||||
</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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"pandas",
|
||||
"vega_datasets"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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")
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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>
|
||||
<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>
|
||||
<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">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import antigravity
|
||||
antigravity.fly()
|
||||
</py-script>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@@ -15,8 +15,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>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
@@ -27,6 +25,7 @@
|
||||
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||
</div>
|
||||
</nav>
|
||||
<py-tutor>
|
||||
<section class="pyscript">
|
||||
<div id="myplot"></div>
|
||||
|
||||
@@ -35,41 +34,12 @@
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script id="main">
|
||||
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))
|
||||
</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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<py-script id="main">
|
||||
import json
|
||||
import pyodide
|
||||
|
||||
@@ -87,29 +57,8 @@ Bokeh.embed.embed_item(JSON.parse(p_json))
|
||||
p_json = json.dumps(json_item(p, "myplot"))
|
||||
|
||||
Bokeh.embed.embed_item(JSON.parse(p_json))
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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,6 +25,7 @@
|
||||
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||
</div>
|
||||
</nav>
|
||||
<py-tutor>
|
||||
<section class="pyscript">
|
||||
<h1>Bokeh Example</h1>
|
||||
<div id="myplot"></div>
|
||||
@@ -36,6 +35,9 @@
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script id="main">
|
||||
@@ -108,109 +110,6 @@ async def show(plot, target):
|
||||
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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<py-script id="main">
|
||||
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'))
|
||||
</py-script>
|
||||
</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>
|
||||
|
||||
218
examples/d3.html
218
examples/d3.html
@@ -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,7 +36,17 @@
|
||||
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
@@ -56,128 +64,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<py-script src="d3.py"></py-script>
|
||||
</section>
|
||||
|
||||
<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 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)
|
||||
}
|
||||
</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">
|
||||
<script>
|
||||
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
|
||||
<script type="module">
|
||||
|
||||
const fruits = [
|
||||
{name: "🍊", count: 21},
|
||||
@@ -229,86 +119,6 @@ for d in data:
|
||||
.attr("dy", "1.3em")
|
||||
.text(d.value)
|
||||
}
|
||||
</script>
|
||||
<py-script>
|
||||
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"]))
|
||||
</py-script>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
64
examples/d3.py
Normal file
64
examples/d3.py
Normal 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"])
|
||||
)
|
||||
@@ -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,11 +19,15 @@
|
||||
<section class="pyscript">
|
||||
<div id="folium"></div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"folium",
|
||||
"pandas"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -61,76 +63,8 @@ folium.LayerControl().add_to(m)
|
||||
|
||||
display(m, target="folium")
|
||||
</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">
|
||||
|
||||
<div id="folium"></div>
|
||||
<py-config>
|
||||
packages = [
|
||||
"folium",
|
||||
"pandas"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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")
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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,6 +21,14 @@
|
||||
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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:
|
||||
@@ -33,39 +38,6 @@
|
||||
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">
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||
</py-script>
|
||||
</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>
|
||||
|
||||
@@ -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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"markdown"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_markdown.py"
|
||||
"../build/plugins/python/py_markdown.py",
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-md>#Hello world!</py-md>
|
||||
</py-tutor>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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,10 +19,14 @@
|
||||
<section class="pyscript">
|
||||
<div id="mpl"></div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"matplotlib"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -62,77 +64,7 @@ ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
display(fig1, target="mpl")
|
||||
</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">
|
||||
<div id="mpl"></div>
|
||||
<py-config>
|
||||
packages = [
|
||||
"matplotlib",
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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")
|
||||
</py-script>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,16 +4,22 @@
|
||||
<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-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"numpy",
|
||||
"networkx",
|
||||
"matplotlib"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -31,13 +37,14 @@ 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>
|
||||
</py-script>
|
||||
</pre>
|
||||
<p>And imagine that we have a message that lives on the graph:</p>
|
||||
<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>
|
||||
|
||||
@@ -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,7 +84,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": [
|
||||
@@ -100,11 +98,14 @@
|
||||
"./fractals.py"
|
||||
]
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"../build/plugins/python/py_tutor.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>
|
||||
</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">
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": [
|
||||
"numpy",
|
||||
"sympy"
|
||||
],
|
||||
"fetch": [
|
||||
{
|
||||
"files": [
|
||||
"./palettes.py",
|
||||
"./fractals.py"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
</py-config>
|
||||
<py-script>
|
||||
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())
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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;">
|
||||
@@ -25,12 +23,16 @@
|
||||
<section class="pyscript">
|
||||
<div id="simple_app"></div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"panel==0.14.1"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -43,52 +45,7 @@
|
||||
|
||||
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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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');
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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,8 +64,8 @@
|
||||
<div class="bk-root" id="plot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
@@ -75,6 +73,9 @@
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -218,185 +219,8 @@
|
||||
app.deck_gl.servable(target='plot')
|
||||
controls.servable(target='widgets');
|
||||
</py-script>
|
||||
</py-tutor>
|
||||
</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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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')
|
||||
</py-script>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -42,8 +42,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>
|
||||
@@ -70,8 +68,7 @@
|
||||
<div class="bk-root" id="table"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
@@ -80,6 +77,9 @@
|
||||
"scikit-learn",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -158,123 +158,8 @@
|
||||
update_table()
|
||||
update_chart()
|
||||
</py-script>
|
||||
</py-tutor>
|
||||
</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">
|
||||
<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
|
||||
|
||||
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()
|
||||
</py-script>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebarCollapse').on('click', function () {
|
||||
|
||||
@@ -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,8 +54,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-tutor>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
@@ -65,6 +63,9 @@
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -114,90 +115,7 @@
|
||||
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">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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')
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -27,97 +25,18 @@
|
||||
<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>
|
||||
</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">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</div>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@@ -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,11 +24,15 @@
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||
<py-tutor modules="antigravity.py">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
|
||||
[[fetch]]
|
||||
files = ["./utils.py", "./antigravity.py"]
|
||||
@@ -39,110 +41,7 @@
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
<div id="output" class="p-4"></div>
|
||||
</div>
|
||||
</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">
|
||||
<div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
<div id="output" class="p-4"></div>
|
||||
</div>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -28,7 +26,12 @@
|
||||
<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>
|
||||
@@ -55,78 +58,7 @@ async def foo():
|
||||
|
||||
pyscript.run_until_complete(foo())
|
||||
</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">
|
||||
<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>
|
||||
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())
|
||||
</py-script>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@@ -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,11 +23,15 @@
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1>To Do List</h1>
|
||||
<py-tutor modules="utils.py;pylist.py">
|
||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||
|
||||
<py-config>
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
files = ["./utils.py", "./pylist.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
@@ -69,102 +71,7 @@
|
||||
|
||||
<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">
|
||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
|
||||
<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() }
|
||||
|
||||
# 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
|
||||
)
|
||||
</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>
|
||||
</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-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>
|
||||
|
||||
@@ -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,9 +23,13 @@
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<py-tutor modules="./utils.py;./todo.py">
|
||||
<py-config>
|
||||
plugins = [
|
||||
"../build/plugins/python/py_tutor.py"
|
||||
]
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
files = ["./utils.py", "./todo.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script src="./todo.py"> </py-script>
|
||||
@@ -46,8 +48,7 @@
|
||||
</div>
|
||||
|
||||
<py-list id="myList"></py-list>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4">
|
||||
</div>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
||||
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
@@ -57,152 +58,8 @@
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</section>
|
||||
</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">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
<py-script src="./todo.py">
|
||||
</py-script>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
</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>
|
||||
|
||||
215
pyscriptjs/src/plugins/python/py_tutor.py
Normal file
215
pyscriptjs/src/plugins/python/py_tutor.py
Normal 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()
|
||||
@@ -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 "<</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 ==============
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user