ggaction 0.0.21 → 0.0.22

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 (253) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +3 -2
  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 +6 -2
  7. package/src/BasicChartProgram.js +1 -1
  8. package/src/accessibility.js +1 -0
  9. package/src/actions/boxPlots/components.js +1 -96
  10. package/src/actions/boxPlots/create.js +6 -1
  11. package/src/actions/boxPlots/edit.js +8 -0
  12. package/src/actions/boxPlots/materialize.js +3 -0
  13. package/src/actions/boxPlots/options.js +1 -1
  14. package/src/actions/canvas/actions.js +2 -1
  15. package/src/actions/canvas/index.js +3 -0
  16. package/src/actions/canvas/plotBorder.js +2 -1
  17. package/src/actions/canvas/plotOrder.js +14 -0
  18. package/src/actions/charts/area.js +1 -1
  19. package/src/actions/charts/bar.js +4 -4
  20. package/src/actions/charts/density.js +1 -1
  21. package/src/actions/charts/ecdf.js +1 -1
  22. package/src/actions/charts/endpoints.js +54 -5
  23. package/src/actions/charts/heatmap.js +1 -1
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +1 -1
  26. package/src/actions/charts/index.js +2 -1
  27. package/src/actions/charts/interval-regression.js +1 -1
  28. package/src/actions/charts/line.js +1 -1
  29. package/src/actions/charts/radar.js +63 -3
  30. package/src/actions/charts/raincloud.js +37 -6
  31. package/src/actions/charts/rug-strip.js +2 -2
  32. package/src/actions/charts/scatter.js +3 -3
  33. package/src/actions/charts/text.js +1 -1
  34. package/src/actions/composition/actions.js +10 -3
  35. package/src/actions/coordinates/actions.js +2 -0
  36. package/src/actions/data/basic.js +6 -0
  37. package/src/actions/data/derived.js +6 -2
  38. package/src/actions/data/edit.js +21 -35
  39. package/src/actions/data/filter.js +1 -376
  40. package/src/actions/data/flatten.js +19 -0
  41. package/src/actions/data/index.js +6 -0
  42. package/src/actions/data/lookup.js +50 -0
  43. package/src/actions/data/raincloudFilter.js +56 -0
  44. package/src/actions/data/shared.js +10 -0
  45. package/src/actions/distributions/revision.js +3 -1
  46. package/src/actions/encodings/angle.js +2 -1
  47. package/src/actions/encodings/appearance.js +38 -13
  48. package/src/actions/encodings/channels.js +14 -3
  49. package/src/actions/encodings/color/continuous.js +26 -10
  50. package/src/actions/encodings/color/index.js +50 -20
  51. package/src/actions/encodings/color/policy.js +57 -4
  52. package/src/actions/encodings/parallel.js +4 -0
  53. package/src/actions/encodings/position/policies/bar.js +10 -6
  54. package/src/actions/encodings/position/policies/line.js +1 -152
  55. package/src/actions/encodings/position/resolve.js +4 -1
  56. package/src/actions/encodings/ranged.js +46 -0
  57. package/src/actions/encodings/remove.js +1 -1
  58. package/src/actions/encodings/ruleAppearance.js +7 -6
  59. package/src/actions/encodings/shared.js +14 -2
  60. package/src/actions/encodings/stroke.js +20 -14
  61. package/src/actions/encodings/strokeDash.js +9 -11
  62. package/src/actions/encodings/text.js +30 -2
  63. package/src/actions/errorBars/edit.js +24 -4
  64. package/src/actions/facets/actions.js +1 -793
  65. package/src/actions/facets/derive.js +34 -5
  66. package/src/actions/facets/replay.js +1 -38
  67. package/src/actions/gradientPlots/components.js +21 -9
  68. package/src/actions/gradientPlots/create.js +4 -2
  69. package/src/actions/gradientPlots/edit.js +18 -1
  70. package/src/actions/gradientPlots/materialize.js +35 -36
  71. package/src/actions/gradientPlots/options.js +1 -1
  72. package/src/actions/gradientPlots/paint.js +13 -7
  73. package/src/actions/guides/applicability.js +2 -0
  74. package/src/actions/guides/axes/axes.js +39 -0
  75. package/src/actions/guides/axes/axis.js +9 -5
  76. package/src/actions/guides/axes/edit.js +11 -8
  77. package/src/actions/guides/axes/labels.js +24 -8
  78. package/src/actions/guides/axes/parallel/policy.js +3 -5
  79. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  80. package/src/actions/guides/axes/policy.js +22 -0
  81. package/src/actions/guides/axes/tickGroups.js +4 -2
  82. package/src/actions/guides/axes/ticks.js +22 -4
  83. package/src/actions/guides/axes/titles.js +19 -6
  84. package/src/actions/guides/facadeAxes.js +3 -0
  85. package/src/actions/guides/grids/grid.js +3 -1
  86. package/src/actions/guides/grids/resolve.js +7 -11
  87. package/src/actions/guides/guides.js +21 -1
  88. package/src/actions/guides/legends/categorical/actions.js +1 -515
  89. package/src/actions/guides/legends/categorical/layout.js +5 -2
  90. package/src/actions/guides/legends/categorical/options.js +4 -0
  91. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  92. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  93. package/src/actions/guides/legends/colorSize.js +2 -0
  94. package/src/actions/guides/legends/continuous/common.js +1 -1
  95. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  96. package/src/actions/guides/legends/continuous/interval.js +1 -1
  97. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  98. package/src/actions/guides/legends/edit.js +1 -1
  99. package/src/actions/guides/legends/focused.js +1 -1
  100. package/src/actions/guides/legends/lane.js +1 -1
  101. package/src/actions/guides/legends/sampling.js +1 -140
  102. package/src/actions/guides/legends/strokeWidth.js +3 -2
  103. package/src/actions/guides/legends/transition.js +19 -2
  104. package/src/actions/guides/polar/axes/facade.js +5 -4
  105. package/src/actions/guides/polar/axes/labels.js +5 -4
  106. package/src/actions/guides/polar/axes/titles.js +5 -4
  107. package/src/actions/guides/polar/resolve.js +2 -1
  108. package/src/actions/guides/tickValues.js +7 -7
  109. package/src/actions/marks/arc/actions.js +5 -7
  110. package/src/actions/marks/area/actions.js +9 -3
  111. package/src/actions/marks/bar/create.js +4 -2
  112. package/src/actions/marks/bar/edit.js +9 -6
  113. package/src/actions/marks/bar/materialize.js +5 -3
  114. package/src/actions/marks/dependentReferences.js +87 -0
  115. package/src/actions/marks/index.js +2 -0
  116. package/src/actions/marks/lifecycle.js +2 -1
  117. package/src/actions/marks/line/actions.js +24 -6
  118. package/src/actions/marks/line/materialize.js +7 -3
  119. package/src/actions/marks/point/create.js +5 -4
  120. package/src/actions/marks/point/edit.js +6 -3
  121. package/src/actions/marks/point/jitter.js +6 -6
  122. package/src/actions/marks/point/materialize.js +36 -39
  123. package/src/actions/marks/rect/actions.js +16 -4
  124. package/src/actions/marks/references.js +183 -35
  125. package/src/actions/marks/rule/actions.js +20 -9
  126. package/src/actions/marks/shared.js +2 -1
  127. package/src/actions/marks/text/actions.js +10 -3
  128. package/src/actions/marks/tick/actions.js +11 -2
  129. package/src/actions/primitives/editGraphics.js +28 -3
  130. package/src/actions/primitives/index.js +9 -0
  131. package/src/actions/primitives/semanticAction.js +13 -7
  132. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  133. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  134. package/src/actions/scales/channels.js +1 -1
  135. package/src/actions/scales/consumers/common.js +19 -1
  136. package/src/actions/scales/consumers/families.js +2 -2
  137. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  138. package/src/actions/scales/edit.js +11 -0
  139. package/src/actions/scales/editPolicy.js +5 -3
  140. package/src/actions/scales/preview.js +2 -1
  141. package/src/actions/scales/quantitativeColor.js +2 -4
  142. package/src/actions/selection/actions.js +10 -1
  143. package/src/actions/theme/reconcile.js +37 -9
  144. package/src/core/compositionState.js +21 -4
  145. package/src/core/datasetReferences.js +17 -0
  146. package/src/core/immutable.js +1 -0
  147. package/src/core/resourceReferences.js +5 -3
  148. package/src/core/validation.js +4 -0
  149. package/src/core/vocabulary.js +24 -2
  150. package/src/grammar/aggregate.js +1 -0
  151. package/src/grammar/arcs.js +1 -1
  152. package/src/grammar/areaSeries.js +1 -1
  153. package/src/grammar/axisTextPlacement.js +1 -1
  154. package/src/grammar/bars/policy.js +2 -2
  155. package/src/grammar/datasetDependencies.js +97 -0
  156. package/src/grammar/datasetSchema.js +31 -1
  157. package/src/grammar/facetHeaders.js +1 -0
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/legendIntent.js +6 -2
  160. package/src/grammar/facets/scales.js +27 -7
  161. package/src/grammar/flatten.js +85 -0
  162. package/src/grammar/histogram.js +13 -1
  163. package/src/grammar/horizon.js +1 -494
  164. package/src/grammar/jitter.js +2 -2
  165. package/src/grammar/legendSpacing.js +45 -0
  166. package/src/grammar/lineMissing.js +90 -0
  167. package/src/grammar/lineSeries.js +1 -376
  168. package/src/grammar/lookup.js +97 -0
  169. package/src/grammar/markFilter.js +4 -2
  170. package/src/grammar/markLabels.js +8 -3
  171. package/src/grammar/numeric.js +1 -1
  172. package/src/grammar/pathCommands.js +1 -1
  173. package/src/grammar/pathSeries.js +1 -109
  174. package/src/grammar/pointPolygon.js +81 -0
  175. package/src/grammar/pointShapes.js +15 -15
  176. package/src/grammar/rects.js +1 -1
  177. package/src/grammar/regression/derive.js +1 -195
  178. package/src/grammar/scales/appearance.js +1 -1
  179. package/src/grammar/scales/color.js +1 -1
  180. package/src/grammar/scales/colorConsumers.js +1 -63
  181. package/src/grammar/scales/continuous.js +58 -1
  182. package/src/grammar/scales/definition.js +1 -1
  183. package/src/grammar/scales/ordinal.js +5 -2
  184. package/src/grammar/scales/policies.js +3 -3
  185. package/src/grammar/schemas/graphicBounds.js +40 -12
  186. package/src/grammar/schemas/graphicClip.js +23 -0
  187. package/src/grammar/schemas/graphicTree.js +16 -0
  188. package/src/grammar/statisticalReference.js +22 -0
  189. package/src/grammar/text.js +65 -1
  190. package/src/grammar/ticks.js +92 -1
  191. package/src/grammar/transformTopology.js +3 -1
  192. package/src/grammar/transforms.js +24 -0
  193. package/src/inspection.js +1 -495
  194. package/src/inspectionDescriptors.js +1 -1
  195. package/src/layout/canvas.js +1 -1
  196. package/src/layout/facetContentFlow.js +101 -0
  197. package/src/layout/facetHeaderTitles.js +1 -0
  198. package/src/layout/facetTopLane.js +29 -0
  199. package/src/layout/facets.js +1 -1
  200. package/src/layout/legendGroup.js +1 -1
  201. package/src/layout/legendItems.js +26 -10
  202. package/src/layout/legendSpacing.js +66 -0
  203. package/src/layout/plotDrawOrder.js +17 -0
  204. package/src/materialization/bars/histogram.js +8 -2
  205. package/src/materialization/bars/resolve.js +1 -1
  206. package/src/materialization/composition.js +1 -0
  207. package/src/materialization/compositionSnapshot.js +22 -2
  208. package/src/materialization/dataProvenance.js +1 -1
  209. package/src/materialization/facetGuides/layout.js +1 -1
  210. package/src/materialization/facetGuides/placement.js +23 -11
  211. package/src/materialization/facetGuides/preparation.js +43 -24
  212. package/src/materialization/facetHeaders.js +1 -1
  213. package/src/materialization/facetTopLane.js +25 -0
  214. package/src/materialization/facets.js +1 -1
  215. package/src/materialization/graphicHierarchy.js +5 -0
  216. package/src/materialization/graphicTranslation.js +2 -0
  217. package/src/materialization/guides/layout.js +1 -1
  218. package/src/materialization/guides/resources.js +1 -1
  219. package/src/materialization/legends.js +1 -1
  220. package/src/materialization/markClip.js +26 -0
  221. package/src/materialization/marks/capabilities.js +3 -2
  222. package/src/materialization/marks/grouping.js +7 -5
  223. package/src/materialization/marks/index.js +4 -2
  224. package/src/materialization/marks/policies.js +4 -0
  225. package/src/materialization/plotDrawOrder.js +44 -0
  226. package/src/materialization/rect.js +11 -3
  227. package/src/materialization/scaleGuideDependencies.js +13 -5
  228. package/src/materialization/selection/items/common.js +1 -1
  229. package/src/materialization/selection/items/path.js +1 -0
  230. package/src/materialization/selection/policies/index.js +2 -1
  231. package/src/materialization/text.js +10 -5
  232. package/src/mcp/cli.js +0 -0
  233. package/src/persistence/validation.js +108 -18
  234. package/src/persistence.js +4 -2
  235. package/src/renderers/canvas/index.js +16 -0
  236. package/src/renderers/canvas/isolated.js +11 -1
  237. package/src/renderers/canvas/native.js +7 -0
  238. package/src/renderers/canvas/scene.js +66 -0
  239. package/src/renderers/canvas/text.js +3 -2
  240. package/src/renderers/svg.js +1 -617
  241. package/src/renderers/svgSerialization.js +643 -0
  242. package/src/renderers/transitionScene.js +67 -0
  243. package/src/scene.js +89 -0
  244. package/src/selectors/axes.js +1 -1
  245. package/src/selectors/datasets.js +1 -0
  246. package/src/selectors/grids.js +9 -0
  247. package/src/selectors/markClip.js +28 -0
  248. package/src/selectors/raincloud.js +7 -0
  249. package/src/version.js +1 -1
  250. package/types/basic.d.ts +7 -0
  251. package/types/index.d.ts +8 -0
  252. package/types/program.d.ts +270 -60
  253. package/types/scene.d.ts +35 -0
