@graphysdk/viz-engine 0.0.1-plugins.0 → 0.0.1-plugins.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,24 +1,44 @@
1
1
  import { internal } from 'arquero';
2
2
 
3
+ /**
4
+ * The aesthetic mapping: data columns (or constants) bound to visual channels. Seeded globally by
5
+ * `createSpec({...})` and overridable per layer via `geom.x({ aes })`. Every field is optional; a channel
6
+ * left unset is simply not driven by data. Each channel binds to the like-named scale (e.g. `color` →
7
+ * `scale.color.*`, `size` → `scale.size.*`); position channels additionally require `scale.x()`/`scale.y()`
8
+ * to be declared (they are NOT auto-inferred).
9
+ */
3
10
  export declare interface AesMapping {
11
+ /** Horizontal position. Binds to `scale.x`. Categorical/temporal x needs `scale.x.discrete()`. */
4
12
  x?: AestheticValue;
13
+ /** Vertical position. Binds to `scale.y` (or `scale.ySecondary` when the layer sets `yScaleType: 'secondary'`). */
5
14
  y?: AestheticValue;
15
+ /** Text drawn on the observation (data labels, slice labels). Binds to no scale; rendered as-is. */
6
16
  label?: AestheticValue;
17
+ /** Series/category color. Binds to `scale.color.*`; mapping a column splits the geom into series and shows a legend. */
7
18
  color?: AestheticValue;
19
+ /** Mark size — point radius / bubble area. Binds to `scale.size.continuous({ range })`. */
8
20
  size?: AestheticValue;
21
+ /** Per-observation opacity in `[0,1]` after scaling. Binds to `scale.alpha.*`. */
9
22
  alpha?: AestheticValue;
23
+ /**
24
+ * Explicit grouping key for which observations form one connected mark (one line/area). Defaults to the
25
+ * `color` column when unset; set it to split lines/areas without colouring them differently.
26
+ */
10
27
  group?: AestheticValue;
28
+ /** Stroke thickness as a data-driven channel. Binds to `scale.strokeWidth.*`; for a fixed width use line `params.lineWidth`. */
11
29
  strokeWidth?: AestheticValue;
30
+ /** Dash style (`'solid'`/`'dashed'`/`'dotted'`). Binds to `scale.lineType.discrete({ domain, range })`; great for actual-vs-forecast lines. */
12
31
  lineType?: AestheticValue;
13
32
  }
14
33
 
34
+ /** The set of built-in aesthetic channel names — the keys of {@link AesMapping}. */
15
35
  export declare type AestheticKey = keyof AesMapping;
16
36
 
17
37
  /**
18
- * Aesthetic value can be:
19
- * - string (shorthand for { variable: string })
20
- * - { variable: string } (explicit variable mapping)
21
- * - { value: DataValue } (constant value applied to every observation)
38
+ * How a single aesthetic channel is fed. One of three forms:
39
+ * - a bare string (`'revenue'`) — shorthand for `{ variable: 'revenue' }`, the common case;
40
+ * - `{ variable: 'revenue' }` the same column binding, written explicitly;
41
+ * - `{ value: 2500 }` — a constant {@link ValueMapping} applied to every observation.
22
42
  */
23
43
  declare type AestheticValue = string | VariableMapping | ValueMapping;
24
44
 
@@ -27,22 +47,28 @@ declare function aggregate(options: AggregateOptions): AggregateTransformInput;
27
47
  /***************************************************************
28
48
  * Aggregate Transform
29
49
  ***************************************************************/
50
+ /** A single group-wise reduction applied by `transform.aggregate`. */
30
51
  declare interface AggregateOperation {
31
- /** The aggregation function to apply. */
52
+ /** Reduction to apply: `'count'` | `'sum'` | `'mean'` | `'median'` | `'mode'` | `'min'` | `'max'`. */
32
53
  op: AggregationFunction;
33
- /** The variable to aggregate. */
54
+ /** The variable to reduce within each group. */
34
55
  variableName: VariableName;
35
- /** The name of the output variable. */
56
+ /** Name of the output variable holding the reduced value. */
36
57
  as: VariableName;
37
58
  }
38
59
 
60
+ /**
61
+ * Options for `transform.aggregate` — groups observations by `groupby`, then reduces each group
62
+ * to one observation via `operations`. The idiom for pre-summarizing data (e.g. sum revenue per region).
63
+ */
39
64
  declare interface AggregateOptions {
40
- /** Variables to group by before aggregating. */
65
+ /** Variables to group by; one output observation is produced per distinct combination. */
41
66
  groupby: VariableName[];
42
- /** Aggregation operations to apply per group. */
67
+ /** One or more reductions to compute per group. */
43
68
  operations: AggregateOperation[];
44
69
  }
45
70
 
