mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Restyle examples and add button to show code on all examples (#968)
This commit is contained in:
@@ -4,59 +4,151 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<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="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<script defer src="./assets/prism/prism.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="altair" style="width: 100%; height: 100%"></div>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
<py-config>
|
<div class="app-header">
|
||||||
packages = [
|
<a href="/">
|
||||||
"altair",
|
<img src="./logo.png" class="logo">
|
||||||
"pandas",
|
</a>
|
||||||
"vega_datasets"
|
<a class="title" href="" style="color: #f0ab3c;">Altair</a>
|
||||||
]
|
</div>
|
||||||
</py-config>
|
</nav>
|
||||||
<py-script>
|
<section class="pyscript">
|
||||||
import altair as alt
|
<div id="altair"></div>
|
||||||
from vega_datasets import data
|
<py-config>
|
||||||
|
packages = [
|
||||||
|
"altair",
|
||||||
|
"pandas",
|
||||||
|
"vega_datasets"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
<py-script>
|
||||||
|
import altair as alt
|
||||||
|
from vega_datasets import data
|
||||||
|
|
||||||
source = data.movies.url
|
source = data.movies.url
|
||||||
|
|
||||||
pts = alt.selection(type="single", encodings=['x'])
|
pts = alt.selection(type="single", encodings=['x'])
|
||||||
|
|
||||||
rect = alt.Chart(data.movies.url).mark_rect().encode(
|
rect = alt.Chart(data.movies.url).mark_rect().encode(
|
||||||
alt.X('IMDB_Rating:Q', bin=True),
|
alt.X('IMDB_Rating:Q', bin=True),
|
||||||
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
|
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
|
||||||
alt.Color('count()',
|
alt.Color('count()',
|
||||||
scale=alt.Scale(scheme='greenblue'),
|
scale=alt.Scale(scheme='greenblue'),
|
||||||
legend=alt.Legend(title='Total Records')
|
legend=alt.Legend(title='Total Records')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
circ = rect.mark_point().encode(
|
circ = rect.mark_point().encode(
|
||||||
alt.ColorValue('grey'),
|
alt.ColorValue('grey'),
|
||||||
alt.Size('count()',
|
alt.Size('count()',
|
||||||
legend=alt.Legend(title='Records in Selection')
|
legend=alt.Legend(title='Records in Selection')
|
||||||
)
|
)
|
||||||
).transform_filter(
|
).transform_filter(
|
||||||
pts
|
pts
|
||||||
)
|
)
|
||||||
|
|
||||||
bar = alt.Chart(source).mark_bar().encode(
|
bar = alt.Chart(source).mark_bar().encode(
|
||||||
x='Major_Genre:N',
|
x='Major_Genre:N',
|
||||||
y='count()',
|
y='count()',
|
||||||
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
|
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
|
||||||
).properties(
|
).properties(
|
||||||
width=550,
|
width=550,
|
||||||
height=200
|
height=200
|
||||||
).add_selection(pts)
|
).add_selection(pts)
|
||||||
|
|
||||||
display(alt.vconcat(
|
display(alt.vconcat(
|
||||||
rect + circ,
|
rect + circ,
|
||||||
bar
|
bar
|
||||||
).resolve_legend(
|
).resolve_legend(
|
||||||
color="independent",
|
color="independent",
|
||||||
size="independent"
|
size="independent"
|
||||||
), target="altair")
|
), target="altair")
|
||||||
</py-script>
|
</py-script>
|
||||||
</body>
|
</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>
|
||||||
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -4,9 +4,21 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<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="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<script defer src="./assets/prism/prism.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Antigravity</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<py-config>
|
<py-config>
|
||||||
[[fetch]]
|
[[fetch]]
|
||||||
files = ["./antigravity.py"]
|
files = ["./antigravity.py"]
|
||||||
@@ -16,5 +28,91 @@
|
|||||||
import antigravity
|
import antigravity
|
||||||
antigravity.fly()
|
antigravity.fly()
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
90
examples/assets/css/examples.css
Normal file
90
examples/assets/css/examples.css
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pyscript {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 9998;
|
||||||
|
top: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1300px) {
|
||||||
|
.code:has(> .code-section-visible) {
|
||||||
|
width: 90%;
|
||||||
|
/* Absolute position is messing up the layout on small screens */
|
||||||
|
right: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-section-hidden {
|
||||||
|
width: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-section-visible {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgb(45 46 53 / 90%);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px 0px 0px 10px;
|
||||||
|
color: #c6c6c8;
|
||||||
|
}
|
||||||
|
.code-section-visible p{
|
||||||
|
margin: 0;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-html, .language-python {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view-code-button {
|
||||||
|
writing-mode: tb-rl;
|
||||||
|
text-orientation: sideways-right;
|
||||||
|
background-color: #1D1D22;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 81px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: sticky;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
padding-right: 10px;
|
||||||
|
font-size: 28px;
|
||||||
|
height: 30px;
|
||||||
|
max-width: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-decoration: none;
|
||||||
|
text-decoration-line: none;
|
||||||
|
text-decoration-style: initial;
|
||||||
|
text-decoration-color: initial;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 2em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.example h2{
|
.example h2{
|
||||||
color: #000000;
|
/* color: #000000; */
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Inconsolata", monospace;
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
@@ -11,10 +11,14 @@
|
|||||||
|
|
||||||
.card {
|
.card {
|
||||||
height: 15rem;
|
height: 15rem;
|
||||||
background-color: #FFFFFF;
|
background-color: var(--color-secondary);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
-webkit-box-shadow: var(--card-shadow);
|
border-radius: 10px;
|
||||||
box-shadow: var(--card-shadow);
|
}
|
||||||
|
|
||||||
|
.card:hover, .card:hover a, .card:hover a:visited, .card:hover h2 {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #1D1D22
|
||||||
}
|
}
|
||||||
|
|
||||||
.card a h2 {
|
.card a h2 {
|
||||||
@@ -24,6 +28,17 @@
|
|||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card a p {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a .card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -31,6 +46,10 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-content a, .card-content a:visited {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.container-card {
|
.container-card {
|
||||||
max-width: 1500px;
|
max-width: 1500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
@import "./variables.css";
|
@import "./variables.css";
|
||||||
@import "./reset.css";
|
@import "./reset.css";
|
||||||
|
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #2D2E35 url('https://assets.anaconda.com/production/Content/1650828148240.png?w=3240&auto=compress%2Cformat&fit=crop&dm=1650828161&s=c558dc55e0ed1f8419a892e842a5728f') repeat-x center bottom / 250px;
|
||||||
|
background-attachment: fixed;
|
||||||
|
overflow-x: hidden;
|
||||||
|
color: var(--text-color)
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1510px;
|
max-width: 1510px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -9,7 +17,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title-main {
|
.title-main {
|
||||||
color: #000000;
|
|
||||||
font-size: 4.25rem;
|
font-size: 4.25rem;
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Inconsolata", monospace;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--color-primary: #2563eb;
|
--color-primary: #FDA703;
|
||||||
|
--color-secondary: #1D1D22;
|
||||||
|
--text-color: white;
|
||||||
--card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%);
|
--card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%);
|
||||||
}
|
}
|
||||||
|
|||||||
3
examples/assets/prism/prism.css
Normal file
3
examples/assets/prism/prism.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/* PrismJS 1.29.0
|
||||||
|
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+clike+javascript+python */
|
||||||
|
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||||
7
examples/assets/prism/prism.js
Normal file
7
examples/assets/prism/prism.js
Normal file
File diff suppressed because one or more lines are too long
@@ -14,9 +14,20 @@
|
|||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Bokeh Example</h1>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
<py-config>
|
<py-config>
|
||||||
@@ -45,6 +56,60 @@ p_json = json.dumps(json_item(p, "myplot"))
|
|||||||
|
|
||||||
Bokeh.embed.embed_item(JSON.parse(p_json))
|
Bokeh.embed.embed_item(JSON.parse(p_json))
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<py-config>
|
||||||
|
packages = [
|
||||||
|
"bokeh",
|
||||||
|
"numpy"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
<py-script id="main">
|
||||||
|
import json
|
||||||
|
import pyodide
|
||||||
|
|
||||||
</body>
|
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>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const viewCodeButton = document.getElementById("view-code-button");
|
||||||
|
const codeSection = document.getElementById("code-section");
|
||||||
|
const handleClick = () => {
|
||||||
|
if (codeSection.classList.contains("code-section-hidden")) {
|
||||||
|
codeSection.classList.remove("code-section-hidden");
|
||||||
|
codeSection.classList.add("code-section-visible");
|
||||||
|
} else {
|
||||||
|
codeSection.classList.remove("code-section-visible");
|
||||||
|
codeSection.classList.add("code-section-hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewCodeButton.addEventListener("click", handleClick)
|
||||||
|
viewCodeButton.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||||
|
handleClick();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
||||||
<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-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>
|
<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">
|
<script type="text/javascript">
|
||||||
Bokeh.set_log_level("info");
|
Bokeh.set_log_level("info");
|
||||||
@@ -16,6 +19,15 @@
|
|||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<h1>Bokeh Example</h1>
|
<h1>Bokeh Example</h1>
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
@@ -95,6 +107,110 @@ async def show(plot, target):
|
|||||||
|
|
||||||
asyncio.ensure_future(show(row, 'myplot'))
|
asyncio.ensure_future(show(row, 'myplot'))
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<py-config>
|
||||||
|
packages = [
|
||||||
|
"bokeh",
|
||||||
|
"numpy"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
<py-script id="main">
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import pyodide
|
||||||
|
|
||||||
</body>
|
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>
|
||||||
|
</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>
|
</html>
|
||||||
|
|||||||
155
examples/d3.html
155
examples/d3.html
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
<style>
|
||||||
.loading {
|
.loading {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -27,6 +29,15 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<b>
|
<b>
|
||||||
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
|
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
|
||||||
</b>
|
</b>
|
||||||
@@ -165,6 +176,146 @@ for d in data:
|
|||||||
.attr("dy", "1.3em")
|
.attr("dy", "1.3em")
|
||||||
.text(d_py["value"]))
|
.text(d_py["value"]))
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<script>
|
||||||
|
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
|
||||||
|
|
||||||
</body>
|
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>
|
||||||
|
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>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,9 +5,21 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="folium" style="width: 100%; height: 100%"></div>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Folium</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
|
<div id="folium"></div>
|
||||||
|
|
||||||
<py-config>
|
<py-config>
|
||||||
packages = [
|
packages = [
|
||||||
@@ -49,5 +61,76 @@ folium.LayerControl().add_to(m)
|
|||||||
|
|
||||||
display(m, target="folium")
|
display(m, target="folium")
|
||||||
</py-script>
|
</py-script>
|
||||||
</body>
|
</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>
|
||||||
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -8,17 +8,64 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<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="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<script defer src="./assets/prism/prism.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
Hello world! <br>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
This is the current date and time, as computed by Python:
|
<div class="app-header">
|
||||||
<py-script>
|
<a href="/">
|
||||||
from datetime import datetime
|
<img src="./logo.png" class="logo">
|
||||||
now = datetime.now()
|
</a>
|
||||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
|
||||||
</py-script>
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
|
Hello world! <br>
|
||||||
|
This is the current date and time, as computed by Python:
|
||||||
|
<py-script>
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now()
|
||||||
|
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||||
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<py-script>
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now()
|
||||||
|
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||||
|
</py-script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -5,8 +5,20 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Matplotlib</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<div id="mpl"></div>
|
<div id="mpl"></div>
|
||||||
|
|
||||||
<py-config>
|
<py-config>
|
||||||
@@ -50,5 +62,77 @@ ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
|||||||
|
|
||||||
display(fig1, target="mpl")
|
display(fig1, target="mpl")
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
<style>
|
||||||
.loading {
|
.loading {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -30,8 +32,15 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<b>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
</b>
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Fractals with NumPy and canvas</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em; width: 600px">
|
<div style="display: flex; flex-direction: column; gap: 1em; width: 600px">
|
||||||
<div id="mandelbrot">
|
<div id="mandelbrot">
|
||||||
<div style="text-align: center">Mandelbrot set</div>
|
<div style="text-align: center">Mandelbrot set</div>
|
||||||
@@ -325,6 +334,281 @@ async def main():
|
|||||||
|
|
||||||
asyncio.ensure_future(main())
|
asyncio.ensure_future(main())
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<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
|
||||||
|
|
||||||
</body>
|
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>
|
||||||
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -9,9 +9,20 @@
|
|||||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Panel Example</h1>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Panel Example</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<div id="simple_app"></div>
|
<div id="simple_app"></div>
|
||||||
|
|
||||||
<py-config>
|
<py-config>
|
||||||
@@ -32,5 +43,52 @@
|
|||||||
|
|
||||||
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
|
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
|
||||||
</py-script>
|
</py-script>
|
||||||
</body>
|
</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>
|
||||||
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -40,32 +40,31 @@
|
|||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
<div class="app-header">
|
||||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
<a href="/">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<img src="./logo.png" class="logo">
|
||||||
</button>
|
</a>
|
||||||
<div class="app-header">
|
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
|
||||||
<a class="navbar-brand app-logo" href="/">
|
</div>
|
||||||
<img src="./logo.png" class="app-logo">
|
</nav>
|
||||||
</a>
|
<section class="pyscript">
|
||||||
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="d-flex flex-nowrap" id="content">
|
<div class="d-flex flex-nowrap" id="content">
|
||||||
<div class="sidenav" id="sidebar">
|
<div class="sidenav" id="sidebar">
|
||||||
<ul class="nav flex-column" >
|
<ul class="nav flex-column" >
|
||||||
<div class="bk-root" id="widgets"></div>
|
<div class="bk-root" id="widgets"></div>
|
||||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mh-100" style="padding: 0">
|
<div class="col mh-100" style="padding: 0">
|
||||||
<div class="bk-root" id="plot"></div>
|
<div class="bk-root" id="plot"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -219,5 +218,185 @@
|
|||||||
app.deck_gl.servable(target='plot')
|
app.deck_gl.servable(target='plot')
|
||||||
controls.servable(target='widgets');
|
controls.servable(target='widgets');
|
||||||
</py-script>
|
</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 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -41,35 +41,35 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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 defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
<div class="app-header">
|
||||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
<a href="/">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<img src="./logo.png" class="logo">
|
||||||
</button>
|
</a>
|
||||||
<div class="app-header">
|
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
|
||||||
<a class="navbar-brand app-logo" href="/">
|
</div>
|
||||||
<img src="./logo.png" class="app-logo">
|
</nav>
|
||||||
</a>
|
<section class="pyscript">
|
||||||
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="row overflow-hidden" id="content">
|
<div class="row overflow-hidden" id="content">
|
||||||
<div class="sidenav" id="sidebar">
|
<div class="sidenav" id="sidebar">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<div class="bk-root" id="x-widget"></div>
|
<div class="bk-root" id="x-widget"></div>
|
||||||
<div class="bk-root" id="y-widget"></div>
|
<div class="bk-root" id="y-widget"></div>
|
||||||
<div class="bk-root" id="n-widget"></div>
|
<div class="bk-root" id="n-widget"></div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mh-100 float-left" id="main">
|
<div class="col mh-100 float-left" id="main">
|
||||||
<div class="bk-root" id="intro"></div>
|
<div class="bk-root" id="intro"></div>
|
||||||
<div class="bk-root" id="cluster-plot"></div>
|
<div class="bk-root" id="cluster-plot"></div>
|
||||||
<div class="bk-root" id="table"></div>
|
<div class="bk-root" id="table"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -159,6 +159,122 @@
|
|||||||
update_table()
|
update_table()
|
||||||
update_chart()
|
update_chart()
|
||||||
</py-script>
|
</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 = [
|
||||||
|
"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>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|||||||
@@ -32,22 +32,21 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
<div class="app-header">
|
||||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
<a href="/">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<img src="./logo.png" class="logo">
|
||||||
</button>
|
</a>
|
||||||
<div class="app-header">
|
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
|
||||||
<a class="navbar-brand app-logo" href="/">
|
</div>
|
||||||
<img src="./logo.png" class="app-logo">
|
</nav>
|
||||||
</a>
|
<section class="pyscript">
|
||||||
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="row overflow-hidden" id="content">
|
<div class="row overflow-hidden" id="content">
|
||||||
<div class="col mh-100 float-left" id="main">
|
<div class="col mh-100 float-left" id="main">
|
||||||
<div class="bk-root" id="controls"></div>
|
<div class="bk-root" id="controls"></div>
|
||||||
@@ -115,5 +114,90 @@
|
|||||||
pn.pane.Bokeh(p).servable(target='plot')
|
pn.pane.Bokeh(p).servable(target='plot')
|
||||||
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
|
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
|
||||||
</py-script>
|
</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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -9,10 +9,22 @@
|
|||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1><b>pyscript REPL</b></h1>
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">PyScript REPL</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
|
<h1><b>PyScript REPL</b></h1>
|
||||||
Tip: press Shift-ENTER to evaluate a cell
|
Tip: press Shift-ENTER to evaluate a cell
|
||||||
<br>
|
<br>
|
||||||
<py-config>
|
<py-config>
|
||||||
@@ -22,5 +34,90 @@
|
|||||||
<div>
|
<div>
|
||||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||||
</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">
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -9,11 +9,22 @@
|
|||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<link rel="stylesheet" href="repl.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>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Custom REPL</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||||
<py-config>
|
<py-config>
|
||||||
packages = [
|
packages = [
|
||||||
@@ -28,5 +39,110 @@
|
|||||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||||
<div id="output" class="p-4"></div>
|
<div id="output" class="p-4"></div>
|
||||||
</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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -10,9 +10,21 @@
|
|||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Simple Clock</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
||||||
<div id="outputDiv2" class="font-mono"></div>
|
<div id="outputDiv2" class="font-mono"></div>
|
||||||
<div id="outputDiv3" class="font-mono"></div>
|
<div id="outputDiv3" class="font-mono"></div>
|
||||||
@@ -43,5 +55,78 @@ async def foo():
|
|||||||
|
|
||||||
pyscript.run_until_complete(foo())
|
pyscript.run_until_complete(foo())
|
||||||
</py-script>
|
</py-script>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -9,6 +9,22 @@
|
|||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||||
|
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||||
|
<script defer src="./assets/prism/prism.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Pyscript Native TODO App</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
|
<h1>To Do List</h1>
|
||||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||||
|
|
||||||
<py-config>
|
<py-config>
|
||||||
@@ -46,10 +62,6 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
</py-script>
|
</py-script>
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>To Do List</h1>
|
|
||||||
<div class="py-box">
|
<div class="py-box">
|
||||||
<input id="new-task-content" />
|
<input id="new-task-content" />
|
||||||
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
|
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
|
||||||
@@ -57,5 +69,101 @@
|
|||||||
|
|
||||||
<py-list id="myList"></py-list>
|
<py-list id="myList"></py-list>
|
||||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
<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">
|
||||||
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
class PyItem(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(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>
|
||||||
|
</section>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
|||||||
@@ -10,11 +10,21 @@
|
|||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body class="container">
|
<body>
|
||||||
<!-- <py-repl id="my-repl" auto-generate="true"> </py-repl> -->
|
<nav class="navbar" style="background-color: #000000;">
|
||||||
|
<div class="app-header">
|
||||||
|
<a href="/">
|
||||||
|
<img src="./logo.png" class="logo">
|
||||||
|
</a>
|
||||||
|
<a class="title" href="" style="color: #f0ab3c;">Todo App</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="pyscript">
|
||||||
<py-config>
|
<py-config>
|
||||||
[[fetch]]
|
[[fetch]]
|
||||||
files = ["./utils.py"]
|
files = ["./utils.py"]
|
||||||
@@ -50,5 +60,149 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
</section>
|
||||||
|
<section class="code">
|
||||||
|
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||||
|
<div id="code-section" class="code-section-hidden">
|
||||||
|
<p>index.html</p>
|
||||||
|
<pre class="prism-code language-html">
|
||||||
|
<code class="language-html">
|
||||||
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user