Feeding a clanker? Grab this page as raw `.md` →

# ObservablePlot API


 Bases: `AnyWidget`


Run `Observable Plot <https://observablehq.com/plot/>`_ code in a notebook.


Observable Plot is a JavaScript charting library. This widget is a thin runner: it loads Plot (and d3) from a CDN, then evaluates a blob of JavaScript you supply (the `code` traitlet) exactly as you would write it in an Observable notebook — a bare `Plot.plot({...})` expression that returns a DOM node. The returned node is mounted inline; Python's job is to hand over the source, the data, and the sizing.


Python values are injected into the code's scope by name via `variables`: each key becomes an in-scope JavaScript variable holding the (JSON-ified) value. So `variables={"vacancies": df}` lets your code reference `vacancies` directly::


```
Plot.plot({
    marks: [
        Plot.barY(vacancies, { x: "month", y: "vacancies" }),
        Plot.ruleY([0]),
    ],
})
```


`Plot` and `d3` are always in scope, along with `container` (the target DOM element), the `width` / `height` ints, and the widget `model`. Pandas / polars DataFrames and numpy arrays in `variables` are converted to plain JSON structures (records / lists) before syncing.


The code is evaluated verbatim in the browser, so only pass JavaScript you trust (typically your own).


Reassigning `code`, `variables`, `width`, `height`, or `version` re-runs the code and rebuilds the chart. Observable Plot has no incremental redraw — each run produces a fresh SVG — but the new node is swapped in atomically (the previous chart stays put until it is ready), so updates driven by e.g. a slider do not flash a blank frame.



```
from wigglystuff import ObservablePlot

import marimo as mo
import pandas as pd
from wigglystuff import ObservablePlot

df = pd.DataFrame({"month": ["Jan", "Feb", "Mar"], "vacancies": [3, 5, 2]})
code = '''
    Plot.plot({
        marks: [
            Plot.barY(vacancies, { x: "month", y: "vacancies" }),
            Plot.ruleY([0]),
        ],
    })
'''
mo.ui.anywidget(ObservablePlot(code, variables={"vacancies": df}))
```


Point at a local file or a URL instead of an inline string — the first argument accepts any of the three forms:


```
ObservablePlot("charts/bars.js", variables={"vacancies": df})
ObservablePlot("https://example.com/chart.js")
```


Create an ObservablePlot widget.


The plot source is resolved in Python, so the browser always receives plain JavaScript. `code` (or `src`) may be any of:



- an inline JavaScript string,

- a path to a local `.js` file, or

- an `http(s)://` URL.


URLs and file paths are fetched/read here at construction time; the detection is by value, so `ObservablePlot("https://.../chart.js")` and `ObservablePlot("Plot.plot({...})")` both do the right thing.




| Type | Description |
| --- | --- |
| `ValueError` | If neither or both of `code` and `src` are given. |

 Source code in `wigglystuff/observable_plot.py`

```
def __init__(
    self,
    code: Optional[str] = None,
    *,
    src: Optional[str] = None,
    variables: Optional[Dict[str, Any]] = None,
    width: int = 500,
    height: int = 300,
    version: str = "latest",
    **kwargs: Any,
) -> None:
    """Create an ObservablePlot widget.

    The plot source is resolved in Python, so the browser always receives
    plain JavaScript. ``code`` (or ``src``) may be any of:

    * an inline JavaScript string,
    * a path to a local ``.js`` file, or
    * an ``http(s)://`` URL.

    URLs and file paths are fetched/read here at construction time; the
    detection is by value, so ``ObservablePlot("https://.../chart.js")`` and
    ``ObservablePlot("Plot.plot({...})")`` both do the right thing.

    Args:
        code: Inline JavaScript, a local file path, or an ``http(s)://``
            URL. Mutually exclusive with ``src``.
        src: An explicit alias for ``code`` (same accepted forms), handy
            when the intent is "load from here". Mutually exclusive with
            ``code``.
        variables: Mapping of name -> value injected into the code's scope
            as JavaScript variables. DataFrames and numpy arrays are
            converted to JSON-serializable structures.
        width: Container width in pixels.
        height: Container height in pixels.
        version: The `@observablehq/plot` version to load from the CDN.
            Defaults to ``"latest"``; pin an exact version (e.g. ``"0.6.17"``)
            for reproducibility.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.

    Raises:
        ValueError: If neither or both of ``code`` and ``src`` are given.
    """
    if (code is None) == (src is None):
        raise ValueError("Provide exactly one of `code` or `src`.")

    source = code if code is not None else src
    super().__init__(
        code=self._resolve_source(source),
        variables=variables if variables is not None else {},
        width=width,
        height=height,
        version=version,
        **kwargs,
    )
```


## Synced traitlets


| Traitlet | Type | Notes |
| --- | --- | --- |
| `code` | `str` | Resolved Observable Plot JavaScript (inline JS, or the contents of a file / URL, fetched in Python), evaluated as an expression that returns the DOM node to mount. |
| `variables` | `dict` | Name → value mapping injected into the code's scope as JavaScript variables. DataFrames and numpy arrays are converted to JSON records/lists on assignment. |
| `width` | `int` | Container width in pixels. |
| `height` | `int` | Container height in pixels. |
| `version` | `str` | `@observablehq/plot` version loaded from the CDN (defaults to `"latest"`). |
| `error` | `str` | Read-back of the latest JS runtime / CDN-load error, or `""`. |
