ggaction 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +8 -3
  7. package/src/actions/basic.js +2 -2
  8. package/src/actions/boxPlots/components.js +14 -4
  9. package/src/actions/boxPlots/edit.js +31 -5
  10. package/src/actions/boxPlots/materialize.js +11 -3
  11. package/src/actions/boxPlots/options.js +48 -16
  12. package/src/actions/categoryOrder/index.js +15 -4
  13. package/src/actions/charts/area.js +5 -1
  14. package/src/actions/charts/bar.js +2 -1
  15. package/src/actions/charts/density.js +6 -2
  16. package/src/actions/charts/ecdf.js +4 -1
  17. package/src/actions/charts/endpoints.js +25 -3
  18. package/src/actions/charts/heatmap.js +2 -1
  19. package/src/actions/charts/histogram.js +4 -3
  20. package/src/actions/charts/horizon.js +3 -1
  21. package/src/actions/charts/interval-regression.js +5 -2
  22. package/src/actions/charts/line.js +3 -1
  23. package/src/actions/charts/parallel.js +8 -2
  24. package/src/actions/charts/pie.js +10 -2
  25. package/src/actions/charts/polar.js +15 -4
  26. package/src/actions/charts/radar.js +8 -2
  27. package/src/actions/charts/radial.js +10 -2
  28. package/src/actions/charts/rug-strip.js +6 -2
  29. package/src/actions/charts/scatter.js +3 -1
  30. package/src/actions/composition/actions.js +13 -4
  31. package/src/actions/coordinates/actions.js +1 -1
  32. package/src/actions/coordinates/edit.js +76 -0
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/bin2d.js +57 -185
  35. package/src/actions/data/complete.js +26 -0
  36. package/src/actions/data/computed.js +2 -2
  37. package/src/actions/data/create.js +2 -2
  38. package/src/actions/data/density.js +12 -20
  39. package/src/actions/data/derived.js +47 -17
  40. package/src/actions/data/edit.js +539 -0
  41. package/src/actions/data/filter.js +14 -2
  42. package/src/actions/data/impute.js +26 -0
  43. package/src/actions/data/index.js +14 -0
  44. package/src/actions/data/normalize.js +27 -0
  45. package/src/actions/data/regression.js +16 -21
  46. package/src/actions/data/revisionBindings.js +145 -0
  47. package/src/actions/data/shared.js +20 -4
  48. package/src/actions/data/summary.js +1 -1
  49. package/src/actions/data/timeUnit.js +4 -1
  50. package/src/actions/data/window.js +1 -1
  51. package/src/actions/encodings/channels.js +415 -0
  52. package/src/actions/encodings/color/index.js +2 -1
  53. package/src/actions/encodings/density.js +5 -2
  54. package/src/actions/encodings/histogram.js +3 -2
  55. package/src/actions/encodings/index.js +4 -0
  56. package/src/actions/encodings/offset.js +20 -13
  57. package/src/actions/encodings/position/apply.js +53 -7
  58. package/src/actions/encodings/position/index.js +10 -2
  59. package/src/actions/encodings/position/policies/bar.js +9 -0
  60. package/src/actions/encodings/position/resolve.js +24 -7
  61. package/src/actions/encodings/remove.js +9 -4
  62. package/src/actions/encodings/ruleAppearance.js +1 -23
  63. package/src/actions/encodings/seriesLayout.js +1 -0
  64. package/src/actions/encodings/shared.js +82 -14
  65. package/src/actions/encodings/stroke.js +226 -0
  66. package/src/actions/errorBands/create.js +23 -3
  67. package/src/actions/errorBands/edit.js +21 -6
  68. package/src/actions/errorBars/create.js +35 -7
  69. package/src/actions/errorBars/edit.js +19 -4
  70. package/src/actions/facets/actions.js +237 -57
  71. package/src/actions/facets/derive.js +91 -26
  72. package/src/actions/gradientPlots/components.js +10 -2
  73. package/src/actions/gradientPlots/edit.js +14 -4
  74. package/src/actions/gradientPlots/materialize.js +3 -1
  75. package/src/actions/gradientPlots/options.js +10 -2
  76. package/src/actions/guides/applicability.js +8 -2
  77. package/src/actions/guides/axes/labels.js +58 -12
  78. package/src/actions/guides/axes/lines.js +6 -2
  79. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  80. package/src/actions/guides/axes/tickGroups.js +2 -1
  81. package/src/actions/guides/axes/ticks.js +6 -2
  82. package/src/actions/guides/axes/titles.js +11 -3
  83. package/src/actions/guides/grids/resolve.js +7 -2
  84. package/src/actions/guides/guides.js +19 -3
  85. package/src/actions/guides/legends/blocks.js +426 -0
  86. package/src/actions/guides/legends/categorical/actions.js +134 -48
  87. package/src/actions/guides/legends/categorical/components.js +17 -12
  88. package/src/actions/guides/legends/categorical/index.js +37 -33
  89. package/src/actions/guides/legends/categorical/layout.js +97 -15
  90. package/src/actions/guides/legends/categorical/options.js +2 -2
  91. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  92. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  93. package/src/actions/guides/legends/categorical/symbols.js +85 -30
  94. package/src/actions/guides/legends/continuous/common.js +30 -12
  95. package/src/actions/guides/legends/continuous/gradient.js +10 -7
  96. package/src/actions/guides/legends/continuous/index.js +2 -0
  97. package/src/actions/guides/legends/continuous/interval.js +8 -3
  98. package/src/actions/guides/legends/continuous/opacity.js +8 -5
  99. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  100. package/src/actions/guides/legends/creation.js +14 -1
  101. package/src/actions/guides/legends/edit.js +158 -59
  102. package/src/actions/guides/legends/index.js +2 -0
  103. package/src/actions/guides/legends/lane.js +54 -19
  104. package/src/actions/guides/legends/lifecycle.js +19 -8
  105. package/src/actions/guides/legends/remove.js +13 -3
  106. package/src/actions/guides/legends/sampling.js +140 -0
  107. package/src/actions/guides/legends/size.js +93 -40
  108. package/src/actions/guides/legends/strokeWidth.js +34 -28
  109. package/src/actions/guides/legends/target.js +112 -1
  110. package/src/actions/guides/legends/transition.js +165 -16
  111. package/src/actions/guides/polar/axes/facade.js +4 -4
  112. package/src/actions/guides/polar/axes/labels.js +24 -5
  113. package/src/actions/guides/polar/axes/lines.js +10 -4
  114. package/src/actions/guides/polar/axes/shared.js +16 -0
  115. package/src/actions/guides/polar/axes/ticks.js +1 -1
  116. package/src/actions/guides/polar/axes/titles.js +1 -1
  117. package/src/actions/guides/polar/grids.js +1 -1
  118. package/src/actions/guides/polar/resolve.js +12 -8
  119. package/src/actions/index.js +14 -3
  120. package/src/actions/marks/arc/actions.js +77 -19
  121. package/src/actions/marks/area/actions.js +34 -9
  122. package/src/actions/marks/area/materialize.js +17 -1
  123. package/src/actions/marks/bar/create.js +11 -2
  124. package/src/actions/marks/bar/edit.js +20 -3
  125. package/src/actions/marks/bar/materialize.js +22 -2
  126. package/src/actions/marks/index.js +10 -1
  127. package/src/actions/marks/line/actions.js +37 -14
  128. package/src/actions/marks/line/materialize.js +29 -10
  129. package/src/actions/marks/point/create.js +8 -2
  130. package/src/actions/marks/point/edit.js +18 -6
  131. package/src/actions/marks/point/materialize.js +49 -15
  132. package/src/actions/marks/rect/actions.js +33 -12
  133. package/src/actions/marks/references.js +601 -30
  134. package/src/actions/marks/remove.js +71 -1
  135. package/src/actions/marks/rule/actions.js +47 -10
  136. package/src/actions/marks/shared.js +1 -1
  137. package/src/actions/marks/text/actions.js +74 -9
  138. package/src/actions/marks/text/index.js +4 -0
  139. package/src/actions/marks/text/layout.js +154 -6
  140. package/src/actions/marks/text/removal.js +173 -0
  141. package/src/actions/marks/text/selection.js +73 -0
  142. package/src/actions/marks/tick/actions.js +22 -5
  143. package/src/actions/primitives/editGraphics.js +26 -0
  144. package/src/actions/primitives/semantic.js +2 -1
  145. package/src/actions/primitives/semanticAction.js +83 -10
  146. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  147. package/src/actions/primitives/semanticValidation/index.js +9 -1
  148. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  149. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  150. package/src/actions/regression/components.js +26 -11
  151. package/src/actions/regression/create.js +10 -2
  152. package/src/actions/regression/edit.js +21 -7
  153. package/src/actions/resources/index.js +7 -0
  154. package/src/actions/resources/remove.js +172 -0
  155. package/src/actions/scales/channels.js +212 -0
  156. package/src/actions/scales/consumers/common.js +1 -1
  157. package/src/actions/scales/consumers/families.js +3 -2
  158. package/src/actions/scales/consumers/index.js +19 -2
  159. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  160. package/src/actions/scales/create.js +54 -22
  161. package/src/actions/scales/definitions.js +94 -40
  162. package/src/actions/scales/edit.js +66 -11
  163. package/src/actions/scales/editPolicy.js +81 -11
  164. package/src/actions/scales/index.js +6 -0
  165. package/src/actions/scales/materialize.js +4 -3
  166. package/src/actions/scales/offset.js +48 -0
  167. package/src/actions/scales/parallel.js +71 -0
  168. package/src/actions/scales/preview.js +111 -10
  169. package/src/actions/selection/actions.js +154 -30
  170. package/src/actions/selection/index.js +3 -0
  171. package/src/actions/theme/actions.js +68 -13
  172. package/src/actions/theme/composition.js +238 -0
  173. package/src/actions/theme/index.js +9 -2
  174. package/src/actions/theme/reconcile.js +315 -67
  175. package/src/actions/theme/state.js +160 -0
  176. package/src/actions/violinPlots/create.js +5 -2
  177. package/src/actions/violinPlots/edit.js +1 -0
  178. package/src/core/action.js +10 -0
  179. package/src/core/compositionState.js +6 -1
  180. package/src/core/programState.js +20 -0
  181. package/src/core/resourceReferences.js +332 -0
  182. package/src/core/vocabulary.js +16 -3
  183. package/src/grammar/bars/aggregate.js +4 -1
  184. package/src/grammar/bars/geometry.js +44 -0
  185. package/src/grammar/bin.js +54 -9
  186. package/src/grammar/complete.js +279 -0
  187. package/src/grammar/computed.js +265 -64
  188. package/src/grammar/coordinates.js +37 -0
  189. package/src/grammar/density.js +239 -37
  190. package/src/grammar/displayLabels.js +57 -0
  191. package/src/grammar/facets/dependencies.js +133 -1
  192. package/src/grammar/facets/guides.js +3 -0
  193. package/src/grammar/facets/index.js +7 -47
  194. package/src/grammar/facets/scales.js +15 -18
  195. package/src/grammar/impute.js +340 -0
  196. package/src/grammar/markLabelSelection.js +57 -0
  197. package/src/grammar/markLabels.js +18 -1
  198. package/src/grammar/normalize.js +366 -0
  199. package/src/grammar/pathSeries.js +26 -8
  200. package/src/grammar/pointShapes.js +13 -4
  201. package/src/grammar/polar.js +90 -8
  202. package/src/grammar/regression/index.js +1 -0
  203. package/src/grammar/regression/parameters.js +23 -0
  204. package/src/grammar/roundedRect.js +180 -0
  205. package/src/grammar/rules.js +1 -0
  206. package/src/grammar/scales/colorConsumers.js +20 -1
  207. package/src/grammar/scales/continuous.js +2 -2
  208. package/src/grammar/scales/definition.js +27 -4
  209. package/src/grammar/scales/index.js +1 -0
  210. package/src/grammar/scales/ordinal.js +12 -3
  211. package/src/grammar/scales/policies.js +2 -2
  212. package/src/grammar/scales/size.js +456 -0
  213. package/src/grammar/schemas/concreteGraphic.js +14 -2
  214. package/src/grammar/schemas/graphic.js +15 -0
  215. package/src/grammar/schemas/graphicBounds.js +179 -30
  216. package/src/grammar/schemas/semanticPath.js +7 -2
  217. package/src/grammar/statisticalReference.js +103 -0
  218. package/src/grammar/strokeStyle.js +77 -0
  219. package/src/grammar/summary.js +78 -16
  220. package/src/grammar/timeUnit.js +216 -5
  221. package/src/grammar/transformTopology.js +4 -0
  222. package/src/grammar/transforms.js +394 -28
  223. package/src/grammar/weightedStatistics.js +374 -0
  224. package/src/grammar/window.js +221 -16
  225. package/src/layout/aspect.js +80 -0
  226. package/src/layout/facets.js +59 -5
  227. package/src/layout/labels.js +343 -0
  228. package/src/materialization/bars/aggregate.js +25 -2
  229. package/src/materialization/bars/histogram.js +84 -24
  230. package/src/materialization/bars/ranged.js +19 -3
  231. package/src/materialization/bars/resolve.js +7 -4
  232. package/src/materialization/composition.js +3 -1
  233. package/src/materialization/coordinateBounds.js +204 -0
  234. package/src/materialization/dependencies.js +36 -0
  235. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  236. package/src/materialization/facetGuides/placement.js +8 -2
  237. package/src/materialization/facetGuides/preparation.js +12 -7
  238. package/src/materialization/facetHeaders.js +346 -0
  239. package/src/materialization/facets.js +15 -65
  240. package/src/materialization/guides/resources.js +26 -0
  241. package/src/materialization/legends.js +1 -1
  242. package/src/materialization/marks/index.js +11 -5
  243. package/src/materialization/marks/policies.js +2 -1
  244. package/src/materialization/rect.js +25 -12
  245. package/src/materialization/rectConfig.js +5 -1
  246. package/src/materialization/scales/map.js +5 -3
  247. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  248. package/src/materialization/scales/policies/offset.js +50 -8
  249. package/src/materialization/scales/policies/series.js +4 -1
  250. package/src/materialization/scales/resolve.js +22 -6
  251. package/src/materialization/selection/items/arc.js +28 -4
  252. package/src/materialization/selection/items/bar.js +30 -3
  253. package/src/materialization/selection/items/common.js +19 -4
  254. package/src/materialization/selection/items/rect.js +28 -4
  255. package/src/materialization/selection/path.js +1 -1
  256. package/src/materialization/selection/point.js +3 -1
  257. package/src/materialization/text.js +265 -14
  258. package/src/renderers/canvas/circle.js +2 -3
  259. package/src/renderers/canvas/line.js +2 -3
  260. package/src/renderers/canvas/native.js +4 -4
  261. package/src/renderers/canvas/path.js +2 -3
  262. package/src/renderers/canvas/rect.js +2 -3
  263. package/src/renderers/canvas/stroke.js +11 -0
  264. package/src/renderers/svg.js +19 -0
  265. package/src/selectors/datasets.js +32 -1
  266. package/src/selectors/layers.js +4 -0
  267. package/src/theme/defaults.js +101 -0
  268. package/types/basic.d.ts +6 -0
  269. package/types/index.d.ts +85 -0
  270. package/types/program.d.ts +910 -117
