ggaction 0.0.21 → 0.0.23

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 (265) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +83 -90
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/package.json +11 -4
  8. package/src/BasicChartProgram.js +1 -1
  9. package/src/accessibility.js +1 -0
  10. package/src/actions/boxPlots/components.js +1 -96
  11. package/src/actions/boxPlots/create.js +6 -1
  12. package/src/actions/boxPlots/edit.js +8 -0
  13. package/src/actions/boxPlots/materialize.js +3 -0
  14. package/src/actions/boxPlots/options.js +1 -1
  15. package/src/actions/canvas/actions.js +2 -1
  16. package/src/actions/canvas/index.js +3 -0
  17. package/src/actions/canvas/plotBorder.js +2 -1
  18. package/src/actions/canvas/plotOrder.js +14 -0
  19. package/src/actions/charts/area.js +1 -1
  20. package/src/actions/charts/bar.js +4 -4
  21. package/src/actions/charts/density.js +1 -1
  22. package/src/actions/charts/ecdf.js +1 -1
  23. package/src/actions/charts/endpoints.js +54 -5
  24. package/src/actions/charts/heatmap.js +1 -1
  25. package/src/actions/charts/histogram.js +1 -1
  26. package/src/actions/charts/horizon.js +1 -1
  27. package/src/actions/charts/index.js +2 -1
  28. package/src/actions/charts/interval-regression.js +3 -3
  29. package/src/actions/charts/line.js +1 -1
  30. package/src/actions/charts/radar.js +63 -3
  31. package/src/actions/charts/raincloud.js +37 -6
  32. package/src/actions/charts/rug-strip.js +2 -2
  33. package/src/actions/charts/scatter.js +3 -3
  34. package/src/actions/charts/text.js +1 -1
  35. package/src/actions/composition/actions.js +10 -3
  36. package/src/actions/coordinates/actions.js +2 -0
  37. package/src/actions/data/basic.js +6 -0
  38. package/src/actions/data/derived.js +6 -2
  39. package/src/actions/data/edit.js +22 -36
  40. package/src/actions/data/filter.js +1 -376
  41. package/src/actions/data/flatten.js +19 -0
  42. package/src/actions/data/index.js +6 -0
  43. package/src/actions/data/lookup.js +50 -0
  44. package/src/actions/data/raincloudFilter.js +56 -0
  45. package/src/actions/data/shared.js +10 -0
  46. package/src/actions/data/timeUnit.js +1 -1
  47. package/src/actions/distributions/revision.js +3 -1
  48. package/src/actions/encodings/angle.js +2 -1
  49. package/src/actions/encodings/appearance.js +38 -13
  50. package/src/actions/encodings/channels.js +14 -3
  51. package/src/actions/encodings/color/continuous.js +26 -10
  52. package/src/actions/encodings/color/index.js +50 -20
  53. package/src/actions/encodings/color/policy.js +57 -4
  54. package/src/actions/encodings/parallel.js +4 -0
  55. package/src/actions/encodings/position/policies/bar.js +10 -6
  56. package/src/actions/encodings/position/policies/line.js +1 -152
  57. package/src/actions/encodings/position/resolve.js +4 -1
  58. package/src/actions/encodings/ranged.js +46 -0
  59. package/src/actions/encodings/remove.js +1 -1
  60. package/src/actions/encodings/ruleAppearance.js +7 -6
  61. package/src/actions/encodings/shared.js +14 -2
  62. package/src/actions/encodings/stroke.js +20 -14
  63. package/src/actions/encodings/strokeDash.js +9 -11
  64. package/src/actions/encodings/text.js +30 -2
  65. package/src/actions/errorBars/edit.js +24 -4
  66. package/src/actions/facets/actions.js +1 -793
  67. package/src/actions/facets/derive.js +34 -5
  68. package/src/actions/facets/replay.js +1 -38
  69. package/src/actions/gradientPlots/components.js +21 -9
  70. package/src/actions/gradientPlots/create.js +4 -2
  71. package/src/actions/gradientPlots/edit.js +18 -1
  72. package/src/actions/gradientPlots/materialize.js +35 -36
  73. package/src/actions/gradientPlots/options.js +1 -1
  74. package/src/actions/gradientPlots/paint.js +13 -7
  75. package/src/actions/guides/applicability.js +2 -0
  76. package/src/actions/guides/axes/axes.js +39 -0
  77. package/src/actions/guides/axes/axis.js +9 -5
  78. package/src/actions/guides/axes/edit.js +11 -8
  79. package/src/actions/guides/axes/labels.js +24 -8
  80. package/src/actions/guides/axes/parallel/policy.js +3 -5
  81. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  82. package/src/actions/guides/axes/policy.js +22 -0
  83. package/src/actions/guides/axes/tickGroups.js +4 -2
  84. package/src/actions/guides/axes/ticks.js +22 -4
  85. package/src/actions/guides/axes/titles.js +19 -6
  86. package/src/actions/guides/facadeAxes.js +3 -0
  87. package/src/actions/guides/grids/grid.js +3 -1
  88. package/src/actions/guides/grids/resolve.js +7 -11
  89. package/src/actions/guides/guides.js +21 -1
  90. package/src/actions/guides/legends/categorical/actions.js +1 -515
  91. package/src/actions/guides/legends/categorical/components.js +1 -4
  92. package/src/actions/guides/legends/categorical/layout.js +5 -2
  93. package/src/actions/guides/legends/categorical/options.js +4 -0
  94. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  95. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  96. package/src/actions/guides/legends/colorSize.js +2 -0
  97. package/src/actions/guides/legends/continuous/common.js +1 -1
  98. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  99. package/src/actions/guides/legends/continuous/interval.js +1 -1
  100. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  101. package/src/actions/guides/legends/edit.js +1 -1
  102. package/src/actions/guides/legends/focused.js +1 -1
  103. package/src/actions/guides/legends/lane.js +1 -1
  104. package/src/actions/guides/legends/sampling.js +1 -140
  105. package/src/actions/guides/legends/strokeWidth.js +3 -2
  106. package/src/actions/guides/legends/transition.js +19 -2
  107. package/src/actions/guides/polar/axes/facade.js +5 -4
  108. package/src/actions/guides/polar/axes/labels.js +5 -4
  109. package/src/actions/guides/polar/axes/titles.js +5 -4
  110. package/src/actions/guides/polar/resolve.js +2 -1
  111. package/src/actions/guides/tickValues.js +7 -7
  112. package/src/actions/marks/arc/actions.js +5 -7
  113. package/src/actions/marks/area/actions.js +9 -3
  114. package/src/actions/marks/bar/create.js +4 -2
  115. package/src/actions/marks/bar/edit.js +9 -6
  116. package/src/actions/marks/bar/materialize.js +5 -3
  117. package/src/actions/marks/dependentReferences.js +87 -0
  118. package/src/actions/marks/index.js +2 -0
  119. package/src/actions/marks/lifecycle.js +2 -1
  120. package/src/actions/marks/line/actions.js +24 -6
  121. package/src/actions/marks/line/materialize.js +7 -3
  122. package/src/actions/marks/point/create.js +5 -4
  123. package/src/actions/marks/point/edit.js +6 -3
  124. package/src/actions/marks/point/jitter.js +6 -6
  125. package/src/actions/marks/point/materialize.js +36 -39
  126. package/src/actions/marks/rect/actions.js +16 -4
  127. package/src/actions/marks/references.js +183 -35
  128. package/src/actions/marks/rule/actions.js +20 -9
  129. package/src/actions/marks/shared.js +2 -1
  130. package/src/actions/marks/text/actions.js +10 -3
  131. package/src/actions/marks/tick/actions.js +11 -2
  132. package/src/actions/primitives/editGraphics.js +28 -3
  133. package/src/actions/primitives/index.js +9 -0
  134. package/src/actions/primitives/semanticAction.js +13 -7
  135. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  136. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  137. package/src/actions/regression/create.js +6 -1
  138. package/src/actions/regression/edit.js +10 -1
  139. package/src/actions/scales/channels.js +1 -1
  140. package/src/actions/scales/consumers/common.js +19 -1
  141. package/src/actions/scales/consumers/families.js +2 -2
  142. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  143. package/src/actions/scales/edit.js +11 -0
  144. package/src/actions/scales/editPolicy.js +5 -3
  145. package/src/actions/scales/preview.js +2 -1
  146. package/src/actions/scales/quantitativeColor.js +2 -4
  147. package/src/actions/selection/actions.js +10 -1
  148. package/src/actions/theme/reconcile.js +37 -9
  149. package/src/core/compositionState.js +26 -7
  150. package/src/core/datasetReferences.js +17 -0
  151. package/src/core/facetAxisPolicy.js +30 -0
  152. package/src/core/immutable.js +1 -0
  153. package/src/core/resourceReferences.js +5 -3
  154. package/src/core/validation.js +4 -0
  155. package/src/core/vocabulary.js +24 -2
  156. package/src/grammar/aggregate.js +1 -0
  157. package/src/grammar/arcs.js +1 -1
  158. package/src/grammar/areaSeries.js +1 -1
  159. package/src/grammar/axisTextPlacement.js +1 -1
  160. package/src/grammar/bars/policy.js +2 -2
  161. package/src/grammar/curveCommands.js +35 -2
  162. package/src/grammar/datasetDependencies.js +97 -0
  163. package/src/grammar/datasetSchema.js +32 -2
  164. package/src/grammar/facetHeaders.js +1 -0
  165. package/src/grammar/facets/dependencies.js +6 -2
  166. package/src/grammar/facets/guides.js +7 -5
  167. package/src/grammar/facets/legendIntent.js +6 -2
  168. package/src/grammar/facets/scales.js +27 -7
  169. package/src/grammar/flatten.js +85 -0
  170. package/src/grammar/histogram.js +13 -1
  171. package/src/grammar/horizon.js +1 -494
  172. package/src/grammar/jitter.js +2 -2
  173. package/src/grammar/legendSpacing.js +45 -0
  174. package/src/grammar/lineMissing.js +90 -0
  175. package/src/grammar/lineSeries.js +1 -376
  176. package/src/grammar/lookup.js +97 -0
  177. package/src/grammar/markFilter.js +4 -2
  178. package/src/grammar/markLabels.js +8 -3
  179. package/src/grammar/numeric.js +1 -1
  180. package/src/grammar/pathCommands.js +1 -1
  181. package/src/grammar/pathSeries.js +1 -109
  182. package/src/grammar/pointPolygon.js +81 -0
  183. package/src/grammar/pointShapes.js +15 -15
  184. package/src/grammar/rects.js +1 -1
  185. package/src/grammar/regression/derive.js +1 -195
  186. package/src/grammar/regression/parameters.js +7 -0
  187. package/src/grammar/scales/appearance.js +1 -1
  188. package/src/grammar/scales/color.js +1 -1
  189. package/src/grammar/scales/colorConsumers.js +1 -63
  190. package/src/grammar/scales/continuous.js +58 -1
  191. package/src/grammar/scales/definition.js +1 -1
  192. package/src/grammar/scales/ordinal.js +5 -2
  193. package/src/grammar/scales/policies.js +3 -3
  194. package/src/grammar/schemas/graphicBounds.js +40 -12
  195. package/src/grammar/schemas/graphicClip.js +23 -0
  196. package/src/grammar/schemas/graphicTree.js +16 -0
  197. package/src/grammar/statisticalReference.js +22 -0
  198. package/src/grammar/text.js +65 -1
  199. package/src/grammar/ticks.js +92 -1
  200. package/src/grammar/timeUnit.js +9 -3
  201. package/src/grammar/transformTopology.js +3 -1
  202. package/src/grammar/transforms.js +24 -0
  203. package/src/inspection.js +1 -495
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -1
  206. package/src/layout/facetContentFlow.js +101 -0
  207. package/src/layout/facetHeaderTitles.js +1 -0
  208. package/src/layout/facetTopLane.js +29 -0
  209. package/src/layout/facets.js +1 -1
  210. package/src/layout/guideCollisions.js +9 -3
  211. package/src/layout/legendGroup.js +1 -1
  212. package/src/layout/legendItems.js +26 -10
  213. package/src/layout/legendSpacing.js +66 -0
  214. package/src/layout/plotDrawOrder.js +17 -0
  215. package/src/materialization/bars/histogram.js +8 -2
  216. package/src/materialization/bars/resolve.js +1 -1
  217. package/src/materialization/composition.js +1 -0
  218. package/src/materialization/compositionSnapshot.js +22 -2
  219. package/src/materialization/dataProvenance.js +1 -1
  220. package/src/materialization/facetContentBounds.js +96 -0
  221. package/src/materialization/facetGuides/layout.js +1 -1
  222. package/src/materialization/facetGuides/placement.js +39 -11
  223. package/src/materialization/facetGuides/preparation.js +43 -24
  224. package/src/materialization/facetHeaders.js +1 -1
  225. package/src/materialization/facetTopLane.js +25 -0
  226. package/src/materialization/facets.js +1 -1
  227. package/src/materialization/graphicHierarchy.js +30 -1
  228. package/src/materialization/graphicTranslation.js +2 -0
  229. package/src/materialization/guides/layout.js +1 -1
  230. package/src/materialization/guides/resources.js +1 -1
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/markClip.js +26 -0
  233. package/src/materialization/marks/capabilities.js +3 -2
  234. package/src/materialization/marks/grouping.js +7 -5
  235. package/src/materialization/marks/index.js +4 -2
  236. package/src/materialization/marks/policies.js +4 -0
  237. package/src/materialization/plotDrawOrder.js +44 -0
  238. package/src/materialization/rect.js +11 -3
  239. package/src/materialization/scaleGuideDependencies.js +13 -5
  240. package/src/materialization/selection/items/common.js +1 -1
  241. package/src/materialization/selection/items/path.js +1 -0
  242. package/src/materialization/selection/policies/index.js +2 -1
  243. package/src/materialization/text.js +10 -5
  244. package/src/mcp/cli.js +0 -0
  245. package/src/persistence/validation.js +114 -18
  246. package/src/persistence.js +4 -2
  247. package/src/renderers/canvas/index.js +16 -0
  248. package/src/renderers/canvas/isolated.js +11 -1
  249. package/src/renderers/canvas/native.js +7 -0
  250. package/src/renderers/canvas/scene.js +66 -0
  251. package/src/renderers/canvas/text.js +3 -2
  252. package/src/renderers/svg.js +1 -617
  253. package/src/renderers/svgSerialization.js +643 -0
  254. package/src/renderers/transitionScene.js +67 -0
  255. package/src/scene.js +89 -0
  256. package/src/selectors/axes.js +1 -1
  257. package/src/selectors/datasets.js +1 -0
  258. package/src/selectors/grids.js +9 -0
  259. package/src/selectors/markClip.js +28 -0
  260. package/src/selectors/raincloud.js +7 -0
  261. package/src/version.js +1 -1
  262. package/types/basic.d.ts +7 -0
  263. package/types/index.d.ts +8 -0
  264. package/types/program.d.ts +286 -70
  265. package/types/scene.d.ts +35 -0
