ggaction 0.0.13 → 0.0.15

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 (270) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +8 -3
  7. package/src/actions/basic.js +2 -2
  8. package/src/actions/boxPlots/components.js +14 -4
  9. package/src/actions/boxPlots/edit.js +31 -5
  10. package/src/actions/boxPlots/materialize.js +11 -3
  11. package/src/actions/boxPlots/options.js +48 -16
  12. package/src/actions/categoryOrder/index.js +15 -4
  13. package/src/actions/charts/area.js +5 -1
  14. package/src/actions/charts/bar.js +2 -1
  15. package/src/actions/charts/density.js +6 -2
  16. package/src/actions/charts/ecdf.js +4 -1
  17. package/src/actions/charts/endpoints.js +25 -3
  18. package/src/actions/charts/heatmap.js +2 -1
  19. package/src/actions/charts/histogram.js +4 -3
  20. package/src/actions/charts/horizon.js +3 -1
  21. package/src/actions/charts/interval-regression.js +5 -2
  22. package/src/actions/charts/line.js +3 -1
  23. package/src/actions/charts/parallel.js +8 -2
  24. package/src/actions/charts/pie.js +10 -2
  25. package/src/actions/charts/polar.js +15 -4
  26. package/src/actions/charts/radar.js +8 -2
  27. package/src/actions/charts/radial.js +10 -2
  28. package/src/actions/charts/rug-strip.js +6 -2
  29. package/src/actions/charts/scatter.js +3 -1
  30. package/src/actions/composition/actions.js +13 -4
  31. package/src/actions/coordinates/actions.js +1 -1
  32. package/src/actions/coordinates/edit.js +76 -0
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/bin2d.js +57 -185
  35. package/src/actions/data/complete.js +26 -0
  36. package/src/actions/data/computed.js +2 -2
  37. package/src/actions/data/create.js +2 -2
  38. package/src/actions/data/density.js +12 -20
  39. package/src/actions/data/derived.js +47 -17
  40. package/src/actions/data/edit.js +539 -0
  41. package/src/actions/data/filter.js +14 -2
  42. package/src/actions/data/impute.js +26 -0
  43. package/src/actions/data/index.js +14 -0
  44. package/src/actions/data/normalize.js +27 -0
  45. package/src/actions/data/regression.js +16 -21
  46. package/src/actions/data/revisionBindings.js +145 -0
  47. package/src/actions/data/shared.js +20 -4
  48. package/src/actions/data/summary.js +1 -1
  49. package/src/actions/data/timeUnit.js +4 -1
  50. package/src/actions/data/window.js +1 -1
  51. package/src/actions/encodings/channels.js +415 -0
  52. package/src/actions/encodings/color/index.js +2 -1
  53. package/src/actions/encodings/density.js +5 -2
  54. package/src/actions/encodings/histogram.js +3 -2
  55. package/src/actions/encodings/index.js +4 -0
  56. package/src/actions/encodings/offset.js +20 -13
  57. package/src/actions/encodings/position/apply.js +53 -7
  58. package/src/actions/encodings/position/index.js +10 -2
  59. package/src/actions/encodings/position/policies/bar.js +9 -0
  60. package/src/actions/encodings/position/resolve.js +24 -7
  61. package/src/actions/encodings/remove.js +9 -4
  62. package/src/actions/encodings/ruleAppearance.js +1 -23
  63. package/src/actions/encodings/seriesLayout.js +1 -0
  64. package/src/actions/encodings/shared.js +82 -14
  65. package/src/actions/encodings/stroke.js +226 -0
  66. package/src/actions/errorBands/create.js +23 -3
  67. package/src/actions/errorBands/edit.js +21 -6
  68. package/src/actions/errorBars/create.js +35 -7
  69. package/src/actions/errorBars/edit.js +19 -4
  70. package/src/actions/facets/actions.js +237 -57
  71. package/src/actions/facets/derive.js +91 -26
  72. package/src/actions/gradientPlots/components.js +10 -2
  73. package/src/actions/gradientPlots/edit.js +14 -4
  74. package/src/actions/gradientPlots/materialize.js +3 -1
  75. package/src/actions/gradientPlots/options.js +10 -2
  76. package/src/actions/guides/applicability.js +8 -2
  77. package/src/actions/guides/axes/labels.js +58 -12
  78. package/src/actions/guides/axes/lines.js +6 -2
  79. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  80. package/src/actions/guides/axes/tickGroups.js +2 -1
  81. package/src/actions/guides/axes/ticks.js +6 -2
  82. package/src/actions/guides/axes/titles.js +11 -3
  83. package/src/actions/guides/grids/resolve.js +7 -2
  84. package/src/actions/guides/guides.js +19 -3
  85. package/src/actions/guides/legends/blocks.js +426 -0
  86. package/src/actions/guides/legends/categorical/actions.js +134 -48
  87. package/src/actions/guides/legends/categorical/components.js +17 -12
  88. package/src/actions/guides/legends/categorical/index.js +37 -33
  89. package/src/actions/guides/legends/categorical/layout.js +97 -15
  90. package/src/actions/guides/legends/categorical/options.js +2 -2
  91. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  92. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  93. package/src/actions/guides/legends/categorical/symbols.js +85 -30
  94. package/src/actions/guides/legends/continuous/common.js +30 -12
  95. package/src/actions/guides/legends/continuous/gradient.js +10 -7
  96. package/src/actions/guides/legends/continuous/index.js +2 -0
  97. package/src/actions/guides/legends/continuous/interval.js +8 -3
  98. package/src/actions/guides/legends/continuous/opacity.js +8 -5
  99. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  100. package/src/actions/guides/legends/creation.js +14 -1
  101. package/src/actions/guides/legends/edit.js +158 -59
  102. package/src/actions/guides/legends/index.js +2 -0
  103. package/src/actions/guides/legends/lane.js +54 -19
  104. package/src/actions/guides/legends/lifecycle.js +19 -8
  105. package/src/actions/guides/legends/remove.js +13 -3
  106. package/src/actions/guides/legends/sampling.js +140 -0
  107. package/src/actions/guides/legends/size.js +93 -40
  108. package/src/actions/guides/legends/strokeWidth.js +34 -28
  109. package/src/actions/guides/legends/target.js +112 -1
  110. package/src/actions/guides/legends/transition.js +165 -16
  111. package/src/actions/guides/polar/axes/facade.js +4 -4
  112. package/src/actions/guides/polar/axes/labels.js +24 -5
  113. package/src/actions/guides/polar/axes/lines.js +10 -4
  114. package/src/actions/guides/polar/axes/shared.js +16 -0
  115. package/src/actions/guides/polar/axes/ticks.js +1 -1
  116. package/src/actions/guides/polar/axes/titles.js +1 -1
  117. package/src/actions/guides/polar/grids.js +1 -1
  118. package/src/actions/guides/polar/resolve.js +12 -8
  119. package/src/actions/index.js +14 -3
  120. package/src/actions/marks/arc/actions.js +77 -19
  121. package/src/actions/marks/area/actions.js +34 -9
  122. package/src/actions/marks/area/materialize.js +17 -1
  123. package/src/actions/marks/bar/create.js +11 -2
  124. package/src/actions/marks/bar/edit.js +20 -3
  125. package/src/actions/marks/bar/materialize.js +22 -2
  126. package/src/actions/marks/index.js +10 -1
  127. package/src/actions/marks/line/actions.js +37 -14
  128. package/src/actions/marks/line/materialize.js +29 -10
  129. package/src/actions/marks/point/create.js +8 -2
  130. package/src/actions/marks/point/edit.js +18 -6
  131. package/src/actions/marks/point/materialize.js +49 -15
  132. package/src/actions/marks/rect/actions.js +33 -12
  133. package/src/actions/marks/references.js +601 -30
  134. package/src/actions/marks/remove.js +71 -1
  135. package/src/actions/marks/rule/actions.js +47 -10
  136. package/src/actions/marks/shared.js +1 -1
  137. package/src/actions/marks/text/actions.js +74 -9
  138. package/src/actions/marks/text/index.js +4 -0
  139. package/src/actions/marks/text/layout.js +154 -6
  140. package/src/actions/marks/text/removal.js +173 -0
  141. package/src/actions/marks/text/selection.js +73 -0
  142. package/src/actions/marks/tick/actions.js +22 -5
  143. package/src/actions/primitives/editGraphics.js +26 -0
  144. package/src/actions/primitives/semantic.js +2 -1
  145. package/src/actions/primitives/semanticAction.js +83 -10
  146. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  147. package/src/actions/primitives/semanticValidation/index.js +9 -1
  148. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  149. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  150. package/src/actions/regression/components.js +26 -11
  151. package/src/actions/regression/create.js +10 -2
  152. package/src/actions/regression/edit.js +21 -7
  153. package/src/actions/resources/index.js +7 -0
  154. package/src/actions/resources/remove.js +172 -0
  155. package/src/actions/scales/channels.js +212 -0
  156. package/src/actions/scales/consumers/common.js +1 -1
  157. package/src/actions/scales/consumers/families.js +3 -2
  158. package/src/actions/scales/consumers/index.js +19 -2
  159. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  160. package/src/actions/scales/create.js +54 -22
  161. package/src/actions/scales/definitions.js +94 -40
  162. package/src/actions/scales/edit.js +66 -11
  163. package/src/actions/scales/editPolicy.js +81 -11
  164. package/src/actions/scales/index.js +6 -0
  165. package/src/actions/scales/materialize.js +4 -3
  166. package/src/actions/scales/offset.js +48 -0
  167. package/src/actions/scales/parallel.js +71 -0
  168. package/src/actions/scales/preview.js +111 -10
  169. package/src/actions/selection/actions.js +154 -30
  170. package/src/actions/selection/index.js +3 -0
  171. package/src/actions/theme/actions.js +68 -13
  172. package/src/actions/theme/composition.js +238 -0
  173. package/src/actions/theme/index.js +9 -2
  174. package/src/actions/theme/reconcile.js +315 -67
  175. package/src/actions/theme/state.js +160 -0
  176. package/src/actions/violinPlots/create.js +5 -2
  177. package/src/actions/violinPlots/edit.js +1 -0
  178. package/src/core/action.js +10 -0
  179. package/src/core/compositionState.js +6 -1
  180. package/src/core/programState.js +20 -0
  181. package/src/core/resourceReferences.js +332 -0
  182. package/src/core/vocabulary.js +16 -3
  183. package/src/grammar/bars/aggregate.js +4 -1
  184. package/src/grammar/bars/geometry.js +44 -0
  185. package/src/grammar/bin.js +54 -9
  186. package/src/grammar/complete.js +279 -0
  187. package/src/grammar/computed.js +265 -64
  188. package/src/grammar/coordinates.js +37 -0
  189. package/src/grammar/density.js +239 -37
  190. package/src/grammar/displayLabels.js +57 -0
  191. package/src/grammar/facets/dependencies.js +133 -1
  192. package/src/grammar/facets/guides.js +3 -0
  193. package/src/grammar/facets/index.js +7 -47
  194. package/src/grammar/facets/scales.js +15 -18
  195. package/src/grammar/impute.js +340 -0
  196. package/src/grammar/markLabelSelection.js +57 -0
  197. package/src/grammar/markLabels.js +18 -1
  198. package/src/grammar/normalize.js +366 -0
  199. package/src/grammar/pathSeries.js +26 -8
  200. package/src/grammar/pointShapes.js +13 -4
  201. package/src/grammar/polar.js +90 -8
  202. package/src/grammar/regression/index.js +1 -0
  203. package/src/grammar/regression/parameters.js +23 -0
  204. package/src/grammar/roundedRect.js +180 -0
  205. package/src/grammar/rules.js +1 -0
  206. package/src/grammar/scales/colorConsumers.js +20 -1
  207. package/src/grammar/scales/continuous.js +2 -2
  208. package/src/grammar/scales/definition.js +27 -4
  209. package/src/grammar/scales/index.js +1 -0
  210. package/src/grammar/scales/ordinal.js +12 -3
  211. package/src/grammar/scales/policies.js +2 -2
  212. package/src/grammar/scales/size.js +456 -0
  213. package/src/grammar/schemas/concreteGraphic.js +14 -2
  214. package/src/grammar/schemas/graphic.js +15 -0
  215. package/src/grammar/schemas/graphicBounds.js +179 -30
  216. package/src/grammar/schemas/semanticPath.js +7 -2
  217. package/src/grammar/statisticalReference.js +103 -0
  218. package/src/grammar/strokeStyle.js +77 -0
  219. package/src/grammar/summary.js +78 -16
  220. package/src/grammar/timeUnit.js +216 -5
  221. package/src/grammar/transformTopology.js +4 -0
  222. package/src/grammar/transforms.js +394 -28
  223. package/src/grammar/weightedStatistics.js +374 -0
  224. package/src/grammar/window.js +221 -16
  225. package/src/layout/aspect.js +80 -0
  226. package/src/layout/facets.js +59 -5
  227. package/src/layout/labels.js +343 -0
  228. package/src/materialization/bars/aggregate.js +25 -2
  229. package/src/materialization/bars/histogram.js +84 -24
  230. package/src/materialization/bars/ranged.js +19 -3
  231. package/src/materialization/bars/resolve.js +7 -4
  232. package/src/materialization/composition.js +3 -1
  233. package/src/materialization/coordinateBounds.js +204 -0
  234. package/src/materialization/dependencies.js +36 -0
  235. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  236. package/src/materialization/facetGuides/placement.js +8 -2
  237. package/src/materialization/facetGuides/preparation.js +12 -7
  238. package/src/materialization/facetHeaders.js +346 -0
  239. package/src/materialization/facets.js +15 -65
  240. package/src/materialization/guides/resources.js +26 -0
  241. package/src/materialization/legends.js +1 -1
  242. package/src/materialization/marks/index.js +11 -5
  243. package/src/materialization/marks/policies.js +2 -1
  244. package/src/materialization/rect.js +25 -12
  245. package/src/materialization/rectConfig.js +5 -1
  246. package/src/materialization/scales/map.js +5 -3
  247. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  248. package/src/materialization/scales/policies/offset.js +50 -8
  249. package/src/materialization/scales/policies/series.js +4 -1
  250. package/src/materialization/scales/resolve.js +22 -6
  251. package/src/materialization/selection/items/arc.js +28 -4
  252. package/src/materialization/selection/items/bar.js +30 -3
  253. package/src/materialization/selection/items/common.js +19 -4
  254. package/src/materialization/selection/items/rect.js +28 -4
  255. package/src/materialization/selection/path.js +1 -1
  256. package/src/materialization/selection/point.js +3 -1
  257. package/src/materialization/text.js +265 -14
  258. package/src/renderers/canvas/circle.js +2 -3
  259. package/src/renderers/canvas/line.js +2 -3
  260. package/src/renderers/canvas/native.js +4 -4
  261. package/src/renderers/canvas/path.js +2 -3
  262. package/src/renderers/canvas/rect.js +2 -3
  263. package/src/renderers/canvas/stroke.js +11 -0
  264. package/src/renderers/svg.js +19 -0
  265. package/src/selectors/datasets.js +32 -1
  266. package/src/selectors/layers.js +4 -0
  267. package/src/theme/defaults.js +101 -0
  268. package/types/basic.d.ts +6 -0
  269. package/types/index.d.ts +85 -0
  270. package/types/program.d.ts +910 -117