@@ -9,19 +9,27 @@ import {
9
9
  REGRESSION_UPPER_FIELD
10
10
  } from "../../grammar/regression/index.js";
11
11
  import { validateAreaCreateOutline } from "../marks/area/index.js";
12
+ import {
13
+ requestedStrokeDetails,
14
+ STROKE_STYLE_PROPERTIES
15
+ } from "../../grammar/strokeStyle.js";
12
16
 
13
17
  const BAND_OPTIONS = Object.freeze([
14
18
  "id", "data", "x", "lower", "upper", "groupBy", "coordinate", "xScale", "yScale",
15
- "color", "opacity", "stroke", "strokeWidth", "curve"
19
+ "color", "opacity", "stroke", "strokeWidth", "curve",
20
+ ...STROKE_STYLE_PROPERTIES
16
21
  ]);
17
22
  const LINE_OPTIONS = Object.freeze([
18
23
  "id", "data", "x", "y", "groupBy", "coordinate", "xScale", "yScale", "colorScale",
19
- "strokeWidth", "curve"
24
+ "strokeWidth", "curve", ...STROKE_STYLE_PROPERTIES
20
25
  ]);
21
26
  const EDIT_BAND_OPTIONS = Object.freeze([
22
- "target", "color", "opacity", "stroke", "strokeWidth", "curve"
27
+ "target", "color", "opacity", "stroke", "strokeWidth", "curve",
28
+ ...STROKE_STYLE_PROPERTIES
29
+ ]);
30
+ const EDIT_LINE_OPTIONS = Object.freeze([
31
+ "target", "strokeWidth", "curve", ...STROKE_STYLE_PROPERTIES
23
32
  ]);
