Pyodide Demo Component

Live example

This example runs Python in your browser with Pyodide. Drag the sliders, use Play and Reset, then expand Show Python code to see the source that generates the SVG figure.

NumPy computes the position of the blue vector. The controls change its angular velocity and elapsed time.

Loading Python example…

Show Python code
"""Draw the SO(2) rotation example with NumPy and drawsvg."""

import numpy as np
import drawsvg as draw


J = np.array([[0.0, -1.0], [1.0, 0.0]])
UNIT_X = np.array([1.0, 0.0])


def rotation_svg(omega: float, time: float) -> str:
    theta = omega * time
    rotation = np.eye(2) * np.cos(theta) + J * np.sin(theta)
    endpoint = rotation @ UNIT_X

    angles = np.linspace(0.0, theta, 121)
    trail = np.column_stack((np.cos(angles), np.sin(angles)))

    drawing = draw.Drawing(2.7, 2.7, origin="center", id_prefix="rotation", aria_hidden="true")
    drawing.set_render_size(400)
    drawing.append(draw.Circle(0, 0, 1, fill="none", class_="unit-circle"))
    drawing.append(draw.Line(-1.2, 0, 1.2, 0, class_="axis"))
    drawing.append(draw.Line(0, -1.2, 0, 1.2, class_="axis"))

    # SVG's y-axis points down, so negate mathematical y coordinates.
    trail[:, 1] *= -1
    drawing.append(draw.Lines(*trail.ravel().tolist(), fill="none", class_="trail"))
    drawing.append(draw.Line(0, 0, 1, 0, class_="reference"))
    drawing.append(draw.Line(0, 0, float(endpoint[0]), -float(endpoint[1]), class_="vector"))
    drawing.append(draw.Circle(float(endpoint[0]), -float(endpoint[1]), 0.045, class_="endpoint"))
    drawing.append(draw.Circle(0, 0, 0.025, class_="origin"))
    drawing.append(draw.Text("x", 0.15, 1.15, 0.16))
    drawing.append(draw.Text("y", 0.15, 0.08, -1.14))

    return drawing.as_svg(header="")

Add one to an article

For a new figure, put its files in the article’s page bundle:

content/blog/my-article/
├── index.md       # Article text and component call
├── rotation.py    # NumPy calculation; returns an SVG string
├── rotation.js    # Sliders/buttons and calls to Python
└── rotation.css   # Figure-specific layout (optional)

Use rotation.py, rotation.js, and rotation.css as the complete example. In this figure, rotation.py defines rotation_svg(omega, time). It computes the rotated vector with NumPy, builds the drawing with drawsvg, and returns drawing.as_svg(header=""). The JavaScript calls the shared loader with packages: ["numpy", "drawsvg"] and functionName: "rotation_svg", then passes the slider values to the returned function and puts its SVG result in .pyodide-demo-plot.

Add this to index.md, using the page bundle’s URL for each file:

{% <pyodide_demo source="/blog/my-article/rotation.py" script="/blog/my-article/rotation.js" stylesheet="/blog/my-article/rotation.css" title="Unit vector rotated from the positive x-axis"> %}
Drag the sliders to change angular velocity and time.
{% </pyodide_demo> %}

source and script are required. stylesheet and title are optional. The component provides a place for controls, an SVG plot, a loading status, and a Show Python code panel. Markdown between the opening and closing tags appears above the controls. Zola reads the Python file at build time and highlights that same file in the code panel. For a Python file outside content/, pass code_path="static/path/to/file.py" as well.

The shared Pyodide loader in static/pyodide/ fetches and executes the Python source and loads its packages. The figure-specific rotation.js defines the controls and calls the Python rotation_svg function. The rotation.py file uses NumPy for the rotation and drawsvg for SVG circles, lines, text, and the trajectory. The script requests NumPy and the locally stored drawsvg wheel through the shared loader, so the Python code does not need to assemble SVG markup by hand.

The drawsvg 2.4.2 wheel is stored at static/pyodide/drawsvg-2.4.2-py3-none-any.whl. It includes its MIT license and supports paths, groups, gradients, clip paths, and SVG animation for more complex figures. The loader maps "drawsvg" in a figure’s packages list to this local wheel.

The site’s Content Security Policy allows the pinned Pyodide runtime from jsDelivr and WebAssembly execution. A network connection is needed the first time a visitor loads the runtime and its packages.