@@ -1,7 +1,10 @@
1
+ import {resolveDatasetTransformInputs} from "./shared.js";
1
2
  import { action, closedAction } from "../../core/action.js";
2
3
  import { isPlainObject } from "../../core/immutable.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
4
5
  import { validateKeys } from "../../core/validation.js";
6
+ import {orderedDatasetDependents} from "../../grammar/datasetDependencies.js";
7
+ import {rebindTransformDatasetInputs} from "../../core/datasetReferences.js";
5
8
  import {
6
9
  findTransformPolicy,
7
10
  normalizeDatasetTransformEdit,
@@ -33,6 +36,8 @@ const CREATOR_BY_TYPE = Object.freeze({
33
36
  density: "createDensityData",
34
37
  ecdf: "createECDFData",
35
38
  filter: "filterData",
39
+ flatten: "createFlattenData",
40
+ lookup:"createLookupData",
36
41
  fold: "createFoldData",
37
42
  impute: "createImputedData",
38
43
  interval: "createIntervalData",
@@ -53,6 +58,8 @@ const REVISION_ROLE = Object.freeze({
53
58
  density: "DensityData",
54
59
  ecdf: "ECDFData",
55
60
  filter: "FilteredData",
61
+ flatten: "FlattenData",
62
+ lookup:"LookupData",
56
63
  fold: "FoldData",
57
64
  impute: "ImputedData",
58
65
  interval: "IntervalData",
@@ -168,30 +175,9 @@ export function normalizeDerivedDependentPolicy(value) {
168
175
  }
169
176
 
170
177
  function downstreamDatasets(program, root) {
171
- const order = new Map(
172
- program.semanticSpec.datasets.map((dataset, index) => [dataset.id, index])
173
- );
174
- const bySource = new Map();
175
- for (const dataset of program.semanticSpec.datasets) {
176
- if (dataset.source === undefined) continue;
177
- const children = bySource.get(dataset.source) ?? [];
178
- children.push(dataset);
179
- bySource.set(dataset.source, children);
180
- }
181
- for (const children of bySource.values()) {
182
- children.sort((left, right) => order.get(left.id) - order.get(right.id));
183
- }
184
-
185
- const result = [];
186
- const visiting = new Set();
187
- const visited = new Set();
188
- function visit(id) {
189
- if (visiting.has(id)) {
190
- throw new Error(`Derived dataset dependency graph contains a cycle at "${id}".`);
191
- }
192
- if (visited.has(id)) return;
193
- visiting.add(id);
194
- for (const child of bySource.get(id) ?? []) {
178
+ // Statistical reference data is an owned cache; its mark lifecycle replays it.
179
+ const result=orderedDatasetDependents(program.semanticSpec.datasets,root,{skip:dataset=>dataset.transform?.[0]?.type==="statisticalReference"});
180
+ for(const child of result){
195
181
  if (child.transform?.length !== 1) {
196
182
  throw new Error(
197
183
  `Derived dataset "${child.id}" requires one replayable transform.`
@@ -204,16 +190,7 @@ function downstreamDatasets(program, root) {
204
190
  `"${child.transform[0].type ?? "unknown"}".`
205
191
  );
206
192
  }
207
- // Statistical reference data is an owned cache. Its mark lifecycle
208
- // reconnects and recomputes it after the visible source mark is rebound.
209
- if (child.transform[0].type === "statisticalReference") continue;
210
- result.push(child);
211
- visit(child.id);
212
- }
213
- visiting.delete(id);
214
- visited.add(id);
215
193
  }
216
- visit(root);
217
194
  return result;
218
195
  }
219
196
 
@@ -260,6 +237,7 @@ function buildRevisionPlan(
260
237
  `Derived dataset "${resolved.owner}" cannot use one of its dependents as source.`
261
238
  );
262
239
  }
240
+ if(definition?.type==="lookup"&&[resolved.current,...downstream.map(d=>d.id)].includes(definition.lookup.data))throw new Error(`Derived dataset "${resolved.owner}" cannot use its own result or dependent as lookup data.`);
263
241
  const reserved = new Set(resolved.revision === undefined ? [] : [resolved.revision]);
264
242
  const revisions = [];
265
243
  const replacements = new Map();
@@ -273,7 +251,7 @@ function buildRevisionPlan(
273
251
  old: dataset.id,
274
252
  id,
275
253
  source,
276
- transform,
254
+ transform:rebindTransformDatasetInputs(transform,replacements),
277
255
  family: ownerRecord?.family,
278
256
  owner: ownerRecord?.owner
279
257
  };
@@ -422,7 +400,7 @@ export const editDerivedData = /* @__PURE__ */ closedAction(
422
400
  args.target,
423
401
  args.definition.type
424
402
  );
425
- const definition = requestedDatasetTransform(args.definition);
403
+ const definition = resolveDatasetTransformInputs(this,requestedDatasetTransform(args.definition));
426
404
  findTransformPolicy(definition.type).validate(definition);
427
405
  const previous = requestedDatasetTransform(resolved.dataset.transform[0]);
428
406
  if (sameRequested(definition, previous)) return this;
@@ -477,6 +455,12 @@ export const editComputedData = /* @__PURE__ */ focusedEditor(
477
455
  export const editFilteredData = /* @__PURE__ */ focusedEditor(
478
456
  "editFilteredData", "filter", ["field", "oneOf", "noneOf", "predicate", "range", "nulls"]
479
457
  );
458
+ export const editFlattenData = /* @__PURE__ */ focusedEditor(
459
+ "editFlattenData", "flatten", ["fields", "as"]
460
+ );
461
+ export const editLookupData=/* @__PURE__ */focusedEditor(
462
+ "editLookupData","lookup",["field","lookup"]
463
+ );
480
464
  export const editFoldData = /* @__PURE__ */ focusedEditor(
481
465
  "editFoldData", "fold", ["fields", "as"]
482
466
  );
@@ -492,7 +476,7 @@ export const editBinData = /* @__PURE__ */ focusedEditor(
492
476
  export const editTimeUnitData = /* @__PURE__ */ focusedEditor(
493
477
  "editTimeUnitData", "timeUnit", [
494
478
  "field", "unit", "as", "temporalUnit", "timeZone", "weekStartsOn",
495
- "weekRule"
479
+ "weekRule", "missing"
496
480
  ]
497
481
  );
498
482
  export const editWindowData = /* @__PURE__ */ focusedEditor(
@@ -549,6 +533,8 @@ export const EDIT_DERIVED_DATA_ACTIONS = Object.freeze({
549
533
  editDerivedData,
550
534
  editComputedData,
551
535
  editFilteredData,
536
+ editFlattenData,
537
+ editLookupData,
552
538
  editFoldData,
553
539
  editSummaryData,
554
540
  editBinData,
@@ -1,376 +1 @@
1
- import { closedAction } from "../../core/action.js";
2
- import { validateUserId } from "../../core/identifiers.js";
3
-
4
- import { withPreviewDatasetValues } from
5
- "../primitives/semanticAction.js";
6
- import {
7
- deriveFilteredRows,
8
- normalizeFilterTransform
9
- } from "../../grammar/filter.js";
10
- import {
11
- deriveMarkFilteredRows,
12
- markFilterSelectors,
13
- normalizeMarkFilterTransform
14
- } from "../../grammar/markFilter.js";
15
- import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
16
- import { findSelectionPolicy } from
17
- "../../materialization/selection/policies/index.js";
18
- import {
19
- applyLayerDataRematerialization,
20
- applyLayerEmptyDataView
21
- } from "../../materialization/dependencies.js";
22
- import {
23
- hasDataset,
24
- findDataset,
25
- resolveDatasetReference,
26
- requireLayer,
27
- resolveEligibleLayer
28
- } from "../../selectors/index.js";
29
- import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
30
- import {
31
- assertFieldsAvailable,
32
- deriveTransformSchema,
33
- transformInputFields
34
- } from "../../grammar/datasetSchema.js";
35
-
36
- const OPTIONS = Object.freeze([
37
- "id", "source", "field", "oneOf", "noneOf", "predicate", "range", "nulls"
38
- ]);
39
- const MARK_SELECTOR_KEYS = Object.freeze([
40
- "grain", "field", "channel", "property", "op", "value", "values",
41
- "min", "max", "inclusive", "count", "groupBy", "ties"
42
- ]);
43
- const MARK_OPTIONS = Object.freeze(["target", "mode", ...MARK_SELECTOR_KEYS]);
44
- const REMOVE_MARK_FILTER_OPTIONS = Object.freeze(["target"]);
45
- const EMPTY_MARK_OPTIONS = Object.freeze(["id"]);
46
-
47
- function retainedHistogramBoundaries(layer, items) {
48
- if (layer.mark?.type !== "bar" || layer.encoding?.x?.bin === undefined) {
49
- return undefined;
50
- }
51
- const boundaries = [...new Set(items.flatMap(item => [
52
- item.channels.x,
53
- item.channels.x2
54
- ]).filter(Number.isFinite))].sort((left, right) => left - right);
55
- return boundaries.length < 2 ? undefined : boundaries;
56
- }
57
-
58
- export const materializeFilteredData = /* @__PURE__ */ closedAction(
59
- { op: "materializeFilteredData", description: "Materialize one filtered derived dataset." }, MATERIALIZE_OPTIONS,
60
- function (args = {}) {
61
- const { id, source, transform } = requireDerivedDataset(
62
- this,
63
- args.id,
64
- "filter"
65
- );
66
- assertFieldsAvailable(source.schema, transformInputFields(transform), {
67
- data: source.id,
68
- operation: "materializeFilteredData"
69
- });
70
- const values = deriveFilteredRows(source.values, transform);
71
- const schema = deriveTransformSchema(source.schema, transform, values, {
72
- sourceId: source.id,
73
- ownerId: id
74
- });
75
- return this.editSemantic({ property: `dataset[${id}].values`, value: values });
76
- }
77
- );
78
-
79
- export const filterData = /* @__PURE__ */ closedAction(
80
- { op: "filterData", description: "Create a named dataset from one field filter." }, OPTIONS,
81
- function (args = {}) {
82
- const id = validateUserId(args.id, "Filtered dataset id");
83
- const requestedSource = validateUserId(
84
- args.source ?? this.context.currentData,
85
- "Source dataset id"
86
- );
87
- const source = resolveDatasetReference(
88
- this,
89
- requestedSource,
90
- "Filter source dataset"
91
- ).id;
92
- const transform = normalizeFilterTransform(args);
93
- const next = this
94
- .createDerivedData({
95
- id,
96
- source,
97
- transform: [transform]
98
- })
99
- .materializeFilteredData({ id });
100
- return this.actionStack.length === 1
101
- ? next._withMaterializationConfig(
102
- ["data", "filter", id],
103
- { current: id }
104
- )
105
- : next;
106
- }
107
- );
108
-
109
- export const materializeMarkFilteredData = /* @__PURE__ */ closedAction(
110
- {
111
- op: "materializeMarkFilteredData",
112
- description: "Materialize member rows retained by one final-item mark selection."
113
- }, MATERIALIZE_OPTIONS,
114
- function (args = {}) {
115
- const { id, dataset, source, transform } = requireDerivedDataset(
116
- this,
117
- args.id,
118
- "markFilter"
119
- );
120
- const layer = requireLayer(this, transform.target);
121
- if (layer?.data !== dataset.source) {
122
- throw new Error(
123
- `Mark filter target "${transform.target}" must still use source dataset "${dataset.source}".`
124
- );
125
- }
126
- let preview = this;
127
- let currentRows = source.values;
128
- const selectors = markFilterSelectors(transform);
129
- for (const [index, selector] of selectors.entries()) {
130
- const resolved = resolveMarkFilterSelection(
131
- preview,
132
- transform.target,
133
- selector
134
- );
135
- currentRows = deriveMarkFilteredRows(
136
- currentRows,
137
- resolved.items,
138
- resolved.keys
139
- );
140
- if (index < selectors.length - 1) {
141
- preview = withPreviewDatasetValues(preview, {
142
- id,
143
- values: currentRows,
144
- target: transform.target
145
- });
146
- preview = currentRows.length === 0
147
- ? applyLayerEmptyDataView(preview, transform.target)
148
- : applyLayerDataRematerialization(preview, transform.target);
149
- currentRows = findDataset(preview, id).values;
150
- }
151
- }
152
- if (selectors.length > 1) {
153
- preview = withPreviewDatasetValues(preview, {
154
- id,
155
- omitValues: true
156
- });
157
- }
158
- let next = preview.editSemantic({
159
- property: `dataset[${id}].values`,
160
- value: currentRows
161
- });
162
- if (requireLayer(next, transform.target).data !== id) {
163
- next = next.editSemantic({
164
- property: `layer[${transform.target}].data`,
165
- value: id
166
- });
167
- }
168
- next = next._withMarkConfig(transform.target, {
169
- ...next.markConfigs[transform.target],
170
- markFilter: {
171
- ...next.markConfigs[transform.target].markFilter,
172
- empty: currentRows.length === 0
173
- }
174
- });
175
- return currentRows.length === 0
176
- ? next.materializeEmptyMark({ id: transform.target })
177
- : applyLayerDataRematerialization(next, transform.target);
178
- }
179
- );
180
-
181
- export const materializeEmptyMark = /* @__PURE__ */ closedAction(
182
- {
183
- op: "materializeEmptyMark",
184
- description: "Clear one active empty-filter mark without changing its domains."
185
- }, EMPTY_MARK_OPTIONS,
186
- function (args = {}) {
187
- const id = validateUserId(args.id, "Empty mark id");
188
- const layer = requireLayer(this, id);
189
- const owner = this.markConfigs[id]?.markFilter;
190
- const dataset = findDataset(this, layer.data);
191
- if (owner?.empty !== true || dataset?.values?.length !== 0) {
192
- throw new Error(`Mark "${id}" does not own an active empty filter.`);
193
- }
194
- return applyLayerEmptyDataView(this, id);
195
- }
196
- );
197
-
198
- function activeMarkFilter(program, layer) {
199
- const configured = program.markConfigs[layer.id]?.markFilter;
200
- const dataset = findDataset(program, layer.data);
201
- const transform = dataset?.transform?.[0];
202
- if (
203
- dataset?.source === undefined ||
204
- transform?.type !== "markFilter" ||
205
- transform.target !== layer.id
206
- ) {
207
- return undefined;
208
- }
209
- return {
210
- dataset: dataset.id,
211
- source: dataset.source,
212
- selectors: markFilterSelectors(transform),
213
- ...(configured?.originalBin === undefined
214
- ? {}
215
- : { originalBin: configured.originalBin })
216
- };
217
- }
218
-
219
- function withoutMarkFilterConfig(program, id) {
220
- const config = program.markConfigs[id] ?? {};
221
- const { markFilter: unused, ...remaining } = config;
222
- void unused;
223
- return program._withMarkConfig(id, remaining);
224
- }
225
-
226
- function restoreHistogramBin(program, layer, originalBin) {
227
- let next = program;
228
- const current = layer.encoding?.x?.bin ?? {};
229
- for (const property of ["maxBins", "step", "boundaries"]) {
230
- if (Object.hasOwn(originalBin, property)) {
231
- next = next.editSemantic({
232
- property: `layer[${layer.id}].encoding.x.bin.${property}`,
233
- value: originalBin[property]
234
- });
235
- } else if (Object.hasOwn(current, property)) {
236
- next = next.editSemantic({
237
- property: `layer[${layer.id}].encoding.x.bin.${property}`,
238
- remove: true
239
- });
240
- }
241
- }
242
- return next;
243
- }
244
-
245
- function removeActiveMarkFilter(program, layer, owner) {
246
- let next = program.editSemantic({
247
- property: `layer[${layer.id}].data`,
248
- value: owner.source
249
- });
250
- if (owner.originalBin !== undefined) {
251
- next = restoreHistogramBin(next, requireLayer(next, layer.id), owner.originalBin);
252
- }
253
- next = withoutMarkFilterConfig(next, layer.id)
254
- .releaseDerivedData({ id: owner.dataset })
255
- ._withContext({ currentData: owner.source });
256
- return applyLayerDataRematerialization(next, layer.id);
257
- }
258
-
259
- function nextMarkFilterDatasetId(program, layerId) {
260
- const base = `${layerId}FilteredData`;
261
- if (!hasDataset(program, base)) return base;
262
- let revision = 2;
263
- while (hasDataset(program, `${base}${revision}`)) revision += 1;
264
- return `${base}${revision}`;
265
- }
266
-
267
- function sameSelector(left, right) {
268
- return JSON.stringify(left) === JSON.stringify(right);
269
- }
270
-
271
- export const removeMarkFilter = /* @__PURE__ */ closedAction(
272
- {
273
- op: "removeMarkFilter",
274
- description: "Remove one active final-item filter and restore its source."
275
- }, REMOVE_MARK_FILTER_OPTIONS,
276
- function (args = {}) {
277
- const target = args.target === undefined
278
- ? undefined
279
- : validateUserId(args.target, "Mark filter target id");
280
- const layer = resolveEligibleLayer(this, {
281
- target,
282
- label: "mark filter",
283
- predicate: candidate => activeMarkFilter(this, candidate) !== undefined
284
- });
285
- return removeActiveMarkFilter(this, layer, activeMarkFilter(this, layer));
286
- }
287
- );
288
-
289
- export const filterMarks = /* @__PURE__ */ closedAction(
290
- {
291
- op: "filterMarks",
292
- description: "Retain selected final mark items through immutable derived data."
293
- }, MARK_OPTIONS,
294
- function (args = {}) {
295
- if (args.mode !== undefined && !["replace", "compose"].includes(args.mode)) {
296
- throw new Error(`Unknown mark filter mode "${args.mode}".`);
297
- }
298
- const target = args.target === undefined
299
- ? undefined
300
- : validateUserId(args.target, "Filter mark target id");
301
- const layer = resolveEligibleLayer(this, {
302
- target,
303
- label: "filter mark",
304
- predicate: candidate =>
305
- candidate.data !== undefined &&
306
- findSelectionPolicy(candidate.mark?.type) !== undefined
307
- });
308
- if (this.markConfigs[layer.id]?.itemFilterable === false) {
309
- throw new Error(
310
- `filterMarks does not support composite mark "${layer.id}"; filter its source dataset first.`
311
- );
312
- }
313
- const owner = activeMarkFilter(this, layer);
314
- const selector = Object.fromEntries(
315
- MARK_SELECTOR_KEYS.flatMap(key =>
316
- Object.hasOwn(args, key) ? [[key, args[key]]] : []
317
- )
318
- );
319
- const resolved = resolveMarkFilterSelection(this, layer.id, selector);
320
- const previousSelector = owner?.selectors.at(-1);
321
- if (previousSelector !== undefined && sameSelector(previousSelector, resolved.selector)) {
322
- return this;
323
- }
324
- if (owner !== undefined && args.mode === undefined) {
325
- throw new Error(
326
- "Repeated filterMarks requires mode \"replace\" or \"compose\"."
327
- );
328
- }
329
- const selectors = owner === undefined || args.mode === "replace"
330
- ? [resolved.selector]
331
- : [...owner.selectors, resolved.selector];
332
- let next = owner === undefined
333
- ? this
334
- : removeActiveMarkFilter(this, layer, owner);
335
- const baseLayer = requireLayer(next, layer.id);
336
- const first = resolveMarkFilterSelection(next, layer.id, selectors[0]);
337
- const transform = normalizeMarkFilterTransform(layer.id, selectors);
338
- const boundaries = retainedHistogramBoundaries(baseLayer, first.items);
339
- const originalBin = baseLayer.mark?.type === "bar" &&
340
- baseLayer.encoding?.x?.bin !== undefined
341
- ? baseLayer.encoding.x.bin
342
- : undefined;
343
- const derivedId = nextMarkFilterDatasetId(next, layer.id);
344
- next = next
345
- .createDerivedData({
346
- id: derivedId,
347
- source: baseLayer.data,
348
- transform: [transform]
349
- });
350
- if (boundaries !== undefined) {
351
- for (const property of ["maxBins", "step"]) {
352
- if (Object.hasOwn(baseLayer.encoding.x.bin, property)) {
353
- next = next.editSemantic({
354
- property: `layer[${baseLayer.id}].encoding.x.bin.${property}`,
355
- remove: true
356
- });
357
- }
358
- }
359
- next = next.editSemantic({
360
- property: `layer[${baseLayer.id}].encoding.x.bin.boundaries`,
361
- value: boundaries
362
- });
363
- }
364
- next = next._withMarkConfig(baseLayer.id, {
365
- ...next.markConfigs[baseLayer.id],
366
- markFilter: {
367
- dataset: derivedId,
368
- source: baseLayer.data,
369
- selectors,
370
- empty: false,
371
- ...(originalBin === undefined ? {} : { originalBin })
372
- }
373
- });
374
- return next.materializeMarkFilteredData({ id: derivedId });
375
- }
376
- );
1
+ import{findRaincloudParent as b}from"../../selectors/raincloud.js";import{filterRaincloud as T,materializeRaincloudFilter as B,removeRaincloudFilter as A}from"./raincloudFilter.js";import{closedAction as m}from"../../core/action.js";import{validateUserId as p}from"../../core/identifiers.js";import{withPreviewDatasetValues as z}from"../primitives/semanticAction.js";import{deriveFilteredRows as P,normalizeFilterTransform as L}from"../../grammar/filter.js";import{deriveMarkFilteredRows as N,markFilterSelectors as E,normalizeMarkFilterTransform as K}from"../../grammar/markFilter.js";import{resolveMarkFilterSelection as M}from"../../materialization/selection/filter.js";import{synchronizeBoxFilter as y}from"../boxPlots/components.js";import{findSelectionPolicy as q}from"../../materialization/selection/policies/index.js";import{applyLayerDataRematerialization as w,applyLayerEmptyDataView as $}from"../../materialization/dependencies.js";import{hasDataset as R,findDataset as O,resolveDatasetReference as V,requireLayer as h,resolveEligibleLayer as C}from"../../selectors/index.js";import{MATERIALIZE_OPTIONS as I,requireDerivedDataset as _}from"./shared.js";import{assertFieldsAvailable as H,deriveTransformSchema as J,transformInputFields as U}from"../../grammar/datasetSchema.js";const Y=Object.freeze(["id","source","field","oneOf","noneOf","predicate","range","nulls"]),x=Object.freeze(["grain","field","channel","property","op","value","values","min","max","inclusive","count","groupBy","ties"]),Z=Object.freeze(["target","mode",...x]),G=Object.freeze(["target"]),Q=Object.freeze(["id"]);function W(i,t){if(i.mark?.type!=="bar"||i.encoding?.x?.bin===void 0)return;const n=[...new Set(t.flatMap(e=>[e.channels.x,e.channels.x2]).filter(Number.isFinite))].sort((e,r)=>e-r);return n.length<2?void 0:n}const ke=m({op:"materializeFilteredData",description:"Materialize one filtered derived dataset."},I,function(i={}){const{id:t,source:n,transform:e}=_(this,i.id,"filter");H(n.schema,U(e),{data:n.id,operation:"materializeFilteredData"});const r=P(n.values,e),d=J(n.schema,e,r,{sourceId:n.id,ownerId:t});return this.editSemantic({property:`dataset[${t}].values`,value:r})}),ve=m({op:"filterData",description:"Create a named dataset from one field filter."},Y,function(i={}){const t=p(i.id,"Filtered dataset id"),n=p(i.source??this.context.currentData,"Source dataset id"),e=V(this,n,"Filter source dataset").id,r=L(i),d=this.createDerivedData({id:t,source:e,transform:[r]}).materializeFilteredData({id:t});return this.actionStack.length===1?d._withMaterializationConfig(["data","filter",t],{current:t}):d}),Fe=m({op:"materializeMarkFilteredData",description:"Materialize member rows retained by one final-item mark selection."},I,function(i={}){const{id:t,dataset:n,source:e,transform:r}=_(this,i.id,"markFilter"),d=b(this,r.target);if(d!==void 0)return B(this,{id:t,source:e,transform:r},d);const f=h(this,r.target);if(f?.data!==n.source)throw new Error(`Mark filter target "${r.target}" must still use source dataset "${n.source}".`);let s=this,c=e.values;const o=E(r);for(const[g,k]of o.entries()){const u=M(s,r.target,k);c=N(c,u.items,u.keys,f),g<o.length-1&&(s=z(s,{id:t,values:c,target:r.target}),s=y(s,r.target,t),s=c.length===0?$(s,r.target):w(s,r.target),c=O(s,t).values)}o.length>1&&(s=z(s,{id:t,omitValues:!0}));let a=s.editSemantic({property:`dataset[${t}].values`,value:c});return h(a,r.target).data!==t&&(a=a.editSemantic({property:`layer[${r.target}].data`,value:t})),a=a._withMarkConfig(r.target,{...a.markConfigs[r.target],markFilter:{...a.markConfigs[r.target].markFilter,empty:c.length===0}}),a=y(a,r.target,t),c.length===0?a.materializeEmptyMark({id:r.target}):w(a,r.target)}),be=m({op:"materializeEmptyMark",description:"Clear one active empty-filter mark without changing its domains."},Q,function(i={}){const t=p(i.id,"Empty mark id"),n=h(this,t),e=this.markConfigs[t]?.markFilter,r=O(this,n.data);if(e?.empty!==!0||r?.values?.length!==0)throw new Error(`Mark "${t}" does not own an active empty filter.`);return $(this,t)});function D(i,t){const n=i.markConfigs[t.id]?.markFilter,e=O(i,t.data),r=e?.transform?.[0];if(!(e?.source===void 0||r?.type!=="markFilter"||r.target!==t.id))return{dataset:e.id,source:e.source,selectors:E(r),...n?.originalBin===void 0?{}:{originalBin:n.originalBin}}}function X(i,t){const n=i.markConfigs[t]??{},{markFilter:e,...r}=n;return i._withMarkConfig(t,r)}function ee(i,t,n){let e=i;const r=t.encoding?.x?.bin??{};for(const d of["maxBins","step","boundaries"])Object.hasOwn(n,d)?e=e.editSemantic({property:`layer[${t.id}].encoding.x.bin.${d}`,value:n[d]}):Object.hasOwn(r,d)&&(e=e.editSemantic({property:`layer[${t.id}].encoding.x.bin.${d}`,remove:!0}));return e}function j(i,t,n){let e=y(i,t.id).editSemantic({property:`layer[${t.id}].data`,value:n.source});return n.originalBin!==void 0&&(e=ee(e,h(e,t.id),n.originalBin)),e=X(e,t.id).releaseDerivedData({id:n.dataset})._withContext({currentData:n.source}),w(e,t.id)}function te(i,t){const n=`${t}FilteredData`;if(!R(i,n))return n;let e=2;for(;R(i,`${n}${e}`);)e+=1;return`${n}${e}`}function re(i,t){return JSON.stringify(i)===JSON.stringify(t)}const Me=m({op:"removeMarkFilter",description:"Remove one active final-item filter and restore its source."},G,function(i={}){const t=i.target===void 0?void 0:p(i.target,"Mark filter target id"),n=b(this,t);if(n!==void 0)return A(this,n);const e=C(this,{target:t,label:"mark filter",predicate:r=>D(this,r)!==void 0});return j(this,e,D(this,e))}),ye=m({op:"filterMarks",description:"Retain selected final mark items through immutable derived data."},Z,function(i={}){if(i.mode!==void 0&&!["replace","compose"].includes(i.mode))throw new Error(`Unknown mark filter mode "${i.mode}".`);const t=i.target===void 0?void 0:p(i.target,"Filter mark target id"),n=b(this,t);if(n!==void 0){const l=Object.fromEntries(x.flatMap(F=>Object.hasOwn(i,F)?[[F,i[F]]]:[]));return T(this,i,n,l)}const e=C(this,{target:t,label:"filter mark",predicate:l=>l.data!==void 0&&q(l.mark?.type)!==void 0});if(this.markConfigs[e.id]?.itemFilterable===!1)throw new Error(`filterMarks does not support composite mark "${e.id}"; filter its source dataset first.`);const r=D(this,e),d=Object.fromEntries(x.flatMap(l=>Object.hasOwn(i,l)?[[l,i[l]]]:[])),f=M(this,e.id,d),s=r?.selectors.at(-1);if(s!==void 0&&re(s,f.selector))return this;if(r!==void 0&&i.mode===void 0)throw new Error('Repeated filterMarks requires mode "replace" or "compose".');const c=r===void 0||i.mode==="replace"?[f.selector]:[...r.selectors,f.selector];let o=r===void 0?this:j(this,e,r);const a=h(o,e.id),g=M(o,e.id,c[0]),k=K(e.id,c),u=W(a,g.items),S=a.mark?.type==="bar"&&a.encoding?.x?.bin!==void 0?a.encoding.x.bin:void 0,v=te(o,e.id);if(o=o.createDerivedData({id:v,source:a.data,transform:[k]}),u!==void 0){for(const l of["maxBins","step"])Object.hasOwn(a.encoding.x.bin,l)&&(o=o.editSemantic({property:`layer[${a.id}].encoding.x.bin.${l}`,remove:!0}));o=o.editSemantic({property:`layer[${a.id}].encoding.x.bin.boundaries`,value:u})}return o=o._withMarkConfig(a.id,{...o.markConfigs[a.id],markFilter:{dataset:v,source:a.data,selectors:c,empty:!1,...S===void 0?{}:{originalBin:S}}}),o.materializeMarkFilteredData({id:v})});export{ve as filterData,ye as filterMarks,be as materializeEmptyMark,ke as materializeFilteredData,Fe as materializeMarkFilteredData,Me as removeMarkFilter};
@@ -0,0 +1,19 @@
1
+ import { deriveFlattenRows, normalizeFlattenTransform } from "../../grammar/flatten.js";
2
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
3
+
4
+ const OPTIONS = Object.freeze(["id", "source", "fields", "as"]);
5
+ export const materializeFlattenData = /* @__PURE__ */ derivedMaterializer(
6
+ "materializeFlattenData",
7
+ "Materialize aligned array elements as immutable rows.",
8
+ "flatten",
9
+ deriveFlattenRows
10
+ );
11
+ export const createFlattenData = /* @__PURE__ */ derivedCreator(
12
+ "createFlattenData",
13
+ "Expand parallel array fields in source-row and array-index order.",
14
+ OPTIONS,
15
+ "Flatten dataset id",
16
+ "Flatten source dataset id",
17
+ normalizeFlattenTransform,
18
+ "materializeFlattenData"
19
+ );
@@ -41,6 +41,8 @@ import { createWindowData, materializeWindowData } from "./window.js";
41
41
  import { createSummaryData, materializeSummaryData } from "./summary.js";
42
42
  import { createBinData, materializeBinData } from "./bin.js";
43
43
  import { createFoldData, materializeFoldData } from "./fold.js";
44
+ import { createFlattenData, materializeFlattenData } from "./flatten.js";
45
+ import {createLookupData,materializeLookupData} from "./lookup.js";
44
46
  import { createComputedData, materializeComputedData } from "./computed.js";
45
47
  import {
46
48
  createNormalizedData,
@@ -89,6 +91,10 @@ export function registerDataActions(ProgramClass) {
89
91
  materializeSummaryData,
90
92
  createBinData,
91
93
  materializeBinData,
94
+ createFlattenData,
95
+ materializeFlattenData,
96
+ createLookupData,
97
+ materializeLookupData,
92
98
  createFoldData,
93
99
  materializeFoldData,
94
100
  createComputedData,
@@ -0,0 +1,50 @@
1
+ import { closedAction } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { deriveLookupRows, normalizeLookupTransform } from "../../grammar/lookup.js";
4
+ import {
5
+ deriveTransformSchema, datasetSchemaContext, validateRowsAgainstSchema
6
+ } from "../../grammar/datasetSchema.js";
7
+ import { orderedDatasetAncestors } from "../../grammar/datasetDependencies.js";
8
+ import { resolveDatasetReference, requireDataset } from "../../selectors/datasets.js";
9
+ import {
10
+ requireDerivedDataset, MATERIALIZE_OPTIONS, resolveDatasetTransformInputs
11
+ } from "./shared.js";
12
+
13
+ function lookupResult(program, source, transform, id) {
14
+ if (!Array.isArray(source.values)) throw new Error(`Source dataset "${source.id}" has no values.`);
15
+ const right = requireDataset(program, transform.lookup.data);
16
+ if (!Array.isArray(right.values)) throw new Error(`Lookup dataset "${right.id}" has no values.`);
17
+ const context = datasetSchemaContext(source, transform, id, reference => requireDataset(program, reference));
18
+ const schema = deriveTransformSchema(source.schema, transform, undefined, context);
19
+ const values = deriveLookupRows(source.values, right.values, transform);
20
+ validateRowsAgainstSchema(values, schema, `Lookup dataset "${id}"`);
21
+ return values;
22
+ }
23
+
24
+ export const materializeLookupData = /* @__PURE__ */ closedAction(
25
+ { op: "materializeLookupData", description: "Materialize one typed named-dataset lookup." },
26
+ MATERIALIZE_OPTIONS,
27
+ function(args = {}) {
28
+ const { id, source, transform } = requireDerivedDataset(this, args.id, "lookup");
29
+ orderedDatasetAncestors(this.semanticSpec.datasets, id);
30
+ const values = lookupResult(this, source, transform, id);
31
+ return this.editSemantic({ property: `dataset[${id}].values`, value: values });
32
+ }
33
+ );
34
+
35
+ export const createLookupData = /* @__PURE__ */ closedAction(
36
+ { op: "createLookupData", description: "Copy fields from one named dataset without changing primary rows." },
37
+ ["id", "source", "field", "lookup"],
38
+ function(args = {}) {
39
+ const id = validateUserId(args.id, "Lookup dataset id");
40
+ const source = resolveDatasetReference(this,
41
+ validateUserId(args.source ?? this.context.currentData, "Lookup source dataset id"),
42
+ "Lookup source dataset");
43
+ const transform = resolveDatasetTransformInputs(this, normalizeLookupTransform(args));
44
+ lookupResult(this, source, transform, id);
45
+ const standalone = this.actionStack.length === 1;
46
+ const next = this.createDerivedData({ id, source: source.id, transform: [transform] })
47
+ .materializeLookupData({ id });
48
+ return standalone ? next._withMaterializationConfig(["data", "lookup", id], { current: id }) : next;
49
+ }
50
+ );
@@ -0,0 +1,56 @@
1
+ import { findDataset, hasDataset } from "../../selectors/datasets.js";
2
+ import { findRaincloudParent } from "../../selectors/raincloud.js";
3
+ import { normalizeMarkSelector, selectMarkItemKeys } from "../../grammar/markSelection.js";
4
+ import { deriveMarkFilteredRows, normalizeMarkFilterTransform } from "../../grammar/markFilter.js";
5
+
6
+ export function deriveRaincloudFilteredRows(values, selectors) {
7
+ let rows = values;
8
+ for (const selector of selectors) {
9
+ if (selector.field === undefined || selector.grain !== "item") {
10
+ throw new Error("Raincloud parent filters require an item-grain observation field selector; use a child target for rendered channels or properties.");
11
+ }
12
+ const items = rows.map((row, index) => ({ key: String(index), fields: row,
13
+ channels: {}, properties: {}, members: [row] }));
14
+ rows = deriveMarkFilteredRows(rows, items, selectMarkItemKeys(items, selector));
15
+ }
16
+ return rows;
17
+ }
18
+
19
+ export function materializeRaincloudFilter(program, { id, source, transform }, owner) {
20
+ const values = deriveRaincloudFilteredRows(source.values, transform.selectors);
21
+ let next = program.editSemantic({ property: `dataset[${id}].values`, value: values });
22
+ next = next.editRaincloudPlot({ target: owner.id, data: id });
23
+ const revised = findRaincloudParent(next, owner.id);
24
+ return next._withMarkConfig(revised.ownerId, {
25
+ ...next.markConfigs[revised.ownerId], raincloudPlot: { ...revised,
26
+ sourceFilter: { dataset: id, source: source.id, selectors: transform.selectors } }
27
+ });
28
+ }
29
+
30
+ export function filterRaincloud(program, args, owner, selectorOptions) {
31
+ const selector = normalizeMarkSelector(selectorOptions);
32
+ const previous = owner.sourceFilter;
33
+ if (previous !== undefined && JSON.stringify(previous.selectors.at(-1)) === JSON.stringify(selector)) return program;
34
+ if (previous !== undefined && args.mode === undefined) {
35
+ throw new Error('Repeated filterMarks requires mode "replace" or "compose".');
36
+ }
37
+ const selectors = previous === undefined || args.mode === "replace"
38
+ ? [selector] : [...previous.selectors, selector];
39
+ const source = previous?.source ?? owner.source;
40
+ // Validate against observations before changing any owner or resource.
41
+ deriveRaincloudFilteredRows(findDataset(program, source).values, selectors);
42
+ const base = `${owner.id}FilteredData`;
43
+ let id = base;
44
+ for (let revision = 2; hasDataset(program, id); revision += 1) id = `${base}${revision}`;
45
+ let next = program.createDerivedData({ id, source,
46
+ transform: [normalizeMarkFilterTransform(owner.id, selectors)] })
47
+ .materializeMarkFilteredData({ id });
48
+ if (previous !== undefined) next = next.releaseDerivedData({ id: previous.dataset });
49
+ return next;
50
+ }
51
+
52
+ export function removeRaincloudFilter(program, owner) {
53
+ if (owner.sourceFilter === undefined) throw new Error(`Raincloud plot "${owner.id}" has no active mark filter.`);
54
+ return program.editRaincloudPlot({ target: owner.id, data: owner.sourceFilter.source })
55
+ .releaseDerivedData({ id: owner.sourceFilter.dataset });
56
+ }