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,6 +2,8 @@ import { validateUserId } from "../../core/identifiers.js";
2
2
  import { findDataset } from "../../selectors/datasets.js";
3
3
  import { resolveEligibleLayer } from "../../selectors/layers.js";
4
4
  import { hasMaterializedLegend } from "../../materialization/legends.js";
5
+ import { applyMaterializationPlan } from "../../materialization/dependencies.js";
6
+ import { planEncodingRematerialization } from "../../materialization/encodings.js";
5
7
  import { findSemanticScale } from "../../selectors/scales.js";
6
8
  import { validateOptionObject } from "../../core/validation.js";
7
9
  import {
@@ -13,6 +15,41 @@ export function validateOptions(args, supported, operation) {
13
15
  validateOptionObject(args, supported, operation);
14
16
  }
15
17
 
18
+ export function setEncodingProperties(
19
+ program,
20
+ target,
21
+ channel,
22
+ properties
23
+ ) {
24
+ let next = program;
25
+ for (const [property, value] of Object.entries(properties)) {
26
+ next = next.editSemantic({
27
+ property: `layer[${target}].encoding.${channel}.${property}`,
28
+ value
29
+ });
30
+ }
31
+ return next;
32
+ }
33
+
34
+ export function rematerializeEncoding(program, target, channel, scale) {
35
+ return applyMaterializationPlan(
36
+ program,
37
+ planEncodingRematerialization(program, { target, channel, scale })
38
+ );
39
+ }
40
+
41
+ export function clearMarkGraphic(program, target) {
42
+ const graphic = program.graphicSpec.objects[target];
43
+ if (graphic === undefined) return program;
44
+ const collection = graphic.type === "collection";
45
+ return program.editGraphics({
46
+ target,
47
+ property: collection ? "items" : "length",
48
+ value: collection ? [] : 0
49
+ });
50
+ }
51
+
52
+
16
53
  export function validateLineSeriesCompatibility(layer, channel, field) {
17
54
  if (layer.mark?.type !== "line") return;
18
55
  for (const companion of ["group", "color", "strokeDash"]) {
@@ -5,12 +5,12 @@ import {
5
5
  validateNominalFieldType
6
6
  } from "../../grammar/scales/index.js";
7
7
  import { resolveStrokeDashScaleDefinition } from "../scales/definitions.js";
8
- import { applyMaterializationPlan } from "../../materialization/dependencies.js";
9
- import { planEncodingRematerialization } from "../../materialization/encodings.js";
10
8
  import {
11
9
  applyEncodingScale,
10
+ rematerializeEncoding,
12
11
  resolveReassignmentScaleOptions,
13
12
  resolveTarget,
13
+ setEncodingProperties,
14
14
  validateLineSeriesCompatibility,
15
15
  validateOptions
16
16
  } from "./shared.js";
@@ -111,31 +111,16 @@ const encodeStrokeDash = action(
111
111
  let next = previous === undefined
112
112
  ? this
113
113
  : this.clearStrokeDashEncoding({ target });
114
- next = next
115
- .editSemantic({
116
- property: `layer[${target}].encoding.strokeDash.field`,
117
- value: args.field
118
- })
119
- .editSemantic({
120
- property: `layer[${target}].encoding.strokeDash.fieldType`,
121
- value: fieldType
122
- })
123
- .editSemantic({
124
- property: `layer[${target}].encoding.strokeDash.scale`,
125
- value: scale.id
126
- });
114
+ next = setEncodingProperties(next, target, "strokeDash", {
115
+ field: args.field,
116
+ fieldType,
117
+ scale: scale.id
118
+ });
127
119
  next = applyEncodingScale(next, scale, requestedScale, {
128
120
  reassignment: previous?.scale === scale.id
129
121
  });
130
122
 
131
- return applyMaterializationPlan(
132
- next,
133
- planEncodingRematerialization(next, {
134
- target,
135
- channel: "strokeDash",
136
- scale: scale.id
137
- })
138
- );
123
+ return rematerializeEncoding(next, target, "strokeDash", scale.id);
139
124
  }
140
125
  );
141
126
 
@@ -2,7 +2,11 @@ import { action } from "../../core/action.js";
2
2
  import { formatTextValue, validateTextFormat } from "../../grammar/text.js";
3
3
  import { canMaterializeText } from "../../materialization/marks/index.js";
4
4
  import { findLayer } from "../../selectors/layers.js";
5
- import { resolveTarget, validateOptions } from "./shared.js";
5
+ import {
6
+ resolveTarget,
7
+ setEncodingProperties,
8
+ validateOptions
9
+ } from "./shared.js";
6
10
 
7
11
  const OPTIONS = Object.freeze(["target", "field", "value", "format"]);
8
12
 
@@ -52,15 +56,10 @@ export const encodeText = action(
52
56
  remove: true
53
57
  });
54
58
  }
