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
@@ -3,8 +3,9 @@ import { validateUserId } from "../../core/identifiers.js";
3
3
  import { validateKeys } from "../../core/validation.js";
4
4
  import {
5
5
  deriveRegression,
6
- normalizeRegressionParameters
6
+ normalizeRegressionTransform
7
7
  } from "../../grammar/regression/index.js";
8
+ import { resolveDatasetReference } from "../../selectors/datasets.js";
8
9
  import { derivedMaterializer } from "./shared.js";
9
10
 
10
11
  const OPTIONS = Object.freeze([
@@ -34,31 +35,25 @@ export const createRegressionData = action(
34
35
  { op: "createRegressionData", description: "Create grouped regression values and optional interval bounds." },
35
36
  function (args = {}) {
36
37
  validateKeys(args, OPTIONS, "createRegressionData");
37
- const parameters = normalizeRegressionParameters(args);
38
38
  const id = validateUserId(args.id, "Regression dataset id");
39
- const source = validateUserId(
39
+ const requestedSource = validateUserId(
40
40
  args.source ?? this.context.currentData,
41
41
  "Source dataset id"
42
42
  );
43
- const transform = {
44
- type: "regression",
45
- method: parameters.method,
46
- x: args.x,
47
- y: args.y,
48
- ...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
49
- ...(parameters.method === "polynomial"
50
- ? { degree: parameters.degree }
51
- : {}),
52
- ...(parameters.method === "loess"
53
- ? { span: parameters.span }
54
- : {
55
- confidenceMethod: parameters.confidenceMethod,
56
- level: parameters.level,
57
- interval: parameters.interval
58
- })
59
- };
60
- return this
43
+ const source = resolveDatasetReference(
44
+ this,
45
+ requestedSource,
46
+ "Regression source dataset"
47
+ ).id;
48
+ const transform = normalizeRegressionTransform(args);
49
+ const next = this
61
50
  .createDerivedData({ id, source, transform: [transform] })
62
51
  .materializeRegressionData({ id });
52
+ return this.actionStack.length === 1
53
+ ? next._withMaterializationConfig(
54
+ ["data", "regression", id],
55
+ { current: id }
56
+ )
57
+ : next;
63
58
  }
64
59
  );
@@ -0,0 +1,145 @@
1
+ import { isPlainObject } from "../../core/immutable.js";
2
+ import { datasetTransformOutputRoles } from "../../grammar/transforms.js";
3
+ import { requireLayer } from "../../selectors/layers.js";
4
+
5
+ const DATA_REFERENCE_KEYS = new Set([
6
+ "data", "dataset", "dataId", "filteredData", "originalData",
7
+ "outlierDataId", "profileId", "source", "summaryId"
8
+ ]);
9
+
10
+ export function outputFieldChanges(previous, next) {
11
+ const before = datasetTransformOutputRoles(previous);
12
+ const after = datasetTransformOutputRoles(next);
13
+ const family = previous.type === "bin2d" ? "2D bin " : "";
14
+ const counts = new Map();
15
+ const retainedFields = new Set(Object.values(after));
16
+ for (const field of Object.values(before)) {
17
+ counts.set(field, (counts.get(field) ?? 0) + 1);
18
+ }
19
+ return new Map(Object.entries(before).flatMap(([role, field]) => {
20
+ if (field === after[role] || retainedFields.has(field)) return [];
21
+ if (counts.get(field) !== 1) {
22
+ throw new Error(
23
+ `Cannot rebind ambiguous derived output field "${field}".`
24
+ );
25
+ }
26
+ return [[field, { role, next: after[role], family }]];
27
+ }));
28
+ }
29
+
30
+ function changedConsumerField(field, changes) {
31
+ const change = changes.get(field);
32
+ if (change === undefined) return field;
33
+ if (change.next === undefined) {
34
+ throw new Error(
35
+ `Cannot remove referenced ${change.family}${change.role} output field "${field}".`
36
+ );
37
+ }
38
+ return change.next;
39
+ }
40
+
41
+ export function rebindLayerOutputFields(program, id, changes) {
42
+ const layer = requireLayer(program, id);
43
+ let next = program;
44
+ const changed = field => changedConsumerField(field, changes);
45
+ const editField = (property, field) => {
46
+ if (field === undefined) return;
47
+ const value = changed(field);
48
+ if (value !== field) next = next.editSemantic({ property, value });
49
+ };
50
+ for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
51
+ editField(`layer[${id}].encoding.${channel}.field`, encoding?.field);
52
+ const summary = encoding?.categoryOrder?.by;
53
+ if (summary?.field !== undefined) {
54
+ const field = changed(summary.field);
55
+ if (field !== summary.field) {
56
+ next = next.editSemantic({
57
+ property: `layer[${id}].encoding.${channel}.categoryOrder`,
58
+ value: { ...encoding.categoryOrder, by: { ...summary, field } }
59
+ });
60
+ }
61
+ }
62
+ }
63
+ editField(
64
+ `layer[${id}].encoding.theta.weight`,
65
+ layer.encoding?.theta?.weight
66
+ );
67
+
68
+ const parallel = layer.encoding?.parallel;
69
+ if (parallel?.dimensions !== undefined) {
70
+ const dimensions = parallel.dimensions.map(dimension => ({
71
+ ...dimension,
72
+ field: changed(dimension.field)
73
+ }));
74
+ if (dimensions.some((dimension, index) =>
75
+ dimension.field !== parallel.dimensions[index].field
76
+ )) {
77
+ next = next.editSemantic({
78
+ property: `layer[${id}].encoding.parallel.dimensions`,
79
+ value: dimensions
80
+ });
81
+ }
82
+ }
83
+ editField(`layer[${id}].encoding.parallel.key`, parallel?.key);
84
+
85
+ for (const [selectionId, config] of Object.entries(
86
+ program.materializationConfigs.selections ?? {}
87
+ )) {
88
+ if (config.target !== id) continue;
89
+ const selector = { ...config.selector };
90
+ if (selector.field !== undefined) selector.field = changed(selector.field);
91
+ if (selector.groupBy !== undefined) {
92
+ selector.groupBy = selector.groupBy.map(changed);
93
+ }
94
+ if (JSON.stringify(selector) !== JSON.stringify(config.selector)) {
95
+ next = next._withSelectionConfig(selectionId, { ...config, selector });
96
+ }
97
+ }
98
+
99
+ const jitter = program.materializationConfigs.jitters?.[id];
100
+ const key = jitter?.key === undefined ? undefined : changed(jitter.key);
101
+ if (key !== undefined && key !== jitter.key) {
102
+ next = next._withMaterializationConfig(
103
+ ["jitters", id],
104
+ { ...jitter, key }
105
+ );
106
+ }
107
+ return next;
108
+ }
109
+
110
+ function replaceReferences(value, replacements, key) {
111
+ if (Array.isArray(value)) {
112
+ let changed = false;
113
+ const items = value.map(item => {
114
+ const next = replaceReferences(item, replacements);
115
+ changed ||= next !== item;
116
+ return next;
117
+ });
118
+ return changed ? items : value;
119
+ }
120
+ if (!isPlainObject(value)) {
121
+ return DATA_REFERENCE_KEYS.has(key) && replacements.has(value)
122
+ ? replacements.get(value)
123
+ : value;
124
+ }
125
+ let changed = false;
126
+ const result = {};
127
+ for (const [childKey, child] of Object.entries(value)) {
128
+ const next = replaceReferences(child, replacements, childKey);
129
+ changed ||= next !== child;
130
+ result[childKey] = next;
131
+ }
132
+ return changed ? result : value;
133
+ }
134
+
135
+ export function rebindMaterializationDataReferences(program, replacements) {
136
+ if (replacements.size === 0) return program;
137
+ let next = program;
138
+ for (const [key, config] of Object.entries(program.materializationConfigs)) {
139
+ const replaced = replaceReferences(config, replacements, key);
140
+ if (replaced !== config) {
141
+ next = next._withMaterializationConfig([key], replaced);
142
+ }
143
+ }
144
+ return next;
145
+ }
@@ -1,7 +1,10 @@
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 { findDataset } from "../../selectors/datasets.js";
4
+ import {
5
+ findDataset,
6
+ resolveDatasetReference
7
+ } from "../../selectors/datasets.js";
5
8
 
6
9
  export const MATERIALIZE_OPTIONS = Object.freeze(["id"]);
7
10
 
@@ -61,12 +64,25 @@ export function derivedCreator(
61
64
  return action({ op, description }, function (args = {}) {
62
65
  validateKeys(args, options, op);
63
66
  const id = validateUserId(args.id, idLabel);
64
- const source = validateUserId(
67
+ const requestedSource = validateUserId(
65
68
  requireSource ? args.source : args.source ?? this.context.currentData,
66
69
  sourceLabel
67
70
  );
68
- return this
69
- .createDerivedData({ id, source, transform: [transform(args, id)] })
71
+ const source = resolveDatasetReference(
72
+ this,
73
+ requestedSource,
74
+ sourceLabel
75
+ ).id;
76
+ const standalone = this.actionStack.length === 1;
77
+ const requestedTransform = transform(args, id);
78
+ const next = this
79
+ .createDerivedData({ id, source, transform: [requestedTransform] })
70
80
  [materialize]({ id });
81
+ return standalone
82
+ ? next._withMaterializationConfig(
83
+ ["data", requestedTransform.type, id],
84
+ { current: id }
85
+ )
86
+ : next;
71
87
  });
72
88
  }
@@ -5,7 +5,7 @@ import {
5
5
  import { derivedCreator, derivedMaterializer } from "./shared.js";
6
6
 
7
7
  const OPTIONS = Object.freeze([
8
- "id", "source", "groupBy", "aggregates", "members"
8
+ "id", "source", "groupBy", "aggregates", "members", "weight"
9
9
  ]);
10
10
 
11
11
  export const materializeSummaryData = derivedMaterializer(
@@ -4,7 +4,10 @@ import {
4
4
  } from "../../grammar/timeUnit.js";
5
5
  import { derivedCreator, derivedMaterializer } from "./shared.js";
6
6
 
7
- const OPTIONS = Object.freeze(["id", "source", "field", "unit", "as", "temporalUnit"]);
7
+ const OPTIONS = Object.freeze([
8
+ "id", "source", "field", "unit", "as", "temporalUnit", "timeZone",
9
+ "weekStartsOn", "weekRule"
10
+ ]);
8
11
 
9
12
  export const materializeTimeUnitData = derivedMaterializer(
10
13
  "materializeTimeUnitData",
@@ -5,7 +5,7 @@ import {
5
5
  import { derivedCreator, derivedMaterializer } from "./shared.js";
6
6
 
7
7
  const OPTIONS = Object.freeze([
8
- "id", "source", "partitionBy", "sortBy", "operations"
8
+ "id", "source", "partitionBy", "sortBy", "operations", "temporalUnit"
9
9
  ]);
10
10
 
11
11
  export const materializeWindowData = derivedMaterializer(
@@ -0,0 +1,415 @@
1
+ import {
2
+ action,
3
+ invokeWrappedActionImplementation
4
+ } from "../../core/action.js";
5
+ import { isPlainObject } from "../../core/immutable.js";
6
+ import { validateUserId } from "../../core/identifiers.js";
7
+ import { validateOptionObject } from "../../core/validation.js";
8
+ import { findLayer } from "../../selectors/layers.js";
9
+ import { findSemanticScale } from "../../selectors/scales.js";
10
+ import { findScaleConsumers } from "../scales/consumers/index.js";
11
+ import { resolveScalePreview, validatePendingMeasuredScale } from
12
+ "../scales/preview.js";
13
+ import { isPendingMeasuredRadiusConsumer } from
14
+ "../../materialization/scales/policies/arc.js";
15
+ import {
16
+ getMarkRematerializationStep,
17
+ getScaleConsumerMarkSteps,
18
+ getSourceDependentMarkSteps
19
+ } from "../../materialization/marks/index.js";
20
+ import {
21
+ applyMaterializationPlan,
22
+ planScaleGuideRematerialization
23
+ } from "../../materialization/dependencies.js";
24
+ import { buildMaterializationPlan } from "../../materialization/planner.js";
25
+ import { hasMaterializedLegend } from "../../materialization/legends.js";
26
+ import { rebindPositionGuides } from "./shared.js";
27
+ import { withoutPreviewLayerEncodings } from
28
+ "../primitives/semanticAction.js";
29
+
30
+ export const ENCODING_CHANNEL_ORDER = Object.freeze([
31
+ "x", "y", "x2", "y2", "theta", "r", "xOffset", "yOffset",
32
+ "group", "pathOrder", "color", "stroke", "size", "shape",
33
+ "opacity", "strokeWidth", "strokeDash", "angle", "text"
34
+ ]);
35
+
36
+ const CHANNEL_METHODS = Object.freeze({
37
+ x: "encodeX",
38
+ y: "encodeY",
39
+ x2: "encodeX2",
40
+ y2: "encodeY2",
41
+ theta: "encodeTheta",
42
+ r: "encodeR",
43
+ xOffset: "encodeXOffset",
44
+ yOffset: "encodeYOffset",
45
+ group: "encodeGroup",
46
+ pathOrder: "encodePathOrder",
47
+ color: "encodeColor",
48
+ stroke: "encodeStroke",
49
+ size: "encodeSize",
50
+ shape: "encodeShape",
51
+ opacity: "encodeOpacity",
52
+ strokeWidth: "encodeStrokeWidth",
53
+ strokeDash: "encodeStrokeDash",
54
+ angle: "encodeAngle",
55
+ text: "encodeText"
56
+ });
57
+
58
+ const FORBIDDEN_PAYLOAD_KEYS = Object.freeze(["target", "coordinate", "id"]);
59
+
60
+ const DEFERRED_MARK_OPERATIONS = Object.freeze([
61
+ "materializeEmptyMark",
62
+ "rematerializePointMark",
63
+ "rematerializeTickMark",
64
+ "rematerializeLineMark",
65
+ "rematerializeAreaMark",
66
+ "rematerializeArcMark",
67
+ "rematerializeBarMark",
68
+ "rematerializeRuleMark",
69
+ "rematerializeTextMark",
70
+ "rematerializeRectMark",
71
+ "rematerializeMarkHighlights",
72
+ "materializeLabelLayout"
73
+ ]);
74
+
75
+ const deferredClassByProgram = new WeakMap();
76
+
77
+ function sameSetting(left, right) {
78
+ if (Object.is(left, right)) return true;
79
+ if (Array.isArray(left) && Array.isArray(right)) {
80
+ return left.length === right.length && left.every(
81
+ (value, index) => sameSetting(value, right[index])
82
+ );
83
+ }
84
+ if (isPlainObject(left) && isPlainObject(right)) {
85
+ const leftKeys = Object.keys(left);
86
+ const rightKeys = Object.keys(right);
87
+ return leftKeys.length === rightKeys.length && leftKeys.every(
88
+ key => Object.hasOwn(right, key) && sameSetting(left[key], right[key])
89
+ );
90
+ }
91
+ return false;
92
+ }
93
+
94
+ function assertCompatibleScaleRequests(requests) {
95
+ const patches = new Map();
96
+ for (const { channel, payload } of requests) {
97
+ if (!isPlainObject(payload.scale) || payload.scale.id === undefined) continue;
98
+ const id = payload.scale.id;
99
+ const previous = patches.get(id) ?? new Map();
100
+ for (const [property, value] of Object.entries(payload.scale)) {
101
+ if (property === "id" || value === undefined) continue;
102
+ const existing = previous.get(property);
103
+ if (existing !== undefined && !sameSetting(existing.value, value)) {
104
+ throw new Error(
105
+ `encodeChannels scale "${id}" has conflicting explicit ${property} requests for ${existing.channel} and ${channel}.`
106
+ );
107
+ }
108
+ if (existing === undefined) previous.set(property, { channel, value });
109
+ }
110
+ patches.set(id, previous);
111
+ }
112
+ }
113
+
114
+ export function normalizeEncodeChannelsArgs(args) {
115
+ validateOptionObject(args, ["target", "channels"], "encodeChannels");
116
+ const target = validateUserId(args.target, "Mark id");
117
+ if (!isPlainObject(args.channels)) {
118
+ throw new TypeError("encodeChannels channels must be a plain object.");
119
+ }
120
+ const keys = Object.keys(args.channels);
121
+ if (keys.length === 0) {
122
+ throw new TypeError("encodeChannels channels must contain at least one channel.");
123
+ }
124
+ for (const key of keys) {
125
+ if (!Object.hasOwn(CHANNEL_METHODS, key)) {
126
+ throw new Error(`encodeChannels does not support channel "${key}".`);
127
+ }
128
+ }
129
+ const requests = ENCODING_CHANNEL_ORDER
130
+ .filter(channel => Object.hasOwn(args.channels, channel))
131
+ .map(channel => {
132
+ const payload = args.channels[channel];
133
+ if (!isPlainObject(payload)) {
134
+ throw new TypeError(
135
+ `encodeChannels ${channel} payload must be a plain object.`
136
+ );
137
+ }
138
+ for (const key of FORBIDDEN_PAYLOAD_KEYS) {
139
+ if (Object.hasOwn(payload, key)) {
140
+ throw new Error(
141
+ `encodeChannels ${channel} payload cannot specify ${key}.`
142
+ );
143
+ }
144
+ }
145
+ return Object.freeze({ channel, payload });
146
+ });
147
+ assertCompatibleScaleRequests(requests);
148
+ return Object.freeze({ target, requests: Object.freeze(requests) });
149
+ }
150
+
151
+ function resolveScaleOnly(program, id) {
152
+ const semanticScale = findSemanticScale(program, id);
153
+ if (semanticScale === undefined) {
154
+ throw new Error(`Unknown scale "${id}".`);
155
+ }
156
+ const consumers = findScaleConsumers(program, id);
157
+ if (
158
+ semanticScale.radialMapping !== undefined &&
159
+ consumers.length > 0 &&
160
+ consumers.every(isPendingMeasuredRadiusConsumer)
161
+ ) {
162
+ validatePendingMeasuredScale(program, semanticScale, consumers);
163
+ return program._withoutResolvedScale(id);
164
+ }
165
+ return program._withResolvedScale(
166
+ id,
167
+ resolveScalePreview(program, id).resolvedScale
168
+ );
169
+ }
170
+
171
+ function deferredProgramClass(ProgramClass) {
172
+ const existing = deferredClassByProgram.get(ProgramClass);
173
+ if (existing !== undefined) return existing;
174
+
175
+ class DeferredEncodingProgram extends ProgramClass {
176
+ rematerializeScale({ id } = {}) {
177
+ return resolveScaleOnly(this, id);
178
+ }
179
+
180
+ rematerializeLegend() {
181
+ return this;
182
+ }
183
+ }
184
+ for (const operation of DEFERRED_MARK_OPERATIONS) {
185
+ Object.defineProperty(DeferredEncodingProgram.prototype, operation, {
186
+ configurable: true,
187
+ value() {
188
+ return this;
189
+ }
190
+ });
191
+ }
192
+ deferredClassByProgram.set(ProgramClass, DeferredEncodingProgram);
193
+ return DeferredEncodingProgram;
194
+ }
195
+
196
+ function programState(program) {
197
+ return Object.freeze({
198
+ semanticSpec: program.semanticSpec,
199
+ graphicSpec: program.graphicSpec,
200
+ resolvedScales: program.resolvedScales,
201
+ materializationConfigs: program.materializationConfigs,
202
+ children: program.children,
203
+ compositionSpec: program.compositionSpec,
204
+ context: program.context,
205
+ trace: program.trace,
206
+ actionStack: program.actionStack,
207
+ actionSequence: program._actionSequence
208
+ });
209
+ }
210
+
211
+ function instantiateProgram(state, ProgramClass) {
212
+ return new ProgramClass({
213
+ semanticSpec: state.semanticSpec,
214
+ graphicSpec: state.graphicSpec,
215
+ resolvedScales: state.resolvedScales,
216
+ materializationConfigs: state.materializationConfigs,
217
+ children: state.children,
218
+ compositionSpec: state.compositionSpec,
219
+ context: state.context,
220
+ trace: state.trace,
221
+ actionStack: state.actionStack,
222
+ actionSequence: state.actionSequence
223
+ });
224
+ }
225
+
226
+ function createDeferredProgram(program) {
227
+ return instantiateProgram(
228
+ programState(program),
229
+ deferredProgramClass(program.constructor)
230
+ );
231
+ }
232
+
233
+ function requestedFieldType(request) {
234
+ if (request === undefined) return undefined;
235
+ return request.payload.fieldType ?? "quantitative";
236
+ }
237
+
238
+ function isCategoricalBarType(fieldType) {
239
+ return ["nominal", "ordinal", "temporal"].includes(fieldType);
240
+ }
241
+
242
+ function stageFinalPositionRoles(program, target, requests) {
243
+ const layer = findLayer(program, target);
244
+ if (layer?.mark?.type !== "bar") return program;
245
+ const byChannel = new Map(requests.map(request => [request.channel, request]));
246
+ const nextX = requestedFieldType(byChannel.get("x"));
247
+ const nextY = requestedFieldType(byChannel.get("y"));
248
+ if (nextX === undefined || nextY === undefined) return program;
249
+ const currentX = layer.encoding?.x?.fieldType;
250
+ const currentY = layer.encoding?.y?.fieldType;
251
+ const changesOrientation =
252
+ isCategoricalBarType(currentX) !== isCategoricalBarType(nextX) &&
253
+ isCategoricalBarType(currentY) !== isCategoricalBarType(nextY);
254
+ if (!changesOrientation) return program;
255
+
256
+ return withoutPreviewLayerEncodings(program, {
257
+ id: target,
258
+ channels: ["x", "y"]
259
+ });
260
+ }
261
+
262
+ const INTERNAL_SCALE_CHANNEL = Object.freeze({ r: "radius" });
263
+
264
+ function requestedScaleIds(originalLayer, finalLayer, requests) {
265
+ const requestedChannels = new Set(requests.map(({ channel }) =>
266
+ INTERNAL_SCALE_CHANNEL[channel] ?? channel
267
+ ));
268
+ const ids = [];
269
+ for (const channel of [
270
+ "x", "y", "x2", "y2", "theta", "radius", "xOffset", "yOffset",
271
+ "color", "stroke", "size", "shape", "opacity", "strokeWidth",
272
+ "strokeDash", "angle", "text", "group", "pathOrder"
273
+ ]) {
274
+ if (!requestedChannels.has(channel)) continue;
275
+ for (const layer of [originalLayer, finalLayer]) {
276
+ const id = layer?.encoding?.[channel]?.scale;
277
+ if (id !== undefined && !ids.includes(id)) ids.push(id);
278
+ }
279
+ }
280
+ return ids;
281
+ }
282
+
283
+ function changedScaleIds(before, after) {
284
+ const beforeById = new Map(before.semanticSpec.scales.map(scale => [scale.id, scale]));
285
+ return after.semanticSpec.scales
286
+ .filter(scale => !sameSetting(scale, beforeById.get(scale.id)))
287
+ .map(scale => scale.id)
288
+ .sort();
289
+ }
290
+
291
+ function finalScaleIds(before, after, originalLayer, finalLayer, requests) {
292
+ const ids = [
293
+ ...requestedScaleIds(originalLayer, finalLayer, requests),
294
+ ...changedScaleIds(before, after)
295
+ ];
296
+ return [...new Set(ids)].filter(id =>
297
+ findSemanticScale(after, id) !== undefined &&
298
+ findScaleConsumers(after, id).length > 0
299
+ );
300
+ }
301
+
302
+ const rematerializeEncodingScales = action(
303
+ {
304
+ op: "rematerializeEncodingScales",
305
+ description: "Resolve every scale affected by one atomic encoding assignment."
306
+ },
307
+ function ({ ids } = {}) {
308
+ let next = this;
309
+ for (const id of ids) next = resolveScaleOnly(next, id);
310
+ return next;
311
+ }
312
+ );
313
+
314
+ function finalMaterializationPlan(program, target, scaleIds) {
315
+ const layer = findLayer(program, target);
316
+ const targetStep = layer === undefined
317
+ ? undefined
318
+ : getMarkRematerializationStep(program, layer);
319
+ const directMarks = [
320
+ ...(targetStep === undefined ? [] : [targetStep]),
321
+ ...getScaleConsumerMarkSteps(program, scaleIds)
322
+ ];
323
+ const marks = [
324
+ ...directMarks,
325
+ ...directMarks.flatMap(step =>
326
+ getSourceDependentMarkSteps(program, step.args.id)
327
+ )
328
+ ];
329
+ const guides = scaleIds.flatMap(id =>
330
+ planScaleGuideRematerialization(program, id)
331
+ );
332
+ if (hasMaterializedLegend(program)) {
333
+ guides.push({ op: "rematerializeLegend" });
334
+ }
335
+ return buildMaterializationPlan({ marks, guides });
336
+ }
337
+
338
+ function applyEncodingDraft(program, target, requests) {
339
+ let draft = stageFinalPositionRoles(
340
+ createDeferredProgram(program),
341
+ target,
342
+ requests
343
+ );
344
+ for (const { channel, payload } of requests) {
345
+ const focusedAction = draft[CHANNEL_METHODS[channel]];
346
+ draft = invokeWrappedActionImplementation(
347
+ focusedAction,
348
+ draft,
349
+ { ...payload, target }
350
+ );
351
+ }
352
+ const originalLayer = findLayer(program, target);
353
+ const finalLayer = findLayer(draft, target);
354
+ for (const channel of ["x", "y"]) {
355
+ const previousScale = originalLayer?.encoding?.[channel]?.scale;
356
+ const nextScale = finalLayer?.encoding?.[channel]?.scale;
357
+ if (previousScale !== undefined && nextScale !== undefined) {
358
+ draft = rebindPositionGuides(
359
+ draft,
360
+ channel,
361
+ previousScale,
362
+ nextScale,
363
+ target
364
+ );
365
+ }
366
+ }
367
+ return draft;
368
+ }
369
+
370
+ function planEncodingAssignments(program, target, requests) {
371
+ const originalLayer = findLayer(program, target);
372
+ const draft = applyEncodingDraft(program, target, requests);
373
+ const finalLayer = findLayer(draft, target);
374
+ return Object.freeze({
375
+ target,
376
+ originalLayer,
377
+ finalLayer,
378
+ state: programState(draft),
379
+ scaleIds: Object.freeze(finalScaleIds(
380
+ program,
381
+ draft,
382
+ originalLayer,
383
+ finalLayer,
384
+ requests
385
+ ))
386
+ });
387
+ }
388
+
389
+ function applyEncodingAssignments(program, plan) {
390
+ return instantiateProgram(plan.state, program.constructor);
391
+ }
392
+
393
+ export const encodeChannels = action(
394
+ {
395
+ op: "encodeChannels",
396
+ description: "Atomically assign multiple encoding channels to one mark."
397
+ },
398
+ function (args = {}) {
399
+ const { target, requests } = normalizeEncodeChannelsArgs(args);
400
+ const plan = planEncodingAssignments(this, target, requests);
401
+ let next = applyEncodingAssignments(this, plan);
402
+ const scaleIds = plan.scaleIds;
403
+ if (scaleIds.length > 0) {
404
+ next = rematerializeEncodingScales.call(next, { ids: scaleIds });
405
+ }
406
+ return applyMaterializationPlan(
407
+ next,
408
+ finalMaterializationPlan(next, target, scaleIds)
409
+ );
410
+ }
411
+ );
412
+
413
+ export function registerAtomicEncodingAction(ProgramClass) {
414
+ ProgramClass.prototype.encodeChannels = encodeChannels;
415
+ }