ggaction 0.0.21 → 0.0.23

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 (265) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +83 -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 +11 -4
  8. package/src/BasicChartProgram.js +1 -1
  9. package/src/accessibility.js +1 -0
  10. package/src/actions/boxPlots/components.js +1 -96
  11. package/src/actions/boxPlots/create.js +6 -1
  12. package/src/actions/boxPlots/edit.js +8 -0
  13. package/src/actions/boxPlots/materialize.js +3 -0
  14. package/src/actions/boxPlots/options.js +1 -1
  15. package/src/actions/canvas/actions.js +2 -1
  16. package/src/actions/canvas/index.js +3 -0
  17. package/src/actions/canvas/plotBorder.js +2 -1
  18. package/src/actions/canvas/plotOrder.js +14 -0
  19. package/src/actions/charts/area.js +1 -1
  20. package/src/actions/charts/bar.js +4 -4
  21. package/src/actions/charts/density.js +1 -1
  22. package/src/actions/charts/ecdf.js +1 -1
  23. package/src/actions/charts/endpoints.js +54 -5
  24. package/src/actions/charts/heatmap.js +1 -1
  25. package/src/actions/charts/histogram.js +1 -1
  26. package/src/actions/charts/horizon.js +1 -1
  27. package/src/actions/charts/index.js +2 -1
  28. package/src/actions/charts/interval-regression.js +3 -3
  29. package/src/actions/charts/line.js +1 -1
  30. package/src/actions/charts/radar.js +63 -3
  31. package/src/actions/charts/raincloud.js +37 -6
  32. package/src/actions/charts/rug-strip.js +2 -2
  33. package/src/actions/charts/scatter.js +3 -3
  34. package/src/actions/charts/text.js +1 -1
  35. package/src/actions/composition/actions.js +10 -3
  36. package/src/actions/coordinates/actions.js +2 -0
  37. package/src/actions/data/basic.js +6 -0
  38. package/src/actions/data/derived.js +6 -2
  39. package/src/actions/data/edit.js +22 -36
  40. package/src/actions/data/filter.js +1 -376
  41. package/src/actions/data/flatten.js +19 -0
  42. package/src/actions/data/index.js +6 -0
  43. package/src/actions/data/lookup.js +50 -0
  44. package/src/actions/data/raincloudFilter.js +56 -0
  45. package/src/actions/data/shared.js +10 -0
  46. package/src/actions/data/timeUnit.js +1 -1
  47. package/src/actions/distributions/revision.js +3 -1
  48. package/src/actions/encodings/angle.js +2 -1
  49. package/src/actions/encodings/appearance.js +38 -13
  50. package/src/actions/encodings/channels.js +14 -3
  51. package/src/actions/encodings/color/continuous.js +26 -10
  52. package/src/actions/encodings/color/index.js +50 -20
  53. package/src/actions/encodings/color/policy.js +57 -4
  54. package/src/actions/encodings/parallel.js +4 -0
  55. package/src/actions/encodings/position/policies/bar.js +10 -6
  56. package/src/actions/encodings/position/policies/line.js +1 -152
  57. package/src/actions/encodings/position/resolve.js +4 -1
  58. package/src/actions/encodings/ranged.js +46 -0
  59. package/src/actions/encodings/remove.js +1 -1
  60. package/src/actions/encodings/ruleAppearance.js +7 -6
  61. package/src/actions/encodings/shared.js +14 -2
  62. package/src/actions/encodings/stroke.js +20 -14
  63. package/src/actions/encodings/strokeDash.js +9 -11
  64. package/src/actions/encodings/text.js +30 -2
  65. package/src/actions/errorBars/edit.js +24 -4
  66. package/src/actions/facets/actions.js +1 -793
  67. package/src/actions/facets/derive.js +34 -5
  68. package/src/actions/facets/replay.js +1 -38
  69. package/src/actions/gradientPlots/components.js +21 -9
  70. package/src/actions/gradientPlots/create.js +4 -2
  71. package/src/actions/gradientPlots/edit.js +18 -1
  72. package/src/actions/gradientPlots/materialize.js +35 -36
  73. package/src/actions/gradientPlots/options.js +1 -1
  74. package/src/actions/gradientPlots/paint.js +13 -7
  75. package/src/actions/guides/applicability.js +2 -0
  76. package/src/actions/guides/axes/axes.js +39 -0
  77. package/src/actions/guides/axes/axis.js +9 -5
  78. package/src/actions/guides/axes/edit.js +11 -8
  79. package/src/actions/guides/axes/labels.js +24 -8
  80. package/src/actions/guides/axes/parallel/policy.js +3 -5
  81. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  82. package/src/actions/guides/axes/policy.js +22 -0
  83. package/src/actions/guides/axes/tickGroups.js +4 -2
  84. package/src/actions/guides/axes/ticks.js +22 -4
  85. package/src/actions/guides/axes/titles.js +19 -6
  86. package/src/actions/guides/facadeAxes.js +3 -0
  87. package/src/actions/guides/grids/grid.js +3 -1
  88. package/src/actions/guides/grids/resolve.js +7 -11
  89. package/src/actions/guides/guides.js +21 -1
  90. package/src/actions/guides/legends/categorical/actions.js +1 -515
  91. package/src/actions/guides/legends/categorical/components.js +1 -4
  92. package/src/actions/guides/legends/categorical/layout.js +5 -2
  93. package/src/actions/guides/legends/categorical/options.js +4 -0
  94. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  95. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  96. package/src/actions/guides/legends/colorSize.js +2 -0
  97. package/src/actions/guides/legends/continuous/common.js +1 -1
  98. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  99. package/src/actions/guides/legends/continuous/interval.js +1 -1
  100. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  101. package/src/actions/guides/legends/edit.js +1 -1
  102. package/src/actions/guides/legends/focused.js +1 -1
  103. package/src/actions/guides/legends/lane.js +1 -1
  104. package/src/actions/guides/legends/sampling.js +1 -140
  105. package/src/actions/guides/legends/strokeWidth.js +3 -2
  106. package/src/actions/guides/legends/transition.js +19 -2
  107. package/src/actions/guides/polar/axes/facade.js +5 -4
  108. package/src/actions/guides/polar/axes/labels.js +5 -4
  109. package/src/actions/guides/polar/axes/titles.js +5 -4
  110. package/src/actions/guides/polar/resolve.js +2 -1
  111. package/src/actions/guides/tickValues.js +7 -7
  112. package/src/actions/marks/arc/actions.js +5 -7
  113. package/src/actions/marks/area/actions.js +9 -3
  114. package/src/actions/marks/bar/create.js +4 -2
  115. package/src/actions/marks/bar/edit.js +9 -6
  116. package/src/actions/marks/bar/materialize.js +5 -3
  117. package/src/actions/marks/dependentReferences.js +87 -0
  118. package/src/actions/marks/index.js +2 -0
  119. package/src/actions/marks/lifecycle.js +2 -1
  120. package/src/actions/marks/line/actions.js +24 -6
  121. package/src/actions/marks/line/materialize.js +7 -3
  122. package/src/actions/marks/point/create.js +5 -4
  123. package/src/actions/marks/point/edit.js +6 -3
  124. package/src/actions/marks/point/jitter.js +6 -6
  125. package/src/actions/marks/point/materialize.js +36 -39
  126. package/src/actions/marks/rect/actions.js +16 -4
  127. package/src/actions/marks/references.js +183 -35
  128. package/src/actions/marks/rule/actions.js +20 -9
  129. package/src/actions/marks/shared.js +2 -1
  130. package/src/actions/marks/text/actions.js +10 -3
  131. package/src/actions/marks/tick/actions.js +11 -2
  132. package/src/actions/primitives/editGraphics.js +28 -3
  133. package/src/actions/primitives/index.js +9 -0
  134. package/src/actions/primitives/semanticAction.js +13 -7
  135. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  136. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  137. package/src/actions/regression/create.js +6 -1
  138. package/src/actions/regression/edit.js +10 -1
  139. package/src/actions/scales/channels.js +1 -1
  140. package/src/actions/scales/consumers/common.js +19 -1
  141. package/src/actions/scales/consumers/families.js +2 -2
  142. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  143. package/src/actions/scales/edit.js +11 -0
  144. package/src/actions/scales/editPolicy.js +5 -3
  145. package/src/actions/scales/preview.js +2 -1
  146. package/src/actions/scales/quantitativeColor.js +2 -4
  147. package/src/actions/selection/actions.js +10 -1
  148. package/src/actions/theme/reconcile.js +37 -9
  149. package/src/core/compositionState.js +26 -7
  150. package/src/core/datasetReferences.js +17 -0
  151. package/src/core/facetAxisPolicy.js +30 -0
  152. package/src/core/immutable.js +1 -0
  153. package/src/core/resourceReferences.js +5 -3
  154. package/src/core/validation.js +4 -0
  155. package/src/core/vocabulary.js +24 -2
  156. package/src/grammar/aggregate.js +1 -0
  157. package/src/grammar/arcs.js +1 -1
  158. package/src/grammar/areaSeries.js +1 -1
  159. package/src/grammar/axisTextPlacement.js +1 -1
  160. package/src/grammar/bars/policy.js +2 -2
  161. package/src/grammar/curveCommands.js +35 -2
  162. package/src/grammar/datasetDependencies.js +97 -0
  163. package/src/grammar/datasetSchema.js +32 -2
  164. package/src/grammar/facetHeaders.js +1 -0
  165. package/src/grammar/facets/dependencies.js +6 -2
  166. package/src/grammar/facets/guides.js +7 -5
  167. package/src/grammar/facets/legendIntent.js +6 -2
  168. package/src/grammar/facets/scales.js +27 -7
  169. package/src/grammar/flatten.js +85 -0
  170. package/src/grammar/histogram.js +13 -1
  171. package/src/grammar/horizon.js +1 -494
  172. package/src/grammar/jitter.js +2 -2
  173. package/src/grammar/legendSpacing.js +45 -0
  174. package/src/grammar/lineMissing.js +90 -0
  175. package/src/grammar/lineSeries.js +1 -376
  176. package/src/grammar/lookup.js +97 -0
  177. package/src/grammar/markFilter.js +4 -2
  178. package/src/grammar/markLabels.js +8 -3
  179. package/src/grammar/numeric.js +1 -1
  180. package/src/grammar/pathCommands.js +1 -1
  181. package/src/grammar/pathSeries.js +1 -109
  182. package/src/grammar/pointPolygon.js +81 -0
  183. package/src/grammar/pointShapes.js +15 -15
  184. package/src/grammar/rects.js +1 -1
  185. package/src/grammar/regression/derive.js +1 -195
  186. package/src/grammar/regression/parameters.js +7 -0
  187. package/src/grammar/scales/appearance.js +1 -1
  188. package/src/grammar/scales/color.js +1 -1
  189. package/src/grammar/scales/colorConsumers.js +1 -63
  190. package/src/grammar/scales/continuous.js +58 -1
  191. package/src/grammar/scales/definition.js +1 -1
  192. package/src/grammar/scales/ordinal.js +5 -2
  193. package/src/grammar/scales/policies.js +3 -3
  194. package/src/grammar/schemas/graphicBounds.js +40 -12
  195. package/src/grammar/schemas/graphicClip.js +23 -0
  196. package/src/grammar/schemas/graphicTree.js +16 -0
  197. package/src/grammar/statisticalReference.js +22 -0
  198. package/src/grammar/text.js +65 -1
  199. package/src/grammar/ticks.js +92 -1
  200. package/src/grammar/timeUnit.js +9 -3
  201. package/src/grammar/transformTopology.js +3 -1
  202. package/src/grammar/transforms.js +24 -0
  203. package/src/inspection.js +1 -495
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -1
  206. package/src/layout/facetContentFlow.js +101 -0
  207. package/src/layout/facetHeaderTitles.js +1 -0
  208. package/src/layout/facetTopLane.js +29 -0
  209. package/src/layout/facets.js +1 -1
  210. package/src/layout/guideCollisions.js +9 -3
  211. package/src/layout/legendGroup.js +1 -1
  212. package/src/layout/legendItems.js +26 -10
  213. package/src/layout/legendSpacing.js +66 -0
  214. package/src/layout/plotDrawOrder.js +17 -0
  215. package/src/materialization/bars/histogram.js +8 -2
  216. package/src/materialization/bars/resolve.js +1 -1
  217. package/src/materialization/composition.js +1 -0
  218. package/src/materialization/compositionSnapshot.js +22 -2
  219. package/src/materialization/dataProvenance.js +1 -1
  220. package/src/materialization/facetContentBounds.js +96 -0
  221. package/src/materialization/facetGuides/layout.js +1 -1
  222. package/src/materialization/facetGuides/placement.js +39 -11
  223. package/src/materialization/facetGuides/preparation.js +43 -24
  224. package/src/materialization/facetHeaders.js +1 -1
  225. package/src/materialization/facetTopLane.js +25 -0
  226. package/src/materialization/facets.js +1 -1
  227. package/src/materialization/graphicHierarchy.js +30 -1
  228. package/src/materialization/graphicTranslation.js +2 -0
  229. package/src/materialization/guides/layout.js +1 -1
  230. package/src/materialization/guides/resources.js +1 -1
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/markClip.js +26 -0
  233. package/src/materialization/marks/capabilities.js +3 -2
  234. package/src/materialization/marks/grouping.js +7 -5
  235. package/src/materialization/marks/index.js +4 -2
  236. package/src/materialization/marks/policies.js +4 -0
  237. package/src/materialization/plotDrawOrder.js +44 -0
  238. package/src/materialization/rect.js +11 -3
  239. package/src/materialization/scaleGuideDependencies.js +13 -5
  240. package/src/materialization/selection/items/common.js +1 -1
  241. package/src/materialization/selection/items/path.js +1 -0
  242. package/src/materialization/selection/policies/index.js +2 -1
  243. package/src/materialization/text.js +10 -5
  244. package/src/mcp/cli.js +0 -0
  245. package/src/persistence/validation.js +114 -18
  246. package/src/persistence.js +4 -2
  247. package/src/renderers/canvas/index.js +16 -0
  248. package/src/renderers/canvas/isolated.js +11 -1
  249. package/src/renderers/canvas/native.js +7 -0
  250. package/src/renderers/canvas/scene.js +66 -0
  251. package/src/renderers/canvas/text.js +3 -2
  252. package/src/renderers/svg.js +1 -617
  253. package/src/renderers/svgSerialization.js +643 -0
  254. package/src/renderers/transitionScene.js +67 -0
  255. package/src/scene.js +89 -0
  256. package/src/selectors/axes.js +1 -1
  257. package/src/selectors/datasets.js +1 -0
  258. package/src/selectors/grids.js +9 -0
  259. package/src/selectors/markClip.js +28 -0
  260. package/src/selectors/raincloud.js +7 -0
  261. package/src/version.js +1 -1
  262. package/types/basic.d.ts +7 -0
  263. package/types/index.d.ts +8 -0
  264. package/types/program.d.ts +286 -70
  265. package/types/scene.d.ts +35 -0
