graphein 0.3.0 → 0.4.0

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
@@ -764,6 +764,269 @@ type FilterClause = {
764
764
  param: string;
765
765
  } | LiteralPredicate;
766
766
 
767
+ /**
768
+ * Declarative data transforms.
769
+ *
770
+ * A `transform` is an ordered pipeline that reshapes the `data` array *inside*
771
+ * the spec — so an agent fixes data shape by editing validatable JSON instead of
772
+ * pre-massaging rows in code. Every transform is plain JSON (no functions) and
773
+ * pure: `applyTransforms` never mutates its input.
774
+ *
775
+ * The pipeline is applied before the chart model is built (and before any
776
+ * cross-filter selection), so encodings can reference fields the transforms
777
+ * produce (e.g. an `aggregate` output column).
778
+ */
779
+
780
+ /** One aggregation within an {@link AggregateTransform}. */
781
+ interface AggregateOp {
782
+ /** Aggregation operation. `count` needs no `field`. */
783
+ op: AggOp;
784
+ /** Source column to aggregate (omit only for `count`). */
785
+ field?: string;
786
+ /** Output column receiving the aggregated value. */
787
+ as: string;
788
+ }
789
+ /**
790
+ * Group rows by `groupby` and summarize each group into a single output row.
791
+ * Omitting `groupby` collapses the whole dataset into one row.
792
+ */
793
+ interface AggregateTransform {
794
+ aggregate: AggregateOp[];
795
+ groupby?: string[];
796
+ }
797
+ /**
798
+ * Bucket a quantitative `bin` field into evenly sized bins. Drives the histogram
799
+ * chart and any "distribution" view without pre-binning in user code.
800
+ */
801
+ interface BinTransform {
802
+ /** Numeric column to bin. */
803
+ bin: string;
804
+ /**
805
+ * Output column(s). A single name receives the bin **start**; a `[start, end]`
806
+ * pair receives both edges (handy for histogram bars and range labels).
807
+ */
808
+ as: string | [string, string];
809
+ /** Target bin count; a "nice" step approximates it. Default 10. */
810
+ maxbins?: number;
811
+ /** Explicit bin width. Overrides `maxbins` when set. */
812
+ step?: number;
813
+ /** Restrict binning to this `[min, max]`; values outside get a `null` bin. */
814
+ extent?: [number, number];
815
+ /** Snap the bin domain to round numbers (default true). */
816
+ nice?: boolean;
817
+ }
818
+ /**
819
+ * A declarative, JSON predicate for {@link FilterTransform}. Leaf predicates test
820
+ * one `field`; `and` / `or` / `not` compose them. Comparisons coerce numerically
821
+ * (numbers first, then dates), so temporal bounds work as ISO strings.
822
+ */
823
+ type FilterPredicate = {
824
+ field: string;
825
+ equals: unknown;
826
+ } | {
827
+ field: string;
828
+ ne: unknown;
829
+ } | {
830
+ field: string;
831
+ oneOf: unknown[];
832
+ } | {
833
+ field: string;
834
+ range: [number | string, number | string];
835
+ } | {
836
+ field: string;
837
+ contains: string;
838
+ } | {
839
+ field: string;
840
+ gt: number | string;
841
+ } | {
842
+ field: string;
843
+ gte: number | string;
844
+ } | {
845
+ field: string;
846
+ lt: number | string;
847
+ } | {
848
+ field: string;
849
+ lte: number | string;
850
+ } | {
851
+ field: string;
852
+ valid: boolean;
853
+ } | {
854
+ and: FilterPredicate[];
855
+ } | {
856
+ or: FilterPredicate[];
857
+ } | {
858
+ not: FilterPredicate;
859
+ };
860
+ /** Keep only rows matching `filter`. */
861
+ interface FilterTransform {
862
+ filter: FilterPredicate;
863
+ }
864
+ /**
865
+ * Gather several columns into key/value rows (wide → long). Each input row is
866
+ * repeated once per folded column. Use it to turn a wide table into a tidy one a
867
+ * `series` channel can split.
868
+ */
869
+ interface FoldTransform {
870
+ fold: string[];
871
+ /** Output `[key, value]` column names. Default `['key', 'value']`. */
872
+ as?: [string, string];
873
+ }
874
+ /** Calendar unit a {@link TimeUnitTransform} truncates a timestamp to. */
875
+ type TimeUnit = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
876
+ /**
877
+ * Truncate a temporal `field` to the start of a calendar unit (e.g. month),
878
+ * writing a `Date` to `as`. Lets agents aggregate by period without date math.
879
+ */
880
+ interface TimeUnitTransform {
881
+ timeUnit: TimeUnit;
882
+ field: string;
883
+ as: string;
884
+ }
885
+ /**
886
+ * Derive a new column from a **safe expression** evaluated per row, writing the
887
+ * result to `as`. The expression language supports arithmetic, comparison,
888
+ * logical and ternary operators, string concatenation, member access
889
+ * (`datum['my field']`), and a whitelist of functions (e.g. `round`, `lower`,
890
+ * `if`, `coalesce`). Bare identifiers reference row columns. It is parsed to an
891
+ * AST and evaluated with **no `eval`/`Function`** and no access to globals.
892
+ */
893
+ interface CalculateTransform {
894
+ calculate: string;
895
+ as: string;
896
+ }
897
+ /**
898
+ * A single declarative transform step. Distinguished by its operator key
899
+ * (`aggregate` / `bin` / `filter` / `fold` / `timeUnit` / `calculate`); a step
900
+ * carries exactly one. Applied in array order by {@link applyTransforms}.
901
+ */
902
+ type Transform = AggregateTransform | BinTransform | FilterTransform | FoldTransform | TimeUnitTransform | CalculateTransform;
903
+ /** Operator keys that identify a transform step (one per step). */
904
+ declare const TRANSFORM_KINDS: readonly ["aggregate", "bin", "filter", "fold", "timeUnit", "calculate"];
905
+ /** Supported {@link TimeUnit} values, for validation/UX. */
906
+ declare const TIME_UNITS: readonly TimeUnit[];
907
+
908
+ /** The transform pipeline: dispatch each step by its operator key, in order. */
909
+
910
+ /** Apply a single {@link Transform} step, returning a new array (never mutating). */
911
+ declare function applyTransform(transform: Transform, data: Datum[]): Datum[];
912
+ /**
913
+ * Apply a transform pipeline in array order. Pure — the input array and rows are
914
+ * never mutated. Returns the original reference unchanged when there are no
915
+ * transforms, so callers can cheaply detect a no-op.
916
+ */
917
+ declare function applyTransforms(transforms: Transform[] | undefined, data: Datum[]): Datum[];
918
+
919
+ /** The `filter` transform: keep rows satisfying a declarative JSON predicate. */
920
+
921
+ /**
922
+ * Compile a {@link FilterPredicate} into a fast `row → boolean` tester, hoisting
923
+ * accessors and lookup sets out of the per-row path. An unrecognized leaf shape
924
+ * compiles to "match nothing" (validation flags it as an error first).
925
+ */
926
+ declare function compilePredicate(pred: FilterPredicate): (row: Datum) => boolean;
927
+ /** Apply a {@link FilterTransform}, returning a new array of matching rows. */
928
+ declare function applyFilter(transform: FilterTransform, data: Datum[]): Datum[];
929
+
930
+ /** The `aggregate` transform: group rows and summarize each group into one row. */
931
+
932
+ /**
933
+ * Apply an {@link AggregateTransform}. Groups are emitted in first-seen order and
934
+ * keep the **original** group-key values (not stringified). With no `groupby`,
935
+ * the whole dataset collapses to a single summary row.
936
+ */
937
+ declare function applyAggregate(transform: AggregateTransform, data: Datum[]): Datum[];
938
+
939
+ /** The `bin` transform: bucket a quantitative field into evenly sized bins. */
940
+
941
+ interface BinLayout {
942
+ /** Lower edge of the first bin. */
943
+ start: number;
944
+ /** Bin width. */
945
+ step: number;
946
+ /** Number of bins. */
947
+ count: number;
948
+ }
949
+ /**
950
+ * Choose a bin layout for `values`. Honors an explicit `step`, otherwise picks a
951
+ * "nice" step approximating `maxbins` (default 10). Returns `null` when there is
952
+ * no finite data to bin.
953
+ */
954
+ declare function computeBins(values: readonly number[], opts?: {
955
+ maxbins?: number;
956
+ step?: number;
957
+ extent?: [number, number];
958
+ nice?: boolean;
959
+ }): BinLayout | null;
960
+ /**
961
+ * Apply a {@link BinTransform}, writing the bin start (and optional end) onto each
962
+ * row. Rows whose value is non-numeric or outside an explicit `extent` get `null`.
963
+ */
964
+ declare function applyBin(transform: BinTransform, data: Datum[]): Datum[];
965
+
966
+ /** The `fold` transform: gather several columns into key/value rows (wide → long). */
967
+
968
+ /**
969
+ * Apply a {@link FoldTransform}. Each input row is repeated once per folded
970
+ * column, carrying all original fields plus the `[key, value]` pair (default
971
+ * names `key` / `value`).
972
+ */
973
+ declare function applyFold(transform: FoldTransform, data: Datum[]): Datum[];
974
+
975
+ /** The `timeUnit` transform: truncate a timestamp to the start of a calendar unit. */
976
+
977
+ /** Truncate `date` to the start of `unit` (local time). */
978
+ declare function truncateTo(date: Date, unit: TimeUnit): Date;
979
+ /**
980
+ * Apply a {@link TimeUnitTransform}, writing a truncated `Date` to `as`. Rows whose
981
+ * field can't be parsed as a date receive `null`.
982
+ */
983
+ declare function applyTimeUnit(transform: TimeUnitTransform, data: Datum[]): Datum[];
984
+
985
+ /**
986
+ * `calculate` transform: derive a new column from a safe expression evaluated
987
+ * per row. The expression is compiled once and never uses `eval`/`Function`.
988
+ */
989
+
990
+ /** Apply a {@link CalculateTransform}, returning new rows with the `as` column. */
991
+ declare function applyCalculate(transform: CalculateTransform, data: Datum[]): Datum[];
992
+
993
+ /** Error thrown by the `calculate` expression tokenizer/parser/evaluator. */
994
+ declare class ExpressionError extends Error {
995
+ /** Source offset where the problem was detected, when known. */
996
+ readonly pos: number | undefined;
997
+ constructor(message: string, pos?: number);
998
+ }
999
+
1000
+ /**
1001
+ * Evaluator for the `calculate` AST. Pure and sandboxed: the only callable names
1002
+ * are the {@link FUNCTIONS} whitelist, member access is guarded against prototype
1003
+ * keys, and there is no access to `eval`, `Function`, globals, or row mutation.
1004
+ * All functions are deterministic (no `now()`/`random()`), per the transform
1005
+ * determinism convention.
1006
+ */
1007
+
1008
+ /** Names the parser may treat as callable — exposed for validation. */
1009
+ declare const FUNCTION_NAMES: readonly string[];
1010
+
1011
+ /**
1012
+ * Safe `calculate` expression engine — public surface.
1013
+ *
1014
+ * `compileExpression(src)` parses once and returns a pure `(row) => value`
1015
+ * function used by the calculate transform. No `eval`/`Function` is ever used;
1016
+ * see {@link evaluate} for the sandbox guarantees.
1017
+ */
1018
+
1019
+ /** Parse `src` once; returns a reusable, pure evaluator over a row. */
1020
+ declare function compileExpression(src: string): (row: Datum) => unknown;
1021
+ /**
1022
+ * Validate an expression's syntax without evaluating it. Returns an error message
1023
+ * (and source offset) when the expression cannot be parsed, else `null`.
1024
+ */
1025
+ declare function checkExpression(src: string): {
1026
+ message: string;
1027
+ pos: number | undefined;
1028
+ } | null;
1029
+
767
1030
  /**
768
1031
  * Graphein declarative chart specs.
769
1032
  *
@@ -902,6 +1165,13 @@ interface SketchConfig {
902
1165
  interface BaseSpec {
903
1166
  /** Row-oriented (tidy) data. Required for all charts/tables. */
