Interactive Viewer (UI)¶
A FastAPI-powered web UI that auto-generates parameter controls from
function signatures or Pydantic models and renders matplotlib figures
in real-time. Install the optional ui extra to use this module:
uv add "dartwork-mpl[ui]"
run(figure_fn, param_model=None, *, title, copy_to, host, port)
Launch the interactive figure viewer in the browser.
Parameters:
figure_fn: callable accepting a singleParamModelinstance, returns aFigure.
param_model: optional explicitParamModelsubclass (auto-detected from type hints if omitted).
title: browser tab title. Default"Dartwork Viewer".
copy_to: onestr/os.PathLike[str], a list of those, orNone. Image saves also create copies in these directories; script saves stay in the script directory.~expands, relative roots use the working directory at startup, and duplicate directory objects are deduplicated.
host: loopback IP literal, or"localhost"(mapped directly to"127.0.0.1"). Default"127.0.0.1". Wildcard, non-loopback, and other hostname binds are rejected before server startup or writes.
port: server port. Default8501.Returns:
None— runs the server until interrupted.
ParamModel
Base Pydantic model for declaring plot parameters. Subclass it and add typed fields with
pydantic.Fieldfor validation, ranges, and default values. The UI auto-generates sliders, dropdowns, and text fields from the field metadata.
Features¶
Auto-generated controls: sliders, dropdowns, and text inputs created from type annotations.
Real-time preview: figure re-renders on every parameter change.
Export: download PNG/SVG/PDF or generate a standalone Python script.
Presets: save and restore parameter combinations.
Server-side save: save images and scripts to the project directory.
Hot reload: restart the server to pick up code changes.
Local access and saves¶
The viewer is a local development tool with trusted Python callbacks. Loopback access establishes network locality, not a person’s identity; local processes are trusted. Every route checks Host, and effects check each supplied Origin and Referer against it. Forwarded headers do not grant access. Hosted access and reverse proxies are outside this contract.
Server image and script saves are create-only below the script directory.
Use a new relative filename for every save, including every copy_to
destination. Safe nested subdirectories are allowed; absolute paths,
traversal, symlinks and non-directory parents are refused. An existing
target at any destination returns 409: file exists; choose a new name.
There is no overwrite action. Generated names also use exclusive creation.
The viewer pins directory handles and uses no-follow, handle-relative operations. Multi-root saves use best-effort rollback, not a cross-root transaction: readers can observe provisional files, cleanup can fail, and a crash may leave new files. A partial-save 503 identifies affected relative paths. A retry needs a new name. This does not protect against a malicious same-owner process moving already-authorized directories or racing cleanup.
State and recovery¶
Only .dartwork_ui_config.json and .dartwork_ui_presets.json use
bounded atomic replacement. Malformed or oversized existing state returns
409 (invalid_stored_state); reads, updates and deletes preserve its
bytes. The UI reports the failure and keeps defaults, controls and Reset
usable. Reset changes active parameters; it does not repair stored files.
Renamed or removed model fields are projected out on reads and reported
through dropped_fields on config and individual presets. Reads do not
rewrite the files. Retained values are not checked against the current
model until used; an obsolete type or range can return 422. Use Reset to
restore current defaults. Preset-list success responses remain JSON lists.
Updates serialize within one process; separate viewer processes sharing a
directory can lose updates because the last replacement wins.
Input limits and failures¶
JSON-bearing requests require application/json with an absent or UTF-8
charset and no Content-Encoding. Limits count UTF-8 bytes where applicable:
Request body and config file: 1 MiB each; preset file: 8 MiB.
JSON depth: 16; total values: 65,536; entries per container: 4,096.
String: 64 KiB; integer token: 640 digits.
Tabs: 64, with unique integer IDs; presets: 1,000; label: 256 bytes.
Relative output path: 1,024 bytes, plus platform component limits.
Active figure width: integer 30..100; out-of-range stored widths use the UI default. Unknown active fields, including nested model fields, are rejected; declared open dictionaries remain open within these bounds.
Controlled API failures include detail and a stable code. Statuses:
Status |
Meaning |
|---|---|
400 |
Malformed, duplicate-key or non-finite JSON; unsafe output path. |
403 |
Local access policy or unclassified effect refusal. |
404 |
Requested preset or route does not exist. |
409 |
Existing output, changed root/target, or invalid stored state. |
413 |
Request or next serialized state exceeds a limit. |
415 |
Unsupported media type, charset or content encoding. |
422 |
Wrong schema, unknown field, or invalid active parameter value. |
500 |
Trusted callback failed ( |
501 |
Safe filesystem primitives unavailable ( |
503 |
Filesystem I/O or partial-save failure. |
The package’s general OS-independent support does not guarantee these filesystem primitives. A scoped 501 leaves rendering and downloads usable; it does not fall back to unsafe writes. Callbacks execute serially and are trusted arbitrary Python: there is no hard CPU, memory or deadline guarantee, and the viewer is not a hostile-code sandbox.
Example¶
from dartwork_mpl.ui import ParamModel, run
from pydantic import Field
import matplotlib.pyplot as plt
import numpy as np
import dartwork_mpl as dm
class Params(ParamModel):
n: int = Field(default=100, ge=10, le=1000, description="Number of points")
alpha: float = Field(default=0.5, ge=0, le=1, description="Opacity")
def scatter_plot(params: Params):
dm.style.use("scientific")
fig, ax = plt.subplots(figsize=dm.figsize("13cm", "standard"))
ax.scatter(range(params.n), np.random.randn(params.n), alpha=params.alpha)
dm.simple_layout(fig)
return fig
run(scatter_plot) # opens the local viewer
dartwork_mpl.ui — Interactive figure viewer.
Provides a FastAPI-powered web UI that auto-generates parameter controls from function signatures or Pydantic models and renders matplotlib figures in real-time.
Quick start:
from dartwork_mpl.ui import ParamModel, run
def my_plot(n: int = 100, alpha: float = 0.5):
...
return fig
run(my_plot)