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,76 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { normalizeCoordinateAspect } from "../../grammar/coordinates.js";
5
+ import { normalizePolarFrameOptions } from "../../grammar/polar.js";
6
+ import {
7
+ applyMaterializationPlan,
8
+ planCoordinateRematerialization
9
+ } from "../../materialization/dependencies.js";
10
+ import { requireCoordinate } from "../../selectors/coordinates.js";
11
+
12
+ const EDIT_COORDINATE_OPTIONS = Object.freeze(["target", "aspect", "polarFrame"]);
13
+
14
+ function applyCoordinatePatch(program, target, property, value) {
15
+ const coordinate = requireCoordinate(program, target);
16
+ if (value === "auto") {
17
+ return coordinate[property] === undefined
18
+ ? program
19
+ : program.editSemantic({
20
+ property: `coordinate[${target}].${property}`,
21
+ remove: true
22
+ });
23
+ }
24
+ return program.editSemantic({
25
+ property: `coordinate[${target}].${property}`,
26
+ value
27
+ });
28
+ }
29
+
30
+ export const editCoordinate = action(
31
+ {
32
+ op: "editCoordinate",
33
+ description: "Edit coordinate layout constraints and rematerialize its consumers."
34
+ },
35
+ function (args = {}) {
36
+ validateOptionObject(args, EDIT_COORDINATE_OPTIONS, "editCoordinate", {
37
+ allowEmpty: false,
38
+ emptyMessage: "editCoordinate requires aspect or polarFrame changes.",
39
+ emptyError: Error
40
+ });
41
+ const target = validateUserId(args.target, "Coordinate id");
42
+ const coordinate = requireCoordinate(this, target);
43
+ const hasAspect = Object.hasOwn(args, "aspect");
44
+ const hasPolarFrame = Object.hasOwn(args, "polarFrame");
45
+ if (!hasAspect && !hasPolarFrame) {
46
+ throw new Error("editCoordinate requires aspect or polarFrame changes.");
47
+ }
48
+ const aspect = hasAspect ? normalizeCoordinateAspect(args.aspect) : undefined;
49
+ if (hasPolarFrame && coordinate.type !== "polar") {
50
+ throw new Error("editCoordinate polarFrame requires a Polar coordinate.");
51
+ }
52
+ const polarFrame = hasPolarFrame
53
+ ? normalizePolarFrameOptions(args.polarFrame)
54
+ : undefined;
55
+ let proposed = this;
56
+ if (hasAspect) {
57
+ proposed = applyCoordinatePatch(proposed, target, "aspect", aspect);
58
+ }
59
+ if (hasPolarFrame) {
60
+ proposed = applyCoordinatePatch(
61
+ proposed,
62
+ target,
63
+ "polarFrame",
64
+ polarFrame
65
+ );
66
+ }
67
+ const plan = planCoordinateRematerialization(proposed, target);
68
+ // Validate the complete dependent geometry on a discarded immutable branch.
69
+ applyMaterializationPlan(proposed, plan);
70
+ return applyMaterializationPlan(proposed, plan);
71
+ }
72
+ );
73
+
74
+ export function registerCoordinateEditActions(ProgramClass) {
75
+ ProgramClass.prototype.editCoordinate = editCoordinate;
76
+ }
@@ -7,7 +7,7 @@ import { derivedCreator, derivedMaterializer } from "./shared.js";
7
7
 
8
8
  const OPTIONS = Object.freeze([
9
9
  "id", "source", "field", "maxBins", "step", "boundaries", "extent",
10
- "nice", "zero", "includeEmpty", "members", "as"
10
+ "nice", "zero", "includeEmpty", "members", "as", "weight"
11
11
  ]);
12
12
 
