Merge branch 'main' into docs

This commit is contained in:
Mark Saroufim
2022-04-30 22:58:04 -07:00
committed by GitHub
4 changed files with 336 additions and 52 deletions

View File

@@ -7,23 +7,21 @@ This page will guide you through getting started with PyScript.
PyScript does not require any development environment other
than a web browser. We recommend using Chrome.
If, you're using [VSCode](https://code.visualstudio.com/) the
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
can be used to reload the page as you edit the HTML file.
## Installation
First, go to https://pyscript.net and download the PyScript assets.
Unzip the archive to a directory where you wish to write PyScript-enabled
HTML files. You should then have three files in your directory.
There is no installation required. In this document we'll use
the PyScript assets served on https://pyscript.net.
```
├── ./
│ ├── pyscript.css
│ ├── pyscript.js
│ └── pyscript.js.map
```
If you want to download the source and build it yourself follow
the instructions in the README.md file.
## Your first PyScript HTML file
Here's a "Hello, world!" example using PyScript using the assets you
downloaded from https://pyscript.net.
Here's a "Hello, world!" example using PyScript
Using your favorite editor create a new file called `hello.html` in
the same directory as your PyScript JavaScript and CSS files with the
@@ -33,8 +31,8 @@ open an HTML by double clicking it in your file explorer.
```html
<html>
<head>
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
</head>
<body> <py-script> print('Hello, World!') </py-script> </body>
</html>
@@ -53,8 +51,8 @@ example we can compute π.
```html
<html>
<head>
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
</head>
<body>
<py-script>
@@ -85,8 +83,8 @@ the `<py-script>` tag write to.
```html
<html>
<head>
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
@@ -124,6 +122,8 @@ HTML head. You can also link to `.whl` files directly on disk like in our [toga
</py-env>
```
If your `.whl` is not a pure Python wheel then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added here https://github.com/pyodide/pyodide/tree/main/packages
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
@@ -131,8 +131,8 @@ as a shortcut, which takes the expression on the last line of the script and run
```html
<html>
<head>
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
@@ -157,4 +157,52 @@ fig
</html>
```
If your `.whl` is not a pure Python wheel then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added here https://github.com/pyodide/pyodide/tree/main/packages
### Local modules
In addition to packages you can declare local Python modules that will
be imported in the `<py-script>` tag. For example we can place the random
number generation steps in a function in the file `data.py`.
```python
# data.py
import numpy as np
def make_x_and_y(n):
x = np.random.randn(n)
y = np.random.randn(n)
return x, y
```
In the HTML tag `<py-env>` paths to local modules are provided in the
`paths:` key.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
- paths:
- /data.py
</py-env>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
from data import make_x_and_y
x, y = make_x_and_y(n=1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
```

View File

@@ -16,8 +16,8 @@ PyScript is a meta project that aims to combine multiple open technologies to cr
To try PyScript, import the pyscript to your html page with:
```
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
```
At that point, you can then use PyScript components in your html page. PyScript currently implements the following elements:
@@ -35,6 +35,12 @@ To contribute:
* install the dependencies with `npm install` - make sure to use nodejs version >= 16
* run `npm run dev` to build and run the dev server. This will also watch for changes and rebuild when a file is saved
## Resources
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
* [Home Page](https://pyscript.net/)
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
## Notes
* This is an extremely experimental project, so expect things to break!

View File

@@ -1,4 +1,6 @@
from typing import Tuple
import numpy as np
from numpy.polynomial import Polynomial
def mandelbrot(width: int, height: int, *,
x: float = -0.5, y: float = 0, zoom: int = 1, max_iterations: int = 100) -> np.array:
@@ -60,3 +62,48 @@ def julia(width: int, height: int, *,
div_time[m] = i
return div_time
Range = Tuple[float, float]
def newton(width: int, height: int, *,
p: Polynomial, a: complex, xr: Range = (-2.5, 1), yr: Range = (-1, 1), max_iterations: int = 100) -> (np.array, np.array):
""" """
# To make navigation easier we calculate these values
x_from, x_to = xr
y_from, y_to = yr
# Here the actual algorithm starts
x = np.linspace(x_from, x_to, width).reshape((1, width))
y = np.linspace(y_from, y_to, height).reshape((height, 1))
z = x + 1j*y
# Compute the derivative
dp = p.deriv()
# Compute roots
roots = p.roots()
epsilon = 1e-5
# Set the initial conditions
a = np.full(z.shape, a)
# To keep track in which iteration the point diverged
div_time = np.zeros(z.shape, dtype=int)
# To keep track on which points did not converge so far
m = np.full(a.shape, True, dtype=bool)
# To keep track which root each point converged to
r = np.full(a.shape, 0, dtype=int)
for i in range(max_iterations):
z[m] = z[m] - a[m]*p(z[m])/dp(z[m])
for j, root in enumerate(roots):
converged = (np.abs(z.real - root.real) < epsilon) & (np.abs(z.imag - root.imag) < epsilon)
m[converged] = False
r[converged] = j + 1
div_time[m] = i
return div_time, r

View File

@@ -1,6 +1,6 @@
<html>
<head>
<title>Visualization of Mandelbrot and Julia sets with NumPy and HTML5 canvas</title>
<title>Visualization of Mandelbrot, Julia and Newton sets with NumPy and HTML5 canvas</title>
<meta charset="utf-8">
<link rel="stylesheet" href="../build/pyscript.css" />
@@ -17,6 +17,10 @@
animation: spin 1s ease-in-out infinite;
}
canvas {
display: none;
}
@keyframes spin {
to {
transform: rotate(360deg);
@@ -28,35 +32,68 @@
<body>
<b>
</b>
<div style="display: flex; flex-direction: row; gap: 1em">
<div>
<div style="display: flex; flex-direction: column; gap: 1em; width: 600px">
<div id="mandelbrot">
<div style="text-align: center">Mandelbrot set</div>
<div id="mandelbrot" style="width: 600px; height: 600px">
<div class="loading"></div>
</div>
</div>
<div>
<div style="text-align: center">Julia set</div>
<div id="julia" style="width: 600px; height: 600px">
<div class="loading"></div>
<canvas></canvas>
</div>
</div>
<div id="julia">
<div style="text-align: center">Julia set</div>
<div>
<div class="loading"></div>
<canvas></canvas>
</div>
</div>
<div id="newton">
<div style="text-align: center">Newton set</div>
<fieldset style="display: flex; flex-direction: row; gap: 1em">
<div><span style="white-space: pre">p(z) = </span><input id="poly" type="text" value="z**3 - 2*z + 2"></div>
<div><span style="white-space: pre">a = </span><input id="coef" type="text" value="1" style="width: 40px"></div>
<div style="display: flex; flex-direction: row">
<span style="white-space: pre">x = [</span>
<input id="x0" type="text" value="-2.5" style="width: 80px; text-align: right">
<span style="white-space: pre">, </span>
<input id="x1" type="text" value="2.5" style="width: 80px; text-align: right">
<span style="white-space: pre">]</span>
</div>
<div style="display: flex; flex-direction: row">
<span style="white-space: pre">y = [</span>
<input id="y0" type="text" value="-5.0" style="width: 80px; text-align: right">
<span style="white-space: pre">, </span>
<input id="y1" type="text" value="5.0" style="width: 80px; text-align: right">
<span style="white-space: pre">]</span>
</div>
<div style="display: flex; flex-direaction: row; gap: 1em">
<div style="white-space: pre"><input type="radio" id="conv" name="type" value="convergence" checked> convergence</div>
<div style="white-space: pre"><input type="radio" id="iter" name="type" value="iterations"> iterations</div>
</div>
</fieldset>
<div>
<div class="loading"></div>
<canvas></canvas>
</div>
</div>
</div>
<py-env>
- numpy
- sympy
- paths:
- /palettes.py
- /fractals.py
</py-env>
<py-script>
from pyodide import to_js
from pyodide import to_js, create_proxy
import numpy as np
import sympy
from palettes import Magma256
from fractals import mandelbrot, julia
from fractals import mandelbrot, julia, newton
from js import (
console,
@@ -65,28 +102,20 @@ from js import (
ImageData,
Uint8ClampedArray,
CanvasRenderingContext2D as Context2d,
requestAnimationFrame,
)
def create_canvas(width: int, height: int, target: str) -> Context2d:
pixel_ratio = devicePixelRatio
canvas = document.createElement("canvas")
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*pixel_ratio
canvas.height = height*pixel_ratio
ctx.scale(pixel_ratio, pixel_ratio)
ctx.translate(0.5, 0.5)
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
el = document.querySelector(target)
el.replaceChildren(canvas)
return ctx
def color_map(array: np.array, palette: np.array) -> np.array:
@@ -105,30 +134,184 @@ def draw_image(ctx: Context2d, image: np.array) -> None:
image_data = ImageData.new(data, width, height)
ctx.putImageData(image_data, 0, 0)
def draw_mandelbrot(width: int, height: int) -> None:
ctx = create_canvas(width, height, "#mandelbrot")
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")
array = mandelbrot(width, height)
iters = mandelbrot(width, height)
console.timeEnd("mandelbrot")
image = color_map(array, Magma256)
image = color_map(iters, Magma256)
draw_image(ctx, image)
def draw_julia(width: int, height: int) -> None:
ctx = create_canvas(width, height, "#julia")
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")
array = julia(width, height)
iters = julia(width, height)
console.timeEnd("julia")
image = color_map(array, Magma256)
image = color_map(iters, Magma256)
draw_image(ctx, image)
draw_mandelbrot(600, 600)
draw_julia(600, 600)
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
_ = await asyncio.gather(
draw_mandelbrot(),
draw_julia(),
draw_newton(),
)
</py-script>
</body>