ggaction 0.0.11 → 0.0.13

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 (289) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +4 -2
  11. package/src/actions/basic.js +2 -0
  12. package/src/actions/boxPlots/materialize.js +2 -1
  13. package/src/actions/canvas/actions.js +3 -1
  14. package/src/actions/canvas/fitting.js +211 -0
  15. package/src/actions/canvas/index.js +2 -0
  16. package/src/actions/categoryOrder/index.js +3 -2
  17. package/src/actions/charts/area.js +47 -0
  18. package/src/actions/charts/bar.js +11 -5
  19. package/src/actions/charts/beeswarm.js +70 -0
  20. package/src/actions/charts/density.js +44 -0
  21. package/src/actions/charts/ecdf.js +183 -0
  22. package/src/actions/charts/endpoints.js +451 -0
  23. package/src/actions/charts/heatmap.js +2 -2
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +71 -0
  26. package/src/actions/charts/index.js +37 -0
  27. package/src/actions/charts/interval-regression.js +169 -0
  28. package/src/actions/charts/line.js +5 -5
  29. package/src/actions/charts/parallel.js +1 -1
  30. package/src/actions/charts/pie.js +44 -0
  31. package/src/actions/charts/polar.js +123 -0
  32. package/src/actions/charts/radar.js +299 -0
  33. package/src/actions/charts/radial.js +42 -0
  34. package/src/actions/charts/raincloud.js +422 -0
  35. package/src/actions/charts/rug-strip.js +279 -0
  36. package/src/actions/charts/scatter.js +9 -3
  37. package/src/actions/charts/shared.js +69 -10
  38. package/src/actions/composition/actions.js +118 -1
  39. package/src/actions/data/bin.js +29 -0
  40. package/src/actions/data/computed.js +24 -0
  41. package/src/actions/data/derived.js +66 -0
  42. package/src/actions/data/ecdf.js +38 -0
  43. package/src/actions/data/filter.js +212 -28
  44. package/src/actions/data/fold.js +24 -0
  45. package/src/actions/data/index.js +24 -0
  46. package/src/actions/data/interval.js +2 -0
  47. package/src/actions/data/intervalEdit.js +86 -4
  48. package/src/actions/data/regression.js +5 -2
  49. package/src/actions/data/stack.js +26 -0
  50. package/src/actions/data/summary.js +26 -0
  51. package/src/actions/data/timeUnit.js +1 -1
  52. package/src/actions/encodings/appearance.js +32 -13
  53. package/src/actions/encodings/barWidth.js +8 -0
  54. package/src/actions/encodings/basic.js +2 -0
  55. package/src/actions/encodings/color/continuous.js +19 -59
  56. package/src/actions/encodings/color/index.js +28 -22
  57. package/src/actions/encodings/color/layout.js +3 -97
  58. package/src/actions/encodings/color/policy.js +5 -8
  59. package/src/actions/encodings/density/resolve.js +37 -5
  60. package/src/actions/encodings/density.js +120 -33
  61. package/src/actions/encodings/horizon/resolve.js +8 -4
  62. package/src/actions/encodings/horizon.js +18 -8
  63. package/src/actions/encodings/index.js +2 -0
  64. package/src/actions/encodings/offset.js +33 -27
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +40 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +37 -2
  69. package/src/actions/encodings/position/policies/area.js +4 -42
  70. package/src/actions/encodings/position/policies/bar.js +48 -13
  71. package/src/actions/encodings/position/resolve.js +79 -24
  72. package/src/actions/encodings/ranged.js +123 -97
  73. package/src/actions/encodings/remove.js +37 -32
  74. package/src/actions/encodings/ruleAppearance.js +23 -9
  75. package/src/actions/encodings/seriesLayout.js +104 -0
  76. package/src/actions/encodings/shared.js +25 -23
  77. package/src/actions/encodings/strokeDash.js +9 -6
  78. package/src/actions/encodings/temporal.js +7 -0
  79. package/src/actions/encodings/text.js +30 -15
  80. package/src/actions/errorBands/create.js +18 -5
  81. package/src/actions/errorBands/edit.js +341 -21
  82. package/src/actions/errorBars/create.js +153 -11
  83. package/src/actions/errorBars/edit.js +365 -19
  84. package/src/actions/facets/actions.js +410 -12
  85. package/src/actions/facets/derive.js +57 -8
  86. package/src/actions/gradientPlots/components.js +33 -11
  87. package/src/actions/gradientPlots/materialize.js +5 -7
  88. package/src/actions/guides/applicability.js +20 -17
  89. package/src/actions/guides/axes/axes.js +4 -3
  90. package/src/actions/guides/axes/axis.js +18 -18
  91. package/src/actions/guides/axes/components.js +43 -0
  92. package/src/actions/guides/axes/edit.js +16 -51
  93. package/src/actions/guides/axes/labels.js +129 -19
  94. package/src/actions/guides/axes/lines.js +7 -5
  95. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  96. package/src/actions/guides/axes/parallel/policy.js +137 -0
  97. package/src/actions/guides/axes/parallel/resolve.js +44 -23
  98. package/src/actions/guides/axes/parallel.js +93 -217
  99. package/src/actions/guides/axes/policy.js +21 -67
  100. package/src/actions/guides/axes/tickGroups.js +14 -8
  101. package/src/actions/guides/axes/ticks.js +5 -4
  102. package/src/actions/guides/axes/titles.js +21 -8
  103. package/src/actions/guides/basic.js +2 -0
  104. package/src/actions/guides/facade.js +106 -0
  105. package/src/actions/guides/facadeAxes.js +200 -0
  106. package/src/actions/guides/facadeLegend.js +63 -0
  107. package/src/actions/guides/grids/grid.js +37 -33
  108. package/src/actions/guides/grids/resolve.js +10 -2
  109. package/src/actions/guides/guides.js +35 -28
  110. package/src/actions/guides/legends/categorical/actions.js +193 -249
  111. package/src/actions/guides/legends/categorical/components.js +4 -5
  112. package/src/actions/guides/legends/categorical/layout.js +36 -349
  113. package/src/actions/guides/legends/categorical/options.js +30 -14
  114. package/src/actions/guides/legends/categorical/recipes.js +14 -0
  115. package/src/actions/guides/legends/categorical/resolve.js +21 -10
  116. package/src/actions/guides/legends/categorical/symbols.js +2 -3
  117. package/src/actions/guides/legends/continuous/common.js +69 -14
  118. package/src/actions/guides/legends/continuous/gradient.js +111 -83
  119. package/src/actions/guides/legends/continuous/index.js +6 -2
  120. package/src/actions/guides/legends/continuous/interval.js +71 -92
  121. package/src/actions/guides/legends/continuous/opacity.js +105 -84
  122. package/src/actions/guides/legends/creation.js +27 -0
  123. package/src/actions/guides/legends/edit.js +198 -66
  124. package/src/actions/guides/legends/focused.js +2 -2
  125. package/src/actions/guides/legends/lane.js +98 -86
  126. package/src/actions/guides/legends/lifecycle.js +132 -0
  127. package/src/actions/guides/legends/remove.js +31 -76
  128. package/src/actions/guides/legends/size.js +149 -105
  129. package/src/actions/guides/legends/strokeWidth.js +106 -64
  130. package/src/actions/guides/legends/target.js +12 -0
  131. package/src/actions/guides/legends/transition.js +43 -0
  132. package/src/actions/guides/polar/axes/facade.js +77 -62
  133. package/src/actions/guides/polar/axes/labels.js +5 -4
  134. package/src/actions/guides/polar/axes/lines.js +2 -1
  135. package/src/actions/guides/polar/axes/shared.js +6 -0
  136. package/src/actions/guides/polar/axes/ticks.js +5 -4
  137. package/src/actions/guides/polar/axes/titles.js +5 -4
  138. package/src/actions/guides/polar/grids.js +7 -7
  139. package/src/actions/guides/reuse.js +44 -0
  140. package/src/actions/guides/tickValues.js +19 -21
  141. package/src/actions/index.js +2 -0
  142. package/src/actions/intervals/resolve.js +35 -15
  143. package/src/actions/marks/arc/actions.js +1 -1
  144. package/src/actions/marks/area/actions.js +18 -4
  145. package/src/actions/marks/area/materialize.js +5 -2
  146. package/src/actions/marks/bar/materialize.js +11 -7
  147. package/src/actions/marks/index.js +5 -0
  148. package/src/actions/marks/line/actions.js +43 -108
  149. package/src/actions/marks/line/materialize.js +34 -52
  150. package/src/actions/marks/point/edit.js +11 -15
  151. package/src/actions/marks/point/index.js +1 -0
  152. package/src/actions/marks/point/jitter.js +3 -0
  153. package/src/actions/marks/point/materialize.js +138 -14
  154. package/src/actions/marks/point/packing.js +68 -0
  155. package/src/actions/marks/references.js +87 -0
  156. package/src/actions/marks/remove.js +17 -4
  157. package/src/actions/marks/rule/actions.js +60 -4
  158. package/src/actions/marks/rule/appearance.js +26 -0
  159. package/src/actions/marks/shared.js +12 -12
  160. package/src/actions/marks/text/actions.js +224 -6
  161. package/src/actions/marks/text/layout.js +2 -2
  162. package/src/actions/primitives/createGraphics.js +3 -1
  163. package/src/actions/primitives/semantic.js +1 -1
  164. package/src/actions/primitives/semanticAction.js +39 -2
  165. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  166. package/src/actions/primitives/semanticValidation/layer.js +18 -1
  167. package/src/actions/primitives/semanticValidation/scale.js +10 -1
  168. package/src/actions/regression/create.js +1 -1
  169. package/src/actions/regression/edit.js +19 -6
  170. package/src/actions/regression/resolve.js +1 -1
  171. package/src/actions/scales/consumers/common.js +20 -53
  172. package/src/actions/scales/consumers/families.js +27 -14
  173. package/src/actions/scales/consumers/index.js +23 -2
  174. package/src/actions/scales/consumers/seriesLayout.js +33 -2
  175. package/src/actions/scales/create.js +4 -4
  176. package/src/actions/scales/definitions.js +19 -3
  177. package/src/actions/scales/edit.js +46 -34
  178. package/src/actions/scales/editPolicy.js +21 -31
  179. package/src/actions/scales/materialize.js +10 -46
  180. package/src/actions/scales/patch.js +1 -1
  181. package/src/actions/scales/preview.js +39 -0
  182. package/src/actions/scales/quantitativeColor.js +9 -5
  183. package/src/actions/theme/actions.js +43 -0
  184. package/src/actions/theme/index.js +9 -0
  185. package/src/actions/theme/reconcile.js +882 -0
  186. package/src/actions/titles/actions.js +11 -10
  187. package/src/actions/titles/resolve.js +1 -40
  188. package/src/actions/violinPlots/create.js +86 -42
  189. package/src/actions/violinPlots/edit.js +191 -0
  190. package/src/actions/violinPlots/index.js +2 -0
  191. package/src/core/ChartProgram.js +7 -0
  192. package/src/core/action.js +22 -2
  193. package/src/core/compositionState.js +136 -8
  194. package/src/core/vocabulary.js +2 -2
  195. package/src/grammar/aggregate.js +66 -23
  196. package/src/grammar/arcs.js +213 -65
  197. package/src/grammar/areaEndpoints.js +46 -0
  198. package/src/grammar/areaSeries.js +87 -41
  199. package/src/grammar/bars/aggregate.js +33 -12
  200. package/src/grammar/bars/policy.js +9 -7
  201. package/src/grammar/bin.js +181 -0
  202. package/src/grammar/categoryOrder.js +34 -0
  203. package/src/grammar/computed.js +150 -0
  204. package/src/grammar/ecdf.js +198 -0
  205. package/src/grammar/facets/index.js +117 -6
  206. package/src/grammar/fold.js +123 -0
  207. package/src/grammar/horizon.js +6 -4
  208. package/src/grammar/interval.js +37 -20
  209. package/src/grammar/lineSeries.js +20 -63
  210. package/src/grammar/markFilter.js +22 -4
  211. package/src/grammar/markLabels.js +93 -0
  212. package/src/grammar/parallelAxisTitles.js +16 -0
  213. package/src/grammar/pathSeries.js +91 -0
  214. package/src/grammar/pointPacking.js +183 -0
  215. package/src/grammar/positionCompatibility.js +1 -1
  216. package/src/grammar/positionDatum.js +12 -0
  217. package/src/grammar/rects.js +9 -1
  218. package/src/grammar/regression/derive.js +4 -0
  219. package/src/grammar/regression/index.js +7 -2
  220. package/src/grammar/regression/models.js +22 -7
  221. package/src/grammar/regression/parameters.js +40 -13
  222. package/src/grammar/rotation.js +29 -0
  223. package/src/grammar/rules.js +9 -21
  224. package/src/grammar/scales/color.js +23 -1
  225. package/src/grammar/scales/colorConsumers.js +38 -0
  226. package/src/grammar/scales/definition.js +15 -1
  227. package/src/grammar/scales/discretized.js +11 -5
  228. package/src/grammar/scales/fields.js +38 -8
  229. package/src/grammar/scales/mapping.js +2 -0
  230. package/src/grammar/scales/ordinal.js +23 -0
  231. package/src/grammar/scales/radial.js +55 -0
  232. package/src/grammar/scales/types.js +1 -0
  233. package/src/grammar/schemas/semanticPath.js +17 -3
  234. package/src/grammar/stack.js +170 -0
  235. package/src/grammar/statistics/confidenceInterval.js +101 -0
  236. package/src/grammar/summary.js +183 -0
  237. package/src/grammar/text.js +22 -16
  238. package/src/grammar/timeUnit.js +21 -11
  239. package/src/grammar/transformTopology.js +6 -0
  240. package/src/grammar/transforms.js +36 -0
  241. package/src/grammar/valueFormat.js +112 -0
  242. package/src/grammar/window.js +13 -7
  243. package/src/layout/facets.js +86 -23
  244. package/src/layout/guideCollisions.js +12 -0
  245. package/src/layout/legend.js +2 -10
  246. package/src/layout/legendItems.js +102 -0
  247. package/src/layout/legendLane.js +100 -11
  248. package/src/materialization/bars/aggregate.js +38 -152
  249. package/src/materialization/bars/histogram.js +24 -0
  250. package/src/materialization/categorySlotOffset.js +40 -0
  251. package/src/materialization/compositionSnapshot.js +1 -1
  252. package/src/materialization/dependencies.js +57 -16
  253. package/src/materialization/encodings.js +1 -1
  254. package/src/materialization/facetGuides/legacyCategorical.js +7 -1
  255. package/src/materialization/facetGuides/placement.js +16 -1
  256. package/src/materialization/facetGuides/preparation.js +26 -1
  257. package/src/materialization/facets.js +11 -2
  258. package/src/materialization/guides/layout.js +57 -0
  259. package/src/materialization/guides/resources.js +2 -2
  260. package/src/materialization/legends.js +10 -1
  261. package/src/materialization/marks/capabilities.js +30 -7
  262. package/src/materialization/marks/grouping.js +52 -0
  263. package/src/materialization/marks/index.js +63 -25
  264. package/src/materialization/marks/policies.js +2 -0
  265. package/src/materialization/rect.js +33 -33
  266. package/src/materialization/rowEncoding.js +70 -14
  267. package/src/materialization/scaleGuideDependencies.js +8 -5
  268. package/src/materialization/scales/map.js +1 -0
  269. package/src/materialization/scales/policies/arc.js +42 -0
  270. package/src/materialization/scales/policies/bar.js +0 -43
  271. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  272. package/src/materialization/scales/policies/offset.js +54 -0
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +48 -18
  275. package/src/materialization/selection/compatibility.js +11 -0
  276. package/src/materialization/selection/items/bar.js +12 -68
  277. package/src/materialization/selection/items/common.js +8 -2
  278. package/src/materialization/selection/items/path.js +3 -13
  279. package/src/materialization/selection/items/rect.js +7 -4
  280. package/src/materialization/text.js +64 -5
  281. package/src/mcp/adapter.js +2 -2
  282. package/src/selectors/datasets.js +13 -0
  283. package/src/selectors/layers.js +3 -2
  284. package/src/selectors/scales.js +6 -0
  285. package/src/theme/defaults.js +39 -0
  286. package/types/basic.d.ts +11 -2
  287. package/types/index.d.ts +126 -0
  288. package/types/program.d.ts +1327 -124
  289. package/src/materialization/bars/grouped.js +0 -144