@@ -11,6 +11,8 @@ import {
11
11
  resolveFacetDefinition,
12
12
  resolveFacetGridDefinition
13
13
  } from "../../grammar/facets/index.js";
14
+ import { collectFacetScaleBindings, resolveFacetFamily } from
15
+ "../../grammar/facets/dependencies.js";
14
16
  import {
15
17
  FACET_SCALE_CHANNELS,
16
18
  normalizeFacetScalePolicies
@@ -18,14 +20,21 @@ import {
18
20
  import { resolveFacetLayout } from "../../layout/facets.js";
19
21
  import { compositionChildDescriptor } from
20
22
  "../../materialization/composition.js";
21
- import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
22
23
  import { deriveFacetChildren } from "./derive.js";
23
24
  import { resolveFacetChildrenScales } from "./derive.js";
24
25
  import { replayDerivedData } from "./replay.js";
25
26
  import { composeFacetGuides } from "./guides.js";
26
27
  import { applyCompositionState } from "../composition/actions.js";
28
+ import { replayCompositionThemeState } from "../theme/composition.js";
27
29
  import { findDataset } from "../../selectors/datasets.js";
28
30
  import { findLayer } from "../../selectors/layers.js";
31
+ import { findSemanticScale } from "../../selectors/scales.js";
32
+ import { normalizeDisplayLabelMap } from "../../grammar/displayLabels.js";
33
+ import {
34
+ createDefaultFacetHeaders,
35
+ normalizeFacetHeadersConfig,
36
+ resolveFacetHeaderConfig
37
+ } from "../../materialization/facetHeaders.js";
29
38
 
30
39
  const FACET_OPTIONS = Object.freeze([
31
40
  "id", "field", "data", "values", "columns", "gap", "align", "padding", "scales",
@@ -42,15 +51,12 @@ const REPEAT_OPTIONS = Object.freeze([
42
51
  const SOURCE_EDIT_OPTIONS = Object.freeze(["program"]);
43
52
  const GUIDE_OPTIONS = Object.freeze(["axes", "legend"]);
44
53
  const HEADER_OPTIONS = Object.freeze([
45
- "fontSize", "fontFamily", "fontWeight", "color", "offset"
54
+ "fontSize", "fontFamily", "fontWeight", "color", "offset", "role",
55
+ "labelMap", "side", "align"
56
+ ]);
57
+ const HEADER_STYLE_OPTIONS = Object.freeze([
58
+ "fontSize", "fontFamily", "fontWeight", "color", "offset", "align"
46
59
  ]);
47
- const DEFAULT_HEADERS = Object.freeze({
48
- fontSize: 12,
49
- fontFamily: DEFAULT_FONT_FAMILY,
50
- fontWeight: 600,
51
- color: DEFAULT_COLORS.strongText,
52
- offset: 10
53
- });
54
60
 
55
61
  function normalizeGuides(guides) {
56
62
  if (guides === undefined) return { axes: "each", legend: false };
@@ -74,11 +80,14 @@ function requireFacetProgram(program, operation) {
74
80
  }
75
81
 
76
82
  function usedFacetScalePolicies(program, policies) {
77
- return Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
78
- program.semanticSpec.layers.some(
79
- layer => layer.encoding?.[channel]?.scale !== undefined
80
- ) ? [[channel, policies[channel]]] : []
81
- ));
83
+ const used = new Set(
84
+ collectFacetScaleBindings(program.semanticSpec).map(binding => binding.policyKey)
85
+ );
86
+ return Object.fromEntries(
87
+ FACET_SCALE_CHANNELS.flatMap(channel =>
88
+ used.has(channel) ? [[channel, policies[channel]]] : []
89
+ )
90
+ );
82
91
  }
83
92
 
84
93
  function facetUnitTemplate(program) {
@@ -96,6 +105,9 @@ function facetUnitTemplate(program) {
96
105
  resolvedScales: program.resolvedScales,
97
106
  materializationConfigs: freezeOwned({
98
107
  ...unitConfigs,
108
+ ...(seed.materializationConfigs.theme === undefined
109
+ ? {}
110
+ : { theme: seed.materializationConfigs.theme }),
99
111
  ...(seed.materializationConfigs.guides === undefined
100
112
  ? {}
101
113
  : { guides: seed.materializationConfigs.guides }),
@@ -145,11 +157,7 @@ function rederiveFacet(program, { scales, guides }) {
145
157
  columns: current.facet.grid.columns,
146
158
  combinations: current.facet.grid.combinations
147
159
  });
148
- const request = Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
149
- program.semanticSpec.layers.some(
150
- layer => layer.encoding?.[channel]?.scale !== undefined
151
- ) ? [[channel, scales[channel]]] : []
152
- ));
160
+ const request = usedFacetScalePolicies(program, scales);
153
161
  const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
154
162
  const template = facetUnitTemplate(program);
155
163
  const derived = deriveFacetChildren(template, definition, {
@@ -184,6 +192,9 @@ export const facet = action(
184
192
  validateOptionObject(args, FACET_OPTIONS, "facet");
185
193
  const guides = normalizeGuides(args.guides);
186
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.");
197
+ }
187
198
  const scalePolicies = normalizeFacetScalePolicies(
188
199
  this.semanticSpec,
189
200
  args.scales ?? {}
@@ -222,7 +233,7 @@ export const facet = action(
222
233
  };
223
234
  return applyCompositionState(
224
235
  this._withMaterializationConfig(["facets", definition.id], {
225
- headers: DEFAULT_HEADERS
236
+ headers: createDefaultFacetHeaders()
226
237
  }),
227
238
  {
228
239
  children: derived.children,
@@ -238,8 +249,20 @@ function resolveRepeatDefinition(program, args) {
238
249
  throw new TypeError("repeatCharts options must be a plain object.");
239
250
  }
240
251
  const id = validateUserId(args.id ?? "repeat", "Repeat id");
241
- if (!["x", "y"].includes(args.channel)) {
242
- throw new Error('repeatCharts channel must be "x" or "y".');
252
+ const parallelChannel = isPlainObject(args.channel);
253
+ if (parallelChannel) {
254
+ const keys = Object.keys(args.channel);
255
+ if (keys.length !== 1 || keys[0] !== "parallelDimension" ||
256
+ typeof args.channel.parallelDimension !== "string" ||
257
+ args.channel.parallelDimension.length === 0) {
258
+ throw new Error(
259
+ "repeatCharts Parallel channel must contain exactly one non-empty parallelDimension."
260
+ );
261
+ }
262
+ } else if (!["x", "y", "theta", "r"].includes(args.channel)) {
263
+ throw new Error(
264
+ 'repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.'
265
+ );
243
266
  }
244
267
  if (!Array.isArray(args.fields) || args.fields.length === 0 ||
245
268
  args.fields.some(field => typeof field !== "string" || field.length === 0)) {
@@ -248,29 +271,53 @@ function resolveRepeatDefinition(program, args) {
248
271
  if (new Set(args.fields).size !== args.fields.length) {
249
272
  throw new Error("repeatCharts fields must be unique.");
250
273
  }
251
- const eligible = program.semanticSpec.layers.filter(layer =>
252
- layer.encoding?.x?.scale !== undefined &&
253
- layer.encoding?.y?.scale !== undefined &&
254
- layer.encoding?.[args.channel]?.field !== undefined &&
255
- ["point", "line", "area", "bar", "rule", "tick", "rect"].includes(layer.mark?.type)
256
- );
274
+ const family = resolveFacetFamily(program.semanticSpec).family;
275
+ const semanticChannel = args.channel === "r" ? "radius" : args.channel;
276
+ const eligible = program.semanticSpec.layers.filter(layer => {
277
+ if (parallelChannel) {
278
+ const matches = (layer.encoding?.parallel?.dimensions ?? []).filter(
279
+ dimension => dimension.field === args.channel.parallelDimension
280
+ );
281
+ return family === "parallel" && layer.mark?.type === "line" && matches.length === 1;
282
+ }
283
+ if (["x", "y"].includes(args.channel)) {
284
+ return family === "cartesian" &&
285
+ layer.encoding?.x?.scale !== undefined &&
286
+ layer.encoding?.y?.scale !== undefined &&
287
+ layer.encoding?.[args.channel]?.field !== undefined &&
288
+ ["point", "line", "area", "bar", "rule", "tick", "rect"].includes(layer.mark?.type);
289
+ }
290
+ const role = program.markConfigs[layer.id]?.compositionRole;
291
+ if (family !== "polar" || ["pie", "radar"].includes(role)) return false;
292
+ return ["point", "line", "arc"].includes(layer.mark?.type) &&
293
+ layer.encoding?.[semanticChannel]?.field !== undefined;
294
+ });
257
295
  let target;
258
296
  if (args.target !== undefined) {
259
297
  target = validateUserId(args.target, "Repeat target");
260
298
  if (!eligible.some(layer => layer.id === target)) {
261
- throw new Error(`repeatCharts target "${target}" is not an eligible Cartesian mark.`);
299
+ throw new Error(
300
+ `repeatCharts target "${target}" is not eligible for the requested field role.`
301
+ );
262
302
  }
263
303
  } else if (eligible.length === 1) {
264
304
  target = eligible[0].id;
265
305
  } else {
266
306
  throw new Error(
267
307
  eligible.length === 0
268
- ? "repeatCharts requires one complete Cartesian mark."
308
+ ? "repeatCharts requires one eligible complete mark for the requested field role."
269
309
  : "repeatCharts target is ambiguous; provide target."
270
310
  );
271
311
  }
272
- if (program.semanticSpec.layers.length !== 1) {
273
- throw new Error("repeatCharts currently supports one direct Cartesian mark only.");
312
+ const unrelatedLayers = program.semanticSpec.layers.filter(layer => {
313
+ if (layer.id === target) return false;
314
+ const attachedLabel = layer.mark?.type === "text" && layer.source === target;
315
+ const statisticalReference = program.markConfigs[layer.id]
316
+ ?.statisticalReference?.source === target;
317
+ return !attachedLabel && !statisticalReference;
318
+ });
319
+ if (unrelatedLayers.length > 0) {
320
+ throw new Error("repeatCharts currently supports one direct mark only.");
274
321
  }
275
322
  const config = program.markConfigs[target] ?? {};
276
323
  const composite = [
@@ -285,14 +332,23 @@ function resolveRepeatDefinition(program, args) {
285
332
  if (dataset?.transform?.length > 0) {
286
333
  throw new Error("repeatCharts does not rewrite a derived dataset dependency.");
287
334
  }
288
- const encoding = layer.encoding[args.channel];
335
+ const encoding = parallelChannel
336
+ ? layer.encoding.parallel
337
+ : layer.encoding[semanticChannel];
338
+ const dimensionIndex = parallelChannel
339
+ ? encoding.dimensions.findIndex(
340
+ dimension => dimension.field === args.channel.parallelDimension
341
+ )
342
+ : undefined;
289
343
  return {
290
344
  id,
291
345
  target,
292
- channel: args.channel,
346
+ channel: parallelChannel ? { ...args.channel } : args.channel,
347
+ policyKey: parallelChannel ? "parallelDimensions" : args.channel,
293
348
  fields: [...args.fields],
294
349
  data: layer.data,
295
350
  encoding,
351
+ ...(dimensionIndex === undefined ? {} : { dimensionIndex }),
296
352
  cells: args.fields.map((field, index) => ({
297
353
  id: `${id}-field-${index + 1}`,
298
354
  field,
@@ -316,15 +372,52 @@ function repeatEncodingArgs(definition, field) {
316
372
  };
317
373
  }
318
374
 
375
+ function parallelRepeatDimensions(program, definition, field) {
376
+ return definition.encoding.dimensions.map((dimension, index) => {
377
+ const semanticScale = findSemanticScale(program, dimension.scale);
378
+ if (semanticScale === undefined) {
379
+ throw new Error(`repeatCharts Parallel dimension scale "${dimension.scale}" is missing.`);
380
+ }
381
+ const { id: _id, ...scale } = semanticScale;
382
+ void _id;
383
+ const replacement = index === definition.dimensionIndex;
384
+ return {
385
+ field: replacement ? field : dimension.field,
386
+ fieldType: dimension.fieldType,
387
+ title: replacement && dimension.title === dimension.field
388
+ ? field
389
+ : dimension.title,
390
+ scale
391
+ };
392
+ });
393
+ }
394
+
395
+ function deriveRepeatedProgram(template, definition, field) {
396
+ if (isPlainObject(definition.channel)) {
397
+ return template.encodeParallelCoordinates({
398
+ target: definition.target,
399
+ coordinate: findLayer(template, definition.target).coordinate,
400
+ dimensions: parallelRepeatDimensions(template, definition, field),
401
+ ...(definition.encoding.key === undefined ? {} : { key: definition.encoding.key }),
402
+ missing: definition.encoding.missing
403
+ });
404
+ }
405
+ const operation = {
406
+ x: "encodeX",
407
+ y: "encodeY",
408
+ theta: "encodeTheta",
409
+ r: "encodeR"
410
+ }[definition.channel];
411
+ return template[operation](repeatEncodingArgs(definition, field));
412
+ }
413
+
319
414
  function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
320
415
  const template = base.semanticSpec.title.text === undefined
321
416
  ? base
322
417
  : base.removeTitle();
323
418
  const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
324
419
  cell.id,
325
- template[definition.channel === "x" ? "encodeX" : "encodeY"](
326
- repeatEncodingArgs(definition, cell.field)
327
- )
420
+ deriveRepeatedProgram(template, definition, cell.field)
328
421
  ]));
329
422
  return resolveFacetChildrenScales(
330
423
  template,
@@ -338,7 +431,7 @@ function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
338
431
  export const repeatCharts = action(
339
432
  {
340
433
  op: "repeatCharts",
341
- description: "Repeat one direct Cartesian chart across an ordered field list."
434
+ description: "Repeat one direct chart across an ordered field-role list."
342
435
  },
343
436
  function (args = {}) {
344
437
  validateOptionObject(args, REPEAT_OPTIONS, "repeatCharts");
@@ -349,7 +442,7 @@ export const repeatCharts = action(
349
442
  const definition = resolveRepeatDefinition(this, args);
350
443
  const requestedScales = {
351
444
  ...(args.scales ?? {}),
352
- [definition.channel]: args.scales?.[definition.channel] ?? "independent"
445
+ [definition.policyKey]: args.scales?.[definition.policyKey] ?? "independent"
353
446
  };
354
447
  const scalePolicies = normalizeFacetScalePolicies(
355
448
  this.semanticSpec,
@@ -394,7 +487,7 @@ export const repeatCharts = action(
394
487
  };
395
488
  return applyCompositionState(
396
489
  this._withMaterializationConfig(["facets", definition.id], {
397
- headers: DEFAULT_HEADERS
490
+ headers: createDefaultFacetHeaders()
398
491
  }),
399
492
  { children: derived.children, compositionSpec },
400
493
  compositionSpec.children
@@ -411,6 +504,9 @@ export const facetGrid = action(
411
504
  validateOptionObject(args, FACET_GRID_OPTIONS, "facetGrid");
412
505
  const guides = normalizeGuides(args.guides);
413
506
  const definition = resolveFacetGridDefinition(this.semanticSpec, args);
507
+ if (definition.family !== "cartesian" && guides.axes === "outer") {
508
+ throw new Error("Polar and Parallel facets do not support outer axes.");
509
+ }
414
510
  const scalePolicies = normalizeFacetScalePolicies(
415
511
  this.semanticSpec,
416
512
  args.scales ?? {}
@@ -461,7 +557,7 @@ export const facetGrid = action(
461
557
  };
462
558
  return applyCompositionState(
463
559
  this._withMaterializationConfig(["facets", definition.id], {
464
- headers: DEFAULT_HEADERS
560
+ headers: createDefaultFacetHeaders()
465
561
  }),
466
562
  { children: derived.children, compositionSpec },
467
563
  compositionSpec.children
@@ -486,18 +582,73 @@ export const editFacetHeaders = action(
486
582
  allowEmpty: false,
487
583
  emptyMessage: "editFacetHeaders requires at least one change."
488
584
  });
489
- const headers = { ...config.headers, ...args };
490
- validatePositiveFinite(headers.fontSize, "Facet header fontSize");
491
- validateNonEmptyString(headers.fontFamily, "Facet header fontFamily");
492
- validateNonEmptyString(headers.color, "Facet header color");
493
- validateNonNegativeFinite(headers.offset, "Facet header offset");
494
- if (!(
495
- (typeof headers.fontWeight === "string" && headers.fontWeight.length > 0) ||
496
- Number.isFinite(headers.fontWeight)
497
- )) {
498
- throw new TypeError(
499
- "Facet header fontWeight must be a non-empty string or number."
500
- );
585
+ const role = args.role ?? "all";
586
+ if (!["all", "row", "column"].includes(role)) {
587
+ throw new Error(`Unknown facet header role "${role}".`);
588
+ }
589
+ if (role === "all" && Object.hasOwn(args, "side")) {
590
+ throw new Error("editFacetHeaders side requires an explicit row or column role.");
591
+ }
592
+ if (role === "row" && this.compositionSpec.facet.grid === undefined) {
593
+ throw new Error("editFacetHeaders row role requires a row-column facet grid.");
594
+ }
595
+ if (Object.hasOwn(args, "side")) {
596
+ const sides = role === "row" ? ["left", "right"] : ["top", "bottom"];
597
+ if (!sides.includes(args.side)) {
598
+ throw new Error(`editFacetHeaders ${role} side must be ${sides.join(" or ")}.`);
599
+ }
600
+ }
601
+ if (Object.hasOwn(args, "align") &&
602
+ !["start", "center", "end"].includes(args.align)) {
603
+ throw new Error("editFacetHeaders align must be start, center, or end.");
604
+ }
605
+ if (role === "all" && Object.keys(args).every(key => key === "role")) {
606
+ throw new Error("editFacetHeaders requires at least one change.");
607
+ }
608
+ const current = normalizeFacetHeadersConfig(config.headers);
609
+ const owner = role === "all" ? "common" : role;
610
+ const patch = { ...current[owner] };
611
+ for (const key of HEADER_STYLE_OPTIONS) {
612
+ if (Object.hasOwn(args, key)) patch[key] = args[key];
613
+ }
614
+ if (Object.hasOwn(args, "side")) patch.side = args.side;
615
+ if (Object.hasOwn(args, "labelMap")) {
616
+ if (args.labelMap === "auto") delete patch.labelMap;
617
+ else {
618
+ patch.labelMap = normalizeDisplayLabelMap(
619
+ args.labelMap,
620
+ `editFacetHeaders ${role} labelMap`
621
+ );
622
+ }
623
+ }
624
+ const headers = {
625
+ ...current,
626
+ ...(role === "all" ? {} : { mode: "roles" }),
627
+ [owner]: patch
628
+ };
629
+ for (const headerRole of ["all", "row", "column"]) {
630
+ const resolved = resolveFacetHeaderConfig(headers, headerRole);
631
+ validatePositiveFinite(resolved.fontSize, `Facet ${headerRole} header fontSize`);
632
+ validateNonEmptyString(resolved.fontFamily, `Facet ${headerRole} header fontFamily`);
633
+ validateNonEmptyString(resolved.color, `Facet ${headerRole} header color`);
634
+ validateNonNegativeFinite(resolved.offset, `Facet ${headerRole} header offset`);
635
+ if (!(
636
+ (typeof resolved.fontWeight === "string" && resolved.fontWeight.length > 0) ||
637
+ Number.isFinite(resolved.fontWeight)
638
+ )) {
639
+ throw new TypeError(
640
+ `Facet ${headerRole} header fontWeight must be a non-empty string or number.`
641
+ );
642
+ }
643
+ if (!["start", "center", "end"].includes(resolved.align)) {
644
+ throw new Error(`Facet ${headerRole} header align must be start, center, or end.`);
645
+ }
646
+ if (resolved.labelMap !== undefined) {
647
+ normalizeDisplayLabelMap(
648
+ resolved.labelMap,
649
+ `Facet ${headerRole} header labelMap`
650
+ );
651
+ }
501
652
  }
502
653
  return this
503
654
  ._withMaterializationConfig(["facets", id], { ...config, headers })
@@ -518,8 +669,8 @@ export const editFacetScales = action(
518
669
  emptyMessage: "editFacetScales requires at least one channel policy change."
519
670
  });
520
671
  for (const channel of Object.keys(args)) {
521
- if (!this.semanticSpec.layers.some(
522
- layer => layer.encoding?.[channel]?.scale !== undefined
672
+ if (!collectFacetScaleBindings(this.semanticSpec).some(
673
+ binding => binding.policyKey === channel
523
674
  )) {
524
675
  throw new Error(
525
676
  `Facet scale channel "${channel}" is not used by an affected layer.`
@@ -554,6 +705,10 @@ export const editFacetGuides = action(
554
705
  });
555
706
  const current = this.compositionSpec.facet;
556
707
  const guides = normalizeGuides({ ...current.guides, ...args });
708
+ const family = resolveFacetFamily(this.semanticSpec).family;
709
+ if (family !== "cartesian" && guides.axes === "outer") {
710
+ throw new Error("Polar and Parallel facets do not support outer axes.");
711
+ }
557
712
  if (current.repeat !== undefined && guides.axes === "outer") {
558
713
  throw new Error("repeatCharts does not promote axes across different repeated fields.");
559
714
  }
@@ -574,11 +729,33 @@ function adoptUnitState(program, actionOwner) {
574
729
  if (program.actionStack.length !== 0) {
575
730
  throw new Error("editFacetSource program has an unfinished action stack.");
576
731
  }
732
+ const seedId = actionOwner.compositionSpec.children.find(id =>
733
+ actionOwner.children[id]?.semanticSpec.layers.length > 0
734
+ ) ?? actionOwner.compositionSpec.children[0];
735
+ const retained = actionOwner.children[seedId]?.materializationConfigs ?? {};
736
+ const {
737
+ canvas: _retainedCanvas,
738
+ theme: _retainedTheme,
739
+ facets: _retainedFacets,
740
+ ...retainedRecipe
741
+ } = retained;
742
+ void _retainedCanvas;
743
+ void _retainedTheme;
744
+ void _retainedFacets;
577
745
  return new actionOwner.constructor({
578
746
  semanticSpec: program.semanticSpec,
579
747
  graphicSpec: program.graphicSpec,
580
748
  resolvedScales: program.resolvedScales,
581
- materializationConfigs: program.materializationConfigs,
749
+ materializationConfigs: freezeOwned({
750
+ ...program.materializationConfigs,
751
+ ...retainedRecipe,
752
+ ...(program.materializationConfigs.canvas === undefined
753
+ ? {}
754
+ : { canvas: program.materializationConfigs.canvas }),
755
+ ...(program.materializationConfigs.theme === undefined
756
+ ? {}
757
+ : { theme: program.materializationConfigs.theme })
758
+ }),
582
759
  children: {},
583
760
  context: program.context,
584
761
  trace: actionOwner.trace,
@@ -653,7 +830,10 @@ export const editFacetSource = action(
653
830
  ...this.semanticSpec.title
654
831
  });
655
832
  }
656
- return revised;
833
+ return replayCompositionThemeState(
834
+ revised,
835
+ this.materializationConfigs.theme
836
+ );
657
837
  }
658
838
  );
659
839