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

ScatterLog API#

Bases: AltairWidget

Accumulate (x, y[, color]) points and draw them as a live scatter.

Unlike a plain marimo state variable -- which reactivity keeps resetting -- a ScatterLog instance is stable across cell re-runs, so it can grow a history. Create it once in its own (dependency-free) cell, then call .append(...) from a separate reactive cell; each re-run of that cell adds a point. Because x and y are passed explicitly they can come from different upstream widgets.

It subclasses :class:AltairWidget, so appends update the chart in place via the Vega changeset API (no flicker, zoom/pan preserved). Read the accumulated points back with .data.

Examples:

import marimo as mo
from wigglystuff import ScatterLog

log = ScatterLog(x_label="energy", y_label="p(win)")
mo.ui.anywidget(log)  # display once

# ...in a reactive cell that depends on `slider`:
log.append(x=slider.value, y=prob, color="run-1")

Create a ScatterLog.

Parameters:

Name Type Description Default
x_label str

Axis title for the x values.

'x'
y_label str

Axis title for the y values.

'y'
color_label str

Legend title used once colored points appear.

'color'
max_points int

Keep at most this many most-recent points (bounds the synced history so marimo exports stay small).

500
width int

Chart width in pixels.

600
height int

Chart height in pixels.

400
**kwargs Any

Forwarded to :class:AltairWidget.

{}
Source code in wigglystuff/scatter_log.py
def __init__(
    self,
    *,
    x_label: str = "x",
    y_label: str = "y",
    color_label: str = "color",
    max_points: int = 500,
    width: int = 600,
    height: int = 400,
    **kwargs: Any,
) -> None:
    """Create a ScatterLog.

    Args:
        x_label: Axis title for the x values.
        y_label: Axis title for the y values.
        color_label: Legend title used once colored points appear.
        max_points: Keep at most this many most-recent points (bounds the
            synced history so marimo exports stay small).
        width: Chart width in pixels.
        height: Chart height in pixels.
        **kwargs: Forwarded to :class:`AltairWidget`.
    """
    if max_points < 1:
        raise ValueError(f"max_points must be >= 1, got {max_points}")
    self._points: list[dict] = []  # source of truth
    self._has_color = False  # sticky: once a color is seen, keep the legend
    self._x_label = x_label
    self._y_label = y_label
    self._color_label = color_label
    self.max_points = int(max_points)
    super().__init__(chart=None, width=width, height=height, **kwargs)

data property #

data: list[dict]

A copy of the accumulated points, oldest first.

append #

append(x: Any, y: Any = _UNSET, color: Optional[Any] = None, **series: Any) -> None

Log one or more points at x and redraw.

A single point (color optionally labels its series)::

log.append(x=1.0, y=2.0)
log.append(x=1.0, y=2.0, color="run-1")

Several named series at the same x -- each keyword becomes a series whose name is its legend label::

log.append(x=step, loss=0.3, acc=0.9)
Source code in wigglystuff/scatter_log.py
def append(
    self, x: Any, y: Any = _UNSET, color: Optional[Any] = None, **series: Any
) -> None:
    """Log one or more points at ``x`` and redraw.

    A single point (``color`` optionally labels its series)::

        log.append(x=1.0, y=2.0)
        log.append(x=1.0, y=2.0, color="run-1")

    Several named series at the same ``x`` -- each keyword becomes a series
    whose name is its legend label::

        log.append(x=step, loss=0.3, acc=0.9)
    """
    if y is not _UNSET:
        self._add_point(x, y, color)
    for name, value in series.items():
        self._add_point(x, value, name)
    if len(self._points) > self.max_points:
        self._points = self._points[-self.max_points :]
    self.spec = self._build_spec()

clear #

clear() -> None

Drop all accumulated points and blank the chart.

Source code in wigglystuff/scatter_log.py
def clear(self) -> None:
    """Drop all accumulated points and blank the chart."""
    self._points = []
    self._has_color = False
    self.spec = {}

Usage#

Create the widget once, display it, then append points from a separate reactive cell. Pass y= for one series or use named keyword arguments to append several series at the same x-coordinate.

log = ScatterLog(x_label="step", y_label="score")
log.append(x=step, loss=loss, accuracy=accuracy)

data returns a copy of the accumulated points, clear() resets the plot, and max_points bounds the retained history.

Synced traitlets#

Traitlet Type Notes
spec dict Current Vega-Lite scatter specification.
width int Container width in pixels.
height int Container height in pixels.