ggaction 0.0.22 → 0.0.24

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +82 -90
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/package.json +6 -3
  8. package/src/actions/charts/heatmap.js +1 -1
  9. package/src/actions/charts/interval-regression.js +2 -2
  10. package/src/actions/data/basicBin2d.js +1 -1
  11. package/src/actions/data/bin2d.js +2 -2
  12. package/src/actions/data/edit.js +1 -1
  13. package/src/actions/data/timeUnit.js +1 -1
  14. package/src/actions/encodings/ranged.js +2 -2
  15. package/src/actions/facets/actions.js +1 -1
  16. package/src/actions/guides/axes/components.js +5 -0
  17. package/src/actions/guides/axes/edit.js +1 -188
  18. package/src/actions/guides/axes/labels.js +1 -537
  19. package/src/actions/guides/axes/tickGroups.js +16 -1
  20. package/src/actions/guides/axes/ticks.js +33 -24
  21. package/src/actions/guides/axes/titles.js +1 -389
  22. package/src/actions/guides/legends/categorical/components.js +1 -4
  23. package/src/actions/regression/create.js +6 -1
  24. package/src/actions/regression/edit.js +10 -1
  25. package/src/actions/titles/resolve.js +11 -1
  26. package/src/core/compositionState.js +9 -4
  27. package/src/core/facetAxisPolicy.js +30 -0
  28. package/src/core/facetGap.js +44 -0
  29. package/src/grammar/axisTextPlacement.js +1 -1
  30. package/src/grammar/bin2d.js +34 -4
  31. package/src/grammar/curveCommands.js +35 -2
  32. package/src/grammar/datasetSchema.js +1 -1
  33. package/src/grammar/facetHeaders.js +1 -1
  34. package/src/grammar/facets/guides.js +7 -5
  35. package/src/grammar/regression/parameters.js +7 -0
  36. package/src/grammar/timeUnit.js +9 -3
  37. package/src/inspectionDescriptors.js +1 -1
  38. package/src/layout/composition.js +2 -6
  39. package/src/layout/facetContentFlow.js +15 -7
  40. package/src/layout/facets.js +1 -1
  41. package/src/layout/guideCollisions.js +9 -3
  42. package/src/layout/title.js +1 -1
  43. package/src/materialization/facetContentBounds.js +96 -0
  44. package/src/materialization/facetGuides/layout.js +1 -1
  45. package/src/materialization/facetGuides/placement.js +16 -0
  46. package/src/materialization/facetHeaders.js +1 -1
  47. package/src/materialization/facets.js +1 -1
  48. package/src/materialization/graphicHierarchy.js +25 -1
  49. package/src/materialization/guides/layout.js +1 -1
  50. package/src/materialization/guides/resources.js +1 -1
  51. package/src/materialization/planner.js +19 -4
  52. package/src/persistence/validation.js +25 -1
  53. package/src/version.js +1 -1
  54. package/types/basic.d.ts +2 -0
  55. package/types/index.d.ts +3 -0
  56. package/types/program.d.ts +36 -18
@@ -1,3 +1,6 @@
1
+ import { validateFacetAxisPolicy } from "../core/facetAxisPolicy.js";
2
+ import { resolveFacetFamily } from "../grammar/facets/dependencies.js";
3
+ import { validateRegressionPlacement } from "../grammar/regression/parameters.js";
1
4
  import { validateGraphicClip } from "../grammar/schemas/graphicClip.js";
2
5
  import { validatePointShape } from "../grammar/pointShapes.js";
3
6
  import {deriveLookupRows,sameLookupValue} from "../grammar/lookup.js";
@@ -27,7 +30,8 @@ import { isContinuousSizeScaleType } from "../grammar/scales/size.js";
27
30
  import { validateGraphicItemLimit } from "../core/validation.js";
28
31
  import { LEGEND_TITLE_STYLE_KEYS, validateLegendTitleAlignment } from "../grammar/legendTitleStyle.js";
29
32
  import { validateGuideDash, normalizeGuideDashMap } from "../grammar/guideDash.js";
30
- import { validateAxisTextPlacement } from "../grammar/axisTextPlacement.js";
33
+ import { validateAxisLabelFlushThreshold, validateAxisTextPlacement,
34
+ axisLabelHasText } from "../grammar/axisTextPlacement.js";
31
35
  import { validateTickSelectionRounding, axisTickSelection } from "../grammar/ticks.js";