24
- const EDIT_LINE_OPTIONS = Object.freeze(["target", "strokeWidth", "curve"]);
25
33
 
26
34
  function isRegressionLayer(program, layer, type) {
27
35
  const dataset = findDataset(program, layer.data);
@@ -93,7 +101,8 @@ export const createRegressionBand = action(
93
101
  coordinate: args.coordinate,
94
102
  fill: args.color ?? DEFAULT_COLORS.regressionBand,
95
103
  opacity: args.opacity ?? 0.18,
96
- ...(args.curve === undefined ? {} : { curve: args.curve })
104
+ ...(args.curve === undefined ? {} : { curve: args.curve }),
105
+ ...requestedStrokeDetails(args, "createRegressionBand")
97
106
  })
98
107
  .editSemantic({
99
108
  property: `layer[${id}].encoding.y.title`,
@@ -119,7 +128,10 @@ export const editRegressionBand = action(
119
128
  },
120
129
  function (args = {}) {
121
130
  validateKeys(args, EDIT_BAND_OPTIONS, "editRegressionBand");
122
- const changes = ["color", "opacity", "stroke", "strokeWidth", "curve"];
131
+ const changes = [
132
+ "color", "opacity", "stroke", "strokeWidth", "curve",
133
+ ...STROKE_STYLE_PROPERTIES
134
+ ];
123
135
  if (!changes.some(key => Object.hasOwn(args, key))) {
124
136
  throw new Error(
125
137
  "editRegressionBand requires color, opacity, stroke, strokeWidth, or curve."
@@ -139,7 +151,8 @@ export const editRegressionBand = action(
139
151
  ...(Object.hasOwn(args, "strokeWidth")
140
152
  ? { strokeWidth: args.strokeWidth }
141
153
  : {}),
142
- ...(Object.hasOwn(args, "curve") ? { curve: args.curve } : {})
154
+ ...(Object.hasOwn(args, "curve") ? { curve: args.curve } : {}),
155
+ ...requestedStrokeDetails(args, "editRegressionBand")
143
156
  });
144
157
  }
145
158
  );
@@ -152,8 +165,8 @@ export const editRegressionLine = action(
152
165
  function (args = {}) {
153
166
  validateKeys(args, EDIT_LINE_OPTIONS, "editRegressionLine");
154
167
  if (
155
- !Object.hasOwn(args, "strokeWidth") &&
156
- !Object.hasOwn(args, "curve")
168
+ !["strokeWidth", "curve", ...STROKE_STYLE_PROPERTIES]
169
+ .some(property => Object.hasOwn(args, property))
157
170
  ) {
158
171
  throw new Error("editRegressionLine requires strokeWidth or curve.");
159
172
  }
@@ -168,7 +181,8 @@ export const editRegressionLine = action(
168
181
  ...(Object.hasOwn(args, "strokeWidth")
169
182
  ? { strokeWidth: args.strokeWidth }
170
183
  : {}),
171
- ...(Object.hasOwn(args, "curve") ? { curve: args.curve } : {})
184
+ ...(Object.hasOwn(args, "curve") ? { curve: args.curve } : {}),
185
+ ...requestedStrokeDetails(args, "editRegressionLine")
172
186
  });
173
187
  }
174
188
  );
@@ -186,7 +200,8 @@ export const createRegressionLine = action(
186
200
  id,
187
201
  data: args.data,
188
202
  strokeWidth: args.strokeWidth ?? 3,
189
- ...(args.curve === undefined ? {} : { curve: args.curve })
203
+ ...(args.curve === undefined ? {} : { curve: args.curve }),
204
+ ...requestedStrokeDetails(args, "createRegressionLine")
190
205
  })