@@ -0,0 +1,882 @@
1
+ import { themeTokens } from "../../theme/defaults.js";
2
+ import { findLayer } from "../../selectors/layers.js";
3
+
4
+ const COLOR_PROPERTIES = Object.freeze(["background", "fill", "stroke"]);
5
+ const COLOR_TOKENS = Object.freeze([
6
+ "mark",
7
+ "text",
8
+ "strongText",
9
+ "mutedText",
10
+ "axis",
11
+ "axisTitle",
12
+ "grid",
13
+ "border",
14
+ "sizeSymbol",
15
+ "regressionBand",
16
+ "boxLine",
17
+ "boxMedian",
18
+ "referenceLine",
19
+ "referenceBand",
20
+ "gradientCenter"
21
+ ]);
22
+
23
+ function tokenFor(value, names) {
24
+ for (const name of names) {
25
+ const tokens = themeTokens(name);
26
+ for (const token of COLOR_TOKENS) {
27
+ if (value === tokens[token]) return token;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ function roleValues(role, names, legacy = []) {
34
+ return new Set([
35
+ ...names.map(name => themeTokens(name)[role]),
36
+ themeTokens("light")[role],
37
+ ...legacy.map(token => themeTokens("light")[token] ?? token)
38
+ ]);
39
+ }
40
+
41
+ function mapRoleValue(value, role, names, targetTokens, legacy) {
42
+ if (Array.isArray(value)) {
43
+ let changed = false;
44
+ const next = value.map(item => {
45
+ const mapped = mapRoleValue(item, role, names, targetTokens, legacy);
46
+ changed ||= mapped.changed;
47
+ return mapped.value;
48
+ });
49
+ return { changed, value: changed ? next : value };
50
+ }
51
+ if (!roleValues(role, names, legacy).has(value)) {
52
+ return { changed: false, value };
53
+ }
54
+ return { changed: value !== targetTokens[role], value: targetTokens[role] };
55
+ }
56
+
57
+ function mapColorValue(value, names, targetTokens) {
58
+ if (Array.isArray(value)) {
59
+ let changed = false;
60
+ const next = value.map(item => {
61
+ const mapped = mapColorValue(item, names, targetTokens);
62
+ changed ||= mapped.changed;
63
+ return mapped.value;
64
+ });
65
+ return { changed, value: changed ? next : value };
66
+ }
67
+ const token = tokenFor(value, names);
68
+ if (token === undefined) return { changed: false, value };
69
+ const next = targetTokens[token];
70
+ return { changed: next !== value, value: next };
71
+ }
72
+
73
+ function isFieldDrivenMark(program, id, property) {
74
+ if (!(["fill", "stroke"].includes(property))) return false;
75
+ const layer = findLayer(program, id);
76
+ return layer?.encoding?.color?.field !== undefined;
77
+ }
78
+
79
+ function isRegressionBand(program, id) {
80
+ return Object.values(program.markConfigs).some(
81
+ config => config.regression?.bandId === id
82
+ );
83
+ }
84
+
85
+ function referenceRole(program, id, property) {
86
+ let role;
87
+ for (const node of program.trace.children) {
88
+ const created = /^create(Point|Line|Area|Arc|Bar|Rect|Rule|Tick|Text)Mark$/u
89
+ .exec(node.op);
90
+ const createdId = created === null
91
+ ? undefined
92
+ : node.args.id ?? created[1].toLowerCase();
93
+ if (createdId === id ||
94
+ (node.op === "removeMark" && node.args.target === id)) {
95
+ role = undefined;
96
+ }
97
+ if (node.op === "createReferenceLine" &&
98
+ (node.args.id ?? "referenceLine") === id && property === "stroke") {
99
+ role = "referenceLine";
100
+ }
101
+ if (node.op === "createReferenceBand" &&
102
+ (node.args.id ?? "referenceBand") === id && property === "fill") {
103
+ role = "referenceBand";
104
+ }
105
+ }
106
+ return role;
107
+ }
108
+
109
+ function componentRole(program, id, property) {
110
+ for (const [owner, config] of Object.entries(program.markConfigs)) {
111
+ const box = config.boxPlot;
112
+ if (box !== undefined) {
113
+ if (id === box.medianId && property === "stroke") return "boxMedian";
114
+ if (id === box.outlierId && ["fill", "stroke"].includes(property)) {
115
+ return "boxLine";
116
+ }
117
+ if (id === box.whiskerId && property === "stroke") return "boxLine";
118
+ const whisker = program.markConfigs[box.whiskerId]?.errorBar;
119
+ if ([whisker?.lowerCapId, whisker?.upperCapId].includes(id) &&
120
+ property === "stroke") return "boxLine";
121
+ }
122
+ if (config.gradientPlot !== undefined &&
123
+ id === `${owner}Center` && property === "stroke") {
124
+ return "gradientCenter";
125
+ }
126
+ }
127
+ return referenceRole(program, id, property);
128
+ }
129
+
130
+ function graphicRole(program, id, property) {
131
+ if (id === "canvas" && property === "background") return "background";
132
+ const component = componentRole(program, id, property);
133
+ if (component !== undefined) return component;
134
+ const layer = findLayer(program, id);
135
+ if (layer !== undefined && ["fill", "stroke"].includes(property)) {
136
+ if (layer.encoding?.color?.field !== undefined) return undefined;
137
+ if (property === "fill" && isRegressionBand(program, id)) {
138
+ return "regressionBand";
139
+ }
140
+ return layer.mark?.type === "text" ? "text" : "mark";
141
+ }
142
+ if (/AxisLines?$/u.test(id) && property === "stroke") return "axis";
143
+ if (/AxisTicks$/u.test(id) && property === "stroke") return "mutedText";
144
+ if (/^parallelAxisLabels$/u.test(id) && property === "fill") return "axis";
145
+ if (/AxisLabels$/u.test(id) && property === "fill") return "text";
146
+ if (/AxisTitles?$/u.test(id) && property === "fill") return "axisTitle";
147
+ if (/Grid(?:Lines|Circles)?$/u.test(id) && property === "stroke") return "grid";
148
+ if (id === "chartTitle" && property === "fill") return "strongText";
149
+ if (id === "chartSubtitle" && property === "fill") return "mutedText";
150
+ if (/(?:Legend|Gradient)Labels$/u.test(id) && property === "fill") return "text";
151
+ if (/(?:Legend|Gradient)Title$/u.test(id) && property === "fill") return "strongText";
152
+ if (/(?:Legend|Gradient)Background$/u.test(id) && property === "stroke") return "border";
153
+ if (/GradientTicks$/u.test(id) && property === "stroke") return "mutedText";
154
+ if (/opacityLegendSymbols$/iu.test(id) && ["fill", "stroke"].includes(property)) {
155
+ return "mark";
156
+ }
157
+ if (/sizeLegendSymbols$/iu.test(id) && ["fill", "stroke"].includes(property)) {
158
+ return "sizeSymbol";
159
+ }
160
+ if (/strokeWidthLegendSymbols$/iu.test(id) && property === "stroke") {
161
+ return "mark";
162
+ }
163
+ return undefined;
164
+ }
165
+
166
+ function roleLegacy(role) {
167
+ if (role === "axis") return ["text"];
168
+ if (role === "axisTitle") return ["text", "strongText"];
169
+ if (role === "strongText") return ["text"];
170
+ if (role === "grid") return ["#d7e0ea"];
171
+ return [];
172
+ }
173
+
174
+ function isOverridden(overrides, key) {
175
+ return overrides.has(key);
176
+ }
177
+
178
+ function isFieldDrivenLegendSymbol(id) {
179
+ return /(?:color|series)Legend(?:Symbol|Symbols|Gradient|Swatches|Lines|Points)/iu
180
+ .test(id);
181
+ }
182
+
183
+ function mapGraphicProperties(
184
+ program,
185
+ id,
186
+ type,
187
+ properties,
188
+ names,
189
+ targetTokens,
190
+ overrides,
191
+ overrideId = id
192
+ ) {
193
+ let changed = false;
194
+ const next = { ...properties };
195
+ for (const property of COLOR_PROPERTIES) {
196
+ if (!Object.hasOwn(properties, property)) continue;
197
+ if (isOverridden(overrides, `g:${overrideId}.${property}`) ||
198
+ isOverridden(overrides, `g:${id}.${property}`)) continue;
199
+ const role = graphicRole(program, id, property);
200
+ if (role === undefined || isFieldDrivenLegendSymbol(id) ||
201
+ isFieldDrivenMark(program, id, property)) continue;
202
+ const mapped = mapRoleValue(
203
+ properties[property],
204
+ role,
205
+ names,
206
+ targetTokens,
207
+ roleLegacy(role)
208
+ );
209
+ if (mapped.changed) {
210
+ next[property] = mapped.value;
211
+ changed = true;
212
+ }
213
+ }
214
+ return { changed, properties: changed ? next : properties, type };
215
+ }
216
+
217
+ function recolorGraphic(program, id, graphic, names, targetTokens, overrides) {
218
+ if (graphic.items === undefined) {
219
+ const mapped = mapGraphicProperties(
220
+ program,
221
+ id,
222
+ graphic.type,
223
+ graphic.properties,
224
+ names,
225
+ targetTokens,
226
+ overrides,
227
+ id
228
+ );
229
+ if (!mapped.changed) return program;
230
+ let next = program;
231
+ for (const property of COLOR_PROPERTIES) {
232
+ if (mapped.properties[property] !== graphic.properties[property]) {
233
+ next = next.editGraphics({
234
+ target: id,
235
+ property,
236
+ value: mapped.properties[property]
237
+ });
238
+ }
239
+ }
240
+ return next;
241
+ }
242
+
243
+ let changed = false;
244
+ const items = graphic.items.map(item => {
245
+ const type = item.type ?? graphic.type;
246
+ const mapped = mapGraphicProperties(
247
+ program,
248
+ id,
249
+ type,
250
+ item.properties,
251
+ names,
252
+ targetTokens,
253
+ overrides,
254
+ item.id
255
+ );
256
+ changed ||= mapped.changed;
257
+ return { type, properties: mapped.properties };
258
+ });
259
+ return changed
260
+ ? program.editGraphics({ target: id, property: "items", value: items })
261
+ : program;
262
+ }
263
+
264
+ function configRole(program, path) {
265
+ const property = path.at(-1);
266
+ if (path[0] === "marks" && ["fill", "stroke"].includes(property)) {
267
+ if (path.includes("boxPlot") && path.includes("median") &&
268
+ property === "stroke") return "boxMedian";
269
+ if (path.includes("gradientPlot") && path.includes("center") &&
270
+ property === "stroke") return "gradientCenter";
271
+ const component = componentRole(program, path[1], property);
272
+ if (component !== undefined) return component;
273
+ const layer = findLayer(program, path[1]);
274
+ if (layer?.encoding?.color?.field !== undefined) return undefined;
275
+ if (property === "fill" && isRegressionBand(program, path[1])) {
276
+ return "regressionBand";
277
+ }
278
+ return layer?.mark?.type === "text" ? "text" : "mark";
279
+ }
280
+ if (path[0] === "guides" && path[1] === "axis") {
281
+ const component = path[3];
282
+ if (property !== "color") return undefined;
283
+ if (component === "line") return "axis";
284
+ if (component === "ticks") return "mutedText";
285
+ if (component === "labels") return "text";
286
+ if (component === "title") return "axisTitle";
287
+ }
288
+ if (path[0] === "guides" && path[1] === "grid" && property === "color") {
289
+ return "grid";
290
+ }
291
+ if (path[0] === "title" && property === "color") {
292
+ if (path.includes("titleStyle")) return "strongText";
293
+ if (path.includes("subtitleStyle")) return "mutedText";
294
+ }
295
+ return undefined;
296
+ }
297
+
298
+ function recolorConfig(
299
+ program,
300
+ value,
301
+ names,
302
+ targetTokens,
303
+ overrides,
304
+ path = []
305
+ ) {
306
+ if (Array.isArray(value)) {
307
+ let changed = false;
308
+ const next = value.map((item, index) => {
309
+ const mapped = recolorConfig(
310
+ program,
311
+ item,
312
+ names,
313
+ targetTokens,
314
+ overrides,
315
+ [...path, index]
316
+ );
317
+ changed ||= mapped.changed;
318
+ return mapped.value;
319
+ });
320
+ return { changed, value: changed ? next : value };
321
+ }
322
+ if (value === null || typeof value !== "object") {
323
+ const property = path.at(-1);
324
+ if (!["color", "fill", "stroke", "background"].includes(property)) {
325
+ return { changed: false, value };
326
+ }
327
+ if (isOverridden(overrides, `c:${path.join(".")}`)) {
328
+ return { changed: false, value };
329
+ }
330
+ if (property === "background" && value === "transparent") {
331
+ return { changed: false, value };
332
+ }
333
+ const role = configRole(program, path);
334
+ return role === undefined
335
+ ? mapColorValue(value, names, targetTokens)
336
+ : mapRoleValue(value, role, names, targetTokens, roleLegacy(role));
337
+ }
338
+ if (["theme", "highlights", "selections"].includes(path[0])) {
339
+ return { changed: false, value };
340
+ }
341
+ let changed = false;
342
+ const next = {};
343
+ for (const [key, item] of Object.entries(value)) {
344
+ const mapped = recolorConfig(
345
+ program,
346
+ item,
347
+ names,
348
+ targetTokens,
349
+ overrides,
350
+ [...path, key]
351
+ );
352
+ changed ||= mapped.changed;
353
+ next[key] = mapped.value;
354
+ }
355
+ return { changed, value: changed ? next : value };
356
+ }
357
+
358
+ function addMarkOverrides(overrides, program, op, args) {
359
+ const match = /^(?:create|edit)(Point|Line|Area|Arc|Bar|Rect|Rule|Tick|Text)Mark$/u
360
+ .exec(op);
361
+ if (match === null) return;
362
+ const type = match[1].toLowerCase();
363
+ const candidates = program.semanticSpec.layers.filter(
364
+ layer => layer.mark?.type === type
365
+ );
366
+ const id = args.target ?? args.id ??
367
+ (op.startsWith("create") ? type : undefined) ??
368
+ (candidates.length === 1 ? candidates[0].id : program.context.currentMark);
369
+ if (typeof id !== "string") return;
370
+ for (const property of ["fill", "stroke"]) {
371
+ if (!Object.hasOwn(args, property)) continue;
372
+ overrides.add(`c:marks.${id}.${property}`);
373
+ overrides.add(`g:${id}.${property}`);
374
+ }
375
+ }
376
+
377
+ function addFacadeMarkOverrides(overrides, op, args) {
378
+ const facades = {
379
+ createScatterPlot: ["scatterPlot", "point", ["fill", "stroke"]],
380
+ createLinePlot: ["linePlot", "line", ["stroke"]],
381
+ createPolarScatterPlot: ["polarScatterPlot", "point", ["fill", "stroke"]],
382
+ createPolarLinePlot: ["polarLinePlot", "line", ["stroke"]],
383
+ createRadarPlot: ["radarPlot", "line", ["stroke"]],
384
+ createRugPlot: ["rugPlot", "tick", ["stroke"]],
385
+ createStripPlot: ["stripPlot", "point", ["fill", "stroke"]],
386
+ createBeeswarmPlot: ["beeswarmPlot", "point", ["fill", "stroke"]],
387
+ createBarPlot: ["barPlot", "bar", ["fill", "stroke"]],
388
+ createAreaPlot: ["areaPlot", "area", ["fill", "stroke"]],
389
+ createPiePlot: ["piePlot", "arc", ["fill", "stroke"]],
390
+ createRosePlot: ["rosePlot", "arc", ["fill", "stroke"]],
391
+ createRadialBarPlot: ["radialBarPlot", "arc", ["fill", "stroke"]],
392
+ createDensityPlot: ["densityPlot", "area", ["fill", "stroke"]],
393
+ createParallelCoordinates: ["parallelCoordinates", "line", ["stroke"]],
394
+ createHistogram: ["histogram", "bar", ["fill", "stroke"]],
395
+ createHeatmap: ["heatmap", "rect", ["stroke"]],
396
+ createHorizonPlot: ["horizonPlot", "area", ["stroke"]],
397
+ editHorizon: ["horizonPlot", "area", ["stroke"]],
398
+ createViolinPlot: ["violinPlot", "area", ["fill", "stroke"]]
399
+ };
400
+ const facade = facades[op];
401
+ if (facade === undefined) return;
402
+ const [defaultId, option, properties] = facade;
403
+ const id = args.id ?? defaultId;
404
+ for (const property of properties) {
405
+ if (!Object.hasOwn(args[option] ?? {}, property)) continue;
406
+ overrides.add(`c:marks.${id}.${property}`);
407
+ overrides.add(`g:${id}.${property}`);
408
+ }
409
+ }
410
+
411
+ function findDescendant(node, op) {
412
+ for (const child of node?.children ?? []) {
413
+ if (child.op === op) return child;
414
+ const nested = findDescendant(child, op);
415
+ if (nested !== undefined) return nested;
416
+ }
417
+ return undefined;
418
+ }
419
+
420
+ function resolveConfiguredOwner(program, key, requested, defaultId) {
421
+ if (typeof requested === "string" && program.markConfigs[requested]?.[key]) {
422
+ return requested;
423
+ }
424
+ if (typeof defaultId === "string" && program.markConfigs[defaultId]?.[key]) {
425
+ return defaultId;
426
+ }
427
+ const candidates = Object.entries(program.markConfigs)
428
+ .filter(([, config]) => config[key] !== undefined)
429
+ .map(([id]) => id);
430
+ return candidates.length === 1 ? candidates[0] : undefined;
431
+ }
432
+
433
+ function addTextFacadeOverrides(overrides, node) {
434
+ if (!["createAnnotation", "createMarkLabels"].includes(node.op) ||
435
+ !Object.hasOwn(node.args, "fill")) return;
436
+ const id = findDescendant(node, "createTextMark")?.args.id ?? node.args.id;
437
+ if (typeof id !== "string") return;
438
+ overrides.add(`c:marks.${id}.fill`);
439
+ overrides.add(`g:${id}.fill`);
440
+ }
441
+
442
+ function addErrorBarOverrides(overrides, program, node) {
443
+ if (!["createErrorBar", "editErrorBar"].includes(node.op) ||
444
+ !Object.hasOwn(node.args, "stroke")) return;
445
+ const id = resolveConfiguredOwner(
446
+ program,
447
+ "errorBar",
448
+ node.op === "createErrorBar" ? node.args.id : node.args.target,
449
+ node.op === "createErrorBar" ? "errorBar" : undefined
450
+ );
451
+ if (id === undefined) return;
452
+ const config = program.markConfigs[id].errorBar;
453
+ for (const target of [id, config.lowerCapId, config.upperCapId]) {
454
+ overrides.add(`c:marks.${target}.stroke`);
455
+ overrides.add(`g:${target}.stroke`);
456
+ }
457
+ }
458
+
459
+ function errorBandTargets(program, node) {
460
+ const id = resolveConfiguredOwner(
461
+ program,
462
+ "errorBand",
463
+ node.op === "createErrorBand" ? node.args.id : node.args.target,
464
+ node.op === "createErrorBand" ? "errorBand" : undefined
465
+ );
466
+ if (id === undefined) return undefined;
467
+ const config = program.markConfigs[id].errorBand;
468
+ return {
469
+ id,
470
+ lower: config.lowerBoundaryId,
471
+ upper: config.upperBoundaryId
472
+ };
473
+ }
474
+
475
+ function addErrorBandOverrides(overrides, program, node) {
476
+ if (!["createErrorBand", "editErrorBand", "editErrorBandBoundary"]
477
+ .includes(node.op)) return;
478
+ const targets = errorBandTargets(program, node);
479
+ if (targets === undefined) return;
480
+ if (Object.hasOwn(node.args, "fill") && node.args.fill !== false) {
481
+ overrides.add(`c:marks.${targets.id}.fill`);
482
+ overrides.add(`g:${targets.id}.fill`);
483
+ }
484
+ const boundary = node.op === "editErrorBandBoundary"
485
+ ? node.args
486
+ : node.args.boundaries;
487
+ if (!Object.hasOwn(boundary ?? {}, "stroke")) return;
488
+ const ids = node.op !== "editErrorBandBoundary" ||
489
+ (node.args.boundary ?? "both") === "both"
490
+ ? [targets.lower, targets.upper]
491
+ : [(node.args.boundary === "lower" ? targets.lower : targets.upper)];
492
+ for (const id of ids) {
493
+ overrides.add(`c:marks.${id}.stroke`);
494
+ overrides.add(`g:${id}.stroke`);
495
+ }
496
+ }
497
+
498
+ function addBoxPlotOverrides(overrides, op, args) {
499
+ if (!["createBoxPlot", "editBoxPlot"].includes(op)) return;
500
+ const id = args.target ?? args.id ?? "boxPlot";
501
+ for (const property of ["fill", "stroke"]) {
502
+ if (!Object.hasOwn(args.box ?? {}, property)) continue;
503
+ overrides.add(`c:marks.${id}.${property}`);
504
+ overrides.add(`c:marks.${id}.boxPlot.box.${property}`);
505
+ overrides.add(`g:${id}.${property}`);
506
+ }
507
+ if (Object.hasOwn(args.median ?? {}, "stroke")) {
508
+ overrides.add(`c:marks.${id}.boxPlot.median.stroke`);
509
+ overrides.add(`c:marks.${id}Median.stroke`);
510
+ overrides.add(`g:${id}Median.stroke`);
511
+ }
512
+ }
513
+
514
+ function addGradientPlotOverrides(overrides, op, args) {
515
+ if (!["createGradientPlot", "editGradientPlot"].includes(op)) return;
516
+ const id = args.target ?? args.id ?? "gradientPlot";
517
+ if (Object.hasOwn(args.center ?? {}, "stroke")) {
518
+ overrides.add(`c:marks.${id}.gradientPlot.center.stroke`);
519
+ overrides.add(`g:${id}Center.stroke`);
520
+ }
521
+ }
522
+
523
+ function addReferenceOverrides(overrides, op, args) {
524
+ const definitions = {
525
+ createReferenceLine: ["referenceLine", ["stroke"]],
526
+ createReferenceBand: ["referenceBand", ["fill", "stroke"]]
527
+ };
528
+ const definition = definitions[op];
529
+ if (definition === undefined) return;
530
+ const [defaultId, properties] = definition;
531
+ const id = args.id ?? defaultId;
532
+ for (const property of properties) {
533
+ if (!Object.hasOwn(args, property)) continue;
534
+ overrides.add(`c:marks.${id}.${property}`);
535
+ overrides.add(`g:${id}.${property}`);
536
+ }
537
+ }
538
+
539
+ function addRegressionOverrides(overrides, node) {
540
+ const style = node.op === "createRegression" || node.op === "editRegression"
541
+ ? node.args.band
542
+ : node.args;
543
+ if (!["createRegression", "editRegression", "createRegressionBand",
544
+ "editRegressionBand"].includes(node.op) || style === false) return;
545
+ const child = findDescendant(
546
+ node,
547
+ node.op.startsWith("create") ? "createRegressionBand" : "editRegressionBand"
548
+ );
549
+ const id = node.op === "createRegressionBand"
550
+ ? node.args.id
551
+ : node.op === "editRegressionBand"
552
+ ? node.args.target ?? child?.args.target
553
+ : child?.args.id ?? child?.args.target;
554
+ if (typeof id !== "string") return;
555
+ if (Object.hasOwn(style ?? {}, "color")) {
556
+ overrides.add(`c:marks.${id}.fill`);
557
+ overrides.add(`g:${id}.fill`);
558
+ }
559
+ if (Object.hasOwn(style ?? {}, "stroke")) {
560
+ overrides.add(`c:marks.${id}.stroke`);
561
+ overrides.add(`g:${id}.stroke`);
562
+ }
563
+ }
564
+
565
+ function clearPrefix(overrides, prefix) {
566
+ for (const key of overrides) {
567
+ if (key.startsWith(prefix)) overrides.delete(key);
568
+ }
569
+ }
570
+
571
+ function visitTrace(node, visitor) {
572
+ visitor(node);
573
+ for (const child of node.children ?? []) visitTrace(child, visitor);
574
+ }
575
+
576
+ function clearRemovedResources(overrides, node) {
577
+ visitTrace(node, current => {
578
+ if (current.op === "editGraphics" && current.args.remove === true &&
579
+ typeof current.args.target === "string") {
580
+ clearPrefix(overrides, `g:${current.args.target}.`);
581
+ }
582
+ if (current.op !== "editSemantic" || current.args.remove !== true ||
583
+ typeof current.args.property !== "string") return;
584
+ const mark = /^layer\[([^\]]+)\]$/u.exec(current.args.property);
585
+ if (mark !== null) {
586
+ clearPrefix(overrides, `c:marks.${mark[1]}.`);
587
+ clearPrefix(overrides, `g:${mark[1]}.`);
588
+ return;
589
+ }
590
+ const axis = /^guide\.axis\.(x|y|theta|radius)$/u
591
+ .exec(current.args.property);
592
+ if (axis !== null) {
593
+ const prefix = axis[1] === "radius" ? "radial" : axis[1];
594
+ clearPrefix(overrides, `c:guides.axis.${axis[1]}.`);
595
+ clearPrefix(overrides, `g:${prefix}Axis`);
596
+ return;
597
+ }
598
+ const grid = /^guide\.grid\.([^\.]+)$/u.exec(current.args.property);
599
+ if (grid !== null) {
600
+ clearPrefix(overrides, `c:guides.grid.${grid[1]}.`);
601
+ clearPrefix(overrides, `g:${grid[1]}Grid`);
602
+ return;
603
+ }
604
+ const legend = /^guide\.legend\.([^\.]+)$/u.exec(current.args.property);
605
+ if (legend !== null) {
606
+ clearPrefix(overrides, `c:guides.legend.${legend[1]}.`);
607
+ }
608
+ });
609
+ }
610
+
611
+ function clearLocalResets(overrides, program, node) {
612
+ if (node.op !== "editErrorBand") return;
613
+ const targets = errorBandTargets(program, node);
614
+ if (targets === undefined) return;
615
+ if (node.args.fill === false) {
616
+ overrides.delete(`c:marks.${targets.id}.fill`);
617
+ overrides.delete(`g:${targets.id}.fill`);
618
+ }
619
+ if (node.args.boundaries === false) {
620
+ for (const id of [targets.lower, targets.upper]) {
621
+ clearPrefix(overrides, `c:marks.${id}.`);
622
+ clearPrefix(overrides, `g:${id}.`);
623
+ }
624
+ }
625
+ }
626
+
627
+ function clearRecreatedOverrides(overrides, op, args) {
628
+ const created = /^create(Point|Line|Area|Arc|Bar|Rect|Rule|Tick|Text)Mark$/u
629
+ .exec(op);
630
+ if (created !== null) {
631
+ const id = args.id ?? created[1].toLowerCase();
632
+ clearPrefix(overrides, `c:marks.${id}.`);
633
+ clearPrefix(overrides, `g:${id}.`);
634
+ }
635
+ if (op === "removeMark" && typeof args.target === "string") {
636
+ clearPrefix(overrides, `c:marks.${args.target}.`);
637
+ clearPrefix(overrides, `g:${args.target}.`);
638
+ }
639
+ if (op === "createTitle") {
640
+ clearPrefix(overrides, "c:title.");
641
+ clearPrefix(overrides, "g:chartTitle.");
642
+ clearPrefix(overrides, "g:chartSubtitle.");
643
+ }
644
+ if (op === "removeTitle") {
645
+ clearPrefix(overrides, "c:title.");
646
+ clearPrefix(overrides, "g:chartTitle.");
647
+ clearPrefix(overrides, "g:chartSubtitle.");
648
+ }
649
+ }
650
+
651
+ function addAxisOverrides(overrides, op, args) {
652
+ const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis(Line|Ticks|Labels|Title)$/u
653
+ .exec(op);
654
+ if (match === null || !Object.hasOwn(args, "color")) return;
655
+ const channel = {
656
+ X: "x",
657
+ Y: "y",
658
+ Theta: "theta",
659
+ Radial: "radius"
660
+ }[match[1]];
661
+ const component = match[2].toLowerCase();
662
+ const idPrefix = match[1] === "Radial" ? "radial" : channel;
663
+ overrides.add(`c:guides.axis.${channel}.${component}.color`);
664
+ overrides.add(
665
+ `g:${idPrefix}Axis${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
666
+ );
667
+ }
668
+
669
+ function addCompleteAxisOverride(overrides, name, args) {
670
+ const channel = { X: "x", Y: "y", Theta: "theta", Radial: "radius" }[name];
671
+ const idPrefix = name === "Radial" ? "radial" : channel;
672
+ const components = {
673
+ line: args.line,
674
+ ticks: args.ticks,
675
+ labels: args.labels,
676
+ title: args.title
677
+ };
678
+ if (args.ticksAndLabels && typeof args.ticksAndLabels === "object") {
679
+ components.ticks = args.ticksAndLabels.ticks;
680
+ components.labels = args.ticksAndLabels.labels;
681
+ }
682
+ for (const [component, options] of Object.entries(components)) {
683
+ if (!Object.hasOwn(options ?? {}, "color")) continue;
684
+ const suffix = `${component[0].toUpperCase()}${component.slice(1)}`;
685
+ overrides.add(`c:guides.axis.${channel}.${component}.color`);
686
+ overrides.add(
687
+ `g:${idPrefix}Axis${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
688
+ );
689
+ }
690
+ }
691
+
692
+ function addCompleteAxisOverrides(overrides, op, args) {
693
+ const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis$/u.exec(op);
694
+ if (match !== null) addCompleteAxisOverride(overrides, match[1], args);
695
+ const group = /^(?:create|edit)(X|Y)AxisTicksAndLabels$/u.exec(op);
696
+ if (group !== null) {
697
+ addCompleteAxisOverride(overrides, group[1], { ticksAndLabels: args });
698
+ }
699
+ if (op !== "createAxes") return;
700
+ for (const [option, name] of [
701
+ ["x", "X"],
702
+ ["y", "Y"],
703
+ ["theta", "Theta"],
704
+ ["radius", "Radial"]
705
+ ]) {
706
+ if (args[option] && typeof args[option] === "object") {
707
+ addCompleteAxisOverride(overrides, name, args[option]);
708
+ }
709
+ }
710
+ }
711
+
712
+ function addGridOverrides(overrides, op, args) {
713
+ const match = /^(?:create|edit)(Horizontal|Vertical|Theta|Radial)Grid$/u.exec(op);
714
+ if (match === null || !Object.hasOwn(args, "color")) return;
715
+ const direction = match[1].toLowerCase();
716
+ overrides.add(`c:guides.grid.${direction}.color`);
717
+ overrides.add(`g:${direction}Grid.stroke`);
718
+ }
719
+
720
+ function addTitleOverrides(overrides, args) {
721
+ for (const [option, id] of [
722
+ ["titleStyle", "chartTitle"],
723
+ ["subtitleStyle", "chartSubtitle"]
724
+ ]) {
725
+ if (!Object.hasOwn(args[option] ?? {}, "color")) continue;
726
+ overrides.add(`c:title.${option}.color`);
727
+ overrides.add(`g:${id}.fill`);
728
+ }
729
+ }
730
+
731
+ function addParallelOverrides(overrides, program, op, args) {
732
+ if (!["createParallelAxis", "editParallelAxis"].includes(op)) return;
733
+ const dimensions = program.guideConfigs.axis?.parallel?.axes?.dimensions;
734
+ const index = dimensions?.findIndex(dimension => dimension.field === args.field);
735
+ if (index === undefined || index < 0) return;
736
+ const components = {
737
+ line: args.line,
738
+ ticks: args.ticks,
739
+ labels: args.labels,
740
+ title: args.title
741
+ };
742
+ if (args.ticksAndLabels && typeof args.ticksAndLabels === "object") {
743
+ components.ticks = args.ticksAndLabels.ticks;
744
+ components.labels = args.ticksAndLabels.labels;
745
+ }
746
+ for (const [component, options] of Object.entries(components)) {
747
+ if (!Object.hasOwn(options ?? {}, "color")) continue;
748
+ overrides.add(
749
+ `c:guides.axis.parallel.axes.dimensions.${index}.${component}.color`
750
+ );
751
+ }
752
+ }
753
+
754
+ function addLegendOverrides(overrides, program, args) {
755
+ const legend = program.guideConfigs.legend ?? {};
756
+ const prefixes = {
757
+ series: "seriesLegend",
758
+ color: "colorLegend",
759
+ interval: "colorLegend",
760
+ gradient: "colorGradient",
761
+ size: "sizeLegend",
762
+ strokeWidth: "strokeWidthLegend",
763
+ opacity: "opacityLegend"
764
+ };
765
+ for (const kind of Object.keys(legend)) {
766
+ if (Object.hasOwn(args.labels ?? {}, "color")) {
767
+ overrides.add(`c:guides.legend.${kind}.labels.color`);
768
+ if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Labels.fill`);
769
+ }
770
+ if (Object.hasOwn(args.titleStyle ?? {}, "color")) {
771
+ overrides.add(`c:guides.legend.${kind}.titleStyle.color`);
772
+ if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Title.fill`);
773
+ }
774
+ if (Object.hasOwn(args.border ?? {}, "color")) {
775
+ overrides.add(`c:guides.legend.${kind}.border.color`);
776
+ if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Background.stroke`);
777
+ }
778
+ }
779
+ }
780
+
781
+ function explicitOverrides(program, node) {
782
+ const { op, args } = node;
783
+ const overrides = new Set();
784
+ if (op === "editGraphics" &&
785
+ typeof args.target === "string" &&
786
+ ["background", "fill", "stroke"].includes(args.property)) {
787
+ overrides.add(`g:${args.target}.${args.property}`);
788
+ }
789
+ if (["createCanvas", "editCanvas"].includes(op) &&
790
+ Object.hasOwn(args, "background")) {
791
+ overrides.add("g:canvas.background");
792
+ }
793
+ addMarkOverrides(overrides, program, op, args);
794
+ addFacadeMarkOverrides(overrides, op, args);
795
+ addTextFacadeOverrides(overrides, node);
796
+ addBoxPlotOverrides(overrides, op, args);
797
+ addGradientPlotOverrides(overrides, op, args);
798
+ addReferenceOverrides(overrides, op, args);
799
+ addErrorBarOverrides(overrides, program, node);
800
+ addErrorBandOverrides(overrides, program, node);
801
+ addRegressionOverrides(overrides, node);
802
+ addAxisOverrides(overrides, op, args);
803
+ addCompleteAxisOverrides(overrides, op, args);
804
+ addGridOverrides(overrides, op, args);
805
+ addParallelOverrides(overrides, program, op, args);
806
+ if (["createTitle", "editTitle"].includes(op)) {
807
+ addTitleOverrides(overrides, args);
808
+ }
809
+ if (["createLegend", "createCategoricalLegend", "editLegend"].includes(op)) {
810
+ addLegendOverrides(overrides, program, args);
811
+ }
812
+ if (op === "editLegendLabels") {
813
+ addLegendOverrides(overrides, program, { labels: args });
814
+ }
815
+ if (op === "editLegendTitle") {
816
+ addLegendOverrides(overrides, program, { titleStyle: args });
817
+ }
818
+ if (op === "editLegendBorder") {
819
+ addLegendOverrides(overrides, program, args);
820
+ }
821
+ return overrides;
822
+ }
823
+
824
+ function collectOverrides(program) {
825
+ const overrides = new Set();
826
+ for (const node of program.trace.children) {
827
+ if (["applyTheme", "removeTheme"].includes(node.op)) continue;
828
+ clearRecreatedOverrides(overrides, node.op, node.args);
829
+ clearRemovedResources(overrides, node);
830
+ clearLocalResets(overrides, program, node);
831
+ for (const key of explicitOverrides(program, node)) {
832
+ overrides.add(key);
833
+ }
834
+ }
835
+ return overrides;
836
+ }
837
+
838
+ export function reconcileProgramTheme(program, { source, metadata }) {
839
+ const state = program.materializationConfigs.theme;
840
+ if (state === undefined) return program;
841
+ const sourceName = source.materializationConfigs.theme?.name ?? "light";
842
+ const targetTokens = themeTokens(state.name);
843
+ const names = sourceName === "light" ? ["light"] : [sourceName, "light"];
844
+ const overrides = collectOverrides(program);
845
+
846
+ let themed = program._withMaterializationConfig(["theme"], {
847
+ ...state,
848
+ overrides: [...overrides].sort()
849
+ });
850
+
851
+ const configs = recolorConfig(
852
+ themed,
853
+ themed.materializationConfigs,
854
+ names,
855
+ targetTokens,
856
+ overrides
857
+ );
858
+ let next = configs.changed
859
+ ? themed
860
+ ._withMaterializationConfig(["marks"], configs.value.marks)
861
+ ._withMaterializationConfig(["guides"], configs.value.guides)
862
+ : themed;
863
+ if (configs.changed && configs.value.title !== undefined) {
864
+ next = next._withMaterializationConfig(["title"], configs.value.title);
865
+ }
866
+
867
+ const parallelChanged = configs.value.guides.axis?.parallel !==
868
+ themed.guideConfigs.axis?.parallel;
869
+ if (parallelChanged && typeof next.rematerializeParallelAxes === "function") {
870
+ next = next.rematerializeParallelAxes();
871
+ }
872
+
873
+ for (const [id, graphic] of Object.entries(next.graphicSpec.objects)) {
874
+ if (parallelChanged && id.startsWith("parallelAxis")) continue;
875
+ next = recolorGraphic(next, id, graphic, names, targetTokens, overrides);
876
+ }
877
+
878
+ if (state.removing === true && metadata.op === "removeTheme") {
879
+ next = next._withoutMaterializationConfig(["theme"]);
880
+ }
881
+ return next;
882
+ }