ggaction 0.0.9 → 0.0.11

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 (222) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +5 -4
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. package/src/actions/errorBars/resolve.js +0 -25
@@ -2,8 +2,10 @@ import { action } from "../../core/action.js";
2
2
  import { validateKeys } from "../../core/validation.js";
3
3
  import { findLayer } from "../../selectors/layers.js";
4
4
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
5
- import { resolveErrorBarAppearance } from "./options.js";
6
- import { resolveErrorBar } from "./resolve.js";
5
+ import { createResolvedIntervalData, ownOptions } from
6
+ "../data/intervalEdit.js";
7
+ import { resolveIntervalComposite } from "../intervals/resolve.js";
8
+ import { errorBarCaps, resolveErrorBarAppearance } from "./edit.js";
7
9
 
8
10
  const OPTIONS = Object.freeze([
9
11
  "id",
@@ -21,13 +23,34 @@ const OPTIONS = Object.freeze([
21
23
  "opacity"
22
24
  ]);
23
25
 
26
+ const ERROR_BAR_POLICY = Object.freeze({
27
+ operation: "createErrorBar",
28
+ resourceLabel: "error-bar",
29
+ defaultId: "errorBar",
30
+ ownerLabel: "Error-bar id",
31
+ positionTypes: Object.freeze(["nominal", "ordinal", "temporal"]),
32
+ defaultPositionType: "nominal",
33
+ defaultIntervalChannel: "y",
34
+ scaleDefaults: () => ({}),
35
+ intervalScaleDefaults: Object.freeze({ nice: true, zero: false }),
36
+ allowExplicitGrouping: false,
37
+ ambiguousMessage:
38
+ "createErrorBar requires one quantitative interval axis and one nominal, ordinal, or temporal position axis."
39
+ });
40
+
41
+ function resolveErrorBar(program, args) {
42
+ const resolved = resolveIntervalComposite(program, args, ERROR_BAR_POLICY);
43
+ return {
44
+ ...resolved,
45
+ lowerCapId: `${resolved.id}LowerCap`,
46
+ upperCapId: `${resolved.id}UpperCap`
47
+ };
48
+ }
49
+
24
50
  function resolveAppearance(args) {
25
- const appearanceArgs = Object.fromEntries(
26
- ["caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"]
27
- .filter(key => Object.hasOwn(args, key))
28
- .map(key => [key, args[key]])
29
- );
30
- return resolveErrorBarAppearance(appearanceArgs, {
51
+ return resolveErrorBarAppearance(ownOptions(args, [
52
+ "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"
53
+ ]), {
31
54
  defaults: {
32
55
  caps: true,
33
56
  capSize: 8,
@@ -40,6 +63,51 @@ function resolveAppearance(args) {
40
63
  });
41
64
  }
42
65
 
66
+ export const createErrorBarCap = action(
67
+ {
68
+ op: "createErrorBarCap",
69
+ description: "Create one fixed-pixel error-bar cap."
70
+ },
71
+ function (args = {}) {
72
+ validateKeys(args, [
73
+ "id", "data", "orientation", "positionField", "positionFieldType",
74
+ "intervalField", "coordinate", "positionScale", "intervalScale",
75
+ "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"
76
+ ], "createErrorBarCap");
77
+ if (!["vertical", "horizontal"].includes(args.orientation)) {
78
+ throw new Error(`Unsupported error-bar orientation "${args.orientation}".`);
79
+ }
80
+ const vertical = args.orientation === "vertical";
81
+ const positionAction = vertical ? "encodeX" : "encodeY";
82
+ const intervalAction = vertical ? "encodeY" : "encodeX";
83
+ return this
84
+ .createRuleMark({ id: args.id, data: args.data })
85
+ [positionAction]({
86
+ target: args.id,
87
+ field: args.positionField,
88
+ fieldType: args.positionFieldType,
89
+ coordinate: args.coordinate,
90
+ scale: { id: args.positionScale }
91
+ })
92
+ [intervalAction]({
93
+ target: args.id,
94
+ field: args.intervalField,
95
+ fieldType: "quantitative",
96
+ coordinate: args.coordinate,
97
+ scale: { id: args.intervalScale }
98
+ })
99
+ .encodeStroke({ target: args.id, value: args.stroke })
100
+ .encodeStrokeWidth({ target: args.id, value: args.strokeWidth })
101
+ .encodeStrokeDash({ target: args.id, value: args.strokeDash })
102
+ .encodeOpacity({ target: args.id, value: args.opacity })
103
+ .materializeRuleSpan({
104
+ id: args.id,
105
+ orientation: vertical ? "horizontal" : "vertical",
106
+ size: args.capSize
107
+ });
108
+ }
109
+ );
110
+
43
111
  function positionArgs(resolved) {
44
112
  return {
45
113
  target: resolved.id,
@@ -69,21 +137,7 @@ export const createErrorBar = action(
69
137
  validateKeys(args, OPTIONS, "createErrorBar");
70
138
  const resolved = resolveErrorBar(this, args);
71
139
  const appearance = resolveAppearance(args);
72
- let next = this;
73
-
74
- if (resolved.interval.mode === "statistical") {
75
- next = next.createIntervalData({
76
- id: resolved.dataId,
77
- source: resolved.source,
78
- field: resolved.interval.field,
79
- groupBy: resolved.groupBy,
80
- center: resolved.interval.center,
81
- extent: resolved.interval.extent,
82
- level: resolved.interval.level,
83
- as: resolved.fields
84
- });
85
- }
86
-
140
+ let next = createResolvedIntervalData(this, resolved);
87
141
  next = next.createRuleMark({ id: resolved.id, data: resolved.dataId });
88
142
  const positionAction = resolved.position.channel === "x" ? "encodeX" : "encodeY";
89
143
  const intervalAction = resolved.interval.channel === "x" ? "encodeX" : "encodeY";
@@ -108,10 +162,7 @@ export const createErrorBar = action(
108
162
 
109
163
  const intervalLayer = findLayer(next, resolved.id);
110
164
  if (appearance.caps) {
111
- for (const [id, field] of [
112
- [resolved.lowerCapId, resolved.fields.lower],
113
- [resolved.upperCapId, resolved.fields.upper]
114
- ]) {
165
+ for (const [id, field] of errorBarCaps(resolved)) {
115
166
  next = next.createErrorBarCap({
116
167
  id,
117
168
  data: resolved.dataId,
@@ -1,26 +1,77 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
- import { validateKeys } from "../../core/validation.js";
4
- import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
5
- import { planIntervalEdit } from "../data/intervalEdit.js";
6
3
  import {
7
- ERROR_BAR_APPEARANCE_OPTIONS,
8
- ERROR_BAR_EDIT_OPTIONS,
9
- resolveErrorBarAppearance
10
- } from "./options.js";
4
+ validateKeys,
5
+ validatePositiveFinite
6
+ } from "../../core/validation.js";
7
+ import {
8
+ normalizeStrokeDashPattern,
9
+ validateOpacityValue
10
+ } from "../../grammar/scales/index.js";
11
+ import {
12
+ validateRuleStroke,
13
+ validateRuleStrokeWidth
14
+ } from "../../grammar/ruleAppearance.js";
15
+ import { findLayer } from "../../selectors/layers.js";
16
+ import {
17
+ applyIntervalRevision,
18
+ ownOptions,
19
+ planIntervalEdit,
20
+ releaseIntervalRevision,
21
+ resolveIntervalOwner
22
+ } from "../data/intervalEdit.js";
23
+
24
+ export const ERROR_BAR_EDIT_OPTIONS = Object.freeze([
25
+ "target", "caps", "capSize", "stroke", "strokeWidth",
26
+ "strokeDash", "opacity", "statistics"
27
+ ]);
28
+
29
+ export const ERROR_BAR_APPEARANCE_OPTIONS = Object.freeze(
30
+ ERROR_BAR_EDIT_OPTIONS.filter(option =>
31
+ option !== "target" && option !== "statistics"
32
+ )
33
+ );
34
+
35
+ export function resolveErrorBarAppearance(args, { defaults, operation }) {
36
+ validateKeys(args, ERROR_BAR_EDIT_OPTIONS, operation);
37
+ const caps = args.caps ?? defaults.caps;
38
+ if (typeof caps !== "boolean") {
39
+ throw new TypeError(`${operation} caps must be a boolean.`);
40
+ }
41
+ const capSize = args.capSize ?? defaults.capSize;
42
+ validatePositiveFinite(capSize, `${operation} capSize`);
43
+ const stroke = validateRuleStroke(
44
+ args.stroke ?? defaults.stroke,
45
+ `${operation} stroke`
46
+ );
47
+ const strokeWidth = validateRuleStrokeWidth(
48
+ args.strokeWidth ?? defaults.strokeWidth,
49
+ `${operation} strokeWidth`
50
+ );
51
+ const strokeDash = args.strokeDash ?? defaults.strokeDash;
52
+ normalizeStrokeDashPattern(strokeDash);
53
+ const opacity = validateOpacityValue(
54
+ args.opacity ?? defaults.opacity,
55
+ `${operation} opacity`
56
+ );
57
+ return { caps, capSize, stroke, strokeWidth, strokeDash, opacity };
58
+ }
11
59
 
12
60
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
13
61
 
62
+ export function errorBarCaps(config) {
63
+ return [
64
+ [config.lowerCapId, config.fields?.lower ?? config.lowerField],
65
+ [config.upperCapId, config.fields?.upper ?? config.upperField]
66
+ ];
67
+ }
68
+
14
69
  function resolveOwner(program, requested) {
15
- const target = requested === undefined
16
- ? undefined
17
- : validateUserId(requested, "Error-bar id");
18
- return resolveEligibleLayer(program, {
19
- target,
70
+ return resolveIntervalOwner(program, requested, {
71
+ idLabel: "Error-bar id",
20
72
  label: "error bar",
21
- predicate: layer =>
22
- layer.mark?.type === "rule" &&
23
- program.markConfigs[layer.id]?.errorBar !== undefined
73
+ mark: "rule",
74
+ config: "errorBar"
24
75
  });
25
76
  }
26
77
 
@@ -82,10 +133,7 @@ export const rematerializeErrorBar = action(
82
133
  throw new Error(`Unknown error bar "${id}".`);
83
134
  }
84
135
  let next = rematerializeRuleAppearance(this, id, config);
85
- const capDefinitions = [
86
- [config.lowerCapId, config.lowerField],
87
- [config.upperCapId, config.upperField]
88
- ];
136
+ const capDefinitions = errorBarCaps(config);
89
137
  if (!config.caps) {
90
138
  for (const [capId] of capDefinitions) next = removeCap(next, capId);
91
139
  return next;
@@ -118,22 +166,20 @@ export const editErrorBar = action(
118
166
  }
119
167
  const owner = resolveOwner(this, args.target);
120
168
  const current = this.markConfigs[owner.id].errorBar;
121
- const appearanceArgs = Object.fromEntries(
122
- ERROR_BAR_APPEARANCE_OPTIONS
123
- .filter(key => Object.hasOwn(args, key))
124
- .map(key => [key, args[key]])
125
- );
126
- const appearance = resolveErrorBarAppearance(appearanceArgs, {
169
+ const appearance = resolveErrorBarAppearance(
170
+ ownOptions(args, ERROR_BAR_APPEARANCE_OPTIONS), {
127
171
  defaults: current,
128
172
  operation: "editErrorBar"
129
- });
173
+ }
174
+ );
130
175
  const interval = Object.hasOwn(args, "statistics")
131
176
  ? planIntervalEdit(this, {
132
177
  owner: owner.id,
133
178
  data: current.data,
134
179
  consumers: [
135
180
  owner.id,
136
- ...[current.lowerCapId, current.upperCapId]
181
+ ...errorBarCaps(current)
182
+ .map(([id]) => id)
137
183
  .filter(id => findLayer(this, id) !== undefined)
138
184
  ],
139
185
  statistics: args.statistics,
@@ -141,27 +187,14 @@ export const editErrorBar = action(
141
187
  })
142
188
  : { changed: false };
143
189
 
144
- const applyEdit = program => {
145
- let next = program;
146
- if (interval.changed) {
147
- next = next.createIntervalData(interval.dataArgs);
148
- for (const rebind of interval.revision.rebinds) {
149
- next = next.rebindLayerData(rebind);
150
- }
190
+ const next = applyIntervalRevision(this, interval);
191
+ return releaseIntervalRevision(next._withMarkConfig(owner.id, {
192
+ ...next.markConfigs[owner.id],
193
+ errorBar: {
194
+ ...current,
195
+ ...appearance,
196
+ ...(interval.changed ? { data: interval.revision.id } : {})
151
197
  }
152
- next = next._withMarkConfig(owner.id, {
153
- ...next.markConfigs[owner.id],
154
- errorBar: {
155
- ...current,
156
- ...appearance,
157
- ...(interval.changed ? { data: interval.revision.id } : {})
158
- }
159
- }).rematerializeErrorBar({ id: owner.id });
160
- return interval.changed
161
- ? next.releaseDerivedData(interval.revision.release)
162
- : next;
163
- };
164
- if (interval.changed) applyEdit(this);
165
- return applyEdit(this);
198
+ }).rematerializeErrorBar({ id: owner.id }), interval);
166
199
  }
167
200
  );
@@ -1,5 +1,4 @@
1
- import { createErrorBarCap } from "./components.js";
2
- import { createErrorBar } from "./create.js";
1
+ import { createErrorBar, createErrorBarCap } from "./create.js";
3
2
  import { editErrorBar, rematerializeErrorBar } from "./edit.js";
4
3
 
5
4
  export function registerErrorBarActions(ProgramClass) {
@@ -7,9 +7,10 @@ import {
7
7
  validatePositiveFinite
8
8
  } from "../../core/validation.js";
9
9
  import { resolveFacetDefinition } from "../../grammar/facets/index.js";
10
- import { normalizeFacetScalePolicies } from
11
- "../../grammar/facets/scales.js";
12
- import { FACET_SCALE_CHANNELS } from "../../grammar/facets/scales.js";
10
+ import {
11
+ FACET_SCALE_CHANNELS,
12
+ normalizeFacetScalePolicies
13
+ } from "../../grammar/facets/scales.js";
13
14
  import { resolveFacetLayout } from "../../layout/facets.js";
14
15
  import { compositionChildDescriptor } from
15
16
  "../../materialization/composition.js";
@@ -17,6 +18,7 @@ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
17
18
  import { deriveFacetChildren } from "./derive.js";
18
19
  import { replayDerivedData } from "./replay.js";
19
20
  import { composeFacetGuides } from "./guides.js";
21
+ import { applyCompositionState } from "../composition/actions.js";
20
22
 
21
23
  const FACET_OPTIONS = Object.freeze([
22
24
  "id", "field", "data", "columns", "gap", "align", "padding", "scales",
@@ -48,25 +50,6 @@ function normalizeGuides(guides) {
48
50
  return { axes, legend };
49
51
  }
50
52
 
51
- function normalizeHeaderPatch(args, previous) {
52
- validateOptionObject(args, HEADER_OPTIONS, "editFacetHeaders", {
53
- allowEmpty: false,
54
- emptyMessage: "editFacetHeaders requires at least one change."
55
- });
56
- const next = { ...previous, ...args };
57
- validatePositiveFinite(next.fontSize, "Facet header fontSize");
58
- validateNonEmptyString(next.fontFamily, "Facet header fontFamily");
59
- validateNonEmptyString(next.color, "Facet header color");
60
- validateNonNegativeFinite(next.offset, "Facet header offset");
61
- if (!(
62
- (typeof next.fontWeight === "string" && next.fontWeight.length > 0) ||
63
- Number.isFinite(next.fontWeight)
64
- )) {
65
- throw new TypeError("Facet header fontWeight must be a non-empty string or number.");
66
- }
67
- return next;
68
- }
69
-
70
53
  function requireFacetProgram(program, operation) {
71
54
  program._assertCompositionProgram(operation);
72
55
  if (program.compositionSpec.type !== "facet") {
@@ -74,16 +57,6 @@ function requireFacetProgram(program, operation) {
74
57
  }
75
58
  }
76
59
 
77
- function currentFacetDefinition(program) {
78
- const current = program.compositionSpec;
79
- return resolveFacetDefinition(program.semanticSpec, {
80
- id: current.id,
81
- data: current.facet.data,
82
- field: current.facet.field,
83
- values: current.facet.values
84
- });
85
- }
86
-
87
60
  function facetUnitTemplate(program) {
88
61
  const seed = program.children[program.compositionSpec.children[0]];
89
62
  if (seed === undefined) {
@@ -106,20 +79,19 @@ function facetUnitTemplate(program) {
106
79
  });
107
80
  }
108
81
 
109
- function applicableScaleRequest(program, channels) {
110
- return Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
82
+ function rederiveFacet(program, { scales, guides }) {
83
+ const current = program.compositionSpec;
84
+ const definition = resolveFacetDefinition(program.semanticSpec, {
85
+ id: current.id,
86
+ data: current.facet.data,
87
+ field: current.facet.field,
88
+ values: current.facet.values
89
+ });
90
+ const request = Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
111
91
  program.semanticSpec.layers.some(
112
92
  layer => layer.encoding?.[channel]?.scale !== undefined
113
- )
114
- ? [[channel, channels[channel]]]
115
- : []
93
+ ) ? [[channel, scales[channel]]] : []
116
94
  ));
117
- }
118
-
119
- function rederiveFacet(program, { scales, guides }) {
120
- const current = program.compositionSpec;
121
- const definition = currentFacetDefinition(program);
122
- const request = applicableScaleRequest(program, scales);
123
95
  const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
124
96
  const derived = deriveFacetChildren(facetUnitTemplate(program), definition, {
125
97
  closeInheritedAction: true,
@@ -134,14 +106,10 @@ function rederiveFacet(program, { scales, guides }) {
134
106
  guides
135
107
  }
136
108
  };
137
- let next = program._withCompositionState({
109
+ return applyCompositionState(program, {
138
110
  children: derived.children,
139
111
  compositionSpec
140
- });
141
- for (const id of compositionSpec.children) {
142
- next = next.useProgram({ id });
143
- }
144
- return next.materializeComposition();
112
+ }, compositionSpec.children);
145
113
  }
146
114
 
147
115
  export const facet = action(
@@ -189,18 +157,16 @@ export const facet = action(
189
157
  guides
190
158
  }
191
159
  };
192
- let next = this
193
- ._withCompositionState({
160
+ return applyCompositionState(
161
+ this._withMaterializationConfig(["facets", definition.id], {
162
+ headers: DEFAULT_HEADERS
163
+ }),
164
+ {
194
165
  children: derived.children,
195
166
  compositionSpec
196
- })
197
- ._withMaterializationConfig(["facets", definition.id], {
198
- headers: DEFAULT_HEADERS
199
- });
200
- for (const id of compositionSpec.children) {
201
- next = next.useProgram({ id });
202
- }
203
- return next.materializeComposition();
167
+ },
168
+ compositionSpec.children
169
+ );
204
170
  }
205
171
  );
206
172
 
@@ -217,7 +183,23 @@ export const editFacetHeaders = action(
217
183
  if (!isPlainObject(config?.headers)) {
218
184
  throw new Error(`Facet "${id}" requires header configuration.`);
219
185
  }
220
- const headers = normalizeHeaderPatch(args, config.headers);
186
+ validateOptionObject(args, HEADER_OPTIONS, "editFacetHeaders", {
187
+ allowEmpty: false,
188
+ emptyMessage: "editFacetHeaders requires at least one change."
189
+ });
190
+ const headers = { ...config.headers, ...args };
191
+ validatePositiveFinite(headers.fontSize, "Facet header fontSize");
192
+ validateNonEmptyString(headers.fontFamily, "Facet header fontFamily");
193
+ validateNonEmptyString(headers.color, "Facet header color");
194
+ validateNonNegativeFinite(headers.offset, "Facet header offset");
195
+ if (!(
196
+ (typeof headers.fontWeight === "string" && headers.fontWeight.length > 0) ||
197
+ Number.isFinite(headers.fontWeight)
198
+ )) {
199
+ throw new TypeError(
200
+ "Facet header fontWeight must be a non-empty string or number."
201
+ );
202
+ }
221
203
  return this
222
204
  ._withMaterializationConfig(["facets", id], { ...config, headers })
223
205
  .materializeComposition();
@@ -252,14 +234,10 @@ export const editFacetScales = action(
252
234
  )) {
253
235
  throw new Error("editFacetScales requires at least one channel policy change.");
254
236
  }
255
- const request = applicableScaleRequest(this, scales);
256
- const normalized = normalizeFacetScalePolicies(this.semanticSpec, request);
257
- const applyEdit = program => rederiveFacet(program, {
258
- scales: normalized.channels,
237
+ return rederiveFacet(this, {
238
+ scales,
259
239
  guides: current.guides
260
240
  });
261
- applyEdit(this);
262
- return applyEdit(this);
263
241
  }
264
242
  );
265
243
 
@@ -277,12 +255,10 @@ export const editFacetGuides = action(
277
255
  });
278
256
  const current = this.compositionSpec.facet;
279
257
  const guides = normalizeGuides({ ...current.guides, ...args });
280
- const applyEdit = program => rederiveFacet(program, {
258
+ return rederiveFacet(this, {
281
259
  scales: current.scales,
282
260
  guides
283
261
  });
284
- applyEdit(this);
285
- return applyEdit(this);
286
262
  }
287
263
  );
288
264
 
@@ -56,40 +56,6 @@ function applySharedHistogramBoundaries(program, layerId, boundaries) {
56
56
  });
57
57
  }
58
58
 
59
- function replayDatasetId(cell, owner) {
60
- return `${cell.id}-${owner}-data`;
61
- }
62
-
63
- function rebindReplayedGradientPlots(child, base, datasets) {
64
- let next = child;
65
- for (const [id, markConfig] of Object.entries(base.markConfigs)) {
66
- const gradientPlot = markConfig.gradientPlot;
67
- if (gradientPlot?.materialized !== true) continue;
68
- const profile = datasets.get(gradientPlot.profileId);
69
- const source = datasets.get(gradientPlot.source);
70
- if (profile === undefined || source === undefined) {
71
- throw new Error(
72
- `Facet gradient plot "${id}" is missing replayed profile dependencies.`
73
- );
74
- }
75
- next = next.rebindGradientPlotProfile({ id, profile, source });
76
- }
77
- return next;
78
- }
79
-
80
- function cellMaterializationPlan(program) {
81
- const scaleIds = [...new Set(program.semanticSpec.layers.flatMap(layer =>
82
- Object.values(layer.encoding ?? {})
83
- .map(encoding => encoding?.scale)
84
- .filter(id => id !== undefined)
85
- ))];
86
- const scales = scaleIds.map(id => ({
87
- op: "rematerializeScale",
88
- args: { id, guides: false, marks: false }
89
- }));
90
- return buildMaterializationPlan({ scales });
91
- }
92
-
93
59
  function deriveCellProgram(
94
60
  base,
95
61
  definition,
@@ -111,7 +77,7 @@ function deriveCellProgram(
111
77
  `Facet replay source "${replay.source}" is not available in cell "${cell.id}".`
112
78
  );
113
79
  }
114
- const id = replayDatasetId(cell, replay.id);
80
+ const id = `${cell.id}-${replay.id}-data`;
115
81
  const policy = findTransformPolicy(replay.transform.type);
116
82
  const transform = policy?.facetReplayTransform?.(
117
83
  replay.transform,
@@ -138,8 +104,29 @@ function deriveCellProgram(
138
104
  histogramBoundaries.get(layer.id)
139
105
  );
140
106
  }
141
- child = rebindReplayedGradientPlots(child, base, datasets);
142
- return applyMaterializationPlan(child, cellMaterializationPlan(child));
107
+ for (const [id, markConfig] of Object.entries(base.markConfigs)) {
108
+ const gradientPlot = markConfig.gradientPlot;
109
+ if (gradientPlot?.materialized !== true) continue;
110
+ const profile = datasets.get(gradientPlot.profileId);
111
+ const source = datasets.get(gradientPlot.source);
112
+ if (profile === undefined || source === undefined) {
113
+ throw new Error(
114
+ `Facet gradient plot "${id}" is missing replayed profile dependencies.`
115
+ );
116
+ }
117
+ child = child.rebindGradientPlotProfile({ id, profile, source });
118
+ }
119
+ const scaleIds = [...new Set(child.semanticSpec.layers.flatMap(layer =>
120
+ Object.values(layer.encoding ?? {})
121
+ .map(encoding => encoding?.scale)
122
+ .filter(id => id !== undefined)
123
+ ))];
124
+ return applyMaterializationPlan(child, buildMaterializationPlan({
125
+ scales: scaleIds.map(id => ({
126
+ op: "rematerializeScale",
127
+ args: { id, guides: false, marks: false }
128
+ }))
129
+ }));
143
130
  }
144
131
 
145
132
  function applyResolvedDomains(program, childId, resolution, baseResolved) {
@@ -186,14 +173,14 @@ export function deriveFacetChildren(
186
173
  const template = stripTitle && base.semanticSpec.title.text !== undefined
187
174
  ? base.removeTitle()
188
175
  : base;
189
- const resolutionRequest = scales ?? {};
190
- const xPolicy = resolutionRequest.x ?? "shared";
176
+ scales ??= {};
177
+ const xPolicy = scales.x ?? "shared";
191
178
  const bins = xPolicy === "shared"
192
179
  ? sharedHistogramBoundaries(template)
193
180
  : new Map();
194
181
  const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
195
182
  cell.id,
196
- deriveCellProgram(template, definition, cell, bins, resolutionRequest)
183
+ deriveCellProgram(template, definition, cell, bins, scales)
197
184
  ]));
198
185
  const resolution = resolveFacetScaleDomains(
199
186
  template.semanticSpec,
@@ -201,14 +188,19 @@ export function deriveFacetChildren(
201
188
  id,
202
189
  child.resolvedScales
203
190
  ])),
204
- resolutionRequest,
191
+ scales,
205
192
  template.resolvedScales
206
193
  );
207
194
  const resolvedChildren = Object.fromEntries(
208
- Object.entries(independentlyResolved).map(([id, child]) => [
209
- id,
210
- applyResolvedDomains(child, id, resolution, template.resolvedScales)
211
- ])
195
+ Object.entries(independentlyResolved).map(([id, child]) => {
196
+ const resolved = applyResolvedDomains(
197
+ child,
198
+ id,
199
+ resolution,
200
+ template.resolvedScales
201
+ );
202
+ return [id, closeInheritedAction ? resolved._exitAction() : resolved];
203
+ })
212
204
  );
213
205
  const sharedScales = Object.fromEntries(
214
206
  Object.entries(resolution.scales)
@@ -216,14 +208,7 @@ export function deriveFacetChildren(
216
208
  .map(([id]) => [id, resolvedChildren[definition.cells[0].id].resolvedScales[id]])
217
209
  );
218
210
  return freezeOwned({
219
- children: freezeOwned(Object.fromEntries(
220
- Object.entries(resolvedChildren).map(([id, child]) => [
221
- id,
222
- closeInheritedAction
223
- ? child._exitAction()
224
- : child
225
- ])
226
- )),
211
+ children: freezeOwned(resolvedChildren),
227
212
  sharedScales: freezeOwned(sharedScales),
228
213
  resolution
229
214
  });
@@ -3,7 +3,6 @@ import { isPlainObject } from "../../core/immutable.js";
3
3
  import { validateUserId } from "../../core/identifiers.js";
4
4
  import { validateKeys } from "../../core/validation.js";
5
5
  import { findTransformPolicy } from "../../grammar/transforms.js";
6
- import { hasDataset } from "../../selectors/datasets.js";
7
6
 
8
7
  const REPLAY_OPTIONS = Object.freeze(["id", "source", "transform"]);
9
8