13
13
  export const materializeBinData = derivedMaterializer(
@@ -6,24 +6,28 @@ import {
6
6
  normalizeBin2DTransform,
7
7
  requestedBin2DTransform
8
8
  } from "../../grammar/bin2d.js";
9
- import { applyLayerDataRematerialization } from
10
- "../../materialization/dependencies.js";
11
- import { planDerivedDataRevision } from
12
- "../../materialization/dataProvenance.js";
13
9
  import {
14
10
  findDataset,
15
- findDatasetConsumer
11
+ resolveDatasetReference
16
12
  } from "../../selectors/datasets.js";
17
- import { requireLayer } from "../../selectors/layers.js";
13
+ import {
14
+ normalizeDerivedDependentPolicy,
15
+ resolveDerivedDataOwner,
16
+ reviseDerivedData
17
+ } from "./edit.js";
18
+ import { normalizeDatasetTransformEdit } from "../../grammar/transforms.js";
18
19
  export { materializeBin2DData } from "./bin2dMaterialize.js";
19
20
 
20
21
  const OPTIONS = Object.freeze([
21
22
  "id", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
22
23
  ]);
23
24
  const EDIT_OPTIONS = Object.freeze([
24
- "target", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
25
+ "target", "source", "x", "y", "bins", "extent", "includeEmpty",
26
+ "members", "as", "dependents"
25
27
  ]);
26
- const EDITABLE = Object.freeze(EDIT_OPTIONS.slice(1));
28
+ const EDITABLE = Object.freeze(EDIT_OPTIONS.filter(option =>
29
+ !["target", "dependents"].includes(option)
30
+ ));
27
31
  const OUTPUT_FIELDS = Object.freeze([
28
32
  "x0", "x1", "y0", "y1", "count", "members"
29
33
  ]);
@@ -69,122 +73,6 @@ function requireCurrentBin2D(program, id, config) {
69
73
  return current;
70
74
  }
71
75
 
72
- function directLayerConsumers(program, data) {
73
- return program.semanticSpec.layers
74
- .filter(layer => layer.data === data)
75
- .map(layer => layer.id);
76
- }
77
-
78
- function outputFieldChanges(previous, next) {
79
- return new Map(OUTPUT_FIELDS.flatMap(role => {
80
- const from = previous.as[role];
81
- const to = next.as[role];
82
- return from !== undefined && from !== to
83
- ? [[from, [role, to]]]
84
- : [];
85
- }));
86
- }
87
-
88
- function changedConsumerField(field, changes) {
89
- const change = changes.get(field);
90
- if (change === undefined) return field;
91
- const [role, next] = change;
92
- if (next === undefined) {
93
- throw new Error(
94
- `Cannot remove referenced 2D bin ${role} output field "${field}".`
95
- );
96
- }
97
- return next;
98
- }
99
-
100
- function rebindLayerOutputFields(program, id, changes) {
101
- const layer = requireLayer(program, id);
102
- let next = program;
103
- const changed = field => changedConsumerField(field, changes);
104
- const editField = (property, field) => {
105
- if (field === undefined) return;
106
- const value = changed(field);
107
- if (value !== field) next = next.editSemantic({ property, value });
108
- };
109
- for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
110
- editField(
111
- `layer[${id}].encoding.${channel}.field`,
112
- encoding?.field
113
- );
114
- const summary = encoding?.categoryOrder?.by;
115
- if (summary?.field !== undefined) {
116
- const field = changed(summary.field);
117
- if (field !== summary.field) {
118
- next = next.editSemantic({
119
- property: `layer[${id}].encoding.${channel}.categoryOrder`,
120
- value: { ...encoding.categoryOrder, by: { ...summary, field } }
121
- });
122
- }
123
- }
124
- }
125
- editField(
126
- `layer[${id}].encoding.theta.weight`,
127
- layer.encoding?.theta?.weight
128
- );
129
-
130
- const parallel = layer.encoding?.parallel;
131
- if (parallel?.dimensions !== undefined) {
132
- const dimensions = parallel.dimensions.map(dimension => {
133
- const field = changed(dimension.field);
134
- return { ...dimension, field };
135
- });
136
- if (dimensions.some((dimension, index) =>
137
- dimension.field !== parallel.dimensions[index].field
138
- )) {
139
- next = next.editSemantic({
140
- property: `layer[${id}].encoding.parallel.dimensions`,
141
- value: dimensions
142
- });
143
- }
144
- }
145
- editField(
146
- `layer[${id}].encoding.parallel.key`,
147
- parallel?.key
148
- );
149
-
150
- for (const [selectionId, config] of Object.entries(
151
- program.materializationConfigs.selections ?? {}
152
- )) {
153
- if (config.target !== id) continue;
154
- const selector = { ...config.selector };
155
- if (selector.field !== undefined) {
156
- selector.field = changed(selector.field);
157
- }
158
- if (selector.groupBy !== undefined) {
159
- selector.groupBy = selector.groupBy.map(changed);
160
- }
161
- if (JSON.stringify(selector) !== JSON.stringify(config.selector)) {
162
- next = next._withSelectionConfig(selectionId, { ...config, selector });
163
- }
164
- }
165
-
166
- const jitter = program.materializationConfigs.jitters?.[id];
167
- const key = jitter?.key === undefined
168
- ? undefined
169
- : changed(jitter.key);
170
- if (key !== undefined && key !== jitter.key) {
171
- next = next._withMaterializationConfig(
172
- ["jitters", id], { ...jitter, key }
173
- );
174
- }
175
- return next;
176
- }
177
-
178
- function rejectDerivedConsumers(program, data) {
179
- const dependent = findDatasetConsumer(program, data);
180
- if (dependent !== undefined) {
181
- throw new Error(
182
- `Cannot replace 2D bin dataset "${data}" while derived dataset ` +
183
- `"${dependent.id}" depends on it.`
184
- );
185
- }
186
- }
187
-
188
76
  function preflight(program, sourceId, transform) {
189
77
  const source = findDataset(program, sourceId);
190
78
  if (source?.values === undefined) {
@@ -204,32 +92,18 @@ function requireCompleteEditOutputFields(value, members) {
204
92
  }
205
93
 
206
94
  function editedTransform(owner, previous, args) {
207
- const prior = requestedBin2DTransform(previous.transform[0]);
208
- const option = property => Object.hasOwn(args, property)
209
- ? args[property]
210
- : prior[property];
211
- const members = option("members");
212
- let as = option("as");
213
- if (!Object.hasOwn(args, "as")) {
214
- as = { ...as };
215
- if (members && as.members === undefined) {
216
- as.members = `__${owner}_members`;
217
- } else if (!members) {
218
- delete as.members;
219
- }
220
- }
221
- const transform = normalizeBin2DTransform({
222
- id: owner,
223
- x: option("x"),
224
- y: option("y"),
225
- bins: option("bins"),
226
- extent: option("extent"),
227
- includeEmpty: option("includeEmpty"),
228
- members,
229
- as
230
- });
95
+ const patch = Object.fromEntries(EDITABLE.flatMap(property =>
96
+ property !== "source" && Object.hasOwn(args, property)
97
+ ? [[property, args[property]]]
98
+ : []
99
+ ));
100
+ const transform = normalizeDatasetTransformEdit(
101
+ previous.transform[0],
102
+ patch,
103
+ owner
104
+ );
231
105
  if (Object.hasOwn(args, "as")) {
232
- requireCompleteEditOutputFields(args.as, members);
106
+ requireCompleteEditOutputFields(args.as, transform.members);
233
107
  }
234
108
  return transform;
235
109
  }
@@ -238,29 +112,18 @@ function sameRequestedTransform(left, right) {
238
112
  return JSON.stringify(left) === JSON.stringify(right);
239
113
  }
240
114
 
241
- function applyBin2DRevision(program, { owner, previous, source, transform }) {
242
- rejectDerivedConsumers(program, previous.id);
243
- const consumers = directLayerConsumers(program, previous.id);
244
- const changes = outputFieldChanges(previous.transform[0], transform);
245
- const revision = planDerivedDataRevision(program, {
246
- owner,
247
- role: "Bin2DData",
248
- previous: previous.id,
249
- consumers
115
+ function applyBin2DRevision(program, {
116
+ owner,
117
+ source,
118
+ transform,
119
+ dependents = "reject"
120
+ }) {
121
+ return reviseDerivedData(program, {
122
+ resolved: resolveDerivedDataOwner(program, owner, "bin2d"),
123
+ definition: transform,
124
+ dependents,
125
+ source
250
126
  });
251
- let next = program
252
- .createDerivedData({ id: revision.id, source, transform: [transform] })
253
- .materializeBin2DData({ id: revision.id });
254
- for (const rebind of revision.rebinds) {
255
- next = next.rebindLayerData(rebind);
256
- next = rebindLayerOutputFields(next, rebind.id, changes);
257
- next = applyLayerDataRematerialization(next, rebind.id);
258
- }
259
- next = next.releaseDerivedData(revision.release);
260
- return next._withMaterializationConfig(
261
- ["data", "bin2d", owner],
262
- { current: revision.id }
263
- );
264
127
  }
265
128
 
266
129
  export const createBin2DData = action(
@@ -275,10 +138,14 @@ export const createBin2DData = action(
275
138
  const previous = config === undefined
276
139
  ? undefined
277
140
  : requireCurrentBin2D(this, owner, config);
278
- const source = validateUserId(
279
- args.source ?? previous?.source ?? this.context.currentData,
280
- "Source dataset id"
281
- );
141
+ const source = resolveDatasetReference(
142
+ this,
143
+ validateUserId(
144
+ args.source ?? previous?.source ?? this.context.currentData,
145
+ "Source dataset id"
146
+ ),
147
+ "Source dataset"
148
+ ).id;
282
149
  const transform = normalizeBin2DTransform({ ...args, id: owner });
283
150
  preflight(this, source, transform);
284
151
 
@@ -313,10 +180,15 @@ export const editBin2DData = action(
313
180
  }
314
181
  const owner = resolveBin2DOwner(this, args.target);
315
182
  const previous = requireCurrentBin2D(this, owner, ownerConfig(this, owner));
316
- const source = validateUserId(
317
- args.source ?? previous.source,
318
- "2D bin source dataset id"
319
- );
183
+ const dependents = normalizeDerivedDependentPolicy(args.dependents);
184
+ const source = resolveDatasetReference(
185
+ this,
186
+ validateUserId(
187
+ args.source ?? previous.source,
188
+ "2D bin source dataset id"
189
+ ),
190
+ "2D bin source dataset"
191
+ ).id;
320
192
  const transform = editedTransform(owner, previous, args);
321
193
  if (
322
194
  source === previous.source &&
@@ -328,11 +200,11 @@ export const editBin2DData = action(
328
200
  throw new Error("editBin2DData requires an actual transform or source change.");
329
201
  }
330
202
  preflight(this, source, transform);
331
- const revision = { owner, previous, source, transform };
332
-
333
- // Execute one speculative immutable branch so every consumer plan is known
334
- // to succeed before the returned action trace records its first child.
335
- applyBin2DRevision(this, revision);
336
- return applyBin2DRevision(this, revision);
203
+ return applyBin2DRevision(this, {
204
+ owner,
205
+ source,
206
+ transform,
207
+ dependents
208
+ });
337
209
  }
338
210
  );
@@ -0,0 +1,26 @@
1
+ import {
2
+ deriveCompleteRows,
3
+ normalizeCompleteTransform
4
+ } from "../../grammar/complete.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
6
+
7
+ const OPTIONS = Object.freeze([
8
+ "id", "source", "key", "groupBy", "values", "sequence", "fill", "members"
9
+ ]);
10
+
11
+ export const materializeCompleteData = derivedMaterializer(
12
+ "materializeCompleteData",
13
+ "Materialize one immutable completed-key dataset.",
14
+ "complete",
15
+ deriveCompleteRows
16
+ );
17
+
18
+ export const createCompleteData = derivedCreator(
19
+ "createCompleteData",
20
+ "Complete one key domain independently within observed groups.",
21
+ OPTIONS,
22
+ "Complete dataset id",
23
+ "Complete source dataset id",
24
+ normalizeCompleteTransform,
25
+ "materializeCompleteData"
26
+ );
@@ -8,14 +8,14 @@ const OPTIONS = Object.freeze(["id", "source", "as", "expression"]);
8
8
 
9
9
  export const materializeComputedData = derivedMaterializer(
10
10
  "materializeComputedData",
11
- "Materialize one immutable row-level arithmetic dataset.",
11
+ "Materialize one immutable row-level typed expression dataset.",
12
12
  "computed",
13
13
  deriveComputedRows
14
14
  );
15
15
 
16
16
  export const createComputedData = derivedCreator(
17
17
  "createComputedData",
18
- "Create a finite computed field from a closed arithmetic expression.",
18
+ "Create a typed computed field from a closed expression.",
19
19
  OPTIONS,
20
20
  "Computed dataset id",
21
21
  "Computed source dataset id",
@@ -2,7 +2,7 @@ import { action } from "../../core/action.js";
2
2
  import { resolveOptionalUserId } from "../../core/identifiers.js";
3
3
  import { isPlainObject } from "../../core/immutable.js";
4
4
  import { validateKeys } from "../../core/validation.js";
5
- import { hasDataset } from "../../selectors/index.js";
5
+ import { hasDataset, hasDatasetOwner } from "../../selectors/index.js";
6
6
 
7
7
  const OPTIONS = Object.freeze(["id", "values"]);
8
8
 
@@ -22,7 +22,7 @@ export const createData = action(
22
22
  if (!args.values.every(isPlainObject)) {
23
23
  throw new TypeError("createData requires every row to be a plain object.");
24
24
  }
25
- if (hasDataset(this, id)) {
25
+ if (hasDataset(this, id) || hasDatasetOwner(this, id)) {
26
26
  throw new Error(`Dataset "${id}" already exists.`);
27
27
  }
28
28
  return this.editSemantic({
@@ -1,28 +1,15 @@
1
- import { deriveKernelDensity } from "../../grammar/density.js";
1
+ import {
2
+ deriveKernelDensity,
3
+ normalizeDensityTransform
4
+ } from "../../grammar/density.js";
2
5
  import { derivedCreator, derivedMaterializer } from "./shared.js";
3
6
 
4
7
  const OPTIONS = Object.freeze([
5
8
  "id", "source", "field", "groupBy", "bandwidth", "extent", "steps",
6
- "kernel", "normalization", "as"
9
+ "kernel", "normalization", "as", "weight"
7
10
  ]);
8
11
  const CATEGORICAL_OPTIONS = Object.freeze([...OPTIONS, "placement"]);
9
12
 
10
- function densityTransform(args, placement) {
11
- return {
12
- type: "density",
13
- field: args.field,
14
- ...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
15
- bandwidth: args.bandwidth ?? "auto",
16
- extent: args.extent ?? "auto",
17
- steps: args.steps ?? 100,
18
- kernel: args.kernel ?? "gaussian",
19
- normalization: args.normalization ?? "unit",
20
- as: args.as ?? [`${args.field}_value`, `${args.field}_density`],
21
- resolve: "shared",
22
- ...(placement === undefined ? {} : { placement })
23
- };
24
- }
25
-
26
13
  function requestedTransform(args, op, categorical = false) {
27
14
  if (typeof args.field !== "string" || args.field.length === 0) {
28
15
  throw new TypeError(`${op} requires a non-empty field string.`);
@@ -32,7 +19,10 @@ function requestedTransform(args, op, categorical = false) {
32
19
  "createCategoricalDensityData requires normalized category placement."
33
20
  );
34
21
  }
35
- return densityTransform(args, categorical ? args.placement : undefined);
22
+ return normalizeDensityTransform({
23
+ ...args,
24
+ ...(categorical ? { placement: args.placement } : {})
25
+ });
36
26
  }
37
27
 
38
28
  export const materializeDensityData = derivedMaterializer(
@@ -43,7 +33,9 @@ export const materializeDensityData = derivedMaterializer(
43
33
  (result, transform) => [{
44
34
  ...transform,
45
35
  resolved: {
46
- bandwidth: result.bandwidth,
36
+ ...(result.bandwidth === undefined
37
+ ? { bandwidths: result.bandwidths }
38
+ : { bandwidth: result.bandwidth }),
47
39
  extent: result.extent,
48
40
  ...(result.splitDomain === undefined
49
41
  ? {}
@@ -1,11 +1,16 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { validateKeys } from "../../core/validation.js";
4
- import { hasDataset } from "../../selectors/index.js";
5
- import { findDataset } from "../../selectors/datasets.js";
4
+ import {
5
+ findDataset,
6
+ hasDataset,
7
+ hasDatasetOwner,
8
+ resolveDatasetReference
9
+ } from "../../selectors/datasets.js";
6
10
  import { requireLayer } from "../../selectors/layers.js";
7
11
  import { applyLayerDataRematerialization } from
8
12
  "../../materialization/dependencies.js";
13
+ import { collectResourceReferences } from "../../core/resourceReferences.js";
9
14
 
10
15
  const OPTIONS = Object.freeze(["id", "source", "transform"]);
11
16
  const RELEASE_OPTIONS = Object.freeze(["id"]);
@@ -18,7 +23,8 @@ const OWNED_MARK_ROLES = Object.freeze([
18
23
  "errorBar",
19
24
  "gradientPlot",
20
25
  "ecdfPlot",
21
- "regression"
26
+ "regression",
27
+ "statisticalReference"
22
28
  ]);
23
29
 
24
30
  export const createDerivedData = action(
@@ -26,8 +32,13 @@ export const createDerivedData = action(
26
32
  function (args = {}) {
27
33
  validateKeys(args, OPTIONS, "createDerivedData");
28
34
  const id = validateUserId(args.id, "Derived dataset id");
29
- const source = validateUserId(args.source, "Source dataset id");
30
- if (hasDataset(this, id)) {
35
+ const requestedSource = validateUserId(args.source, "Source dataset id");
36
+ const source = resolveDatasetReference(
37
+ this,
38
+ requestedSource,
39
+ "Source dataset"
40
+ ).id;
41
+ if (hasDataset(this, id) || hasDatasetOwner(this, id)) {
31
42
  throw new Error(`Dataset "${id}" already exists.`);
32
43
  }
33
44
  if (!hasDataset(this, source)) {
@@ -51,17 +62,26 @@ export const releaseDerivedData = action(
51
62
  if (dataset === undefined || dataset.source === undefined) {
52
63
  throw new Error(`Unknown derived dataset "${validatedId}".`);
53
64
  }
54
- const referenced = this.semanticSpec.layers.some(
55
- layer => layer.data === validatedId
56
- ) || this.semanticSpec.datasets.some(
57
- candidate => candidate.source === validatedId
65
+ const references = collectResourceReferences(this, {
66
+ kind: "data",
67
+ id: validatedId
68
+ });
69
+ const selfOwners = references.filter(reference =>
70
+ reference.ownerKind === "dataOwner" &&
71
+ reference.path.at(-1) === "current"
58
72
  );
59
- return referenced
60
- ? this
61
- : this.editSemantic({
62
- property: `dataset[${validatedId}]`,
63
- remove: true
64
- });
73
+ if (references.some(reference =>
74
+ reference.strength === "live" && !selfOwners.includes(reference)
75
+ )) return this;
76
+ let next = this.editSemantic({
77
+ property: `dataset[${validatedId}]`,
78
+ remove: true
79
+ });
80
+ for (const owner of selfOwners) {
81
+ const [, family, ownerId] = owner.path;
82
+ next = next._withoutMaterializationConfig(["data", family, ownerId]);
83
+ }
84
+ return next;
65
85
  }
66
86
  );
67
87
 
@@ -73,7 +93,12 @@ export const rebindLayerData = action(
73
93
  function (args = {}) {
74
94
  validateKeys(args, REBIND_OPTIONS, "rebindLayerData");
75
95
  const id = validateUserId(args.id, "Layer id");
76
- const data = validateUserId(args.data, "Layer dataset id");
96
+ const requestedData = validateUserId(args.data, "Layer dataset id");
97
+ const data = resolveDatasetReference(
98
+ this,
99
+ requestedData,
100
+ "Layer dataset"
101
+ ).id;
77
102
  requireLayer(this, id);
78
103
  if (!hasDataset(this, data)) {
79
104
  throw new Error(`Layer dataset "${data}" does not exist.`);
@@ -117,7 +142,12 @@ export const bindMarkData = action(
117
142
  function (args = {}) {
118
143
  validateKeys(args, BIND_OPTIONS, "bindMarkData");
119
144
  const target = validateUserId(args.target, "Mark target id");
120
- const data = validateUserId(args.data, "Mark dataset id");
145
+ const requestedData = validateUserId(args.data, "Mark dataset id");
146
+ const data = resolveDatasetReference(
147
+ this,
148
+ requestedData,
149
+ "Mark dataset"
150
+ ).id;
121
151
  const layer = requireLayer(this, target);
122
152
  const dataset = findDataset(this, data);
123
153
  if (dataset === undefined) {