ggaction 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +8 -3
  7. package/src/actions/basic.js +2 -2
  8. package/src/actions/boxPlots/components.js +14 -4
  9. package/src/actions/boxPlots/edit.js +31 -5
  10. package/src/actions/boxPlots/materialize.js +11 -3
  11. package/src/actions/boxPlots/options.js +48 -16
  12. package/src/actions/categoryOrder/index.js +15 -4
  13. package/src/actions/charts/area.js +5 -1
  14. package/src/actions/charts/bar.js +2 -1
  15. package/src/actions/charts/density.js +6 -2
  16. package/src/actions/charts/ecdf.js +4 -1
  17. package/src/actions/charts/endpoints.js +25 -3
  18. package/src/actions/charts/heatmap.js +2 -1
  19. package/src/actions/charts/histogram.js +4 -3
  20. package/src/actions/charts/horizon.js +3 -1
  21. package/src/actions/charts/interval-regression.js +5 -2
  22. package/src/actions/charts/line.js +3 -1
  23. package/src/actions/charts/parallel.js +8 -2
  24. package/src/actions/charts/pie.js +10 -2
  25. package/src/actions/charts/polar.js +15 -4
  26. package/src/actions/charts/radar.js +8 -2
  27. package/src/actions/charts/radial.js +10 -2
  28. package/src/actions/charts/rug-strip.js +6 -2
  29. package/src/actions/charts/scatter.js +3 -1
  30. package/src/actions/composition/actions.js +13 -4
  31. package/src/actions/coordinates/actions.js +1 -1
  32. package/src/actions/coordinates/edit.js +76 -0
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/bin2d.js +57 -185
  35. package/src/actions/data/complete.js +26 -0
  36. package/src/actions/data/computed.js +2 -2
  37. package/src/actions/data/create.js +2 -2
  38. package/src/actions/data/density.js +12 -20
  39. package/src/actions/data/derived.js +47 -17
  40. package/src/actions/data/edit.js +539 -0
  41. package/src/actions/data/filter.js +14 -2
  42. package/src/actions/data/impute.js +26 -0
  43. package/src/actions/data/index.js +14 -0
  44. package/src/actions/data/normalize.js +27 -0
  45. package/src/actions/data/regression.js +16 -21
  46. package/src/actions/data/revisionBindings.js +145 -0
  47. package/src/actions/data/shared.js +20 -4
  48. package/src/actions/data/summary.js +1 -1
  49. package/src/actions/data/timeUnit.js +4 -1
  50. package/src/actions/data/window.js +1 -1
  51. package/src/actions/encodings/channels.js +415 -0
  52. package/src/actions/encodings/color/index.js +2 -1
  53. package/src/actions/encodings/density.js +5 -2
  54. package/src/actions/encodings/histogram.js +3 -2
  55. package/src/actions/encodings/index.js +4 -0
  56. package/src/actions/encodings/offset.js +20 -13
  57. package/src/actions/encodings/position/apply.js +53 -7
  58. package/src/actions/encodings/position/index.js +10 -2
  59. package/src/actions/encodings/position/policies/bar.js +9 -0
  60. package/src/actions/encodings/position/resolve.js +24 -7
  61. package/src/actions/encodings/remove.js +9 -4
  62. package/src/actions/encodings/ruleAppearance.js +1 -23
  63. package/src/actions/encodings/seriesLayout.js +1 -0
  64. package/src/actions/encodings/shared.js +82 -14
  65. package/src/actions/encodings/stroke.js +226 -0
  66. package/src/actions/errorBands/create.js +23 -3
  67. package/src/actions/errorBands/edit.js +21 -6
  68. package/src/actions/errorBars/create.js +35 -7
  69. package/src/actions/errorBars/edit.js +19 -4
  70. package/src/actions/facets/actions.js +237 -57
  71. package/src/actions/facets/derive.js +91 -26
  72. package/src/actions/gradientPlots/components.js +10 -2
  73. package/src/actions/gradientPlots/edit.js +14 -4
  74. package/src/actions/gradientPlots/materialize.js +3 -1
  75. package/src/actions/gradientPlots/options.js +10 -2
  76. package/src/actions/guides/applicability.js +8 -2
  77. package/src/actions/guides/axes/labels.js +58 -12
  78. package/src/actions/guides/axes/lines.js +6 -2
  79. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  80. package/src/actions/guides/axes/tickGroups.js +2 -1
  81. package/src/actions/guides/axes/ticks.js +6 -2
  82. package/src/actions/guides/axes/titles.js +11 -3
  83. package/src/actions/guides/grids/resolve.js +7 -2
  84. package/src/actions/guides/guides.js +19 -3
  85. package/src/actions/guides/legends/blocks.js +426 -0
  86. package/src/actions/guides/legends/categorical/actions.js +134 -48
  87. package/src/actions/guides/legends/categorical/components.js +17 -12
  88. package/src/actions/guides/legends/categorical/index.js +37 -33
  89. package/src/actions/guides/legends/categorical/layout.js +97 -15
  90. package/src/actions/guides/legends/categorical/options.js +2 -2
  91. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  92. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  93. package/src/actions/guides/legends/categorical/symbols.js +85 -30
  94. package/src/actions/guides/legends/continuous/common.js +30 -12
  95. package/src/actions/guides/legends/continuous/gradient.js +10 -7
  96. package/src/actions/guides/legends/continuous/index.js +2 -0
  97. package/src/actions/guides/legends/continuous/interval.js +8 -3
  98. package/src/actions/guides/legends/continuous/opacity.js +8 -5
  99. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  100. package/src/actions/guides/legends/creation.js +14 -1
  101. package/src/actions/guides/legends/edit.js +158 -59
  102. package/src/actions/guides/legends/index.js +2 -0
  103. package/src/actions/guides/legends/lane.js +54 -19
  104. package/src/actions/guides/legends/lifecycle.js +19 -8
  105. package/src/actions/guides/legends/remove.js +13 -3
  106. package/src/actions/guides/legends/sampling.js +140 -0
  107. package/src/actions/guides/legends/size.js +93 -40
  108. package/src/actions/guides/legends/strokeWidth.js +34 -28
  109. package/src/actions/guides/legends/target.js +112 -1
  110. package/src/actions/guides/legends/transition.js +165 -16
  111. package/src/actions/guides/polar/axes/facade.js +4 -4
  112. package/src/actions/guides/polar/axes/labels.js +24 -5
  113. package/src/actions/guides/polar/axes/lines.js +10 -4
  114. package/src/actions/guides/polar/axes/shared.js +16 -0
  115. package/src/actions/guides/polar/axes/ticks.js +1 -1
  116. package/src/actions/guides/polar/axes/titles.js +1 -1
  117. package/src/actions/guides/polar/grids.js +1 -1
  118. package/src/actions/guides/polar/resolve.js +12 -8
  119. package/src/actions/index.js +14 -3
  120. package/src/actions/marks/arc/actions.js +77 -19
  121. package/src/actions/marks/area/actions.js +34 -9
  122. package/src/actions/marks/area/materialize.js +17 -1
  123. package/src/actions/marks/bar/create.js +11 -2
  124. package/src/actions/marks/bar/edit.js +20 -3
  125. package/src/actions/marks/bar/materialize.js +22 -2
  126. package/src/actions/marks/index.js +10 -1
  127. package/src/actions/marks/line/actions.js +37 -14
  128. package/src/actions/marks/line/materialize.js +29 -10
  129. package/src/actions/marks/point/create.js +8 -2
  130. package/src/actions/marks/point/edit.js +18 -6
  131. package/src/actions/marks/point/materialize.js +49 -15
  132. package/src/actions/marks/rect/actions.js +33 -12
  133. package/src/actions/marks/references.js +601 -30
  134. package/src/actions/marks/remove.js +71 -1
  135. package/src/actions/marks/rule/actions.js +47 -10
  136. package/src/actions/marks/shared.js +1 -1
  137. package/src/actions/marks/text/actions.js +74 -9
  138. package/src/actions/marks/text/index.js +4 -0
  139. package/src/actions/marks/text/layout.js +154 -6
  140. package/src/actions/marks/text/removal.js +173 -0
  141. package/src/actions/marks/text/selection.js +73 -0
  142. package/src/actions/marks/tick/actions.js +22 -5
  143. package/src/actions/primitives/editGraphics.js +26 -0
  144. package/src/actions/primitives/semantic.js +2 -1
  145. package/src/actions/primitives/semanticAction.js +83 -10
  146. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  147. package/src/actions/primitives/semanticValidation/index.js +9 -1
  148. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  149. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  150. package/src/actions/regression/components.js +26 -11
  151. package/src/actions/regression/create.js +10 -2
  152. package/src/actions/regression/edit.js +21 -7
  153. package/src/actions/resources/index.js +7 -0
  154. package/src/actions/resources/remove.js +172 -0
  155. package/src/actions/scales/channels.js +212 -0
  156. package/src/actions/scales/consumers/common.js +1 -1
  157. package/src/actions/scales/consumers/families.js +3 -2
  158. package/src/actions/scales/consumers/index.js +19 -2
  159. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  160. package/src/actions/scales/create.js +54 -22
  161. package/src/actions/scales/definitions.js +94 -40
  162. package/src/actions/scales/edit.js +66 -11
  163. package/src/actions/scales/editPolicy.js +81 -11
  164. package/src/actions/scales/index.js +6 -0
  165. package/src/actions/scales/materialize.js +4 -3
  166. package/src/actions/scales/offset.js +48 -0
  167. package/src/actions/scales/parallel.js +71 -0
  168. package/src/actions/scales/preview.js +111 -10
  169. package/src/actions/selection/actions.js +154 -30
  170. package/src/actions/selection/index.js +3 -0
  171. package/src/actions/theme/actions.js +68 -13
  172. package/src/actions/theme/composition.js +238 -0
  173. package/src/actions/theme/index.js +9 -2
  174. package/src/actions/theme/reconcile.js +315 -67
  175. package/src/actions/theme/state.js +160 -0
  176. package/src/actions/violinPlots/create.js +5 -2
  177. package/src/actions/violinPlots/edit.js +1 -0
  178. package/src/core/action.js +10 -0
  179. package/src/core/compositionState.js +6 -1
  180. package/src/core/programState.js +20 -0
  181. package/src/core/resourceReferences.js +332 -0
  182. package/src/core/vocabulary.js +16 -3
  183. package/src/grammar/bars/aggregate.js +4 -1
  184. package/src/grammar/bars/geometry.js +44 -0
  185. package/src/grammar/bin.js +54 -9
  186. package/src/grammar/complete.js +279 -0
  187. package/src/grammar/computed.js +265 -64
  188. package/src/grammar/coordinates.js +37 -0
  189. package/src/grammar/density.js +239 -37
  190. package/src/grammar/displayLabels.js +57 -0
  191. package/src/grammar/facets/dependencies.js +133 -1
  192. package/src/grammar/facets/guides.js +3 -0
  193. package/src/grammar/facets/index.js +7 -47
  194. package/src/grammar/facets/scales.js +15 -18
  195. package/src/grammar/impute.js +340 -0
  196. package/src/grammar/markLabelSelection.js +57 -0
  197. package/src/grammar/markLabels.js +18 -1
  198. package/src/grammar/normalize.js +366 -0
  199. package/src/grammar/pathSeries.js +26 -8
  200. package/src/grammar/pointShapes.js +13 -4
  201. package/src/grammar/polar.js +90 -8
  202. package/src/grammar/regression/index.js +1 -0
  203. package/src/grammar/regression/parameters.js +23 -0
  204. package/src/grammar/roundedRect.js +180 -0
  205. package/src/grammar/rules.js +1 -0
  206. package/src/grammar/scales/colorConsumers.js +20 -1
  207. package/src/grammar/scales/continuous.js +2 -2
  208. package/src/grammar/scales/definition.js +27 -4
  209. package/src/grammar/scales/index.js +1 -0
  210. package/src/grammar/scales/ordinal.js +12 -3
  211. package/src/grammar/scales/policies.js +2 -2
  212. package/src/grammar/scales/size.js +456 -0
  213. package/src/grammar/schemas/concreteGraphic.js +14 -2
  214. package/src/grammar/schemas/graphic.js +15 -0
  215. package/src/grammar/schemas/graphicBounds.js +179 -30
  216. package/src/grammar/schemas/semanticPath.js +7 -2
  217. package/src/grammar/statisticalReference.js +103 -0
  218. package/src/grammar/strokeStyle.js +77 -0
  219. package/src/grammar/summary.js +78 -16
  220. package/src/grammar/timeUnit.js +216 -5
  221. package/src/grammar/transformTopology.js +4 -0
  222. package/src/grammar/transforms.js +394 -28
  223. package/src/grammar/weightedStatistics.js +374 -0
  224. package/src/grammar/window.js +221 -16
  225. package/src/layout/aspect.js +80 -0
  226. package/src/layout/facets.js +59 -5
  227. package/src/layout/labels.js +343 -0
  228. package/src/materialization/bars/aggregate.js +25 -2
  229. package/src/materialization/bars/histogram.js +84 -24
  230. package/src/materialization/bars/ranged.js +19 -3
  231. package/src/materialization/bars/resolve.js +7 -4
  232. package/src/materialization/composition.js +3 -1
  233. package/src/materialization/coordinateBounds.js +204 -0
  234. package/src/materialization/dependencies.js +36 -0
  235. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  236. package/src/materialization/facetGuides/placement.js +8 -2
  237. package/src/materialization/facetGuides/preparation.js +12 -7
  238. package/src/materialization/facetHeaders.js +346 -0
  239. package/src/materialization/facets.js +15 -65
  240. package/src/materialization/guides/resources.js +26 -0
  241. package/src/materialization/legends.js +1 -1
  242. package/src/materialization/marks/index.js +11 -5
  243. package/src/materialization/marks/policies.js +2 -1
  244. package/src/materialization/rect.js +25 -12
  245. package/src/materialization/rectConfig.js +5 -1
  246. package/src/materialization/scales/map.js +5 -3
  247. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  248. package/src/materialization/scales/policies/offset.js +50 -8
  249. package/src/materialization/scales/policies/series.js +4 -1
  250. package/src/materialization/scales/resolve.js +22 -6
  251. package/src/materialization/selection/items/arc.js +28 -4
  252. package/src/materialization/selection/items/bar.js +30 -3
  253. package/src/materialization/selection/items/common.js +19 -4
  254. package/src/materialization/selection/items/rect.js +28 -4
  255. package/src/materialization/selection/path.js +1 -1
  256. package/src/materialization/selection/point.js +3 -1
  257. package/src/materialization/text.js +265 -14
  258. package/src/renderers/canvas/circle.js +2 -3
  259. package/src/renderers/canvas/line.js +2 -3
  260. package/src/renderers/canvas/native.js +4 -4
  261. package/src/renderers/canvas/path.js +2 -3
  262. package/src/renderers/canvas/rect.js +2 -3
  263. package/src/renderers/canvas/stroke.js +11 -0
  264. package/src/renderers/svg.js +19 -0
  265. package/src/selectors/datasets.js +32 -1
  266. package/src/selectors/layers.js +4 -0
  267. package/src/theme/defaults.js +101 -0
  268. package/types/basic.d.ts +6 -0
  269. package/types/index.d.ts +85 -0
  270. package/types/program.d.ts +910 -117
