Skip to content

API reference

Auto-generated from the public qtviz namespace (everything in qtviz.__all__).

Describe a plot once as immutable data (an Element), compose with * (overlay) and + (layout), then render through any backend — pyqtgraph, matplotlib, or webengine — swappable at runtime. qv.show(...) is the script one-liner; qv.View is the plain QWidget for applications.

qtviz.__version__ reports the installed version.

Elements

qtviz.Scatter

Bases: NormedRaster, Element

A point cloud — x/y positions with optional color/size encoding. norm/clim engage the shared colormap-normalization surface on the color_by mapping — the same vocabulary Image/Heatmap/Mesh use.

legend_entry

legend_entry(theme, index: int = 0)

A color_by Scatter already emits its own categorical/continuous Legend from the color mapping — contributing a swatch entry too would double-legend ([D60] risk #3), so it opts out of the contract.

select_xy

select_xy()

Brush/pick registration coordinates — replaces the isinstance tuples in backend event wiring.

channels

channels() -> dict

x/y always; color/size roles when bound to a data column, so the resolve pipeline materializes them for the native renderers.

qtviz.Curve

Bases: Element

A connected line through ordered x/y points; optionally stepped (step=) and/or with point markers (marker=); axis="y2" puts it on the twin right-hand axis.

legend_entry

legend_entry(theme, index: int = 0)

A color_by Curve emits its own categorical/continuous key — contributing a swatch too would double-legend ([D60] risk #3).

select_xy

select_xy()

Brush/pick registration coordinates — replaces the isinstance tuples in backend event wiring.

qtviz.Bars

Bases: Element

Bars — x categories (or numeric positions) with y heights. With by= each distinct group value becomes its own palette-colored series, laid out side-by-side (mode="grouped") or cumulatively ("stacked"); mode is meaningful only with by.

annotate= is the [D131] union: True labels each bar with its value ("auto"%g), a str is a [D86] format spec for that value, and a non-str accessor (col() expression, callable, or raw array) labels each bar from the data instead ([D136] accessor arm) — the one place the accessor union's plain-string form is taken by the format spec, so a column label is spelled annotate=col("name").

annotate_by property

annotate_by: Accessor | None

The [D136] accessor arm of annotate=None when annotate is off or a format spec (the fmt arm stays on .annotate as a str).

channels

channels() -> dict

x/y always; the by role when set, so the resolve pipeline materializes the category column for the renderers (same pattern as Scatter's color_by).

legend_entry

legend_entry(theme, index: int = 0)

A color_by Bars emits its own key ([D60] risk #3 rule).

qtviz.Histogram

Bases: Element

Binned frequency of a single raw value column. bins is a count or one of numpy's rule names ("auto", "fd", "sturges", …); the binning is computed once in core so every backend draws the same bars.

qtviz.Image

Bases: NormedRaster, Element

A 2-D array drawn as an image over explicit extent (also hosts RGBA rasters). norm/vmin/vmax/gamma engage the color surface — normalized once in core, colorbar/limits appear only when used.

resolved_grid

resolved_grid()

The one gridded accessor: the resolved GridData — replaces scattered element.data.grid() reach-through in the backends.

qtviz.Heatmap

Bases: NormedRaster, Element

A grid of tidy x/y cells shaded by a z value. Duplicate rows landing on one cell reduce through aggregator (the pre-0.4 implicit behavior was "last", kept in the vocabulary).

annotate= writes each aggregated value at its cell center — "auto" for %g, or any format spec (".1f", "{:.0%}", …). The text color is computed in core per cell (WCAG luminance of the cell's ramp color → theme foreground or background), and grids above ~400 cells warn and skip labels rather than smear unreadable text.

resolved_cell_labels

resolved_cell_labels(xs, ys, grid, theme)

The core-computed labels for an already-pivoted grid, or [] when the option is off — one call site per backend renderer.

qtviz.ErrorBars

Bases: Element

Error bars around yerr is symmetric, or (lo, hi) for asymmetric.

lo_limit= / hi_limit= name optional boolean columns; where true, that side's cap is drawn as an outward arrowhead — "the true value lies beyond" (mpl's lolims/uplims semantic). Heads reuse the shared quiver construction and refer to the direction axis, so limits are per-axis (direction="both" rejects them — use two elements).

resolved_limits

resolved_limits()

(err_lo, err_hi, arrows) from the resolved channels: limited sides are zeroed (the arrow shaft replaces the bar) and arrows is the core (shaft, head) polyline pair — None when no limit columns are set, so plain error bars render exactly as before.

qtviz.Spread

Bases: Element

A filled band between lo and hi. Exactly one of x/y positions it: x= runs the band in y over x positions (the confidence-interval case); y= runs it in x over y positions. lo/hi always name the band edges.

orient property

orient: str

Derived, not stored — with_() reconstructs from real fields.

lower

lower(ctx)

One Band mark, either orientation.

qtviz.BoxPlot

Bases: _Distribution

A five-number-summary box (median, quartiles, 1.5·IQR whiskers clipped to the data, outlier points) of value — one box, or one per by category.

qtviz.Violin

Bases: _Distribution

A kernel-density silhouette of value (Gaussian KDE, Scott's rule) — one violin, or one per by category.

qtviz.Area

Bases: Element

A series filled to the zero baseline. With by= each distinct group value becomes its own palette-colored band — layered translucently (mode="overlay") or cumulatively stacked ("stacked") — the shared grouping pattern, so stacking stays inside one element. Grouped data pivots on unique x (duplicate rows in a group sum, like Bars).

qtviz.Ecdf

Bases: Element

The empirical cumulative distribution of a raw value column — a step curve rising 0→1. The statistic is computed in core (_stats.ecdf, the house rule: qtviz decides the numbers) and drawn through each backend's post-step curve path.

lower

lower(ctx)

The empirical CDF as one post-step polyline from the shared core ecdf.

qtviz.Pie

Bases: Element

Proportional wedges of a non-negative value column, optionally labeled by a by category column; hole (0 ≤ hole < 1) makes a donut. Slice colors cycle the theme palette in row order.

Supported on matplotlib and webengine; pyqtgraph has no pie primitive, so negotiation routes around it (the RawFigure precedent — an element need not render everywhere).

qtviz.Contour

Bases: Element

Iso-value contours of a 2-D array over explicit extent (the Image data contract). levels is a count (uniform interior levels, computed once in core so every backend draws the same lines — ) or an explicit sequence of values. filled=True shades between levels; pyqtgraph draws lines only and warns on filled (capability-honest).

annotate= writes each level's value inline on its longest iso-line — True for %g, or any format spec. Placement (marching squares → arc-length midpoint, tangent angle normalized upright, and a background mask segment that breaks the line) is computed once in core, so every backend places identical labels — deliberately not mpl's native clabel (over engine fidelity).

resolved_grid

resolved_grid()

The one gridded accessor: the resolved GridData — replaces scattered element.data.grid() reach-through in the backends.

resolved_labels

resolved_labels()

The core-placed inline labels, or [] when off — one call site per backend renderer.

qtviz.Mesh

Bases: NormedRaster, Element

A 2-D value grid over explicit cell edges: values[j, i] fills the cell x[i]..x[i+1] × y[j]..y[j+1] — edges are the canonical contract (Heatmap owns the centers convention; Image the uniform-bounds one). Non-uniform spacing is the point: log-spaced frequency rows, irregular time bins. Shares the norm surface.

resolved_grid

resolved_grid()

The one gridded accessor: the resolved GridData — replaces scattered element.data.grid() reach-through in the backends.

check_shape

check_shape(values) -> np.ndarray

Render-seam guard: (len(y)-1, len(x)-1) values.

qtviz.Quiver

Bases: Element

arrows at (x, y) with components (u, v).

arrow_scale converts (u, v) units to data-space arrow length ("auto" sizes the largest arrow to ~90% of the field's typical cell); head_scale scales the barbs. Geometry is computed once in core

so every backend draws the identical field.

key= adds a reference key — a legend entry whose sample glyph is an arrow built by the same core construction as the field, labeled with the stated magnitude (key_label, e.g. key=10, key_label="10 m/s", or the bare number). Legend-based deliberately: qtviz does not model axes-fraction chrome, and the legend is where readers look for what a mark means.

legend_entry

legend_entry(theme, index: int = 0)

With key= set, the entry is the reference key: an arrow sample glyph plus a label stating the magnitude (key_label or the number). An element label folds in as "label (key)". Without a key this behaves like any labeled element.

lower

lower(ctx)

The whole cross-backend implementation: two NaN-separated polylines from the geometry, style resolved into one Stroke, legend routed through legend_entry() — every backend draws these marks through its _marks adapter.

resolved_segments

resolved_segments()

The shared core geometry from the resolved channels.

qtviz.Stem

Bases: Element

A stem (lollipop) series: a vertical line from baseline to each (x, y), capped by a marker head. The segment geometry is computed once in core (_geometry.stem_segments, ) and drawn as ONE pair-connected polyline plus a marker layer per backend — never an item per stem. Heads pick/hover like Scatter points; the element takes a palette slot and contributes a legend entry like any series.

resolved_segments

resolved_segments()

The shared core stem geometry from the resolved channels.

select_xy

select_xy()

Brush registration — heads select like Scatter points.

lower

lower(ctx)

ONE pair-connected polyline for every stalk (never an item per stem) + a pickable marker layer for the heads.

qtviz.Streamlines

Bases: Element

Streamlines of a vector field: u/v are accessors resolving to 2-D arrays on the Image/Contour grid contract, placed in data space by extent — deliberately grids, not per-point columns, because field topology needs the grid. The integrator runs once in core (_streamlines): seeds on a coarse mask grid (30×30 · density), RK4 both directions with bilinear interpolation, termination on domain exit / stagnation / an occupied mask cell — the mask enforces line spacing. Every backend draws the resulting polylines + one mid-line arrowhead each as two cheap NaN-separated curves.

Recorded v1 scope cuts: no color_by=speed gradient lines (pg cannot draw gradient polylines — the same honesty tier as Curve(color_by=); revisit together), no varying line width, no start-point control.

lower

lower(ctx)

Lines + heads as two NaN-separated polylines — exactly the Quiver primitive pair.

resolved_paths

resolved_paths()

The shared core integration: (paths, heads) polylines.

resolved_segments

resolved_segments()

The two NaN-separated polylines every backend draws — all lines joined, then all arrowheads joined: ((lx, ly), (hx, hy)).

qtviz.Inset

Bases: Element

indicator_window

indicator_window()

The child's declared x/y window ((x0, x1), (y0, y1)), or None when either lim is unset. [D154] I4b: an undeclared window is no longer a warn-skip — the backend seeds the rectangle from the inset's rendered (autoranged) window instead, and the live InsetIndicator controller tracks it from there.

indicator_rect staticmethod

indicator_rect(window)

The parent-side Rect marking window ([D154]) — the wave-1 annotation, so it lowers everywhere with zero new drawing code.

qtviz.RawFigure

Bases: Element

Host an existing Plotly / Bokeh / HoloViews figure unchanged (webengine only).

Sizing follows the figure's own configuration — a passthrough never mutates your figure. Plotly figures track the widget automatically (responsive config + a Qt-side resize nudge); a Bokeh figure keeps its declared size, so pass sizing_mode="stretch_both" when you want it to fill the widget.

Polar ([D119])

qtviz.PolarGrid

Bases: Element

Circular grid chrome ([D70]-class): rings concentric circles out to r_max, spokes radial lines, degree labels (or custom theta_labels — the radar case) and radius labels. Chrome, not data: it binds nothing, draws in the theme foreground, and contributes no legend entry.

lower

lower(ctx)

Rings as ONE NaN-separated PolygonMark, spokes as ONE pair-connected Polyline, labels as TextMarks.

qtviz.polar

Polar plotting ([D119] Option B — design/spikes/polar-spike-report.md).

Polar is a transform, not a projection: polar() rebinds a tabular element's x/y through (θ, r) → (r·cosθ, r·sinθ) before the data seam, PolarGrid draws the circular chrome (rings/spokes/labels) from marks — one lower(), zero backend edits — and wedge() builds annulus-sector points for Polygon polar bars. The surface stays rectilinear (pair with .opts(aspect=1, grid=False) and AxisSpec(ticks=())), so R1, events, brushes, ViewState, and backend switching are untouched by design. The recorded costs of that choice (hover reads x/y, no r-zoom semantics) live in the spike report.

PolarGrid

Bases: Element

Circular grid chrome ([D70]-class): rings concentric circles out to r_max, spokes radial lines, degree labels (or custom theta_labels — the radar case) and radius labels. Chrome, not data: it binds nothing, draws in the theme foreground, and contributes no legend entry.

lower
lower(ctx)

Rings as ONE NaN-separated PolygonMark, spokes as ONE pair-connected Polyline, labels as TextMarks.

polar

polar(element, *, theta: Accessor | None = None, r: Accessor | None = None)

Reinterpret a tabular element's position channels as polar ([D119] Option B): x becomes θ (radians, CCW from +x) and y becomes r unless theta=/r= name other bindings; the returned copy plots (r·cosθ, r·sinθ). Column-name / col() bindings compose into a serializable Expression (lazy-capable, value-equal); a callable or raw-array binding falls back to a callable pair ([D14] escape-hatch semantics). Pair with PolarGrid and .opts(aspect=1).

wedge

wedge(theta0: float, theta1: float, r0: float = 0.0, r1: float = 1.0, *, steps: int = 16) -> tuple[tuple[float, float], ...]

Annulus-sector outline points — the polar bar, ready for Polygon(wedge(...), fill=True): outer arc θ0→θ1 at r1, inner arc back at r0 (a point when r0 == 0).

qtviz.wedge

wedge(theta0: float, theta1: float, r0: float = 0.0, r1: float = 1.0, *, steps: int = 16) -> tuple[tuple[float, float], ...]

Annulus-sector outline points — the polar bar, ready for Polygon(wedge(...), fill=True): outer arc θ0→θ1 at r1, inner arc back at r0 (a point when r0 == 0).

Annotation & reference elements

qtviz.HLine

Bases: _Reference

A horizontal reference line at y, spanning the full x extent.

qtviz.VLine

Bases: _Reference

A vertical reference line at x, spanning the full y extent.

qtviz.Span

Bases: _Reference

A filled reference band from lo to hi — horizontal (orient="horizontal", a y-range across the full width) or vertical (orient="vertical", an x-range).

qtviz.Text

Bases: _Reference

A text note anchored at data coordinates (x, y); rotation is counter-clockwise degrees, halign/valign place the box relative to the point, and frame=True draws a theme-styled box behind it.

qtviz.Arrow

Bases: _Reference

An arrow between two data points, head at the end point (head="end", or "both"/"none") — the pointing half of annotate; pair with a Text for a callout.

qtviz.Rect

Bases: _Shape

An axis-aligned rectangle from (x0, y0) to (x1, y1).

qtviz.Ellipse

Bases: _Shape

An ellipse centered at (cx, cy) with radii rx/ry, rotated by angle degrees (counter-clockwise, about the center).

qtviz.Polygon

Bases: _Shape

A closed polygon through literal points (≥ 3 (x, y) pairs).

qtviz.RefLine

Bases: _Reference

An infinite reference line y = slope·x + intercept (the axline analog; HLine/VLine cover the axis-parallel cases). A straight data-space line isn't straight under log scales, so it warns-and-drops there.

Composition & View

Element is the base class of every element above — the type to use in your own annotations (def render(el: qv.Element) -> None). Node is the Element | Overlay | Layout union that View and show accept.

qtviz.Element

Bases: Immutable

channels

channels() -> dict

{role: accessor} — what the resolve pipeline materializes into role-keyed arrays. Override for non-uniform shapes (e.g. ErrorBars).

legend_entry

legend_entry(theme, index: int = 0)

This element's contribution to a multi-series legend ([D60]): its label + swatch, or None when it shouldn't contribute (no label, or — per override — it already emits its own Legend, like a color_by Scatter). index is the element's position in its Overlay, which decides the default palette slot exactly as the renderers do.

lower

lower(ctx)

This element's Mark lowering ([D122]) — Lowered | None. None (the default) means the element does not lower: every backend must register a native renderer for it, and type(el).lower is not Element.lower is the dispatch predicate backends use. Overrides run on resolved data and must be pure: marks in linear data space ([D121]), style resolved through ctx. A registered native renderer wins over lowering (the fast-path override).

select_xy

select_xy()

Brush/pick registration coordinates (x, y) | None for elements a backend wires natively ([D124] — the declared replacement for isinstance tuples in backend event code). Lowered elements carry this on Lowered.select_xy instead.

opts

opts(**kw)

[D133] surface configuration without abandoning the algebra: el.opts(title=…, x="t [s]", y=AxisSpec(scale="log")) wraps this element in a one-child Overlay carrying the options — the exact Overlay([el], options=…) construction, as sugar. Accepts the OverlayOptions keywords (title, x, y, y2, aspect, legend, background, grid); x/y/y2 take a label string or an AxisSpec. Chain on the result to refine.

qtviz.Node module-attribute

Node = Union[Element, 'Overlay', 'Layout']

qtviz.Overlay

Bases: Immutable

Same axes, layered. Built by *. Single-surface → single backend.

opts

opts(*, title: str | None | _Unset = UNSET, x: AxisSpec | str | None | _Unset = UNSET, y: AxisSpec | str | None | _Unset = UNSET, y2: AxisSpec | str | None | _Unset = UNSET, aspect: float | None | _Unset = UNSET, legend: bool | str | _Unset = UNSET, background: ColorSpec | None | _Unset = UNSET, grid: bool | _Unset = UNSET) -> Overlay

Field-wise options merge: only the fields you pass change (UNSET keeps; None clears where meaningful). For x/y/y2 a bare string merges into the existing spec's .label; a full AxisSpec replaces it. Chains — later calls win per field.

qtviz.Layout

Bases: Immutable

Side-by-side / grid / splitter / tabs / dock. Built by +. Children may use different backends. A grid built by Layout.mosaic additionally carries per-child cells(row, col, rowspan, colspan) — so panes can span.

Children may be named ([D145]): pass a mapping (Layout.grid({"price": p, "volume": v})), a mosaic (labels retained), or explicit labels=. Labels name the direct children — layout["price"] looks one up, layout.with_pane("price", node) swaps one immutably — and become the pane labels downstream (state capture/restore keys, view.pane(...), event scoping). Unlabeled panes get their flat index as a string.

__getitem__

__getitem__(key: str | int) -> Node

A child by explicit label (searching nested Layouts too) or by direct index. Only given labels resolve — default index labels are positional, so address those with the int form.

with_pane

with_pane(label: str, node: Node) -> Layout

Copy-with: the layout with the pane named label replaced by node ([D145]) — the declarative way to update one pane (view.set_root(root.with_pane("price", new_price))). Searches nested Layouts; raises KeyError if the label is absent.

opts

opts(*, title: str | None | _Unset = UNSET, rows: int | None | _Unset = UNSET, cols: int | None | _Unset = UNSET, spacing: int | _Unset = UNSET, link_x: bool | str | _Unset = UNSET, link_y: bool | str | _Unset = UNSET, tab_labels: Sequence[str] | None | _Unset = UNSET, dock_areas: Mapping[int, str] | Sequence[tuple] | None | _Unset = UNSET, width_ratios: Sequence[float] | None | _Unset = UNSET, height_ratios: Sequence[float] | None | _Unset = UNSET) -> Layout

Field-wise merge of the layout options (title here is the container suptitle). Only passed fields change; chains.

grid classmethod

grid(children: Sequence[Node] | Mapping[str, Node], *, cells: Mapping[str, Cell] | Sequence[Cell] | None = None, **kw) -> Layout

A grid — from a sequence, or a mapping whose keys become the pane labels ([D145]). cells= places panes explicitly ([D148]) — the programmatic answer to spans, symmetric with the mosaic's output:

Layout.grid({"ch0": a, "ch1": b, "summary": s},
            cells={"ch0": (0, 0, 1, 1), "ch1": (1, 0, 1, 1),
                   "summary": (0, 1, 2, 1)})

A cells mapping is keyed by label (mapping children required) and may reorder freely; a sequence aligns with the children. Overlap and span validity are checked like the mosaic parser's.

mosaic classmethod

mosaic(spec: MosaicSpec, mapping: Mapping[str, Node] | None = None, *, options: LayoutOptions | None = None, backend_hint: str | None = None, **panes: Node) -> Layout

A grid from an ASCII plan (the subplot_mosaic precedent):

Layout.mosaic("AAB\nCCB", A=curve, B=sidebar, C=table)
Layout.mosaic([["price",  "book"],
               ["volume", "book"]], price=p, volume=v, book=ob)

String form: each distinct character is one pane (spanning its rectangle); . is a hole. List form ([D145]): arbitrary string labels, None (or ".") a hole. Panes come as keywords or as a mapping (for labels that aren't identifiers); every label in the spec must be given, and vice versa. Labels are retained — they key state capture/restore, view.pane(...), and layout[label].

qtviz.View

Bases: QWidget

The widget that renders a node tree — the bridge from declarative elements to a live Qt widget.

View(root) renders an Element, Overlay, or Layout (or a reactive Signal[Node], re-rendering on change) through the chosen backend and IS a plain QWidget: embed it in any PySide6 layout. Pan/zoom/selection state survives set_backend() switches; on() subscribes to the typed interaction events; lazy data resolves off the GUI thread with the last render kept visible. Constructing a View before a QApplication exists is safe — it creates one on demand.

handle property

handle: Any

The live backend RenderHandle for the current render (or None while resolving) — the [D53] escape hatch to backend-native objects.

root property

root: Any

The rendered node tree — pair with Layout.with_pane for the declarative per-pane update: view.set_root(view.root.with_pane("price", new_node)).

panes property

panes: tuple

Every pane of the current render, flattened in layout order (empty while a lazy render is resolving).

set_backend

set_backend(name_or_backend: str | Backend) -> None

Re-render through a different backend — by registered name ("pyqtgraph", "matplotlib", "webengine", "auto") or a Backend instance. The current pan/zoom/selection state carries over; event subscriptions survive the switch.

set_theme

set_theme(theme: Theme) -> None

Re-render with a different Theme (e.g. Theme.dark()).

set_root

set_root(root: Any) -> None

Replace the rendered node tree (an Element/Overlay/Layout) and re-render. For continuously-updating data prefer a qv.stream source or a reactive Signal[Node] root over repeated set_root.

on

on(event_type: type, cb: Callable, *, throttle_ms: int | None = None, source=None, pane=None) -> Disposable

Subscribe to a typed event. source= ([D134]) filters by the emitting element — pass an Element (or its id), or a sequence of either; it replaces the e.source_id == el.id lambda idiom. pane= ([D149]) filters by the emitting surface — a pane label ([D145]; "0", "1", … when unlabeled) or a sequence of labels: view.on(qv.RangeEvent, on_zoom, pane="price"). Both filters compose.

pane

pane(key: str | int | None = None)

The current render's PaneHandle for one surface ([D147]) — by label ([D145]), by flat index, or (key=None) the only pane. The "Axes of qtviz": interaction-side verbs only —

view.pane("price").set_range(x=(0, 10))   # programmatic pan/zoom
view.pane("price").autorange()
view.pane("price").select(x0, y0, x1, y1) # programmatic brush
view.pane("price").capture()              # ViewState, data space
view.pane("price").native                 # PlotItem / Axes / host
view.pane("price").elements               # ids on this surface

Facades wrap the current render: fetch fresh rather than caching — a pane kept across a rebuild goes dead (pane.alive) and raises DisposedError. Describe-side config (title, scale, …) stays on the node (.opts() / set_root). Raises while a lazy render is still resolving (View.loading).

native

native(element_id: str) -> Any

The live backend primitive for an element (handle.native, [D53]) — the escape valve for backend-native work (ROIs, crosshairs, native signals) the typed events don't expose. None if not rendered. Non-portable by design.

qtviz.show

show(root, *, title: str | None = None, size: tuple[int, int] = (960, 640), backend: str | Backend = 'auto', theme: Theme | None = None, toolbar: bool = False, block: bool = True) -> View

The one-liner for scripts: wrap root in a View (an existing View passes through), size/title/show it, and — with block=True — run the Qt event loop. Returns the View either way, so block=False hands back a live widget for embedding or tests. View itself stays a plain QWidget for real applications.

Constructing widgets before calling show is safe: View ensures a QApplication exists, so qv.show(qv.View(...)) and qv.show(qv.Scatter(...)) both just work. root may also be a zero-argument callable returning the node or a ready View — a convenience for keeping build()-style examples importable, no longer a requirement. toolbar/backend/theme apply when show constructs the View; a passed-in View keeps its own settings.

Panes — downstream use of each surface

After render, each surface (grid cell, tab, splitter pane — or the whole plot of a single-surface render) is addressable as a pane ([D145]/[D147]): view.pane("price") returns a live PaneHandle — the "Axes of qtviz" — scoped to interaction-side verbs (set_range, autorange, select, capture/restore, native, elements). Describe-side configuration stays on the node (.opts(), Layout.with_pane + set_root). Whole-render state is a LayoutState — ordered (label, ViewState) pairs, matched by label across rebuilds and backend switches. These are returned types, importable from qtviz.core.backend ([D135] return-type convention).

qtviz.core.backend.PaneHandle

One surface of a live render — the "Axes of qtviz" ([D147]). A thin facade over the current render, scoped strictly to interaction-side concerns: view ranges, programmatic brush, the native escape hatch. Describe-side config (title, scale, …) never flows through here — that is .opts() on the node; one-way data flow keeps rebuilds reasoned about.

Facades are built fresh by RenderHandle.panes() on every call and go stale with the render they wrap — fetch fresh, never cache across a rebuild. All widget-touching methods are GUI-thread-only. The base class is an inert single pane, so any single-surface backend that predates the protocol is compliant with zero changes ([D125]).

alive property

alive: bool

Whether the render this pane wraps is still the live one. A pane kept across a rebuild/backend switch goes dead — its widgets are Qt-disposed — and every state-touching call raises DisposedError.

native property

native: Any

The live backend surface primitive — a pg PlotItem, an mpl Axes, the webengine host ([D53]). Non-portable by design; None if unknown.

elements property

elements: tuple[str, ...]

Ids of the elements rendered on this surface (feed View.on(source=…)).

capture

capture() -> ViewState

This surface's current state, data space (R1).

restore

restore(state: ViewState) -> None

Apply stateNone fields leave the current range untouched.

set_range

set_range(*, x: tuple[float, float] | None = None, y: tuple[float, float] | None = None, y2: tuple[float, float] | None = None) -> None

Programmatic pan/zoom sugar — data-space (lo, hi) per axis; omitted axes keep their current range. The same interaction-state class of change as a user drag, so events/rasters react identically.

autorange

autorange() -> None

Reset this surface to fit its data (the double-click/home verb).

select

select(x0: float, y0: float, x1: float, y1: float) -> None

Programmatic brush: emits the same per-element SelectEvents a Shift-drag over (x0, y0)–(x1, y1) (data space) would.

export

export(fmt: str, path, *, dpi: float | None = None, transparent: bool = False) -> Path

Write just this pane to path ([D150] — the per-pane answer to [D72]'s whole-container raster). Formats follow the backend's own export capabilities.

qtviz.core.backend.LayoutState dataclass

Portable interaction state for a whole render ([D150]): ordered (pane label, ViewState) pairs, one per surface. Restore matches by label — same label = same role, so state survives backend switches and root swaps; labels the new render doesn't have drop silently (a changed dashboard shape is not an error). Default pane labels are index strings ("0", "1", …), so unlabeled layouts degrade to positional matching.

For the single-surface case (x_range & co.) the first pane's fields pass through, so handle.capture_state().x_range keeps reading naturally.

first property

first: ViewState

The first pane's state — the whole state of a single-surface render.

get

get(label: str) -> ViewState | None

The named pane's state, or None.

Data binding

qtviz.col

col(name: str) -> Col

Reference a data column by name in an Expression (e.g. col("a") - col("b")).

qtviz.lit

lit(value) -> Lit

A literal scalar inside an Expression (broadcast against the columns).

qtviz.tabular

tabular(data: Any) -> DataRef

Force data to a tabular ref — e.g. a 1-D xarray DataArray as columns.

qtviz.gridded

gridded(data: Any) -> DataRef

Force data to a gridded ref — e.g. a plain 2-D array as an image grid.

qtviz.set_raster_threshold

set_raster_threshold(n: int) -> None

raster='auto' rasterizes a Scatter once its point count exceeds n.

qtviz.set_raster_size

set_raster_size(width: int, height: int) -> None

Default datashader canvas resolution for static rasterization.

qtviz.stream

The streaming data source ([D76], milestone-0.6-live §1).

StreamRef is a mutable, append-able tabular DataRef with a ring-buffer rolling window. It notifies through the base contract's subscribe seam — designed in Phase 1, stubbed NOOP everywhere until now. Pure Python + numpy, no Qt: append is thread-safe under a lock; whoever subscribes owns the GUI-thread marshaling (the View's StreamBinding, increment 2).

Purity (R1/[D38]): the Element holding a StreamRef stays immutable — it holds a handle to changing data, exactly like a pandas frame the user mutates. The only new power is that this handle tells its subscribers.

StreamRef

Bases: TabularRef

Append-able named columns with an optional rolling window (max rows — old rows drop as new ones arrive, the spec §12 "stream-time auto-rolling" deferral lifted). is_lazy stays False: reads are cheap in-memory slices; nothing here needs the async resolve path.

append
append(**columns: Any) -> None

Append rows (scalars or equal-length 1-D arrays for every column). Thread-safe; fires each subscriber once per append (after the write).

resolve_channels
resolve_channels(channels: dict[str, Any], *, who: str | None = None) -> dict[str, np.ndarray]

Snapshot the buffer under the lock, then resolve accessors against the copy — a render never sees a torn append, and later appends never mutate an already-resolved frame.

stream

stream(columns: dict[str, Any], *, window: int | None = None) -> StreamRef

A live, append-able data source: stream({"t": float, "v": float}, window=100_000). Bind it to any element like a dict/DataFrame; views on it update as you append (from any thread). window keeps the last N rows.

Encoding & styling

qtviz.Color

Immutable canonical color. str / tuple inputs auto-convert.

qtviz.Palette

Bases: Immutable

An ordered, cycling sequence of colors used for categorical encoding.

at

at(t: float) -> Color

t in [0, 1]. Discrete: bucketize. Continuous: linear interpolation.

qtviz.palettes module-attribute

palettes = _PaletteRegistry()

qtviz.Theme

Bases: Immutable

Backend-agnostic styling (background, axes, palette) applied to any View. Construct directly or via Theme.light() / dark() / from_qt_app().

qtviz.set_default_theme

set_default_theme(theme: Theme | None) -> None

The app-level default a View(theme=None) consults — symmetry with set_default_backend. None restores the built-in light theme.

qtviz.Norm

The one colormap-normalization spec, shared by every element that maps values to color (Image, Heatmap, Mesh, Scatter.color_by). The string shorthand (norm="log") covers the 90% case; Norm carries only the transform's own parameters (gamma for power, linthresh for symlog, levels for boundary) — the value-range clamp is the separate clim=. A parameter set for a kind that ignores it raises: accept-then-ignore is the one sin this library refuses.

qtviz.OverlayOptions

Bases: Immutable

Shared-surface options for an Overlay: title, per-axis AxisSpec (x/y, plus the twin y2, ), aspect, legend toggle + position, background, grid toggle.

`x`/`y`/`y2` take an `AxisSpec` or — the shorthand — a bare string
meaning the axis label; `AxisSpec.label` is the one canonical home. `y2`
configures the right-hand axis that appears when any series element sets
`axis="y2"` — it is ignored when none does. `legend` is one union field

: True/"auto" places automatically, "right"/"top" place explicitly, False/"none" hides everything.

legend_enabled property

legend_enabled: bool

The one switch backends consult: legend=False/"none" hides every legend on the surface (aggregated and color-mapping).

legend_position property

legend_position: str

The placement backends translate: "auto" unless placed explicitly.

qtviz.LayoutOptions

Bases: Immutable

Arrangement options for a Layout: rows/cols, spacing, axis linking (link_x/link_yTrue links all panes, "col"/"row" link within each grid column/row, [D146]), tab/dock labels, relative column/row sizes (width_ratios/height_ratios, ), and a container title (the figure suptitle).

qtviz.AxisSpec

Bases: Immutable

Per-axis surface configuration — label, scale, limits, ticks.

scale (linear|log|symlog|time), declarative lim, invert; tick_format is "auto", "eng", a Python format-spec (".2f", ",d", ".0%"), a strftime pattern, or a one-field template ("${:,.0f}", "{:.0f} ms"); explicit ticks/tick_labels pin the positions/labels; minor=True requests minor ticks and tick_rotation rotates the labels. All positions are data space (R1). A backend that can't render the requested scale warns and falls back to linear.

Events

qtviz.Event dataclass

Base of every typed interaction event. source_id names the emitter — the element for element events (pick/select/hover), the pane label for surface events (range/tap). pane ([D149]) always carries the pane label of the surface the event came from ([D145]; "0", "1", … when unlabeled) — subscribe with view.on(EventType, cb), filtered by element via source= or by pane via pane=.

qtviz.RangeEvent dataclass

Bases: Event

The visible axis ranges changed (pan/zoom): x/y are the new (lo, hi) data-space bounds.

qtviz.PickEvent dataclass

Bases: Event

A single data point was clicked: its point_index in the element's data plus the data-space x/y of the point.

qtviz.SelectEvent dataclass

Bases: Event

A brush/box selection completed: the selected indices into the element's data and the data-space bounds (x0, y0, x1, y1).

qtviz.HoverEvent dataclass

Bases: Event

The pointer moved over the plot: nearest point_index (or None off the data) and the data-space cursor position. On a datashaded raster value carries the aggregated count/mean under the cursor.

qtviz.TapEvent dataclass

Bases: Event

A click on empty plot space (no point hit): the data-space x/y of the click.

Reactive

qtviz.signal

S-style reactivity — Signal / derived / effect / batch (spec §9; D38–D40).

Auto-tracking: while a derived/effect body runs, it sits on the _observers stack and every Signal.get() registers it as a subscriber — so dependencies are discovered automatically. Synchronous, GUI-thread-only (a cross-thread set marshals via run_on_gui); simple propagation + batch() to coalesce, not topological/glitch-free (D39). The whole graph is tiny and runs on one thread, so no locks.

Signal

Bases: Generic[T]

A reactive cell: read with get() (auto-tracks), write with set().

signal

signal(initial: T) -> Signal[T]

Create a writable reactive cell holding initial.

derived

derived(fn: Callable[[], T]) -> Signal[T]

A read-only signal that recomputes fn when any signal it reads changes.

effect

effect(fn: Callable[[], None], *, owner=None) -> Disposable

Run fn now and re-run it on any tracked-signal change; dispose to stop (auto-disposed when owner QObject is destroyed).

batch

batch(fn: Callable[[], None]) -> None

Run fn, coalescing all sets into a single notification pass.

qtviz.derived

derived(fn: Callable[[], T]) -> Signal[T]

A read-only signal that recomputes fn when any signal it reads changes.

qtviz.effect

effect(fn: Callable[[], None], *, owner=None) -> Disposable

Run fn now and re-run it on any tracked-signal change; dispose to stop (auto-disposed when owner QObject is destroyed).

qtviz.batch

batch(fn: Callable[[], None]) -> None

Run fn, coalescing all sets into a single notification pass.

qtviz.Signal

Bases: Generic[T]

A reactive cell: read with get() (auto-tracks), write with set().

HoloViews / hvplot adapter

qtviz.from_holoviews

from_holoviews(obj: Any)

Translate a HoloViews element/container into a qtviz Node.

Returns a native Element/Overlay/Layout where qtviz models the type, else a RawFigure hosted on the webengine backend. A DynamicMap with kdims returns a Signal[Node] (seeded at default kdim values — use from_holoviews_dmap to drive the kdims); a stream-only DynamicMap renders its current frame statically with a warning (L1). Raises UnsupportedHoloViewsElement only if even the RawFigure fallback cannot apply.

qtviz.from_holoviews_dmap

from_holoviews_dmap(dm: Any) -> DMapBinding

Build a DMapBinding from a HoloViews DynamicMap (one-way, L1).

One writable Signal per kdim (seeded by _kdim_default) feeds a derived Signal[Node] that resolves dm[values] and runs the static translation on each frame. No qtviz→hv write-back (that is Level 2, deferred).

qtviz.from_hvplot

from_hvplot(data: Any, kind: str, **kwargs: Any)

Translate an hvplot call into a qtviz Node (Path A).

data.hvplot(kind=kind, **kwargs) returns a HoloViews object (Element / Overlay / often a DynamicMap), which from_holoviews already consumes — so this is a thin convenience. hvplot is an optional extra (qtviz[hvplot]), imported lazily here.

kind and **kwargs are forwarded verbatim to hvplot; they are hvplot's contract, not qtviz's stable surface, and are not validated here.

Errors

QtvizError is the base of every error qtviz raises on purpose — except qv.QtvizError catches every deliberate rejection. The full taxonomy (validation, negotiation, adapters, missing dependencies) lives in qtviz.errors.

qtviz.QtvizError

Bases: Exception

Base for every error qtviz raises on purpose.

Backend selection

qtviz.set_default_backend

set_default_backend(name: str) -> None

Set the backend used when a View is created with backend="auto" and no hint.

Validated eagerly: an unregistered name raises here rather than surfacing later, deep inside negotiation, far from this call.

Backend authors (qtviz.backends)

The extension namespace ([D125]): third-party backends register through the qtviz.backends entry-point group, and the author-facing contracts live here — qtviz.backends.Capabilities (the honesty declaration) and qtviz.backends.set_backend_priority (the auto-negotiation preference order). See Backends for the full extension guide.