@@ -0,0 +1,97 @@
1
+ import { datasetInputIds } from "../core/datasetReferences.js";
2
+ export { datasetInputReferences, datasetInputIds, rebindTransformDatasetInputs } from "../core/datasetReferences.js";
3
+
4
+ export function orderedDatasetAncestors(datasets, root) {
5
+ const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
6
+ const visiting = new Set(), visited = new Set(), result = [];
7
+ if (byId.size !== datasets.length) throw new Error("Dataset dependency graph contains duplicate ids.");
8
+ function visit(id) {
9
+ if (visiting.has(id)) throw new Error(`Dataset dependency graph contains a cycle at "${id}".`);
10
+ if (visited.has(id)) return;
11
+ const dataset = byId.get(id);
12
+ if (dataset === undefined) throw new Error(`Dataset dependency graph references missing input "${id}".`);
13
+ visiting.add(id);
14
+ for (const input of datasetInputIds(dataset)) visit(input);
15
+ visiting.delete(id);
16
+ visited.add(id);
17
+ result.push(dataset);
18
+ }
19
+ visit(root);
20
+ return result;
21
+ }
22
+
23
+ // A stable topological extension of the historical primary-tree DFS order.
24
+ // A binary input waits for both changed parents; an ordinary tree keeps its old order.
25
+ export function orderedDatasetDependents(datasets, root, { skip = () => false } = {}) {
26
+ const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
27
+ if (byId.size !== datasets.length) throw new Error("Dataset dependency graph contains duplicate ids.");
28
+ if (!byId.has(root)) throw new Error(`Dataset dependency graph has no root "${root}".`);
29
+ const children = new Map();
30
+ for (const dataset of datasets) {
31
+ for (const input of datasetInputIds(dataset)) {
32
+ if (!byId.has(input)) throw new Error(`Dataset "${dataset.id}" references missing input "${input}".`);
33
+ const values = children.get(input) ?? [];
34
+ values.push(dataset);
35
+ children.set(input, values);
36
+ }
37
+ }
38
+ const rank = new Map(), visiting = new Set(), visited = new Set();
39
+ function visit(id) {
40
+ if (visiting.has(id)) throw new Error(`Dataset dependency graph contains a cycle at "${id}".`);
41
+ if (visited.has(id)) return;
42
+ visiting.add(id);
43
+ for (const dataset of children.get(id) ?? []) {
44
+ if (skip(dataset)) continue;
45
+ if (!rank.has(dataset.id)) rank.set(dataset.id, rank.size);
46
+ visit(dataset.id);
47
+ }
48
+ visiting.delete(id);
49
+ visited.add(id);
50
+ }
51
+ visit(root);
52
+ const remaining = new Map([...rank].map(([id]) => [id,
53
+ datasetInputIds(byId.get(id)).filter(parent => rank.has(parent)).length]));
54
+ const ready = [];
55
+ const less = (a, b) => rank.get(a) < rank.get(b);
56
+ function push(id) {
57
+ ready.push(id);
58
+ let index = ready.length - 1;
59
+ while (index > 0) {
60
+ const parent = Math.floor((index - 1) / 2);
61
+ if (!less(ready[index], ready[parent])) break;
62
+ [ready[index], ready[parent]] = [ready[parent], ready[index]];
63
+ index = parent;
64
+ }
65
+ }
66
+ function pop() {
67
+ const first = ready[0], last = ready.pop();
68
+ if (ready.length) {
69
+ ready[0] = last;
70
+ let index = 0;
71
+ for (;;) {
72
+ const left = index * 2 + 1, right = left + 1;
73
+ let next = index;
74
+ if (left < ready.length && less(ready[left], ready[next])) next = left;
75
+ if (right < ready.length && less(ready[right], ready[next])) next = right;
76
+ if (next === index) break;
77
+ [ready[index], ready[next]] = [ready[next], ready[index]];
78
+ index = next;
79
+ }
80
+ }
81
+ return first;
82
+ }
83
+ for (const [id, count] of remaining) if (count === 0) push(id);
84
+ const result = [];
85
+ while (ready.length) {
86
+ const id = pop();
87
+ result.push(byId.get(id));
88
+ for (const child of children.get(id) ?? []) {
89
+ if (!remaining.has(child.id)) continue;
90
+ const count = remaining.get(child.id) - 1;
91
+ remaining.set(child.id, count);
92
+ if (count === 0) push(child.id);
93
+ }
94
+ }
95
+ if (result.length !== rank.size) throw new Error("Dataset dependency graph contains a cycle.");
96
+ return result;
97
+ }
@@ -268,8 +268,33 @@ function generatedField(name, storageType, transform, role, inputs = []) {
268
268
  };