904
1167
  data?: Datum[];
1168
+ /**
1169
+ * Declarative data transforms applied to `data` (in order) before the chart is
1170
+ * built — aggregate, bin, filter, fold, timeUnit. Reshape data *inside* the
1171
+ * spec instead of pre-massaging the array, so encodings can reference fields
1172
+ * the pipeline produces. Pure JSON; see {@link Transform}.
1173
+ */
1174
+ transform?: Transform[];
905
1175
  /** Theme name ('light' | 'dark') or a partial override object. */
906
1176
  theme?: ThemeInput;
907
1177
  dimensions?: Dimensions;
@@ -946,6 +1216,103 @@ interface BaseSpec {
946
1216
  filter?: FilterClause[];
947
1217
  }
948
1218
  type CurveType = 'linear' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' | 'catmullRom';
1219
+ /**
1220
+ * A reference annotation overlaid on a cartesian chart: a single reference
1221
+ * **line** at a value, or a filled **band** / threshold **zone** between two
1222
+ * values. Useful for targets, goals, averages, control limits, and "danger"
1223
+ * ranges. Drawn over the gridlines and data marks; labels render in the overlay.
1224
+ */
1225
+ interface Annotation {
1226
+ /**
1227
+ * What to draw. Omit to infer: a `line` when `value` is set, a `band` when
1228
+ * `from`/`to` are set. `zone` is a band — a semantic alias for a threshold band.
1229
+ * A `point` marks a single (`x`, `y`) data coordinate with a labeled dot.
1230
+ */
1231
+ type?: 'line' | 'band' | 'zone' | 'point';
1232
+ /**
1233
+ * Which axis the value(s) are measured against. `y` (default) draws a
1234
+ * horizontal line / full-width band; `x` draws a vertical line / full-height band.
1235
+ */
1236
+ axis?: 'x' | 'y';
1237
+ /** Reference value for a `line` (number, category, or date — matching the axis). */
1238
+ value?: number | string | Date;
1239
+ /** Start of the span for a `band`/`zone`. */
1240
+ from?: number | string | Date;
1241
+ /** End of the span for a `band`/`zone`. */
1242
+ to?: number | string | Date;
1243
+ /** X coordinate of a `point` callout (a data value on the x-axis). */
1244
+ x?: number | string | Date;
1245
+ /** Y coordinate of a `point` callout (a data value on the y-axis). */
1246
+ y?: number | string | Date;
1247
+ /** Marker radius in pixels for a `point` (default 3.5). */
1248
+ markerRadius?: number;
1249
+ /** Short text label drawn beside the annotation. */
1250
+ label?: string;
1251
+ /** Stroke (line) / fill (band) color. Defaults to a muted theme color. */
1252
+ color?: string;
1253
+ /** Line width in pixels for a `line` (default 1.5). */
1254
+ strokeWidth?: number;
1255
+ /** Dash pattern for the stroke; `[]` is solid. Lines/bands default to dashed. */
1256
+ strokeDash?: number[];
1257
+ /** Fill opacity for a `band`/`zone` (0..1, default 0.12). */
1258
+ fillOpacity?: number;
1259
+ /** Where the label anchors along the annotation (default `end`). */
1260
+ labelPosition?: 'start' | 'middle' | 'end';
1261
+ }
1262
+ /**
1263
+ * Which automatic data insights to mark on a cartesian plot. Enable via a
1264
+ * chart's `insights` field — `true` marks the max and min; an object opts into
1265
+ * specific callouts. The library derives the points and draws labeled markers,
1266
+ * so an agent never has to reason out (or hardcode) where the peak is.
1267
+ */
1268
+ interface InsightOptions {
1269
+ /** Mark the maximum point (default true). */
1270
+ max?: boolean;
1271
+ /** Mark the minimum point (default true). */
1272
+ min?: boolean;
1273
+ /** Mark statistical outliers beyond the 1.5×IQR fences (default false). */
1274
+ outliers?: boolean;
1275
+ }
1276
+ /**
1277
+ * A derived **trendline** (line of best fit) overlaid on a cartesian plot.
1278
+ * Enable via a chart's `trendline` field — `true` fits a single linear
1279
+ * regression; an object configures the fit and its styling. The library
1280
+ * computes the regression from the plotted rows, so an agent never has to
1281
+ * derive slope/intercept coordinates by hand. Requires a continuous or temporal
1282
+ * x-axis (scatter, line, area) — it is meaningless on a categorical/band axis.
1283
+ */
1284
+ interface TrendlineConfig {
1285
+ /** Fit method. Currently `'linear'` (ordinary least squares). */
1286
+ method?: 'linear';
1287
+ /**
1288
+ * Fit a separate line per color/series group. Defaults to `true` when the
1289
+ * chart splits into multiple series, `false` (one overall fit) otherwise.
1290
+ */
1291
+ groupBy?: boolean;
1292
+ /** Draw an `R²=…` label at the end of each fitted line (default false). */
1293
+ label?: boolean;
1294
+ /** Line color. Defaults to the series color (grouped) or a muted ink. */
1295
+ color?: string;
1296
+ /** Line width in px (default 2). */
1297
+ strokeWidth?: number;
1298
+ /** Dash pattern; `[]` is solid (the default). */
1299
+ strokeDash?: number[];
1300
+ }
1301
+ /**
1302
+ * **Faceting** (small multiples): split a chart into a trellis grid of panels,
1303
+ * one per distinct value of a field, all sharing identical x/y/color scales so
1304
+ * the panels are directly comparable. Enable via a chart's `facet` field — a
1305
+ * single field reference yields a whole comparison grid, which is why it is so
1306
+ * agent-friendly. Supported on `line`, `area`, `bar`, and `scatter`.
1307
+ */
1308
+ interface FacetConfig {
1309
+ /** The field whose distinct values become one panel each. */
1310
+ field: string;
1311
+ /** Number of grid columns. Defaults to roughly √n, capped for readability. */
1312
+ columns?: number;
1313
+ /** Order the panels by their facet value (default `ascending`). */
1314
+ sort?: 'ascending' | 'descending' | 'none';
1315
+ }
949
1316
  interface LineSpec extends BaseSpec {
950
1317
  type: 'line';
951
1318
  encoding: Encoding & {
@@ -957,6 +1324,14 @@ interface LineSpec extends BaseSpec {
957
1324
  points?: boolean;
958
1325
  /** Fill the area under the line. */
959
1326
  area?: boolean;
1327
+ /** Reference lines, bands, and threshold zones overlaid on the plot. */
1328
+ annotations?: Annotation[];
1329
+ /** Auto-mark notable data points (`true` = max + min). See {@link InsightOptions}. */
1330
+ insights?: boolean | InsightOptions;
1331
+ /** Overlay a linear line of best fit. See {@link TrendlineConfig}. */
1332
+ trendline?: boolean | TrendlineConfig;
1333
+ /** Split into a trellis grid of small multiples. See {@link FacetConfig}. */
1334
+ facet?: FacetConfig;
960
1335
  }
961
1336
  interface AreaSpec extends BaseSpec {
962
1337
  type: 'area';
@@ -967,6 +1342,14 @@ interface AreaSpec extends BaseSpec {
967
1342
  curve?: CurveType;
968
1343
  /** Stack multiple series. */
969
1344
  stack?: boolean;
1345
+ /** Reference lines, bands, and threshold zones overlaid on the plot. */
1346
+ annotations?: Annotation[];
1347
+ /** Auto-mark notable data points (`true` = max + min). See {@link InsightOptions}. */
1348
+ insights?: boolean | InsightOptions;
1349
+ /** Overlay a linear line of best fit. See {@link TrendlineConfig}. */
1350
+ trendline?: boolean | TrendlineConfig;
1351
+ /** Split into a trellis grid of small multiples. See {@link FacetConfig}. */
1352
+ facet?: FacetConfig;
970
1353
  }
971
1354
  interface BarSpec extends BaseSpec {
972
1355
  type: 'bar';
@@ -980,6 +1363,12 @@ interface BarSpec extends BaseSpec {
980
1363
  /** Group series side-by-side (default when `series` present and not stacked). */
981
1364
  group?: boolean;
982
1365
  cornerRadius?: number;
1366
+ /** Reference lines, bands, and threshold zones overlaid on the plot. */
1367
+ annotations?: Annotation[];
1368
+ /** Auto-mark notable data points (`true` = top + bottom category). See {@link InsightOptions}. */
1369
+ insights?: boolean | InsightOptions;
1370
+ /** Split into a trellis grid of small multiples. See {@link FacetConfig}. */
1371
+ facet?: FacetConfig;
983
1372
  }
984
1373
  interface ScatterSpec extends BaseSpec {
985
1374
  type: 'scatter';
@@ -987,6 +1376,87 @@ interface ScatterSpec extends BaseSpec {
987
1376
  x: FieldDef;
988
1377
  y: FieldDef;
989
1378
  };
1379
+ /** Reference lines, bands, and threshold zones overlaid on the plot. */
1380
+ annotations?: Annotation[];
1381
+ /** Overlay a linear line of best fit. See {@link TrendlineConfig}. */
1382
+ trendline?: boolean | TrendlineConfig;
1383
+ /** Split into a trellis grid of small multiples. See {@link FacetConfig}. */
1384
+ facet?: FacetConfig;
1385
+ }
1386
+ /** The mark a single combo layer draws. */
1387
+ type ComboMark = 'line' | 'bar' | 'area' | 'scatter';
1388
+ /**
1389
+ * One layer of a {@link ComboSpec}: a mark plus the measure it plots on `y`. All
1390
+ * layers share the combo's `x`. A layer can be measured against the primary
1391
+ * (`left`) y-axis or an independent secondary (`right`) axis — the basis of a
1392
+ * dual-axis bar+line chart.
1393
+ */
1394
+ interface ComboLayer {
1395
+ /** Which mark to draw for this layer. */
1396
+ mark: ComboMark;
1397
+ /** The measure (and optional per-layer formatting) plotted on `y`. */
1398
+ encoding: {
1399
+ y: FieldDef;
1400
+ };
1401
+ /** Which y-axis this layer is measured against. Defaults to `left`. */
1402
+ axis?: 'left' | 'right';
1403
+ /** Curve interpolation for `line`/`area` layers. */
1404
+ curve?: CurveType;
1405
+ /** Show point markers (line/area layers). */
1406
+ points?: boolean;
1407
+ /** Fill under the line (line layers — equivalent to an `area` mark). */
1408
+ area?: boolean;
1409
+ /** Bar corner radius (bar layers). */
1410
+ cornerRadius?: number;
1411
+ /** Legend label for the layer. Defaults to the y field's title or name. */
1412
+ name?: string;
1413
+ /** Override the layer's color (otherwise drawn from the theme palette). */
1414
+ color?: string;
1415
+ }
1416
+ /**
1417
+ * Combo / dual-axis chart: composes multiple cartesian {@link ComboLayer}s over a
1418
+ * shared `x` axis — the canonical BI "bar + line" view. Layers may target a primary
1419
+ * (`left`) or secondary (`right`) y-axis, each with its own independent scale.
1420
+ * Bars require a categorical `x`; lines/areas/points align to category centers.
1421
+ */
1422
+ interface ComboSpec extends BaseSpec {
1423
+ type: 'combo';
1424
+ encoding: {
1425
+ x: FieldDef;
1426
+ };
1427
+ layers: ComboLayer[];
1428
+ }
1429
+ /** Binning controls for a {@link HistogramSpec} (mirror the `bin` transform). */
1430
+ interface HistogramBin {
1431
+ /** Target bin count (approximate — a "nice" step is chosen). Default 10. */
1432
+ maxbins?: number;
1433
+ /** Explicit bin width (overrides `maxbins`). */
1434
+ step?: number;
1435
+ /** Restrict binning to `[min, max]`; values outside are dropped. */
1436
+ extent?: [number, number];
1437
+ /** Snap bin edges to nice round numbers (default true). */
1438
+ nice?: boolean;
1439
+ }
1440
+ /**
1441
+ * Histogram: bins a single quantitative field and draws the per-bin frequency as
1442
+ * gapless bars. Binning happens inside the chart (reusing the `bin` transform), so
1443
+ * an agent passes raw observations — no manual pre-binning. Bar height is the bin
1444
+ * count by default, or a probability density (area sums to 1) when `density:true`.
1445
+ */
1446
+ interface HistogramSpec extends BaseSpec {
1447
+ type: 'histogram';
1448
+ /** `x` is the quantitative field to bin (also carries the axis title/format). */
1449
+ encoding: {
1450
+ x: FieldDef;
1451
+ };
1452
+ /** Binning controls (default ~10 nice bins, or an explicit `step`/`extent`). */
1453
+ bin?: HistogramBin;
1454
+ /** Normalize bar heights to a probability density (area sums to 1). */
1455
+ density?: boolean;
1456
+ /** Bar colour (defaults to the first theme palette colour). */
1457
+ color?: string;
1458
+ /** Bar corner radius. */
1459
+ cornerRadius?: number;
990
1460
  }
991
1461
  interface PieSpec extends BaseSpec {
992
1462
  type: 'pie';
@@ -1073,6 +1543,173 @@ interface KpiSpec extends BaseSpec {
1073
1543
  field: string;
1074
1544
  };
1075
1545
  }
1546
+ /**
1547
+ * Treemap — part-to-whole as nested rectangles sized by a measure. Pass tidy
1548
+ * rows (one per leaf); the area of each tile is proportional to `value`. An
1549
+ * optional `group` field nests leaves under parent tiles (one level), and
1550
+ * `color` overrides the default per-group/leaf fill (numeric → sequential scale,
1551
+ * categorical → palette).
1552
+ */
1553
+ interface TreemapSpec extends BaseSpec {
1554
+ type: 'treemap';
1555
+ encoding: Encoding & {
1556
+ /** Leaf tile label + identity. */
1557
+ category: FieldDef;
1558
+ /** Numeric field sizing each tile's area (summed per leaf). */
1559
+ value: FieldDef;
1560
+ /** Optional parent grouping → nested parent tiles (one level deep). */
1561
+ group?: FieldDef;
1562
+ /** Optional field driving tile color (numeric → sequential, else palette). */
1563
+ color?: FieldDef;
1564
+ };
1565
+ /** Sequential color scheme name when `color` is numeric (default 'teal'). */
1566
+ scheme?: string;
1567
+ /** Show the value beneath the label inside each tile (default true). */
1568
+ labels?: boolean;
1569
+ }
1570
+ /**
1571
+ * Gauge — a radial dial showing a single value against a `[min, max]` scale,
1572
+ * with an optional `target` needle and qualitative background `bands`. The
1573
+ * value is a literal or a field (optionally aggregated, like {@link KpiSpec}).
1574
+ */
1575
+ interface GaugeSpec extends BaseSpec {
1576
+ type: 'gauge';
1577
+ /** The measured value (literal or a field, optionally aggregated over data). */
1578
+ value: ValueRef;
1579
+ /** Scale start (default 0). */
1580
+ min?: number;
1581
+ /** Scale end — the gauge's full-scale (required). */
1582
+ max: number;
1583
+ /** Optional target/threshold marker drawn as a needle/tick. */
1584
+ target?: ValueRef;
1585
+ /** Caption under the value (defaults to the spec title or value field). */
1586
+ label?: string;
1587
+ /** Number format for the value + scale ticks (e.g. ',.0f', '.0%'). */
1588
+ format?: string;
1589
+ /** Qualitative arc bands, each filling the scale up to `to`. */
1590
+ bands?: {
1591
+ to: number;
1592
+ color?: string;
1593
+ }[];
1594
+ }
1595
+ /**
1596
+ * Bullet graph — a compact linear KPI-vs-target: a measure bar over qualitative
1597
+ * `ranges` (poor/ok/good background bands) with a `target` comparative marker.
1598
+ * The featured value/target are literals or fields (optionally aggregated).
1599
+ */
1600
+ interface BulletSpec extends BaseSpec {
1601
+ type: 'bullet';
1602
+ /** The featured measure (literal or a field, optionally aggregated). */
1603
+ value: ValueRef;
1604
+ /** Target/comparative marker (literal or field) drawn as a vertical tick. */
1605
+ target?: ValueRef;
1606
+ /** Scale start (default 0). */
1607
+ min?: number;
1608
+ /** Scale end (default: derived from value/target/ranges). */
1609
+ max?: number;
1610
+ /** Qualitative range boundaries on the scale (e.g. [50, 75, 100]). */
1611
+ ranges?: number[];
1612
+ /** Caption to the left of the bar (defaults to the spec title or value field). */
1613
+ label?: string;
1614
+ /** Number format for the value + axis ticks. */
1615
+ format?: string;
1616
+ }
1617
+ /**
1618
+ * Calendar heatmap — a GitHub-contributions-style grid of one cell per day,
1619
+ * colored by a value via a sequential scale, with weekday rows and month
1620
+ * labels. Pass tidy rows of `{ date, value }`; dates may be `Date` objects or
1621
+ * ISO strings.
1622
+ */
1623
+ interface CalendarHeatmapSpec extends BaseSpec {
1624
+ type: 'calendarHeatmap';
1625
+ encoding: Encoding & {
1626
+ /** Date field (Date or ISO string) → one cell per day. */
1627
+ date: FieldDef;
1628
+ /** Numeric value field → cell color via a sequential scale. */
1629
+ color: FieldDef;
1630
+ };
1631
+ /** Sequential color scheme name for the value scale (default 'teal'). */
1632
+ scheme?: string;
1633
+ }
1634
+ /**
1635
+ * Waterfall — a column chart of signed steps where each bar floats from the
1636
+ * running total to its new level, showing how sequential increases and decreases
1637
+ * build to a final value. Each `value` is the **signed change** at that stage
1638
+ * (positive rises, negative falls). Stages named in `totals` (and an optional
1639
+ * appended grand total) draw as absolute bars from the baseline. Increases,
1640
+ * decreases, and totals are colored distinctly and joined by connector lines.
1641
+ */
1642
+ interface WaterfallSpec extends BaseSpec {
1643
+ type: 'waterfall';
1644
+ encoding: Encoding & {
1645
+ /** Stage label along the x-axis (one bar per row, in data order). */
1646
+ stage: FieldDef;
1647
+ /** Signed change at each stage (positive rises, negative falls). */
1648
+ value: FieldDef;
1649
+ };
1650
+ /** Stage labels to draw as absolute running-total bars from the baseline. */
1651
+ totals?: string[];
1652
+ /** Append a final bar summing every step (default false). */
1653
+ showTotal?: boolean;
1654
+ /** Label for the appended total bar (default 'Total'). */
1655
+ totalLabel?: string;
1656
+ /** Show the per-bar value labels (default true). */
1657
+ labels?: boolean;
1658
+ /** Bar corner radius in pixels (default 2). */
1659
+ cornerRadius?: number;
1660
+ /** Color for upward steps (default theme positive/green). */
1661
+ increaseColor?: string;
1662
+ /** Color for downward steps (default theme negative/red). */
1663
+ decreaseColor?: string;
1664
+ /** Color for total/subtotal bars (default theme accent). */
1665
+ totalColor?: string;
1666
+ }
1667
+ /**
1668
+ * Slope graph — a minimal "before/after" chart: each `series` is a line joining
1669
+ * its `y` value across two (or a few) ordinal `x` positions, with direct end
1670
+ * labels instead of a legend. Reads change-in-rank and rise/fall at a glance.
1671
+ * Pass tidy rows of `{ x, y, series }` (one row per series per x position).
1672
+ */
1673
+ interface SlopeSpec extends BaseSpec {
1674
+ type: 'slope';
1675
+ encoding: Encoding & {
1676
+ /** The ordinal positions along the x-axis (typically two: e.g. start/end). */
1677
+ x: FieldDef;
1678
+ /** Numeric value plotted on the y-axis. */
1679
+ y: FieldDef;
1680
+ /** One line per series (the entity whose change is traced). */
1681
+ series: FieldDef;
1682
+ };
1683
+ /** Direct labels (series name + value) at each line's ends (default true). */
1684
+ labels?: boolean;
1685
+ /** Color each line green/red by its net rise/fall instead of by series. */
1686
+ colorByChange?: boolean;
1687
+ /** Number format for value labels and the y-axis. */
1688
+ format?: string;
1689
+ }
1690
+ /**
1691
+ * Dumbbell / connected dot plot — for each `category`, a dot per `group` placed
1692
+ * on a shared horizontal value axis and joined by a connector, so the gap between
1693
+ * groups (e.g. before vs. after, male vs. female) reads directly. Categories run
1694
+ * down the y-axis. Pass tidy rows of `{ category, value, group }`.
1695
+ */
1696
+ interface DumbbellSpec extends BaseSpec {
1697
+ type: 'dumbbell';
1698
+ encoding: Encoding & {
1699
+ /** The category for each row (one band down the y-axis). */
1700
+ category: FieldDef;
1701
+ /** Numeric value → dot position on the horizontal axis. */
1702
+ value: FieldDef;
1703
+ /** The group each dot belongs to (2+ levels → one dot each, connected). */
1704
+ group: FieldDef;
1705
+ };
1706
+ /** Value labels beside the dots (default false). */
1707
+ labels?: boolean;
1708
+ /** Number format for value labels and the x-axis. */
1709
+ format?: string;
1710
+ /** Order the category rows (default: data order). `gap` sorts by dot spread. */
1711
+ sort?: 'ascending' | 'descending' | 'gap';
1712
+ }
1076
1713
  type ConditionalFormat = {
1077
1714
  type: 'colorScale';
1078
1715
  scheme?: string;
@@ -1193,6 +1830,8 @@ interface BoxSpec extends BaseSpec {
1193
1830
  whisker?: 'tukey' | 'minMax';
1194
1831
  /** Draw outlier points beyond the whiskers (tukey only; default true). */
1195
1832
  outliers?: boolean;
1833
+ /** Reference lines, bands, and threshold zones overlaid on the plot. */
1834
+ annotations?: Annotation[];
1196
1835
  }
1197
1836
  interface SankeySpec extends BaseSpec {
1198
1837
  type: 'sankey';
@@ -1335,7 +1974,7 @@ interface DateRangeSlicerSpec extends BaseSlicerSpec {
1335
1974
  type SlicerSpec = DropdownSlicerSpec | SearchSlicerSpec | ListSlicerSpec | RangeSlicerSpec | DateRangeSlicerSpec;
1336
1975
  type SlicerType = SlicerSpec['type'];
1337
1976
  declare const SLICER_TYPES: readonly SlicerType[];
1338
- type ChartSpec = LineSpec | AreaSpec | BarSpec | ScatterSpec | PieSpec | HeatmapSpec | FunnelSpec | KpiSpec | TableSpec | MatrixSpec | BoxSpec | SankeySpec | ChoroplethSpec | DropdownSlicerSpec | SearchSlicerSpec | ListSlicerSpec | RangeSlicerSpec | DateRangeSlicerSpec;
1977
+ type ChartSpec = LineSpec | AreaSpec | BarSpec | ScatterSpec | ComboSpec | HistogramSpec | PieSpec | HeatmapSpec | FunnelSpec | KpiSpec | TreemapSpec | GaugeSpec | BulletSpec | CalendarHeatmapSpec | WaterfallSpec | SlopeSpec | DumbbellSpec | TableSpec | MatrixSpec | BoxSpec | SankeySpec | ChoroplethSpec | DropdownSlicerSpec | SearchSlicerSpec | ListSlicerSpec | RangeSlicerSpec | DateRangeSlicerSpec;
1339
1978
  type ChartType = ChartSpec['type'];
1340
1979
  declare const CHART_TYPES: readonly ChartType[];
1341
1980
 
@@ -1486,6 +2125,13 @@ declare class Surface {
1486
2125
  width: number;
1487
2126
  height: number;
1488
2127
  dpr: number;
2128
+ /**
2129
+ * When true, overlay text (axis labels, titles, legends, annotation labels) is
2130
+ * painted onto the `marks` canvas instead of the HTML overlay. Set by headless
2131
+ * renderers (`@graphein/node`) where there is no DOM to host crisp text.
2132
+ * Defaults to false (browser).
2133
+ */
2134
+ headless: boolean;
1489
2135
  constructor(container: HTMLElement);
1490
2136
  resize(width: number, height: number, dpr?: number): void;
1491
2137
  /** Clear both canvas layers and empty the HTML overlay. */
@@ -1503,6 +2149,211 @@ declare class Surface {
1503
2149
 
1504
2150
  declare function applyA11y(surface: Surface, spec: ChartSpec): void;
1505
2151
 
2152
+ /**
2153
+ * Data insight analysis — the pure analytical core behind self-explaining charts.
2154
+ *
2155
+ * Given a {@link ChartSpec} (spec + its `data`), {@link analyzeChart} derives a
2156
+ * structured, dependency-free set of facts about what the numbers *say*: the
2157
+ * trend and net change of a series, the largest/smallest category and its share,
2158
+ * outliers, scatter correlation, a value vs. its target. Two consumers build on
2159
+ * it without re-deriving anything:
2160
+ * - {@link summarize} turns insights into a deterministic plain-English summary
2161
+ * (doubles as alt-text — no LLM).
2162
+ * - auto-insight annotations turn the same `PointRef`s into on-chart callouts.
2163
+ *
2164
+ * The analysis is a pure function of the spec; it reads rows as-is (charts plot
2165
+ * rows in data order, so first/last reflect data order) and never mutates input.
2166
+ */
2167
+
2168
+ /** A single salient point in a series: its value, its x-label, and row index. */
2169
+ interface PointRef {
2170
+ /** The x-axis label for this point (formatted as a string). */
2171
+ label: string;
2172
+ /** The numeric value at this point. */
2173
+ value: number;
2174
+ /** Index of the originating row within its series. */
2175
+ index: number;
2176
+ /**
2177
+ * The raw x-axis domain value (a `Date`, string, or number) for this point,
2178
+ * suitable for mapping back to a pixel via a chart's x-scale. Undefined when
2179
+ * the analysis had no x channel (e.g. a raw distribution).
2180
+ */
2181
+ raw?: unknown;
2182
+ }
2183
+ /** Trend + spread facts for one ordered numeric series. */
2184
+ interface SeriesInsights {
2185
+ /** Series name (`''` for a single, unsplit series). */
2186
+ key: string;
2187
+ /** Number of points. */
2188
+ count: number;
2189
+ first: PointRef;
2190
+ last: PointRef;
2191
+ min: PointRef;
2192
+ max: PointRef;
2193
+ /** Arithmetic mean of the values. */
2194
+ mean: number;
2195
+ /** Sum of the values. */
2196
+ sum: number;
2197
+ /** `last - first`. */
2198
+ netChange: number;
2199
+ /** `(last - first) / |first|`, or `null` when `first` is 0. */
2200
+ pctChange: number | null;
2201
+ /** Overall direction of travel from first to last. */
2202
+ direction: 'up' | 'down' | 'flat';
2203
+ /** The largest single step between consecutive points (by absolute delta). */
2204
+ biggestJump: {
2205
+ from: PointRef;
2206
+ to: PointRef;
2207
+ delta: number;
2208
+ } | null;
2209
+ /** Points beyond the 1.5×IQR Tukey fences (empty when none). */
2210
+ outliers: PointRef[];
2211
+ }
2212
+ /** Part-to-whole facts for a categorical measure. */
2213
+ interface CategoryInsights {
2214
+ /** Number of categories. */
2215
+ count: number;
2216
+ /** Sum of the measure across all categories. */
2217
+ total: number;
2218
+ /** The largest category. */
2219
+ top: PointRef;
2220
+ /** The smallest category. */
2221
+ bottom: PointRef;
2222
+ /** `top.value / total` (0..1), or 0 when the total is not positive. */
2223
+ topShare: number;
2224
+ }
2225
+ /** Relationship facts for an x/y point cloud. */
2226
+ interface ScatterInsights {
2227
+ count: number;
2228
+ xExtent: [number, number];
2229
+ yExtent: [number, number];
2230
+ /** Pearson correlation coefficient (−1..1), or `null` when undefined. */
2231
+ correlation: number | null;
2232
+ }
2233
+ /** A single value measured against an optional target. */
2234
+ interface ValueInsights {
2235
+ value: number;
2236
+ target: number | null;
2237
+ /** `value - target`, or `null` when there is no target. */
2238
+ toTarget: number | null;
2239
+ /** `value / target`, or `null` when there is no (non-zero) target. */
2240
+ pctOfTarget: number | null;
2241
+ }
2242
+ type InsightFamily = 'series' | 'category' | 'scatter' | 'value' | 'distribution';
2243
+ /** The structured insight payload for a chart (see {@link analyzeChart}). */
2244
+ interface ChartInsights {
2245
+ type: ChartType;
2246
+ family: InsightFamily;
2247
+ /** Rows backing the chart. */
2248
+ rowCount: number;
2249
+ /** The measure field name (y / value / theta), when there is one. */
2250
+ measureField?: string;
2251
+ /** The measure's number-format hint (for downstream formatting). */
2252
+ measureFormat?: string;
2253
+ /** Display title for the measure (field title or field name). */
2254
+ measureLabel?: string;
2255
+ /** The category / x field name, when there is one. */
2256
+ categoryField?: string;
2257
+ /** Inferred type of the x / category field. */
2258
+ categoryType?: FieldType;
2259
+ /** Per-series trend facts (family `series`). */
2260
+ series?: SeriesInsights[];
2261
+ /** Highest-ending series (multi-series only). */
2262
+ leader?: {
2263
+ key: string;
2264
+ value: number;
2265
+ };
2266
+ /** The series that moved most from first→last (multi-series only). */
2267
+ biggestMover?: {
2268
+ key: string;
2269
+ delta: number;
2270
+ };
2271
+ /** Part-to-whole facts (family `category`). */
2272
+ category?: CategoryInsights;
2273
+ /** Point-cloud facts (family `scatter`). */
2274
+ scatter?: ScatterInsights;
2275
+ /** Single value vs. target (family `value`). */
2276
+ value?: ValueInsights;
2277
+ /** Distribution of one measure (family `distribution`, e.g. histogram). */
2278
+ distribution?: SeriesInsights;
2279
+ }
2280
+ /**
2281
+ * Compute trend + spread insights for a parallel `values`/`labels` series.
2282
+ * Returns `null` when there are no finite values.
2283
+ */
2284
+ declare function computeSeriesInsights(values: readonly number[], labels: readonly string[], key?: string, raws?: readonly unknown[]): SeriesInsights | null;
2285
+ /**
2286
+ * Derive structured insights from a chart spec, or `null` when the chart type
2287
+ * carries no summarizable trend (e.g. table, matrix, sankey, maps).
2288
+ */
2289
+ declare function analyzeChart(spec: ChartSpec): ChartInsights | null;
2290
+
2291
+ /**
2292
+ * Deterministic natural-language chart summaries — the chart explains itself.
2293
+ *
2294
+ * {@link summarize} turns the structured facts from {@link analyzeChart} into one
2295
+ * or two plain-English sentences ("Users rose 46% from 4,200 to 6,150…"). It is
2296
+ * a pure, LLM-free function of the spec, so the same string is reproducible in a
2297
+ * report, an email, or — crucially — as the alt-text behind a canvas chart.
2298
+ *
2299
+ * Returns `''` for chart types that carry no summarizable trend (tables, maps,
2300
+ * sankey), so callers can treat an empty string as "nothing worth narrating".
2301
+ */
2302
+
2303
+ /**
2304
+ * Build a deterministic, plain-English summary of what a chart's data shows.
2305
+ * Doubles as alt-text. Returns `''` when the chart type isn't summarizable.
2306
+ */
2307
+ declare function summarize(spec: ChartSpec): string;
2308
+
2309
+ /**
2310
+ * Auto-insight annotations — turn a chart's {@link analyzeChart} facts into
2311
+ * on-chart callouts. A cartesian chart opts in with `insights: true` (or an
2312
+ * {@link InsightOptions} object); the library finds the notable points (the peak,
2313
+ * the trough, statistical outliers, or the top/bottom category) and emits labeled
2314
+ * `point` {@link Annotation}s — so an agent never has to reason out *where* the
2315
+ * maximum is or hardcode its coordinates.
2316
+ *
2317
+ * Pure and deterministic: a function of the spec + its data, reusing the same
2318
+ * analytical core as {@link summarize}. Multi-series charts are skipped (markers
2319
+ * on every series would clutter the plot); use `insights` on a single series.
2320
+ */
2321
+
2322
+ /** Normalize the `insights` field to concrete flags, or `null` when disabled. */
2323
+ declare function resolveInsightOptions(insights: boolean | InsightOptions | undefined): Required<InsightOptions> | null;
2324
+ /**
2325
+ * Expand a chart's `insights` option into `point` annotations for its notable
2326
+ * data points. Returns `[]` when insights are disabled, the chart has no
2327
+ * summarizable trend, or it splits into multiple series.
2328
+ */
2329
+ declare function autoInsightAnnotations(spec: ChartSpec): Annotation[];
2330
+
2331
+ /**
2332
+ * Pure linear-regression helpers — the analytical core behind the `trendline`
2333
+ * overlay. Dependency-free and deterministic so the same fit is computed in the
2334
+ * browser and headless. Mirrors the covariance math used by `pearson()` in
2335
+ * `./insights`, exposed here as a reusable line-of-best-fit.
2336
+ */
2337
+ interface RegressionFit {
2338
+ /** Slope (dy/dx) of the fitted line. */
2339
+ slope: number;
2340
+ /** Y-intercept of the fitted line. */
2341
+ intercept: number;
2342
+ /** Coefficient of determination (R²), in [0, 1]. */
2343
+ r2: number;
2344
+ /** Number of finite (x, y) pairs the fit used. */
2345
+ n: number;
2346
+ /** Predict y for a given x along the fitted line. */
2347
+ predict(x: number): number;
2348
+ }
2349
+ /**
2350
+ * Ordinary-least-squares linear fit over paired numeric samples.
2351
+ *
2352
+ * Returns `null` when there are fewer than two points or the x values have no
2353
+ * spread (a vertical fit has an undefined slope). Non-finite pairs are ignored.
2354
+ */
2355
+ declare function linearRegression(xs: readonly number[], ys: readonly number[]): RegressionFit | null;
2356
+
1506
2357
  /** Environment helpers — safe in both browser and Node (SSR/test). */
1507
2358
  declare const isBrowser: boolean;
1508
2359
  /** Current device pixel ratio (defaults to 1 outside the browser). */
@@ -1535,10 +2386,88 @@ declare function createDiv(className?: string, style?: Partial<CSSStyleDeclarati
1535
2386
  /** Absolute-position a layer to fill its positioned parent. */
1536
2387
  declare const fillParentStyle: Partial<CSSStyleDeclaration>;
1537
2388
 
2389
+ /**
2390
+ * Canvas painting for chart "overlay" text (axis labels, titles, legends,
2391
+ * annotation labels). In the browser this text lives in a crisp, selectable
2392
+ * HTML overlay; headless (no DOM) there is no overlay, so the very same text is
2393
+ * painted onto the marks canvas with these helpers.
2394
+ *
2395
+ * The geometry is shared: call sites compute one set of positions and either
2396
+ * realise them as absolutely-positioned DOM nodes (browser) or feed them here
2397
+ * (headless). The transform/width vocabulary the DOM path uses
2398
+ * (`translateX(-50%)`, `translateY(-50%)`, `translate(-50%,-50%) rotate(±90deg)`,
2399
+ * box `width` + `align`) maps deterministically onto canvas
2400
+ * `textAlign`/`textBaseline`/rotation, so both paths land the same pixels.
2401
+ */
2402
+ interface CanvasPillStyle {
2403
+ background: string;
2404
+ border?: string;
2405
+ radius?: number;
2406
+ padX?: number;
2407
+ padY?: number;
2408
+ }
2409
+ interface CanvasTextCmd {
2410
+ /** Anchor x (interpreted per `align`). */
2411
+ x: number;
2412
+ /** Anchor y (interpreted per `baseline`). */
2413
+ y: number;
2414
+ text: string;
2415
+ /** CSS font shorthand (authoritative — already encodes size + weight). */
2416
+ font: string;
2417
+ color: string;
2418
+ /** Font pixel size, used to size the optional pill. */
2419
+ size: number;
2420
+ align?: CanvasTextAlign;
2421
+ baseline?: CanvasTextBaseline;
2422
+ /** Rotation about (x, y), in radians. */
2423
+ rotate?: number;
2424
+ opacity?: number;
2425
+ /** Draw a rounded "pill"/badge behind the text (solid fill + hairline border). */
2426
+ pill?: CanvasPillStyle;
2427
+ }
2428
+ /** Paint a single text command onto a 2D context. Self-contained (save/restore). */
2429
+ declare function paintCanvasText(ctx: CanvasRenderingContext2D, c: CanvasTextCmd): void;
2430
+ /** Option shape shared by the DOM text helpers (axes + chrome). */
2431
+ interface OverlayTextOpts {
2432
+ left: number;
2433
+ top: number;
2434
+ width?: number;
2435
+ text: string;
2436
+ color: string;
2437
+ size: number;
2438
+ align?: 'left' | 'center' | 'right';
2439
+ transform?: string;
2440
+ opacity?: number;
2441
+ pill?: CanvasPillStyle;
2442
+ }
2443
+ /**
2444
+ * Translate an absolutely-positioned overlay text node (left/top/width/align +
2445
+ * a CSS `transform` from the known vocabulary) into the equivalent canvas
2446
+ * command. Keeps the headless output pixel-aligned with the browser overlay.
2447
+ */
2448
+ declare function overlayTextToCanvasCmd(o: OverlayTextOpts, font: string): CanvasTextCmd;
2449
+ type LegendSymbol = 'square' | 'circle' | 'line';
2450
+ /** Total horizontal footprint of a legend swatch (square/circle = 11, line = 14). */
2451
+ declare function legendSwatchWidth(symbol: LegendSymbol | undefined, swatch?: number): number;
2452
+ /**
2453
+ * Paint a legend swatch onto canvas, vertically centered on `midY`. Mirrors the
2454
+ * DOM swatch (square/circle 11px, line 14×3px).
2455
+ */
2456
+ declare function paintLegendSwatch(ctx: CanvasRenderingContext2D, x: number, midY: number, symbol: LegendSymbol | undefined, color: string, swatch?: number): void;
2457
+
1538
2458
  /**
1539
2459
  * Text measurement using a shared offscreen canvas context. Falls back to a
1540
- * rough heuristic when no canvas is available (SSR/test).
2460
+ * rough heuristic when no canvas is available (SSR/test). In a non-DOM
2461
+ * environment (e.g. `@graphein/node`) a real 2D context can be injected via
2462
+ * {@link setMeasureContext} so layout uses true font metrics.
1541
2463
  */
2464
+ /**
2465
+ * Provide a 2D context used to measure text advance widths when no DOM is
2466
+ * available (headless rendering). Pass `null` to clear and fall back to the
2467
+ * DOM canvas / heuristic. The context's `font` is overwritten on each measure,
2468
+ * so use a dedicated measurement context rather than your drawing context.
2469
+ */
2470
+ declare function setMeasureContext(ctx: CanvasRenderingContext2D | null): void;
1542
2471
  interface TextMetricsLite {
1543
2472
  width: number;
1544
2473
  }
@@ -1685,6 +2614,12 @@ interface DashboardSpec {
1685
2614
  */
1686
2615
  interactions?: 'auto' | 'none' | InteractionLink[];
1687
2616
  }
2617
+ /**
2618
+ * Any top-level Graphein spec: a chart, a slicer, or a dashboard. This is the
2619
+ * root type the JSON Schema (`docs/chart-spec.schema.json`) is generated from,
2620
+ * and the broadest type `validateSpec` accepts.
2621
+ */
2622
+ type AnySpec = ChartSpec | DashboardSpec;
1688
2623
 
1689
2624
  /** Infer the channel type of a single value. */
1690
2625
  declare function inferValueType(value: unknown): FieldType | undefined;
@@ -1696,10 +2631,65 @@ declare function inferFieldType(data: Datum[], field: string, sample?: number):
1696
2631
  /** Infer types for every field present in the first rows of the dataset. */
1697
2632
  declare function inferFieldTypes(data: Datum[]): Record<string, FieldType>;
1698
2633
 
2634
+ /**
2635
+ * Minimal, apply-only JSON Patch (RFC 6902) used by `repairSpec` to apply the
2636
+ * safe `fix` operations that `validateSpec` attaches to errors. Pure: returns a
2637
+ * new document, never mutates the input. Preserves `Date` values (specs may hold
2638
+ * `Date` objects for temporal fields), unlike a JSON round-trip clone.
2639
+ */
2640
+ type JsonPatchOp = {
2641
+ op: 'add';
2642
+ path: string;
2643
+ value: unknown;
2644
+ } | {
2645
+ op: 'replace';
2646
+ path: string;
2647
+ value: unknown;
2648
+ } | {
2649
+ op: 'remove';
2650
+ path: string;
2651
+ };
2652
+ /**
2653
+ * Convert a dotted/bracketed validation path (`encoding.x.type`,
2654
+ * `transform[0].calculate`, `values[2].op`) into a JSON Pointer
2655
+ * (`/encoding/x/type`, `/transform/0/calculate`, `/values/2/op`). Authoring
2656
+ * fixes from the same `path` an error already reports keeps the two in sync.
2657
+ */
2658
+ declare function toPointer(path: string): string;
2659
+ /** Apply an ordered list of patch ops to a deep clone of `doc`. */
2660
+ declare function applyPatch<T>(doc: T, ops: readonly JsonPatchOp[]): T;
2661
+
1699
2662
  interface ValidationError {
1700
2663
  /** JSON-path-ish location, e.g. "encoding.x.field". */
1701
2664
  path: string;
1702
2665
  message: string;
2666
+ /**
2667
+ * Stable rule id for lint findings (e.g. "pie-too-many-slices"), so agents can
2668
+ * recognize, suppress, or learn from a specific best-practice warning. Absent
2669
+ * on structural validation errors.
2670
+ */
2671
+ rule?: string;
2672
+ /**
2673
+ * Severity of a lint finding. Structural problems are reported via `errors`
2674
+ * (implicitly 'error'); lint findings in `warnings` are 'warning' or 'info'.
2675
+ */
2676
+ severity?: 'error' | 'warning' | 'info';
2677
+ /**
2678
+ * Safe, unambiguous JSON Patch (RFC 6902) operations that resolve this problem
2679
+ * — present only when a correction is clear (e.g. a misspelled enum or chart
2680
+ * type, or a temporal-looking field typed as a category). An agent can apply
2681
+ * these directly instead of regenerating; {@link repairSpec} applies them for
2682
+ * you. Absent when the right fix is ambiguous.
2683
+ */
2684
+ fix?: JsonPatchOp[];
2685
+ /**
2686
+ * "Did you mean" candidates, nearest first, for an unrecognized value. `kind`
2687
+ * names what is being suggested so an agent can react appropriately.
2688
+ */
2689
+ suggestion?: {
2690
+ kind: 'chartType' | 'enum' | 'field' | 'channel';
2691
+ candidates: string[];
2692
+ };
1703
2693
  }
1704
2694
  interface ValidationResult {
1705
2695
  valid: boolean;
@@ -1720,6 +2710,57 @@ declare function validateSpec(spec: unknown): ValidationResult;
1720
2710
  declare function validateDashboard(spec: Record<string, unknown>): ValidationResult;
1721
2711
  declare function assertValidSpec(spec: unknown): ChartSpec;
1722
2712
 
2713
+ /**
2714
+ * Dataviz linter — best-practice rules that encode the visualization expertise an
2715
+ * agent may lack. Each rule is pure and returns findings with a stable `rule` id
2716
+ * and a `severity` ('warning' | 'info'), so an agent can recognize, suppress, or
2717
+ * learn from a specific issue. Findings are surfaced through `validateSpec`'s
2718
+ * `warnings`, and directly via {@link lintSpec}.
2719
+ *
2720
+ * Rules lint the *effective* data (after `transform`), so cardinality reflects
2721
+ * what actually renders (e.g. pie slices after an `aggregate`). The linter never
2722
+ * throws and never blocks rendering — it is advisory only.
2723
+ */
2724
+
2725
+ /** A lint finding always carries a `rule` id and a non-error `severity`. */
2726
+ interface LintFinding extends ValidationError {
2727
+ rule: string;
2728
+ severity: 'warning' | 'info';
2729
+ }
2730
+ /**
2731
+ * Lint a (structurally valid) chart spec for best-practice issues. Returns an
2732
+ * empty array for slicers/dashboards or when no rule fires. Pure and total.
2733
+ */
2734
+ declare function lintSpec(spec: ChartSpec): LintFinding[];
2735
+
2736
+ /**
2737
+ * Self-repairing validation. {@link repairSpec} applies the safe, unambiguous
2738
+ * JSON Patch fixes that {@link validateSpec} attaches to its findings, then
2739
+ * re-validates — turning common agent mistakes (a misspelled chart type or enum,
2740
+ * a temporal field typed as a category) into a one-step correction instead of a
2741
+ * full regenerate. Only fixes the validator deems unambiguous are applied; when
2742
+ * the right correction is unclear, the error is left for the agent to resolve.
2743
+ */
2744
+
2745
+ interface RepairResult {
2746
+ /** The (possibly) corrected spec — a new object; the input is never mutated. */
2747
+ spec: unknown;
2748
+ /** Patch operations that were applied, in order. Empty when nothing changed. */
2749
+ applied: JsonPatchOp[];
2750
+ /**
2751
+ * Structural errors that remain after repair. `remaining.length === 0` means
2752
+ * the repaired spec is valid. Advisory lint warnings are not counted here.
2753
+ */
2754
+ remaining: ValidationError[];
2755
+ }
2756
+ /**
2757
+ * Apply every safe auto-fix `validateSpec` proposes, iterating until the spec is
2758
+ * valid or no further progress can be made (a fix may unlock another — e.g.
2759
+ * correcting the chart `type` changes which channels are required). Pure: the
2760
+ * input spec is deep-cloned before any patch is applied.
2761
+ */
2762
+ declare function repairSpec(spec: unknown): RepairResult;
2763
+
1723
2764
  /**
1724
2765
  * Resolve a spec's `sketch` option into concrete, fully-defaulted knobs for the
1725
2766
  * rough engine. Returns `null` when sketching is off, which lets every chart
@@ -1840,6 +2881,9 @@ interface FrameInput {
1840
2881
  height: number;
1841
2882
  padding: Insets;
1842
2883
  font: ThemeFont;
2884
+ /** Optional origin offset — lay the frame out within a sub-region (e.g. a facet cell). */
2885
+ originX?: number;
2886
+ originY?: number;
1843
2887
  title?: TitleInput;
1844
2888
  legend?: {
1845
2889
  items: LegendItem[];
@@ -1848,10 +2892,15 @@ interface FrameInput {
1848
2892
  /** Cartesian charts pass both axes; non-cartesian omit them. */
1849
2893
  xAxis?: AxisInput;
1850
2894
  yAxis?: AxisInput;
2895
+ /** Secondary (right) y-axis for dual-axis combo charts; reserves a right gutter. */
2896
+ y2Axis?: AxisInput;
1851
2897
  }
1852
2898
  interface Frame {
1853
2899
  width: number;
1854
2900
  height: number;
2901
+ /** Absolute origin offset of this frame within the surface (0 unless faceted). */
2902
+ originX: number;
2903
+ originY: number;
1855
2904
  plot: Rect;
1856
2905
  titleRect?: Rect;
1857
2906
  subtitleRect?: Rect;
@@ -2007,6 +3056,22 @@ declare function resolveCurve(curve?: CurveType): Curve;
2007
3056
  interface BuildOptions {
2008
3057
  width: number;
2009
3058
  height: number;
3059
+ /** Origin offset, so the model lays out within a sub-region (a facet cell). */
3060
+ originX?: number;
3061
+ originY?: number;
3062
+ /** Shared scale domains/colors, so faceted panels stay directly comparable. */
3063
+ shared?: SharedScales;
3064
+ }
3065
+ /** Scale state shared across a facet grid so every panel is comparable. */
3066
+ interface SharedScales {
3067
+ /** Band/point category domain (the full ordered category list). */
3068
+ categories?: string[];
3069
+ /** Continuous (linear) or temporal (epoch ms) x-domain. */
3070
+ xDomain?: [number, number];
3071
+ /** Linear y-domain (already accounting for any zero baseline). */
3072
+ yDomain?: [number, number];
3073
+ /** Series-key → color, so the same series keeps its color in every panel. */
3074
+ colorOf?: (key: string) => string;
2010
3075
  }
2011
3076
  declare function buildCartesianModel(spec: CartesianChartSpec, tokens: ThemeTokens, opts: BuildOptions): CartesianModel;
2012
3077
 
@@ -2023,6 +3088,8 @@ declare function buildCartesianModel(spec: CartesianChartSpec, tokens: ThemeToke
2023
3088
  declare function drawAxesUnderlay(surface: Surface, model: CartesianModel): void;
2024
3089
  /** Draw all overlay text: tick labels, axis titles, chart title, legend. */
2025
3090
  declare function drawOverlay(surface: Surface, model: CartesianModel): void;
3091
+ declare function drawTitle(surface: Surface, frame: Frame, spec: CartesianModel['spec'], tokens: ThemeTokens): void;
3092
+ declare function drawLegend(surface: Surface, items: PositionedLegendItem[], model: CartesianModel): void;
2026
3093
 
2027
3094
  /**
2028
3095
  * SelectionStore — the dependency-free bus that links interactive visuals.
@@ -2054,6 +3121,51 @@ interface SelectionStore {
2054
3121
  }
2055
3122
  declare function createSelectionStore(initial?: Record<string, SelectionValue | null>): SelectionStore;
2056
3123
 
3124
+ /**
3125
+ * Reference annotations for cartesian charts — lines, bands, and threshold zones.
3126
+ *
3127
+ * A cross-cutting overlay primitive available to every cartesian chart via
3128
+ * `spec.annotations`. Marks (the rule strokes and band fills) are painted on the
3129
+ * marks canvas; labels live in the HTML overlay for crisp, selectable text,
3130
+ * mirroring how axes render. Geometry comes from the already-resolved
3131
+ * `CartesianModel` scales, so this module is pure presentation.
3132
+ */
3133
+
3134
+ /**
3135
+ * Paint reference lines and band/zone fills on the marks canvas, clipped to the
3136
+ * plot. Call after the chart's data marks so reference lines sit on top.
3137
+ */
3138
+ declare function drawAnnotations(surface: Surface, model: CartesianModel): void;
3139
+ /**
3140
+ * Append annotation labels to the HTML overlay. Call after `drawOverlay` so the
3141
+ * labels layer on top of axis text. No-op when there are no labeled annotations.
3142
+ */
3143
+ declare function drawAnnotationLabels(surface: Surface, model: CartesianModel): void;
3144
+
3145
+ /**
3146
+ * Derived trendlines for cartesian charts — a linear line of best fit overlaid
3147
+ * on a scatter, line, or area plot via `spec.trendline`.
3148
+ *
3149
+ * The regression is computed from the already-resolved `CartesianModel` series
3150
+ * (one fit per group, or a single overall fit), and the fitted line is stroked
3151
+ * across each group's x-extent. Marks land on the marks canvas; optional `R²`
3152
+ * labels live in the HTML overlay (or are painted to canvas when headless),
3153
+ * mirroring how `annotations` render. Pure presentation — no data reshaping.
3154
+ */
3155
+
3156
+ /**
3157
+ * Stroke the fitted line(s) on the marks canvas, clipped to the plot. Call after
3158
+ * the chart's data marks so the trendline sits on top. No-op unless the spec
3159
+ * opts in via `trendline` and the x-axis is continuous/temporal.
3160
+ */
3161
+ declare function drawTrendlines(surface: Surface, model: CartesianModel): void;
3162
+ /**
3163
+ * Append `R²` labels for each fitted line to the HTML overlay (or paint them to
3164
+ * canvas when headless). Call after `drawAnnotationLabels`. No-op unless
3165
+ * `trendline.label` is set.
3166
+ */
3167
+ declare function drawTrendlineLabels(surface: Surface, model: CartesianModel): void;
3168
+
2057
3169
  /**
2058
3170
  * Chart registry.
2059
3171
  *
@@ -2248,6 +3360,257 @@ declare function resolveFilterValues(filter: FilterClause[] | undefined, store:
2248
3360
  /** The set of param names a spec reacts to (for change-driven redraws). */
2249
3361
  declare function dependentParams(highlight: HighlightConfig | HighlightConfig[] | undefined, filter: FilterClause[] | undefined, ownParams?: readonly string[]): Set<string>;
2250
3362
 
3363
+ /**
3364
+ * Combo / dual-axis model builder.
3365
+ *
3366
+ * A `combo` spec composes several cartesian layers (bar, line, area, scatter) over
3367
+ * a shared x axis, optionally split across a primary (left) and secondary (right)
3368
+ * y axis. This builder resolves one shared frame / plot / x-scale and, for every
3369
+ * layer, a fully-formed {@link CartesianModel} that the existing mark renderers
3370
+ * (`drawLine`, `drawBar`, …) consume unchanged. Two independent y-scales back the
3371
+ * left and right axes; bar layers are offset side-by-side so multiple bars group.
3372
+ *
3373
+ * The builder is deliberately self-contained (it reuses only the public scale /
3374
+ * tick / frame / color primitives) so the single-chart `buildCartesianModel` path
3375
+ * stays untouched.
3376
+ */
3377
+
3378
+ type ComboSide = 'left' | 'right';
3379
+ interface ComboLayerModel {
3380
+ mark: ComboMark;
3381
+ side: ComboSide;
3382
+ color: string;
3383
+ name: string;
3384
+ /** A ready-to-draw cartesian model for this layer's mark renderer. */
3385
+ model: CartesianModel;
3386
+ }
3387
+ interface ComboAxisModel {
3388
+ show: boolean;
3389
+ ticks: Tick[];
3390
+ title?: string;
3391
+ }
3392
+ interface ComboModel {
3393
+ spec: ComboSpec;
3394
+ tokens: ThemeTokens;
3395
+ frame: Frame;
3396
+ plot: Rect;
3397
+ x: XModel;
3398
+ xTicks: Tick[];
3399
+ left: ComboAxisModel;
3400
+ right?: ComboAxisModel;
3401
+ layers: ComboLayerModel[];
3402
+ legendItems: LegendItem[];
3403
+ /** Primary-axis cartesian model reused for the axis underlay / overlay chrome. */
3404
+ base: CartesianModel;
3405
+ sketch: ResolvedSketch | null;
3406
+ emphasis?: Emphasis | null;
3407
+ }
3408
+ declare function buildComboModel(spec: ComboSpec, tokens: ThemeTokens, opts: BuildOptions): ComboModel;
3409
+
3410
+ /**
3411
+ * Histogram model builder.
3412
+ *
3413
+ * A `histogram` bins a single quantitative field (reusing the shared `bin`
3414
+ * transform's {@link computeBins}) and draws the per-bin frequency as gapless
3415
+ * bars on a continuous x-axis. Like the combo builder, it resolves a synthetic
3416
+ * `base` {@link CartesianModel} so the existing axis chrome (`drawAxesUnderlay` /
3417
+ * `drawOverlay`) renders the gridlines, ticks, labels and titles unchanged — the
3418
+ * bars themselves are painted by {@link drawHistogram}.
3419
+ */
3420
+
3421
+ /** One resolved histogram bin (closed-open `[start, end)`). */
3422
+ interface HistogramBinDatum {
3423
+ start: number;
3424
+ end: number;
3425
+ mid: number;
3426
+ /** Raw observation count in the bin. */
3427
+ count: number;
3428
+ /** Plotted height: `count`, or a probability density when `density:true`. */
3429
+ value: number;
3430
+ }
3431
+ interface HistogramModel {
3432
+ spec: HistogramSpec;
3433
+ tokens: ThemeTokens;
3434
+ frame: Frame;
3435
+ plot: Rect;
3436
+ bins: HistogramBinDatum[];
3437
+ layout: BinLayout | null;
3438
+ color: string;
3439
+ /** Map a quantitative x value to a pixel. */
3440
+ xPixel: (v: number) => number;
3441
+ /** Map a bar height (count/density) to a pixel. */
3442
+ yPixel: (v: number) => number;
3443
+ /** Pixel of the y=0 baseline. */
3444
+ yBaseline: number;
3445
+ cornerRadius: number;
3446
+ /** Drives the shared axis underlay / overlay chrome. */
3447
+ base: CartesianModel;
3448
+ sketch: ResolvedSketch | null;
3449
+ }
3450
+ declare function buildHistogramModel(spec: HistogramSpec, tokens: ThemeTokens, opts: BuildOptions): HistogramModel;
3451
+
3452
+ /**
3453
+ * Render report / introspection API.
3454
+ *
3455
+ * After a chart draws, `buildRenderReport` derives a machine-readable set of
3456
+ * diagnostics from the *resolved* model (scales, plot rect, ticks, legend,
3457
+ * theme colors) — no pixels are read back. This lets an agent verify a chart
3458
+ * "looks right" without vision: it can see the mark count, whether axis labels
3459
+ * collide, whether the legend was truncated, whether marks fall outside the
3460
+ * plot, and whether colors clear contrast thresholds.
3461
+ *
3462
+ * The builder is pure and dependency-free so it runs identically in the browser
3463
+ * and headless (the server-side critique loop consumes the same report).
3464
+ */
3465
+
3466
+ type ReportSeverity = 'error' | 'warning' | 'info';
3467
+ /** One machine-readable finding about the rendered chart. */
3468
+ interface RenderDiagnostic {
3469
+ /** Stable identifier an agent can match/suppress on (e.g. `axis-label-overlap`). */
3470
+ code: string;
3471
+ severity: ReportSeverity;
3472
+ /** Human- and agent-readable explanation. */
3473
+ message: string;
3474
+ /** The axis a layout diagnostic refers to, when applicable. */
3475
+ axis?: 'x' | 'y';
3476
+ /** Structured extras (counts, ratios) for programmatic consumers. */
3477
+ details?: Record<string, unknown>;
3478
+ }
3479
+ /** A post-render, vision-free description of what was drawn. */
3480
+ interface RenderReport {
3481
+ type: ChartType;
3482
+ /** Surface size in CSS pixels. */
3483
+ size: Size;
3484
+ /** Plot rectangle (cartesian-derived reports only). */
3485
+ plot?: Rect;
3486
+ /** Number of data marks the chart drew. */
3487
+ markCount: number;
3488
+ /** Number of distinct series. */
3489
+ seriesCount: number;
3490
+ /** Number of distinct colors used for series. */
3491
+ colorCount: number;
3492
+ /** True when no `error` or `warning` diagnostics were raised. */
3493
+ ok: boolean;
3494
+ /** All findings, ordered most-severe first. */
3495
+ diagnostics: RenderDiagnostic[];
3496
+ /**
3497
+ * Deterministic plain-English summary of what the data shows (alt-text without
3498
+ * an LLM). Absent for chart types that carry no summarizable trend.
3499
+ */
3500
+ summary?: string;
3501
+ }
3502
+ /** Inputs for {@link buildRenderReport}. */
3503
+ interface ReportInput {
3504
+ type: ChartType;
3505
+ spec: ChartSpec;
3506
+ /** Effective (post-transform, post-filter) data that was rendered. */
3507
+ data: Datum[];
3508
+ tokens: ThemeTokens;
3509
+ size: Size;
3510
+ /** The resolved cartesian model, when the chart type is cartesian. */
3511
+ model?: CartesianModel;
3512
+ }
3513
+ /**
3514
+ * Build the render report for a freshly-drawn chart. Pure: it reads the
3515
+ * resolved model + theme, never the canvas bitmap.
3516
+ */
3517
+ declare function buildRenderReport(input: ReportInput): RenderReport;
3518
+
3519
+ /**
3520
+ * Faceting / small multiples.
3521
+ *
3522
+ * A faceted chart splits into a trellis grid of panels — one per distinct value
3523
+ * of a field — all sharing identical x/y/color scales so the panels are directly
3524
+ * comparable. Each panel is a full {@link CartesianModel} laid out into its grid
3525
+ * cell (via the frame's origin offset), drawn with the very same cartesian
3526
+ * pipeline (underlay → marks → trendline → annotations → overlay) on a single
3527
+ * canvas. That means faceting works identically headless, preserving the
3528
+ * critique-loop moat.
3529
+ *
3530
+ * The shared scales are derived from a *reference model* built over the full
3531
+ * dataset: its domains and color map ARE the shared state every panel reuses, so
3532
+ * a series keeps its color and position in every panel even when a panel's subset
3533
+ * is missing some categories or series.
3534
+ */
3535
+
3536
+ /** Cartesian chart kinds that can be split into a facet grid. */
3537
+ declare const FACETABLE_TYPES: ReadonlySet<string>;
3538
+ /** One panel of a facet grid: its facet value plus the resolved model. */
3539
+ interface FacetPanel {
3540
+ value: string;
3541
+ model: CartesianModel;
3542
+ }
3543
+ /** The resolved layout of a faceted chart — the header frame plus panel models. */
3544
+ interface FacetLayout {
3545
+ field: string;
3546
+ columns: number;
3547
+ rows: number;
3548
+ /** Outer frame: reserves the overall title + shared legend; its plot is the grid region. */
3549
+ outerFrame: Frame;
3550
+ panels: FacetPanel[];
3551
+ /** Positioned shared legend items (multi-series only). */
3552
+ legendItems?: PositionedLegendItem[];
3553
+ /** A representative panel model (panels[0]) for token/legend draw. */
3554
+ repModel: CartesianModel;
3555
+ }
3556
+ /** True when `spec` declares a usable facet on a facet-eligible cartesian type. */
3557
+ declare function isFaceted(spec: ChartSpec): boolean;
3558
+ /**
3559
+ * Resolve a faceted spec into a grid of panel models sharing one set of scales.
3560
+ * Returns `null` when there is nothing to facet (no rows / no distinct values),
3561
+ * so the caller can fall back to the normal single-chart path.
3562
+ */
3563
+ declare function buildFacetModels(spec: ChartSpec, tokens: ThemeTokens, size: Size): FacetLayout | null;
3564
+ /**
3565
+ * Draw a faceted chart onto `surface`: the shared header (overall title + legend)
3566
+ * then every panel through the full cartesian pipeline. Static — no interaction
3567
+ * in v1. The background is assumed already painted by the caller.
3568
+ */
3569
+ declare function drawFacet(surface: Surface, spec: ChartSpec, layout: FacetLayout, tokens: ThemeTokens): void;
3570
+ /**
3571
+ * Merge per-panel render reports into one report for the faceted chart: mark
3572
+ * counts sum, series/color counts take the panel maximum, diagnostics union by
3573
+ * code (most-severe first), and `ok` holds only if every panel is ok.
3574
+ */
3575
+ declare function facetReport(spec: ChartSpec, layout: FacetLayout, tokens: ThemeTokens, size: Size): RenderReport;
3576
+
3577
+ /**
3578
+ * Headless rendering — paint a chart onto any caller-supplied 2D context
3579
+ * (e.g. `@napi-rs/canvas`, `node-canvas`, an `OffscreenCanvas`) with **no DOM**.
3580
+ *
3581
+ * This is the server-side half of the critique loop: it reuses the exact same
3582
+ * model build + mark renderers as the browser, but routes overlay text through
3583
+ * the canvas text path (see `render/overlayText.ts`) instead of the HTML overlay.
3584
+ * It is dependency-free — the caller owns canvas creation and PNG/SVG encoding,
3585
+ * so `graphein` itself stays zero-dependency. The companion `@graphein/node`
3586
+ * package wires this to `@napi-rs/canvas`.
3587
+ *
3588
+ * Returns the same {@link RenderReport} as `instance.report()`, so an agent can
3589
+ * generate → validate → render → critique entirely on the server.
3590
+ */
3591
+
3592
+ /** A minimal 2D-context target. Pass `interaction` if any renderer needs it. */
3593
+ interface HeadlessTarget {
3594
+ /** The primary 2D context chart marks + text are painted onto. */
3595
+ marks: CanvasRenderingContext2D;
3596
+ /** Optional secondary context for hover layers (unused on the static path). */
3597
+ interaction?: CanvasRenderingContext2D;
3598
+ /** Logical (CSS-pixel) width to draw at. */
3599
+ width: number;
3600
+ /** Logical (CSS-pixel) height to draw at. */
3601
+ height: number;
3602
+ }
3603
+ /**
3604
+ * Paint `spec` onto `target.marks` (in CSS pixels — the caller sets up any
3605
+ * device-pixel-ratio scaling on the context beforehand) and return the render
3606
+ * report. No animation, no interactivity, no DOM.
3607
+ *
3608
+ * Supports every canvas-backed chart: line, area, bar, scatter, box, pie,
3609
+ * heatmap, sankey, choropleth, combo, histogram, funnel. DOM-only kinds
3610
+ * (kpi/table/matrix/slicers/dashboard) are unsupported headlessly.
3611
+ */
3612
+ declare function renderToContext(target: HeadlessTarget, spec: ChartSpec): RenderReport;
3613
+
2251
3614
  /**
2252
3615
  * Chart runtime: the public `render(container, spec)` entry point.
2253
3616
  *
@@ -2284,6 +3647,14 @@ interface ChartInstance {
2284
3647
  readonly spec: ChartSpec;
2285
3648
  /** The mounted surface (advanced/imperative use). */
2286
3649
  readonly surface: Surface;
3650
+ /**
3651
+ * Machine-readable diagnostics from the most recent render: mark count,
3652
+ * clipped axis labels, legend overflow, low-contrast colors, degenerate axes,
3653
+ * and out-of-bounds marks. Lets an agent verify the chart "looks right"
3654
+ * without vision. Computed purely from the resolved model, so it works
3655
+ * identically in the browser and headless.
3656
+ */
3657
+ report(): RenderReport;
2287
3658
  /** The selection bus this chart is bound to (advanced/linking use). */
2288
3659
  readonly store: SelectionStore;
2289
3660
  /** Read a param's current value, or a snapshot of all params when omitted. */
@@ -2389,4 +3760,4 @@ declare function renderDashboard(target: HTMLElement | string, spec: DashboardSp
2389
3760
  */
2390
3761
  declare const VERSION = "0.1.0";
2391
3762
 
2392
- export { type AggOp, type AnimateOptions, type AnimationConfig, type AnimationHandle, type ArcOptions, type AreaOptions, type AreaPoint, type AreaSpec, type AxesConfig, type AxisConfig, type AxisInput, type BackingSize, type BandScale, type BandScaleOptions, type BarSpec, type BaseSlicerSpec, type BaseSpec, type BoxSpec, type BuildOptions, CARTESIAN_TYPES, CHART_TYPES, CanvasLayer, type CartesianChartSpec, type CartesianInteractionBuilder, type CartesianModel, type CartesianRenderer, type ChartInstance, type ChartSpec, type ChartSummary, type ChartType, type ChoroplethSpec, type ColorScale, type Comparable, type ConditionalFormat, type ContinuousScale, type ControllerSelect, type Curve, type CurveType, type CustomRenderer, DEFAULT_ENTRANCE_DURATION, DEFAULT_ROUGH_STYLE, DEFAULT_UPDATE_DURATION, DIM_ALPHA, type DashboardInstance, type DashboardLayout, type DashboardResponsiveSpan, type DashboardSection, type DashboardSpec, type DashboardView, type DateRangeSlicerSpec, type Datum, type DecimateOptions, type Dimensions, type DropdownSlicerSpec, type EasingFunction, type Emphasis, type Encoding, type EntranceContext, type Extent, type FieldDef, type FieldType, type FieldValue, type FillStyle, type FilterClause, type Frame, type FrameInput, type FunnelSpec, type GeoFeature, type GeoFeatureCollection, type GeoGeometry, type GeoMultiPolygon, type GeoPolygon, type GeoPosition, type GroupedMap, type HachureSegment, type HeatmapSpec, type HighlightConfig, type Hover, type IconRule, type Insets, InteractionController, type InteractionLink, type InteractionModel, type Interpolator, type KpiSpec, type LegendConfig, type LegendItem, type LegendPosition, type LineOptions, type LineSpec, type LinearScaleOptions, type ListSlicerSpec, type LiteralPredicate, type LogScaleOptions, type MapProjection, type MarkOptions, type MatrixSpec, type MatrixValueDef, type NumberFormatSpec, type OKLCH, type OKLab, type PathSink, type PieLabels, type PieSpec, type PivotCell, type PivotFlatRow, type PivotHeaderNode, type PivotOptions, type PivotResult, type PivotValueDef, type Point, type PointScale, type PointScaleOptions, type PointSelection, type PositionedLegendItem, type RGBA, type RangeSelection, type RangeSlicerSpec, type Rect, type RenderContext, type RenderDashboardOptions, type RenderOptions, type ResolvedEntrance, type ResolvedSeries, type ResolvedSketch, type Rng, type RoughContext, RoughPen, type RoughStyle, SKETCH_FONT_FAMILY, SKETCH_FONT_NAME, SLICER_TYPES, SR_ONLY_STYLE, type SankeySpec, type ScaleConfig, type ScaleType, type ScatterSpec, type SearchSlicerSpec, type SelectConfig, type SelectionChangeListener, type SelectionDef, type SelectionListener, type SelectionParam, type SelectionStore, type SelectionValue, type SetSelection, type Size, type SketchConfig, type SlicerSpec, type SlicerType, Surface, TICK_SIZE, type TableColumn, type TableSpec, type TextMetricsLite, type TextSelection, type ThemeColors, type ThemeFont, type ThemeInput, type ThemeTokens, type Tick, type TimeScaleOptions, type TitleConfig, type TitleInput, Tooltip, type TooltipConfig, type TooltipContent, type TooltipRow, type UpdateContext, VERSION, type ValidationError, type ValidationResult, type ValueRef, type ValueRule, type XKind, type XModel, type YModel, aggregateValues, animate, applyA11y, applyPick, arc, area, assertValidSpec, backOut, bandScale, bounceOut, buildCartesianInteraction, buildCartesianModel, buildDataTableFallback, cartesianInteractionBuilders, cartesianRenderers, categorical, chartTitleText, chartTypeLabel, clamp01, clamp255, computeBackingSize, computeFrame, contrastRatio, createDiv, createRoughPen, createSelectionStore, cubicIn, cubicInOut, cubicOut, curveCatmullRom, curveLinear, curveMonotoneX, curveStep, curveStepAfter, curveStepBefore, customRenderers, darkTheme, decimate, dependentParams, diverging, divergingColorScale, divergingSchemes, drawAxesUnderlay, drawOverlay, easings, elasticOut, ensureSketchFont, expoInOut, expoOut, fillParentStyle, filterRows, fontString, formatDate, formatNumber, formatValue, getDevicePixelRatio, groupBy, hashString, inferFieldType, inferFieldTypes, inferValueType, interpolateArray, interpolateNumber, interpolateNumberArray, interpolateObject, interpolateOklab, interpolateRgb, isBrowser, isEmptyValue, isParamClause, isSlicerType, keyFields, lightTheme, line, linear, linearScale, linearToSrgb, literalToValue, logScale, lttbRun, makeMatcher, matchesValue, measureText, monotoneXTangents, mulberry32, niceDomain, oklabToOklch, oklabToRgb, oklchToOklab, ordinalColorScale, parseColor, parseNumberFormat, pivot, pointScale, polygonHachureLines, prefersReducedMotion, prettyDate, quadIn, quadInOut, quadOut, rampFromStops, readableTextColor, relativeLuminance, render, renderDashboard, resolveCurve, resolveDashboardLayout, resolveDashboardSections, resolveEmphasis, resolveEntrance, resolveFilterValues, resolveSketch, resolveTheme, resolveUpdate, rgbToOklab, rgbaToCss, rotatePoint, roundedRect, sampleArc, sequential, sequentialColorScale, sequentialSchemes, setStyle, sinInOut, slicerParamName, smartDate, specFields, srgbToLinear, summarizeChart, themes, tickIncrement, tickStep, ticks, timeScale, timeTickFormat, timeTicks, toHex, tooltipEnabled, validateDashboard, validateSpec, wireViews, withAlpha, withSketchFont };
3763
+ export { type AggOp, type AggregateOp, type AggregateTransform, type AnimateOptions, type AnimationConfig, type AnimationHandle, type Annotation, type AnySpec, type ArcOptions, type AreaOptions, type AreaPoint, type AreaSpec, type AxesConfig, type AxisConfig, type AxisInput, type BackingSize, type BandScale, type BandScaleOptions, type BarSpec, type BaseSlicerSpec, type BaseSpec, type BinLayout, type BinTransform, type BoxSpec, type BuildOptions, type BulletSpec, CARTESIAN_TYPES, CHART_TYPES, type CalculateTransform, type CalendarHeatmapSpec, CanvasLayer, type CanvasPillStyle, type CanvasTextCmd, type CartesianChartSpec, type CartesianInteractionBuilder, type CartesianModel, type CartesianRenderer, type CategoryInsights, type ChartInsights, type ChartInstance, type ChartSpec, type ChartSummary, type ChartType, type ChoroplethSpec, type ColorScale, type ComboAxisModel, type ComboLayer, type ComboLayerModel, type ComboMark, type ComboModel, type ComboSide, type ComboSpec, type Comparable, type ConditionalFormat, type ContinuousScale, type ControllerSelect, type Curve, type CurveType, type CustomRenderer, DEFAULT_ENTRANCE_DURATION, DEFAULT_ROUGH_STYLE, DEFAULT_UPDATE_DURATION, DIM_ALPHA, type DashboardInstance, type DashboardLayout, type DashboardResponsiveSpan, type DashboardSection, type DashboardSpec, type DashboardView, type DateRangeSlicerSpec, type Datum, type DecimateOptions, type Dimensions, type DropdownSlicerSpec, type DumbbellSpec, type EasingFunction, type Emphasis, type Encoding, type EntranceContext, ExpressionError, type Extent, FACETABLE_TYPES, FUNCTION_NAMES, type FacetConfig, type FacetLayout, type FacetPanel, type FieldDef, type FieldType, type FieldValue, type FillStyle, type FilterClause, type FilterPredicate, type FilterTransform, type FoldTransform, type Frame, type FrameInput, type FunnelSpec, type GaugeSpec, type GeoFeature, type GeoFeatureCollection, type GeoGeometry, type GeoMultiPolygon, type GeoPolygon, type GeoPosition, type GroupedMap, type HachureSegment, type HeadlessTarget, type HeatmapSpec, type HighlightConfig, type HistogramBin, type HistogramBinDatum, type HistogramModel, type HistogramSpec, type Hover, type IconRule, type Insets, type InsightFamily, type InsightOptions, InteractionController, type InteractionLink, type InteractionModel, type Interpolator, type JsonPatchOp, type KpiSpec, type LegendConfig, type LegendItem, type LegendPosition, type LegendSymbol, type LineOptions, type LineSpec, type LinearScaleOptions, type LintFinding, type ListSlicerSpec, type LiteralPredicate, type LogScaleOptions, type MapProjection, type MarkOptions, type MatrixSpec, type MatrixValueDef, type NumberFormatSpec, type OKLCH, type OKLab, type OverlayTextOpts, type PathSink, type PieLabels, type PieSpec, type PivotCell, type PivotFlatRow, type PivotHeaderNode, type PivotOptions, type PivotResult, type PivotValueDef, type Point, type PointRef, type PointScale, type PointScaleOptions, type PointSelection, type PositionedLegendItem, type RGBA, type RangeSelection, type RangeSlicerSpec, type Rect, type RegressionFit, type RenderContext, type RenderDashboardOptions, type RenderDiagnostic, type RenderOptions, type RenderReport, type RepairResult, type ReportInput, type ReportSeverity, type ResolvedEntrance, type ResolvedSeries, type ResolvedSketch, type Rng, type RoughContext, RoughPen, type RoughStyle, SKETCH_FONT_FAMILY, SKETCH_FONT_NAME, SLICER_TYPES, SR_ONLY_STYLE, type SankeySpec, type ScaleConfig, type ScaleType, type ScatterInsights, type ScatterSpec, type SearchSlicerSpec, type SelectConfig, type SelectionChangeListener, type SelectionDef, type SelectionListener, type SelectionParam, type SelectionStore, type SelectionValue, type SeriesInsights, type SetSelection, type SharedScales, type Size, type SketchConfig, type SlicerSpec, type SlicerType, type SlopeSpec, Surface, TICK_SIZE, TIME_UNITS, TRANSFORM_KINDS, type TableColumn, type TableSpec, type TextMetricsLite, type TextSelection, type ThemeColors, type ThemeFont, type ThemeInput, type ThemeTokens, type Tick, type TimeScaleOptions, type TimeUnit, type TimeUnitTransform, type TitleConfig, type TitleInput, Tooltip, type TooltipConfig, type TooltipContent, type TooltipRow, type Transform, type TreemapSpec, type TrendlineConfig, type UpdateContext, VERSION, type ValidationError, type ValidationResult, type ValueInsights, type ValueRef, type ValueRule, type WaterfallSpec, type XKind, type XModel, type YModel, aggregateValues, analyzeChart, animate, applyA11y, applyAggregate, applyBin, applyCalculate, applyFilter, applyFold, applyPatch, applyPick, applyTimeUnit, applyTransform, applyTransforms, arc, area, assertValidSpec, autoInsightAnnotations, backOut, bandScale, bounceOut, buildCartesianInteraction, buildCartesianModel, buildComboModel, buildDataTableFallback, buildFacetModels, buildHistogramModel, buildRenderReport, cartesianInteractionBuilders, cartesianRenderers, categorical, chartTitleText, chartTypeLabel, checkExpression, clamp01, clamp255, compileExpression, compilePredicate, computeBackingSize, computeBins, computeFrame, computeSeriesInsights, contrastRatio, createDiv, createRoughPen, createSelectionStore, cubicIn, cubicInOut, cubicOut, curveCatmullRom, curveLinear, curveMonotoneX, curveStep, curveStepAfter, curveStepBefore, customRenderers, darkTheme, decimate, dependentParams, diverging, divergingColorScale, divergingSchemes, drawAnnotationLabels, drawAnnotations, drawAxesUnderlay, drawFacet, drawLegend, drawOverlay, drawTitle, drawTrendlineLabels, drawTrendlines, easings, elasticOut, ensureSketchFont, expoInOut, expoOut, facetReport, fillParentStyle, filterRows, fontString, formatDate, formatNumber, formatValue, getDevicePixelRatio, groupBy, hashString, inferFieldType, inferFieldTypes, inferValueType, interpolateArray, interpolateNumber, interpolateNumberArray, interpolateObject, interpolateOklab, interpolateRgb, isBrowser, isEmptyValue, isFaceted, isParamClause, isSlicerType, keyFields, legendSwatchWidth, lightTheme, line, linear, linearRegression, linearScale, linearToSrgb, lintSpec, literalToValue, logScale, lttbRun, makeMatcher, matchesValue, measureText, monotoneXTangents, mulberry32, niceDomain, oklabToOklch, oklabToRgb, oklchToOklab, ordinalColorScale, overlayTextToCanvasCmd, paintCanvasText, paintLegendSwatch, parseColor, parseNumberFormat, pivot, pointScale, polygonHachureLines, prefersReducedMotion, prettyDate, quadIn, quadInOut, quadOut, rampFromStops, readableTextColor, relativeLuminance, render, renderDashboard, renderToContext, repairSpec, resolveCurve, resolveDashboardLayout, resolveDashboardSections, resolveEmphasis, resolveEntrance, resolveFilterValues, resolveInsightOptions, resolveSketch, resolveTheme, resolveUpdate, rgbToOklab, rgbaToCss, rotatePoint, roundedRect, sampleArc, sequential, sequentialColorScale, sequentialSchemes, setMeasureContext, setStyle, sinInOut, slicerParamName, smartDate, specFields, srgbToLinear, summarize, summarizeChart, themes, tickIncrement, tickStep, ticks, timeScale, timeTickFormat, timeTicks, toHex, toPointer, tooltipEnabled, truncateTo, validateDashboard, validateSpec, wireViews, withAlpha, withSketchFont };