32
36
  import { validateAreaPaintConfig } from "../grammar/areaPaint.js";
33
37
  import { validateOptionObject, validateKeys, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString, validateGeneratedItemLimit } from "../core/validation.js";
@@ -215,6 +219,8 @@ export function validateProgramState(program, path, { allowMissingLookupSchemas
215
219
  validateAuthoredFacetLegend(program, program.compositionSpec.facet.guides.legend);
216
220
  }
217
221
  if (program.compositionSpec?.type === "facet") {
222
+ validateFacetAxisPolicy(program.compositionSpec.facet.guides.axes,
223
+ resolveFacetFamily(program.semanticSpec).family, program.compositionSpec.facet.repeat !== undefined);
218
224
  const headers = program.materializationConfigs.facets?.[program.compositionSpec.id]?.headers;
219
225
  if (headers !== undefined) validateFacetHeaderRotations(headers);
220
226
  if (headers?.row?.title !== undefined && program.compositionSpec.facet.grid === undefined) {
@@ -231,6 +237,23 @@ export function validateProgramState(program, path, { allowMissingLookupSchemas
231
237
  }
232
238
  const cartesianOwners = cartesianAxisOwners(program);
233
239
  for (const [id, axis] of Object.entries(program.guideConfigs.axis ?? {})) {
240
+ if (isPlainObject(axis?.title?.offset)) {
241
+ if (!cartesianOwners.some(owner => owner.id === id)) throw invalidSnapshot("Relative title offset requires a Cartesian axis owner.", path);
242
+ validateAxisTextPlacement(axis.title, { title: true });
243
+ if (axis.title.inferredOffset === true) throw invalidSnapshot("Relative title offset cannot be inferred.", path);
244
+ const owner = cartesianOwners.find(owner => owner.id === id);
245
+ const labels = program.graphicSpec.objects[axisGraphicIds(owner.channel, id)[2]];
246
+ // Composition parents retain source intent, not local unit graphics.
247
+ // Their retained unit/children are validated through the same unit rule.
248
+ if (axis.labels?.position !== axis.title.position || (program.compositionSpec === undefined &&
249
+ !(labels?.items ?? (labels ? [labels] : [])).some(item => axisLabelHasText(item.properties)))) {
250
+ throw invalidSnapshot("Relative title offset requires nonempty same-side labels.", path);
251
+ }
252
+ }
253
+ if (axis?.labels?.flushThreshold !== undefined) {
254
+ if (!cartesianOwners.some(owner => owner.id === id)) throw invalidSnapshot("Label flushThreshold requires a Cartesian axis owner.", path);
255
+ validateAxisLabelFlushThreshold(axis.labels.flushThreshold);
256
+ }
234
257
  for (const config of [axis?.ticks, axis?.labels]) {
235
258
  if (config === undefined || !Object.hasOwn(config, "rounding")) continue;
236
259
  if (!cartesianOwners.some(owner => owner.id === id)) {
@@ -463,6 +486,7 @@ export function validateProgramState(program, path, { allowMissingLookupSchemas
463
486
  }
464
487
  }
465
488
  for (const id of Object.keys(program.markConfigs)) {
489
+ validateRegressionPlacement(program.markConfigs[id].regression?.placement);
466
490
  requestedMarkClip(program.markConfigs[id]);
467
491
  validateMarkClipCoordinate(program, resources.get("mark").get(id), program.markConfigs[id].clip);
468
492
  if (!resources.get("mark").has(id)) throw invalidSnapshot(`Mark config "${id}" has no layer owner.`, path);
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Must match package.json; enforced by the snapshot producer-version test.
2
- export const packageVersion = "0.0.22";
2
+ export const packageVersion = "0.0.24";
package/types/basic.d.ts CHANGED
@@ -10,11 +10,13 @@ export type {
10
10
  CartesianGuideDashMap,
11
11
  AxisTicksAndLabelsOptions,
12
12
  AxisTitleOptions,
13
+ AxisTitleOffset,
13
14
  DisplayLabelMap,
14
15
  DisplayLabelOptions,
15
16
  ApplyThemeOptions,
16
17
  BarWidthOptions,
17
18
  BasicSeriesLayoutOptions,
19
+ Bin2DAssignment,
18
20
  Bin2DDataOptions,
19
21
  CanvasOptions,
20
22
  ColorEncodingOptions,
package/types/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export type {
27
27
  AxisTicksAndLabelsOptions,
28
28
  AxisTickStyleOptions,
29
29
  AxisTitleOptions,
30
+ AxisTitleOffset,
30
31
  AxisValue,
31
32
  DisplayLabelMap,
32
33
  DisplayLabelOptions,
@@ -36,6 +37,7 @@ export type {
36
37
  BinnedHeatmapColorOptions,
37
38
  BinnedHeatmapOptions,
38
39
  BinnedHeatmapPositionChannel,
40
+ Bin2DAssignment,
39
41
  Bin2DCounts,
40
42
  Bin2DDataOptions,
41
43
  Bin2DExtent,
@@ -287,6 +289,7 @@ export type {
287
289
  FieldType,
288
290
  FillPaint,
289
291
  FacetCompositionSpec,
292
+ FacetGap,
290
293
  FacetGuideOptions,
291
294
  FacetOptions,
292
295
  FacetTopLaneMode,
@@ -100,6 +100,7 @@ export type CurveInterpolation =
100
100
  | "basis"
101
101
  | "cardinal"
102
102
  | "monotone"
103
+ | "monotone-steffen"
103
104
  | "natural";
104
105
  export type DashStyle = "solid" | "dashed" | "dotted" | "dashdot";
105
106
  export type DashPattern = readonly number[];
@@ -163,13 +164,15 @@ export interface CompositionOptions {
163
164
  align?: CompositionAlign;
164
165
  padding?: number | CompositionPadding;
165
166
  }
167
+ /** Facets may set independent axis gaps; concat continues to require a number. */
168
+ export type FacetGap = number | Readonly<{ x: number; y: number }>;
166
169
  export interface EditCompositionLayoutOptions {
167
170
  columns?: number;
168
- gap?: number;
171
+ gap?: FacetGap;
169
172
  align?: CompositionAlign;
170
173
  padding?: number | CompositionPadding;
171
- /** Interpret gap between child canvases (default) or inner plot regions. */
172
- spacing?: "canvas" | "plot";
174
+ /** Interpret gap between child canvases (default), inner plots, or retained full content (wrapped facets). */
175
+ spacing?: "canvas" | "plot" | "content";
173
176
  /** Share existing top margin with parent title and headers; requires plot spacing. Stacked restores the default. */
174
177
  topLane?: FacetTopLaneMode;
175
178
  }
@@ -213,7 +216,7 @@ interface StoredFacetLegendIntent {
213
216
  readonly groups: Readonly<Record<string, unknown>>;
214
217
  }
215
218
  export interface FacetGuideOptions {
216
- axes?: "each" | "outer";
219
+ axes?: "each" | "outer" | { x?: "each" | "outer"; y?: "each" | "outer" };
217
220
  legend?: false | "shared" | LegendOptions;
218
221
  }
219
222
  export interface FacetOptions {
@@ -223,14 +226,14 @@ export interface FacetOptions {
223
226
  data?: string;
224
227
  values?: readonly DatasetScalar[];
225
228
  columns?: number;
226
- gap?: number;
229
+ gap?: FacetGap;
227
230
  align?: CompositionAlign;
228
231
  padding?: number | CompositionPadding;
229
232
  scales?: FacetScaleResolutions;
230
233
  guides?: FacetGuideOptions;
231
234
  headers?: FacetHeadersOptions;
232
- /** Interpret gap between child canvases (default) or inner plot regions. */
233
- spacing?: "canvas" | "plot";
235
+ /** Interpret gap between child canvases (default), inner plots, or retained full content (wrapped facets). */
236
+ spacing?: "canvas" | "plot" | "content";
234
237
  /** Share the existing top margin rather than adding title/header reservations. Requires plot spacing. */
235
238
  topLane?: FacetTopLaneMode;
236
239
  }
@@ -244,13 +247,13 @@ export interface FacetGridOptions {
244
247
  rows: FacetGridRole;
245
248
  columns: FacetGridRole;
246
249
  combinations?: "observed" | "full";
247
- gap?: number;
250
+ gap?: FacetGap;
248
251
  align?: CompositionAlign;
249
252
  padding?: number | CompositionPadding;
250
253
  scales?: FacetScaleResolutions;
251
254
  guides?: FacetGuideOptions;
252
255
  headers?: FacetHeadersOptions;
253
- /** Interpret gap between child canvases (default) or inner plot regions. */
256
+ /** Interpret gap between child canvases (default), inner plots, or retained full content (wrapped facets). */
254
257
  spacing?: "canvas" | "plot";
255
258
  /** Share the existing top margin rather than adding title/header reservations. Requires plot spacing. */
256
259
  topLane?: FacetTopLaneMode;
@@ -261,14 +264,14 @@ export interface RepeatChartsOptions {
261
264
  channel: "x" | "y" | "theta" | "r" | { parallelDimension: string };
262
265
  fields: readonly [string, ...string[]];
263
266
  columns?: number;
264
- gap?: number;
267
+ gap?: FacetGap;
265
268
  align?: CompositionAlign;
266
269
  padding?: number | CompositionPadding;
267
270
  scales?: FacetScaleResolutions;
268
271
  guides?: FacetGuideOptions;
269
272
  headers?: FacetHeadersOptions;
270
- /** Interpret gap between child canvases (default) or inner plot regions. */
271
- spacing?: "canvas" | "plot";
273
+ /** Interpret gap between child canvases (default), inner plots, or retained full content (wrapped facets). */
274
+ spacing?: "canvas" | "plot" | "content";
272
275
  /** Share the existing top margin rather than adding title/header reservations. Requires plot spacing. */
273
276
  topLane?: FacetTopLaneMode;
274
277
  }
@@ -294,7 +297,7 @@ export interface EditFacetHeadersOptions {
294
297
  color?: string;
295
298
  offset?: number;
296
299
  /** Content follows concrete mark bounds with plot spacing; plot preserves existing placement. */
297
- anchor?: "plot" | "content";
300
+ anchor?: "plot" | "content" | "view";
298
301
  role?: FacetHeaderRole;
299
302
  labelMap?: DisplayLabelMap | "auto";
300
303
  side?: FacetHeaderSide;
@@ -324,10 +327,10 @@ export interface FacetCompositionSpec {
324
327
  readonly type: "facet";
325
328
  readonly children: readonly string[];
326
329
  readonly columns: number;
327
- readonly spacing?: "canvas" | "plot";
330
+ readonly spacing?: "canvas" | "plot" | "content";
328
331
  /** Canonical opt-in intent; stacked is represented by omission. */
329
332
  readonly topLane?: "shared";
330
- readonly gap: number;
333
+ readonly gap: FacetGap;
331
334
  readonly align: CompositionAlign;
332
335
  readonly padding: Readonly<Required<CompositionPadding>>;
333
336
  readonly facet: {
@@ -354,7 +357,7 @@ export interface FacetCompositionSpec {
354
357
  };
355
358
  readonly scales: Readonly<Required<FacetScaleResolutions>>;
356
359
  readonly guides: {
357
- readonly axes: "each" | "outer";
360
+ readonly axes: "each" | "outer" | { readonly x: "each" | "outer"; readonly y: "each" | "outer" };
358
361
  readonly legend: false | "shared" | Readonly<LegendOptions> | StoredFacetLegendIntent;
359
362
  };
360
363
  };
@@ -735,6 +738,7 @@ export type TimeUnit =
735
738
  | "week"
736
739
  | "weekday";
737
740
  type DatasetTimeUnitTransformBase = {
741
+ readonly missing?: "error" | "null";
738
742
  readonly type: "timeUnit";
739
743
  readonly field: string;
740
744
  readonly temporalUnit?: TemporalInputUnit;
@@ -753,6 +757,7 @@ export type DatasetTimeUnitTransform = DatasetTimeUnitTransformBase & (
753
757
  readonly weekRule: "calendar" | "iso";
754
758
  }
755
759
  );
760
+ export type Bin2DAssignment = "edges" | "step";
756
761
  export interface Bin2DCounts {
757
762
  x: number;
758
763
  y: number;
@@ -779,6 +784,7 @@ export interface DatasetBin2DOutputFields {
779
784
  }
780
785
  export interface DatasetBin2DTransform {
781
786
  readonly type: "bin2d";
787
+ readonly assignment?: "step";
782
788
  readonly x: string;
783
789
  readonly y: string;
784
790
  readonly bins: Readonly<Bin2DCounts>;
@@ -1467,6 +1473,8 @@ export interface CartesianAxisLabelStyleOptions extends AxisLabelStyleOptions {
1467
1473
  colorMap?: CartesianAxisLabelColorMap | "auto";
1468
1474
  /** Keep unrotated label bounds inside the mapped axis span; defaults to false. */
1469
1475
  flush?: boolean;
1476
+ /** Only flush mapped ticks within this non-negative distance of a range edge. Auto restores all-label containment. */
1477
+ flushThreshold?: number | "auto";
1470
1478
  /** Signed displacement from the mapped tick position, independent of outward offset. */
1471
1479
  alongOffset?: number;
1472
1480
  }
@@ -1492,6 +1500,7 @@ export interface AxisTicksAndLabelsOptions<P extends string> {
1492
1500
  ticks?: CartesianAxisTickStyleOptions;
1493
1501
  labels?: CartesianAxisLabelStyleOptions & AxisLabelLayoutOptions & DisplayLabelOptions;
1494
1502
  }
1503
+ export type AxisTitleOffset = number | Readonly<{ from: "labels"; gap: number }>;
1495
1504
  export interface AxisTitleOptions<P extends string> {
1496
1505
  /** Axis resource identity; omission prefers canonical, then a unique named owner. */
1497
1506
  target?: string;
@@ -1506,8 +1515,8 @@ export interface AxisTitleOptions<P extends string> {
1506
1515
  scale?: string;
1507
1516
  position?: P;
1508
1517
  at?: "start" | "center" | "end" | number;
1509
- /** Signed distance from the axis frame: positive outward, negative inward. */
1510
- offset?: number;
1518
+ /** Signed distance from the axis frame, or a nonnegative gap outside same-owner labels. */
1519
+ offset?: AxisTitleOffset;
1511
1520
  rotation?: RotationInput;
1512
1521
  color?: string;
1513
1522
  fontSize?: number;
@@ -2496,6 +2505,8 @@ export interface ECDFDataOptions {
2496
2505
  }
2497
2506
 
2498
2507
  type TimeUnitDataBaseOptions = {
2508
+ /** Preserve null or absent inputs as null buckets; defaults to strict errors. */
2509
+ missing?: "error" | "null";
2499
2510
  id: string;
2500
2511
  source?: string;
2501
2512
  field: string;
@@ -2669,6 +2680,7 @@ export interface Bin2DDataOptions {
2669
2680
  y: string;
2670
2681
  bins?: number | Bin2DCounts;
2671
2682
  extent?: Bin2DExtent;
2683
+ assignment?: "edges" | "step";
2672
2684
  includeEmpty?: boolean;
2673
2685
  members?: boolean;
2674
2686
  as?: Bin2DOutputFields;
@@ -2681,6 +2693,7 @@ export interface EditBin2DDataOptions {
2681
2693
  y?: string;
2682
2694
  bins?: number | Bin2DCounts;
2683
2695
  extent?: Bin2DExtent;
2696
+ assignment?: "edges" | "step";
2684
2697
  includeEmpty?: boolean;
2685
2698
  members?: boolean;
2686
2699
  as?: DatasetBin2DOutputFields;
@@ -4039,6 +4052,7 @@ export interface BinnedHeatmapColorOptions {
4039
4052
  export interface HeatmapBinOptions {
4040
4053
  bins?: number | Bin2DCounts;
4041
4054
  extent?: Bin2DExtent;
4055
+ assignment?: "edges" | "step";
4042
4056
  includeEmpty?: boolean;
4043
4057
  }
4044
4058
 
@@ -4800,6 +4814,7 @@ export interface EditRegressionStatisticsDataOptions {
4800
4814
  }
4801
4815
 
4802
4816
  type RegressionCommonOptions = {
4817
+ placement?: "source" | "plot-last";
4803
4818
  target?: string;
4804
4819
  x?: string;
4805
4820
  y?: string;
@@ -4825,6 +4840,7 @@ export type RegressionOptions = RegressionCommonOptions & (
4825
4840
  );
4826
4841
 
4827
4842
  export interface EditRegressionOptions {
4843
+ placement?: "source" | "plot-last";
4828
4844
  target?: string;
4829
4845
  data?: string;
4830
4846
  x?: string;
@@ -5187,6 +5203,8 @@ export interface TitleOptions {
5187
5203
  maxWidth?: number;
5188
5204
  wrap?: "word" | "character";
5189
5205
  lineHeight?: number;
5206
+ /** Subtitle-only line spacing; overrides lineHeight without requiring maxWidth. */
5207
+ subtitleLineHeight?: number;
5190
5208
  titleStyle?: TitleTextStyleOptions;
5191
5209
  subtitleStyle?: TitleTextStyleOptions;
5192
5210
  }