269
269
  }
270
270
 
271
- export function deriveTransformSchema(inputSchema, transform, values, { sourceId, ownerId, sourceTransform } = {}) {
271
+ export function datasetSchemaContext(source,transform,ownerId,resolveInput) {
272
+ const context={sourceId:source.id,ownerId,sourceTransform:source.transform?.[0]};
273
+ if(transform.type==="lookup"){
274
+ const input=resolveInput(transform.lookup.data);
275
+ if(input?.schema===undefined)throw new Error(`Lookup dataset "${transform.lookup.data}" requires schema.`);
276
+ assertFieldsAvailable(source.schema,[transform.field],{data:source.id,operation:"Lookup"});
277
+ assertFieldsAvailable(input.schema,[transform.lookup.key,...transform.lookup.fields],{data:input.id,operation:"Lookup"});
278
+ context.lookupSchema=input.schema;context.lookupSourceId=input.id;
279
+ }
280
+ return context;
281
+ }
282
+
283
+ export function deriveTransformSchema(inputSchema, transform, values, { sourceId, ownerId, sourceTransform,lookupSchema,lookupSourceId } = {}) {
272
284
  const input = validateDatasetSchema(inputSchema);
285
+ if(transform.type==="lookup"){
286
+ const right=validateDatasetSchema(lookupSchema),fields=input.fields.map(field=>({...field}));
287
+ for(let i=0;i<transform.lookup.fields.length;i++){
288
+ const field=transform.lookup.fields[i],name=transform.lookup.as[i];
289
+ if(fields.some(f=>f.name===name))throw new Error(`Lookup output field "${name}" already exists in primary data.`);
290
+ const inherited=right.fields.find(f=>f.name===field);
291
+ const references=[{data:sourceId,field:transform.field},{data:lookupSourceId,field:transform.lookup.key},{data:lookupSourceId,field}];
292
+ fields.push({name,storageType:inherited?.storageType??"unknown",nullable:true,optional:false,lineage:{
293
+ inputs:[...new Map(references.map(r=>[JSON.stringify(r),r])).values()],owner:ownerId,role:`lookup:${field}`
294
+ }});
295
+ }
296
+ return cloneAndFreeze({version:1,completeness:input.completeness==="known"||fields.length>0?"known":"unknown",origin:"derived",fields});
297
+ }
273
298
  const definition = { ...transform, __sourceId: sourceId, __ownerId: ownerId };
274
299
  const retain = ["filter", "markFilter", "sort", "computed", "timeUnit", "window", "normalize", "stack", "impute", "complete"];
275
300
  let fields = retain.includes(transform.type) ? input.fields.map(field => ({ ...field })) : [];
@@ -354,6 +379,11 @@ export function deriveTransformSchema(inputSchema, transform, values, { sourceId
354
379
  for (const [role, name] of Object.entries(transform.as ?? {})) {
355
380
  push(generatedField(name, "number", definition, role, [transform.field]));
356
381
  }
382
+ } else if (transform.type === "flatten") {
383
+ fields = input.fields.map(field => ({ ...field }));
384
+ transform.fields.forEach((field, index) => {
385
+ push(generatedField(transform.as[index], "unknown", definition, `value:${field}`, [field]));
386
+ });
357
387
  } else if (transform.type === "fold") {
358
388
  fields = input.fields.filter(field => !transform.fields.includes(field.name)).map(field => ({ ...field }));
359
389
  push(generatedField(transform.as.key, "string", definition, "key", transform.fields));
@@ -374,7 +404,7 @@ export function deriveTransformSchema(inputSchema, transform, values, { sourceId
374
404
  if (planned.lineage === undefined && !["complete", "impute"].includes(transform.type)) {
375
405
  return planned;
376
406
  }
377
- return { ...planned, storageType: transform.type === "regressionStatistics" && planned.storageType === "number"
407
+ return { ...planned, storageType: ["regressionStatistics", "timeUnit"].includes(transform.type) && planned.storageType === "number"
378
408
  ? "number" : field.storageType, nullable: field.nullable, optional: field.optional };
379
409
  });
380
410
  for (const field of inferred.fields) {
@@ -0,0 +1 @@
1
+ import{validateOptionObject as o,validateNonEmptyString as r,validatePositiveFinite as a,validateNonNegativeFinite as c}from"../core/validation.js";import{resolveRotation as s}from"./rotation.js";const f=Object.freeze(["text","fontSize","fontFamily","fontWeight","color","offset","align","rotation","alongOffset"]);function d(e){if(o(e,f,"Facet header title",{allowEmpty:!1}),r(e.text,"Facet header title text"),/[\r\n\u2028\u2029]/u.test(e.text))throw new TypeError("Facet header title text must be one line.");e.fontSize!==void 0&&a(e.fontSize,"Facet header title fontSize");for(const t of["fontFamily","color"])e[t]!==void 0&&r(e[t],`Facet header title ${t}`);if(e.fontWeight!==void 0&&!(typeof e.fontWeight=="string"&&e.fontWeight.length>0||Number.isFinite(e.fontWeight)))throw new TypeError("Facet header title fontWeight must be a non-empty string or finite number.");if(e.offset!==void 0&&c(e.offset,"Facet header title offset"),e.align!==void 0&&!["start","center","end"].includes(e.align))throw new TypeError("Facet header title align must be start, center, or end.");for(const t of["rotation","alongOffset"])if(Object.hasOwn(e,t)&&!Number.isFinite(e[t]))throw new TypeError(`Facet header title ${t} must contain finite normalized radians or coordinates.`)}function h(e,t){if(t===!1)return;o(t,f,"Facet header title",{allowEmpty:!1});for(const i of Object.keys(t))if(t[i]===void 0)throw new TypeError(`Facet header title ${i} must not be undefined.`);const n={...e,...t};return Object.hasOwn(t,"rotation")&&(n.rotation=s(t.rotation,"Facet header title rotation")),d(n),n}function w(e){for(const t of e.common===void 0?[e]:[e.common,e.row,e.column]){if(t!==void 0&&Object.hasOwn(t,"anchor")&&!["plot","content","view"].includes(t.anchor))throw new TypeError("Facet header anchor must be plot, content, or view.");if(t?.rotation!==void 0&&!Number.isFinite(t.rotation))throw new TypeError("Facet header rotation must contain finite normalized radians.");t?.title!==void 0&&d(t.title)}if(e.title!==void 0||e.common?.title!==void 0)throw new TypeError("Facet header title requires an explicit row or column role.");if(e.mode!=="roles"&&(e.row?.title!==void 0||e.column?.title!==void 0))throw new TypeError("Facet field titles require role header mode.")}export{h as normalizeFacetHeaderTitle,w as validateFacetHeaderRotations,d as validateFacetHeaderTitle};
@@ -227,9 +227,13 @@ function resolveAnchor(paths, datasets, classifications, requested) {
227
227
  if (!datasets.has(id)) {
228
228
  throw new Error(`Facet partition dataset "${id}" does not exist.`);
229
229
  }
230
- if (!common.includes(id)) {
230
+ const explicitExpansion = ["fold", "flatten"].includes(
231
+ datasets.get(id).value.transform?.[0]?.type
232
+ ) &&
233
+ paths.every(path => path.includes(id));
234
+ if (!common.includes(id) && !explicitExpansion) {
231
235
  throw new Error(
232
- `Facet partition dataset "${id}" is not a common row-preserving ancestor of every layer.`
236
+ `Facet partition dataset "${id}" is not a common row-preserving ancestor or materialized fold boundary or aligned-array boundary of every layer.`
233
237
  );
234
238
  }
235
239
  return id;
@@ -1,5 +1,8 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../../core/immutable.js";
2
2
 
3
+
4
+ import { normalizeFacetAxes, facetAxisMode, hasOuterFacetAxes } from "../../core/facetAxisPolicy.js";
5
+
3
6
  const LEGEND_FAMILIES = Object.freeze({
4
7
  series: "categorical",
5
8
  color: "categorical",
@@ -243,9 +246,7 @@ export function planFacetGuideOwnership({
243
246
  if (!Array.isArray(children) || children.length !== cells.length) {
244
247
  throw new Error("Facet guide ownership requires one child descriptor per placement.");
245
248
  }
246
- if (!["each", "outer"].includes(axes)) {
247
- throw new Error("Facet axes must be each or outer.");
248
- }
249
+ axes = normalizeFacetAxes(axes, "each", { stored: true });
249
250
  if (![false, "shared"].includes(legend) && !isPlainObject(legend)) {
250
251
  throw new Error("Facet legend must be false or shared.");
251
252
  }
@@ -264,11 +265,12 @@ export function planFacetGuideOwnership({
264
265
  const presentAxes = descriptor.axes ?? [];
265
266
  const records = descriptor.axisOwners;
266
267
  const removed = {};
267
- const keepAxes = axes === "each" ? presentAxes : records === undefined
268
- ? presentAxes.filter(channel => outer.children[cell.id][channel])
268
+ const keepAxes = !hasOuterFacetAxes(axes) ? presentAxes : records === undefined
269
+ ? presentAxes.filter(channel => facetAxisMode(axes, channel) === "each" || outer.children[cell.id][channel])
269
270
  : presentAxes.filter(id => {
270
271
  const owner = records.find(owner => owner.id === id);
271
272
  if (owner === undefined) throw new Error("Facet axis descriptors must identify every retained owner.");
273
+ if (facetAxisMode(axes, owner.channel) === "each") return true;
272
274
  const components = Object.entries(owner.components);
273
275
  const remove = components.filter(([, side]) => !sides[side]?.includes(cell.id)).map(([component]) => component);
274
276
  if (remove.length > 0 && remove.length < components.length) removed[id] = remove;
@@ -1,17 +1,19 @@
1
1
  import { isPlainObject } from "../../core/immutable.js";
2
+ import {validateLegendSpacing} from "../legendSpacing.js";
2
3
  import { validateOptionObject, validateNonEmptyString } from "../../core/validation.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
4
5
  import { LEGEND_CONFIG_KINDS, LEGEND_POSITIONS } from "../../core/vocabulary.js";
5
6
  import { describeLegendBlock } from "../legendBlocks.js";
6
7
  import { validateLegendGroupState } from "../legendGroups.js";
7
8
  import { resolveSharedFacetLegends, sameFacetGuideValue as same } from "./guides.js";
9
+ import { normalizeDisplayLabelMap } from "../displayLabels.js";
8
10
 
9
11
  const CONFIG_KEYS = Object.freeze([
10
12
  "kind", "target", "channels", "scales", "scale", "field", "fieldType", "domain", "title", "titleVisible",
11
13
  "inferredTitle", "inferredSymbol", "inferredStrokeWidth", "inferredOffset", "position", "align", "offset", "direction", "columns",
12
- "titlePosition", "symbol", "labels", "titleStyle", "itemGap", "layout", "overflow", "border", "gradient",
14
+ "titlePosition", "symbol", "labels", "titleStyle", "itemGap", "rowPadding", "titlePadding", "layout", "overflow", "border", "gradient",
13
15
  "count", "sampling", "representation", "gradientDirection", "inheritAppearance", "inheritPosition", "blockOverrides",
14
- "combine", "sizeAreas", "clipHeight"
16
+ "combine", "sizeAreas", "clipHeight", "defaultLabelMap"
15
17
  ]);
16
18
 
17
19
  export function isAuthoredFacetLegend(value) {
@@ -31,6 +33,8 @@ export function validateAuthoredFacetLegend(program, intent) {
31
33
  validateOptionObject(block, ["config", "guide"], "Stored parent legend block");
32
34
  validateOptionObject(block.config, kind === "colorSize" ? [...CONFIG_KEYS, "colorScale", "sizeScale"] : CONFIG_KEYS, "Stored parent legend config");
33
35
  const config = block.config;
36
+ validateLegendSpacing(kind,config);
37
+ if (config.defaultLabelMap !== undefined) normalizeDisplayLabelMap(config.defaultLabelMap, "Stored inferred legend labels");
34
38
  validateUserId(config.target, "Parent legend target");
35
39
  if (!LEGEND_POSITIONS.includes(config.position)) throw new Error("Stored parent legend requires a native position.");
36
40
  for (const flag of ["inferredTitle", "inferredSymbol", "inferredStrokeWidth", "inferredOffset", "titleVisible", "inheritAppearance", "inheritPosition"]) {
@@ -89,17 +89,37 @@ export function normalizeFacetScalePolicies(semanticSpec, requested = {}) {
89
89
  );
90
90
  }
91
91
  }
92
+ const inferred = { ...requested };
93
+ let changed = true;
94
+ while (changed) {
95
+ changed = false;
96
+ for (const [id, channelSet] of used) {
97
+ const attached = [...channelSet];
98
+ const policies = [...new Set(attached
99
+ .filter(channel => inferred[channel] !== undefined)
100
+ .map(channel => inferred[channel]))];
101
+ if (policies.length > 1) {
102
+ throw new Error(
103
+ `Facet scale "${id}" has conflicting channel policies for ${attached.join(", ")}.`
104
+ );
105
+ }
106
+ if (policies.length === 1) for (const channel of attached) {
107
+ if (inferred[channel] === undefined) {
108
+ inferred[channel] = policies[0];
109
+ changed = true;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ Object.assign(channels, inferred);
92
115
  const scalePolicies = {};
93
116
  for (const [id, channelSet] of used) {
94
117
  const attached = [...channelSet];
95
- const policies = [...new Set(attached.map(channel => channels[channel]))];
96
- if (policies.length !== 1) {
97
- throw new Error(
98
- `Facet scale "${id}" has conflicting channel policies for ${attached.join(", ")}.`
99
- );
100
- }
118
+ // One semantic scale cannot have different resolutions through its aliases.
119
+ // An explicitly chosen channel policy also owns omitted aliases; only
120
+ // contradictory explicit requests are ambiguous.
101
121
  scalePolicies[id] = {
102
- policy: policies[0],
122
+ policy: channels[attached[0]],
103
123
  channels: attached
104
124
  };
105
125
  }
@@ -0,0 +1,85 @@
1
+ import { rejectUnknownProperties, requireStringValue } from "../core/validation.js";
2
+ import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
3
+
4
+ const KEYS = Object.freeze(["type", "fields", "as"]);
5
+ const MAX_FIELDS = 64;
6
+ const MAX_OUTPUT_ROWS = 10_000;
7
+
8
+ export function normalizeFlattenTransform({ fields, as } = {}) {
9
+ const aliases = as === undefined ? fields : as;
10
+ const transform = {
11
+ type: "flatten",
12
+ fields: Array.isArray(fields) ? [...fields] : fields,
13
+ as: Array.isArray(aliases) ? [...aliases] : aliases
14
+ };
15
+ validateFlattenTransform(transform);
16
+ return cloneAndFreeze(transform);
17
+ }
18
+
19
+ export function validateFlattenTransform(transform) {
20
+ if (!isPlainObject(transform)) {
21
+ throw new TypeError("Flatten transform must be a plain object.");
22
+ }
23
+ rejectUnknownProperties(transform, KEYS, "flatten transform");
24
+ if (transform.type !== "flatten") {
25
+ throw new Error(`Unsupported flatten transform "${transform.type}".`);
26
+ }
27
+ if (!Array.isArray(transform.fields) || transform.fields.length === 0) {
28
+ throw new TypeError("Flatten fields must be a non-empty array.");
29
+ }
30
+ if (transform.fields.length > MAX_FIELDS) {
31
+ throw new RangeError(`Flatten fields cannot contain more than ${MAX_FIELDS} entries.`);
32
+ }
33
+ if (!Array.isArray(transform.as) || transform.as.length !== transform.fields.length) {
34
+ throw new TypeError("Flatten as must be an array with one alias per field.");
35
+ }
36
+ for (const key of ["fields", "as"]) {
37
+ for (const [index, field] of transform[key].entries()) {
38
+ requireStringValue(field, `Flatten ${key}[${index}]`);
39
+ }
40
+ if (new Set(transform[key]).size !== transform[key].length) {
41
+ throw new Error(`Flatten ${key} must be unique.`);
42
+ }
43
+ }
44
+ return transform;
45
+ }
46
+
47
+ export function deriveFlattenRows(rows, transform) {
48
+ validateFlattenTransform(transform);
49
+ const selected = new Set(transform.fields);
50
+ const existing = new Set(rows.flatMap(row => Object.keys(row)));
51
+ for (const name of transform.as) {
52
+ if (existing.has(name) && !selected.has(name)) {
53
+ throw new Error(`Flatten output field "${name}" already exists outside the selected fields.`);
54
+ }
55
+ }
56
+ let total = 0;
57
+ const lengths = rows.map((row, rowIndex) => {
58
+ let length = 0;
59
+ for (const field of transform.fields) {
60
+ const value = row[field];
61
+ if (value === undefined || value === null) continue;
62
+ if (!Array.isArray(value)) {
63
+ throw new TypeError(`Flatten field "${field}" must be an array at row ${rowIndex}.`);
64
+ }
65
+ length = Math.max(length, value.length);
66
+ }
67
+ total += length;
68
+ if (total > MAX_OUTPUT_ROWS) {
69
+ throw new RangeError(`Flatten output cannot exceed ${MAX_OUTPUT_ROWS} rows.`);
70
+ }
71
+ return length;
72
+ });
73
+ const output = [];
74
+ rows.forEach((row, rowIndex) => {
75
+ for (let index = 0; index < lengths[rowIndex]; index += 1) {
76
+ const next = { ...row };
77
+ transform.fields.forEach((field, fieldIndex) => {
78
+ const values = row[field];
79
+ next[transform.as[fieldIndex]] = values?.[index] ?? null;
80
+ });
81
+ output.push(next);
82
+ }
83
+ });
84
+ return output;
85
+ }
@@ -1,5 +1,6 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
2
  import { MAX_GENERATED_ITEMS } from "../core/validation.js";
3
+ import { niceLinearCountDomain, normalizeScaleNice } from "./scales/continuous.js";
3
4
  import {
4
5
  alignNumericStep,
5
6
  cleanNumericValue,
@@ -299,7 +300,18 @@ function niceBins(extent, maxBins) {
299
300
  return equalBins(extent, maxBins);
300
301
  }
301
302
 
302
- export function resolveHistogramBins({
303
+ export function resolveHistogramBins(options) {
304
+ if (isPlainObject(options.nice)) {
305
+ const { count } = normalizeScaleNice("linear", options.nice);
306
+ const bins = resolveBooleanHistogramBins({ ...options, nice: true });
307
+ return options.domain === undefined || options.domain === "auto"
308
+ ? cloneAndFreeze({ ...bins, domain: niceLinearCountDomain(bins.domain, count) })
309
+ : bins;
310
+ }
311
+ return resolveBooleanHistogramBins(options);
312
+ }
313
+
314
+ function resolveBooleanHistogramBins({
303
315
  values,
304
316
  bin,
305
317
  maxBins,