191
206
  .encodeX({
192
207
  target: id,
@@ -1,6 +1,7 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateKeys } from "../../core/validation.js";
3
3
  import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
4
+ import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
4
5
  import {
5
6
  findRegressionPoint,
6
7
  inferRegressionGroup,
@@ -49,11 +50,18 @@ export const createRegression = action(
49
50
  if (band !== false) {
50
51
  validateKeys(
51
52
  band,
52
- ["color", "opacity", "stroke", "strokeWidth", "curve"],
53
+ [
54
+ "color", "opacity", "stroke", "strokeWidth", "curve",
55
+ ...STROKE_STYLE_PROPERTIES
56
+ ],
53
57
  "regression band"
54
58
  );
55
59
  }
56
- validateKeys(line, ["strokeWidth", "curve"], "regression line");
60
+ validateKeys(
61
+ line,
62
+ ["strokeWidth", "curve", ...STROKE_STYLE_PROPERTIES],
63
+ "regression line"
64
+ );
57
65
  const colorEncoding = point.encoding?.color;
58
66
  const namespace = point.id;
59
67
  const dataId = `${namespace}RegressionData`;
@@ -9,6 +9,10 @@ import {
9
9
  } from "../../core/validation.js";
10
10
  import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
11
11
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
+ import {
13
+ requestedStrokeDetails,
14
+ STROKE_STYLE_PROPERTIES
15
+ } from "../../grammar/strokeStyle.js";
12
16
  import { planDerivedDataRevision } from
13
17
  "../../materialization/dataProvenance.js";
14
18
  import { findLayer } from "../../selectors/layers.js";
@@ -21,9 +25,12 @@ const OPTIONS = Object.freeze([
21
25
  "confidenceMethod", "level", "confidence", "interval", "band", "line"
22
26
  ]);
23
27
  const BAND_OPTIONS = Object.freeze([
24
- "color", "opacity", "stroke", "strokeWidth", "curve"
28
+ "color", "opacity", "stroke", "strokeWidth", "curve",
29
+ ...STROKE_STYLE_PROPERTIES
30
+ ]);
31
+ const LINE_OPTIONS = Object.freeze([
32
+ "strokeWidth", "curve", ...STROKE_STYLE_PROPERTIES
25
33
  ]);
26
- const LINE_OPTIONS = Object.freeze(["strokeWidth", "curve"]);
27
34
 
28
35
  function resolveRegressionOwner(program, requested) {
29
36
  const eligible = program.semanticSpec.layers.filter(
@@ -80,6 +87,7 @@ function validateBandPatch(value) {
80
87
  if (Object.hasOwn(patch, "curve")) {
81
88
  validateCurveInterpolation(patch.curve);
82
89
  }
90
+ requestedStrokeDetails(patch, "editRegression band");
83
91
  return patch;
84
92
  }
85
93
 
@@ -94,6 +102,7 @@ function validateLinePatch(value) {
94
102
  if (Object.hasOwn(patch, "curve")) {
95
103
  validateCurveInterpolation(patch.curve);
96
104
  }
105
+ requestedStrokeDetails(patch, "editRegression line");
97
106
  return patch;
98
107
  }
99
108
 
@@ -325,7 +334,11 @@ export const editRegression = action(
325
334
  const bandConfig = next.markConfigs[bandId];
326
335
  next = next._withMarkConfig(bandId, {
327
336
  ...bandConfig,
328
- errorBand: { ...bandConfig.errorBand, data: dataId }
337
+ errorBand: {
338
+ ...bandConfig.errorBand,
339
+ source: dataId,
340
+ data: dataId
341
+ }
329
342
  });
330
343
  }
331
344
 
@@ -356,10 +369,7 @@ export const editRegression = action(
356
369
  next = next.rematerializeLineMark({ id: current.lineId });
357
370
  }
358
371
 
359
- if (changesStatistics) {
360
- next = next.releaseDerivedData(revision.release);
361
- }
362
- return next._withMarkConfig(owner.id, {
372
+ next = next._withMarkConfig(owner.id, {
363
373
  ...next.markConfigs[owner.id],
364
374
  regression: {
365
375
  ...current,
@@ -373,6 +383,10 @@ export const editRegression = action(
373
383
  parameters
374
384
  }
375
385
  });
386
+ if (changesStatistics) {
387
+ next = next.releaseDerivedData(revision.release);
388
+ }
389
+ return next;
376
390
  };
377
391
  return applyEdit(this);
378
392
  }
@@ -0,0 +1,7 @@
1
+ import { removeCoordinate, removeData, removeScale } from "./remove.js";
2
+
3
+ export function registerResourceActions(ProgramClass) {
4
+ ProgramClass.prototype.removeData = removeData;
5
+ ProgramClass.prototype.removeScale = removeScale;
6
+ ProgramClass.prototype.removeCoordinate = removeCoordinate;
7
+ }
@@ -0,0 +1,172 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import {
5
+ canonicalResourcePath,
6
+ collectResourceReferences
7
+ } from "../../core/resourceReferences.js";
8
+ import { releaseNamedResourceOwnership } from "../../core/programState.js";
9
+ import { findCoordinate } from "../../selectors/coordinates.js";
10
+ import { findDataset } from "../../selectors/datasets.js";
11
+ import { findLayer } from "../../selectors/layers.js";
12
+ import { findSemanticScale } from "../../selectors/scales.js";
13
+
14
+ const OPTIONS = Object.freeze(["id"]);
15
+
16
+ function dataOwners(program) {
17
+ return Object.entries(program.materializationConfigs.data ?? {}).flatMap(
18
+ ([family, owners]) => Object.entries(owners ?? {}).flatMap(([owner, config]) =>
19
+ typeof config?.current === "string"
20
+ ? [{ family, owner, current: config.current }]
21
+ : []
22
+ )
23
+ );
24
+ }
25
+
26
+ function ownedGeneratedDatasetIds(program) {
27
+ const result = new Set();
28
+ for (const [id, config] of Object.entries(program.markConfigs)) {
29
+ const layer = findLayer(program, id);
30
+ for (const value of [
31
+ config.errorBar?.data,
32
+ config.errorBand?.data,
33
+ config.regression?.dataId,
34
+ config.boxPlot?.summaryId,
35
+ config.boxPlot?.outlierDataId,
36
+ config.gradientPlot?.profileId,
37
+ config.violinPlot?.materialized === true ? layer?.data : undefined,
38
+ config.ecdfPlot?.data,
39
+ config.endpointPlot?.data,
40
+ config.statisticalReference?.dataId
41
+ ]) {
42
+ if (typeof value === "string") result.add(value);
43
+ }
44
+ const dataset = findDataset(program, layer?.data);
45
+ if (dataset?.transform?.[0]?.type === "markFilter") result.add(dataset.id);
46
+ }
47
+ return Object.freeze([...result].sort());
48
+ }
49
+
50
+ function exactResource(program, kind, id) {
51
+ if (kind === "data") return findDataset(program, id);
52
+ if (kind === "scale") return findSemanticScale(program, id);
53
+ return findCoordinate(program, id);
54
+ }
55
+
56
+ function existingKind(program, id) {
57
+ if (findDataset(program, id) !== undefined ||
58
+ dataOwners(program).some(owner => owner.owner === id)) return "data";
59
+ if (findSemanticScale(program, id) !== undefined) return "scale";
60
+ if (findCoordinate(program, id) !== undefined) {
61
+ return "coordinate";
62
+ }
63
+ if (findLayer(program, id) !== undefined) return "mark";
64
+ return undefined;
65
+ }
66
+
67
+ export function resolveResourceOwnership(program, { kind, id }) {
68
+ const resource = exactResource(program, kind, id);
69
+ if (kind !== "data") {
70
+ if (resource !== undefined) return { ownership: "named", resource };
71
+ const other = existingKind(program, id);
72
+ if (other !== undefined) {
73
+ throw new Error(`Resource "${id}" exists as ${other}, not ${kind}.`);
74
+ }
75
+ throw new Error(`Unknown ${kind} "${id}".`);
76
+ }
77
+
78
+ const owners = dataOwners(program).filter(owner => owner.owner === id);
79
+ if (owners.length > 1) {
80
+ throw new Error(`Data resource "${id}" has ambiguous logical ownership.`);
81
+ }
82
+ if (owners.length === 1) {
83
+ const owner = owners[0];
84
+ const current = exactResource(program, "data", owner.current);
85
+ if (current === undefined) {
86
+ throw new Error(`Data owner "${id}" has no current dataset.`);
87
+ }
88
+ return { ownership: "standalone", resource: current, dataOwner: owner };
89
+ }
90
+ if (resource !== undefined) {
91
+ if (ownedGeneratedDatasetIds(program).includes(id) ||
92
+ dataOwners(program).some(owner => owner.current === id)) {
93
+ throw new Error(
94
+ `Dataset "${id}" is chart-owned; use its owning resource action.`
95
+ );
96
+ }
97
+ return { ownership: "named", resource };
98
+ }
99
+ const other = existingKind(program, id);
100
+ if (other !== undefined) {
101
+ throw new Error(`Resource "${id}" exists as ${other}, not data.`);
102
+ }
103
+ throw new Error(`Unknown data "${id}".`);
104
+ }
105
+
106
+ function assertSupportedProgram(program, operation) {
107
+ if (program.compositionSpec !== undefined && program.compositionSpec.type !== "facet") {
108
+ throw new Error(`${operation} supports unit and facet composition programs.`);
109
+ }
110
+ }
111
+
112
+ function formatReference(value) {
113
+ return `${value.ownerKind} "${value.ownerId}" at ` +
114
+ canonicalResourcePath(value.path);
115
+ }
116
+
117
+ export function planResourceRemoval(program, { kind, id, operation }) {
118
+ assertSupportedProgram(program, operation);
119
+ const resolved = resolveResourceOwnership(program, { kind, id });
120
+ const actualId = resolved.resource.id;
121
+ const references = collectResourceReferences(program, { kind, id: actualId });
122
+ const live = references.filter(value => value.strength === "live" && !(
123
+ resolved.ownership === "standalone" &&
124
+ value.ownerKind === "dataOwner" &&
125
+ value.ownerId === resolved.dataOwner.owner &&
126
+ value.path.at(-1) === "current"
127
+ ));
128
+ if (live.length > 0) {
129
+ throw new Error(
130
+ `Cannot remove ${kind} "${id}"; live references: ` +
131
+ live.map(formatReference).join("; ")
132
+ );
133
+ }
134
+ return Object.freeze({
135
+ kind,
136
+ id,
137
+ semanticIds: Object.freeze([actualId]),
138
+ ...(resolved.dataOwner === undefined
139
+ ? {}
140
+ : { dataOwner: Object.freeze({ ...resolved.dataOwner }) })
141
+ });
142
+ }
143
+
144
+ function removeResource(kind, operation, description) {
145
+ return action(
146
+ { op: operation, description, scope: "any" },
147
+ function (args = {}) {
148
+ validateOptionObject(args, OPTIONS, operation);
149
+ const id = validateUserId(args.id, `${operation} id`);
150
+ const plan = planResourceRemoval(this, { kind, id, operation });
151
+ // Release a validated logical owner before removing its physical revision.
152
+ // The primitive then checks the remaining consumers without mistaking the
153
+ // owner's own replay entry for an independently chart-owned resource.
154
+ let next = releaseNamedResourceOwnership(this, plan);
155
+ const selector = kind === "data" ? "dataset" : kind;
156
+ for (const semanticId of plan.semanticIds) {
157
+ next = next.editSemantic({ property: `${selector}[${semanticId}]`, remove: true });
158
+ }
159
+ return next;
160
+ }
161
+ );
162
+ }
163
+
164
+ export const removeData = removeResource(
165
+ "data", "removeData", "Remove one unreferenced named dataset."
166
+ );
167
+ export const removeScale = removeResource(
168
+ "scale", "removeScale", "Remove one unreferenced named scale."
169
+ );
170
+ export const removeCoordinate = removeResource(
171
+ "coordinate", "removeCoordinate", "Remove one unreferenced named coordinate."
172
+ );
@@ -0,0 +1,212 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
5
+ import { findLayer } from "../../selectors/layers.js";
6
+ import { requireSemanticScale } from "../../selectors/scales.js";
7
+ import { findScaleConsumers } from "./consumers/index.js";
8
+
9
+ const DEFINITIONS = Object.freeze({
10
+ editXScale: Object.freeze({
11
+ channel: "x",
12
+ options: Object.freeze([
13
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
14
+ "base", "exponent", "constant", "paddingInner", "paddingOuter",
15
+ "padding", "align", "unknown"
16
+ ])
17
+ }),
18
+ editYScale: Object.freeze({
19
+ channel: "y",
20
+ options: Object.freeze([
21
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
22
+ "base", "exponent", "constant", "paddingInner", "paddingOuter",
23
+ "padding", "align", "unknown"
24
+ ])
25
+ }),
26
+ editThetaScale: Object.freeze({
27
+ channel: "theta",
28
+ options: Object.freeze([
29
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
30
+ "paddingInner", "paddingOuter", "padding", "align"
31
+ ])
32
+ }),
33
+ editRScale: Object.freeze({
34
+ channel: "radius",
35
+ options: Object.freeze([
36
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
37
+ "base", "exponent", "constant", "radialMapping"
38
+ ])
39
+ }),
40
+ editColorScale: Object.freeze({
41
+ channel: "color",
42
+ options: Object.freeze([
43
+ "type", "domain", "range", "clamp", "reverse", "palette",
44
+ "interpolate", "midpoint", "unknown"
45
+ ])
46
+ }),
47
+ editStrokeScale: Object.freeze({
48
+ channel: "stroke",
49
+ targetOnly: true,
50
+ options: Object.freeze([
51
+ "type", "domain", "range", "clamp", "reverse", "palette",
52
+ "interpolate", "midpoint", "unknown"
53
+ ])
54
+ }),
55
+ editSizeScale: Object.freeze({
56
+ channel: "size",
57
+ options: Object.freeze([
58
+ "type", "domain", "range", "unknown", "clamp", "reverse", "base",
59
+ "exponent"
60
+ ])
61
+ }),
62
+ editOpacityScale: Object.freeze({
63
+ channel: "opacity",
64
+ options: Object.freeze([
65
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse", "unknown"
66
+ ])
67
+ }),
68
+ editShapeScale: Object.freeze({
69
+ channel: "shape",
70
+ options: Object.freeze(["type", "domain", "range", "unknown"])
71
+ }),
72
+ editStrokeWidthScale: Object.freeze({
73
+ channel: "strokeWidth",
74
+ options: Object.freeze([
75
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
76
+ "base", "exponent", "constant"
77
+ ])
78
+ }),
79
+ editStrokeDashScale: Object.freeze({
80
+ channel: "strokeDash",
81
+ options: Object.freeze(["type", "domain", "range"])
82
+ })
83
+ });
84
+
85
+ function scaleIdsForLayer(program, layer, channel) {
86
+ if (layer === undefined) return [];
87
+ return [...new Set(allConsumers(program)
88
+ .filter(consumer =>
89
+ consumer.layer.id === layer.id &&
90
+ normalizePositionScaleChannel(consumer.channel) === channel
91
+ )
92
+ .map(consumer => consumer.encoding.scale))];
93
+ }
94
+
95
+ function allConsumers(program) {
96
+ return program.semanticSpec.scales.flatMap(scale =>
97
+ findScaleConsumers(program, scale.id)
98
+ );
99
+ }
100
+
101
+ function channelScaleIds(program, channel) {
102
+ return [...new Set(allConsumers(program)
103
+ .filter(consumer => normalizePositionScaleChannel(consumer.channel) === channel)
104
+ .map(consumer => consumer.encoding.scale))];
105
+ }
106
+
107
+ function requireSingleScale(ids, operation, scope) {
108
+ if (ids.length === 1) return ids[0];
109
+ if (ids.length === 0) {
110
+ throw new Error(`${operation} could not find a ${scope} scale.`);
111
+ }
112
+ throw new Error(`${operation} ${scope} scale is ambiguous; provide id or target.`);
113
+ }
114
+
115
+ function validateScaleChannel(program, id, channel, operation) {
116
+ requireSemanticScale(program, id);
117
+ const consumers = findScaleConsumers(program, id);
118
+ if (consumers.length === 0) {
119
+ throw new Error(`${operation} requires a scale bound to the ${channel} channel.`);
120
+ }
121
+ const compatible = ["color", "stroke"].includes(channel)
122
+ ? new Set(["color", "stroke"])
123
+ : new Set([channel]);
124
+ if (!consumers.some(consumer =>
125
+ normalizePositionScaleChannel(consumer.channel) === channel
126
+ ) || consumers.some(consumer =>
127
+ !compatible.has(normalizePositionScaleChannel(consumer.channel))
128
+ )) {
129
+ throw new Error(
130
+ `Scale "${id}" must include a ${channel} channel consumer and no incompatible channel consumers.`
131
+ );
132
+ }
133
+ return id;
134
+ }
135
+
136
+ function resolveScaleId(program, args, channel, operation) {
137
+ const explicitId = args.id === undefined
138
+ ? undefined
139
+ : validateUserId(args.id, "Scale id");
140
+ const target = args.target === undefined
141
+ ? undefined
142
+ : validateUserId(args.target, "Mark id");
143
+ const targetLayer = target === undefined ? undefined : findLayer(program, target);
144
+ if (target !== undefined && targetLayer === undefined) {
145
+ throw new Error(`Unknown mark target "${target}".`);
146
+ }
147
+ const targetId = targetLayer === undefined
148
+ ? undefined
149
+ : requireSingleScale(scaleIdsForLayer(program, targetLayer, channel), operation, `target ${channel}`);
150
+ if (explicitId !== undefined && targetId !== undefined && explicitId !== targetId) {
151
+ throw new Error(`${operation} id "${explicitId}" does not match target "${target}" scale "${targetId}".`);
152
+ }
153
+ if (explicitId !== undefined) {
154
+ return validateScaleChannel(program, explicitId, channel, operation);
155
+ }
156
+ if (targetId !== undefined) return validateScaleChannel(program, targetId, channel, operation);
157
+
158
+ const current = findLayer(program, program.context.currentMark);
159
+ const currentIds = scaleIdsForLayer(program, current, channel);
160
+ if (currentIds.length > 1) {
161
+ throw new Error(`${operation} current-mark ${channel} scale is ambiguous; provide id or target.`);
162
+ }
163
+ if (currentIds.length === 1) {
164
+ return validateScaleChannel(program, currentIds[0], channel, operation);
165
+ }
166
+ return validateScaleChannel(
167
+ program,
168
+ requireSingleScale(channelScaleIds(program, channel), operation, channel),
169
+ channel,
170
+ operation
171
+ );
172
+ }
173
+
174
+ function createChannelScaleEditor(operation, definition) {
175
+ return action(
176
+ {
177
+ op: operation,
178
+ description: `Edit the scale bound to the ${definition.channel} channel.`
179
+ },
180
+ function (args = {}) {
181
+ validateOptionObject(
182
+ args,
183
+ [...(definition.targetOnly === true ? [] : ["id"]), "target", ...definition.options],
184
+ operation
185
+ );
186
+ if (definition.targetOnly === true && args.target === undefined) {
187
+ throw new Error(`${operation} requires target.`);
188
+ }
189
+ if (!definition.options.some(property => Object.hasOwn(args, property))) {
190
+ throw new Error(`${operation} requires at least one editable property.`);
191
+ }
192
+ const id = resolveScaleId(this, args, definition.channel, operation);
193
+ const patch = Object.fromEntries(
194
+ Object.entries(args).filter(([property]) => property !== "target")
195
+ );
196
+ return this.editScale({ ...patch, id });
197
+ }
198
+ );
199
+ }
200
+
201
+ export const channelScaleEditors = Object.freeze(Object.fromEntries(
202
+ Object.entries(DEFINITIONS).map(([operation, definition]) => [
203
+ operation,
204
+ createChannelScaleEditor(operation, definition)
205
+ ])
206
+ ));
207
+
208
+ export function registerChannelScaleActions(ProgramClass) {
209
+ for (const [operation, editor] of Object.entries(channelScaleEditors)) {
210
+ ProgramClass.prototype[operation] = editor;
211
+ }
212
+ }
@@ -43,7 +43,7 @@ export function requireConsumerDataset(program, consumer) {
43
43
  }
44
44
 
45
45
  export function isDirectCategoricalConsumer(consumer) {
46
- return ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(
46
+ return ["color", "stroke", "strokeDash", "xOffset", "yOffset", "shape"].includes(
47
47
  consumer.channel
48
48
  ) && ["nominal", "ordinal"].includes(consumer.encoding.fieldType);
49
49
  }
@@ -45,7 +45,7 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
45
45
  }
46
46
  const { layer, channel } = consumer;
47
47
  const encoding = layer.encoding ?? {};
48
- const appearance = ["strokeWidth", "opacity"].includes(channel);
48
+ const appearance = ["stroke", "strokeWidth", "opacity"].includes(channel);
49
49
  if (
50
50
  layer.mark?.type === "line" &&
51
51
  ((["x", "y"].includes(channel) && encoding.x !== undefined &&
@@ -69,7 +69,8 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
69
69
  dataset.values,
70
70
  derived.series,
71
71
  consumer.encoding.field,
72
- channel
72
+ channel,
73
+ consumer.encoding
73
74
  )
74
75
  : channel === "x" ? derived.xValues : derived.yValues
75
76
  };
@@ -9,6 +9,8 @@ import {
9
9
  } from "./common.js";
10
10
  import { resolveMarkFamilyConsumerValues } from "./families.js";
11
11
  import { CATEGORY_ORDER_CHANNELS, resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
12
+ import { resolveHistogramInput } from
13
+ "../../../materialization/bars/histogram.js";
12
14
 
13
15
  export { findScale, findScaleConsumers } from "./common.js";
14
16
  export {
@@ -16,8 +18,7 @@ export {
16
18
  resolveSeriesLayoutScaleValues
17
19
  } from "./seriesLayout.js";
18
20
 
19
- export function resolveConsumerValues(program, consumer) {
20
- const dataset = requireConsumerDataset(program, consumer);
21
+ export function resolveConsumerValuesForDataset(program, consumer, dataset) {
21
22
  if (consumer.layer.mark.type === "rect") return resolveMarkFamilyConsumerValues(program, consumer, dataset).values;
22
23
  if (Object.hasOwn(consumer.encoding, "datum")) {
23
24
  if (consumer.layer.mark.type === "area") {
@@ -49,6 +50,14 @@ export function resolveConsumerValues(program, consumer) {
49
50
  );
50
51
  }
51
52
  const scale = findScale(program, consumer.encoding.scale);
53
+ if (
54
+ consumer.layer.mark.type === "bar" &&
55
+ consumer.channel === "x" &&
56
+ consumer.encoding.bin !== undefined &&
57
+ consumer.encoding.weight !== undefined
58
+ ) {
59
+ return resolveHistogramInput(dataset, consumer.encoding).values;
60
+ }
52
61
  if (isDirectCategoricalConsumer(consumer)) {
53
62
  return readConsumerFieldValues(program, consumer, dataset, scale);
54
63
  }
@@ -58,6 +67,14 @@ export function resolveConsumerValues(program, consumer) {
58
67
  : readConsumerFieldValues(program, consumer, dataset, scale);
59
68
  }
60
69
 
70
+ export function resolveConsumerValues(program, consumer) {
71
+ return resolveConsumerValuesForDataset(
72
+ program,
73
+ consumer,
74
+ requireConsumerDataset(program, consumer)
75
+ );
76
+ }
77
+
61
78
  export function resolveConsumerCategoryOrder(program, consumer) {
62
79
  const order = consumer.encoding.categoryOrder;
63
80
  if (order === undefined) return undefined;