Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·part ofmodule widget.module·originates fromworld 07158574-5…

_viewport

Pan/zoom viewport helper for canvas-hosted charts. Persists `{ xMin, xMax, yMin, yMax }` in `ui.widgetState(plotId, "viewport")` so the bounds survive across renders; the first render seeds from `defaultBounds`. Subsequent renders read the cached state; pan/zoom handlers (`viewpo…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

_viewport

Pan/zoom viewport helper for canvas-hosted charts. Persists { xMin, xMax, yMin, yMax } in ui.widgetState(plotId, "viewport") so the bounds survive across renders; the first render seeds from defaultBounds. Subsequent renders read the cached state; pan/zoom handlers (viewport:pan, viewport:zoom) mutate the state in place and write it back to widgetState. Underscore prefix marks it as a package-internal helper — not exported from zui.widget and not part of the Z.* surface.

Exports

  • Viewport.make(plotId: string?, rect: Rect?, defaultBounds: Bounds?) -> ViewportInstance — construct a viewport over rect. plotId enables widgetState persistence.
  • vp:dataToScreen(x: number, y: number) -> (number, number) — data → canvas-local pixels.
  • vp:screenToData(sx: number, sy: number) -> (number, number) — canvas-local pixels → data.
  • vp:pan(dxScreen: number, dyScreen: number, axisAllow: AxisFilter?) — pan and write-back.
  • vp:zoom(factor: number, anchorScreen: ScreenPoint?, axisAllow: AxisFilter?) — zoom around anchor and write-back.
  • vp:setBounds(bounds: Bounds) — replace bounds and write-back.
  • Viewport.bounds(vp) -> Bounds — snapshot bounds as a fresh table.

Types:

  • Rect = { x: number, y: number, width: number, height: number } — canvas-local plot body.
  • Bounds = { xMin: number, xMax: number, yMin: number, yMax: number } — data-space.
  • ScreenPoint = { x: number, y: number }
  • AxisFilter = { x: boolean?, y: boolean? }
  • ViewportInstance — instance shape; carries rect, bounds fields, invertX/Y, and the bound methods.

Usage

local Viewport = require("@builtin::modules.zui.widget._viewport")

local vp = Viewport.make("plot1",
    { x = 0, y = 0, width = 200, height = 100 },
    { xMin = 0, xMax = 100, yMin = 0, yMax = 1 })

-- Inside onDrag handler:
vp:pan(dragDx, dragDy)

-- Inside onScroll handler:
vp:zoom(1 + scrollAmount * 0.1, { x = mx, y = my })

-- Reset:
vp:setBounds({ xMin = 0, xMax = 100, yMin = 0, yMax = 1 })

Notes

  • Coordinate convention: origin top-left, +y down. yMax in data space maps to rect.y (top of viewport) so the cursor sits above the data point during pan.
  • Persistence is opt-in — passing plotId = nil skips widgetState reads / writes; bounds live only on the instance for the duration of the call.
  • Caller can reset by clearing widgetState explicitly: ui.widgetStateSet(plotId, "viewport", nil).
  • Viewport.bounds(vp) is a free function (no colon) — it's a thin snapshot helper, useful when you want a fresh bounds table without mutating the instance.
  • invertX / invertY flip the axis mapping in both dataToScreen and screenToData; default false for both.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

ZuiViewport Module Pan/zoom viewport helper for canvas-hosted charts. Persists `{ xMin, xMax, yMin, yMax }` in `ui.widgetState(plotId, "viewport")` so the bounds survive across renders; the first render seeds from `defaultBounds`. Subsequent renders read the cached state; pan/zoom handlers (`viewport:pan`, `viewport:zoom`) mutate the state in place and write it back to widgetState. Coordinate convention: - `rect` is the canvas-local pixel rectangle reserved for the plot body (excludes axis labels and padding). - `dataToScreen(x, y)` returns canvas-local pixels with origin top-left (+y down); yMax in data space maps to rect.y (top). - `screenToData(sx, sy)` is the inverse. Pan: shift bounds by the data-space equivalent of a screen-space delta. `viewport:pan(dxScreen, dyScreen)` is the typical onDrag handler — passing the raw drag delta moves bounds the "right way" so the cursor stays anchored to the same data point. Zoom: scale bounds around an anchor point. `viewport:zoom(factor, anchorScreen)` keeps `anchorScreen` fixed under the cursor while the surrounding range narrows (factor > 1, zoom in) or widens (factor < 1, zoom out). Caller can reset by clearing the widgetState entry: `ui.widgetStateSet(plotId, "viewport", nil)` Consumers: local Viewport = require("modules.deprecated.zui.widget._viewport") local vp = Viewport.make("plot1", { x=0, y=0, width=200, height=100 })

make(plotId: string?, rect: Rect?, defaultBounds: Bounds?) → ViewportInstance

Constructs a viewport for `plotId` over the given canvas-local `rect` (the rectangle reserved for the plot body, excluding axes / labels). `defaultBounds` seeds widgetState on first call; subsequent calls re-hydrate the cached bounds.

argtypedescription
plotIdstring?Widget id for `ui.widgetState`. Pass nil for a non-persisted viewport.
rectRect?`{ x, y, width, height }` in canvas-local pixels. Default `{0,0,200,100}`.
defaultBoundsBounds?`{ xMin, xMax, yMin, yMax }`. Default `{0,1,0,1}`.

examples

local vp = Viewport.make("plot1", { x=0, y=0, width=200, height=100 })

_writeBack( ) → void

Internal: persist current bounds back to widgetState. No-op when plotId / FFI binding is absent.

dataToScreen(x: number, y: number) →

Map `(x, y)` in data space to canvas-local pixels. yMax maps to the top of the viewport (rect.y) so the cursor sits above the data point. Honors `invertX` / `invertY` for flipped axes.

argtypedescription
xnumberData-space X coordinate.
ynumberData-space Y coordinate.

examples

local px, py = vp:dataToScreen(50, 0.5)

screenToData(sx: number, sy: number) →

Inverse of `dataToScreen` — map `(sx, sy)` in canvas-local pixels to data space. Honors `invertX` / `invertY`.

argtypedescription
sxnumberCanvas-local pixel X.
synumberCanvas-local pixel Y.

examples

local x, y = vp:screenToData(100, 50)

pan(dxScreen: number, dyScreen: number, axisAllow: AxisFilter?) → void

Pan by a screen-space delta. Positive `dxScreen` shifts the view rightward (bounds shift left so the same data point stays under the cursor). Pass `axisAllow = { x, y }` to filter per axis. Writes the new bounds back to widgetState.

argtypedescription
dxScreennumberX delta in screen pixels.
dyScreennumberY delta in screen pixels.
axisAllowAxisFilter?Optional `{ x: boolean?, y: boolean? }` filter (default both `true`).

examples

vp:pan(20, 0)            -- pan right by 20 px
vp:pan(0, 5, { y = true }) -- vertical only

zoom(factor: number, anchorScreen: ScreenPoint?, axisAllow: AxisFilter?) → void

Zoom by `factor` (> 1 zoom in, < 1 zoom out) around an anchor in screen space. `axisAllow = { x, y }` filters per axis. Writes the new bounds back to widgetState.

argtypedescription
factornumberScale factor; `factor > 1` zooms in, `factor < 1` zooms out. `<= 0` is no-op.
anchorScreenScreenPoint?Optional screen-space anchor `{ x, y }` (default: rect center).
axisAllowAxisFilter?Optional `{ x: boolean?, y: boolean? }` filter (default both `true`).

examples

vp:zoom(1.1, { x = 100, y = 50 })

setBounds(bounds: Bounds) → void

Reset to a given bounds table. Useful for "fit data" / double-click. Writes the new bounds back to widgetState.

argtypedescription
boundsBounds`{ xMin, xMax, yMin, yMax }`.

examples

vp:setBounds({ xMin = 0, xMax = 100, yMin = 0, yMax = 1 })

bounds(self: ViewportInstance) → Bounds

Snapshot the current bounds as a `Bounds` table. Called as a free function (`Viewport.bounds(vp)`), not via colon syntax.

argtypedescription
selfViewportInstanceThe viewport instance.

examples

local b = Viewport.bounds(vp)
⌬ Types
Rect = { x: number, y: number, width: number, height: number }Bounds = { xMin: number, xMax: number, yMin: number, yMax: number }ScreenPoint = { x: number, y: number }AxisFilter = { x: boolean?, y: boolean? }ViewportInstance = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

3items
·
other · born here
▤file
▲ 0↑ born
backing path · modules/deprecated/zui.module/widget.module/_viewport.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.