@@ -0,0 +1,160 @@
1
+ import {
2
+ cloneAndFreeze,
3
+ freezeOwned,
4
+ isPlainObject
5
+ } from "../../core/immutable.js";
6
+ import {
7
+ normalizeThemeDefinition,
8
+ resolveThemeTokens
9
+ } from "../../theme/defaults.js";
10
+
11
+ export const LOCAL_THEME_OWNER = "local";
12
+
13
+ function ownArray(value) {
14
+ return freezeOwned([...(value ?? [])]);
15
+ }
16
+
17
+ function requireThemeScope(value) {
18
+ if (!["self", "descendants"].includes(value)) {
19
+ throw new Error(`Unsupported theme scope "${value}".`);
20
+ }
21
+ return value;
22
+ }
23
+
24
+ function requireThemeOwner(value) {
25
+ if (typeof value !== "string" || value.length === 0) {
26
+ throw new TypeError("Theme frame owner must be a non-empty string.");
27
+ }
28
+ return value;
29
+ }
30
+
31
+ export function compositionThemeOwner(id, scope = "descendants") {
32
+ if (typeof id !== "string" || id.length === 0) {
33
+ throw new TypeError("Composition theme owner requires a non-empty id.");
34
+ }
35
+ requireThemeScope(scope);
36
+ return scope === "self" ? `composition:${id}:self` : `composition:${id}`;
37
+ }
38
+
39
+ export function createThemeFrame({
40
+ owner = LOCAL_THEME_OWNER,
41
+ scope = "self",
42
+ definition
43
+ }) {
44
+ const normalized = normalizeThemeDefinition(definition);
45
+ return freezeOwned({
46
+ owner: requireThemeOwner(owner),
47
+ scope: requireThemeScope(scope),
48
+ name: normalized.name,
49
+ tokens: normalized.tokens
50
+ });
51
+ }
52
+
53
+ export function upsertThemeFrame(frames = [], frame) {
54
+ if (!Array.isArray(frames)) {
55
+ throw new TypeError("Theme frames must be an array.");
56
+ }
57
+ if (!isPlainObject(frame)) {
58
+ throw new TypeError("Theme frame must be a plain object.");
59
+ }
60
+ return freezeOwned([
61
+ ...frames.filter(current => current.owner !== frame.owner),
62
+ frame
63
+ ]);
64
+ }
65
+
66
+ export function removeThemeFrameOwner(frames = [], owner) {
67
+ if (!Array.isArray(frames)) {
68
+ throw new TypeError("Theme frames must be an array.");
69
+ }
70
+ requireThemeOwner(owner);
71
+ const next = frames.filter(frame => frame.owner !== owner);
72
+ return freezeOwned({
73
+ frames: freezeOwned(next),
74
+ removed: next.length !== frames.length
75
+ });
76
+ }
77
+
78
+ export function moveLocalThemeScope(localOrder = [], scope) {
79
+ if (!Array.isArray(localOrder)) {
80
+ throw new TypeError("Theme localOrder must be an array.");
81
+ }
82
+ requireThemeScope(scope);
83
+ return freezeOwned([
84
+ ...localOrder.filter(current => current !== scope),
85
+ scope
86
+ ]);
87
+ }
88
+
89
+ function ownCanonicalThemeState(state, composition) {
90
+ const frames = ownArray(state.frames);
91
+ const localOrder = ownArray(state.localOrder);
92
+ const overrides = ownArray(state.overrides);
93
+ const result = { frames, localOrder, overrides };
94
+ if (composition || state.descendantFrames !== undefined) {
95
+ result.descendantFrames = ownArray(state.descendantFrames);
96
+ }
97
+ return cloneAndFreeze(result);
98
+ }
99
+
100
+ export function normalizeThemeState(state, { composition = false } = {}) {
101
+ if (state === undefined) {
102
+ return freezeOwned({
103
+ frames: freezeOwned([]),
104
+ ...(composition ? { descendantFrames: freezeOwned([]) } : {}),
105
+ localOrder: freezeOwned([]),
106
+ overrides: freezeOwned([])
107
+ });
108
+ }
109
+ if (!isPlainObject(state)) {
110
+ throw new TypeError("Program theme state must be a plain object.");
111
+ }
112
+ if (Array.isArray(state.frames)) {
113
+ return ownCanonicalThemeState(state, composition);
114
+ }
115
+
116
+ const name = state.name ?? "light";
117
+ const tokens = state.tokens ?? {};
118
+ const scope = state.scope ?? "self";
119
+ const origin = state.origin?.ownerCompositionId;
120
+ const owner = origin === undefined
121
+ ? LOCAL_THEME_OWNER
122
+ : compositionThemeOwner(origin, "descendants");
123
+ const frame = createThemeFrame({
124
+ owner,
125
+ scope,
126
+ definition: { base: name, tokens }
127
+ });
128
+ return freezeOwned({
129
+ frames: freezeOwned([frame]),
130
+ ...(composition && scope === "descendants"
131
+ ? { descendantFrames: freezeOwned([frame]) }
132
+ : composition
133
+ ? { descendantFrames: freezeOwned([]) }
134
+ : {}),
135
+ localOrder: origin === undefined ? freezeOwned([scope]) : freezeOwned([]),
136
+ overrides: cloneAndFreeze(state.overrides ?? [])
137
+ });
138
+ }
139
+
140
+ export function resolveEffectiveThemeFrame(state) {
141
+ const frames = state?.frames;
142
+ return Array.isArray(frames) ? frames.at(-1) : undefined;
143
+ }
144
+
145
+ export function resolveEffectiveThemeTokens(state) {
146
+ const frame = resolveEffectiveThemeFrame(state);
147
+ return frame === undefined
148
+ ? resolveThemeTokens("light")
149
+ : resolveThemeTokens({ base: frame.name, tokens: frame.tokens });
150
+ }
151
+
152
+ export function setThemeStateOverrides(state, overrides) {
153
+ if (!Array.isArray(overrides)) {
154
+ throw new TypeError("Theme overrides must be an array.");
155
+ }
156
+ return freezeOwned({
157
+ ...state,
158
+ overrides: cloneAndFreeze([...overrides].sort())
159
+ });
160
+ }
@@ -2,6 +2,7 @@ import { action } from "../../core/action.js";
2
2
  import { validateOptionObject } from "../../core/validation.js";
