import type { PlotLayout } from "../layout/plot-layout"; export interface ZoomState { scaleX: number; scaleY: number; normTranslateX: number; normTranslateY: number; } /** * Runtime config for `ZoomController`. Not part of `ZoomState` — wired * by the owning chart at construction, not serialized with zoom level. * * `lockAxis` pins one axis at `scale=1, translate=0` and suppresses * wheel / pan updates on that axis, leaving the other axis fully * zoomable. Categorical axes are the typical candidates. * * `lockAspect` keeps `dataPerPixel` equal on both axes by padding the * narrower axis of the rendered domain to match the plot rect's aspect * ratio. Required by map plugins (Mercator preserves local angle, so * map glyphs distort under independent X/Y zoom), and useful for any * cartesian view where stretching points along one axis would lie * about the data. */ export interface ZoomConfig { lockAxis?: "x" | "y" | null; lockAspect?: boolean; } export declare const MAX_ZOOM = 100000; export declare const MIN_ZOOM = 1; export declare class ZoomController { private _scaleX; private _scaleY; private _normTX; private _normTY; private _baseXMin; private _baseXMax; private _baseYMin; private _baseYMax; private _lockAxis; private _lockAspect; private _xPinned; private _yPinned; private _element; private _layout; private _onUpdate; private _pointerDown; private _lastPointerX; private _lastPointerY; private _onWheel; private _onPointerDown; private _onPointerMove; private _onPointerUp; get lockedAxis(): "x" | "y" | null; get scaleX(): number; get scaleY(): number; set scaleX(v: number); set scaleY(v: number); get normTranslateX(): number; get normTranslateY(): number; set normTranslateX(v: number); set normTranslateY(v: number); get baseXRange(): number; get baseYRange(): number; /** * Update the base (full-data) domain. Each axis is handled * independently: * * - If the axis is at default (no user pan or zoom on this axis), * just swap the new base in — no further math needed. * - If the axis has been explicitly *pinned* (`pinAxis("x" | "y")`), * preserve its *absolute* visible center across the swap: * re-solve `_normT` so the data-space center is unchanged. * This is paused-frame-review semantics — the user has marked a * region of interest and wants it to stay put even as new data * flows in. No current caller pins, but the API is here so the * default rule below doesn't bake the choice in. * - Otherwise (the default — "follow"): keep `_normT` as-is and * just swap the new base. The visible window's *fractional* * position is preserved, so sliding windows slide with the data * and extending windows grow proportionally with the data. * * Per-axis handling matters because `_scaleX/_normTX` and * `_scaleY/_normTY` are independent. A user who panned X to scroll * through time should not force Y onto the rebase path — Y data * updates should still flow through cleanly. */ setBaseDomain(xMin: number, xMax: number, yMin: number, yMax: number): void; /** * Mark an axis as "pinned" so subsequent `setBaseDomain` calls * preserve its *absolute* visible center (paused-frame-review * semantics). Default-cleared on construction; both axes follow * data growth fractionally until explicitly pinned. */ pinAxis(axis: "x" | "y"): void; unpinAxis(axis: "x" | "y"): void; /** * Apply config. Called once by the chart during `setZoomController`. * Locking an axis snaps its `scale`/`translate` to identity so any * pre-existing state on that axis is cleared; subsequent wheel / * pan events leave the locked axis alone. */ configure(config: ZoomConfig): void; isXDefault(): boolean; isYDefault(): boolean; isDefault(): boolean; getVisibleDomain(): { xMin: number; xMax: number; yMin: number; yMax: number; }; attach(element: HTMLElement, layout: PlotLayout, onUpdate: () => void): void; updateLayout(layout: PlotLayout): void; detach(): void; reset(): void; serialize(): ZoomState; restore(state: ZoomState): void; }