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`.



```
from wigglystuff import ScatterLog

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.


  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. |
