Skip to content
Feeding a clanker? Grab this page as raw .md

BezierCurve API#

Bases: AnyWidget

Interactive arbitrary-degree Bezier curve editor.

Double-click to add control points, drag points to reshape the curve, and use the play controls to advance t from 0 to 1. Coordinates synced to Python use the configurable data bounds rather than SVG pixels.

Examples:

from wigglystuff import BezierCurve

curve = BezierCurve(closed=True, loop=True)
curve

Create a BezierCurve widget.

Parameters:

Name Type Description Default
points Iterable[dict[str, Any]] | None

Initial control points as {"x": float, "y": float}.

None
closed bool

Whether to append the first point virtually for rendering.

False
playing bool

Whether playback starts immediately.

False
loop bool

Whether playback wraps from t=1 back to t=0.

False
t float

Initial curve parameter in [0, 1].

0.0
x_bounds tuple[float, float]

Data-coordinate x bounds.

(0.0, 1.0)
y_bounds tuple[float, float]

Data-coordinate y bounds.

(0.0, 1.0)
width int

SVG width in pixels.

600
height int

SVG height in pixels.

360
interval_ms int

Milliseconds between browser playback ticks.

30
duration_ms int

Milliseconds for one full t=0 to t=1 traversal.

12000
sync_throttle_ms int

Minimum milliseconds between playback syncs.

250
**kwargs Any

Forwarded to anywidget.AnyWidget.

{}
Source code in wigglystuff/bezier_curve.py
def __init__(
    self,
    points: Iterable[dict[str, Any]] | None = None,
    *,
    closed: bool = False,
    playing: bool = False,
    loop: bool = False,
    t: float = 0.0,
    x_bounds: tuple[float, float] = (0.0, 1.0),
    y_bounds: tuple[float, float] = (0.0, 1.0),
    width: int = 600,
    height: int = 360,
    interval_ms: int = 30,
    duration_ms: int = 12000,
    sync_throttle_ms: int = 250,
    **kwargs: Any,
) -> None:
    """Create a BezierCurve widget.

    Args:
        points: Initial control points as ``{"x": float, "y": float}``.
        closed: Whether to append the first point virtually for rendering.
        playing: Whether playback starts immediately.
        loop: Whether playback wraps from ``t=1`` back to ``t=0``.
        t: Initial curve parameter in ``[0, 1]``.
        x_bounds: Data-coordinate x bounds.
        y_bounds: Data-coordinate y bounds.
        width: SVG width in pixels.
        height: SVG height in pixels.
        interval_ms: Milliseconds between browser playback ticks.
        duration_ms: Milliseconds for one full ``t=0`` to ``t=1`` traversal.
        sync_throttle_ms: Minimum milliseconds between playback syncs.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.
    """
    coerced_points = _coerce_points(points)
    initial_t = _clamp01(t)
    initial_x, initial_y = _de_casteljau(
        _effective_points(coerced_points, closed), initial_t
    )
    super().__init__(
        points=coerced_points,
        x=initial_x,
        y=initial_y,
        t=initial_t,
        closed=closed,
        playing=playing,
        loop=loop,
        x_bounds=x_bounds,
        y_bounds=y_bounds,
        width=width,
        height=height,
        interval_ms=interval_ms,
        duration_ms=duration_ms,
        sync_throttle_ms=sync_throttle_ms,
        **kwargs,
    )
    self.observe(self._refresh_current_point, names=["points", "t", "closed"])

current_point #

current_point() -> tuple[float, float]

Return the current Bezier point at t as (x, y).

Source code in wigglystuff/bezier_curve.py
def current_point(self) -> tuple[float, float]:
    """Return the current Bezier point at ``t`` as ``(x, y)``."""
    return _de_casteljau(_effective_points(self.points, self.closed), self.t)

sample #

sample(n: int = 100) -> list[dict[str, float]]

Sample n points along the current Bezier curve.

Source code in wigglystuff/bezier_curve.py
def sample(self, n: int = 100) -> list[dict[str, float]]:
    """Sample ``n`` points along the current Bezier curve."""
    if n <= 0:
        raise ValueError("n must be positive.")
    points = _effective_points(self.points, self.closed)
    if not points:
        return []
    if n == 1:
        x, y = _de_casteljau(points, 0.0)
        return [{"x": x, "y": y}]
    return [
        {"x": x, "y": y}
        for x, y in (
            _de_casteljau(points, index / (n - 1)) for index in range(n)
        )
    ]

Synced traitlets#

Traitlet Type Notes
points list[dict] Control points as {"x": float, "y": float} in data coordinates.
x float Current Bezier point x-coordinate at t.
y float Current Bezier point y-coordinate at t.
t float Curve parameter, clamped to [0, 1].
closed bool Whether to virtually append the first point so the curve returns to the start.
playing bool Whether playback is currently advancing t.
loop bool Whether playback wraps from t=1 to t=0.
interval_ms int Milliseconds between browser playback ticks.
duration_ms int Milliseconds for one full t=0 to t=1 traversal.
sync_throttle_ms int Minimum milliseconds between playback updates synced to Python.
x_bounds tuple[float, float] Data-coordinate x bounds.
y_bounds tuple[float, float] Data-coordinate y bounds.
width int SVG width in pixels.
height int SVG height in pixels.

Helper methods#

Method Returns Description
current_point() tuple[float, float] Current Bezier point at t.
sample(n=100) list[dict] n sampled points along the curve.