import { type VegaLiteSpec, type ZoomChannel } from "./specs"; import { type ChartTheme } from "./theme"; import type { ThemeMode } from "./types"; import { type VegaEmbed } from "./vega_loader"; /** A scenegraph item's box, in the plot rectangle's coordinate frame. */ export interface Bounds { x1: number; x2: number; y1: number; y2: number; } interface SceneItem { role?: string; bounds?: Bounds; items?: SceneItem[]; } /** Minimal shape of the vega-embed result we depend on. */ export interface EmbedResult { view: { data(name: string, values?: unknown[]): unknown; resize(): { run(): unknown; }; run(): unknown; /** Top-left of the plot rectangle within the rendered element. */ origin(): number[]; signal(name: string): unknown; /** Named scale: data value → plot-local pixel (undefined if missing). */ scale(name: string): ((value: number) => number) | undefined; scenegraph(): { root: SceneItem; }; addEventListener(type: string, handler: (e: unknown, item?: unknown) => void): void; finalize(): void; }; } /** * Which axis, if any, the pointer sits on. All coordinates share the plot * rectangle's frame: its interior is `[0, width] × [0, height]`, so an axis * governing x lies above or below it and one governing y lies beside it. * * `axes` must be the `role: "axis"` scenegraph items. Vega emits one per axis * plus one per grid; a grid's box *is* the plot edge, so its centre lands on * the boundary and it classifies as neither gutter. Legends carry * `role: "legend"` and never reach here — a legend drawn under the x axis * (gantt) must stay inert. */ export declare function axisChannelAt(axes: Bounds[], x: number, y: number, width: number, height: number): ZoomChannel | null; /** * Shared lifecycle for every chart: lazy vega-embed load, spec→embed render, * cheap streaming data updates (`view.data(name, rows)`), rAF-debounced * re-embed on resize, `` theme tracking, and click→datum * wiring. Subclasses implement {@link buildSpec} (produce a Vega-Lite spec — * the portable intermediate language) and {@link datasets} (current rows per * named dataset), and optionally {@link onDatum} (turn a clicked datum into a * typed event). */ export declare abstract class VegaChart { protected readonly container: HTMLElement; protected themeMode: ThemeMode; protected presetName: string | undefined; protected disposed: boolean; protected embed: VegaEmbed | null; protected result: EmbedResult | null; protected readonly mountPromise: Promise; private resizeObserver; private themeObserver; private detachAxisZoom; /** The element Vega rendered into, and whether its spec declares zoom params. */ private rendered; private zoomable; private resizeRaf; private lastW; private lastH; private renderInFlight; constructor(container: HTMLElement, themeMode: ThemeMode, presetName?: string); /** * Keep the host on its allocated layout box. Inline styles beat the * stylesheet vega-embed injects after page CSS (same specificity, later * rule would win). We only force `display` / overflow / box-sizing — * position and size stay under the host author's control (absolute fill, * flex child, aspect-ratio card, …). */ private pinHostLayout; /** Resolves once the initial render completes. */ ready(): Promise; resize(): Promise; dispose(): void; /** Build the Vega-Lite spec for the current state + theme. */ protected abstract buildSpec(theme: ChartTheme, size: { width: number; height: number; }): VegaLiteSpec; /** Current rows per named dataset referenced by the spec. */ protected abstract datasets(): Record; /** Handle a clicked datum. Default: no-op. */ protected onDatum(_datum: Record): void; private mount; /** Full re-embed. Serialised so a theme/resize fire can't interleave. */ protected render(): Promise; private renderImpl; /** Hook after a successful embed. Default no-op. */ protected afterRender(_result: EmbedResult): void; /** * Wheel interaction for scale zoom: * - Axis gutter → that axis alone (classic molplot behaviour) * - Plot interior → both continuous axes (embed-friendly; chat ScrollAreas * no longer need Shift+wheel for a first useful zoom) * - Shift+wheel still works via the VL filter without flags * * Bound once on `container` (vega-embed never replaces it). Non-passive so * we can `preventDefault` and stop the host page/chat from scrolling when * a zoom actually applies. */ private bindAxisHoverZoom; /** Push the current datasets into a freshly embedded view. */ private feed; /** Cheap in-place data swap (streaming path); re-embeds if no live view. */ protected setData(name: string, rows: unknown[]): Promise; /** Await mount then re-embed — the entry point full mutators call. */ protected rerender(): Promise; protected dims(): { width: number; height: number; }; /** Whether a ResizeObserver measurement requires a full re-embed. */ protected resizeChanged(previous: { width: number; height: number; }, next: { width: number; height: number; }): boolean; private setupResizeObserver; private setupThemeObserver; } export {};