_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…
_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 overrect.plotIdenables 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; carriesrect, 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,
+ydown. yMax in data space maps torect.y(top of viewport) so the cursor sits above the data point during pan. - Persistence is opt-in — passing
plotId = nilskips 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/invertYflip the axis mapping in bothdataToScreenandscreenToData; defaultfalsefor both.
Scoped to this part · feeds back into the world's score.