ggaction 0.0.17 → 0.0.18

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 (263) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -2
  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/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -1,3 +1,4 @@
1
+ import { resolveGraphicBounds } from "../../layout/canvas.js";
1
2
  import { action, closedAction } from "../../core/action.js";
2
3
  import { registerFacetDataRevision, reviseUnitData } from "../data/revise.js";
3
4
  import { freezeOwned, isPlainObject } from "../../core/immutable.js";
@@ -38,16 +39,16 @@ import {
38
39
  } from "../../materialization/facetHeaders.js";
39
40
 
40
41
  const FACET_OPTIONS = Object.freeze([
41
- "id", "field", "data", "values", "columns", "gap", "align", "padding", "scales",
42
- "guides"
42
+ "id", "field", "data", "values", "columns", "gap", "spacing", "align", "padding", "scales",
43
+ "guides", "headers"
43
44
  ]);
44
45
  const FACET_GRID_OPTIONS = Object.freeze([
45
- "id", "data", "rows", "columns", "combinations", "gap", "align",
46
- "padding", "scales", "guides"
46
+ "id", "data", "rows", "columns", "combinations", "gap", "spacing", "align",
47
+ "padding", "scales", "guides", "headers"
47
48
  ]);
48
49
  const REPEAT_OPTIONS = Object.freeze([
49
- "id", "target", "channel", "fields", "columns", "gap", "align",
50
- "padding", "scales", "guides"
50
+ "id", "target", "channel", "fields", "columns", "gap", "spacing", "align",
51
+ "padding", "scales", "guides", "headers"
51
52
  ]);
52
53
  const SOURCE_EDIT_OPTIONS = Object.freeze(["program"]);
53
54
  const GUIDE_OPTIONS = Object.freeze(["axes", "legend"]);
@@ -184,65 +185,149 @@ function rederiveFacet(program, { scales, guides }) {
184
185
  }, compositionSpec.children);
185
186
  }
186
187
 