@@ -1,793 +1 @@
1
- import { resolveGraphicBounds } from "../../layout/canvas.js";
2
- import { action, closedAction } from "../../core/action.js";
3
- import { registerFacetDataRevision, reviseUnitData } from "../data/revise.js";
4
- import { freezeOwned, isPlainObject } from "../../core/immutable.js";
5
- import { validateUserId } from "../../core/identifiers.js";
6
- import {
7
- validateNonEmptyString,
8
- validateNonNegativeFinite,
9
- validateOptionObject,
10
- validatePositiveFinite
11
- } from "../../core/validation.js";
12
- import {
13
- resolveFacetDefinition,
14
- resolveFacetGridDefinition
15
- } from "../../grammar/facets/index.js";
16
- import { collectFacetScaleBindings, resolveFacetFamily } from
17
- "../../grammar/facets/dependencies.js";
18
- import {
19
- FACET_SCALE_CHANNELS,
20
- normalizeFacetScalePolicies
21
- } from "../../grammar/facets/scales.js";
22
- import { resolveFacetLayout } from "../../layout/facets.js";
23
- import { compositionChildDescriptor } from
24
- "../../materialization/composition.js";
25
- import { deriveFacetChildren } from "./derive.js";
26
- import { resolveFacetChildrenScales } from "./derive.js";
27
- import { replayDerivedData } from "./replay.js";
28
- import { composeFacetGuides } from "./guides.js";
29
- import { applyCompositionState } from "../composition/actions.js";
30
- import { replayCompositionThemeState } from "../theme/composition.js";
31
- import { findDataset } from "../../selectors/datasets.js";
32
- import { findLayer } from "../../selectors/layers.js";
33
- import { findSemanticScale } from "../../selectors/scales.js";
34
- import { normalizeDisplayLabelMap } from "../../grammar/displayLabels.js";
35
- import {
36
- createDefaultFacetHeaders,
37
- normalizeFacetHeadersConfig,
38
- resolveFacetHeaderConfig
39
- } from "../../materialization/facetHeaders.js";
40
-
41
- const FACET_OPTIONS = Object.freeze([
42
- "id", "field", "data", "values", "columns", "gap", "spacing", "align", "padding", "scales",
43
- "guides", "headers"
44
- ]);
45
- const FACET_GRID_OPTIONS = Object.freeze([
46
- "id", "data", "rows", "columns", "combinations", "gap", "spacing", "align",
47
- "padding", "scales", "guides", "headers"
48
- ]);
49
- const REPEAT_OPTIONS = Object.freeze([
50
- "id", "target", "channel", "fields", "columns", "gap", "spacing", "align",
51
- "padding", "scales", "guides", "headers"
52
- ]);
53
- const SOURCE_EDIT_OPTIONS = Object.freeze(["program"]);
54
- const GUIDE_OPTIONS = Object.freeze(["axes", "legend"]);
55
- const HEADER_OPTIONS = Object.freeze([
56
- "fontSize", "fontFamily", "fontWeight", "color", "offset", "role",
57
- "labelMap", "side", "align"
58
- ]);
59
- const HEADER_STYLE_OPTIONS = Object.freeze([
60
- "fontSize", "fontFamily", "fontWeight", "color", "offset", "align"
61
- ]);
62
-
63
- function normalizeGuides(guides, allowStored = false) {
64
- if (guides === undefined) return { axes: "each", legend: false };
65
- validateOptionObject(guides, GUIDE_OPTIONS, "facet.guides");
66
- const axes = guides.axes ?? "each";
67
- if (!["each", "outer"].includes(axes)) {
68
- throw new Error('facet guides.axes must be "each" or "outer".');
69
- }
70
- const legend = guides.legend ?? false;
71
- if (legend !== false && legend !== "shared" && !isPlainObject(legend)) {
72
- throw new Error('facet guides.legend must be false, "shared", or legend options.');
73
- }
74
- if (!allowStored && isPlainObject(legend) && Object.hasOwn(legend, "type")) {
75
- throw new Error("Facet legend options do not accept internal type tags.");
76
- }
77
- return { axes, legend };
78
- }
79
-
80
- function requireFacetProgram(program, operation) {
81
- program._assertCompositionProgram(operation);
82
- if (program.compositionSpec.type !== "facet") {
83
- throw new Error(`${operation} requires a facet composition.`);
84
- }
85
- }
86
-
87
- function usedFacetScalePolicies(program, policies) {
88
- const used = new Set(
89
- collectFacetScaleBindings(program.semanticSpec).map(binding => binding.policyKey)
90
- );
91
- return Object.fromEntries(
92
- FACET_SCALE_CHANNELS.flatMap(channel =>
93
- used.has(channel) ? [[channel, policies[channel]]] : []
94
- )
95
- );
96
- }
97
-
98
- function facetUnitTemplate(program) {
99
- const seedId = program.compositionSpec.children.find(id =>
100
- program.children[id]?.semanticSpec.layers.length > 0
101
- ) ?? program.compositionSpec.children[0];
102
- const seed = program.children[seedId];
103
- if (seed === undefined) {
104
- throw new Error(`Facet "${program.compositionSpec.id}" requires a retained child.`);
105
- }
106
- const { facets: _facets, guides: _guides, ...unitConfigs } = program.materializationConfigs;
107
- return new program.constructor({
108
- semanticSpec: program.semanticSpec,
109
- graphicSpec: seed.graphicSpec,
110
- resolvedScales: program.resolvedScales,
111
- materializationConfigs: freezeOwned({
112
- ...unitConfigs,
113
- ...(seed.materializationConfigs.theme === undefined
114
- ? {}
115
- : { theme: seed.materializationConfigs.theme }),
116
- ...(seed.materializationConfigs.guides === undefined
117
- ? {}
118
- : { guides: seed.materializationConfigs.guides }),
119
- canvas: seed.materializationConfigs.canvas
120
- }),
121
- children: {},
122
- context: program.context,
123
- trace: program.trace,
124
- actionStack: program.actionStack,
125
- actionSequence: program._actionSequence
126
- });
127
- }
128
-
129
- function rederiveFacet(program, { scales, guides }) {
130
- const current = program.compositionSpec;
131
- if (current.facet.repeat !== undefined) {
132
- const template = facetUnitTemplate(program);
133
- const definition = resolveRepeatDefinition(template, {
134
- id: current.id,
135
- ...current.facet.repeat
136
- });
137
- const derived = deriveRepeatChildren(
138
- template,
139
- definition,
140
- usedFacetScalePolicies(template, scales),
141
- true
142
- );
143
- return applyCompositionState(program, {
144
- children: derived.children,
145
- compositionSpec: {
146
- ...current,
147
- facet: { ...current.facet, scales, guides }
148
- }
149
- }, current.children);
150
- }
151
- const definition = current.facet.grid === undefined
152
- ? resolveFacetDefinition(program.semanticSpec, {
153
- id: current.id,
154
- data: current.facet.data,
155
- field: current.facet.field,
156
- values: current.facet.values
157
- })
158
- : resolveFacetGridDefinition(program.semanticSpec, {
159
- id: current.id,
160
- data: current.facet.data,
161
- rows: current.facet.grid.rows,
162
- columns: current.facet.grid.columns,
163
- combinations: current.facet.grid.combinations
164
- });
165
- const request = usedFacetScalePolicies(program, scales);
166
- const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
167
- const template = facetUnitTemplate(program);
168
- const derived = deriveFacetChildren(template, definition, {
169
- closeInheritedAction: true,
170
- stripTitle: true,
171
- scales: request
172
- });
173
- const compositionSpec = {
174
- ...current,
175
- facet: {
176
- ...current.facet,
177
- scales: normalized.channels,
178
- guides
179
- }
180
- };
181
- let parent = program._withoutMaterializationConfig(["guides", "legend"]);
182
- for (const [kind, config] of Object.entries(template.guideConfigs.legend ?? {})) {
183
- parent = parent._withLegendConfig(kind, config);
184
- }
185
- return applyCompositionState(parent, {
186
- children: derived.children,
187
- compositionSpec
188
- }, compositionSpec.children);
189
- }
190
-
191
- function resolveEditedFacetHeaders(currentHeaders, args, grid, operation = "editFacetHeaders") {
192
- validateOptionObject(args, HEADER_OPTIONS, operation, {
193
- allowEmpty: false,
194
- emptyMessage: `${operation} requires at least one change.`
195
- });
196
- const role = args.role ?? "all";
197
- if (!["all", "row", "column"].includes(role)) {
198
- throw new Error(`Unknown facet header role "${role}".`);
199
- }
200
- if (role === "all" && Object.hasOwn(args, "side")) {
201
- throw new Error(`${operation} side requires an explicit row or column role.`);
202
- }
203
- if (role === "row" && grid === undefined) {
204
- throw new Error(`${operation} row role requires a row-column facet grid.`);
205
- }
206
- if (Object.hasOwn(args, "side")) {
207
- const sides = role === "row" ? ["left", "right"]
208
- : grid === undefined
209
- ? ["top", "bottom", "left", "right"] : ["top", "bottom"];
210
- if (!sides.includes(args.side)) {
211
- throw new Error(`${operation} ${role} side must be ${sides.join(" or ")}.`);
212
- }
213
- }
214
- if (Object.hasOwn(args, "align") &&
215
- !["start", "center", "end"].includes(args.align)) {
216
- throw new Error(`${operation} align must be start, center, or end.`);
217
- }
218
- if (role === "all" && Object.keys(args).every(key => key === "role")) {
219
- throw new Error(`${operation} requires at least one change.`);
220
- }
221
- const current = normalizeFacetHeadersConfig(currentHeaders);
222
- const owner = role === "all" ? "common" : role;
223
- const patch = { ...current[owner] };
224
- for (const key of HEADER_STYLE_OPTIONS) {
225
- if (Object.hasOwn(args, key)) patch[key] = args[key];
226
- }
227
- if (Object.hasOwn(args, "side")) patch.side = args.side;
228
- if (Object.hasOwn(args, "labelMap")) {
229
- if (args.labelMap === "auto") delete patch.labelMap;
230
- else {
231
- patch.labelMap = normalizeDisplayLabelMap(
232
- args.labelMap,
233
- `${operation} ${role} labelMap`
234
- );
235
- }
236
- }
237
- const headers = {
238
- ...current,
239
- ...(role === "all" ? {} : { mode: "roles" }),
240
- [owner]: patch
241
- };
242
- for (const headerRole of ["all", "row", "column"]) {
243
- const resolved = resolveFacetHeaderConfig(headers, headerRole);
244
- validatePositiveFinite(resolved.fontSize, `Facet ${headerRole} header fontSize`);
245
- validateNonEmptyString(resolved.fontFamily, `Facet ${headerRole} header fontFamily`);
246
- validateNonEmptyString(resolved.color, `Facet ${headerRole} header color`);
247
- validateNonNegativeFinite(resolved.offset, `Facet ${headerRole} header offset`);
248
- if (!(
249
- (typeof resolved.fontWeight === "string" && resolved.fontWeight.length > 0) ||
250
- Number.isFinite(resolved.fontWeight)
251
- )) {
252
- throw new TypeError(
253
- `Facet ${headerRole} header fontWeight must be a non-empty string or number.`
254
- );
255
- }
256
- if (!["start", "center", "end"].includes(resolved.align)) {
257
- throw new Error(`Facet ${headerRole} header align must be start, center, or end.`);
258
- }
259
- if (resolved.labelMap !== undefined) {
260
- normalizeDisplayLabelMap(
261
- resolved.labelMap,
262
- `Facet ${headerRole} header labelMap`
263
- );
264
- }
265
- }
266
- return headers;
267
- }
268
-
269
- function initialFacetHeaders(value, grid) {
270
- let headers = createDefaultFacetHeaders();
271
- if (value === undefined) return headers;
272
- validateOptionObject(value, [...HEADER_STYLE_OPTIONS, "labelMap", "row", "column"], "facet.headers");
273
- const { row, column, ...common } = value;
274
- if (Object.keys(common).length > 0) headers = resolveEditedFacetHeaders(headers, common, grid, "facet.headers");
275
- for (const [role, patch] of [["row", row], ["column", column]]) {
276
- if (patch === undefined) continue;
277
- validateOptionObject(patch, HEADER_OPTIONS.filter(key => key !== "role"), `facet.headers.${role}`);
278
- headers = resolveEditedFacetHeaders(headers, { ...patch, role }, grid, `facet.headers.${role}`);
279
- }
280
- return headers;
281
- }
282
-
283
- function composeDerivedFacet(program, args, { definition, derived, guides, scalePolicies, facet }) {
284
- const headers = initialFacetHeaders(args.headers, definition.grid);
285
- const preflight = resolveFacetLayout({
286
- spacing: args.spacing,
287
- plots: Object.entries(derived.children).map(([id, child]) => ({ id, ...resolveGraphicBounds(child) })),
288
- children: definition.cells.map(cell => ({
289
- ...compositionChildDescriptor(cell.id, derived.children[cell.id]), value: cell.value,
290
- ...(definition.grid === undefined ? {} : { row: cell.row, column: cell.column })
291
- })),
292
- columns: definition.grid?.columns.values.length ?? args.columns,
293
- gap: args.gap, align: args.align, padding: args.padding,
294
- sharedLegend: guides.legend === "shared"
295
- });
296
- const compositionSpec = {
297
- id: definition.id, type: "facet", children: definition.cells.map(cell => cell.id),
298
- columns: preflight.columns, gap: preflight.gap, align: preflight.align, padding: preflight.padding,
299
- ...(preflight.spacing === undefined ? {} : { spacing: preflight.spacing }),
300
- facet: { data: definition.data, ...facet, scales: scalePolicies.channels, guides }
301
- };
302
- return applyCompositionState(
303
- program._withMaterializationConfig(["facets", definition.id], { headers }),
304
- { children: derived.children, compositionSpec }, compositionSpec.children
305
- );
306
- }
307
-
308
- function partitionFacetAction(op, description, definitionResolver) {
309
- return closedAction({ op, description }, op === "facet" ? FACET_OPTIONS : FACET_GRID_OPTIONS,
310
- function (args = {}) {
311
- const guides = normalizeGuides(args.guides);
312
- const definition = definitionResolver(this.semanticSpec, args);
313
- if (definition.family !== "cartesian" && guides.axes === "outer") {
314
- throw new Error("Polar and Parallel facets do not support outer axes.");
315
- }
316
- const scalePolicies = normalizeFacetScalePolicies(this.semanticSpec, args.scales ?? {});
317
- const derived = deriveFacetChildren(this, definition, {
318
- closeInheritedAction: true, stripTitle: true, scales: args.scales ?? {}
319
- });
320
- const facet = definition.grid === undefined
321
- ? { field: definition.field, values: definition.values }
322
- : { values: definition.cells.map(cell => cell.value), grid: {
323
- ...definition.grid, cells: definition.cells.map(cell => ({
324
- id: cell.id, row: cell.row, column: cell.column,
325
- rowValue: cell.rowValue, columnValue: cell.columnValue, empty: cell.empty
326
- }))
327
- } };
328
- return composeDerivedFacet(this, args, { definition, derived, guides, scalePolicies, facet });
329
- });
330
- }
331
-
332
- export const facet = /* @__PURE__ */ partitionFacetAction("facet",
333
- "Repeat one direct-source chart by field value.", resolveFacetDefinition);
334
-
335
- function resolveRepeatDefinition(program, args) {
336
- if (!isPlainObject(args)) {
337
- throw new TypeError("repeatCharts options must be a plain object.");
338
- }
339
- const id = validateUserId(args.id ?? "repeat", "Repeat id");
340
- const parallelChannel = isPlainObject(args.channel);
341
- if (parallelChannel) {
342
- const keys = Object.keys(args.channel);
343
- if (keys.length !== 1 || keys[0] !== "parallelDimension" ||
344
- typeof args.channel.parallelDimension !== "string" ||
345
- args.channel.parallelDimension.length === 0) {
346
- throw new Error(
347
- "repeatCharts Parallel channel must contain exactly one non-empty parallelDimension."
348
- );
349
- }
350
- } else if (!["x", "y", "theta", "r"].includes(args.channel)) {
351
- throw new Error(
352
- 'repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.'
353
- );
354
- }
355
- if (!Array.isArray(args.fields) || args.fields.length === 0 ||
356
- args.fields.some(field => typeof field !== "string" || field.length === 0)) {
357
- throw new TypeError("repeatCharts fields must be a non-empty array of field names.");
358
- }
359
- if (new Set(args.fields).size !== args.fields.length) {
360
- throw new Error("repeatCharts fields must be unique.");
361
- }
362
- const family = resolveFacetFamily(program.semanticSpec).family;
363
- const semanticChannel = args.channel === "r" ? "radius" : args.channel;
364
- const eligible = program.semanticSpec.layers.filter(layer => {
365
- if (parallelChannel) {
366
- const matches = (layer.encoding?.parallel?.dimensions ?? []).filter(
367
- dimension => dimension.field === args.channel.parallelDimension
368
- );
369
- return family === "parallel" && layer.mark?.type === "line" && matches.length === 1;
370
- }
371
- if (["x", "y"].includes(args.channel)) {
372
- return family === "cartesian" &&
373
- layer.encoding?.x?.scale !== undefined &&
374
- layer.encoding?.y?.scale !== undefined &&
375
- layer.encoding?.[args.channel]?.field !== undefined &&
376
- ["point", "line", "area", "bar", "rule", "tick", "rect"].includes(layer.mark?.type);
377
- }
378
- const role = program.markConfigs[layer.id]?.compositionRole;
379
- if (family !== "polar" || ["pie", "radar"].includes(role)) return false;
380
- return ["point", "line", "arc"].includes(layer.mark?.type) &&
381
- layer.encoding?.[semanticChannel]?.field !== undefined;
382
- });
383
- let target;
384
- if (args.target !== undefined) {
385
- target = validateUserId(args.target, "Repeat target");
386
- if (!eligible.some(layer => layer.id === target)) {
387
- throw new Error(
388
- `repeatCharts target "${target}" is not eligible for the requested field role.`
389
- );
390
- }
391
- } else if (eligible.length === 1) {
392
- target = eligible[0].id;
393
- } else {
394
- throw new Error(
395
- eligible.length === 0
396
- ? "repeatCharts requires one eligible complete mark for the requested field role."
397
- : "repeatCharts target is ambiguous; provide target."
398
- );
399
- }
400
- const unrelatedLayers = program.semanticSpec.layers.filter(layer => {
401
- if (layer.id === target) return false;
402
- const attachedLabel = layer.mark?.type === "text" && layer.source === target;
403
- const statisticalReference = program.markConfigs[layer.id]
404
- ?.statisticalReference?.source === target;
405
- return !attachedLabel && !statisticalReference;
406
- });
407
- if (unrelatedLayers.length > 0) {
408
- throw new Error("repeatCharts currently supports one direct mark only.");
409
- }
410
- const config = program.markConfigs[target] ?? {};
411
- const composite = [
412
- "boxPlot", "gradientPlot", "violinPlot", "regressionPlot", "endpointPlot",
413
- "ecdfPlot", "raincloudPlot", "intervalPlot"
414
- ].find(key => config[key] !== undefined);
415
- if (composite !== undefined) {
416
- throw new Error(`repeatCharts does not replace the ${composite} composite role.`);
417
- }
418
- const layer = findLayer(program, target);
419
- const dataset = findDataset(program, layer.data);
420
- if (dataset?.transform?.length > 0) {
421
- throw new Error("repeatCharts does not rewrite a derived dataset dependency.");
422
- }
423
- const encoding = parallelChannel
424
- ? layer.encoding.parallel
425
- : layer.encoding[semanticChannel];
426
- const dimensionIndex = parallelChannel
427
- ? encoding.dimensions.findIndex(
428
- dimension => dimension.field === args.channel.parallelDimension
429
- )
430
- : undefined;
431
- return {
432
- id,
433
- target,
434
- channel: parallelChannel ? { ...args.channel } : args.channel,
435
- policyKey: parallelChannel ? "parallelDimensions" : args.channel,
436
- fields: [...args.fields],
437
- data: layer.data,
438
- encoding,
439
- ...(dimensionIndex === undefined ? {} : { dimensionIndex }),
440
- cells: args.fields.map((field, index) => ({
441
- id: `${id}-field-${index + 1}`,
442
- field,
443
- value: field
444
- }))
445
- };
446
- }
447
-
448
- function repeatEncodingArgs(definition, field) {
449
- const encoding = definition.encoding;
450
- return {
451
- target: definition.target,
452
- field,
453
- fieldType: encoding.fieldType,
454
- ...(encoding.temporalUnit === undefined ? {} : { temporalUnit: encoding.temporalUnit }),
455
- ...(encoding.aggregate === undefined ? {} : { aggregate: encoding.aggregate }),
456
- ...(encoding.bin === undefined ? {} : { bin: encoding.bin }),
457
- ...(encoding.stack === undefined ? {} : { stack: encoding.stack }),
458
- ...(encoding.weight === undefined ? {} : { weight: encoding.weight }),
459
- scale: { id: encoding.scale }
460
- };
461
- }
462
-
463
- function parallelRepeatDimensions(program, definition, field) {
464
- return definition.encoding.dimensions.map((dimension, index) => {
465
- const semanticScale = findSemanticScale(program, dimension.scale);
466
- if (semanticScale === undefined) {
467
- throw new Error(`repeatCharts Parallel dimension scale "${dimension.scale}" is missing.`);
468
- }
469
- const { id: _id, ...scale } = semanticScale;
470
- void _id;
471
- const replacement = index === definition.dimensionIndex;
472
- return {
473
- field: replacement ? field : dimension.field,
474
- fieldType: dimension.fieldType,
475
- title: replacement && dimension.title === dimension.field
476
- ? field
477
- : dimension.title,
478
- scale
479
- };
480
- });
481
- }
482
-
483
- function deriveRepeatedProgram(template, definition, field) {
484
- if (isPlainObject(definition.channel)) {
485
- return template.encodeParallelCoordinates({
486
- target: definition.target,
487
- coordinate: findLayer(template, definition.target).coordinate,
488
- dimensions: parallelRepeatDimensions(template, definition, field),
489
- ...(definition.encoding.key === undefined ? {} : { key: definition.encoding.key }),
490
- missing: definition.encoding.missing
491
- });
492
- }
493
- const operation = {
494
- x: "encodeX",
495
- y: "encodeY",
496
- theta: "encodeTheta",
497
- r: "encodeR"
498
- }[definition.channel];
499
- return template[operation](repeatEncodingArgs(definition, field));
500
- }
501
-
502
- function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
503
- const template = base.semanticSpec.title.text === undefined
504
- ? base
505
- : base.removeTitle();
506
- const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
507
- cell.id,
508
- deriveRepeatedProgram(template, definition, cell.field)
509
- ]));
510
- return resolveFacetChildrenScales(
511
- template,
512
- definition.cells.map(cell => cell.id),
513
- independentlyResolved,
514
- scales,
515
- closeInheritedAction
516
- );
517
- }
518
-
519
- export const repeatCharts = /* @__PURE__ */ closedAction(
520
- {
521
- op: "repeatCharts",
522
- description: "Repeat one direct chart across an ordered field-role list."
523
- }, REPEAT_OPTIONS,
524
- function (args = {}) {
525
- const guides = normalizeGuides(args.guides);
526
- if (guides.axes === "outer") {
527
- throw new Error("repeatCharts does not promote axes across different repeated fields.");
528
- }
529
- const definition = resolveRepeatDefinition(this, args);
530
- const requestedScales = {
531
- ...(args.scales ?? {}),
532
- [definition.policyKey]: args.scales?.[definition.policyKey] ?? "independent"
533
- };
534
- const scalePolicies = normalizeFacetScalePolicies(
535
- this.semanticSpec,
536
- requestedScales
537
- );
538
- const derived = deriveRepeatChildren(
539
- this,
540
- definition,
541
- requestedScales,
542
- true
543
- );
544
- return composeDerivedFacet(this, args, {
545
- definition, derived, guides, scalePolicies,
546
- facet: { values: definition.fields,
547
- repeat: { target: definition.target, channel: definition.channel, fields: definition.fields } }
548
- });
549
- }
550
- );
551
-
552
- export const facetGrid = /* @__PURE__ */ partitionFacetAction("facetGrid",
553
- "Repeat one direct-source Cartesian chart across a row and column field grid.", resolveFacetGridDefinition);
554
-
555
- export const editFacetHeaders = /* @__PURE__ */ action(
556
- {
557
- op: "editFacetHeaders",
558
- description: "Edit parent-owned facet header appearance.",
559
- scope: "composition"
560
- },
561
- function (args = {}) {
562
- requireFacetProgram(this, "editFacetHeaders");
563
- const id = this.compositionSpec.id;
564
- const config = this.materializationConfigs.facets?.[id];
565
- if (!isPlainObject(config?.headers)) {
566
- throw new Error(`Facet "${id}" requires header configuration.`);
567
- }
568
- const headers = resolveEditedFacetHeaders(config.headers, args, this.compositionSpec.facet.grid);
569
- return this
570
- ._withMaterializationConfig(["facets", id], { ...config, headers })
571
- .materializeComposition();
572
- }
573
- );
574
-
575
- export const editFacetScales = /* @__PURE__ */ action(
576
- {
577
- op: "editFacetScales",
578
- description: "Edit facet scale-resolution policies and rederive every cell.",
579
- scope: "composition"
580
- },
581
- function (args = {}) {
582
- requireFacetProgram(this, "editFacetScales");
583
- validateOptionObject(args, FACET_SCALE_CHANNELS, "editFacetScales", {
584
- allowEmpty: false,
585
- emptyMessage: "editFacetScales requires at least one channel policy change."
586
- });
587
- for (const channel of Object.keys(args)) {
588
- if (!collectFacetScaleBindings(this.semanticSpec).some(
589
- binding => binding.policyKey === channel
590
- )) {
591
- throw new Error(
592
- `Facet scale channel "${channel}" is not used by an affected layer.`
593
- );
594
- }
595
- }
596
- const current = this.compositionSpec.facet;
597
- const scales = { ...current.scales, ...args };
598
- if (FACET_SCALE_CHANNELS.every(
599
- channel => scales[channel] === current.scales[channel]
600
- )) {
601
- throw new Error("editFacetScales requires at least one channel policy change.");
602
- }
603
- return rederiveFacet(this, {
604
- scales,
605
- guides: current.guides
606
- });
607
- }
608
- );
609
-
610
- export const editFacetGuides = /* @__PURE__ */ action(
611
- {
612
- op: "editFacetGuides",
613
- description: "Edit facet guide ownership and rederive every cell.",
614
- scope: "composition"
615
- },
616
- function (args = {}) {
617
- requireFacetProgram(this, "editFacetGuides");
618
- validateOptionObject(args, GUIDE_OPTIONS, "editFacetGuides", {
619
- allowEmpty: false,
620
- emptyMessage: "editFacetGuides requires at least one guide policy."
621
- });
622
- const current = this.compositionSpec.facet;
623
- if (Object.hasOwn(args, "legend")) normalizeGuides({ legend: args.legend });
624
- const guides = normalizeGuides({ ...current.guides, ...args }, true);
625
- const family = resolveFacetFamily(this.semanticSpec).family;
626
- if (family !== "cartesian" && guides.axes === "outer") {
627
- throw new Error("Polar and Parallel facets do not support outer axes.");
628
- }
629
- if (current.repeat !== undefined && guides.axes === "outer") {
630
- throw new Error("repeatCharts does not promote axes across different repeated fields.");
631
- }
632
- return rederiveFacet(this, {
633
- scales: current.scales,
634
- guides
635
- });
636
- }
637
- );
638
-
639
- function adoptUnitState(program, actionOwner) {
640
- if (!(program instanceof actionOwner.constructor)) {
641
- throw new TypeError("editFacetSource program must be a ChartProgram.");
642
- }
643
- if (program.compositionSpec !== undefined) {
644
- throw new Error("editFacetSource program must be a complete unit ChartProgram.");
645
- }
646
- if (program.actionStack.length !== 0) {
647
- throw new Error("editFacetSource program has an unfinished action stack.");
648
- }
649
- const seedId = actionOwner.compositionSpec.children.find(id =>
650
- actionOwner.children[id]?.semanticSpec.layers.length > 0
651
- ) ?? actionOwner.compositionSpec.children[0];
652
- const retained = actionOwner.children[seedId]?.materializationConfigs ?? {};
653
- const {
654
- canvas: _retainedCanvas,
655
- theme: _retainedTheme,
656
- facets: _retainedFacets,
657
- ...retainedRecipe
658
- } = retained;
659
- void _retainedCanvas;
660
- void _retainedTheme;
661
- void _retainedFacets;
662
- return new actionOwner.constructor({
663
- semanticSpec: program.semanticSpec,
664
- graphicSpec: program.graphicSpec,
665
- resolvedScales: program.resolvedScales,
666
- materializationConfigs: freezeOwned({
667
- ...program.materializationConfigs,
668
- ...retainedRecipe,
669
- ...(program.materializationConfigs.canvas === undefined
670
- ? {}
671
- : { canvas: program.materializationConfigs.canvas }),
672
- ...(program.materializationConfigs.theme === undefined
673
- ? {}
674
- : { theme: program.materializationConfigs.theme })
675
- }),
676
- children: {},
677
- context: program.context,
678
- trace: actionOwner.trace,
679
- actionStack: actionOwner.actionStack,
680
- actionSequence: actionOwner._actionSequence
681
- });
682
- }
683
-
684
- export const editFacetSource = /* @__PURE__ */ closedAction(
685
- {
686
- op: "editFacetSource",
687
- description: "Reapply one facet, grid, or repeat recipe to a revised complete unit program.",
688
- scope: "composition"
689
- }, SOURCE_EDIT_OPTIONS,
690
- function (args = {}) {
691
- requireFacetProgram(this, "editFacetSource");
692
- const current = this.compositionSpec;
693
- const facetConfig = this.materializationConfigs.facets?.[current.id];
694
- const base = adoptUnitState(args.program, this);
695
- const authored = isPlainObject(current.facet.guides.legend);
696
- const guides = authored ? { ...current.facet.guides, legend: false } : current.facet.guides;
697
- let revised;
698
- const scales = usedFacetScalePolicies(base, current.facet.scales);
699
- if (current.facet.grid !== undefined) {
700
- revised = base.facetGrid({
701
- id: current.id,
702
- data: current.facet.data,
703
- rows: current.facet.grid.rows,
704
- columns: current.facet.grid.columns,
705
- combinations: current.facet.grid.combinations,
706
- gap: current.gap,
707
- ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
708
- align: current.align,
709
- padding: current.padding,
710
- scales,
711
- guides
712
- });
713
- } else if (current.facet.repeat !== undefined) {
714
- revised = base.repeatCharts({
715
- id: current.id,
716
- ...current.facet.repeat,
717
- columns: current.columns,
718
- gap: current.gap,
719
- ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
720
- align: current.align,
721
- padding: current.padding,
722
- scales,
723
- guides
724
- });
725
- } else {
726
- revised = base.facet({
727
- id: current.id,
728
- data: current.facet.data,
729
- field: current.facet.field,
730
- values: current.facet.values,
731
- columns: current.columns,
732
- gap: current.gap,
733
- ...(current.spacing === undefined ? {} : { spacing: current.spacing }),
734
- align: current.align,
735
- padding: current.padding,
736
- scales,
737
- guides
738
- });
739
- }
740
- if (authored) {
741
- const spec = revised.compositionSpec;
742
- revised = revised._withCompositionState({ children: revised.children,
743
- compositionSpec: { ...spec, facet: { ...spec.facet, guides: current.facet.guides } } });
744
- }
745
- if (facetConfig !== undefined) {
746
- revised = revised
747
- ._withMaterializationConfig(["facets", current.id], facetConfig)
748
- .materializeComposition();
749
- }
750
- if (revised.semanticSpec.title.text !== undefined) {
751
- revised = revised.removeTitle();
752
- }
753
- if (this.semanticSpec.title.text !== undefined) {
754
- revised = revised.createTitle({
755
- ...this.titleConfig,
756
- ...this.semanticSpec.title
757
- });
758
- }
759
- return replayCompositionThemeState(
760
- revised,
761
- this.materializationConfigs.theme
762
- );
763
- }
764
- );
765
-
766
- export function registerFacetActions(ProgramClass) {
767
- registerFacetDataRevision((program, args) => {
768
- const { program: unit, plan } = reviseUnitData(facetUnitTemplate(program), args);
769
- const current = program.compositionSpec;
770
- // Adopt an already-materialized unit revision into the retained parent.
771
- const revised = new program.constructor({
772
- ...unit,
773
- graphicSpec: program.graphicSpec,
774
- children: program.children,
775
- materializationConfigs: { ...unit.materializationConfigs,
776
- facets: program.materializationConfigs.facets,
777
- ...(program.titleConfig === undefined ? {} : { title: program.titleConfig }) },
778
- actionSequence: unit._actionSequence,
779
- compositionSpec: { ...current, facet: { ...current.facet,
780
- data: plan.replacements.get(current.facet.data) ?? current.facet.data } }
781
- });
782
- return rederiveFacet(revised, current.facet);
783
- });
784
- ProgramClass.prototype.replayDerivedData = replayDerivedData;
785
- ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
786
- ProgramClass.prototype.facet = facet;
787
- ProgramClass.prototype.facetGrid = facetGrid;
788
- ProgramClass.prototype.repeatCharts = repeatCharts;
789
- ProgramClass.prototype.editFacetHeaders = editFacetHeaders;
790
- ProgramClass.prototype.editFacetScales = editFacetScales;
791
- ProgramClass.prototype.editFacetGuides = editFacetGuides;
792
- ProgramClass.prototype.editFacetSource = editFacetSource;
793
- }
1
+ import{DEFAULT_ACTION_METHODS as Z}from"../../core/vocabulary.js";import{axisGraphicIds as ee}from"../../materialization/guides/resources.js";import{cartesianAxisOwners as te,axisTargetArgs as ie}from"../../selectors/axes.js";import{resolveGraphicBounds as ne}from"../../layout/canvas.js";import{action as x,closedAction as z,getWrappedActionMetadata as ae}from"../../core/action.js";import{registerFacetDataRevision as oe,reviseUnitData as ce}from"../data/revise.js";import{applyMaterializationPlan as re}from"../../materialization/dependencies.js";import{getMarkMaterializationStep as se}from"../../materialization/marks/index.js";import{buildMaterializationPlan as de}from"../../materialization/planner.js";import{freezeOwned as L,isPlainObject as w}from"../../core/immutable.js";import{validateUserId as M}from"../../core/identifiers.js";import{validateNonEmptyString as T,validateNonNegativeFinite as le,validateOptionObject as S,validatePositiveFinite as fe}from"../../core/validation.js";import{resolveFacetDefinition as P,resolveFacetGridDefinition as R}from"../../grammar/facets/index.js";import{collectFacetScaleBindings as A,resolveFacetFamily as G}from"../../grammar/facets/dependencies.js";import{FACET_SCALE_CHANNELS as $,normalizeFacetScalePolicies as k}from"../../grammar/facets/scales.js";import{resolveFacetLayout as pe}from"../../layout/facets.js";import{compositionChildDescriptor as ue}from"../../materialization/composition.js";import{deriveFacetChildren as I}from"./derive.js";import{resolveFacetChildrenScales as he}from"./derive.js";import{replayDerivedData as me}from"./replay.js";import{composeFacetGuides as ge}from"./guides.js";import{applyCompositionState as _}from"../composition/actions.js";import{replayCompositionThemeState as we}from"../theme/composition.js";import{findDataset as Se}from"../../selectors/datasets.js";import{findLayer as N}from"../../selectors/layers.js";import{findSemanticScale as ye}from"../../selectors/scales.js";import{normalizeDisplayLabelMap as H}from"../../grammar/displayLabels.js";import{resolveRotation as be}from"../../grammar/rotation.js";import{normalizeFacetHeaderTitle as Ce}from"../../grammar/facetHeaders.js";import{createDefaultFacetHeaders as Fe,normalizeFacetHeadersConfig as U,resolveFacetHeaderConfig as ve}from"../../materialization/facetHeaders.js";const Ee=Object.freeze(["id","field","data","values","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),Oe=Object.freeze(["id","data","rows","columns","combinations","gap","spacing","topLane","align","padding","scales","guides","headers"]),xe=Object.freeze(["id","target","channel","fields","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),ze=new Set(Z),Te=Object.freeze(["program","action"]),W=Object.freeze(["axes","legend"]),K=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","role","labelMap","side","align","rotation","title","anchor"]),V=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","align","rotation","anchor"]);function C(t,e=!1){if(t===void 0)return{axes:"each",legend:!1};S(t,W,"facet.guides");const n=t.axes??"each";if(!["each","outer"].includes(n))throw new Error('facet guides.axes must be "each" or "outer".');const i=t.legend??!1;if(i!==!1&&i!=="shared"&&!w(i))throw new Error('facet guides.legend must be false, "shared", or legend options.');if(!e&&w(i)&&Object.hasOwn(i,"type"))throw new Error("Facet legend options do not accept internal type tags.");return{axes:n,legend:i}}function F(t,e){if(t._assertCompositionProgram(e),t.compositionSpec.type!=="facet")throw new Error(`${e} requires a facet composition.`)}function v(t,e){const n=new Set(A(t.semanticSpec).map(i=>i.policyKey));return Object.fromEntries($.flatMap(i=>n.has(i)&&e[i]!==void 0?[[i,e[i]]]:[]))}function E(t,e=!1){const n=t.compositionSpec.children.find(l=>t.children[l]?.semanticSpec.layers.length>0)??t.compositionSpec.children[0],i=t.children[n];if(i===void 0)throw new Error(`Facet "${t.compositionSpec.id}" requires a retained child.`);const{facets:c,guides:o,...d}=t.materializationConfigs,{legend:r,...s}=t.semanticSpec.guides,a={...i.materializationConfigs.guides,axis:o?.axis??i.guideConfigs.axis,grid:o?.grid??i.guideConfigs.grid};return e&&delete d.title,new t.constructor({semanticSpec:e?{...t.semanticSpec,title:{},guides:{...s,...i.semanticSpec.guides.legend===void 0?{}:{legend:i.semanticSpec.guides.legend}}}:t.semanticSpec,graphicSpec:i.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:L({...d,...i.materializationConfigs.theme===void 0?{}:{theme:i.materializationConfigs.theme},...a===void 0?{}:{guides:a},canvas:i.materializationConfigs.canvas}),children:{},context:t.context,trace:t.trace,actionStack:t.actionStack,actionSequence:t._actionSequence})}function j(t,{scales:e,guides:n}){const i=t.compositionSpec;if(i.facet.repeat!==void 0){const f=E(t),u=X(f,{id:i.id,...i.facet.repeat}),m=J(f,u,v(f,e),!0);return _(t,{children:m.children,compositionSpec:{...i,facet:{...i.facet,scales:e,guides:n}}},i.children)}const c=i.facet.grid===void 0?P(t.semanticSpec,{id:i.id,data:i.facet.data,field:i.facet.field,values:i.facet.values}):R(t.semanticSpec,{id:i.id,data:i.facet.data,rows:i.facet.grid.rows,columns:i.facet.grid.columns,combinations:i.facet.grid.combinations}),o=v(t,e),d=k(t.semanticSpec,o),r=E(t),s=I(r,c,{closeInheritedAction:!0,stripTitle:!0,scales:o}),a={...i,facet:{...i.facet,scales:d.channels,guides:n}};let l=t._withoutMaterializationConfig(["guides","legend"]);for(const[f,u]of Object.entries(r.guideConfigs.legend??{}))l=l._withLegendConfig(f,u);return _(l,{children:s.children,compositionSpec:a},a.children)}function q(t,e,n,i="editFacetHeaders"){S(e,K,i,{allowEmpty:!1,emptyMessage:`${i} requires at least one change.`});const c=e.role??"all";if(!["all","row","column"].includes(c))throw new Error(`Unknown facet header role "${c}".`);if(c==="all"&&Object.hasOwn(e,"side"))throw new Error(`${i} side requires an explicit row or column role.`);if(c==="all"&&Object.hasOwn(e,"title"))throw new Error(`${i} title requires an explicit row or column role.`);if(c==="row"&&n===void 0)throw new Error(`${i} row role requires a row-column facet grid.`);if(Object.hasOwn(e,"side")){const a=c==="row"?["left","right"]:n===void 0?["top","bottom","left","right"]:["top","bottom"];if(!a.includes(e.side))throw new Error(`${i} ${c} side must be ${a.join(" or ")}.`)}if(Object.hasOwn(e,"align")&&!["start","center","end"].includes(e.align))throw new Error(`${i} align must be start, center, or end.`);if(c==="all"&&Object.keys(e).every(a=>a==="role"))throw new Error(`${i} requires at least one change.`);const o=U(t),d=c==="all"?"common":c,r={...o[d]};if(Object.hasOwn(e,"title")){const a=Ce(r.title,e.title);a===void 0?delete r.title:r.title=a}for(const a of V)Object.hasOwn(e,a)&&(r[a]=e[a]);Object.hasOwn(e,"rotation")&&(r.rotation=be(e.rotation,`${i} ${c} rotation`)),Object.hasOwn(e,"side")&&(r.side=e.side),Object.hasOwn(e,"labelMap")&&(e.labelMap==="auto"?delete r.labelMap:r.labelMap=H(e.labelMap,`${i} ${c} labelMap`));const s={...o,...c==="all"?{}:{mode:"roles"},[d]:r};for(const a of["all","row","column"]){const l=ve(s,a);if(fe(l.fontSize,`Facet ${a} header fontSize`),T(l.fontFamily,`Facet ${a} header fontFamily`),T(l.color,`Facet ${a} header color`),le(l.offset,`Facet ${a} header offset`),l.rotation!==void 0&&!Number.isFinite(l.rotation))throw new TypeError(`Facet ${a} header rotation must resolve to finite radians.`);if(!(typeof l.fontWeight=="string"&&l.fontWeight.length>0||Number.isFinite(l.fontWeight)))throw new TypeError(`Facet ${a} header fontWeight must be a non-empty string or number.`);if(!["start","center","end"].includes(l.align))throw new Error(`Facet ${a} header align must be start, center, or end.`);l.labelMap!==void 0&&H(l.labelMap,`Facet ${a} header labelMap`)}return s}function $e(t,e){let n=Fe();if(t===void 0)return n;S(t,[...V,"labelMap","row","column"],"facet.headers");const{row:i,column:c,...o}=t;Object.keys(o).length>0&&(n=q(n,o,e,"facet.headers"));for(const[d,r]of[["row",i],["column",c]])r!==void 0&&(S(r,K.filter(s=>s!=="role"),`facet.headers.${d}`),n=q(n,{...r,role:d},e,`facet.headers.${d}`));return n}function B(t,e,{definition:n,derived:i,guides:c,scalePolicies:o,facet:d}){const r=t.materializationConfigs.facets?.[n.id]?.headers,s=e.headers===void 0&&r!==void 0?U(r):$e(e.headers,n.grid),a=pe({spacing:e.spacing,topLane:e.topLane,plots:Object.entries(i.children).map(([f,u])=>({id:f,...ne(u)})),children:n.cells.map(f=>({...ue(f.id,i.children[f.id]),value:f.value,...n.grid===void 0?{}:{row:f.row,column:f.column}})),columns:n.grid?.columns.values.length??e.columns,gap:e.gap,align:e.align,padding:e.padding,sharedLegend:c.legend==="shared"}),l={id:n.id,type:"facet",children:n.cells.map(f=>f.id),columns:a.columns,gap:a.gap,align:a.align,padding:a.padding,...a.spacing===void 0?{}:{spacing:a.spacing},...a.topLane===void 0?{}:{topLane:a.topLane},facet:{data:n.data,...d,scales:o.channels,guides:c}};return _(t._withMaterializationConfig(["facets",n.id],{headers:s}),{children:i.children,compositionSpec:l},l.children)}function Y(t,e,n){return z({op:t,description:e},t==="facet"?Ee:Oe,function(i={}){const c=C(i.guides),o=n(this.semanticSpec,i);if(o.family!=="cartesian"&&c.axes==="outer")throw new Error("Polar and Parallel facets do not support outer axes.");const d=k(this.semanticSpec,i.scales??{}),r=I(this,o,{closeInheritedAction:!0,stripTitle:!0,scales:i.scales??{}}),s=o.grid===void 0?{field:o.field,values:o.values}:{values:o.cells.map(a=>a.value),grid:{...o.grid,cells:o.cells.map(a=>({id:a.id,row:a.row,column:a.column,rowValue:a.rowValue,columnValue:a.columnValue,empty:a.empty}))}};return B(this,i,{definition:o,derived:r,guides:c,scalePolicies:d,facet:s})})}const ke=Y("facet","Repeat one direct-source chart by field value.",P);function X(t,e){if(!w(e))throw new TypeError("repeatCharts options must be a plain object.");const n=M(e.id??"repeat","Repeat id"),i=w(e.channel);if(i){const p=Object.keys(e.channel);if(p.length!==1||p[0]!=="parallelDimension"||typeof e.channel.parallelDimension!="string"||e.channel.parallelDimension.length===0)throw new Error("repeatCharts Parallel channel must contain exactly one non-empty parallelDimension.")}else if(!["x","y","theta","r"].includes(e.channel))throw new Error('repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.');if(!Array.isArray(e.fields)||e.fields.length===0||e.fields.some(p=>typeof p!="string"||p.length===0))throw new TypeError("repeatCharts fields must be a non-empty array of field names.");if(new Set(e.fields).size!==e.fields.length)throw new Error("repeatCharts fields must be unique.");const c=G(t.semanticSpec).family,o=e.channel==="r"?"radius":e.channel,d=t.semanticSpec.layers.filter(p=>{if(i){const b=(p.encoding?.parallel?.dimensions??[]).filter(y=>y.field===e.channel.parallelDimension);return c==="parallel"&&p.mark?.type==="line"&&b.length===1}if(["x","y"].includes(e.channel))return c==="cartesian"&&p.encoding?.x?.scale!==void 0&&p.encoding?.y?.scale!==void 0&&p.encoding?.[e.channel]?.field!==void 0&&["point","line","area","bar","rule","tick","rect"].includes(p.mark?.type);const g=t.markConfigs[p.id]?.compositionRole;return c!=="polar"||["pie","radar"].includes(g)?!1:["point","line","arc"].includes(p.mark?.type)&&p.encoding?.[o]?.field!==void 0});let r;if(e.target!==void 0){if(r=M(e.target,"Repeat target"),!d.some(p=>p.id===r))throw new Error(`repeatCharts target "${r}" is not eligible for the requested field role.`)}else if(d.length===1)r=d[0].id;else throw new Error(d.length===0?"repeatCharts requires one eligible complete mark for the requested field role.":"repeatCharts target is ambiguous; provide target.");if(t.semanticSpec.layers.filter(p=>{if(p.id===r)return!1;const g=p.mark?.type==="text"&&p.source===r,b=t.markConfigs[p.id]?.statisticalReference?.source===r;return!g&&!b}).length>0)throw new Error("repeatCharts currently supports one direct mark only.");const a=t.markConfigs[r]??{},l=["boxPlot","gradientPlot","violinPlot","regressionPlot","endpointPlot","ecdfPlot","raincloudPlot","intervalPlot"].find(p=>a[p]!==void 0);if(l!==void 0)throw new Error(`repeatCharts does not replace the ${l} composite role.`);const f=N(t,r),u=Se(t,f.data);if(u?.transform?.length>0&&u.transform[0].type!=="lookup")throw new Error("repeatCharts does not rewrite a derived dataset dependency.");const m=i?f.encoding.parallel:f.encoding[o],h=i?m.dimensions.findIndex(p=>p.field===e.channel.parallelDimension):void 0;return{id:n,target:r,channel:i?{...e.channel}:e.channel,policyKey:i?"parallelDimensions":e.channel,fields:[...e.fields],data:f.data,encoding:m,...h===void 0?{}:{dimensionIndex:h},cells:e.fields.map((p,g)=>({id:`${n}-field-${g+1}`,field:p,value:p}))}}function _e(t,e){const n=t.encoding;return{target:t.target,field:e,fieldType:n.fieldType,...n.temporalUnit===void 0?{}:{temporalUnit:n.temporalUnit},...n.aggregate===void 0?{}:{aggregate:n.aggregate},...n.bin===void 0?{}:{bin:n.bin},...n.stack===void 0?{}:{stack:n.stack},...n.weight===void 0?{}:{weight:n.weight},scale:{id:n.scale}}}function je(t,e,n){return e.encoding.dimensions.map((i,c)=>{const o=ye(t,i.scale);if(o===void 0)throw new Error(`repeatCharts Parallel dimension scale "${i.scale}" is missing.`);const{id:d,...r}=o,s=c===e.dimensionIndex;return{field:s?n:i.field,fieldType:i.fieldType,title:s&&i.title===i.field?n:i.title,scale:r}})}function qe(t,e,n){if(w(e.channel))return t.encodeParallelCoordinates({target:e.target,coordinate:N(t,e.target).coordinate,dimensions:je(t,e,n),...e.encoding.key===void 0?{}:{key:e.encoding.key},missing:e.encoding.missing});const i={x:"encodeX",y:"encodeY",theta:"encodeTheta",r:"encodeR"}[e.channel];return t[i](_e(e,n))}function J(t,e,n,i){const c=t.semanticSpec.title.text===void 0?t:t.removeTitle(),o=Object.fromEntries(e.cells.map(d=>[d.id,qe(c,e,d.field)]));return he(c,e.cells.map(d=>d.id),o,n,i)}const De=z({op:"repeatCharts",description:"Repeat one direct chart across an ordered field-role list."},xe,function(t={}){const e=C(t.guides);if(e.axes==="outer")throw new Error("repeatCharts does not promote axes across different repeated fields.");const n=X(this,t),i={...t.scales??{},[n.policyKey]:t.scales?.[n.policyKey]??"independent"},c=k(this.semanticSpec,i),o=J(this,n,i,!0);return B(this,t,{definition:n,derived:o,guides:e,scalePolicies:c,facet:{values:n.fields,repeat:{target:n.target,channel:n.channel,fields:n.fields}}})}),Le=Y("facetGrid","Repeat one direct-source Cartesian chart across a row and column field grid.",R),Me=x({op:"editFacetHeaders",description:"Edit parent-owned facet header appearance.",scope:"composition"},function(t={}){F(this,"editFacetHeaders");const e=this.compositionSpec.id,n=this.materializationConfigs.facets?.[e];if(!w(n?.headers))throw new Error(`Facet "${e}" requires header configuration.`);const i=q(n.headers,t,this.compositionSpec.facet.grid);return this._withMaterializationConfig(["facets",e],{...n,headers:i}).materializeComposition()}),Pe=x({op:"editFacetScales",description:"Edit facet scale-resolution policies and rederive every cell.",scope:"composition"},function(t={}){F(this,"editFacetScales"),S(t,$,"editFacetScales",{allowEmpty:!1,emptyMessage:"editFacetScales requires at least one channel policy change."});for(const i of Object.keys(t))if(!A(this.semanticSpec).some(c=>c.policyKey===i))throw new Error(`Facet scale channel "${i}" is not used by an affected layer.`);const e=this.compositionSpec.facet,n={...e.scales,...t};if($.every(i=>n[i]===e.scales[i]))throw new Error("editFacetScales requires at least one channel policy change.");return j(this,{scales:n,guides:e.guides})}),Re=x({op:"editFacetGuides",description:"Edit facet guide ownership and rederive every cell.",scope:"composition"},function(t={}){F(this,"editFacetGuides"),S(t,W,"editFacetGuides",{allowEmpty:!1,emptyMessage:"editFacetGuides requires at least one guide policy."});const e=this.compositionSpec.facet;Object.hasOwn(t,"legend")&&C({legend:t.legend});const n=C({...e.guides,...t},!0);if(G(this.semanticSpec).family!=="cartesian"&&n.axes==="outer")throw new Error("Polar and Parallel facets do not support outer axes.");if(e.repeat!==void 0&&n.axes==="outer")throw new Error("repeatCharts does not promote axes across different repeated fields.");return j(this,{scales:e.scales,guides:n})});function Ae(t,e,n=!1){if(!(t instanceof e.constructor))throw new TypeError("editFacetSource program must be a ChartProgram.");if(t.compositionSpec!==void 0)throw new Error("editFacetSource program must be a complete unit ChartProgram.");if(t.actionStack.length!==0)throw new Error("editFacetSource program has an unfinished action stack.");const i=e.compositionSpec.children.find(a=>e.children[a]?.semanticSpec.layers.length>0)??e.compositionSpec.children[0],c=e.children[i]?.materializationConfigs??{},{canvas:o,theme:d,facets:r,...s}=c;return new e.constructor({semanticSpec:t.semanticSpec,graphicSpec:t.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:L({...n?{...s,...t.materializationConfigs}:{...t.materializationConfigs,...s},...t.materializationConfigs.canvas===void 0?{}:{canvas:t.materializationConfigs.canvas},...t.materializationConfigs.theme===void 0?{}:{theme:t.materializationConfigs.theme}}),children:{},context:t.context,trace:e.trace,actionStack:e.actionStack,actionSequence:e._actionSequence})}const Ge=z({op:"editFacetSource",description:"Reapply one facet, grid, or repeat recipe to a revised complete unit program.",scope:"composition"},Te,function(t={}){F(this,"editFacetSource");const e=this.compositionSpec,n=this.materializationConfigs.facets?.[e.id];if(Object.hasOwn(t,"program")===Object.hasOwn(t,"action"))throw new Error("editFacetSource requires exactly one of program or action.");let i=t.program,c=this;if(t.action!==void 0){S(t.action,["method","options"],"editFacetSource action");const l=T(t.action.method,"editFacetSource action method");let f=E(this,!0);const u=typeof f[l]=="function"?ae(f[l]):void 0;if(!u||!ze.has(l)||u.scope==="composition"||/^(?:_|materialize|rematerialize|replay|editSemantic$|editGraphics$|createGraphics$)/.test(l))throw new Error("editFacetSource action requires a unit chart-authoring action.");f=re(f,de({marks:f.semanticSpec.layers.map(h=>se(f,h))}));for(const h of te(f)){const p=f.guideConfigs.axis?.[h.id]??{},g=ee(h.channel,h.id);for(const[b,y]of["line","ticks","labels","title"].entries()){if(!p[y]||f.graphicSpec.objects[g[b]])continue;const{mode:D,inferredValues:Ie,inferredText:Ne,inferredRotation:He,inferredOffset:Ue,...O}=p[y];D==="values"&&delete O.count,D==="count"&&delete O.values;const Q=y[0].toUpperCase()+y.slice(1);f=f[`create${h.channel.toUpperCase()}Axis${Q}`]({...O,...ie(h.id,h.channel)})}}const m=f[l](t.action.options??{});c=new this.constructor({...this,trace:m.trace,actionStack:m.actionStack,actionSequence:m._actionSequence}),i=new this.constructor({...m,actionStack:[],actionSequence:m._actionSequence})}let o=Ae(i,c,t.action!==void 0);n!==void 0&&(o=o._withMaterializationConfig(["facets",e.id],n));const d=w(e.facet.guides.legend),r=d?{...e.facet.guides,legend:!1}:e.facet.guides;let s;const a=v(o,v(this,e.facet.scales));if(e.facet.grid!==void 0?s=o.facetGrid({id:e.id,data:e.facet.data,rows:e.facet.grid.rows,columns:e.facet.grid.columns,combinations:e.facet.grid.combinations,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:r}):e.facet.repeat!==void 0?s=o.repeatCharts({id:e.id,...e.facet.repeat,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:r}):s=o.facet({id:e.id,data:e.facet.data,field:e.facet.field,values:e.facet.values,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:r}),d){const l=s.compositionSpec;s=s._withCompositionState({children:s.children,compositionSpec:{...l,facet:{...l.facet,guides:e.facet.guides}}})}return n!==void 0&&(s=s._withMaterializationConfig(["facets",e.id],n).materializeComposition()),s.semanticSpec.title.text!==void 0&&(s=s.removeTitle()),this.semanticSpec.title.text!==void 0&&(s=s.createTitle({...this.titleConfig,...this.semanticSpec.title})),we(s,this.materializationConfigs.theme)});function Ct(t){oe((e,n)=>{const{program:i,plan:c}=ce(E(e),n),o=e.compositionSpec,d=new e.constructor({...i,graphicSpec:e.graphicSpec,children:e.children,materializationConfigs:{...i.materializationConfigs,facets:e.materializationConfigs.facets,...e.titleConfig===void 0?{}:{title:e.titleConfig}},actionSequence:i._actionSequence,compositionSpec:{...o,facet:{...o.facet,data:c.replacements.get(o.facet.data)??o.facet.data}}});return j(d,o.facet)}),t.prototype.replayDerivedData=me,t.prototype.composeFacetGuides=ge,t.prototype.facet=ke,t.prototype.facetGrid=Le,t.prototype.repeatCharts=De,t.prototype.editFacetHeaders=Me,t.prototype.editFacetScales=Pe,t.prototype.editFacetGuides=Re,t.prototype.editFacetSource=Ge}export{Re as editFacetGuides,Me as editFacetHeaders,Pe as editFacetScales,Ge as editFacetSource,ke as facet,Le as facetGrid,Ct as registerFacetActions,De as repeatCharts};