71
+ /** Group-and-reduce transform produced by `transform.aggregate`. */
46
72
  declare interface AggregateTransformInput {
47
73
  type: 'transform';
48
74
  transformType: 'aggregate';
@@ -82,11 +108,63 @@ declare interface AnchorSegment {
82
108
  direction: 'positive' | 'negative';
83
109
  }
84
110
 
111
+ /**
112
+ * The angular span of an arc/wedge in a polar coord, in **radians** (0 = straight up, increasing
113
+ * clockwise — the d3-arc convention). Either endpoint is `null` when the observation declares no x
114
+ * interval. Returned by {@link getAngleExtent}.
115
+ */
85
116
  export declare interface AngleExtent {
86
117
  startAngle: NumericDataValue;
87
118
  endAngle: NumericDataValue;
88
119
  }
89
120
 
121
+ /**
122
+ * Builder for the built-in annotation kinds — the pipeable counterpart to setting the `annotations`
123
+ * field by hand. Each method returns an {@link AnnotationItem}; piped into `createSpec`/`pipe` it appends
124
+ * to the matching {@link AnnotationsInput} field, so annotations compose left-to-right like every other
125
+ * spec feature (geoms, scales, highlights). Multiple calls of the same kind accumulate.
126
+ *
127
+ * Anchoring differs per kind: only {@link annotation.differenceArrow} snaps to DATA (two observations);
128
+ * `shape`, `text`, and `freeformArrow` position in panel fractions (`[0,1]`, top-left origin). `sticker`,
129
+ * `pinnedNumber`, and `comment` compile but have NO painter in `@graphysdk/react-renderer` (editor-only) —
130
+ * avoid them when authoring for the React renderer. For a data-anchored callout/band beyond a difference
131
+ * arrow, register a custom kind via `createGraphyBuilder({ annotations })`; its `annotation` builder adds
132
+ * one method per registered kind alongside these built-ins.
133
+ *
134
+ * @example
135
+ * import { pipe, createSpec, geom, scale, annotation } from '@graphysdk/viz-engine';
136
+ *
137
+ * pipe(
138
+ * createSpec({ x: 'month', y: 'revenue', color: 'region' }),
139
+ * geom.line(),
140
+ * scale.x.discrete(),
141
+ * scale.y(),
142
+ * scale.color.palette(),
143
+ * annotation.differenceArrow({
144
+ * start: { anchorValue: 'Jan', groupValue: 'North' },
145
+ * end: { anchorValue: 'Jun', groupValue: 'North' },
146
+ * label: 'relative-difference',
147
+ * }),
148
+ * annotation.shape({ x: 0, y: 0.7, width: 1, height: 0.3, fillColor: '#e15759', fillOpacity: 0.12 }),
149
+ * );
150
+ */
151
+ export declare const annotation: {
152
+ /** A labelled delta between two data observations — the only built-in kind that snaps to data. */
153
+ differenceArrow(input: DifferenceArrowInput): AnnotationItem;
154
+ /** A shaded box positioned in panel fractions (`[0,1]`); does not snap to a data value. */
155
+ shape(input: ShapeInput): AnnotationItem;
156
+ /** A free-standing arrow with endpoints in panel fractions (`[0,1]`); does not snap to a data value. */
157
+ freeformArrow(input: FreeformArrowInput): AnnotationItem;
158
+ /** A free-standing rich-text label positioned in panel fractions (`[0,1]`); does not snap to a data value. */
159
+ text(input: TextAnnotationInput): AnnotationItem;
160
+ /** Editor-only: compiles but has NO painter in `@graphysdk/react-renderer`. */
161
+ sticker(input: StickerAnnotationInput): AnnotationItem;
162
+ /** Editor-only: compiles but has NO painter in `@graphysdk/react-renderer`. */
163
+ pinnedNumber(input: PinnedNumberAnnotationInput): AnnotationItem;
164
+ /** Editor-only: compiles but has NO painter in `@graphysdk/react-renderer`. */
165
+ comment(input: CommentAnnotationInput): AnnotationItem;
166
+ };
167
+
90
168
  /**
91
169
  * The compile-half definition of a custom annotation kind (ADR-035).
92
170
  *
@@ -95,9 +173,11 @@ export declare interface AngleExtent {
95
173
  * params })` from `TParams`, merge `defaultParams`, and enforce the optional coordinate arity. The
96
174
  * render-half `draw` lives in the renderer and binds to this definition by import (`defineAnnotationRenderer`).
97
175
  */
98
- /** Optional coordinate-count guardrail enforced by the builder; unbounded when omitted. */
99
- declare interface AnnotationArity {
176
+ /** Allowed coordinate count for an annotation kind; each bound is inclusive, unbounded when omitted. */
177
+ export declare interface AnnotationArity {
178
+ /** Minimum coordinates required. */
100
179
  min?: number;
180
+ /** Maximum coordinates allowed. */
101
181
  max?: number;
102
182
  }
103
183
 
@@ -142,18 +222,59 @@ declare interface AnnotationDataPoint {
142
222
  rowValue?: DataValue;
143
223
  }
144
224
 
145
- declare interface AnnotationDef<TParams extends object = object, TType extends string = string> {
225
+ /**
226
+ * The compile-half definition produced by {@link defineAnnotation}. Pass an array of these to
227
+ * `createGraphyBuilder({ annotations })` to get a typed `annotation.<type>(...)` spec method; the
228
+ * render-half `draw` binds to it by import in `@graphysdk/react-renderer`.
229
+ */
230
+ export declare interface AnnotationDef<TParams extends object = object, TType extends string = string> {
231
+ /** The registered kind name; keys the `annotation.<type>(...)` builder method and the render-side `draw`. */
146
232
  type: TType;
147
233
  /** Carrier that lets the builder recover `TParams` and merge defaults before a param reaches `draw`. */
148
234
  defaultParams: TParams;
235
+ /** Coordinate-count guardrail enforced by the builder; unbounded when omitted. */
149
236
  coordinates?: AnnotationArity;
150
237
  }
151
238
 
152
- /** Pipeable spec item produced by the registration-typed `annotation.<kind>(...)` builder. */
153
- declare interface AnnotationItem {
239
+ /**
240
+ * Pipeable spec item produced by an `annotation.*` builder. `kind` routes the carried input to the
241
+ * matching {@link AnnotationsInput} field when the item is folded onto a spec by `createSpec`/`pipe`:
242
+ * each built-in kind targets its like-named field, and `'custom'` carries a registered
243
+ * {@link CustomAnnotationInput} onto `annotations.custom`.
244
+ */
245
+ export declare type AnnotationItem = {
246
+ type: 'annotation';
247
+ kind: 'differenceArrow';
248
+ annotation: DifferenceArrowInput;
249
+ } | {
250
+ type: 'annotation';
251
+ kind: 'shape';
252
+ annotation: ShapeInput;
253
+ } | {
254
+ type: 'annotation';
255
+ kind: 'freeformArrow';
256
+ annotation: FreeformArrowInput;
257
+ } | {
258
+ type: 'annotation';
259
+ kind: 'text';
260
+ annotation: TextAnnotationInput;
261
+ } | {
262
+ type: 'annotation';
263
+ kind: 'sticker';
264
+ annotation: StickerAnnotationInput;
265
+ } | {
266
+ type: 'annotation';
267
+ kind: 'pinnedNumber';
268
+ annotation: PinnedNumberAnnotationInput;
269
+ } | {
270
+ type: 'annotation';
271
+ kind: 'comment';
272
+ annotation: CommentAnnotationInput;
273
+ } | {
154
274
  type: 'annotation';
275
+ kind: 'custom';
155
276
  annotation: CustomAnnotationInput;
156
- }
277
+ };
157
278
 
158
279
  /** Recovers an annotation definition's params type — carried structurally by its `defaultParams`. */
159
280
  declare type AnnotationParamsOf<Definition> = Definition extends AnnotationDef<infer TParams> ? TParams : never;
@@ -182,17 +303,38 @@ declare interface AnnotationsCompilerInput {
182
303
  scales: CompiledScales;
183
304
  }
184
305
 
306
+ /**
307
+ * Non-data marks layered onto the panel, set as the `annotations` field on a spec. Each field holds a
308
+ * different built-in kind.
309
+ *
310
+ * Anchoring differs per field and is the deciding detail: only `differenceArrows` snap to DATA (two
311
+ * observations). `shapes`, `textAnnotations` and `freeformArrows` position in panel fractions (`[0,1]`,
312
+ * top-left origin) — they re-flow on resize but do NOT snap to a data value. For a data-anchored callout,
313
+ * band, or marker beyond a difference arrow, author a `custom` annotation.
314
+ *
315
+ * `stickers`, `pinnedNumbers` and `comments` COMPILE but have no painter in `@graphysdk/react-renderer`
316
+ * (they draw only in the editor's legacy engine) — don't use them when authoring for the React renderer.
317
+ */
185
318
  export declare interface AnnotationsInput {
319
+ /** Labelled deltas between two data observations. The only built-in kind that anchors to data. */
186
320
  differenceArrows?: DifferenceArrowInput[];
321
+ /** Shaded boxes positioned in panel fractions (`[0,1]`), not data values. */
187
322
  shapes?: ShapeInput[];
323
+ /** Free-standing arrows positioned in panel fractions (`[0,1]`), not data values. */
188
324
  freeformArrows?: FreeformArrowInput[];
325
+ /** Free-standing rich-text labels positioned in panel fractions (`[0,1]`), not data values. */
189
326
  textAnnotations?: TextAnnotationInput[];
327
+ /** Compiles but has NO painter in `@graphysdk/react-renderer` (editor-only). Avoid here. */
190
328
  stickers?: StickerAnnotationInput[];
329
+ /** Compiles but has NO painter in `@graphysdk/react-renderer` (editor-only). Avoid here. */
191
330
  pinnedNumbers?: PinnedNumberAnnotationInput[];
331
+ /** Compiles but has NO painter in `@graphysdk/react-renderer` (editor-only). Avoid here. */
192
332
  comments?: CommentAnnotationInput[];
333
+ /** Registered custom-annotation instances; the data-anchorable escape hatch when no built-in fits. */
193
334
  custom?: CustomAnnotationInput[];
194
335
  }
195
336
 
337
+ /** Resolved form of {@link AnnotationsInput} — every field present, each entry defaulted. */
196
338
  export declare interface AnnotationsSpec {
197
339
  differenceArrows: DifferenceArrowSpec[];
198
340
  shapes: ShapeSpec[];
@@ -266,11 +408,20 @@ export declare interface AppearanceSpec {
266
408
  */
267
409
  cornerRadius: number;
268
410
  /**
411
+ * How non-matched observations are de-emphasised when a highlight is active.
412
+ * See {@link HighlightStyle}: `'dim'` lowers opacity, `'desaturate'` greys them out.
269
413
  * @default 'dim'
270
414
  */
271
415
  highlightStyle: HighlightStyle;
272
416
  }
273
417
 
418
+ /**
419
+ * Area marks — a line with the region below it filled. Same render knobs as line ({@link AreaGeomParams}).
420
+ * Use `position: 'stack'` for a stacked area chart or `'fill'` for a 100%-stacked one.
421
+ *
422
+ * @example
423
+ * pipe(createSpec({ x: 'month', y: 'sales', color: 'region' }), geom.area({ position: 'stack' }), scale.x(), scale.y(), scale.color.palette());
424
+ */
274
425
  declare function area(options?: GeomOptions<'area'>): LayerInputOf<'area'>;
275
426
 
276
427
  /**
@@ -291,21 +442,37 @@ declare class AreaGeom extends Geom {
291
442
  }
292
443
 
293
444
  /**
294
- * Area-specific parameters (same rendering knobs as line, but fills under the curve)
445
+ * Render parameters for `geom.area` — same knobs as {@link LineGeomParams}, but the region below the curve
446
+ * is filled. Passed under `params`.
295
447
  */
296
448
  export declare interface AreaGeomParams {
449
+ /**
450
+ * Outline stroke width in pixels, or `'auto'` to let the theme pick a width.
451
+ * @default 'auto'
452
+ */
297
453
  lineWidth: number | 'auto';
454
+ /**
455
+ * Interpolation method between points: `'linear'` for straight segments, `'catmull-rom'` for a smooth spline.
456
+ * @default 'linear'
457
+ */
298
458
  interpolate: InterpolateType;
459
+ /**
460
+ * How to handle missing (`null`) y-values: `'zero'` drops to zero, `'gap'` breaks the area, `'connect'`
461
+ * bridges across the gap.
462
+ * @default 'zero'
463
+ */
299
464
  missingValues: MissingValuesType;
300
465
  }
301
466
 
467
+ /** An arrow endpoint as a panel fraction (`[0,1]`, top-left origin). */
302
468
  export declare interface ArrowEndpoint {
303
- /** 0..1 of plot width. */
469
+ /** `[0,1]` of panel width (0 = left). */
304
470
  x: number;
305
- /** 0..1 of plot height. */
471
+ /** `[0,1]` of panel height (0 = top). */
306
472
  y: number;
307
473
  }
308
474
 
475
+ /** Arrowhead at an endpoint: `'none'` (bare line) or `'line-arrow'` (drawn head). */
309
476
  export declare type ArrowheadStyle = 'none' | 'line-arrow';
310
477
 
311
478
  export declare type ArrowLineStyle = 'solid' | 'dashed';
@@ -338,8 +505,11 @@ declare interface Axes {
338
505
  * Groups all axis-related settings per axis.
339
506
  */
340
507
  declare interface AxesConfig {
508
+ /** Configuration for the horizontal (x) axis. */
341
509
  x: XAxisConfig;
510
+ /** Configuration for the primary vertical (y) axis. */
342
511
  y: YAxisConfig;
512
+ /** Configuration for the secondary y axis, present only on dual-axis charts. */
343
513
  ySecondary?: YAxisConfig;
344
514
  }
345
515
 
@@ -415,7 +585,9 @@ export declare interface AxisTickCandidate {
415
585
  * Configuration for a single axis's ticks
416
586
  */
417
587
  declare interface AxisTicksConfig {
588
+ /** Whether tick marks and their labels are drawn for this axis. */
418
589
  isVisible: boolean;
590
+ /** Which ticks to label — see {@link AxisLabelMode} (`'auto'` = all, `'edges'` = first/last only). */
419
591
  mode: AxisLabelMode;
420
592
  }
421
593
 
@@ -446,6 +618,14 @@ export declare type BackgroundSpec = {
446
618
  color?: string;
447
619
  };
448
620
 
621
+ /**
622
+ * Bar/column marks. Drives most categorical charts: plain, stacked (`position: 'stack'`), grouped
623
+ * (`'dodge'`), 100%-stacked (`'fill'`), horizontal (add `coord.flip()`), and pie/donut (`position: 'fill'`
624
+ * inside `coord.polar({ theta: 'y' })`). No render `params`.
625
+ *
626
+ * @example
627
+ * pipe(createSpec({ x: 'quarter', y: 'sales', color: 'region' }), geom.bar({ position: 'stack' }), scale.x(), scale.y(), scale.color.palette());
628
+ */
449
629
  declare function bar(options?: GeomOptions<'bar'>): LayerInputOf<'bar'>;
450
630
 
451
631
  /**
@@ -496,27 +676,68 @@ declare interface BarOptions {
496
676
  }
497
677
 
498
678
  /**
499
- * Base params shared by all coordinate systems
679
+ * Params shared by every coordinate system. Axis limits clamp the displayed range
680
+ * after scaling.
500
681
  */
501
682
  declare interface BaseCoordParams {
502
683
  /**
503
- * Limits for x-axis [min, max]
684
+ * Fixed x-axis range as `[min, max]` in data units, or `null` to auto-fit from data.
685
+ * @default null
504
686
  */
505
687
  xLimits: [number, number] | null;
506
688
  /**
507
- * Limits for y-axis [min, max]
689
+ * Fixed y-axis range as `[min, max]` in data units, or `null` to auto-fit from data.
690
+ * @default null
508
691
  */
509
692
  yLimits: [number, number] | null;
510
693
  }
511
694
 
695
+ /**
696
+ * Options accepted by every `geom.*` builder. All fields are optional; each builder fills defaults during
697
+ * resolution. The generic `T` is the per-geom `params` shape so `geom.line` accepts {@link LineGeomParams}
698
+ * while `geom.bar` accepts none.
699
+ */
512
700
  declare interface BaseGeomOptions<T extends GeomParams> {
701
+ /**
702
+ * Layer-level aesthetic overrides, shallow-merged OVER the spec-level mapping for this layer only.
703
+ * The place to retarget a channel per layer in a combo (`geom.line({ aes: { y: 'margin' } })`) or to pin a
704
+ * constant (`aes: { y: { value: 2500 } }` for a reference line).
705
+ */
513
706
  aes?: AesMapping;
514
- stat?: StatName | StatInput;
707
+ /**
708
+ * Statistical transform applied to this layer's data before positioning. `'identity'` (default) plots rows
709
+ * as-is; `'count'` tallies observations per x; `stat.mean()` collapses to a single mean-of-`y` observation
710
+ * (average line); `stat.smooth({ method })` fits a regression curve (trendline).
711
+ * @default 'identity'
712
+ */
713
+ stat?: StatLayerInput | StatLayerInput[];
714
+ /**
715
+ * How sibling marks sharing an x position are arranged. `'identity'` overlaps them; `'stack'` stacks by
716
+ * `color`; `'dodge'` places them side by side; `'fill'` stacks then normalises each column to 100% (also
717
+ * the basis of pie/donut under `coord.polar`). Default is per-geom: `area` → `'stack'`, `bar` → `'dodge'`,
718
+ * `point`/`line`/`rule` → `'identity'`.
719
+ */
515
720
  position?: PositionType;
721
+ /**
722
+ * Which Y axis this layer binds to. `'secondary'` puts it on the right-hand axis for dual-axis combos
723
+ * (pair with `scale.ySecondary()`); the layer still maps to the `y` channel.
724
+ * @default 'primary'
725
+ */
516
726
  yScaleType?: YScaleType;
727
+ /** Geom-specific render knobs — static styling only (widths, colors, interpolation), never data channels. */
517
728
  params?: Partial<T>;
729
+ /**
730
+ * Ordered transforms applied to this layer's view of the data, on top of the spec-level transforms. Use
731
+ * when this geom needs a different data shape than its siblings.
732
+ */
518
733
  transforms?: TransformInput[];
734
+ /**
735
+ * When `false`, the layer is excluded from hover hit-detection — set it on non-data overlays like
736
+ * average and trend lines so they don't steal the tooltip. Defaults to `true` for all geoms except `rule`,
737
+ * which defaults to `false`.
738
+ */
519
739
  interactive?: boolean;
740
+ /** Per-observation value labels drawn on the marks. Off by default; see {@link DataLabelsInput}. */
520
741
  dataLabels?: DataLabelsInput;
521
742
  }
522
743
 
@@ -708,6 +929,12 @@ declare type BuiltinParams<G extends string> = G extends GeomName ? Extract<Buil
708
929
  geom: G;
709
930
  }>['params'] : Record<string, unknown>;
710
931
 
932
+ /***************************************************************
933
+ * Transform Input
934
+ ***************************************************************/
935
+ /** The built-in transforms; their `transformType` literals form the closed {@link TransformType}. */
936
+ declare type BuiltinTransformInput = ReshapeTransformInput | FilterTransformInput | SortTransformInput | AggregateTransformInput | ConstantTransformInput;
937
+
711
938
  /**
712
939
  * Caching decorator for any TextMeasurer implementation.
713
940
  *
@@ -851,6 +1078,17 @@ export declare interface CommandApplyResult {
851
1078
  readonly revert: Command;
852
1079
  }
853
1080
 
1081
+ /**
1082
+ * Descriptor that knows how to deserialize a specific command type.
1083
+ * Each concrete command co-locates its descriptor alongside the command class.
1084
+ *
1085
+ * Serialization is handled uniformly by the registry via `Command.params`.
1086
+ */
1087
+ declare interface CommandDescriptor<TParams extends Record<string, unknown> = Record<string, unknown>> {
1088
+ readonly type: string;
1089
+ deserialize: (params: TParams, metadata: CommandMetadata) => Command;
1090
+ }
1091
+
854
1092
  /**
855
1093
  * Unique identifier for commands.
856
1094
  */
@@ -870,6 +1108,33 @@ export declare interface CommandMetadata {
870
1108
  readonly author: string;
871
1109
  }
872
1110
 
1111
+ /**
1112
+ * Central registry mapping command types to their serialization descriptors.
1113
+ */
1114
+ export declare class CommandRegistry {
1115
+ private readonly descriptors;
1116
+ /**
1117
+ * Register a command descriptor. Throws if the type is already registered.
1118
+ */
1119
+ register<TParams extends Record<string, unknown>>(descriptor: CommandDescriptor<TParams>): void;
1120
+ /**
1121
+ * Serialize a command to its wire format.
1122
+ */
1123
+ serialize(command: Command): SerializedCommand;
1124
+ /**
1125
+ * Deserialize a command from its wire format.
1126
+ */
1127
+ deserialize(data: SerializedCommand): Command;
1128
+ /**
1129
+ * Get all registered command type names.
1130
+ */
1131
+ getRegisteredTypes(): string[];
1132
+ private getDescriptor;
1133
+ }
1134
+
1135
+ /** Default singleton registry instance. */
1136
+ export declare const commandRegistry: CommandRegistry;
1137
+
873
1138
  /**
874
1139
  * Event types emitted by CommandStackManager.
875
1140
  */
@@ -989,6 +1254,9 @@ export declare interface CommandStackSnapshot {
989
1254
  * Comment annotation: a marker dot pinned to a single observation, carrying
990
1255
  * rich-text content. The renderer's mini view shows a truncated comment; hover
991
1256
  * reveals the full text.
1257
+ *
1258
+ * NO PAINTER in `@graphysdk/react-renderer` — this compiles but never draws there (it renders only in
1259
+ * the editor's legacy engine). Don't reach for it when authoring for the React renderer.
992
1260
  */
993
1261
  declare interface CommentAnnotationInput {
994
1262
  id?: string;
@@ -996,6 +1264,7 @@ declare interface CommentAnnotationInput {
996
1264
  content: RichTextContent;
997
1265
  }
998
1266
 
1267
+ /** Resolved form of {@link CommentAnnotationInput} — defaults applied, anchor normalised. */
999
1268
  declare interface CommentAnnotationSpec {
1000
1269
  id: string;
1001
1270
  anchor: ObservationAnchor;
@@ -1028,6 +1297,12 @@ export declare interface CompiledAxisGuide {
1028
1297
  aesthetic: AestheticKey;
1029
1298
  /** Axis placement */
1030
1299
  position: AxisPosition;
1300
+ /**
1301
+ * Axis geometry: `'linear'` for cartesian (straight edge axis), `'circular'` for the angular axis
1302
+ * of a polar chart (ticks around the rim — the radar spokes/labels), `'radial'` for the radial axis
1303
+ * (concentric ring gridlines). Renderers branch on this before `position`.
1304
+ */
1305
+ geometry: GuideGeometry;
1031
1306
  /** Title text, null means no title */
1032
1307
  label: string | null;
1033
1308
  /** Whether the axis (line + ticks + labels) is visible */
@@ -1108,10 +1383,25 @@ export declare interface CompiledFreeformArrow {
1108
1383
  hasStickerStyle: boolean;
1109
1384
  }
1110
1385
 
1111
- declare interface CompiledGeom {
1112
- /** The reparameterized dataset (may have new computed variables) */
1386
+ /**
1387
+ * What {@link Geom.compile} returns the reparameterised data plus any mapping the geom injects.
1388
+ * Everything here must be JSON-serialisable (it rides in the compiled spec): emit data columns and
1389
+ * plain mapping values only — no closures, no class instances.
1390
+ */
1391
+ export declare interface CompiledGeom {
1392
+ /**
1393
+ * The reparameterised dataset: the input dataset with the position columns the mark owns added. Write
1394
+ * each through `variableFor(axis, role | name)` — never a literal column string like `'yMin'` — so the
1395
+ * value readers and the coord projection find them. A geom writes only the columns it owns; the mapper
1396
+ * scales any `scalar` channel that declares an `aes` source in place from the author's mapping.
1397
+ */
1113
1398
  data: Dataset;
1114
- /** Any mapping overrides produced by the geom */
1399
+ /**
1400
+ * Mapping overrides the geom injects, merged over the layer's mapping. The common case is attaching a
1401
+ * scale a mark needs but the author never mapped — e.g. injecting `{ y: { variable } }` so a price
1402
+ * scale forms for an OHLC mark whose extent comes from a y-interval. Return `{}` to inject nothing;
1403
+ * never echo the author's own aesthetics back here.
1404
+ */
1115
1405
  mapping: AesMapping;
1116
1406
  /**
1117
1407
  * Extra single-observation tooltip rows this geom contributes (e.g. OHLC). The compiler derives
@@ -1334,7 +1624,7 @@ export declare interface CompiledSpec {
1334
1624
  annotations: CompiledAnnotations;
1335
1625
  }
1336
1626
 
1337
- declare interface CompiledStat {
1627
+ export declare interface CompiledStat {
1338
1628
  /** The transformed dataset. */
1339
1629
  data: Dataset;
1340
1630
  /** Any mapping overrides produced by the stat (e.g., `y` → `'count'` for `CountStat`). */
@@ -1549,7 +1839,37 @@ declare interface ComputeFreeformArrowParams {
1549
1839
  }
1550
1840
 
1551
1841
  /**
1552
- * Create a custom configuration
1842
+ * Pipeable spec item carrying chart-level configuration. Every group is
1843
+ * optional; only the keys you set override the resolved defaults. Accepts:
1844
+ *
1845
+ * - `content`: titles and attribution — `title` / `subtitle` / `caption`
1846
+ * (each a {@link TextContent}) plus `source` ({@link SourceContent}), each
1847
+ * paired with an `isXVisible` toggle.
1848
+ * - `legend`: `{ position }` — see {@link LegendPosition}.
1849
+ * - `axes`: per-axis `{ x, y, ySecondary }` overrides, e.g. `{ label }`.
1850
+ * - `numberFormat`: chart-wide number formatting — see {@link NumberFormatConfig}.
1851
+ * - `headline`: big-number summary figure — `show` / `compareWith` / `size` /
1852
+ * `position` (see {@link HeadlineShow}, highlights-headlines.md).
1853
+ * - `appearance`: render-only styling — `textScale`, `highlightStyle`,
1854
+ * `background`, `border`, `cornerRadius` (see {@link AppearanceSpec}).
1855
+ *
1856
+ * @example
1857
+ * import { pipe, createSpec, geom, scale, config } from '@graphysdk/viz-engine';
1858
+ *
1859
+ * pipe(
1860
+ * createSpec({ x: 'quarter', y: 'revenue', color: 'region' }),
1861
+ * geom.bar({ position: 'stack' }),
1862
+ * scale.x(),
1863
+ * scale.y(),
1864
+ * config({
1865
+ * content: { title: 'Quarterly revenue by region', source: { label: 'Finance', url: 'https://…' } },
1866
+ * legend: { position: 'top' },
1867
+ * axes: { y: { label: 'Revenue ($)' } },
1868
+ * numberFormat: { decimals: 0, abbreviation: 'auto', prefix: '$' },
1869
+ * headline: { show: 'total' },
1870
+ * appearance: { highlightStyle: 'dim' },
1871
+ * })
1872
+ * );
1553
1873
  */
1554
1874
  export declare function config(options: ConfigInput): ConfigItem;
1555
1875
 
@@ -1566,8 +1886,20 @@ declare interface ConfigCompilerInput {
1566
1886
  scales: CompiledScales;
1567
1887
  }
1568
1888
 
1889
+ /**
1890
+ * Author-facing argument to `config(...)`: a deep-partial of {@link ConfigSpec}.
1891
+ * Any omitted group or field falls back to its resolved default.
1892
+ */
1569
1893
  declare type ConfigInput = Omit<DeepPartial<ConfigSpec>, 'legend' | 'content'> & {
1894
+ /**
1895
+ * Legend overrides. Overridden from the deep-partial default so it accepts the
1896
+ * flat {@link LegendConfigInput} (`{ position, display }`) rather than a nested partial.
1897
+ */
1570
1898
  legend?: LegendConfigInput;
1899
+ /**
1900
+ * Content overrides. Overridden from the deep-partial default so titles/source
1901
+ * accept the author-friendly {@link ContentInput} shape (strings or rich objects).
1902
+ */
1571
1903
  content?: ContentInput;
1572
1904
  };
1573
1905
 
@@ -1575,22 +1907,38 @@ declare type ConfigInput = Omit<DeepPartial<ConfigSpec>, 'legend' | 'content'> &
1575
1907
  * Config specification with type tag
1576
1908
  */
1577
1909
  declare interface ConfigItem {
1910
+ /** Discriminant marking this as a config item in a pipeable spec. */
1578
1911
  type: 'config';
1912
+ /** The author-supplied partial configuration to merge over the defaults. */
1579
1913
  config: ConfigInput;
1580
1914
  }
1581
1915
 
1582
1916
  /**
1583
- * Feature configuration with resolved defaults.
1584
- * All fields are required and always populated after resolution.
1917
+ * Fully-resolved chart configuration: every group present with defaults
1918
+ * applied. This is the shape carried on a compiled spec; authors pass the
1919
+ * partial {@link ConfigInput} to `config(...)` instead.
1585
1920
  */
1586
1921
  export declare interface ConfigSpec {
1922
+ /**
1923
+ * Locale used to interpret raw string values into numbers/dates (e.g. which
1924
+ * thousands/decimal separators to expect). Acts as the fallback for output
1925
+ * formatting when no separate `formattingLocale` is supplied.
1926
+ * @default 'en-US'
1927
+ */
1587
1928
  parsingLocale: Locale;
1929
+ /** Legend placement and display mode. */
1588
1930
  legend: LegendConfig;
1931
+ /** Per-axis settings for the x, y, and optional secondary y axes. */
1589
1932
  axes: AxesConfig;
1933
+ /** Plot panel framing (the box drawn around the data area). */
1590
1934
  panel: PanelConfig;
1935
+ /** Big-number summary figure shown above or inside the chart. */
1591
1936
  headline: HeadlineConfig;
1937
+ /** Chart-wide number formatting applied by the renderer to every numeric value. */
1592
1938
  numberFormat: NumberFormatConfig;
1939
+ /** Titles, subtitle, caption, and source attribution. */
1593
1940
  content: ContentConfig;
1941
+ /** Render-only styling: text scale, background, border, corner radius, highlight style. */
1594
1942
  appearance: AppearanceSpec;
1595
1943
  }
1596
1944
 
@@ -1618,15 +1966,20 @@ declare interface ConstantMappingCompilerOutput {
1618
1966
  /***************************************************************
1619
1967
  * Constant Transform
1620
1968
  ***************************************************************/
1969
+ /**
1970
+ * Options for `transform.constant` — adds a new variable with the same value on every observation.
1971
+ * Useful to synthesize a constant axis or a single-category grouping variable.
1972
+ */
1621
1973
  declare interface ConstantOptions {
1622
- /** The name of the new variable. */
1974
+ /** Name of the new variable to add. */
1623
1975
  variableName: VariableName;
1624
- /** The type of the new variable. */
1976
+ /** Data type of the new variable. */
1625
1977
  type: DataType;
1626
- /** The constant value to assign to every observation. */
1978
+ /** The constant value assigned to every observation. */
1627
1979
  value: DataValue;
1628
1980
  }
1629
1981
 
1982
+ /** Add-a-constant-column transform produced by `transform.constant`. */
1630
1983
  declare interface ConstantTransformInput {
1631
1984
  type: 'transform';
1632
1985
  transformType: 'constant';
@@ -1656,17 +2009,29 @@ declare interface Content {
1656
2009
  * hide cycles without losing the text the user typed.
1657
2010
  */
1658
2011
  export declare interface ContentConfig {
2012
+ /** Main chart title. `null` = unset. */
1659
2013
  title: TextContent | null;
2014
+ /** @default true */
1660
2015
  isTitleVisible: boolean;
2016
+ /** Secondary line shown under the title. `null` = unset. */
1661
2017
  subtitle: TextContent | null;
2018
+ /** @default true */
1662
2019
  isSubtitleVisible: boolean;
2020
+ /** Explanatory note shown below the plot. `null` = unset. */
1663
2021
  caption: TextContent | null;
2022
+ /** @default false */
1664
2023
  isCaptionVisible: boolean;
2024
+ /** Data-source attribution shown under the caption. `null` = unset. */
1665
2025
  source: SourceContent | null;
2026
+ /** @default false */
1666
2027
  isSourceVisible: boolean;
1667
2028
  }
1668
2029
 
1669
- /** Content input — all fields optional. */
2030
+ /**
2031
+ * Author-facing `content` argument to `config(...)`: all fields optional.
2032
+ * Setting a text slot does not show it unless the matching `isXVisible` flag is
2033
+ * also true (title and subtitle default visible; caption and source default hidden).
2034
+ */
1670
2035
  declare type ContentInput = Partial<ContentConfig>;
1671
2036
 
1672
2037
  declare type ContinuousScaleInput = {
@@ -1752,27 +2117,60 @@ declare type ContinuousScaleSpec = Required<ContinuousScaleInput>;
1752
2117
  */
1753
2118
  export declare function convertSpecToInput(spec: Spec): SpecInput;
1754
2119
 
2120
+ /**
2121
+ * Coordinate-system builder. A coord is a geom-agnostic projection applied AFTER scaling
2122
+ * that remaps the already-scaled `[0,1]` positions of any geom; it changes neither the data,
2123
+ * the scales, nor the chart's tier. Pipe at most one onto a spec — cartesian is assumed when
2124
+ * none is given.
2125
+ *
2126
+ * - `cartesian` — standard x→horizontal, y→vertical (the default).
2127
+ * - `flip` — swaps the x and y axes; the idiom for horizontal bars and long category labels.
2128
+ * - `polar` — wraps x/y around a centre; `theta` selects the angle aesthetic and the other
2129
+ * becomes the radius. The basis for pie, donut, and radar charts.
2130
+ *
2131
+ * @example
2132
+ * import { pipe, createSpec, geom, scale, coord } from '@graphysdk/viz-engine';
2133
+ *
2134
+ * // Donut: stacked value → angle, innerRadius > 0 carves the hole
2135
+ * pipe(
2136
+ * createSpec({ x: '', y: 'spend', color: 'department' }),
2137
+ * geom.bar({ position: 'fill' }),
2138
+ * coord.polar({ theta: 'y', innerRadius: 0.55 }),
2139
+ * scale.x(),
2140
+ * scale.y(),
2141
+ * scale.color.palette()
2142
+ * );
2143
+ */
1755
2144
  export declare const coord: {
1756
2145
  /**
1757
- * Standard cartesian (x-y) coordinate system. This is the default if no coord is specified.
2146
+ * Standard cartesian (x→horizontal, y→vertical) coordinate system. This is the default
2147
+ * when no coord is piped onto the spec; declare it explicitly only to set axis limits.
1758
2148
  *
1759
2149
  * @example coord.cartesian() // auto-scaled axes
1760
- * @example coord.cartesian({ yLimits: [0, 100] }) // fixed y-axis
2150
+ * @example coord.cartesian({ yLimits: [0, 100] }) // fixed y-axis range
1761
2151
  */
1762
2152
  cartesian: (params?: Partial<CartesianCoordParams>) => CartesianCoordInput;
1763
2153
  /**
1764
- * Flipped cartesian coordinates — swaps x and y axes.
1765
- * Useful for horizontal bar charts or when category labels are long.
2154
+ * Flipped cartesian coordinates — swaps the x and y axes so the x aesthetic runs
2155
+ * vertically and y runs horizontally. The idiom for horizontal bar charts and for
2156
+ * long category labels. The mapping stays the same; only the on-screen orientation flips.
1766
2157
  *
1767
- * @example coord.flip() // horizontal bars
2158
+ * @example coord.flip() // horizontal bars from a vertical-bar spec
1768
2159
  */
1769
2160
  flip: (params?: Partial<FlipCoordParams>) => FlipCoordInput;
1770
2161
  /**
1771
- * Polar coordinate system — maps data to angle (theta) and radius.
1772
- * Used for pie charts, donut charts, and radar/radial visualizations.
2162
+ * Polar coordinate system — wraps the scaled positions around a centre, mapping one
2163
+ * aesthetic to the angle (theta) and the other to the radius (scaled into
2164
+ * `[innerRadius, 1]`). `theta` defaults to `'x'`.
2165
+ *
2166
+ * - Pie / donut: `geom.bar({ position: 'fill' })` with `theta: 'y'` (stacked value → angle);
2167
+ * set `innerRadius > 0` for a donut.
2168
+ * - Radar / spider: `geom.line` or `geom.point` with `theta: 'x'` over a discrete x axis
2169
+ * (one evenly-spaced spoke per category).
1773
2170
  *
1774
- * @example coord.polar() // pie chart
1775
- * @example coord.polar({ innerRadius: 0.5 }) // donut chart
2171
+ * @example coord.polar({ theta: 'y' }) // pie: stacked value → angle
2172
+ * @example coord.polar({ theta: 'y', innerRadius: 0.5, startAngle: 90 }) // donut rotated 90°
2173
+ * @example coord.polar({ theta: 'x' }) // radar: category → spoke angle
1776
2174
  */
1777
2175
  polar: (params?: Partial<PolarCoordParams>) => PolarCoordInput;
1778
2176
  };
@@ -1791,7 +2189,10 @@ declare class CoordCompiler {
1791
2189
  }
1792
2190
 
1793
2191
  /**
1794
- * Discriminated union of all coordinate input specs (user-provided, optional params).
2192
+ * A coordinate system produced by the `coord` builder, before resolution.
2193
+ * A coord is a geom-agnostic projection applied AFTER scaling: it remaps the already-scaled
2194
+ * `[0,1]` positions of any geom without touching the data, the scales, or the chart's tier.
2195
+ * One coord per spec; defaults to cartesian when none is piped on.
1795
2196
  */
1796
2197
  declare type CoordInput = CartesianCoordInput | FlipCoordInput | PolarCoordInput;
1797
2198
 
@@ -1815,7 +2216,7 @@ declare type CoordSetupResult = {
1815
2216
  };
1816
2217
 
1817
2218
  /**
1818
- * Discriminated union of all resolved coordinate specs (params fully defaulted).
2219
+ * A fully resolved coordinate system (params defaulted) as it appears on the compiled spec.
1819
2220
  */
1820
2221
  declare type CoordSpec = CartesianCoordSpec | FlipCoordSpec | PolarCoordSpec;
1821
2222
 
@@ -1858,7 +2259,7 @@ declare interface CoordTransformInput {
1858
2259
  * - `'polar'` — Polar coordinates for pie, radar, and radial charts
1859
2260
  * - `'flip'` — Cartesian with x and y axes swapped
1860
2261
  */
1861
- declare type CoordType = 'cartesian' | 'polar' | 'flip';
2262
+ export declare type CoordType = 'cartesian' | 'polar' | 'flip';
1862
2263
 
1863
2264
  declare function count(): CountStatSpec;
1864
2265
 
@@ -1874,12 +2275,27 @@ export declare const createAlphaValueReader: (data: Dataset, mapping: AesMapping
1874
2275
  export declare const createColorValueReader: (data: Dataset, mapping: AesMapping) => (observation: Observation) => DataValue;
1875
2276
 
1876
2277
  /**
1877
- * Builds a compiler instance. Pass `geoms` to register custom (or override built-in) geom
1878
- * definitions per-instance — there is no global registry to mutate, so injected geoms never bleed
1879
- * across instances. An injected geom whose `type` matches a built-in overrides it (last write wins).
2278
+ * Create command metadata with defaults.
2279
+ */
2280
+ export declare function createCommandMetadata(options: CreateCommandMetadataOptions): CommandMetadata;
2281
+
2282
+ declare interface CreateCommandMetadataOptions {
2283
+ id?: string;
2284
+ timestamp?: number;
2285
+ description: string;
2286
+ author?: string;
2287
+ }
2288
+
2289
+ /**
2290
+ * Builds a compiler instance. Pass `geoms` / `stats` / `transforms` to register custom (or override
2291
+ * built-in) definitions per-instance — there is no global registry to mutate, so injected definitions
2292
+ * never bleed across instances. An injected definition whose `type` matches a built-in overrides it
2293
+ * (last write wins).
1880
2294
  */
1881
2295
  export declare const createCompiler: (opts?: {
1882
2296
  geoms?: readonly Geom[];
2297
+ stats?: readonly Stat[];
2298
+ transforms?: readonly TransformStrategy[];
1883
2299
  }) => Compiler;
1884
2300
 
1885
2301
  /**
@@ -1889,20 +2305,27 @@ export declare const createCompiler: (opts?: {
1889
2305
  export declare function createEmptyHighlight(strategy: HighlightStrategy | null): CompiledLayerHighlight | null;
1890
2306
 
1891
2307
  /**
1892
- * Builds a Graphy authoring surface for a set of custom geoms and/or annotations: a `geom` builder that
1893
- * merges the built-in methods with one method per registered custom geom, an `annotation` builder with
1894
- * one method per registered annotation kind, plus the standard `createSpec`. The 90% case stays the
1895
- * plain `import { geom, createSpec }`; reach for this only when authoring custom geoms (decision 8) or
1896
- * custom annotations (ADR-035). Registration is per-instance geoms are injected to
1897
- * `createCompiler({ geoms })`; annotations need no compile-side registry (coordinate resolution is
1898
- * generic), only the render plugin via `<GraphProvider annotationPlugins={[...]}>`.
2308
+ * Builds a Graphy authoring surface for a set of custom geoms, stats, transforms, and/or annotations: a
2309
+ * `geom` builder merging the built-in methods with one per registered custom geom, a `stat` builder
2310
+ * merging the built-in stats with one per registered custom stat, a `transform` builder merging the
2311
+ * built-in transforms with one per registered custom transform, an `annotation` builder merging the
2312
+ * built-in kinds with one per registered annotation kind, plus the standard `createSpec`. The 90% case
2313
+ * stays the plain `import { geom, stat, transform, annotation, createSpec }`; reach for this only when
2314
+ * authoring custom geoms (ADR-033), stats/transforms (ADR-039), or annotations (ADR-035). Registration is
2315
+ * per-instance — geoms, stats, and transforms are injected to `createCompiler({ geoms, stats, transforms })`;
2316
+ * annotations need no compile-side registry (coordinate resolution is generic), only the render plugin via
2317
+ * `<GraphProvider annotationPlugins={[...]}>`.
1899
2318
  */
1900
- export declare function createGraphyBuilder<const Geoms extends readonly Geom[] = readonly [], const Annotations extends readonly AnnotationDef[] = readonly []>(options: {
2319
+ export declare function createGraphyBuilder<const Geoms extends readonly Geom[] = readonly [], const Stats extends readonly StatDef[] = readonly [], const Transforms extends readonly TransformDef[] = readonly [], const Annotations extends readonly AnnotationDef[] = readonly []>(options: {
1901
2320
  geoms?: Geoms;
2321
+ stats?: Stats;
2322
+ transforms?: Transforms;
1902
2323
  annotations?: Annotations;
1903
2324
  }): {
1904
2325
  geom: typeof geom & CustomGeomBuilders<Geoms>;
1905
- annotation: CustomAnnotationBuilders<Annotations>;
2326
+ stat: typeof stat & CustomStatBuilders<Stats>;
2327
+ transform: typeof transform & CustomTransformBuilders<Transforms>;
2328
+ annotation: typeof annotation & CustomAnnotationBuilders<Annotations>;
1906
2329
  createSpec: typeof createSpec;
1907
2330
  };
1908
2331
 
@@ -1910,6 +2333,9 @@ export declare const createGroupValueReader: (data: Dataset, mapping: AesMapping
1910
2333
 
1911
2334
  export declare const createLabelValueReader: (data: Dataset, mapping: AesMapping) => (observation: Observation) => DataValue;
1912
2335
 
2336
+ /** Opens a {@link MarkTable} builder for a heterogeneous, kind-tagged geom-layout dataset. */
2337
+ export declare function createMarkTable(): MarkTable;
2338
+
1913
2339
  /**
1914
2340
  * Builds a per-observation reader for an `AestheticValue`:
1915
2341
  * - `{ value: X }` → returns `X` for every observation.
@@ -1926,20 +2352,32 @@ export declare function createSegmentYReader(layer: CompiledLayer): (observation
1926
2352
  export declare const createSizeValueReader: (data: Dataset, mapping: AesMapping) => (observation: Observation) => DataValue;
1927
2353
 
1928
2354
  /**
1929
- * Create a new spec, optionally piping spec items in one call. Data is passed separately
1930
- * to {@link compile}.
2355
+ * Seed a spec the entry point for every chart. The first argument may be a bare {@link AesMapping}
2356
+ * (`{ x, y, color, ... }`), which becomes the spec's global aesthetic mapping; any further arguments are
2357
+ * pipeable spec items (geoms, scales, coords, transforms, config, ...) folded on in order. Data is supplied
2358
+ * separately to `compile` / `<GraphProvider data>`.
2359
+ *
2360
+ * This is the builder pattern: `createSpec` seeds the mapping, then `pipe` (or extra args here) folds each
2361
+ * item onto an immutable spec, accumulating layers/scales/etc. Always declare `scale.x()` / `scale.y()` for
2362
+ * any position channel — they are NOT auto-inferred and yield NaN positions if omitted.
1931
2363
  *
1932
2364
  * @example
1933
- * When the first arg is a mapping:
1934
- * createSpec({ x: 'date', y: 'value' })
2365
+ * import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine';
1935
2366
  *
1936
- * When piping spec items (more readable when chaining transforms):
1937
- * createSpec(
1938
- * transform.reshape({ reshape: ['revenue'], keyName: 'metric', valueName: 'amount' }),
1939
- * mapping({ x: 'month', y: 'amount', color: 'metric' }),
1940
- * geom.bar(),
1941
- * scale.x(),
1942
- * )
2367
+ * // Most common: mapping first, then pipe the rest.
2368
+ * const spec = pipe(createSpec({ x: 'category', y: 'revenue' }), geom.bar(), scale.x(), scale.y());
2369
+ *
2370
+ * @example
2371
+ * import { createSpec, transform, mapping, geom, scale } from '@graphysdk/viz-engine';
2372
+ *
2373
+ * // All-in-one form, clearer when a transform must run before the mapping is read.
2374
+ * const spec = createSpec(
2375
+ * transform.reshape({ reshape: ['revenue'], keyName: 'metric', valueName: 'amount' }),
2376
+ * mapping({ x: 'month', y: 'amount', color: 'metric' }),
2377
+ * geom.bar(),
2378
+ * scale.x(),
2379
+ * scale.y(),
2380
+ * );
1943
2381
  */
1944
2382
  export declare function createSpec(...items: Array<AesMapping | SpecItem>): SpecInput;
1945
2383
 
@@ -1993,6 +2431,7 @@ declare interface CustomAnnotationOptions<TParams extends object> {
1993
2431
  id?: string;
1994
2432
  }
1995
2433
 
2434
+ /** Resolved form of {@link CustomAnnotationInput} — params defaulted to `{}`, coordinates resolved. */
1996
2435
  export declare interface CustomAnnotationSpec {
1997
2436
  id: string;
1998
2437
  type: string;
@@ -2018,7 +2457,7 @@ declare type CustomGeomBuilders<Geoms extends readonly Geom[]> = {
2018
2457
  */
2019
2458
  declare interface CustomGeomOptions<TParams extends object, TAes extends string> {
2020
2459
  aes?: Partial<Record<TAes, AestheticValue>>;
2021
- stat?: StatName | StatInput;
2460
+ stat?: StatLayerInput | StatLayerInput[];
2022
2461
  position?: PositionType;
2023
2462
  yScaleType?: YScaleType;
2024
2463
  params?: Partial<TParams>;
@@ -2056,12 +2495,53 @@ declare type CustomPaletteInput = {
2056
2495
  export declare type CustomPalettesInput = Record<string, string[]>;
2057
2496
 
2058
2497
  /**
2059
- * Data to visualize. Structured as a table.
2498
+ * One builder method per registered custom stat, keyed by its `type` and typed from its definition — so
2499
+ * `stat.shareOfTotal(...)` exists because `shareOfTotal` was registered, with options checked against the
2500
+ * stat's resolved spec. The options argument is required only when the stat declares a required option;
2501
+ * a stat with no options or only optional ones is callable with none.
2502
+ */
2503
+ declare type CustomStatBuilders<Stats extends readonly StatDef[]> = {
2504
+ [Definition in Stats[number] as Definition['type']]: Partial<StatOptionsOf<Definition>> extends StatOptionsOf<Definition> ? (options?: StatOptionsOf<Definition>) => CustomStatInput : (options: StatOptionsOf<Definition>) => CustomStatInput;
2505
+ };
2506
+
2507
+ /**
2508
+ * A custom stat's serialised input — its registered `type` plus arbitrary plain-data options. Produced
2509
+ * by the `stat.<type>(...)` method of `createGraphyBuilder({ stats })`; carried on a layer's `stat`
2510
+ * field and passed through resolution unchanged (it is already a resolved spec).
2511
+ */
2512
+ export declare interface CustomStatInput extends StatSpecBase {
2513
+ [option: string]: unknown;
2514
+ }
2515
+
2516
+ /**
2517
+ * One builder method per registered custom transform, keyed by its `transformType` and typed from its
2518
+ * definition — so `transform.topN(...)` exists because `topN` was registered, with options checked
2519
+ * against the transform's options. The options argument is required only when the transform declares a
2520
+ * required option; a transform with no options or only optional ones is callable with none.
2521
+ */
2522
+ declare type CustomTransformBuilders<Transforms extends readonly TransformDef[]> = {
2523
+ [Definition in Transforms[number] as Definition['transformType']]: Partial<TransformOptionsOf<Definition>> extends TransformOptionsOf<Definition> ? (options?: TransformOptionsOf<Definition>) => CustomTransformInput : (options: TransformOptionsOf<Definition>) => CustomTransformInput;
2524
+ };
2525
+
2526
+ /**
2527
+ * A custom transform's serialised input — its registered `transformType` plus plain-data options.
2528
+ * Produced by the `transform.<type>(...)` method of `createGraphyBuilder({ transforms })`; carried on a
2529
+ * spec or layer `transforms` array and dispatched by `transformType` like any built-in.
2530
+ */
2531
+ export declare interface CustomTransformInput {
2532
+ type: 'transform';
2533
+ transformType: string;
2534
+ options?: Record<string, unknown>;
2535
+ }
2536
+
2537
+ /**
2538
+ * The raw input dataset to visualize, structured as a table of `columns` + `rows`. This is what you
2539
+ * hand to the compiler and to `<GraphProvider data>` — the untransformed, pre-compile shape, distinct
2540
+ * from the per-observation {@link Observation} records a geom reads after compilation.
2060
2541
  *
2061
- * The public-API contract. Row values must be {@link DataValue} (string, number,
2062
- * Date, or null). Internal entry points (e.g. the dataset parser) accept a
2063
- * looser row type — see {@link RawData} — because they must defensively handle
2064
- * malformed input.
2542
+ * The public-API contract: row values must be {@link DataValue} (string, number, Date, or null).
2543
+ * Internal entry points (e.g. the dataset parser) accept a looser row type — see {@link RawData} —
2544
+ * because they must defensively handle malformed input.
2065
2545
  */
2066
2546
  export declare interface Data {
2067
2547
  /**
@@ -2149,6 +2629,11 @@ export declare interface DataLabelsContent {
2149
2629
  labels: PlacedDataLabel[];
2150
2630
  }
2151
2631
 
2632
+ /**
2633
+ * User-facing data-labels options for a layer (`geom.x({ dataLabels })`). A partial of {@link DataLabelsConfig}
2634
+ * minus `labelSource` (the label source is derived from the geom, not set here); unset fields fall back to the
2635
+ * config defaults. Set `{ showDataLabels: true }` to turn labels on.
2636
+ */
2152
2637
  export declare type DataLabelsInput = DeepPartial<Omit<DataLabelsConfig, 'labelSource'>>;
2153
2638
 
2154
2639
  /**
@@ -2172,6 +2657,10 @@ export declare type DataLabelTextMeasurer = (kind: DataLabelKind, text: string)
2172
2657
  *
2173
2658
  * All transformation methods (filter, orderBy, addVariable etc.) return a new instance.
2174
2659
  *
2660
+ * In a geom, this is what a `compile()` half reparameterises (e.g. `addVariable` to write computed
2661
+ * columns) and what a render half receives as `layer.data` — iterate it (or `groupBy` it) to walk the
2662
+ * compiled {@link Observation}s and read each mark's positions with the value readers.
2663
+ *
2175
2664
  * @example
2176
2665
  * const data = new Dataset({
2177
2666
  * age: { type: 'numeric', values: [25, 30, 35, null] },
@@ -2439,9 +2928,13 @@ declare type DatetimeTickIntervalUnit = 'hour' | 'day' | 'week' | 'month' | 'qua
2439
2928
  /**
2440
2929
  * Recursively makes every property of `T` optional.
2441
2930
  * Unlike the built-in `Partial`, this applies to nested objects as well.
2931
+ *
2932
+ * An `unknown`/`any` property is treated as a leaf (kept as-is) rather than recursed into — recursing an
2933
+ * open value bag like `Record<string, unknown>` would otherwise rewrite each `unknown` value to `{}`,
2934
+ * making the original value un-assignable to its own `DeepPartial`.
2442
2935
  */
2443
2936
  declare type DeepPartial<T> = {
2444
- [K in keyof T]?: T[K] extends Array<infer U> ? Array<DeepPartial<U>> : NonNullable<T[K]> extends object ? DeepPartial<NonNullable<T[K]>> : T[K];
2937
+ [K in keyof T]?: unknown extends T[K] ? T[K] : T[K] extends Array<infer U> ? Array<DeepPartial<U>> : NonNullable<T[K]> extends object ? DeepPartial<NonNullable<T[K]>> : T[K];
2445
2938
  };
2446
2939
 
2447
2940
  export declare const DEFAULT_COLOR_PALETTE: string[];
@@ -2462,6 +2955,62 @@ declare type DefaultPaletteConfig = {
2462
2955
  type: 'default';
2463
2956
  };
2464
2957
 
2958
+ /**
2959
+ * Declares the compile-half of a custom annotation kind: its `type` name, `defaultParams`, and optional
2960
+ * coordinate `arity`. There is no compile logic here — coordinate resolution is generic — so this only
2961
+ * exists to type and register the kind.
2962
+ *
2963
+ * `TType` is a `const` type parameter so the literal kind name (`'calloutBox'`) survives to the type
2964
+ * level — the registration-typed builder keys `annotation.<kind>(...)` off it, the same way
2965
+ * `createGraphyBuilder` captures a geom's name. `TParams` is recovered from `defaultParams`; annotate or
2966
+ * cast it (`defaultParams: {...} as CalloutBoxParams`) when a param's literal union would otherwise widen.
2967
+ */
2968
+ export declare function defineAnnotation<const TType extends string, TParams extends object = object>(def: {
2969
+ type: TType;
2970
+ defaultParams?: TParams;
2971
+ coordinates?: AnnotationArity;
2972
+ }): AnnotationDef<TParams, TType>;
2973
+
2974
+ /**
2975
+ * Declares a custom stat — the grammar-correct home for a per-group derived value (share-of-total,
2976
+ * running total, rank, z-score). The `compute` receives the layer dataset, the effective mapping, the
2977
+ * resolved spec, and a `column` namespacing helper; it returns the transformed dataset plus any mapping
2978
+ * rebinding (`{ y: column('share') }`), exactly as the built-in `count`/`smooth` stats do.
2979
+ *
2980
+ * `TSpec` is the resolved spec interface (its `type` literal plus options). Declare it so the builder
2981
+ * method `stat.<type>(options)` is typed; the compute reads `input.spec` narrowed to it.
2982
+ *
2983
+ * @example
2984
+ * interface ShareStatSpec { type: 'shareOfTotal'; field: string }
2985
+ * export const shareOfTotal = defineStat<ShareStatSpec>({
2986
+ * type: 'shareOfTotal',
2987
+ * computedColumns: ['share'],
2988
+ * computedVariables: ['y'],
2989
+ * compute: ({ data, spec, column }) => {
2990
+ * const total = data.getValues(spec.field, { type: 'numeric', skipNulls: true }).reduce((a, b) => a + b, 0);
2991
+ * const share = column('share');
2992
+ * const values = data.getValues(spec.field, { type: 'numeric' }).map((v) => (v ?? 0) / total);
2993
+ * return { data: data.addVariable(share, 'numeric', values), mapping: { y: share } };
2994
+ * },
2995
+ * });
2996
+ */
2997
+ export declare function defineStat<const TSpec extends StatSpecBase = StatSpecBase>(manifest: StatDefinitionManifest<TSpec>): StatDef<TSpec>;
2998
+
2999
+ /**
3000
+ * Declares a custom transform — mapping-blind, whole-table reshaping (top-N-with-"Other", an exotic
3001
+ * fold/pivot) the five built-ins don't cover. Prefer a stat (`defineStat`) for any per-group derived
3002
+ * value; reach for a transform only to reshape the table itself. The `apply` receives the dataset and
3003
+ * the transform's options and returns a new dataset.
3004
+ *
3005
+ * @example
3006
+ * interface TopNOptions { valueName: string; n: number }
3007
+ * export const topN = defineTransform<'topN', TopNOptions>({
3008
+ * transformType: 'topN',
3009
+ * apply: (data, { valueName }) => data.orderBy(valueName, 'desc'), // …keep first n, fold rest into "Other"…
3010
+ * });
3011
+ */
3012
+ export declare function defineTransform<const TType extends string, TOptions extends object = object>(manifest: TransformDefinitionManifest<TType, TOptions>): TransformDef<TType, TOptions>;
3013
+
2465
3014
  declare interface DifferenceArrowDimensions {
2466
3015
  /** Gap between the arrow start point and its anchored observation. */
2467
3016
  arrowStartGap: number;
@@ -2488,25 +3037,34 @@ declare interface DifferenceArrowDimensions {
2488
3037
  }
2489
3038
 
2490
3039
  /**
2491
- * User-facing difference-arrow input. `size`, `color` and `labelCrossPosition`
2492
- * are defaulted by the resolver.
3040
+ * A labelled delta drawn between two data observations — the only built-in annotation that anchors to
3041
+ * DATA. Both endpoints are observation anchors (main-axis value + series), so the arrow snaps to the
3042
+ * dataset and survives resize. Only drawn under a cartesian coordinate system. `size`, `color` and
3043
+ * `labelCrossPosition` are defaulted by the resolver.
2493
3044
  */
2494
3045
  export declare interface DifferenceArrowInput {
2495
3046
  id?: string;
3047
+ /** Observation the arrow starts from. */
2496
3048
  start: ObservationAnchorInput;
3049
+ /** Observation the arrow points to. */
2497
3050
  end: ObservationAnchorInput;
3051
+ /** Which delta the label reports. */
2498
3052
  label: DifferenceArrowLabelKind;
3053
+ /** Arrow colour; `null`/omitted falls back to the theme default. @default null */
2499
3054
  color?: string | null;
3055
+ /** @default 'small' */
2500
3056
  size?: DifferenceArrowSize;
3057
+ /** Where the label sits along the arrow's cross-axis, as a `[0,1]` fraction. @default 0.5 */
2501
3058
  labelCrossPosition?: number;
2502
3059
  }
2503
3060
 
3061
+ /** What the arrow's label reports about the `start → end` delta. */
2504
3062
  export declare type DifferenceArrowLabelKind = 'absolute-difference' | 'relative-difference' | 'proportion';
2505
3063
 
2506
3064
  export declare type DifferenceArrowSize = 'small' | 'medium' | 'large';
2507
3065
 
2508
3066
  /**
2509
- * Resolved difference-arrow spec with all optional fields defaulted.
3067
+ * Resolved form of {@link DifferenceArrowInput} defaults applied, anchors normalised.
2510
3068
  */
2511
3069
  export declare interface DifferenceArrowSpec {
2512
3070
  id: string;
@@ -2578,23 +3136,39 @@ export declare interface ExternalMeasurements {
2578
3136
  footerSize: BoxSize;
2579
3137
  }
2580
3138
 
3139
+ /**
3140
+ * Extracts the constant value from a `{ value }` mapping. Returns undefined for variable mappings.
3141
+ */
3142
+ export declare function extractConstantValue(aestheticValue: AestheticValue | undefined): DataValue | undefined;
3143
+
2581
3144
  /** Flattens a title, subtitle, or caption to plain text for measurement and static renderers. */
2582
3145
  export declare const extractPlainText: (content: TextContent) => string;
2583
3146
 
3147
+ /**
3148
+ * Extracts the variable name from an AestheticValue.
3149
+ * Returns the variable name for string shorthands and { variable } mappings.
3150
+ * Returns null for constant { value } mappings or undefined values.
3151
+ */
3152
+ export declare function extractVariableName(aestheticValue: AestheticValue | undefined): VariableName | null;
3153
+
2584
3154
  declare function filter(options: FilterOptions): FilterTransformInput;
2585
3155
 
2586
3156
  /***************************************************************
2587
3157
  * Filter Transform
2588
3158
  ***************************************************************/
3159
+ /**
3160
+ * Options for `transform.filter` — keeps only observations where `variableName <operator> value`.
3161
+ */
2589
3162
  declare interface FilterOptions {
2590
3163
  /** The variable to filter on. */
2591
3164
  variableName: VariableName;
2592
- /** The comparison operator. */
3165
+ /** Comparison operator: `'eq'` | `'neq'` | `'gt'` | `'gte'` | `'lt'` | `'lte'`. */
2593
3166
  operator: ComparisonOperator;
2594
- /** The value to compare against. */
3167
+ /** The value to compare each observation's `variableName` against. */
2595
3168
  value: DataValue;
2596
3169
  }
2597
3170
 
3171
+ /** Row-filtering transform produced by `transform.filter`. */
2598
3172
  declare interface FilterTransformInput {
2599
3173
  type: 'transform';
2600
3174
  transformType: 'filter';
@@ -2617,6 +3191,24 @@ export declare function findAxisGuide(guides: CompiledGuides, scaleAestheticKey:
2617
3191
  */
2618
3192
  export declare function findLegendForAesthetic(guides: CompiledGuides, aesthetic: AestheticKey): CompiledLegendGuide | null;
2619
3193
 
3194
+ /**
3195
+ * A gate fixture: a chart authored as plain data — its spec, the custom geom(s) it uses, and the rows —
3196
+ * with no React. The codegen harness's compile and semantic gates load one (authored beside the geom as
3197
+ * `src/<name>.fixture.ts`, exporting a `fixture`) to check that the geom compiles to finite, serialisable
3198
+ * positions and that a synthetic cursor placed on each probed observation resolves hover and a localised
3199
+ * tooltip. It is the rendered chart minus the renderer, so the gates run headlessly.
3200
+ */
3201
+ export declare interface Fixture {
3202
+ /** The custom geom instance(s) to register with the compiler — the same instances the spec uses. */
3203
+ geoms: readonly Geom[];
3204
+ /** The spec rendered in `App.tsx`, built with `createGraphyBuilder` + `pipe`. */
3205
+ spec: SpecInput;
3206
+ /** Rows matching the spec's channels (OHLC for a candlestick, a node/link graph for a sankey, …). */
3207
+ data: Data;
3208
+ /** Observation indices the semantic gate fires a cursor at. Defaults to `[0]` when omitted. */
3209
+ probes?: number[];
3210
+ }
3211
+
2620
3212
  declare interface FlipCoordInput {
2621
3213
  type: 'coord';
2622
3214
  coordType: 'flip';
@@ -2733,23 +3325,31 @@ export declare interface FormattedPerGroupHeadline {
2733
3325
  }
2734
3326
 
2735
3327
  /**
2736
- * Freeform arrow annotation. Endpoints sit in plot-fractional coordinates
2737
- * (0..1), so they re-flow with panel size. Distinct from
2738
- * {@link DifferenceArrowInput}, which anchors to dataset observations.
3328
+ * A free-standing arrow pointing at something on the panel. Both endpoints sit in panel fractions
3329
+ * (`[0,1]`, top-left origin), so they re-flow with panel size but do NOT snap to a data point. Distinct
3330
+ * from {@link DifferenceArrowInput}, which anchors to dataset observations.
2739
3331
  */
2740
3332
  export declare interface FreeformArrowInput {
2741
3333
  id?: string;
3334
+ /** Tail endpoint. */
2742
3335
  start: ArrowEndpoint;
3336
+ /** Head endpoint (the end pointed at). */
2743
3337
  end: ArrowEndpoint;
2744
- /** null falls back to the theme `defaultAnnotationArrowStroke`. */
3338
+ /** `null` falls back to the theme `defaultAnnotationArrowStroke`. @default null */
2745
3339
  color?: string | null;
3340
+ /** @default 'medium' */
2746
3341
  thickness?: ArrowThickness;
3342
+ /** Arrowhead at the `start` (tail) endpoint. @default 'none' */
2747
3343
  startArrowheadStyle?: ArrowheadStyle;
3344
+ /** Arrowhead at the `end` (head) endpoint. @default 'line-arrow' */
2748
3345
  endArrowheadStyle?: ArrowheadStyle;
3346
+ /** @default 'solid' */
2749
3347
  lineStyle?: ArrowLineStyle;
3348
+ /** Apply the editor's hand-drawn "sticker" styling. @default false */
2750
3349
  hasStickerStyle?: boolean;
2751
3350
  }
2752
3351
 
3352
+ /** Resolved form of {@link FreeformArrowInput} — defaults applied. */
2753
3353
  export declare interface FreeformArrowSpec {
2754
3354
  id: string;
2755
3355
  start: ArrowEndpoint;
@@ -2786,7 +3386,7 @@ declare type GenerateTicksOptions = {
2786
3386
  * empty default and keep their typed params through the spec builder's static surface; a custom geom
2787
3387
  * names its params type and declares matching {@link defaultParams}.
2788
3388
  */
2789
- declare abstract class Geom<TParams extends object = object> {
3389
+ export declare abstract class Geom<TParams extends object = object> {
2790
3390
  /**
2791
3391
  * The aesthetics an author must map for this geom. Built-ins list closed aesthetic keys
2792
3392
  * (`['x','y']`); a custom geom may also list open channel names (a box plot's `min`/`q1`/…) that it
@@ -2878,6 +3478,13 @@ declare abstract class Geom<TParams extends object = object> {
2878
3478
  * renderer how to place the annotation.
2879
3479
  */
2880
3480
  resolveAnchorPosition(_observation: Observation, _coordSystem: CoordSystem): AnchorPosition | null;
3481
+ /**
3482
+ * Reparameterizes the stat-transformed data into the shape this geom's geometry needs, the central
3483
+ * hook a custom geom implements. Receives the transformed dataset, effective mapping and geom params;
3484
+ * returns the dataset with any computed position variables added (e.g. a bar's `xMin`/`xMax`/`yMin`
3485
+ * interval), the mapping overrides the geom injects, and any extra tooltip rows it contributes. The
3486
+ * compile pipeline runs this per layer before the position and visual mappers read the result.
3487
+ */
2881
3488
  abstract compile(input: GeomCompilerInput): CompiledGeom;
2882
3489
  /**
2883
3490
  * Validates the layer's mapping against invariants specific to this geom (e.g. a rule needs exactly
@@ -2887,6 +3494,25 @@ declare abstract class Geom<TParams extends object = object> {
2887
3494
  validateMapping?(input: GeomMappingValidationInput): ValidationIssue[];
2888
3495
  }
2889
3496
 
3497
+ /**
3498
+ * The built-in geom builders. Each is called with one {@link BaseGeomOptions} object and returns a pipeable
3499
+ * layer that `pipe`/`createSpec` folds onto the spec. Compose several to layer marks (e.g. bars + a trend
3500
+ * line). The five marks: `point` (scatter/bubble), `line`, `area`, `bar` (also pie/donut in polar), and
3501
+ * `rule` (a constant or data-driven reference line).
3502
+ *
3503
+ * @example
3504
+ * import { createSpec, pipe, geom, scale, config } from '@graphysdk/viz-engine';
3505
+ *
3506
+ * // Multi-series line; mapping `color` to a column splits series and adds a legend.
3507
+ * const spec = pipe(
3508
+ * createSpec({ x: 'month', y: 'sales', color: 'region' }),
3509
+ * geom.line(),
3510
+ * scale.x(),
3511
+ * scale.y(),
3512
+ * scale.color.palette(),
3513
+ * config({ legend: { position: 'top' } }),
3514
+ * );
3515
+ */
2890
3516
  export declare const geom: {
2891
3517
  point: typeof point;
2892
3518
  line: typeof line;
@@ -2946,12 +3572,31 @@ declare class GeomCompiler {
2946
3572
  resolveAnchorPosition(geomName: GeomIdentity, observation: Observation, coordSystem: CoordSystem): AnchorPosition | null;
2947
3573
  }
2948
3574
 
2949
- declare interface GeomCompilerInput {
2950
- /** The dataset after stat transformation */
3575
+ /**
3576
+ * What {@link Geom.compile} receives. The geom reads these to compute its mark geometry and returns a
3577
+ * {@link CompiledGeom}. The pipeline has already run the layer's stat and resolved its aesthetics, so
3578
+ * `compile` sees finished input and only reparameterises it.
3579
+ */
3580
+ export declare interface GeomCompilerInput {
3581
+ /**
3582
+ * The dataset after stat transformation — one row per observation, columnar. Read a mapped channel's
3583
+ * column with `extractVariableName(mapping[channel])`, then `data.getValues(column, { type })`; write
3584
+ * computed columns with `data.addVariable` / `data.addConstantVariable` (each returns a new dataset —
3585
+ * the Dataset is immutable).
3586
+ */
2951
3587
  data: Dataset;
2952
- /** The effective mapping for the layer */
3588
+ /**
3589
+ * The effective mapping for the layer: which data column (or constant) backs each aesthetic the author
3590
+ * declared. The source of every channel column the geom reads — including the custom `aes` channels in
3591
+ * {@link Geom.requiredAesthetics} (an OHLC `open`, a box plot `q1`). Read a custom channel with
3592
+ * `readAesthetic(mapping, channel)`.
3593
+ */
2953
3594
  mapping: AesMapping;
2954
- /** Geom-specific params */
3595
+ /**
3596
+ * The geom's static params, already merged over {@link Geom.defaultParams} by the builder. Render
3597
+ * configuration only (widths, radii, colours) — never data columns that bind to a scale, which belong
3598
+ * in `aes`. Typed as the geom's `TParams` at the call site.
3599
+ */
2955
3600
  params: LayerSpec['params'];
2956
3601
  }
2957
3602
 
@@ -2980,8 +3625,10 @@ declare interface GeomMappingValidationInput {
2980
3625
  /** A built-in geom's name — the default vocabulary the spec builder offers out of the box. */
2981
3626
  export declare type GeomName = (typeof GEOM_NAMES)[number];
2982
3627
 
3628
+ /** {@link BaseGeomOptions} specialised to geom `G`, so its `params` is typed to that geom's param shape. */
2983
3629
  declare type GeomOptions<G extends GeomName> = BaseGeomOptions<GeomParamsMap[G]>;
2984
3630
 
3631
+ /** Union of every built-in geom's params type; the upper bound for {@link BaseGeomOptions}'s generic. */
2985
3632
  declare type GeomParams = GeomParamsMap[keyof GeomParamsMap];
2986
3633
 
2987
3634
  /**
@@ -3014,19 +3661,34 @@ declare class GeomRegistry extends Registry<string, Geom> {
3014
3661
  * data: the `label` is static text and the `variable` names a column, so the rows ride in the
3015
3662
  * serialisable compiled spec.
3016
3663
  */
3017
- declare interface GeomTooltipRow {
3664
+ export declare interface GeomTooltipRow {
3018
3665
  /** The row's label (e.g. "Open"). Static text the geom supplies. */
3019
3666
  label: string;
3020
3667
  /** The data column whose per-observation value the row displays. */
3021
3668
  variable: VariableName;
3022
3669
  }
3023
3670
 
3024
- /** Reads the resolved alpha (opacity) value from an observation. */
3671
+ /**
3672
+ * Reads the observation's resolved opacity in `[0,1]` (0 = transparent, 1 = opaque) — pass straight to
3673
+ * `fillOpacity`/`opacity`. The `alpha` aesthetic mapped through its scale. `null` when no `alpha`
3674
+ * aesthetic is mapped.
3675
+ */
3025
3676
  export declare function getAlpha(observation: Observation): NumericDataValue;
3026
3677
 
3678
+ /**
3679
+ * Reads a polar observation's angular extent — the x interval projected to angles. Use it to draw the
3680
+ * wedge of a pie/donut slice or polar bar; pair with {@link getRadiusExtent} for the radial span.
3681
+ * `startAngle`/`endAngle` are in **radians** (0 = straight up, increasing clockwise). The compiler has
3682
+ * already projected the x interval under `coord.polar()`, so no manual angle math is needed.
3683
+ */
3027
3684
  export declare function getAngleExtent(observation: Observation): AngleExtent;
3028
3685
 
3029
- /** Reads the resolved color string from an observation. */
3686
+ /**
3687
+ * Reads the observation's resolved fill/stroke colour as a paint-ready CSS colour string. The visual
3688
+ * mapper has already run the `color` aesthetic through the colour scale, so this is the final string to
3689
+ * hand to `fill`/`stroke` — no further lookup needed. `undefined` when the layer maps no `color`
3690
+ * aesthetic; supply your own series colour (e.g. via `useCategoricalColor`) in that case.
3691
+ */
3030
3692
  export declare function getColor(observation: Observation): string | undefined;
3031
3693
 
3032
3694
  /** Reads the coordinate lying on the cross axis of the coord system. */
@@ -3040,6 +3702,13 @@ export declare function getCrossAxisCoordinate(mainAxis: MainAxis, point: XYPoin
3040
3702
  */
3041
3703
  export declare const getDifferenceArrowDimensions: (size: DifferenceArrowSize, textScale: number) => DifferenceArrowDimensions;
3042
3704
 
3705
+ /**
3706
+ * Reads the observation's resolved series identity: the category the `group`/`color` aesthetic placed
3707
+ * it in, as a plain string. Use it to split a layer's observations into series (one polygon, line, or
3708
+ * colour per group) when painting. `null` when the layer maps no grouping aesthetic — a single,
3709
+ * ungrouped series. Reads the compiler-emitted `group` column, so the value survives any renaming of
3710
+ * the user's grouping mapping.
3711
+ */
3043
3712
  export declare const getGroup: (observation: Observation) => CategoricalDataValue;
3044
3713
 
3045
3714
  /**
@@ -3050,20 +3719,33 @@ export declare const getGroup: (observation: Observation) => CategoricalDataValu
3050
3719
  export declare const getIdentityKey: (observation: Observation) => string;
3051
3720
 
3052
3721
  /**
3053
- * Reads the resolved line type (stroke style) from an observation.
3054
- * Falls back to `'solid'` when no `lineType` variable was derived.
3722
+ * Reads the observation's resolved line style (`'solid'`, `'dashed'`, …) for use as a stroke pattern.
3723
+ * The `lineType` aesthetic mapped through its scale, falling back to `'solid'` when no `lineType`
3724
+ * aesthetic is mapped — so this reader, unlike the others, never returns `null`.
3055
3725
  */
3056
3726
  export declare function getLineType(observation: Observation): LineStyleType;
3057
3727
 
3058
3728
  /** Reads the coordinate lying on the main (independent) axis of the coord system. */
3059
3729
  export declare function getMainAxisCoordinate(mainAxis: MainAxis, point: XYPoint): number;
3060
3730
 
3731
+ /**
3732
+ * Reads a polar observation's radial extent — the y interval projected to radii. Use it with
3733
+ * {@link getAngleExtent} to draw a donut/polar-bar segment. `innerRadius`/`outerRadius` are in `[0,1]`
3734
+ * (0 = centre, 1 = outer ring); `outerRadius` falls back to the `point` y radius when the observation
3735
+ * carries no upper y endpoint (a pie slice, which has no inner cutout to oppose).
3736
+ */
3061
3737
  export declare function getRadiusExtent(observation: Observation): RadiusExtent;
3062
3738
 
3063
- /** Reads the resolved size value from an observation. */
3739
+ /**
3740
+ * Reads the observation's resolved size in **pixels** (e.g. a point's diameter or a mark's nominal
3741
+ * extent), already mapped through the `size` scale. `null` when no `size` aesthetic is mapped.
3742
+ */
3064
3743
  export declare function getSize(observation: Observation): NumericDataValue;
3065
3744
 
3066
- /** Reads the resolved stroke width value from an observation. */
3745
+ /**
3746
+ * Reads the observation's resolved stroke width in **pixels** — pass straight to `strokeWidth`. The
3747
+ * `strokeWidth` aesthetic mapped through its scale. `null` when no `strokeWidth` aesthetic is mapped.
3748
+ */
3067
3749
  export declare function getStrokeWidth(observation: Observation): NumericDataValue;
3068
3750
 
3069
3751
  declare interface GetValuesOptions {
@@ -3075,29 +3757,61 @@ declare interface GetValuesOptions {
3075
3757
  distinct?: boolean;
3076
3758
  }
3077
3759
 
3078
- /** Reads the normalized x position from an observation. */
3760
+ /**
3761
+ * Reads the observation's scaled x position: the value of the `point` x channel, already mapped
3762
+ * through the x scale to `[0,1]` of the panel width (0 = left edge, 1 = right edge). `null` when the
3763
+ * observation has no x position. Under `coord.polar({ theta: 'x' })` this returns the vertex **angle
3764
+ * in radians** instead (0 = straight up, increasing clockwise). The everyday position reader — pair
3765
+ * it with {@link getY} to place a point-anchored mark.
3766
+ */
3079
3767
  export declare function getX(observation: Observation): NumericDataValue;
3080
3768
 
3081
- /** Reads the normalized xMax (right band edge) from an observation. */
3769
+ /**
3770
+ * Reads the upper x endpoint of the observation's x interval, scaled to `[0,1]` of the panel width
3771
+ * (1 = right edge). The right edge of a band/bar or the end of a horizontal range bar. Pairs with
3772
+ * {@link getXMin}. `null` when the observation declares no x interval.
3773
+ */
3082
3774
  export declare function getXMax(observation: Observation): NumericDataValue;
3083
3775
 
3084
- /** Reads the normalized xMin (left band edge) from an observation. */
3776
+ /**
3777
+ * Reads the lower x endpoint of the observation's x interval, scaled to `[0,1]` of the panel width
3778
+ * (0 = left edge). The left edge of a band/bar, the start of a horizontal range bar, or a body's left
3779
+ * side. Pairs with {@link getXMax}; `getXMin`/`getXMax` preserve the values `compile()` wrote and are
3780
+ * never re-sorted, so `getXMin` can exceed `getXMax`. `null` when the observation declares no x interval.
3781
+ */
3085
3782
  export declare function getXMin(observation: Observation): NumericDataValue;
3086
3783
 
3087
- /** Reads the normalized y position from an observation. */
3784
+ /**
3785
+ * Reads the observation's scaled y position: the value of the `point` y channel, already mapped
3786
+ * through the y scale to `[0,1]` of the panel height with a **bottom origin** (0 = bottom, 1 = top).
3787
+ * SVG y grows downward, so paint with `1 - getY(...)`. `null` when the observation has no y position.
3788
+ * Under polar coords this returns the **radius in `[0,1]`** (0 = centre, 1 = outer ring). See
3789
+ * {@link getYRaw} to recover the pre-stack segment magnitude.
3790
+ */
3088
3791
  export declare function getY(observation: Observation): NumericDataValue;
3089
3792
 
3090
- /** Reads the normalized yMax (upper extent) from an observation. */
3793
+ /**
3794
+ * Reads the upper y endpoint of the observation's y interval, scaled to `[0,1]` of the panel height
3795
+ * with a **bottom origin** (1 = top; paint with `1 - getYMax(...)`). The bar top, the top of a
3796
+ * candlestick wick, or the end of a vertical range/gantt span. Pairs with {@link getYMin}.
3797
+ * `null` when the observation declares no y interval.
3798
+ */
3091
3799
  export declare function getYMax(observation: Observation): NumericDataValue;
3092
3800
 
3093
- /** Reads the normalized yMin (lower extent) from an observation. */
3801
+ /**
3802
+ * Reads the lower y endpoint of the observation's y interval, scaled to `[0,1]` of the panel height
3803
+ * with a **bottom origin** (0 = bottom; paint with `1 - getYMin(...)`). The bar baseline, the bottom of
3804
+ * a candlestick wick, or the start of a vertical range/gantt span. Pairs with {@link getYMax}; the pair
3805
+ * preserves the values `compile()` wrote and is never re-sorted, so `getYMin` can exceed `getYMax`.
3806
+ * `null` when the observation declares no y interval.
3807
+ */
3094
3808
  export declare function getYMin(observation: Observation): NumericDataValue;
3095
3809
 
3096
3810
  /**
3097
- * Reads the segment value written by stacking position adjusters.
3098
- *
3099
- * Survives theposition-mapper untouched, so renderers can recover original-unit values
3100
- * for stacked segments after `mapping.y` has been rewritten to the cumulative band top.
3811
+ * Reads the observation's pre-stack segment magnitude in **original data units** (not `[0,1]`).
3812
+ * Stacking position adjusters rewrite the mapped `y` to the cumulative band top and stash the segment's
3813
+ * own value here, so a renderer or data label can recover what the segment contributed before stacking.
3814
+ * `null` when the layer was not stacked (the column is written only when stacking along y).
3101
3815
  */
3102
3816
  export declare function getYRaw(observation: Observation): NumericDataValue;
3103
3817
 
@@ -3238,7 +3952,7 @@ declare type GraphyPaletteVariant = 'default' | 'waterfall';
3238
3952
  * for instance. A geom declares these so the axis guide resolves grid policy from the definition
3239
3953
  * instead of a geom-keyed lookup. Every field absent means the geom imposes no policy.
3240
3954
  */
3241
- declare interface GridPolicy {
3955
+ export declare interface GridPolicy {
3242
3956
  hideGridX?: boolean;
3243
3957
  hideGridY?: boolean;
3244
3958
  hideBorder?: boolean;
@@ -3484,15 +4198,29 @@ export declare class HeuristicTextMeasurer implements TextMeasurer {
3484
4198
  }
3485
4199
 
3486
4200
  /**
3487
- * Create a pipeable highlight spec item.
4201
+ * Pipeable spec item that emphasises the observations matching `predicate` and
4202
+ * de-emphasises (dims or desaturates) everything else. Multiple `highlight(...)`
4203
+ * calls accumulate — their matches union. The de-emphasis style is chosen
4204
+ * separately via `config({ appearance: { highlightStyle: 'dim' | 'desaturate' } })`.
4205
+ *
4206
+ * @param predicate - which observations to emphasise (see {@link Predicate}).
4207
+ * @param options - `scope` ({@link HighlightScope}, default `'data-point'`),
4208
+ * `layerIndex` (target a single layer; omit to apply to all layers), and an
4209
+ * optional explicit `id`.
3488
4210
  *
3489
4211
  * @example
4212
+ * import { pipe, createSpec, geom, scale, highlight } from '@graphysdk/viz-engine';
4213
+ *
3490
4214
  * pipe(
3491
- * createSpec(data, { x: 'month', y: 'revenue', color: 'region' }),
4215
+ * createSpec({ x: 'month', y: 'revenue', color: 'region' }),
3492
4216
  * geom.bar(),
3493
- * highlight({ field: 'region', eq: 'EU' }),
3494
- * highlight({ field: 'region', eq: 'US' }, { scope: 'series' }),
3495
- * )
4217
+ * scale.x(),
4218
+ * scale.y(),
4219
+ * // emphasise one whole series; leave other layers untouched
4220
+ * highlight({ variable: 'region', eq: 'EU' }, { scope: 'series' }),
4221
+ * // and every observation at or above a threshold
4222
+ * highlight({ variable: 'revenue', gte: 2000 })
4223
+ * );
3496
4224
  */
3497
4225
  export declare function highlight(predicate: Predicate, options?: HighlightBuilderOptions): HighlightInput;
3498
4226
 
@@ -3642,10 +4370,10 @@ export declare class HoverEngine {
3642
4370
  */
3643
4371
  private nonInteractiveLayerIds;
3644
4372
  /**
3645
- * Render-side hit-testers registered per layer for `render-hit-test` (Tier-C) layers, keyed by
4373
+ * Render-side hit-testers registered per layer for `render-hit-test` (geom-layout) layers, keyed by
3646
4374
  * `CompiledLayer.id`. The renderer owns this map and injects it via {@link setHitTesters}; the
3647
4375
  * engine holds the live reference so a plugin mounting or updating its tester is visible at the
3648
- * next `query()` without a re-index. Empty for charts with no Tier-C geom.
4376
+ * next `query()` without a re-index. Empty for charts with no geom-layout geom.
3649
4377
  */
3650
4378
  private hitTesters;
3651
4379
  constructor({ layers, coordSystem }: HoverEngineInput);
@@ -3786,6 +4514,17 @@ declare interface InferredScaleInput {
3786
4514
 
3787
4515
  declare type InferredScaleOptions = ContinuousScaleOptions | DiscreteScaleOptions | DatetimeScaleOptions;
3788
4516
 
4517
+ /**
4518
+ * Recovers where a raw sub-value sits inside an already-scaled interval. Given a raw `[rawLo, rawHi]`
4519
+ * pair that the compiler mapped to the scaled `[scaledLo, scaledHi]` endpoints, returns the scaled
4520
+ * position of `raw` by affine interpolation. The geom-scaled trick a candlestick uses to place its open/close
4521
+ * inside the scaled `[low, high]` wick without re-running the y-scale.
4522
+ *
4523
+ * Exact only when the scale between raw and scaled space is **linear** — both endpoints pin a straight
4524
+ * line every interior value reads off. A degenerate interval (`rawLo === rawHi`) returns `scaledLo`.
4525
+ */
4526
+ export declare function interpolateInScaledInterval(raw: number, rawLo: number, rawHi: number, scaledLo: number, scaledHi: number): number;
4527
+
3789
4528
  /**
3790
4529
  * Curve interpolation method for lines and areas.
3791
4530
  *
@@ -3869,13 +4608,27 @@ declare interface LayerCompilerInput {
3869
4608
  */
3870
4609
  declare type LayerInput = BuiltinLayerInput | CustomLayerInput;
3871
4610
 
4611
+ /**
4612
+ * Geom-agnostic fields shared by every layer input, before resolution. Builders produce this shape (with
4613
+ * `geom` and `params` added per geom); all fields are optional and filled with defaults during resolution.
4614
+ */
3872
4615
  declare interface LayerInputBase {
4616
+ /** Discriminant marking this spec item as a layer. */
3873
4617
  type: 'layer';
4618
+ /** Optional stable identifier for the layer; auto-assigned during resolution when omitted. */
3874
4619
  id?: string;
4620
+ /**
4621
+ * Layer-level aesthetic mapping (the builder's `aes`), shallow-merged OVER the spec-level mapping for this
4622
+ * layer only. Retargets a channel per layer or pins a constant via `{ y: { value } }`.
4623
+ */
3875
4624
  mapping?: AesMapping;
3876
- stat?: StatName | StatInput;
4625
+ /** Statistical transform(s) applied before positioning — a single stat or a pipeline. @default 'identity' */
4626
+ stat?: StatLayerInput | StatLayerInput[];
4627
+ /** How sibling marks sharing an x position are arranged (`'stack'`, `'dodge'`, `'fill'`, `'identity'`). Default is per-geom. */
3877
4628
  position?: PositionType;
4629
+ /** Which Y axis the layer binds to — `'secondary'` targets the right-hand axis in a dual-axis combo. @default 'primary' */
3878
4630
  yScaleType?: YScaleType;
4631
+ /** Per-observation value labels drawn on the marks. @default off */
3879
4632
  dataLabels?: DataLabelsInput;
3880
4633
  /**
3881
4634
  * Ordered transforms applied to this layer's view of the data, on top of any
@@ -3890,6 +4643,7 @@ declare interface LayerInputBase {
3890
4643
  interactive?: boolean;
3891
4644
  }
3892
4645
 
4646
+ /** A built-in layer input narrowed to geom `G`: the shared base plus that geom's tag and partial `params`. */
3893
4647
  declare type LayerInputOf<G extends GeomName> = LayerInputBase & {
3894
4648
  geom: G;
3895
4649
  params?: Partial<GeomParamsMap[G]>;
@@ -3900,18 +4654,32 @@ declare type LayerInputOf<G extends GeomName> = LayerInputBase & {
3900
4654
  */
3901
4655
  declare type LayerSpec = BuiltinLayerSpec | CustomLayerSpec;
3902
4656
 
4657
+ /**
4658
+ * Geom-agnostic fields shared by every resolved layer spec. Mirrors {@link LayerInputBase} with all fields
4659
+ * required and defaults applied (`stat` resolved to {@link ResolvedStatSpec}[], `dataLabels` fully expanded).
4660
+ */
3903
4661
  declare interface LayerSpecBase {
4662
+ /** Discriminant marking this spec item as a layer. */
3904
4663
  type: 'layer';
4664
+ /** Resolved stable identifier for the layer (always present after resolution). */
3905
4665
  id: string;
4666
+ /** Resolved aesthetic mapping for this layer, merged from spec-level and layer-level inputs. */
3906
4667
  mapping: AesMapping;
3907
- stat: StatSpec;
4668
+ /** Resolved statistical-transform pipeline (empty array means identity). */
4669
+ stat: ResolvedStatSpec[];
4670
+ /** Resolved arrangement of sibling marks at the same x position. */
3908
4671
  position: PositionType;
4672
+ /** Resolved Y-axis binding (`'primary'` or `'secondary'`). */
3909
4673
  yScaleType: YScaleType;
4674
+ /** Resolved ordered transforms applied to this layer's view of the data. */
3910
4675
  transforms: TransformInput[];
4676
+ /** Whether the layer participates in hover hit-detection. */
3911
4677
  interactive: boolean;
4678
+ /** Resolved per-observation data-labels configuration. */
3912
4679
  dataLabels: DataLabelsConfig;
3913
4680
  }
3914
4681
 
4682
+ /** A resolved built-in layer spec narrowed to geom `G`: the shared base plus that geom's tag and full `params`. */
3915
4683
  declare type LayerSpecOf<G extends GeomName> = LayerSpecBase & {
3916
4684
  geom: G;
3917
4685
  params: GeomParamsMap[G];
@@ -3947,7 +4715,7 @@ declare interface LayerValidationCheckInput {
3947
4715
  declare interface LayerValidationInput {
3948
4716
  layerId: string;
3949
4717
  geom: GeomIdentity;
3950
- stat: StatSpec;
4718
+ stat: ResolvedStatSpec[];
3951
4719
  /** `spec.mapping` merged with `layer.mapping` */
3952
4720
  effectiveMapping: AesMapping;
3953
4721
  /** Layer's dataset after its own transforms have been applied */
@@ -3986,6 +4754,9 @@ declare class LayerValidator {
3986
4754
  * without a `validateMapping` hook impose none.
3987
4755
  */
3988
4756
  private validateGeomMapping;
4757
+ /** Unions the `computedVariables` of every stat in the layer's pipeline — any aesthetic computed by
4758
+ * any stage is waived from the pre-stat existence/required checks. */
4759
+ private collectComputedVariables;
3989
4760
  /**
3990
4761
  * Rejects a layer whose coord is absent from the geom's declared `supportedCoordTypes` (e.g. a rule
3991
4762
  * has no meaning under polar pie/donut coords). The supported set lives on the geom definition, so
@@ -4004,14 +4775,16 @@ export declare const LAYOUT_PADDING = 24;
4004
4775
  * settled after the first resolve.
4005
4776
  *
4006
4777
  * Here's the pipeline:
4007
- * 1. **Seed**: stamp each axis with a placeholder label so the grid has something to measure.
4008
- * 2. **Resolve v1**: first grid pass; `panel.height` is now final.
4009
- * 3. **Finalize vertical**: pick the densest candidate that fits `panel.height` for left/right
4778
+ * 1. **Polar prep**: split cartesian from polar axes, format the polar axes whole, and derive the
4779
+ * symmetric `polarMargin` their rim labels reserve (both empty/zero for a cartesian chart).
4780
+ * 2. **Seed**: stamp each cartesian axis with a placeholder label so the grid has something to measure.
4781
+ * 3. **Resolve v1**: first grid pass; `panel.height` is now final.
4782
+ * 4. **Finalize vertical**: pick the densest candidate that fits `panel.height` for left/right
4010
4783
  * axes; horizontal axes keep their seed.
4011
- * 4. **Resolve v2**: vertical edge widths now reflect final labels, so `panel.width` is final.
4012
- * 5. **Finalize horizontal**: pick the densest candidate that fits `panel.width` for top/bottom
4013
- * axes. Vertical axes carry over from step 3.
4014
- * 6. **Resolve v3**: final grid pass with all axes finalized.
4784
+ * 5. **Resolve v2**: vertical edge widths now reflect final labels, so `panel.width` is final.
4785
+ * 6. **Finalize horizontal**: pick the densest candidate that fits `panel.width` for top/bottom
4786
+ * axes. Vertical axes carry over from step 4.
4787
+ * 7. **Resolve v3**: final grid pass with all axes finalized.
4015
4788
  */
4016
4789
  export declare class LayoutCompiler {
4017
4790
  private readonly measurer;
@@ -4047,6 +4820,13 @@ export declare class LayoutCompiler {
4047
4820
  export declare interface LayoutCompileResult {
4048
4821
  layout: GraphLayout;
4049
4822
  formattedAxes: FormattedAxis[];
4823
+ /**
4824
+ * Polar axes (circular angular + radial), formatted with every tick (no fit-based selection or
4825
+ * truncation — a radar shows one spoke per category and rings at the radial ticks). Empty for cartesian
4826
+ * charts. The polar guide renderer consumes these together with the panel/plot rects; they are kept apart
4827
+ * from {@link formattedAxes} because they project around a circle, not onto the four cartesian edges.
4828
+ */
4829
+ formattedPolarAxes: FormattedAxis[];
4050
4830
  }
4051
4831
 
4052
4832
  /** Input for the layout compiler. */
@@ -4091,8 +4871,9 @@ declare interface Legend {
4091
4871
  */
4092
4872
  declare interface LegendConfig {
4093
4873
  /**
4094
- * Position of the legend
4095
- * @default 'top'
4874
+ * Where the legend sits relative to the plot. See {@link LegendPosition} for the values;
4875
+ * `'auto'` lets the renderer pick based on chart type and series count.
4876
+ * @default 'auto'
4096
4877
  */
4097
4878
  position: LegendPosition;
4098
4879
  /**
@@ -4152,8 +4933,23 @@ declare interface LegendItemVisual {
4152
4933
  lineType?: LineStyleType;
4153
4934
  }
4154
4935
 
4936
+ /**
4937
+ * Where the legend sits relative to the plot, set via
4938
+ * `config({ legend: { position: … } })`.
4939
+ * - 'auto': let the compiler choose based on chart type (default).
4940
+ * - 'right' | 'left' | 'top' | 'bottom': pin to that edge.
4941
+ * - 'none': hide the legend entirely.
4942
+ */
4155
4943
  declare type LegendPosition = 'auto' | 'right' | 'left' | 'top' | 'bottom' | 'none';
4156
4944
 
4945
+ /**
4946
+ * Line marks — connected series. One line per `group` (defaults to the `color` column). Tune the stroke via
4947
+ * {@link LineGeomParams}. Pair with `stat.smooth()` for a trendline. Observations are connected in data
4948
+ * order, so sort by x first.
4949
+ *
4950
+ * @example
4951
+ * pipe(createSpec({ x: 'month', y: 'sales', color: 'region' }), geom.line(), scale.x(), scale.y(), scale.color.palette());
4952
+ */
4157
4953
  declare function line(options?: GeomOptions<'line'>): LayerInputOf<'line'>;
4158
4954
 
4159
4955
  /**
@@ -4176,17 +4972,22 @@ declare class LineGeom extends Geom {
4176
4972
  }
4177
4973
 
4178
4974
  /**
4179
- * Line-specific parameters
4975
+ * Render parameters for `geom.line`. Passed under `params`.
4180
4976
  */
4181
4977
  export declare interface LineGeomParams {
4978
+ /**
4979
+ * Stroke width in pixels, or `'auto'` to let the theme pick a width.
4980
+ * @default 'auto'
4981
+ */
4182
4982
  lineWidth: number | 'auto';
4183
4983
  /**
4184
- * Interpolation method to use for the line.
4984
+ * Interpolation method between points: `'linear'` for straight segments, `'catmull-rom'` for a smooth spline.
4185
4985
  * @default 'linear'
4186
4986
  */
4187
4987
  interpolate: InterpolateType;
4188
4988
  /**
4189
- * How to handle missing (NULL/undefined) values.
4989
+ * How to handle missing (`null`) y-values: `'gap'` breaks the line, `'zero'` drops to zero, `'connect'`
4990
+ * bridges across the gap.
4190
4991
  * @default 'gap'
4191
4992
  */
4192
4993
  missingValues: MissingValuesType;
@@ -4213,7 +5014,12 @@ export declare type Locale = (typeof LOCALES)[number];
4213
5014
  /** A BCP-47 string representing a supported locale. */
4214
5015
  declare const LOCALES: readonly ["en-GB", "en-US", "ar", "pt-PT"];
4215
5016
 
4216
- /** Logical composition of any predicate. */
5017
+ /**
5018
+ * Boolean composition of nested predicates:
5019
+ * - `and`: every sub-predicate matches.
5020
+ * - `or`: at least one matches.
5021
+ * - `not`: the sub-predicate does not match.
5022
+ */
4217
5023
  export declare type LogicalPredicate = {
4218
5024
  and: Predicate[];
4219
5025
  } | {
@@ -4248,7 +5054,9 @@ export declare type MainAxis = 'x' | 'y';
4248
5054
  declare type MappableAes<Definition extends Geom> = Definition['requiredAesthetics'][number] | Definition['visualAesthetics'][number] | 'group';
4249
5055
 
4250
5056
  /**
4251
- * Create a pipeable mapping spec item.
5057
+ * Create a pipeable mapping spec item. Use this form (rather than passing the mapping as the first
5058
+ * `createSpec` arg) when a transform must run before the mapping is read — e.g. reshaping wide columns
5059
+ * to long so a freshly-created column can be bound to a channel.
4252
5060
  *
4253
5061
  * @example
4254
5062
  * createSpec(
@@ -4261,13 +5069,38 @@ declare type MappableAes<Definition extends Geom> = Definition['requiredAestheti
4261
5069
  export declare function mapping(aes: AesMapping): MappingItem;
4262
5070
 
4263
5071
  /**
4264
- * A pipeable spec item that sets/merges the global aesthetic mapping.
5072
+ * A pipeable spec item that sets/merges the global {@link AesMapping}. Produced by {@link mapping} and
5073
+ * folded into the spec by `pipe`/`createSpec`; later mapping items shallow-merge over earlier channels.
4265
5074
  */
4266
5075
  declare interface MappingItem {
4267
5076
  type: 'mapping';
4268
5077
  mapping: AesMapping;
4269
5078
  }
4270
5079
 
5080
+ /** Declares one mark kind's own columns and the data type of each. */
5081
+ export declare type MarkColumnSchema = Record<string, DataType>;
5082
+
5083
+ /**
5084
+ * Builds one columnar {@link Dataset} from heterogeneous marks discriminated by a `kind` column — *the*
5085
+ * geom-layout dataset shape (node+link, group+leaf, node+edge). Each kind declares its own columns; the union
5086
+ * across kinds forms the dataset's columns, and a row's off-kind columns are filled with `null` **by
5087
+ * construction**, so the null-padding invariant a hand-built builder maintains by hand (and breaks when a
5088
+ * column is omitted from one kind's push) can no longer drift.
5089
+ */
5090
+ declare class MarkTable {
5091
+ /** Column name → declared type, accumulated as the union across every declared kind. */
5092
+ private readonly columnTypes;
5093
+ /** Kind name → the column names that kind owns. */
5094
+ private readonly kindColumns;
5095
+ private readonly rows;
5096
+ /** Declares a mark kind and the columns it carries. Throws if the kind repeats or a column's type conflicts. */
5097
+ kind(name: string, columns: MarkColumnSchema): this;
5098
+ /** Appends one row for a declared kind. Throws if the kind is unknown, or a value misses/overshoots the kind's columns. */
5099
+ push(kind: string, values: Record<string, DataValue>): this;
5100
+ /** Materialises the rows into a {@link Dataset}, null-padding every off-kind column. */
5101
+ toDataset(options: ToDatasetOptions): Dataset;
5102
+ }
5103
+
4271
5104
  declare function mean(): MeanStatSpec;
4272
5105
 
4273
5106
  /**
@@ -4353,14 +5186,16 @@ declare type NeonPaletteConfig = {
4353
5186
  declare type NeonPaletteVariant = 'default' | 'waterfall';
4354
5187
 
4355
5188
  /**
4356
- * Configuration for formatting a single number.
4357
- * Defines how numeric values should be displayed in the chart.
5189
+ * Chart-wide number formatting, applied by the renderer to every numeric value
5190
+ * (axis ticks, tooltips, data labels, headline figures). Set via
5191
+ * `config({ numberFormat: { … } })`.
4358
5192
  */
4359
5193
  export declare interface NumberFormatConfig {
4360
5194
  /**
4361
5195
  * Number of decimal places to display.
4362
5196
  * - number: Fixed decimal places (e.g., 2 → "1234.56")
4363
- * - 'auto': Automatic based on value magnitude (default)
5197
+ * - 'auto': Automatic based on value magnitude
5198
+ * @default 'auto'
4364
5199
  */
4365
5200
  decimals: number | 'auto';
4366
5201
  /**
@@ -4370,6 +5205,7 @@ export declare interface NumberFormatConfig {
4370
5205
  * - 'k': Force thousands (1234567 → "1,234.6K")
4371
5206
  * - 'm': Force millions (1234567 → "1.2M")
4372
5207
  * - 'b': Force billions (1234567890 → "1.2B")
5208
+ * @default 'auto'
4373
5209
  */
4374
5210
  abbreviation: 'auto' | 'k' | 'm' | 'b' | 'none';
4375
5211
  /**
@@ -4398,7 +5234,13 @@ declare interface NumericValueFormat {
4398
5234
  type: 'decimal' | 'integer' | 'percentage' | 'duration';
4399
5235
  }
4400
5236
 
4401
- /** A single row of data, mapping every variable name to the value in that row. */
5237
+ /**
5238
+ * One compiled per-observation record — the unit a geom's render half iterates and reads to paint a
5239
+ * single mark. Maps every variable name (the author's data columns plus the compiler's internal
5240
+ * position/visual/group columns) to that observation's value. Read positions and encodings off it with
5241
+ * the value readers ({@link getX}, {@link getYMin}, {@link getColor}, …) rather than indexing internal
5242
+ * keys by hand; read your own named columns with `readNumber`/`readString`.
5243
+ */
4402
5244
  export declare type Observation = Record<VariableName, DataValue>;
4403
5245
 
4404
5246
  /**
@@ -4417,13 +5259,16 @@ export declare interface ObservationAnchor {
4417
5259
  groupValue: DataValue;
4418
5260
  }
4419
5261
 
4420
- /** Points at a single observation by its anchor value and series. */
5262
+ /**
5263
+ * Snaps to a single data observation by its main-axis value and series. The annotation tracks that
5264
+ * observation across resize and re-layout (unlike panel-fractional positioning).
5265
+ */
4421
5266
  export declare interface ObservationAnchorInput {
4422
- /** Pick a specific layer when multiple share the same `(anchorValue, groupValue)` pair. */
5267
+ /** Pick a specific layer when multiple share the same `(anchorValue, groupValue)` pair. Index into the spec's layers. */
4423
5268
  layerIndex?: number;
4424
- /** Value on the main axis (x in cartesian, y in flipped). */
5269
+ /** Value on the main axis (x in cartesian, y in flipped) that selects the observation. */
4425
5270
  anchorValue: DataValue;
4426
- /** Series identity (the `group` aesthetic value). */
5271
+ /** Series identity the `color`/`group` aesthetic value that disambiguates within the axis value. */
4427
5272
  groupValue: DataValue;
4428
5273
  }
4429
5274
 
@@ -4455,13 +5300,15 @@ declare interface PaletteScaleSpec {
4455
5300
  * Panel configuration
4456
5301
  */
4457
5302
  declare interface PanelConfig {
5303
+ /** Border drawn around the plot panel (the data area). */
4458
5304
  border: {
5305
+ /** Whether the panel border is rendered. */
4459
5306
  isVisible: boolean;
4460
5307
  };
4461
5308
  }
4462
5309
 
4463
5310
  /**
4464
- * A render-side hit-test a Tier-C geom registers for a `render-hit-test` layer. The engine calls it
5311
+ * A render-side hit-test a geom-layout geom registers for a `render-hit-test` layer. The engine calls it
4465
5312
  * with the cursor in panel `[0, 1]` space using a **top-left origin (y-down)** — the same frame the
4466
5313
  * geom paints in (unit-space SVG / `toPercent`), so the geom can test against its own rendered
4467
5314
  * geometry without re-flipping. It returns the declared identity `key` of the observation under the
@@ -4518,19 +5365,29 @@ declare interface PieOptions {
4518
5365
  * Pinned-number annotation: a marker dot pinned to a single observation. The
4519
5366
  * renderer's mini view shows the observation's measurement value; hover reveals
4520
5367
  * the full tooltip (x + y + trend).
5368
+ *
5369
+ * NO PAINTER in `@graphysdk/react-renderer` — this compiles but never draws there (it renders only in
5370
+ * the editor's legacy engine). Don't reach for it when authoring for the React renderer.
4521
5371
  */
4522
5372
  declare interface PinnedNumberAnnotationInput {
4523
5373
  id?: string;
4524
5374
  anchor: ObservationAnchorInput;
4525
5375
  }
4526
5376
 
5377
+ /** Resolved form of {@link PinnedNumberAnnotationInput} — defaults applied, anchor normalised. */
4527
5378
  declare interface PinnedNumberAnnotationSpec {
4528
5379
  id: string;
4529
5380
  anchor: ObservationAnchor;
4530
5381
  }
4531
5382
 
4532
5383
  /**
4533
- * Pipe a spec through a series of spec items (left-to-right).
5384
+ * Fold a sequence of pipeable spec items onto an existing spec, left to right, returning a new spec.
5385
+ * Each item is appended by kind: layers accumulate (call `geom.*` once per mark), scales accumulate,
5386
+ * `config` deep-merges, `coord`/`mapping` overwrite/merge. The usual shape is
5387
+ * `pipe(createSpec({...}), geom.x(), scale.x(), scale.y(), ...)`.
5388
+ *
5389
+ * @example
5390
+ * pipe(createSpec({ x: 'month', y: 'sales', color: 'region' }), geom.line(), scale.x(), scale.y(), scale.color.palette());
4534
5391
  */
4535
5392
  export declare function pipe(spec: SpecInput, ...items: SpecItem[]): SpecInput;
4536
5393
 
@@ -4560,6 +5417,20 @@ export declare interface PlacedDataLabel {
4560
5417
  position: DataLabelPosition;
4561
5418
  }
4562
5419
 
5420
+ /**
5421
+ * Point marks — scatter plots and bubble charts. Map `size` to a column for a bubble chart and `color` for
5422
+ * categorical series. Sizing is controlled via {@link PointGeomParams} `size` or `scale.size.continuous`.
5423
+ *
5424
+ * @example
5425
+ * pipe(
5426
+ * createSpec({ x: 'gdp', y: 'lifeExp', size: 'population', color: 'continent' }),
5427
+ * geom.point(),
5428
+ * scale.x(),
5429
+ * scale.y(),
5430
+ * scale.size.continuous({ range: [4, 40] }),
5431
+ * scale.color.palette(),
5432
+ * );
5433
+ */
4563
5434
  declare function point(options?: GeomOptions<'point'>): LayerInputOf<'point'>;
4564
5435
 
4565
5436
  /**
@@ -4578,9 +5449,14 @@ declare class PointGeom extends Geom {
4578
5449
  }
4579
5450
 
4580
5451
  /**
4581
- * Point-specific parameters
5452
+ * Render parameters for `geom.point`. Passed under `params`.
4582
5453
  */
4583
5454
  declare interface PointGeomParams {
5455
+ /**
5456
+ * Mark diameter in pixels, used when `size` is not a data channel. To size by data instead, map the `size`
5457
+ * aesthetic and declare `scale.size.continuous({ range })`.
5458
+ * @default 8
5459
+ */
4584
5460
  size: number;
4585
5461
  }
4586
5462
 
@@ -4596,19 +5472,28 @@ declare interface PolarCoordInput {
4596
5472
  }
4597
5473
 
4598
5474
  /**
4599
- * Params for polar coordinate system
5475
+ * Resolved params for the polar coordinate system (defaults applied).
5476
+ * Drives pie, donut, and radar/radial layouts by mapping one scaled aesthetic to the
5477
+ * angle and the other to the radius.
4600
5478
  */
4601
5479
  declare interface PolarCoordParams extends BaseCoordParams {
4602
5480
  /**
4603
- * Which aesthetic maps to theta (angle): 'x' or 'y'
5481
+ * Which aesthetic becomes the angle (theta); the other aesthetic becomes the radius,
5482
+ * scaled into `[innerRadius, 1]`. Use `'y'` for pie/donut (stacked value → angle),
5483
+ * `'x'` for radar (one spoke per category).
5484
+ * @default 'x'
4604
5485
  */
4605
5486
  theta: 'x' | 'y';
4606
5487
  /**
4607
- * Starting angle in degrees
5488
+ * Rotation offset of the whole layout, in degrees. Shifts where the first datum begins;
5489
+ * the full sweep is 360°.
5490
+ * @default 0
4608
5491
  */
4609
5492
  startAngle: number;
4610
5493
  /**
4611
- * Inner radius as fraction 0-1 (for donut charts)
5494
+ * Hole radius as a fraction of the outer radius, `0`–`1`. `0` is a full pie;
5495
+ * any value `> 0` produces a donut (e.g. `0.55`).
5496
+ * @default 0
4612
5497
  */
4613
5498
  innerRadius: number;
4614
5499
  }
@@ -4631,6 +5516,13 @@ export declare interface PolarCoordSystem {
4631
5516
  axisMapping: AxisMapping;
4632
5517
  /** Donut hole radius as a fraction of the outer radius (0-1). 0 for a full pie. */
4633
5518
  innerRadius: number;
5519
+ /**
5520
+ * Angular offset, in radians, applied to every angular position — the spec's `startAngle` (degrees)
5521
+ * converted once at setup. The polar guide renderer recovers a tick's angle as `position * 2π +
5522
+ * startAngle`, matching the angular transform the coord applies to the data, so spokes/labels align
5523
+ * with the painted marks.
5524
+ */
5525
+ startAngle: number;
4634
5526
  }
4635
5527
 
4636
5528
  export declare const POSITION_VARIABLES: {
@@ -4723,7 +5615,7 @@ declare interface PositionalScaleMethods {
4723
5615
  * derives from. A geom's channels are the manifest the position mapper and coord projection iterate
4724
5616
  * instead of hardcoding the column set.
4725
5617
  */
4726
- declare type PositionChannel = RolePositionChannel | ScalarPositionChannel;
5618
+ export declare type PositionChannel = RolePositionChannel | ScalarPositionChannel;
4727
5619
 
4728
5620
  declare interface PositionChannelBase {
4729
5621
  axis: ChannelAxis;
@@ -4771,6 +5663,10 @@ export declare type PositionType = 'stack' | 'dodge' | 'identity' | 'fill';
4771
5663
  */
4772
5664
  declare type PositionValueKind = 'value' | 'bandOffset';
4773
5665
 
5666
+ /**
5667
+ * Selects which observations a highlight emphasises: either a single-column
5668
+ * {@link VariablePredicate} or a {@link LogicalPredicate} combining several.
5669
+ */
4774
5670
  export declare type Predicate = VariablePredicate | LogicalPredicate;
4775
5671
 
4776
5672
  export declare const prefixInternalVariable: (name: string) => string;
@@ -4793,11 +5689,51 @@ declare interface QuantitativeScaleMethods {
4793
5689
  identity: (options?: IdentityScaleOptions) => IdentityScaleInput;
4794
5690
  }
4795
5691
 
5692
+ /**
5693
+ * The radial span of an arc/wedge in a polar coord, in `[0,1]` (0 = centre, 1 = outer ring).
5694
+ * `innerRadius` is `null` when the observation declares no y interval; `outerRadius` falls back to the
5695
+ * `point` radius when no upper endpoint exists. Returned by {@link getRadiusExtent}.
5696
+ */
4796
5697
  export declare interface RadiusExtent {
4797
5698
  innerRadius: NumericDataValue;
4798
5699
  outerRadius: NumericDataValue;
4799
5700
  }
4800
5701
 
5702
+ /**
5703
+ * Reads an aesthetic value by an open channel name — built-in (`x`, `color`, …) or custom. A custom
5704
+ * geom maps extra channels (a box plot's `q1`/`median`/`q3`, an error bar's bounds) under names outside
5705
+ * the closed {@link AestheticKey} set; those keys ride in the mapping at runtime and are read here
5706
+ * through the one sanctioned widening, so a channel's value is sourced from `aes` rather than `params`.
5707
+ */
5708
+ export declare function readAesthetic(aesMapping: AesMapping, name: string): AestheticValue | undefined;
5709
+
5710
+ /**
5711
+ * Reads a value by **column name** from an observation, as a number. Use this for the columns a custom
5712
+ * geom named itself (via `variableFor(axis, name)` for scalar channels, or `addVariable` in `compile()`)
5713
+ * — the position readers (`getX`, `getYMin`, …) and visual readers (`getColor`, …) cover the built-in
5714
+ * channels by their fixed internal keys, but there is no typed accessor for an author-named column, and
5715
+ * this fills that gap. The returned number is **whatever was written to that column** (a scalar channel
5716
+ * is already scaled to `[0,1]`; a plain `addVariable` value is in its original units — it carries no
5717
+ * scaling on its own).
5718
+ *
5719
+ * Shares the readers' null-discipline: a missing or wrong-typed value is `null`, never silently coerced
5720
+ * to `0`. Pass `fallback` to opt into a default for genuinely-missing values; the overload then narrows
5721
+ * the return to `number`, so a geom that wants `0`-on-missing says so explicitly.
5722
+ */
5723
+ export declare function readNumber(observation: Observation, key: string): number | null;
5724
+
5725
+ export declare function readNumber(observation: Observation, key: string, fallback: number): number;
5726
+
5727
+ /**
5728
+ * Reads a value by **column name** from an observation, as a string — the string counterpart to
5729
+ * {@link readNumber}, for author-named categorical/label columns a custom geom wrote in `compile()`.
5730
+ * A missing or wrong-typed value is `null` unless a `fallback` is given (the overload then narrows the
5731
+ * return to `string`).
5732
+ */
5733
+ export declare function readString(observation: Observation, key: string): string | null;
5734
+
5735
+ export declare function readString(observation: Observation, key: string, fallback: string): string;
5736
+
4801
5737
  /** A rectangle in pixel coordinates, origin at top-left. */
4802
5738
  export declare interface Rect {
4803
5739
  x: number;
@@ -4857,6 +5793,11 @@ declare function reshape(options?: ReshapeOptions): ReshapeTransformInput;
4857
5793
  /***************************************************************
4858
5794
  * Reshape Transform
4859
5795
  ***************************************************************/
5796
+ /**
5797
+ * Options for `transform.reshape` — pivots a wide table to long ("tidy") form by collapsing
5798
+ * several numeric columns into two: a key column (the original column name) and a value column.
5799
+ * The idiom for turning a multi-metric table into a single series mappable by `color`.
5800
+ */
4860
5801
  declare interface ReshapeOptions {
4861
5802
  /**
4862
5803
  * Numeric variables to collapse into rows.
@@ -4880,6 +5821,7 @@ declare interface ReshapeOptions {
4880
5821
  valueName?: VariableName;
4881
5822
  }
4882
5823
 
5824
+ /** Pivot-to-long transform produced by `transform.reshape`. */
4883
5825
  declare interface ReshapeTransformInput {
4884
5826
  type: 'transform';
4885
5827
  transformType: 'reshape';
@@ -4918,6 +5860,12 @@ export declare interface ResolvedObservationAnchor extends AnchorPosition {
4918
5860
  color: string | undefined;
4919
5861
  }
4920
5862
 
5863
+ /**
5864
+ * A fully-resolved stat spec: a built-in {@link StatSpec} or a custom stat's `{ type, ...options }`.
5865
+ * What the {@link StatCompiler} dispatches on and a stat's `compute` receives.
5866
+ */
5867
+ export declare type ResolvedStatSpec = StatSpec | CustomStatInput;
5868
+
4921
5869
  /**
4922
5870
  * A custom annotation's coordinate resolved to normalized panel space, in **top-left [0,1]** — the
4923
5871
  * space the draw function paints in. `observation` is attached only for a snap-to-observation
@@ -4963,15 +5911,35 @@ export declare function resolveYScaleAesthetic(yScaleType: YScaleType): ScaledAe
4963
5911
  */
4964
5912
  export declare const RESTING_HOVER_STATE: HoverState;
4965
5913
 
4966
- /** TipTap-compatible rich text node (no tiptap dependency). */
5914
+ /**
5915
+ * A node in a ProseMirror/TipTap-style rich-text document tree (no tiptap
5916
+ * dependency). NOT a plain string — it is a recursive node where `content`
5917
+ * holds child nodes and a leaf text node carries `text`. Used both for chart
5918
+ * titles/captions and for text annotation bodies.
5919
+ *
5920
+ * The root is a `{ type: 'doc' }` node; block children are `'paragraph'` or
5921
+ * `'heading'` (with `attrs.level`); inline runs are `'text'` nodes whose
5922
+ * `marks` apply styling (e.g. `{ type: 'bold' }`, `{ type: 'italic' }`,
5923
+ * `{ type: 'link', attrs: { href } }`). Plain prose is one paragraph of one
5924
+ * text node:
5925
+ *
5926
+ * ```ts
5927
+ * { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Quarterly sales' }] }] }
5928
+ * ```
5929
+ */
4967
5930
  export declare interface RichTextContent {
5931
+ /** Node kind: `'doc'` (root), `'paragraph'`, `'heading'`, `'text'`, etc. */
4968
5932
  type?: string;
5933
+ /** Child nodes. Present on container nodes; absent on `'text'` leaves. */
4969
5934
  content?: RichTextContent[];
5935
+ /** The literal string carried by a `'text'` leaf node. */
4970
5936
  text?: string;
5937
+ /** Inline formatting applied to a `'text'` node (bold, italic, link, …). */
4971
5938
  marks?: Array<{
4972
5939
  type: string;
4973
5940
  attrs?: Record<string, unknown>;
4974
5941
  }>;
5942
+ /** Node attributes, e.g. `{ level: 2 }` on a heading or `{ href }` on a link mark target. */
4975
5943
  attrs?: Record<string, unknown>;
4976
5944
  }
4977
5945
 
@@ -4986,6 +5954,18 @@ declare interface RolePositionChannel extends PositionChannelBase {
4986
5954
  name?: string;
4987
5955
  }
4988
5956
 
5957
+ /**
5958
+ * Rule marks — a single horizontal or vertical reference line, the built-in for goal/threshold/average
5959
+ * lines (no custom geom needed). Pin a constant with `aes: { y: { value } }` (horizontal) or
5960
+ * `aes: { x: { value } }` (vertical, numeric x), or compute a data-driven line with `stat.mean()`. Style and
5961
+ * label it via {@link RuleGeomParams}; set `interactive: false` so it doesn't take hover.
5962
+ *
5963
+ * @example
5964
+ * // Constant goal line at y = 2500
5965
+ * geom.rule({ aes: { y: { value: 2500 } }, params: { label: 'Target', lineType: 'dashed', labelPosition: 'start' } });
5966
+ * // Data-driven average line
5967
+ * geom.rule({ aes: { y: 'revenue' }, stat: stat.mean(), params: { label: 'Average' }, interactive: false });
5968
+ */
4989
5969
  declare function rule(options?: GeomOptions<'rule'>): LayerInputOf<'rule'>;
4990
5970
 
4991
5971
  /**
@@ -5008,20 +5988,34 @@ declare class RuleGeom extends Geom {
5008
5988
  }
5009
5989
 
5010
5990
  /**
5011
- * Rule-specific parameters.
5991
+ * Render parameters for `geom.rule`. Passed under `params`. The line's value comes from `aes`
5992
+ * (`{ y: { value } }` or `stat.mean()`), not from here — these are styling and labelling only.
5012
5993
  */
5013
5994
  export declare interface RuleGeomParams {
5014
- /** Stroke color; falls back to a theme token. */
5995
+ /** Stroke color (any CSS color). Falls back to a theme token when omitted. */
5015
5996
  color?: string;
5997
+ /**
5998
+ * Stroke width in pixels.
5999
+ * @default 1
6000
+ */
5016
6001
  strokeWidth: number;
6002
+ /**
6003
+ * Dash style of the line.
6004
+ * @default 'dashed'
6005
+ */
5017
6006
  lineType: LineStyleType;
5018
- /** Optional inline text label rendered alongside the line. */
6007
+ /** Optional inline text label rendered alongside the line (e.g. `'Target'`, `'Average'`). */
5019
6008
  label?: string;
6009
+ /**
6010
+ * Which end of the line the `label` is anchored to.
6011
+ * @default 'start'
6012
+ */
5020
6013
  labelPosition: RuleLabelPosition;
5021
6014
  }
5022
6015
 
5023
6016
  /**
5024
- * Where the optional inline label is anchored along a reference line.
6017
+ * Where the optional inline label sits along a reference line: `'start'` (left/top end) or `'end'`
6018
+ * (right/bottom end).
5025
6019
  */
5026
6020
  export declare type RuleLabelPosition = 'start' | 'end';
5027
6021
 
@@ -5053,6 +6047,31 @@ declare abstract class Scale {
5053
6047
  abstract compile(spec: ScaleSpec, values: DataValue[]): CompiledScale;
5054
6048
  }
5055
6049
 
6050
+ /**
6051
+ * Scale builder — declares how each mapped variable is turned into a visual value
6052
+ * (axis position, color, size, …). Pipe the result onto a spec.
6053
+ *
6054
+ * Position scales must be declared EXPLICITLY: the builder never auto-infers `x`/`y`,
6055
+ * so omitting `scale.x()` / `scale.y()` yields NaN positions. `scale.x`/`.y`/`.ySecondary`
6056
+ * are callable for an inferred scale (type auto-detected from the data) or expose explicit
6057
+ * sub-methods: `.continuous` / `.discrete` / `.datetime` / `.log` / `.sqrt`. Use
6058
+ * `scale.x.discrete()` for categorical or temporal-string axes.
6059
+ *
6060
+ * Non-position aesthetics auto-infer from the mapping, so their scale entry is optional —
6061
+ * add one only to override the default (e.g. `scale.color.palette()`, `scale.size.continuous({ range })`).
6062
+ *
6063
+ * @example
6064
+ * import { pipe, createSpec, geom, scale } from '@graphysdk/viz-engine';
6065
+ *
6066
+ * pipe(
6067
+ * createSpec({ x: 'gdp', y: 'lifeExp', size: 'population', color: 'continent' }),
6068
+ * geom.point(),
6069
+ * scale.x.log({ domainMin: 1 }),
6070
+ * scale.y.continuous({ zero: false, nice: true }),
6071
+ * scale.size.continuous({ range: [4, 40] }),
6072
+ * scale.color.palette()
6073
+ * );
6074
+ */
5056
6075
  export declare const scale: ScaleAPI;
5057
6076
 
5058
6077
  declare interface ScaleAPI {
@@ -5159,10 +6178,13 @@ export declare type ScaledAestheticKey = ScaledPositionAestheticKey | ScaledVisu
5159
6178
 
5160
6179
  declare type ScaledPositionAestheticKey = 'x' | 'y' | 'ySecondary';
5161
6180
 
5162
- declare type ScaledVisualAestheticKey = 'color' | 'size' | 'alpha' | 'strokeWidth' | 'lineType';
6181
+ export declare type ScaledVisualAestheticKey = 'color' | 'size' | 'alpha' | 'strokeWidth' | 'lineType';
5163
6182
 
5164
6183
  /**
5165
- * Union type for all possible scale specifications (including inferred, pre-resolution).
6184
+ * Any value the `scale` builder produces, before resolution. Each pipe item carries the
6185
+ * target aesthetic plus its scale type and options; an `inferred` entry has its concrete
6186
+ * type chosen from the data during compilation. This is the type accepted by the spec
6187
+ * pipeline — author scales with the `scale` builder rather than constructing it by hand.
5166
6188
  */
5167
6189
  declare type ScaleInput = ContinuousScaleInput | DiscreteScaleInput | PaletteScaleInput | DatetimeScaleInput | IdentityScaleInput | InferredScaleInput;
5168
6190
 
@@ -5171,8 +6193,9 @@ declare class ScaleRegistry extends Registry<ScaleType, Scale> {
5171
6193
  }
5172
6194
 
5173
6195
  /**
5174
- * Union of scale specs that can appear after resolution (all fields required).
5175
- * InferredScaleInput is resolved to a concrete type during spec resolution.
6196
+ * A fully resolved scale (every option defaulted) as it appears on the compiled spec.
6197
+ * The `inferred` variant has already been collapsed to one of these concrete types
6198
+ * during resolution, so this union has no `inferred` member.
5176
6199
  */
5177
6200
  declare type ScaleSpec = ContinuousScaleSpec | DiscreteScaleSpec | DatetimeScaleSpec | IdentityScaleSpec | PaletteScaleSpec;
5178
6201
 
@@ -5386,27 +6409,37 @@ declare type SetScaleDomainParams = {
5386
6409
  };
5387
6410
 
5388
6411
  /**
5389
- * Freeform rectangle annotation. Position and size are expressed as
5390
- * fractions of the plot rect (0..1) so the annotation re-flows when the
5391
- * panel resizes.
6412
+ * A shaded box layered onto the panel. Position and size are panel fractions (`[0,1]`, top-left
6413
+ * origin) NOT data values so the shape re-flows on resize but does not snap to a data point. Use a
6414
+ * difference arrow or a custom annotation when you need data anchoring.
5392
6415
  */
5393
6416
  export declare interface ShapeInput {
5394
6417
  id?: string;
6418
+ /** @default 'rectangle' */
5395
6419
  kind?: ShapeKind;
6420
+ /** @default 'foreground' */
5396
6421
  zOrder?: ShapeZOrder;
6422
+ /** Left edge as a `[0,1]` fraction of panel width (0 = left). */
5397
6423
  x: number;
6424
+ /** Top edge as a `[0,1]` fraction of panel height (0 = top). */
5398
6425
  y: number;
6426
+ /** Width as a `[0,1]` fraction of panel width. */
5399
6427
  width: number;
6428
+ /** Height as a `[0,1]` fraction of panel height. */
5400
6429
  height: number;
6430
+ /** @default 'transparent' */
5401
6431
  fillColor?: string;
6432
+ /** Fill alpha, `[0,1]`. @default 1 */
5402
6433
  fillOpacity?: number;
6434
+ /** Stroke width in pixels. @default 1 */
5403
6435
  strokeWidth?: number;
5404
- /** null falls back to the theme `defaultAnnotationShapeStroke`. */
6436
+ /** `null` falls back to the theme `defaultAnnotationShapeStroke`. @default null */
5405
6437
  strokeColor?: string | null;
5406
6438
  }
5407
6439
 
5408
6440
  export declare type ShapeKind = 'rectangle';
5409
6441
 
6442
+ /** Resolved form of {@link ShapeInput} — defaults applied. */
5410
6443
  export declare interface ShapeSpec {
5411
6444
  id: string;
5412
6445
  kind: ShapeKind;
@@ -5427,7 +6460,9 @@ export declare interface ShapeSpec {
5427
6460
  export declare type ShapeZOrder = 'background' | 'foreground';
5428
6461
 
5429
6462
  /**
5430
- * Builder for the smooth stat.
6463
+ * Builder for the smooth stat — fits a regression trendline through the observations.
6464
+ * Pair with `geom.line` for a drawn trendline. `order` applies only to `'polynomial'`,
6465
+ * `bandwidth` only to `'loess'`; both are ignored by the other methods.
5431
6466
  *
5432
6467
  * @example
5433
6468
  * geom.line({ stat: stat.smooth({ method: 'linear' }) })
@@ -5441,7 +6476,14 @@ declare function smooth(options: {
5441
6476
  }): SmoothStatInput;
5442
6477
 
5443
6478
  /**
5444
- * Regression methods supported by the `smooth` stat.
6479
+ * Regression/trendline method fitted by the `smooth` stat through the observations:
6480
+ * - `'linear'` — straight line of best fit (`y = a + b·x`). The default.
6481
+ * - `'loess'` — locally weighted smoothing; follows local structure. Tune with `bandwidth`.
6482
+ * - `'exponential'` — `y = a·e^(b·x)`; constant-rate growth/decay.
6483
+ * - `'logarithmic'` — `y = a + b·ln(x)`; fast early then flattening.
6484
+ * - `'quadratic'` — parabola (`y = a + b·x + c·x²`); a single bend.
6485
+ * - `'power'` — `y = a·x^b`; scale-free relationships.
6486
+ * - `'polynomial'` — degree-`order` polynomial; multiple bends. Tune with `order`.
5445
6487
  */
5446
6488
  export declare type SmoothMethod = 'linear' | 'loess' | 'exponential' | 'logarithmic' | 'quadratic' | 'power' | 'polynomial';
5447
6489
 
@@ -5451,7 +6493,9 @@ export declare type SmoothMethod = 'linear' | 'loess' | 'exponential' | 'logarit
5451
6493
  declare interface SmoothStatInput {
5452
6494
  type: 'smooth';
5453
6495
  method: SmoothMethod;
6496
+ /** Polynomial degree. Only used when `method: 'polynomial'`. @default 3 */
5454
6497
  order?: number;
6498
+ /** LOESS smoothing window as a fraction (0–1) of the data. Only used when `method: 'loess'`. @default 0.3 */
5455
6499
  bandwidth?: number;
5456
6500
  }
5457
6501
 
@@ -5469,9 +6513,18 @@ declare interface SmoothStatSpec {
5469
6513
 
5470
6514
  declare function sort(options: SortOptions): SortTransformInput;
5471
6515
 
6516
+ /**
6517
+ * Sorts the data by the x variable if it is numeric or temporal.
6518
+ */
6519
+ export declare const sortByXIfContinuous: (data: Dataset, mapping: AesMapping) => Dataset;
6520
+
5472
6521
  /***************************************************************
5473
6522
  * Sort Transform
5474
6523
  ***************************************************************/
6524
+ /**
6525
+ * Options for `transform.sort` — reorders observations by one variable. Affects draw order
6526
+ * and the order categories are first seen (and thus discrete-scale domain order).
6527
+ */
5475
6528
  declare interface SortOptions {
5476
6529
  /** The variable to sort by. */
5477
6530
  variableName: VariableName;
@@ -5479,15 +6532,18 @@ declare interface SortOptions {
5479
6532
  direction?: 'asc' | 'desc';
5480
6533
  }
5481
6534
 
6535
+ /** Observation-ordering transform produced by `transform.sort`. */
5482
6536
  declare interface SortTransformInput {
5483
6537
  type: 'transform';
5484
6538
  transformType: 'sort';
5485
6539
  options: SortOptions;
5486
6540
  }
5487
6541
 
5488
- /** Data-source attribution shown under the caption. */
6542
+ /** Data-source attribution shown under the caption: a `label` and optional `url`. */
5489
6543
  export declare interface SourceContent {
6544
+ /** Displayed attribution text, e.g. `'Internal pipeline'`. */
5490
6545
  label?: string;
6546
+ /** Optional link the label points to. */
5491
6547
  url?: string;
5492
6548
  }
5493
6549
 
@@ -5496,19 +6552,26 @@ export declare interface SourceContent {
5496
6552
  * runtime's index builders, so the descriptor lets the engine dispatch on declared data instead
5497
6553
  * of branching on the geom name.
5498
6554
  *
5499
- * `render-hit-test` is the Tier-C escape hatch: the geom's geometry comes from a layout algorithm,
6555
+ * `render-hit-test` is the geom-layout escape hatch: the geom's geometry comes from a layout algorithm,
5500
6556
  * not from scales, so the compiler cannot build a spatial index from position columns. The geom
5501
6557
  * instead provides a render-side hit-test function (injected per-instance through the renderer),
5502
6558
  * and the engine resolves the observation it returns against the declared identity key. Only the
5503
6559
  * kind rides in the compiled spec — the closure never crosses the serialisable boundary.
6560
+ *
6561
+ * `polar-points` is the polar refinement of `points` (a radar's vertices read in polar). The cursor
6562
+ * and the vertices live in incompatible spaces for the cartesian nearest query — vertices are
6563
+ * `(angle, radius)`, the cursor is plot-local `[0,1]` — so the polar query converts each at query
6564
+ * time (no stored cartesian, no Delaunay), staying correct across resizes. Geoms never declare it; a
6565
+ * polar coord refines a `points`-natural mark to it during the coord transform.
5504
6566
  */
5505
- declare type SpatialIndexKind = 'buckets' | 'rects' | 'points' | 'arcs' | 'noop' | 'render-hit-test';
6567
+ export declare type SpatialIndexKind = 'buckets' | 'rects' | 'points' | 'arcs' | 'noop' | 'render-hit-test' | 'polar-points';
5506
6568
 
5507
6569
  /**
5508
6570
  * A layer's geometry-agnostic hit-test declaration. Pure serialisable data riding in the compiled
5509
6571
  * spec: it names the spatial structure the marks present so the runtime can build the matching
5510
6572
  * index without knowing which geom produced it. A geom declares its cartesian-natural kind on its
5511
- * definition; a polar coord refines it to `arcs` during the coord transform.
6573
+ * definition; a polar coord refines it to `arcs` (filled wedges) or `polar-points` (radar vertices)
6574
+ * during the coord transform.
5512
6575
  */
5513
6576
  declare interface SpatialMapDescriptor {
5514
6577
  kind: SpatialIndexKind;
@@ -5554,17 +6617,30 @@ export declare interface Spec {
5554
6617
  }
5555
6618
 
5556
6619
  /**
5557
- * The canonical spec type — plain JSON, serializable. Data is provided separately
5558
- * (as a `Data` value to {@link compile}, or as a prop to `<GraphProvider>`).
6620
+ * The canonical spec type — plain JSON, serializable. Built by `createSpec`/`pipe`; data is provided
6621
+ * separately (as a `Data` value to {@link compile}, or as a prop to `<GraphProvider>`). Hand-construct it
6622
+ * only when you cannot use the builders; otherwise prefer `pipe(createSpec({...}), geom.x(), scale.x(), ...)`.
5559
6623
  */
5560
6624
  export declare interface SpecInput {
6625
+ /** Global aesthetic mapping (data columns → channels); layer `aes` overrides merge over this. */
5561
6626
  mapping: AesMapping;
6627
+ /** Geometry layers to render, in draw order. One entry per `geom.*` call. */
5562
6628
  layers: LayerInput[];
6629
+ /**
6630
+ * Scale declarations, one per aesthetic. Position scales (`scale.x`/`scale.y`/`scale.ySecondary`) are NOT
6631
+ * auto-inferred — declare them explicitly or position channels resolve to NaN. Visual scales
6632
+ * (`color`/`size`/...) are inferred from the data when omitted.
6633
+ */
5563
6634
  scales: ScaleInput[];
6635
+ /** Spec-level data transforms applied before any layer is compiled (reshape, filter, ...). */
5564
6636
  transforms: TransformInput[];
6637
+ /** Predicate-driven emphasis rules that dim or accentuate matching observations. */
5565
6638
  highlights: HighlightInput[];
6639
+ /** Annotation overlays — difference arrows, shapes, text, freeform arrows. Optional. */
5566
6640
  annotations?: AnnotationsInput;
6641
+ /** Coordinate system: cartesian (default), `coord.flip()`, or `coord.polar(...)`. Optional. */
5567
6642
  coords?: CoordInput;
6643
+ /** Chart configuration: titles/captions, legend, axes, number format, headline, appearance. */
5568
6644
  config: ConfigInput;
5569
6645
  }
5570
6646
 
@@ -5581,6 +6657,18 @@ export declare class SpecResolver {
5581
6657
  }): Spec;
5582
6658
  }
5583
6659
 
6660
+ /**
6661
+ * Render a {@link SpecInput} as fluent builder source — `pipe(createSpec({…}), geom.x(…), scale.x(…),
6662
+ * config({…}))` — emitting only values that differ from their defaults. Returns `null` when the spec
6663
+ * contains something the builder form cannot faithfully represent (a custom geom, a transform, a polar
6664
+ * coord, …), so the caller can fall back to a literal object and never lose data.
6665
+ *
6666
+ * The defaults are static (data-independent), so this needs no dataset. Position scales are emitted in
6667
+ * the inferred `scale.x(opts)` form on purpose: the resolver only applies context defaults (e.g. a bar
6668
+ * chart's `zero: true`) to inferred scales, so converting them to an explicit type would change the chart.
6669
+ */
6670
+ export declare function specToBuilderSource(input: SpecInput): string | null;
6671
+
5584
6672
  export declare interface StackTotalEntry {
5585
6673
  /** Serialised x value for stable join keys across observations. */
5586
6674
  xKey: string;
@@ -5623,9 +6711,11 @@ declare abstract class Stage<Input, Output> {
5623
6711
 
5624
6712
  /**
5625
6713
  * Base class for statistical transformations applied to layer data (e.g. binning, counting, smoothing).
6714
+ * Built-ins use a literal {@link StatName}; custom stats authored with `defineStat` carry any registered
6715
+ * `type` string ({@link StatIdentity}).
5626
6716
  */
5627
- declare abstract class Stat {
5628
- abstract readonly type: StatName;
6717
+ export declare abstract class Stat {
6718
+ abstract readonly type: StatIdentity;
5629
6719
  /**
5630
6720
  * Aesthetics this stat will compute (e.g. count computes 'y'). Used by validation to skip existence checks.
5631
6721
  */
@@ -5634,6 +6724,23 @@ declare abstract class Stat {
5634
6724
  protected abstract computeStat(input: StatCompilerInput): CompiledStat;
5635
6725
  }
5636
6726
 
6727
+ /**
6728
+ * Statistical-transform builder — sets a geom's `stat`, replacing each layer's raw observations
6729
+ * with a derived summary before positions are computed. Defaults to `identity` (raw data).
6730
+ *
6731
+ * - `identity()` — pass observations through unchanged (the default).
6732
+ * - `count()` — number of observations per x value, written to `y`; do NOT also map `y`.
6733
+ * - `mean()` — reduce the mapped `y` to its average (a single value); the idiom for an
6734
+ * average line (`geom.rule({ stat: stat.mean() })`).
6735
+ * - `smooth({ method })` — fit a regression trendline; the idiom for a trendline
6736
+ * (`geom.line({ stat: stat.smooth({ method: 'linear' }) })`).
6737
+ *
6738
+ * @example
6739
+ * import { geom, stat } from '@graphysdk/viz-engine';
6740
+ *
6741
+ * geom.rule({ aes: { y: 'revenue' }, stat: stat.mean(), params: { label: 'Average' } });
6742
+ * geom.line({ stat: stat.smooth({ method: 'linear' }), interactive: false });
6743
+ */
5637
6744
  export declare const stat: {
5638
6745
  identity: typeof identity;
5639
6746
  count: typeof count;
@@ -5642,25 +6749,28 @@ export declare const stat: {
5642
6749
  };
5643
6750
 
5644
6751
  /**
5645
- * Resolves a stat by name and delegates computation.
6752
+ * Resolves stats by name and delegates computation. A layer's `stat` is an ordered pipeline (Decision 8):
6753
+ * each step sees the dataset and effective mapping produced by the previous step, so a later stat can read
6754
+ * an earlier stat's emitted column. Returns the net mapping overrides (last-writer-wins) for the caller to
6755
+ * merge over the layer's effective mapping.
5646
6756
  */
5647
6757
  declare class StatCompiler {
5648
6758
  private readonly registry;
5649
6759
  constructor(registry: StatRegistry);
5650
- compute(spec: StatSpec, input: {
6760
+ compute(specs: ResolvedStatSpec[], input: {
5651
6761
  data: Dataset;
5652
6762
  mapping: AesMapping;
5653
6763
  xScaleIsDiscrete: boolean;
5654
6764
  }): CompiledStat;
5655
6765
  }
5656
6766
 
5657
- declare interface StatCompilerInput {
6767
+ export declare interface StatCompilerInput {
5658
6768
  /** The input dataset. */
5659
6769
  data: Dataset;
5660
6770
  /** The effective mapping for the layer. */
5661
6771
  mapping: AesMapping;
5662
- /** The resolved stat spec. Narrow by `spec.type` to access stat-specific params. */
5663
- spec: StatSpec;
6772
+ /** The resolved stat spec (built-in or custom). Narrow by `spec.type` to access stat-specific params. */
6773
+ spec: ResolvedStatSpec;
5664
6774
  /**
5665
6775
  * Whether the x aesthetic resolves to a discrete (band) scale. The `smooth` stat emits one fitted
5666
6776
  * point per observed x when set.
@@ -5669,9 +6779,70 @@ declare interface StatCompilerInput {
5669
6779
  }
5670
6780
 
5671
6781
  /**
5672
- * User-facing stat input either a {@link StatName} string shorthand or an object spec.
6782
+ * A registered custom stat: a {@link Stat} the compiler consumes, carrying the resolved-spec type so
6783
+ * `createGraphyBuilder({ stats })` can type its `stat.<type>(options)` builder method.
6784
+ */
6785
+ export declare interface StatDef<TSpec extends StatSpecBase = StatSpecBase> extends Stat {
6786
+ readonly type: TSpec['type'] & string;
6787
+ /**
6788
+ * Phantom — carries the resolved-spec type to the type level so the registration-typed builder can
6789
+ * recover the stat's options. Never set at runtime.
6790
+ */
6791
+ readonly __spec?: TSpec;
6792
+ }
6793
+
6794
+ /**
6795
+ * The compute input an authored stat receives: the standard {@link StatCompilerInput}, but with `spec`
6796
+ * narrowed to the stat's own resolved spec `TSpec`, plus a `column` helper that namespaces a declared
6797
+ * computed column to a collision-safe internal name (Decision 9).
6798
+ */
6799
+ export declare interface StatDefinitionInput<TSpec extends StatSpecBase = StatSpecBase> extends Omit<StatCompilerInput, 'spec'> {
6800
+ /** The resolved spec for this stat — its `type` plus the options the builder passed. */
6801
+ spec: TSpec;
6802
+ /**
6803
+ * Namespaces a column declared in `computedColumns` to `\0graphy\0_<type>_<local>` — globally unique,
6804
+ * so a stat's output can never collide with a user column or another stat. Throws on an undeclared name.
6805
+ * Write the returned name into `addVariable`/`addConstantVariable` and into any `mapping` rebinding.
6806
+ */
6807
+ column: (localName: string) => string;
6808
+ }
6809
+
6810
+ /**
6811
+ * The manifest passed to {@link defineStat}. Declares the stat's `type`, the columns it emits
6812
+ * (`computedColumns`, namespaced per-type), the aesthetics it rebinds (`computedVariables`, which waive
6813
+ * the validator's pre-stat existence/required checks), and the `compute` itself.
6814
+ */
6815
+ export declare interface StatDefinitionManifest<TSpec extends StatSpecBase = StatSpecBase> {
6816
+ /** The registered stat name; keys both the registry and the `stat.<type>(...)` builder method. */
6817
+ type: TSpec['type'] & string;
6818
+ /** Local names of the columns `compute` emits via `column(...)`. Namespaced per-type for collision-safety. */
6819
+ computedColumns?: readonly string[];
6820
+ /** Aesthetics `compute` rebinds (e.g. `'y'`). Waives the validator's pre-stat existence/required checks. */
6821
+ computedVariables?: readonly AestheticKey[];
6822
+ /** Derives the layer's summary from its dataset. Not called on an empty dataset (the base short-circuits). */
6823
+ compute: (input: StatDefinitionInput<TSpec>) => CompiledStat;
6824
+ }
6825
+
6826
+ /**
6827
+ * The open identity of a stat: a built-in {@link StatName} or any custom stat's `type` string
6828
+ * registered via `createCompiler({ stats })`. The `& {}` keeps the built-in names as autocomplete
6829
+ * candidates without collapsing the union to bare `string` (mirrors `GeomIdentity`).
6830
+ */
6831
+ export declare type StatIdentity = StatName | (string & {});
6832
+
6833
+ /**
6834
+ * Any value the `stat` builder produces — passed as the `stat` option of a geom.
6835
+ * The string-shorthand variants (`stat.identity()`, `stat.count()`, `stat.mean()`) carry only
6836
+ * a `type`; `smooth` additionally carries the regression parameters.
6837
+ */
6838
+ export declare type StatInput = IdentityStatSpec | CountStatSpec | SmoothStatInput | MeanStatSpec;
6839
+
6840
+ /**
6841
+ * A single stat a layer's `stat` option accepts — a built-in name, a built-in {@link StatInput}, or a
6842
+ * custom stat input. A layer's `stat` is one of these or an ordered list of them (Decision 8): each step
6843
+ * sees the previous step's emitted columns, e.g. `[stat.percentOfTotal(...), stat.window({ op: 'rank' })]`.
5673
6844
  */
5674
- declare type StatInput = IdentityStatSpec | CountStatSpec | SmoothStatInput | MeanStatSpec;
6845
+ declare type StatLayerInput = StatName | StatInput | CustomStatInput;
5675
6846
 
5676
6847
  /**
5677
6848
  * Statistical transformation applied to data before rendering.
@@ -5681,22 +6852,43 @@ declare type StatInput = IdentityStatSpec | CountStatSpec | SmoothStatInput | Me
5681
6852
  * - `'smooth'` — Fit a regression curve through `(x, y)` and emit the fitted points
5682
6853
  * - `'mean'` — Reduce the dataset to a single observation holding the mean of `y`
5683
6854
  */
5684
- declare type StatName = 'identity' | 'count' | 'smooth' | 'mean';
6855
+ export declare type StatName = 'identity' | 'count' | 'smooth' | 'mean';
5685
6856
 
5686
6857
  /**
5687
- * Built-in stat implementations keyed by {@link StatName}.
6858
+ * The options a registered custom stat's builder method accepts its resolved spec minus the `type`
6859
+ * discriminant (the builder fills `type`). Recovered structurally from the definition's phantom spec.
5688
6860
  */
5689
- declare class StatRegistry extends Registry<StatName, Stat> {
5690
- constructor();
6861
+ declare type StatOptionsOf<Definition> = Definition extends StatDef<infer TSpec> ? Omit<TSpec, 'type'> : never;
6862
+
6863
+ /**
6864
+ * Stat implementations keyed by `type`. Built-ins are registered first; any custom stats injected via
6865
+ * `createCompiler({ stats })` register afterwards (a custom `type` matching a built-in overrides it,
6866
+ * last write wins) — mirroring `GeomRegistry`.
6867
+ */
6868
+ declare class StatRegistry extends Registry<string, Stat> {
6869
+ constructor(opts?: {
6870
+ stats?: readonly Stat[];
6871
+ });
5691
6872
  }
5692
6873
 
5693
6874
  /**
5694
- * Discriminated union of all resolved stat specs (post-resolution).
6875
+ * Discriminated union of all built-in resolved stat specs (post-resolution).
5695
6876
  */
5696
- declare type StatSpec = IdentityStatSpec | CountStatSpec | SmoothStatSpec | MeanStatSpec;
6877
+ export declare type StatSpec = IdentityStatSpec | CountStatSpec | SmoothStatSpec | MeanStatSpec;
6878
+
6879
+ /**
6880
+ * The minimal shape every stat spec shares: its `type` discriminant. A custom stat's resolved spec
6881
+ * extends this with arbitrary plain-data options; `defineStat<TSpec>` narrows `TSpec` from it.
6882
+ */
6883
+ export declare interface StatSpecBase {
6884
+ type: StatIdentity;
6885
+ }
5697
6886
 
5698
6887
  /**
5699
6888
  * Sticker annotation: a built-in emoji-like image pinned to a single observation.
6889
+ *
6890
+ * NO PAINTER in `@graphysdk/react-renderer` — this compiles but never draws there (it renders only in
6891
+ * the editor's legacy engine). Don't reach for it when authoring for the React renderer.
5700
6892
  */
5701
6893
  declare interface StickerAnnotationInput {
5702
6894
  id?: string;
@@ -5704,6 +6896,7 @@ declare interface StickerAnnotationInput {
5704
6896
  sticker: StickerId;
5705
6897
  }
5706
6898
 
6899
+ /** Resolved form of {@link StickerAnnotationInput} — defaults applied, anchor normalised. */
5707
6900
  declare interface StickerAnnotationSpec {
5708
6901
  id: string;
5709
6902
  anchor: ObservationAnchor;
@@ -5754,26 +6947,33 @@ declare interface TemporalValueFormat {
5754
6947
  dateFormat?: string;
5755
6948
  }
5756
6949
 
6950
+ /** How `backgroundColor` is applied: `'fade'` (soft gradient) or `'opaque'` (flat fill). */
5757
6951
  export declare type TextAnnotationBackgroundColorStyle = 'fade' | 'opaque';
5758
6952
 
5759
6953
  /**
5760
- * Freeform rich-text annotation. Position and width are fractions of the plot
5761
- * rect (0..1). Height is intrinsic to the rendered content.
6954
+ * A free-standing text label on the panel. Positioned in panel fractions (`[0,1]`, top-left origin) —
6955
+ * NOT data values so it re-flows on resize but does not snap to a data point. There is no `height`
6956
+ * field: height is intrinsic to the rendered content. `content` is a structured {@link RichTextContent}
6957
+ * node tree (ProseMirror/TipTap-style), NOT a plain string — wrap a string as
6958
+ * `{ type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Label' }] }] }`.
5762
6959
  */
5763
6960
  export declare interface TextAnnotationInput {
5764
6961
  id?: string;
6962
+ /** Rich-text node tree to render (not a plain string). */
5765
6963
  content: RichTextContent;
5766
- /** 0..1 of plot width top-left corner. */
6964
+ /** Left edge as a `[0,1]` fraction of panel width (0 = left, top-left corner). */
5767
6965
  x: number;
5768
- /** 0..1 of plot height top-left corner. */
6966
+ /** Top edge as a `[0,1]` fraction of panel height (0 = top, top-left corner). */
5769
6967
  y: number;
5770
- /** 0..1 of plot width. */
6968
+ /** Box width as a `[0,1]` fraction of panel width; text wraps within it (height is intrinsic). */
5771
6969
  width: number;
5772
- /** null falls back to a transparent background. */
6970
+ /** `null` falls back to a transparent background. @default null */
5773
6971
  backgroundColor?: string | null;
6972
+ /** @default 'opaque' */
5774
6973
  backgroundColorStyle?: TextAnnotationBackgroundColorStyle;
5775
6974
  }
5776
6975
 
6976
+ /** Resolved form of {@link TextAnnotationInput} — defaults applied. */
5777
6977
  export declare interface TextAnnotationSpec {
5778
6978
  id: string;
5779
6979
  content: RichTextContent;
@@ -5784,13 +6984,22 @@ export declare interface TextAnnotationSpec {
5784
6984
  backgroundColorStyle: TextAnnotationBackgroundColorStyle;
5785
6985
  }
5786
6986
 
5787
- /** A text value — plain string or structured rich text. */
6987
+ /**
6988
+ * A text value for a title, subtitle, or caption: either a plain `string`
6989
+ * (rendered as-is) or a structured {@link RichTextContent} document tree for
6990
+ * multi-style / multi-line text.
6991
+ */
5788
6992
  export declare type TextContent = string | RichTextContent;
5789
6993
 
5790
6994
  export declare interface TextMeasurer {
5791
6995
  measureText: (text: string, font: FontSpec) => MeasuredText;
5792
6996
  }
5793
6997
 
6998
+ declare interface ToDatasetOptions {
6999
+ /** Name of the categorical column that discriminates each row's kind. Must not collide with a declared column. */
7000
+ kindColumn: string;
7001
+ }
7002
+
5794
7003
  /** A chart's semantic content for the hovered observations, in reading order. */
5795
7004
  export declare interface TooltipContent {
5796
7005
  /** Localized main-axis value of the primary's observation. `null` for polar. */
@@ -5823,6 +7032,29 @@ export declare interface TooltipRow {
5823
7032
  key: string;
5824
7033
  }
5825
7034
 
7035
+ /**
7036
+ * Data-transform builder — reshapes the dataset BEFORE any geom maps over it. Pipe one or more
7037
+ * onto a spec; they apply in order, ahead of stats and scaling, and affect every layer.
7038
+ *
7039
+ * - `reshape(opts?)` — pivot wide numeric columns to long form (key/value); the move for plotting
7040
+ * several metrics as one color-split series.
7041
+ * - `filter(opts)` — keep observations matching `variableName <operator> value`.
7042
+ * - `sort(opts)` — order observations by a variable (`'asc'` | `'desc'`).
7043
+ * - `aggregate(opts)` — group by variables and reduce each group (sum/mean/count/…).
7044
+ * - `constant(opts)` — add a column with a fixed value on every observation.
7045
+ *
7046
+ * @example
7047
+ * import { pipe, createSpec, geom, scale, transform } from '@graphysdk/viz-engine';
7048
+ *
7049
+ * pipe(
7050
+ * createSpec({ x: 'region', y: 'total', color: 'region' }),
7051
+ * transform.filter({ variableName: 'year', operator: 'eq', value: 2024 }),
7052
+ * transform.aggregate({ groupby: ['region'], operations: [{ op: 'sum', variableName: 'revenue', as: 'total' }] }),
7053
+ * geom.bar(),
7054
+ * scale.x(),
7055
+ * scale.y()
7056
+ * );
7057
+ */
5826
7058
  export declare const transform: {
5827
7059
  reshape: typeof reshape;
5828
7060
  filter: typeof filter;
@@ -5848,27 +7080,71 @@ declare interface TransformCompilerInput {
5848
7080
  transforms: TransformInput[];
5849
7081
  }
5850
7082
 
5851
- /***************************************************************
5852
- * Transform Input
5853
- ***************************************************************/
5854
- declare type TransformInput = ReshapeTransformInput | FilterTransformInput | SortTransformInput | AggregateTransformInput | ConstantTransformInput;
7083
+ /**
7084
+ * A registered custom transform: a {@link TransformStrategy} the compiler consumes, carrying the option
7085
+ * type so `createGraphyBuilder({ transforms })` can type its `transform.<type>(options)` builder method.
7086
+ */
7087
+ export declare interface TransformDef<TType extends string = string, TOptions extends object = object> extends TransformStrategy {
7088
+ readonly transformType: TType;
7089
+ /**
7090
+ * Phantom — carries the options type to the type level so the registration-typed builder can recover
7091
+ * the transform's options. Never set at runtime.
7092
+ */
7093
+ readonly __options?: TOptions;
7094
+ }
5855
7095
 
5856
7096
  /**
5857
- * Built-in transform implementations keyed by transform type.
7097
+ * The manifest passed to {@link defineTransform}. Declares the `transformType` and an `apply` that
7098
+ * reshapes the dataset given the transform's plain-data options.
5858
7099
  */
5859
- declare class TransformRegistry extends Registry<TransformType, TransformStrategy> {
5860
- constructor();
7100
+ export declare interface TransformDefinitionManifest<TType extends string = string, TOptions extends object = object> {
7101
+ /** The registered transform name; keys both the registry and the `transform.<type>(...)` builder method. */
7102
+ transformType: TType;
7103
+ /** Reshapes the dataset given the transform's options. Mapping-blind, whole-table surgery. */
7104
+ apply: (data: Dataset, options: TOptions) => Dataset;
5861
7105
  }
5862
7106
 
5863
7107
  /**
5864
- * Strategy interface for compiling a specific transform type.
7108
+ * The open identity of a transform: a built-in {@link TransformType} or any custom transform's
7109
+ * `transformType` string registered via `createCompiler({ transforms })` (mirrors `GeomIdentity`).
7110
+ */
7111
+ export declare type TransformIdentity = TransformType | (string & {});
7112
+
7113
+ /**
7114
+ * Any value the `transform` builder produces. Transforms run before stats and scaling, in the
7115
+ * order they appear, reshaping the dataset that every layer then maps over.
7116
+ */
7117
+ export declare type TransformInput = BuiltinTransformInput | CustomTransformInput;
7118
+
7119
+ /**
7120
+ * The options a registered custom transform's builder method accepts — recovered structurally from the
7121
+ * definition's phantom options type.
5865
7122
  */
5866
- declare interface TransformStrategy {
5867
- readonly transformType: TransformType;
7123
+ declare type TransformOptionsOf<Definition> = Definition extends TransformDef<string, infer TOptions> ? TOptions : never;
7124
+
7125
+ /**
7126
+ * Transform strategies keyed by `transformType`. Built-ins register first; any custom transforms
7127
+ * injected via `createCompiler({ transforms })` register afterwards (a custom `transformType` matching
7128
+ * a built-in overrides it, last write wins) — mirroring `GeomRegistry` / `StatRegistry`.
7129
+ */
7130
+ declare class TransformRegistry extends Registry<string, TransformStrategy> {
7131
+ constructor(opts?: {
7132
+ transforms?: readonly TransformStrategy[];
7133
+ });
7134
+ }
7135
+
7136
+ /**
7137
+ * Strategy interface for compiling a specific transform type. Built-ins use a literal
7138
+ * {@link TransformType}; custom transforms authored with `defineTransform` carry any registered
7139
+ * `transformType` string ({@link TransformIdentity}).
7140
+ */
7141
+ export declare interface TransformStrategy {
7142
+ readonly transformType: TransformIdentity;
5868
7143
  apply: (data: Dataset, transform: TransformInput) => Dataset;
5869
7144
  }
5870
7145
 
5871
- declare type TransformType = TransformInput['transformType'];
7146
+ /** Discriminant tag of a built-in {@link TransformInput}. */
7147
+ export declare type TransformType = BuiltinTransformInput['transformType'];
5872
7148
 
5873
7149
  declare type TrendlineType = 'linear' | 'loess' | 'exponential' | 'logarithmic' | 'quadratic' | 'power' | 'polynomial';
5874
7150
 
@@ -5914,27 +7190,39 @@ export declare interface ValueFormatterFactoryParams<T = ValueFormat> {
5914
7190
  }
5915
7191
 
5916
7192
  /**
5917
- * Constant mapping - a literal value applied to every observation.
5918
- * Analogous to Vega-Lite's `{datum: X}` / ggplot2's `aes(color = "literal")`.
7193
+ * Pins a channel to a single literal value applied to every observation, instead of reading a column.
7194
+ * Use it for reference-line constants (`geom.rule({ aes: { y: { value: 2500 } } })`) or to force a fixed
7195
+ * style (`aes: { lineType: { value: 'dashed' } }`). Analogous to Vega-Lite's `{datum: X}`.
5919
7196
  */
5920
7197
  declare interface ValueMapping {
7198
+ /** The constant — a number, string, Date, or null — shared by all observations. */
5921
7199
  value: DataValue;
5922
7200
  }
5923
7201
 
5924
7202
  /** A column of a variable in the dataset. When `valueFormat` is omitted, the Dataset assigns a type-based default (`numeric → decimal`, `categorical → text`, `temporal → date`). */
5925
- declare type Variable = {
7203
+ export declare type Variable = {
5926
7204
  type: DataType;
5927
7205
  values: DataValue[];
5928
7206
  valueFormat?: ValueFormat;
5929
7207
  };
5930
7208
 
7209
+ /**
7210
+ * The internal dataset variable a channel reads and writes, derived from its axis and open name. The
7211
+ * built-in names (`point`/`lower`/`upper`) resolve to the canonical position columns (`x`, `xMin`, …),
7212
+ * so value readers and renderer recipes stay untouched; any other name resolves to a namespaced column,
7213
+ * so a custom scaled channel never collides with a built-in or another geom's channel.
7214
+ */
7215
+ export declare function variableFor(axis: ChannelAxis, name: string): string;
7216
+
5931
7217
  /** A map of variable names to their type and values. */
5932
- declare type VariableMap = Record<VariableName, Variable>;
7218
+ export declare type VariableMap = Record<VariableName, Variable>;
5933
7219
 
5934
7220
  /**
5935
- * Variable mapping - references a column in the data
7221
+ * Binds a channel to a data column by name. `{ variable: 'revenue' }` reads the `revenue` column
7222
+ * per observation. Equivalent to the bare-string shorthand `'revenue'` in an {@link AesMapping}.
5936
7223
  */
5937
7224
  declare interface VariableMapping {
7225
+ /** Column key in the data, matching a `columns[i].key`. */
5938
7226
  variable: string;
5939
7227
  }
5940
7228
 
@@ -5943,15 +7231,20 @@ declare type VariableMetadata = Record<VariableName, {
5943
7231
  valueFormat: ValueFormat;
5944
7232
  }>;
5945
7233
 
5946
- /** A type alias for variable names. */
7234
+ /** A variable (column) name. Names with the internal prefix address compiler-emitted columns; address those through the value readers (`getX`, …) or `variableFor`, never by literal. */
5947
7235
  export declare type VariableName = string;
5948
7236
 
5949
7237
  /**
5950
- * Field-based predicates against post-transform user columns.
7238
+ * A value test against one post-transform user column. The selected operator
7239
+ * decides which observations a highlight emphasises:
7240
+ * - `eq`: column equals the value.
7241
+ * - `oneOf`: column is one of the listed values.
7242
+ * - `lt` / `lte` / `gt` / `gte`: ordering comparison (numeric / datetime only).
7243
+ * - `range`: inclusive `[min, max]` interval.
5951
7244
  *
5952
- * `lt`, `lte`, `gt`, `gte`, and `range` accept `DataValue`s and are coerced at
5953
- * evaluation time by the referenced column's `DataType`. Ordering operators
5954
- * against a categorical field are a resolve-time validation error.
7245
+ * Comparison values are `DataValue`s coerced at evaluation time by the
7246
+ * referenced column's `DataType`. Ordering operators against a categorical
7247
+ * field are a resolve-time validation error.
5955
7248
  */
5956
7249
  export declare type VariablePredicate = {
5957
7250
  variable: VariableName;
@@ -6021,7 +7314,7 @@ declare interface XAxisConfig {
6021
7314
  */
6022
7315
  label: string | null;
6023
7316
  /**
6024
- * Position of the y axis.
7317
+ * Position of the x axis.
6025
7318
  * @default 'bottom'
6026
7319
  */
6027
7320
  position: AxisPosition;