55
- next = next
56
- .editSemantic({
57
- property: `layer[${target}].encoding.text.${hasField ? "field" : "datum"}`,
58
- value: hasField ? args.field : args.value
59
- })
60
- .editSemantic({
61
- property: `layer[${target}].encoding.text.format`,
62
- value: format
63
- });
59
+ next = setEncodingProperties(next, target, "text", {
60
+ [hasField ? "field" : "datum"]: hasField ? args.field : args.value,
61
+ format
62
+ });
64
63
  const updated = findLayer(next, target);
65
64
  return canMaterializeText(next, updated)
66
65
  ? next.rematerializeTextMark({ id: target })
@@ -5,8 +5,9 @@ import {
5
5
  } from "../../core/validation.js";
6
6
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
7
7
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
8
- import { resolveBoundaryAppearance } from "./options.js";
9
- import { resolveErrorBand } from "./resolve.js";
8
+ import { createResolvedIntervalData } from "../data/intervalEdit.js";
9
+ import { resolveIntervalComposite } from "../intervals/resolve.js";
10
+ import { errorBandBoundaries, resolveBoundaryAppearance } from "./edit.js";
10
11
 
11
12
  const OPTIONS = Object.freeze([
12
13
  "id",
@@ -21,6 +22,38 @@ const OPTIONS = Object.freeze([
21
22
  "curve",
22
23
  "boundaries"
23
24
  ]);
25
+
26
+ const ERROR_BAND_POLICY = Object.freeze({
27
+ operation: "createErrorBand",
28
+ resourceLabel: "error-band",
29
+ defaultId: "errorBand",
30
+ ownerLabel: "Error-band id",
31
+ positionTypes: Object.freeze(["quantitative", "temporal"]),
32
+ defaultPositionType: "quantitative",
33
+ defaultIntervalChannel: "y",
34
+ scaleDefaults: fieldType => fieldType === "temporal"
35
+ ? { nice: true }
36
+ : { nice: true, zero: false },
37
+ intervalScaleDefaults: Object.freeze({ nice: true, zero: false }),
38
+ allowExplicitGrouping: true,
39
+ ambiguousMessage:
40
+ "createErrorBand cannot infer the interval axis when both positions are quantitative; provide an interval option."
41
+ });
42
+
43
+ function resolveErrorBand(program, args) {
44
+ const resolved = resolveIntervalComposite(program, args, ERROR_BAND_POLICY);
45
+ if (resolved.groupField === resolved.position.field) {
46
+ throw new Error(
47
+ "createErrorBand groupBy must differ from the independent position field."
48
+ );
49
+ }
50
+ return {
51
+ ...resolved,
52
+ lowerBoundaryId: `${resolved.id}LowerBoundary`,
53
+ upperBoundaryId: `${resolved.id}UpperBoundary`
54
+ };
55
+ }
56
+
24
57
  function resolveBoundaries(value, areaCurve) {
25
58
  if (value === undefined || value === false) return undefined;
26
59
  if (!isPlainObject(value)) {
@@ -38,6 +71,62 @@ function resolveBoundaries(value, areaCurve) {
38
71
  });
39
72
  }
40
73
 
74
+ function positionOptions({ target, field, fieldType, coordinate, scale }) {
75
+ return { target, field, fieldType, coordinate, scale: { id: scale } };
76
+ }
77
+
78
+ export const createErrorBandBoundary = action(
79
+ {
80
+ op: "createErrorBandBoundary",
81
+ description: "Create one lower or upper error-band boundary line."
82
+ },
83
+ function ({
84
+ id,
85
+ data,
86
+ orientation,
87
+ bound,
88
+ position,
89
+ coordinate,
90
+ intervalScale,
91
+ positionScale,
92
+ groupBy,
93
+ stroke,
94
+ strokeWidth,
95
+ strokeDash,
96
+ opacity,
97
+ curve
98
+ } = {}) {
99
+ const vertical = orientation === "vertical";
100
+ let next = this
101
+ .createLineMark({ id, data, strokeWidth, curve })
102
+ .encodeY(positionOptions({
103
+ target: id,
104
+ field: vertical ? bound : position.field,
105
+ fieldType: vertical ? "quantitative" : position.fieldType,
106
+ coordinate,
107
+ scale: vertical ? intervalScale : positionScale
108
+ }))
109
+ .encodeX(positionOptions({
110
+ target: id,
111
+ field: vertical ? position.field : bound,
112
+ fieldType: vertical ? position.fieldType : "quantitative",
113
+ coordinate,
114
+ scale: vertical ? positionScale : intervalScale
115
+ }));
116
+ if (groupBy !== undefined) {
117
+ next = next.encodeGroup({ target: id, field: groupBy });
118
+ }
119
+ return next
120
+ .editGraphics({ target: id, property: "stroke", value: stroke })
121
+ .editGraphics({
122
+ target: id,
123
+ property: "strokeDash",
124
+ value: next.graphicSpec.objects[id].items.map(() => strokeDash)
125
+ })
126
+ .editGraphics({ target: id, property: "opacity", value: opacity });
127
+ }
128
+ );
129
+
41
130
  function positionArgs(resolved) {
42
131
  return {
43
132
  target: resolved.id,
@@ -69,21 +158,7 @@ export const createErrorBand = action(
69
158
  const resolved = resolveErrorBand(this, args);
70
159
  const curve = validateCurveInterpolation(args.curve ?? "linear");
71
160
  const boundaries = resolveBoundaries(args.boundaries, curve);
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
-
161
+ let next = createResolvedIntervalData(this, resolved);
87
162
  next = next.createAreaMark({
88
163
  id: resolved.id,
89
164
  data: resolved.dataId,
@@ -91,13 +166,11 @@ export const createErrorBand = action(
91
166
  ...(args.opacity === undefined ? {} : { opacity: args.opacity }),
92
167
  ...(Object.hasOwn(args, "curve") ? { curve } : {})
93
168
  });
94
- next = resolved.orientation === "vertical"
95
- ? next
96
- .encodeX(positionArgs(resolved))
97
- .encodeYRange(rangeArgs(resolved))
98
- : next
99
- .encodeY(positionArgs(resolved))
100
- .encodeXRange(rangeArgs(resolved));
169
+ const vertical = resolved.orientation === "vertical";
170
+ next = next[vertical ? "encodeX" : "encodeY"](positionArgs(resolved));
171
+ next = next[vertical ? "encodeYRange" : "encodeXRange"](
172
+ rangeArgs(resolved)
173
+ );
101
174
 
102
175
  if (resolved.interval.mode === "explicit") {
103
176
  next = next.editSemantic({
@@ -125,21 +198,14 @@ export const createErrorBand = action(
125
198
  groupBy: resolved.groupField,
126
199
  ...boundaries
127
200
  };
128
- next = next
129
- .createErrorBandBoundary({
201
+ for (const [id, field] of errorBandBoundaries(resolved)) {
202
+ next = next.createErrorBandBoundary({
130
203
  ...shared,
131
- id: `${resolved.id}LowerBoundary`,
132
- bound: resolved.fields.lower
133
- })
134
- .createErrorBandBoundary({
135
- ...shared,
136
- id: `${resolved.id}UpperBoundary`,
137
- bound: resolved.fields.upper
204
+ id,
205
+ bound: field
138
206
  });
139
- for (const [id, bound] of [
140
- [`${resolved.id}LowerBoundary`, "lower"],
141
- [`${resolved.id}UpperBoundary`, "upper"]
142
- ]) {
207
+ }
208
+ for (const [id, , bound] of errorBandBoundaries(resolved)) {
143
209
  next = next._withMarkConfig(id, {
144
210
  ...next.markConfigs[id],
145
211
  ...boundaries,
@@ -159,8 +225,8 @@ export const createErrorBand = action(
159
225
  groupBy: resolved.groupField,
160
226
  lowerField: resolved.fields.lower,
161
227
  upperField: resolved.fields.upper,
162
- lowerBoundaryId: `${resolved.id}LowerBoundary`,
163
- upperBoundaryId: `${resolved.id}UpperBoundary`
228
+ lowerBoundaryId: resolved.lowerBoundaryId,
229
+ upperBoundaryId: resolved.upperBoundaryId
164
230
  }
165
231
  });
166
232
  }
@@ -3,17 +3,44 @@ import { isPlainObject } from "../../core/immutable.js";
3
3
  import { validateUserId } from "../../core/identifiers.js";
4
4
  import {
5
5
  validateOptionObject,
6
+ validateKeys,
6
7
  validateNonEmptyString,
8
+ validateNonNegativeFinite,
7
9
  validateUnitInterval
8
10
  } from "../../core/validation.js";
9
11
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
10
- import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
12
+ import { normalizeStrokeDashPattern } from "../../grammar/scales/index.js";
13
+ import { findLayer } from "../../selectors/layers.js";
14
+ import { removeOwnedMark } from "../marks/remove.js";
11
15
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
12
- import { planIntervalEdit } from "../data/intervalEdit.js";
13
16
  import {
14
- ERROR_BAND_BOUNDARY_OPTIONS,
15
- resolveBoundaryAppearance
16
- } from "./options.js";
17
+ applyIntervalRevision,
18
+ ownOptions,
19
+ planIntervalEdit,
20
+ releaseIntervalRevision,
21
+ resolveIntervalOwner
22
+ } from "../data/intervalEdit.js";
23
+
24
+ export const ERROR_BAND_BOUNDARY_OPTIONS = Object.freeze([
25
+ "stroke", "strokeWidth", "strokeDash", "opacity", "curve"
26
+ ]);
27
+
28
+ export function resolveBoundaryAppearance(value, { defaults, operation }) {
29
+ if (!isPlainObject(value)) {
30
+ throw new TypeError(`${operation} must be a plain object.`);
31
+ }
32
+ validateKeys(value, ERROR_BAND_BOUNDARY_OPTIONS, operation);
33
+ const stroke = value.stroke ?? defaults.stroke;
34
+ const strokeWidth = value.strokeWidth ?? defaults.strokeWidth;
35
+ const strokeDash = value.strokeDash ?? defaults.strokeDash;
36
+ const opacity = value.opacity ?? defaults.opacity;
37
+ const curve = validateCurveInterpolation(value.curve ?? defaults.curve);
38
+ validateNonEmptyString(stroke, `${operation} stroke`);
39
+ validateNonNegativeFinite(strokeWidth, `${operation} strokeWidth`);
40
+ const resolvedStrokeDash = normalizeStrokeDashPattern(strokeDash);
41
+ validateUnitInterval(opacity, `${operation} opacity`);
42
+ return { stroke, strokeWidth, strokeDash: resolvedStrokeDash, opacity, curve };
43
+ }
17
44
 
18
45
  const EDIT_OPTIONS = Object.freeze([
19
46
  "target", "fill", "opacity", "curve", "statistics", "boundaries"
@@ -25,16 +52,27 @@ const REMATERIALIZE_OPTIONS = Object.freeze([
25
52
  "id", ...ERROR_BAND_BOUNDARY_OPTIONS
26
53
  ]);
27
54
 
55
+ export function errorBandBoundaries(config) {
56
+ return [
57
+ [
58
+ config.lowerBoundaryId,
59
+ config.fields?.lower ?? config.lowerField,
60
+ "lower"
61
+ ],
62
+ [
63
+ config.upperBoundaryId,
64
+ config.fields?.upper ?? config.upperField,
65
+ "upper"
66
+ ]
67
+ ];
68
+ }
69
+
28
70
  function resolveOwner(program, requested) {
29
- const target = requested === undefined
30
- ? undefined
31
- : validateUserId(requested, "Error-band id");
32
- return resolveEligibleLayer(program, {
33
- target,
71
+ return resolveIntervalOwner(program, requested, {
72
+ idLabel: "Error-band id",
34
73
  label: "error band",
35
- predicate: layer =>
36
- layer.mark?.type === "area" &&
37
- program.markConfigs[layer.id]?.errorBand !== undefined
74
+ mark: "area",
75
+ config: "errorBand"
38
76
  });
39
77
  }
40
78
 
@@ -90,31 +128,7 @@ function createBoundary(program, owner, id, bound, appearance) {
90
128
  }
91
129
 
92
130
  function removeBoundary(program, id) {
93
- if (findLayer(program, id) === undefined) return program;
94
- const selectionIds = Object.entries(
95
- program.materializationConfigs.selections ?? {}
96
- ).filter(([, config]) => config.target === id).map(([selection]) => selection);
97
- let next = program;
98
- for (const [highlight, config] of Object.entries(
99
- program.materializationConfigs.highlights ?? {}
100
- )) {
101
- if (config.target === id || selectionIds.includes(config.selection)) {
102
- next = next._withoutMaterializationConfig(["highlights", highlight]);
103
- }
104
- }
105
- for (const selection of selectionIds) {
106
- next = next._withoutMaterializationConfig(["selections", selection]);
107
- }
108
- return next
109
- .editSemantic({ property: `layer[${id}]`, remove: true })
110
- .editGraphics({ target: id, remove: true })
111
- ._withoutMaterializationConfig(["marks", id])
112
- ._withContext({
113
- ...(program.context.currentMark === id ? { currentMark: undefined } : {}),
114
- ...(selectionIds.includes(program.context.currentSelection)
115
- ? { currentSelection: undefined }
116
- : {})
117
- });
131
+ return removeOwnedMark(program, id);
118
132
  }
119
133
 
120
134
  export const rematerializeErrorBandBoundary = action(
@@ -178,7 +192,8 @@ export const editErrorBand = action(
178
192
  data: errorBand.data,
179
193
  consumers: [
180
194
  owner.id,
181
- ...[errorBand.lowerBoundaryId, errorBand.upperBoundaryId]
195
+ ...errorBandBoundaries(errorBand)
196
+ .map(([id]) => id)
182
197
  .filter(id => findLayer(this, id) !== undefined)
183
198
  ],
184
199
  statistics: args.statistics,
@@ -195,58 +210,43 @@ export const editErrorBand = action(
195
210
  );
196
211
  }
197
212
 
198
- const applyEdit = program => {
199
- let next = program;
200
- if (interval.changed) {
201
- next = next.createIntervalData(interval.dataArgs);
202
- for (const rebind of interval.revision.rebinds) {
203
- next = next.rebindLayerData(rebind);
213
+ let next = applyIntervalRevision(this, interval);
214
+ if (interval.changed) errorBand.data = interval.revision.id;
215
+ next = next
216
+ ._withMarkConfig(owner.id, { ...config, errorBand })
217
+ .rematerializeAreaMark({ id: owner.id });
218
+ if (Object.hasOwn(args, "boundaries")) {
219
+ if (args.boundaries === false) {
220
+ next = removeBoundary(next, errorBand.lowerBoundaryId);
221
+ next = removeBoundary(next, errorBand.upperBoundaryId);
222
+ } else if (Object.keys(args.boundaries).length === 0) {
223
+ for (const [id, bound] of errorBandBoundaries(errorBand)) {
224
+ next = findLayer(next, id) === undefined
225
+ ? createBoundary(
226
+ next,
227
+ owner.id,
228
+ id,
229
+ bound,
230
+ defaultBoundaryAppearance(next, owner.id)
231
+ )
232
+ : interval.changed
233
+ ? next.rematerializeLineMark({ id })
234
+ : next;
204
235
  }
205
- errorBand.data = interval.revision.id;
236
+ } else {
237
+ next = next.editErrorBandBoundary({
238
+ target: owner.id,
239
+ ...args.boundaries
240
+ });
206
241
  }
207
- next = next
208
- ._withMarkConfig(owner.id, { ...config, errorBand })
209
- .rematerializeAreaMark({ id: owner.id });
210
- if (Object.hasOwn(args, "boundaries")) {
211
- if (args.boundaries === false) {
212
- next = removeBoundary(next, errorBand.lowerBoundaryId);
213
- next = removeBoundary(next, errorBand.upperBoundaryId);
214
- } else if (Object.keys(args.boundaries).length === 0) {
215
- for (const [id, bound] of [
216
- [errorBand.lowerBoundaryId, errorBand.lowerField],
217
- [errorBand.upperBoundaryId, errorBand.upperField]
218
- ]) {
219
- next = findLayer(next, id) === undefined
220
- ? createBoundary(
221
- next,
222
- owner.id,
223
- id,
224
- bound,
225
- defaultBoundaryAppearance(next, owner.id)
226
- )
227
- : interval.changed
228
- ? next.rematerializeLineMark({ id })
229
- : next;
230
- }
231
- } else {
232
- next = next.editErrorBandBoundary({
233
- target: owner.id,
234
- ...args.boundaries
235
- });
236
- }
237
- } else if (interval.changed) {
238
- for (const id of [errorBand.lowerBoundaryId, errorBand.upperBoundaryId]) {
239
- if (findLayer(next, id) !== undefined) {
240
- next = next.rematerializeLineMark({ id });
241
- }
242
+ } else if (interval.changed) {
243
+ for (const [id] of errorBandBoundaries(errorBand)) {
244
+ if (findLayer(next, id) !== undefined) {
245
+ next = next.rematerializeLineMark({ id });
242
246
  }
243
247
  }
244
- return interval.changed
245
- ? next.releaseDerivedData(interval.revision.release)
246
- : next;
247
- };
248
- if (interval.changed || Object.hasOwn(args, "boundaries")) applyEdit(this);
249
- return applyEdit(this);
248
+ }
249
+ return releaseIntervalRevision(next, interval);
250
250
  }
251
251
  );
252
252
 
@@ -270,13 +270,9 @@ export const editErrorBandBoundary = action(
270
270
  }
271
271
  const owner = resolveOwner(this, args.target);
272
272
  const config = this.markConfigs[owner.id].errorBand;
273
- const patch = Object.fromEntries(
274
- ERROR_BAND_BOUNDARY_OPTIONS
275
- .filter(key => Object.hasOwn(args, key))
276
- .map(key => [key, args[key]])
277
- );
273
+ const patch = ownOptions(args, ERROR_BAND_BOUNDARY_OPTIONS);
278
274
  const ids = boundary === "both"
279
- ? [config.lowerBoundaryId, config.upperBoundaryId]
275
+ ? errorBandBoundaries(config).map(([id]) => id)
280
276
  : [boundary === "lower" ? config.lowerBoundaryId : config.upperBoundaryId];
281
277
  const plans = ids.map(id => {
282
278
  const existing = findLayer(this, id) !== undefined;
@@ -1,5 +1,4 @@
1
- import { createErrorBand } from "./create.js";
2
- import { createErrorBandBoundary } from "./components.js";
1
+ import { createErrorBand, createErrorBandBoundary } from "./create.js";
3
2
  import {
4
3
  editErrorBand,
5
4
  editErrorBandBoundary,