187
- export const facet = /* @__PURE__ */ closedAction(
188
- {
189
- op: "facet",
190
- description: "Repeat one direct-source chart by field value."
191
- }, FACET_OPTIONS,
192
- function (args = {}) {
193
- const guides = normalizeGuides(args.guides);
194
- const definition = resolveFacetDefinition(this.semanticSpec, args);
195
- if (definition.family !== "cartesian" && guides.axes === "outer") {
196
- throw new Error("Polar and Parallel facets do not support outer axes.");
188
+ function resolveEditedFacetHeaders(currentHeaders, args, grid, operation = "editFacetHeaders") {
189
+ validateOptionObject(args, HEADER_OPTIONS, operation, {
190
+ allowEmpty: false,
191
+ emptyMessage: `${operation} requires at least one change.`
192
+ });
193
+ const role = args.role ?? "all";
194
+ if (!["all", "row", "column"].includes(role)) {
195
+ throw new Error(`Unknown facet header role "${role}".`);
196
+ }
197
+ if (role === "all" && Object.hasOwn(args, "side")) {
198
+ throw new Error(`${operation} side requires an explicit row or column role.`);
199
+ }
200
+ if (role === "row" && grid === undefined) {
201
+ throw new Error(`${operation} row role requires a row-column facet grid.`);
202
+ }
203
+ if (Object.hasOwn(args, "side")) {
204
+ const sides = role === "row" ? ["left", "right"]
205
+ : grid === undefined
206
+ ? ["top", "bottom", "left", "right"] : ["top", "bottom"];
207
+ if (!sides.includes(args.side)) {
208
+ throw new Error(`${operation} ${role} side must be ${sides.join(" or ")}.`);
197
209
  }
198
- const scalePolicies = normalizeFacetScalePolicies(
199
- this.semanticSpec,
200
- args.scales ?? {}
201
- );
202
- const derived = deriveFacetChildren(this, definition, {
203
- closeInheritedAction: true,
204
- stripTitle: true,
205
- scales: args.scales ?? {}
206
- });
207
- const preflight = resolveFacetLayout({
208
- children: definition.cells.map(cell => ({
209
- ...compositionChildDescriptor(cell.id, derived.children[cell.id]),
210
- value: cell.value
211
- })),
212
- ...(Object.hasOwn(args, "columns") ? { columns: args.columns } : {}),
213
- ...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
214
- ...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
215
- ...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
216
- sharedLegend: guides.legend === "shared"
217
- });
218
- const compositionSpec = {
219
- id: definition.id,
220
- type: "facet",
221
- children: definition.cells.map(cell => cell.id),
222
- columns: preflight.columns,
223
- gap: preflight.gap,
224
- align: preflight.align,
225
- padding: preflight.padding,
226
- facet: {
227
- data: definition.data,
228
- field: definition.field,
229
- values: definition.values,
230
- scales: scalePolicies.channels,
231
- guides
232
- }
233
- };
234
- return applyCompositionState(
235
- this._withMaterializationConfig(["facets", definition.id], {
236
- headers: createDefaultFacetHeaders()
237
- }),
238
- {
239
- children: derived.children,
240
- compositionSpec
241
- },
242
- compositionSpec.children
243
- );
244
210
  }
245
- );
211
+ if (Object.hasOwn(args, "align") &&
212
+ !["start", "center", "end"].includes(args.align)) {
213
+ throw new Error(`${operation} align must be start, center, or end.`);
214
+ }
215
+ if (role === "all" && Object.keys(args).every(key => key === "role")) {
216
+ throw new Error(`${operation} requires at least one change.`);
217
+ }
218
+ const current = normalizeFacetHeadersConfig(currentHeaders);
219
+ const owner = role === "all" ? "common" : role;
220
+ const patch = { ...current[owner] };
221
+ for (const key of HEADER_STYLE_OPTIONS) {
222
+ if (Object.hasOwn(args, key)) patch[key] = args[key];
223
+ }
224
+ if (Object.hasOwn(args, "side")) patch.side = args.side;
225
+ if (Object.hasOwn(args, "labelMap")) {
226
+ if (args.labelMap === "auto") delete patch.labelMap;
227
+ else {
228
+ patch.labelMap = normalizeDisplayLabelMap(
229
+ args.labelMap,
230
+ `${operation} ${role} labelMap`
231
+ );
232
+ }
233
+ }
234
+ const headers = {
235
+ ...current,
236
+ ...(role === "all" ? {} : { mode: "roles" }),
237
+ [owner]: patch
238
+ };
239
+ for (const headerRole of ["all", "row", "column"]) {
240
+ const resolved = resolveFacetHeaderConfig(headers, headerRole);
241
+ validatePositiveFinite(resolved.fontSize, `Facet ${headerRole} header fontSize`);
242
+ validateNonEmptyString(resolved.fontFamily, `Facet ${headerRole} header fontFamily`);
243
+ validateNonEmptyString(resolved.color, `Facet ${headerRole} header color`);
244
+ validateNonNegativeFinite(resolved.offset, `Facet ${headerRole} header offset`);
245
+ if (!(
246
+ (typeof resolved.fontWeight === "string" && resolved.fontWeight.length > 0) ||
247
+ Number.isFinite(resolved.fontWeight)
248
+ )) {
249
+ throw new TypeError(
250
+ `Facet ${headerRole} header fontWeight must be a non-empty string or number.`
251
+ );
252
+ }
253
+ if (!["start", "center", "end"].includes(resolved.align)) {
254
+ throw new Error(`Facet ${headerRole} header align must be start, center, or end.`);
255
+ }
256
+ if (resolved.labelMap !== undefined) {
257
+ normalizeDisplayLabelMap(
258
+ resolved.labelMap,
259
+ `Facet ${headerRole} header labelMap`
260
+ );
261
+ }
262
+ }
263
+ return headers;
264
+ }
265
+
266
+ function initialFacetHeaders(value, grid) {
267
+ let headers = createDefaultFacetHeaders();
268
+ if (value === undefined) return headers;
269
+ validateOptionObject(value, [...HEADER_STYLE_OPTIONS, "labelMap", "row", "column"], "facet.headers");
270
+ const { row, column, ...common } = value;
271
+ if (Object.keys(common).length > 0) headers = resolveEditedFacetHeaders(headers, common, grid, "facet.headers");
272
+ for (const [role, patch] of [["row", row], ["column", column]]) {
273
+ if (patch === undefined) continue;
274
+ validateOptionObject(patch, HEADER_OPTIONS.filter(key => key !== "role"), `facet.headers.${role}`);
275
+ headers = resolveEditedFacetHeaders(headers, { ...patch, role }, grid, `facet.headers.${role}`);
276
+ }
277
+ return headers;
278
+ }
279
+
280
+ function composeDerivedFacet(program, args, { definition, derived, guides, scalePolicies, facet }) {
281
+ const headers = initialFacetHeaders(args.headers, definition.grid);
282
+ const preflight = resolveFacetLayout({
283
+ spacing: args.spacing,
284
+ plots: Object.entries(derived.children).map(([id, child]) => ({ id, ...resolveGraphicBounds(child) })),
285
+ children: definition.cells.map(cell => ({
286
+ ...compositionChildDescriptor(cell.id, derived.children[cell.id]), value: cell.value,
287
+ ...(definition.grid === undefined ? {} : { row: cell.row, column: cell.column })
288
+ })),
289
+ columns: definition.grid?.columns.values.length ?? args.columns,
290
+ gap: args.gap, align: args.align, padding: args.padding,
291
+ sharedLegend: guides.legend === "shared"
292
+ });
293
+ const compositionSpec = {
294
+ id: definition.id, type: "facet", children: definition.cells.map(cell => cell.id),
295
+ columns: preflight.columns, gap: preflight.gap, align: preflight.align, padding: preflight.padding,
296
+ ...(preflight.spacing === undefined ? {} : { spacing: preflight.spacing }),
297
+ facet: { data: definition.data, ...facet, scales: scalePolicies.channels, guides }
298
+ };
299
+ return applyCompositionState(
300
+ program._withMaterializationConfig(["facets", definition.id], { headers }),
301
+ { children: derived.children, compositionSpec }, compositionSpec.children
302
+ );
303
+ }
304
+
305
+ function partitionFacetAction(op, description, definitionResolver) {
306
+ return closedAction({ op, description }, op === "facet" ? FACET_OPTIONS : FACET_GRID_OPTIONS,
307
+ function (args = {}) {
308
+ const guides = normalizeGuides(args.guides);
309
+ const definition = definitionResolver(this.semanticSpec, args);
310
+ if (definition.family !== "cartesian" && guides.axes === "outer") {
311
+ throw new Error("Polar and Parallel facets do not support outer axes.");
312
+ }
313
+ const scalePolicies = normalizeFacetScalePolicies(this.semanticSpec, args.scales ?? {});
314
+ const derived = deriveFacetChildren(this, definition, {
315
+ closeInheritedAction: true, stripTitle: true, scales: args.scales ?? {}
316
+ });
317
+ const facet = definition.grid === undefined
318
+ ? { field: definition.field, values: definition.values }
319
+ : { values: definition.cells.map(cell => cell.value), grid: {
320
+ ...definition.grid, cells: definition.cells.map(cell => ({
321
+ id: cell.id, row: cell.row, column: cell.column,
322
+ rowValue: cell.rowValue, columnValue: cell.columnValue, empty: cell.empty
323
+ }))
324
+ } };
325
+ return composeDerivedFacet(this, args, { definition, derived, guides, scalePolicies, facet });
326
+ });
327
+ }
328
+
329
+ export const facet = /* @__PURE__ */ partitionFacetAction("facet",
330
+ "Repeat one direct-source chart by field value.", resolveFacetDefinition);
246
331
 
247
332
  function resolveRepeatDefinition(program, args) {
248
333
  if (!isPlainObject(args)) {
@@ -453,115 +538,16 @@ export const repeatCharts = /* @__PURE__ */ closedAction(
453
538
  requestedScales,
454
539
  true
455
540
  );
456
- const preflight = resolveFacetLayout({
457
- children: definition.cells.map(cell => ({
458
- ...compositionChildDescriptor(cell.id, derived.children[cell.id]),
459
- value: cell.value
460
- })),
461
- ...(Object.hasOwn(args, "columns") ? { columns: args.columns } : {}),
462
- ...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
463
- ...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
464
- ...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
465
- sharedLegend: guides.legend === "shared"
541
+ return composeDerivedFacet(this, args, {
542
+ definition, derived, guides, scalePolicies,
543
+ facet: { values: definition.fields,
544
+ repeat: { target: definition.target, channel: definition.channel, fields: definition.fields } }
466
545
  });
467
- const compositionSpec = {
468
- id: definition.id,
469
- type: "facet",
470
- children: definition.cells.map(cell => cell.id),
471
- columns: preflight.columns,
472
- gap: preflight.gap,
473
- align: preflight.align,
474
- padding: preflight.padding,
475
- facet: {
476
- data: definition.data,
477
- values: definition.fields,
478
- repeat: {
479
- target: definition.target,
480
- channel: definition.channel,
481
- fields: definition.fields
482
- },
483
- scales: scalePolicies.channels,
484
- guides
485
- }
486
- };
487
- return applyCompositionState(
488
- this._withMaterializationConfig(["facets", definition.id], {
489
- headers: createDefaultFacetHeaders()
490
- }),
491
- { children: derived.children, compositionSpec },
492
- compositionSpec.children
493
- );
494
546
  }
495
547
  );
496
548
 
497
- export const facetGrid = /* @__PURE__ */ closedAction(
498
- {
499
- op: "facetGrid",
500
- description: "Repeat one direct-source Cartesian chart across a row and column field grid."
501
- }, FACET_GRID_OPTIONS,
502
- function (args = {}) {
503
- const guides = normalizeGuides(args.guides);
504
- const definition = resolveFacetGridDefinition(this.semanticSpec, args);
505
- if (definition.family !== "cartesian" && guides.axes === "outer") {
506
- throw new Error("Polar and Parallel facets do not support outer axes.");
507
- }
508
- const scalePolicies = normalizeFacetScalePolicies(
509
- this.semanticSpec,
510
- args.scales ?? {}
511
- );
512
- const derived = deriveFacetChildren(this, definition, {
513
- closeInheritedAction: true,
514
- stripTitle: true,
515
- scales: args.scales ?? {}
516
- });
517
- const preflight = resolveFacetLayout({
518
- children: definition.cells.map(cell => ({
519
- ...compositionChildDescriptor(cell.id, derived.children[cell.id]),
520
- value: cell.value,
521
- row: cell.row,
522
- column: cell.column
523
- })),
524
- columns: definition.grid.columns.values.length,
525
- ...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
526
- ...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
527
- ...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
528
- sharedLegend: guides.legend === "shared"
529
- });
530
- const compositionSpec = {
531
- id: definition.id,
532
- type: "facet",
533
- children: definition.cells.map(cell => cell.id),
534
- columns: preflight.columns,
535
- gap: preflight.gap,
536
- align: preflight.align,
537
- padding: preflight.padding,
538
- facet: {
539
- data: definition.data,
540
- values: definition.cells.map(cell => cell.value),
541
- grid: {
542
- ...definition.grid,
543
- cells: definition.cells.map(cell => ({
544
- id: cell.id,
545
- row: cell.row,
546
- column: cell.column,
547
- rowValue: cell.rowValue,
548
- columnValue: cell.columnValue,
549
- empty: cell.empty
550
- }))
551
- },
552
- scales: scalePolicies.channels,
553
- guides
554
- }
555
- };
556
- return applyCompositionState(
557
- this._withMaterializationConfig(["facets", definition.id], {
558
- headers: createDefaultFacetHeaders()
559
- }),
560
- { children: derived.children, compositionSpec },
561
- compositionSpec.children
562
- );
563
- }
564
- );
549
+ export const facetGrid = /* @__PURE__ */ partitionFacetAction("facetGrid",
550
+ "Repeat one direct-source Cartesian chart across a row and column field grid.", resolveFacetGridDefinition);
565
551
 
566
552
  export const editFacetHeaders = /* @__PURE__ */ action(
567
553
  {
@@ -576,78 +562,7 @@ export const editFacetHeaders = /* @__PURE__ */ action(
576
562
  if (!isPlainObject(config?.headers)) {
577
563
  throw new Error(`Facet "${id}" requires header configuration.`);
578
564
  }
579
- validateOptionObject(args, HEADER_OPTIONS, "editFacetHeaders", {
580
- allowEmpty: false,
581
- emptyMessage: "editFacetHeaders requires at least one change."
582
- });
583
- const role = args.role ?? "all";
584
- if (!["all", "row", "column"].includes(role)) {
585
- throw new Error(`Unknown facet header role "${role}".`);
586
- }
587
- if (role === "all" && Object.hasOwn(args, "side")) {
588
- throw new Error("editFacetHeaders side requires an explicit row or column role.");
589
- }
590
- if (role === "row" && this.compositionSpec.facet.grid === undefined) {
591
- throw new Error("editFacetHeaders row role requires a row-column facet grid.");
592
- }
593
- if (Object.hasOwn(args, "side")) {
594
- const sides = role === "row" ? ["left", "right"] : ["top", "bottom"];
595
- if (!sides.includes(args.side)) {
596
- throw new Error(`editFacetHeaders ${role} side must be ${sides.join(" or ")}.`);
597
- }
598
- }
599
- if (Object.hasOwn(args, "align") &&
600
- !["start", "center", "end"].includes(args.align)) {
601
- throw new Error("editFacetHeaders align must be start, center, or end.");
602
- }
603
- if (role === "all" && Object.keys(args).every(key => key === "role")) {
604
- throw new Error("editFacetHeaders requires at least one change.");
605
- }
606
- const current = normalizeFacetHeadersConfig(config.headers);
607
- const owner = role === "all" ? "common" : role;
608
- const patch = { ...current[owner] };
609
- for (const key of HEADER_STYLE_OPTIONS) {
610
- if (Object.hasOwn(args, key)) patch[key] = args[key];
611
- }
612
- if (Object.hasOwn(args, "side")) patch.side = args.side;
613
- if (Object.hasOwn(args, "labelMap")) {
614
- if (args.labelMap === "auto") delete patch.labelMap;
615
- else {
616
- patch.labelMap = normalizeDisplayLabelMap(
617
- args.labelMap,
618
- `editFacetHeaders ${role} labelMap`
619
- );
620
- }
621
- }
622
- const headers = {
623
- ...current,
624
- ...(role === "all" ? {} : { mode: "roles" }),
625
- [owner]: patch
626
- };
627
- for (const headerRole of ["all", "row", "column"]) {
628
- const resolved = resolveFacetHeaderConfig(headers, headerRole);
629
- validatePositiveFinite(resolved.fontSize, `Facet ${headerRole} header fontSize`);
630
- validateNonEmptyString(resolved.fontFamily, `Facet ${headerRole} header fontFamily`);
631
- validateNonEmptyString(resolved.color, `Facet ${headerRole} header color`);
632
- validateNonNegativeFinite(resolved.offset, `Facet ${headerRole} header offset`);
633
- if (!(
634
- (typeof resolved.fontWeight === "string" && resolved.fontWeight.length > 0) ||
635
- Number.isFinite(resolved.fontWeight)
636
- )) {
637
- throw new TypeError(
638
- `Facet ${headerRole} header fontWeight must be a non-empty string or number.`
639
- );
640
- }
641
- if (!["start", "center", "end"].includes(resolved.align)) {
642
- throw new Error(`Facet ${headerRole} header align must be start, center, or end.`);
643
- }
644
- if (resolved.labelMap !== undefined) {
645
- normalizeDisplayLabelMap(
646
- resolved.labelMap,
647
- `Facet ${headerRole} header labelMap`
648
- );
649
- }
650
- }
565
+ const headers = resolveEditedFacetHeaders(config.headers, args, this.compositionSpec.facet.grid);
651
566
  return this
652
567
  ._withMaterializationConfig(["facets", id], { ...config, headers })
653
568
  .materializeComposition();
@@ -783,6 +698,7 @@ export const editFacetSource = /* @__PURE__ */ closedAction(
783
698
  columns: current.facet.grid.columns,
784
699
  combinations: current.facet.grid.combinations,
785
700
  gap: current.gap,
701
+ ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
786
702
  align: current.align,
787
703
  padding: current.padding,
788
704
  scales,
@@ -794,6 +710,7 @@ export const editFacetSource = /* @__PURE__ */ closedAction(
794
710
  ...current.facet.repeat,
795
711
  columns: current.columns,
796
712
  gap: current.gap,
713
+ ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
797
714
  align: current.align,
798
715
  padding: current.padding,
799
716
  scales,
@@ -807,6 +724,7 @@ export const editFacetSource = /* @__PURE__ */ closedAction(
807
724
  values: current.facet.values,
808
725
  columns: current.columns,
809
726
  gap: current.gap,
727
+ ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
810
728
  align: current.align,
811
729
  padding: current.padding,
812
730
  scales,
@@ -1,5 +1,5 @@
1
1
  import { resolveOptionalUserId, validateUserId } from "../../core/identifiers.js";
2
- import { findLayer, hasLayer } from "../../selectors/layers.js";
2
+ import { findLayer, hasLayer, resolveConfiguredOwner } from "../../selectors/layers.js";
3
3
 
4
4
  const CATEGORICAL = Object.freeze(["nominal", "ordinal"]);
5
5
 
@@ -62,20 +62,5 @@ export function resolveGradientPlotId(program, requested) {
62
62
  }
63
63
 
64
64
  export function resolveGradientOwner(program, requested, operation) {
65
- const eligible = program.semanticSpec.layers.filter(
66
- layer => program.markConfigs[layer.id]?.gradientPlot?.materialized === true
67
- );
68
- if (requested !== undefined) {
69
- const id = validateUserId(requested, "Gradient-plot owner id");
70
- const layer = findLayer(program, id);
71
- if (layer === undefined || !eligible.includes(layer)) {
72
- throw new Error(`Unknown gradient-plot owner "${id}".`);
73
- }
74
- return layer;
75
- }
76
- const current = findLayer(program, program.context.currentMark);
77
- if (current !== undefined && eligible.includes(current)) return current;
78
- if (eligible.length === 1) return eligible[0];
79
- if (eligible.length === 0) throw new Error(`${operation} requires a gradient plot.`);
80
- throw new Error(`${operation} target is ambiguous; provide target.`);
65
+ return resolveConfiguredOwner(program, requested, { config: "gradientPlot", operation, kind: "gradient", materialized: true }, validateUserId);
81
66
  }
@@ -56,7 +56,7 @@ export function hasInferableLegend(program, layers = program.semanticSpec.layers
56
56
  (layer.mark?.type === "point" &&
57
57
  layer.encoding?.color?.scale !== undefined &&
58
58
  (layer.encoding?.shape?.scale !== undefined ||
59
- ["sequential", "quantize", "quantile", "threshold"].includes(
59
+ ["sequential", "log", "sqrt", "symlog", "quantize", "quantile", "threshold"].includes(
60
60
  findSemanticScale(program, layer.encoding.color.scale)?.type
61
61
  ))) ||
62
62
  (layer.mark?.type === "line" &&
@@ -6,6 +6,8 @@ import { validateUserId } from "../../../core/identifiers.js";
6
6
  import { validateKeys } from "../../../core/validation.js";
7
7
  import { hasCoordinate } from "../../../selectors/index.js";
8
8
  import { validateEnabledAxisComponents } from "./components.js";
9
+ import { requireSemanticScale } from "../../../selectors/scales.js";
10
+ import { requireCoordinate } from "../../../selectors/coordinates.js";
9
11
 
10
12
  const TOP_OPTIONS = Object.freeze([
11
13
  "scale",
@@ -100,16 +102,26 @@ function makeCreateAxis(channel) {
100
102
  if (!exists) {
101
103
  throw new Error(`Unknown coordinate "${coordinate}".`);
102
104
  }
103
- if (!hasConsumer) {
104
- throw new Error(
105
- `${operation.create} found no ${channel} encoding for coordinate "${coordinate}" and scale "${scale}".`
106
- );
105
+ const standalone = !hasConsumer;
106
+ if (standalone && (!Object.hasOwn(args, "scale") ||
107
+ next.semanticSpec.layers.some(layer => Object.values(layer.encoding ?? {}).some(encoding => encoding?.scale === scale)))) {
108
+ throw new Error(`${operation.create} found no ${channel} encoding for coordinate "${coordinate}" and scale "${scale}"; standalone binding requires an explicit unused scale.`);
109
+ }
110
+ if (standalone) {
111
+ const definition = requireSemanticScale(next, scale);
112
+ if (!Array.isArray(definition.domain) || definition.domain.length === 0 ||
113
+ requireCoordinate(next, coordinate).type !== "cartesian") {
114
+ throw new Error("Standalone axes require a Cartesian coordinate and explicit nonempty scale domain.");
115
+ }
107
116
  }
108
117
 
109
118
  next = next.editSemantic({
110
119
  property: `guide.axis.${channel}.coordinate`,
111
120
  value: coordinate
112
121
  });
122
+ if (standalone) next = next._withGuideConfig(channel, "binding", { standalone: true })
123
+ .editSemantic({ property: `guide.axis.${channel}.scale`, value: scale })
124
+ .rematerializeScale({ id: scale, guides: false, marks: false });
113
125
  }
114
126
 
115
127
  for (const component of ["line", "ticksAndLabels", "title"]) {
@@ -16,12 +16,12 @@ const OPTIONS = Object.freeze([
16
16
  ]);
17
17
  const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
18
18
  const TICK_OPTIONS = Object.freeze([
19
- "count", "values", "length", "color", "lineWidth"
19
+ "count", "values", "length", "color", "lineWidth", "alongOffset"
20
20
  ]);
21
21
  const LABEL_OPTIONS = Object.freeze([
22
22
  "count", "values", "offset", "format", "color", "fontSize",
23
23
  "fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
24
- "lineHeight", "overlap"
24
+ "lineHeight", "overlap", "alongOffset", "labelMap", "flush", "colorMap"
25
25
  ]);
26
26
  const GROUP_OPTIONS = Object.freeze(["count", "values", "ticks", "labels"]);
27
27
  const TITLE_OPTIONS = Object.freeze([
@@ -59,12 +59,12 @@ function validateArgs(args, operation) {
59
59
  );
60
60
  }
61
61
  if (args.ticksAndLabels !== false && args.ticksAndLabels?.ticks !== undefined) {
62
- validateNested(args.ticksAndLabels, "ticks", ["length", "color", "lineWidth"], `${operation}.ticksAndLabels`);
62
+ validateNested(args.ticksAndLabels, "ticks", ["length", "color", "lineWidth", "alongOffset"], `${operation}.ticksAndLabels`);
63
63
  }
64
64
  if (args.ticksAndLabels !== false && args.ticksAndLabels?.labels !== undefined) {
65
65
  validateNested(args.ticksAndLabels, "labels", [
66
66
  "offset", "format", "color", "fontSize", "fontFamily", "fontWeight",
67
- "rotation", "maxWidth", "wrap", "lineHeight", "overlap"
67
+ "rotation", "maxWidth", "wrap", "lineHeight", "overlap", "alongOffset", "labelMap", "flush", "colorMap"
68
68
  ], `${operation}.ticksAndLabels`);
69
69
  }
70
70
  }