3
3
  import { findDataset } from "../../selectors/datasets.js";
4
4
  import { findLayer } from "../../selectors/layers.js";
5
+ import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
5
6
  import {
6
7
  applyFacadeGuides,
7
8
  normalizeAppearance,
@@ -20,12 +21,13 @@ const OPTIONS = Object.freeze([
20
21
  ]);
21
22
  const POSITION_OPTIONS = Object.freeze(["field", "fieldType", "scale"]);
22
23
  const DENSITY_OPTIONS = Object.freeze([
23
- "bandwidth", "extent", "steps", "kernel", "normalization", "width", "side"
24
+ "bandwidth", "extent", "steps", "kernel", "normalization", "weight", "width", "side"
24
25
  ]);
25
26
  const WIDTH_OPTIONS = Object.freeze(["band", "resolve"]);
26
27
  const SPLIT_OPTIONS = Object.freeze(["field", "domain"]);
27
28
  const AREA_OPTIONS = Object.freeze([
28
- "fill", "opacity", "stroke", "strokeWidth", "curve"
29
+ "fill", "opacity", "stroke", "strokeWidth", "curve",
30
+ ...STROKE_STYLE_PROPERTIES
29
31
  ]);
30
32
  const CATEGORICAL_TYPES = Object.freeze(["nominal", "ordinal"]);
31
33
  const OPERATION = "createViolinPlot";
@@ -221,6 +223,7 @@ export const createViolinPlot = action(
221
223
  steps: transform.steps,
222
224
  kernel: transform.kernel,
223
225
  normalization: transform.normalization,
226
+ ...(transform.weight === undefined ? {} : { weight: transform.weight }),
224
227
  width: transform.placement.width,
225
228
  side: transform.placement.side
226
229
  },
@@ -181,6 +181,7 @@ export const editViolinPlot = action(
181
181
  steps: transform.steps,
182
182
  kernel: transform.kernel,
183
183
  normalization: transform.normalization,
184
+ ...(transform.weight === undefined ? {} : { weight: transform.weight }),
184
185
  width: transform.placement.width,
185
186
  side: transform.placement.side
186
187
  }
@@ -1,6 +1,7 @@
1
1
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "./immutable.js";
2
2
 
3
3
  const metadataByWrappedAction = new WeakMap();
4
+ const implementationByWrappedAction = new WeakMap();
4
5
  let actionCompletionHook;
5
6
 
6
7
  export function setActionCompletionHook(hook) {
@@ -14,6 +15,14 @@ export function getWrappedActionMetadata(value) {
14
15
  return metadataByWrappedAction.get(value);
15
16
  }
16
17
 
18
+ export function invokeWrappedActionImplementation(value, program, args = {}) {
19
+ const implementation = implementationByWrappedAction.get(value);
20
+ if (implementation === undefined) {
21
+ throw new TypeError("Expected an action created by action().");
22
+ }
23
+ return implementation.call(program, args);
24
+ }
25
+
17
26
  function summarizeObject(value, ancestors = new WeakSet()) {
18
27
  if (ancestors.has(value)) {
19
28
  throw new TypeError("Action arguments must not contain circular references.");
@@ -161,5 +170,6 @@ export function action(metadata, implementation) {
161
170
  };
162
171
 
163
172
  metadataByWrappedAction.set(wrappedAction, ownedMetadata);
173
+ implementationByWrappedAction.set(wrappedAction, implementation);
164
174
  return wrappedAction;
165
175
  }
@@ -150,8 +150,13 @@ function validateFacetSpec(compositionSpec) {
150
150
  "compositionSpec.facet.repeat requires target, channel, and fields."
151
151
  );
152
152
  }
153
+ const repeatChannel = ["x", "y", "theta", "r"].includes(repeat.channel) ||
154
+ (isPlainObject(repeat.channel) &&
155
+ Object.keys(repeat.channel).length === 1 &&
156
+ typeof repeat.channel.parallelDimension === "string" &&
157
+ repeat.channel.parallelDimension.length > 0);
153
158
  if (typeof repeat.target !== "string" || repeat.target.length === 0 ||
154
- !["x", "y"].includes(repeat.channel) ||
159
+ !repeatChannel ||
155
160
  !Array.isArray(repeat.fields) || repeat.fields.length === 0 ||
156
161
  repeat.fields.some(field => typeof field !== "string" || field.length === 0) ||
157
162
  new Set(repeat.fields).size !== repeat.fields.length) {
@@ -4,6 +4,7 @@ import {
4
4
  isOwned,
5
5
  isPlainObject
6
6
  } from "./immutable.js";
7
+ import { removeMaterializationConfig } from "./materializationState.js";
7
8
 
8
9
  export function ownProgramState(value) {
9
10
  return isOwned(value) ? value : cloneAndFreeze(value);
@@ -26,3 +27,22 @@ export function ownChildPrograms(children, ProgramClass) {
26
27
  }
27
28
  return freezeOwned(owned);
28
29
  }
30
+
31
+ // Semantic entries, resolved scale caches, and physical current-resource IDs
32
+ // are removed by editSemantic. A logical data owner also retains its own ID
33
+ // and replay configuration; release those non-semantic references after the
34
+ // domain's dependency validation and before the primitive deletion.
35
+ export function releaseNamedResourceOwnership(program, { kind, id, dataOwner }) {
36
+ if (kind !== "data") return program;
37
+ const materializationConfigs = dataOwner === undefined
38
+ ? program.materializationConfigs
39
+ : removeMaterializationConfig(
40
+ program.materializationConfigs,
41
+ ["data", dataOwner.family, dataOwner.owner]
42
+ ).value;
43
+ const context = program.context.currentData === id
44
+ ? freezeOwned({ ...program.context, currentData: undefined })
45
+ : program.context;
46
+ if (context === program.context && materializationConfigs === program.materializationConfigs) return program;
47
+ return program._clone({ context, materializationConfigs });
48
+ }
@@ -0,0 +1,332 @@
1
+ import { freezeOwned, isPlainObject } from "./immutable.js";
2
+ import { validateUserId } from "./identifiers.js";
3
+
4
+ export const RESOURCE_KINDS = Object.freeze([
5
+ "data", "scale", "coordinate", "mark", "selection"
6
+ ]);
7
+
8
+ const DATA_VALUE_KEYS = new Set([
9
+ "data", "dataset", "dataId", "filteredData", "originalData",
10
+ "outlierDataId", "profileId", "summaryId"
11
+ ]);
12
+ const DATA_SOURCE_CONFIGS = new Set([
13
+ "boxPlot", "density", "ecdfPlot", "endpointPlot", "errorBand", "errorBar",
14
+ "gradientPlot", "horizon", "intervalPlot", "markFilter", "raincloudPlot",
15
+ "regression", "violinPlot"
16
+ ]);
17
+ const MARK_ID_KEYS = new Set([
18
+ "bandId", "bodyId", "centerId", "connectorId", "intervalId", "lineId",
19
+ "lowerBoundaryId", "lowerCapId", "medianId", "outlierId", "startId",
20
+ "stemId", "upperBoundaryId", "upperCapId", "whiskerId"
21
+ ]);
22
+
23
+ function ownedPath(path) {
24
+ return freezeOwned([...path]);
25
+ }
26
+
27
+ function reference(kind, id, ownerKind, ownerId, path, strength = "live") {
28
+ if (typeof id !== "string" || id.length === 0) return undefined;
29
+ return freezeOwned({
30
+ kind,
31
+ id,
32
+ ownerKind,
33
+ ownerId,
34
+ path: ownedPath(path),
35
+ strength
36
+ });
37
+ }
38
+
39
+ function append(references, value) {
40
+ if (value !== undefined) references.push(value);
41
+ }
42
+
43
+ function collectKnownKeys(value, path, visit) {
44
+ if (Array.isArray(value)) {
45
+ value.forEach((item, index) => collectKnownKeys(item, [...path, index], visit));
46
+ return;
47
+ }
48
+ if (!isPlainObject(value)) return;
49
+ for (const [key, child] of Object.entries(value)) {
50
+ visit(key, child, [...path, key], path);
51
+ collectKnownKeys(child, [...path, key], visit);
52
+ }
53
+ }
54
+
55
+ export function canonicalResourcePath(path) {
56
+ if (!Array.isArray(path)) {
57
+ throw new TypeError("Resource reference path must be an array.");
58
+ }
59
+ return path.map(part => {
60
+ if (Number.isInteger(part) && part >= 0) return `[${part}]`;
61
+ if (typeof part !== "string" || part.length === 0) {
62
+ throw new TypeError("Resource reference path parts must be names or indexes.");
63
+ }
64
+ return `.${part}`;
65
+ }).join("");
66
+ }
67
+
68
+ export function collectSemanticDataReferences(program) {
69
+ const references = [];
70
+ for (const layer of program.semanticSpec.layers) {
71
+ append(references, reference("data", layer.data, "layer", layer.id, ["data"]));
72
+ }
73
+ for (const dataset of program.semanticSpec.datasets) {
74
+ append(references, reference(
75
+ "data", dataset.source, "dataset", dataset.id, ["source"]
76
+ ));
77
+ }
78
+ return freezeOwned(references);
79
+ }
80
+
81
+ export function collectSemanticScaleReferences(program) {
82
+ const references = [];
83
+ for (const layer of program.semanticSpec.layers) {
84
+ for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
85
+ append(references, reference(
86
+ "scale", encoding?.scale, "layer", layer.id,
87
+ ["encoding", channel, "scale"]
88
+ ));
89
+ if (channel === "parallel") {
90
+ for (const [index, dimension] of (encoding?.dimensions ?? []).entries()) {
91
+ append(references, reference(
92
+ "scale", dimension.scale, "layer", layer.id,
93
+ ["encoding", "parallel", "dimensions", index, "scale"]
94
+ ));
95
+ }
96
+ }
97
+ }
98
+ }
99
+ collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
100
+ if (key === "scale") {
101
+ append(references, reference(
102
+ "scale", value, "semanticGuide", "guides", path
103
+ ));
104
+ } else if (key === "scales" && Array.isArray(value)) {
105
+ value.forEach((id, index) => append(references, reference(
106
+ "scale", id, "semanticGuide", "guides", [...path, index]
107
+ )));
108
+ }
109
+ });
110
+ return freezeOwned(references);
111
+ }
112
+
113
+ export function collectSemanticCoordinateReferences(program) {
114
+ const references = [];
115
+ for (const layer of program.semanticSpec.layers) {
116
+ append(references, reference(
117
+ "coordinate", layer.coordinate, "layer", layer.id, ["coordinate"]
118
+ ));
119
+ }
120
+ collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
121
+ if (key === "coordinate") {
122
+ append(references, reference(
123
+ "coordinate", value, "semanticGuide", "guides", path
124
+ ));
125
+ }
126
+ });
127
+ return freezeOwned(references);
128
+ }
129
+
130
+ export function collectMarkConfigReferences(program) {
131
+ const references = [];
132
+ for (const layer of program.semanticSpec.layers) {
133
+ append(references, reference(
134
+ "mark", layer.source, "layer", layer.id, ["source"]
135
+ ));
136
+ }
137
+ for (const [ownerId, config] of Object.entries(program.markConfigs)) {
138
+ collectKnownKeys(config, [], (key, value, path, parentPath) => {
139
+ if (DATA_VALUE_KEYS.has(key)) {
140
+ append(references, reference("data", value, "markConfig", ownerId, path));
141
+ }
142
+ const root = parentPath[0];
143
+ if (key === "source" && DATA_SOURCE_CONFIGS.has(root)) {
144
+ append(references, reference("data", value, "markConfig", ownerId, path));
145
+ }
146
+ if (key === "coordinate") {
147
+ append(references, reference(
148
+ "coordinate", value, "markConfig", ownerId, path
149
+ ));
150
+ }
151
+ if ((key === "scale" || key.endsWith("Scale")) && typeof value === "string") {
152
+ append(references, reference("scale", value, "markConfig", ownerId, path));
153
+ } else if (key === "scale" && isPlainObject(value)) {
154
+ append(references, reference(
155
+ "scale", value.id, "markConfig", ownerId, [...path, "id"]
156
+ ));
157
+ }
158
+ if (MARK_ID_KEYS.has(key)) {
159
+ append(references, reference("mark", value, "markConfig", ownerId, path));
160
+ }
161
+ if (
162
+ (root === "errorBandBoundary" && key === "owner") ||
163
+ (parentPath.length === 0 && key === "boxSpanOwner") ||
164
+ (root === "raincloudPlot" && (
165
+ key === "ownerId" || parentPath[1] === "childIds" ||
166
+ parentPath[1] === "ownedChildIds"
167
+ ))
168
+ ) {
169
+ append(references, reference("mark", value, "markConfig", ownerId, path));
170
+ }
171
+ });
172
+ append(references, reference(
173
+ "mark", config.statisticalReference?.source,
174
+ "markConfig", ownerId, ["statisticalReference", "source"]
175
+ ));
176
+ for (const [index, id] of (
177
+ config.raincloudPlot?.ownedChildIds ?? []
178
+ ).entries()) {
179
+ append(references, reference(
180
+ "mark", id, "markConfig", ownerId,
181
+ ["raincloudPlot", "ownedChildIds", index]
182
+ ));
183
+ }
184
+ const selection = config.labelAuthoring?.selection;
185
+ if (selection?.kind === "named") {
186
+ append(references, reference(
187
+ "selection", selection.id, "markConfig", ownerId,
188
+ ["labelAuthoring", "selection", "id"]
189
+ ));
190
+ }
191
+ }
192
+ return freezeOwned(references);
193
+ }
194
+
195
+ export function collectGuideConfigReferences(program) {
196
+ const references = [];
197
+ for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
198
+ collectKnownKeys(configs, [], (key, value, path, parentPath) => {
199
+ const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
200
+ const relativePath = parentPath.length === 0
201
+ ? path
202
+ : path.slice(parentPath.length);
203
+ if (key === "scale") {
204
+ append(references, reference(
205
+ "scale", value, family, ownerId, relativePath
206
+ ));
207
+ } else if (key === "scales" && Array.isArray(value)) {
208
+ value.forEach((id, index) => append(references, reference(
209
+ "scale", id, family, ownerId, [...relativePath, index]
210
+ )));
211
+ } else if (key === "coordinate") {
212
+ append(references, reference(
213
+ "coordinate", value, family, ownerId, relativePath
214
+ ));
215
+ } else if (key === "target") {
216
+ append(references, reference(
217
+ "mark", value, family, ownerId, relativePath
218
+ ));
219
+ }
220
+ });
221
+ }
222
+ return freezeOwned(references);
223
+ }
224
+
225
+ export function collectSelectionReferences(program) {
226
+ const references = [];
227
+ for (const [id, config] of Object.entries(
228
+ program.materializationConfigs.selections ?? {}
229
+ )) {
230
+ append(references, reference(
231
+ "mark", config.target, "selection", id, ["target"]
232
+ ));
233
+ }
234
+ for (const [id, config] of Object.entries(
235
+ program.materializationConfigs.highlights ?? {}
236
+ )) {
237
+ append(references, reference(
238
+ "mark", config.target, "highlight", id, ["target"]
239
+ ));
240
+ append(references, reference(
241
+ "selection", config.selection, "highlight", id, ["selection"]
242
+ ));
243
+ }
244
+ return freezeOwned(references);
245
+ }
246
+
247
+ export function collectDataOwnerReferences(program) {
248
+ const references = [];
249
+ for (const [family, owners] of Object.entries(
250
+ program.materializationConfigs.data ?? {}
251
+ )) {
252
+ for (const [owner, config] of Object.entries(owners ?? {})) {
253
+ for (const key of ["current", "source", "previous"]) {
254
+ append(references, reference(
255
+ "data", config?.[key], "dataOwner", owner,
256
+ ["data", family, owner, key]
257
+ ));
258
+ }
259
+ }
260
+ }
261
+ return freezeOwned(references);
262
+ }
263
+
264
+ export function collectCompositionReferences(program) {
265
+ const references = [];
266
+ const composition = program.compositionSpec;
267
+ if (composition?.type !== "facet") return freezeOwned(references);
268
+ append(references, reference(
269
+ "data", composition.facet.data, "composition", composition.id,
270
+ ["facet", "data"]
271
+ ));
272
+ append(references, reference(
273
+ "mark", composition.facet.repeat?.target, "composition", composition.id,
274
+ ["facet", "repeat", "target"]
275
+ ));
276
+ return freezeOwned(references);
277
+ }
278
+
279
+ export function collectContextReferences(program) {
280
+ const references = [];
281
+ for (const [key, kind] of Object.entries({
282
+ currentData: "data",
283
+ currentScale: "scale",
284
+ currentCoordinate: "coordinate",
285
+ currentMark: "mark",
286
+ currentSelection: "selection"
287
+ })) {
288
+ append(references, reference(
289
+ kind, program.context[key], "context", "program", [key], "context"
290
+ ));
291
+ }
292
+ return freezeOwned(references);
293
+ }
294
+
295
+ function referenceKey(value) {
296
+ return [
297
+ value.kind, value.id, value.ownerKind, value.ownerId,
298
+ canonicalResourcePath(value.path), value.strength
299
+ ].join("\u0000");
300
+ }
301
+
302
+ function compareReferences(left, right) {
303
+ const leftKey = [
304
+ left.ownerKind, left.ownerId, canonicalResourcePath(left.path),
305
+ left.kind, left.id, left.strength
306
+ ].join("\u0000");
307
+ const rightKey = [
308
+ right.ownerKind, right.ownerId, canonicalResourcePath(right.path),
309
+ right.kind, right.id, right.strength
310
+ ].join("\u0000");
311
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
312
+ }
313
+
314
+ export function collectResourceReferences(program, { kind, id }) {
315
+ if (!RESOURCE_KINDS.includes(kind)) {
316
+ throw new Error(`Unknown resource kind "${kind}".`);
317
+ }
318
+ const target = validateUserId(id, "Resource id");
319
+ const combined = [
320
+ ...collectSemanticDataReferences(program),
321
+ ...collectSemanticScaleReferences(program),
322
+ ...collectSemanticCoordinateReferences(program),
323
+ ...collectMarkConfigReferences(program),
324
+ ...collectGuideConfigReferences(program),
325
+ ...collectSelectionReferences(program),
326
+ ...collectDataOwnerReferences(program),
327
+ ...collectCompositionReferences(program),
328
+ ...collectContextReferences(program)
329
+ ].filter(value => value.kind === kind && value.id === target);
330
+ const unique = new Map(combined.map(value => [referenceKey(value), value]));
331
+ return freezeOwned([...unique.values()].sort(compareReferences));
332
+ }
@@ -13,13 +13,13 @@ export const MARK_TYPES = Object.freeze([
13
13
  export const MARK_GRAPHIC_TYPES = Object.freeze({
14
14
  point: Object.freeze(["circle", "rect", "path", "collection"]),
15
15
  line: Object.freeze(["path"]),
16
- bar: Object.freeze(["rect"]),
16
+ bar: Object.freeze(["rect", "collection"]),
17
17
  area: Object.freeze(["path"]),
18
18
  arc: Object.freeze(["path"]),
19
19
  rule: Object.freeze(["line"]),
20
20
  tick: Object.freeze(["line"]),
21
21
  text: Object.freeze(["text"]),
22
- rect: Object.freeze(["rect"])
22
+ rect: Object.freeze(["rect", "collection"])
23
23
  });
24
24
 
25
25
  export const ENCODING_CHANNELS = Object.freeze([
@@ -32,6 +32,7 @@ export const ENCODING_CHANNELS = Object.freeze([
32
32
  "theta",
33
33
  "radius",
34
34
  "color",
35
+ "stroke",
35
36
  "strokeDash",
36
37
  "strokeWidth",
37
38
  "size",
@@ -155,13 +156,21 @@ export const FACET_SCALE_CHANNELS = Object.freeze([
155
156
  "y",
156
157
  "xOffset",
157
158
  "yOffset",
159
+ "theta",
160
+ "r",
158
161
  "color",
162
+ "stroke",
159
163
  "size",
160
164
  "shape",
161
165
  "opacity",
162
- "strokeDash"
166
+ "strokeDash",
167
+ "parallelDimensions"
163
168
  ]);
164
169
 
170
+ export function facetSemanticChannel(channel) {
171
+ return channel === "r" ? "radius" : channel;
172
+ }
173
+
165
174
  export const FACET_SCALE_RESOLUTIONS = Object.freeze([
166
175
  "shared",
167
176
  "independent"
@@ -198,6 +207,7 @@ export const STACK_MODES = Object.freeze(["zero", "normalize", "center"]);
198
207
 
199
208
  export const CATEGORICAL_LEGEND_CHANNELS = Object.freeze([
200
209
  "color",
210
+ "stroke",
201
211
  "strokeDash",
202
212
  "shape"
203
213
  ]);
@@ -212,9 +222,12 @@ export const LEGEND_CHANNELS = Object.freeze([
212
222
  export const LEGEND_CONFIG_KINDS = Object.freeze([
213
223
  "series",
214
224
  "color",
225
+ "stroke",
215
226
  "size",
216
227
  "gradient",
217
228
  "interval",
229
+ "strokeGradient",
230
+ "strokeInterval",
218
231
  "opacity",
219
232
  "strokeWidth"
220
233
  ]);