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
@@ -7,9 +7,12 @@ import {
7
7
  } from "../../../core/validation.js";
8
8
  import { deriveArcSectors } from "../../../grammar/arcs.js";
9
9
  import { buildAnnularSectorCommands } from "../../../grammar/polarPaths.js";
10
- import { resolvePolarFrame } from "../../../grammar/polar.js";
11
- import { mapOrdinalValues } from "../../../grammar/scales/index.js";
12
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
10
+ import {
11
+ mapOrdinalValues,
12
+ readScaleField
13
+ } from "../../../grammar/scales/index.js";
14
+ import { resolveCoordinatePolarFrame } from
15
+ "../../../materialization/coordinateBounds.js";
13
16
  import {
14
17
  canMaterializeArc,
15
18
  getSourceDependentMarkSteps
@@ -19,7 +22,10 @@ import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
19
22
  import { DEFAULT_COLORS } from "../../../theme/defaults.js";
20
23
  import { resolveMarkGraphicPlacement } from
21
24
  "../../../materialization/graphicHierarchy.js";
25
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
22
26
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
27
+ import { mapScaleConsumerValues } from
28
+ "../../../materialization/scales/map.js";
23
29
  import {
24
30
  assertMarkAvailable,
25
31
  applyLayeredMarkInheritance,
@@ -30,13 +36,18 @@ import {
30
36
  resolveMarkId,
31
37
  validateMarkOptions
32
38
  } from "../shared.js";
39
+ import {
40
+ requestedStrokeDetails,
41
+ STROKE_STYLE_PROPERTIES
42
+ } from "../../../grammar/strokeStyle.js";
33
43
 
34
44
  const ARC_OPTIONS = Object.freeze([
35
- "innerRadius", "padAngle", "fill", "opacity", "stroke", "strokeWidth"
45
+ "innerRadius", "padAngle", "fill", "opacity", "stroke", "strokeWidth",
46
+ ...STROKE_STYLE_PROPERTIES
36
47
  ]);
37
48
  const CREATE_OPTIONS = Object.freeze(["id", "data", ...ARC_OPTIONS]);
38
49
  const EDIT_OPTIONS = Object.freeze(["target", ...ARC_OPTIONS]);
39
- const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
50
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
40
51
 
41
52
  function validateInnerRadius(value) {
42
53
  if (!Number.isFinite(value) || value < 0 || value >= 1) {
@@ -47,9 +58,14 @@ function validateInnerRadius(value) {
47
58
  return value;
48
59
  }
49
60
 
50
- function normalizeConfig(args, previous = {}, { allowStrokeRemoval = false } = {}) {
61
+ function normalizeConfig(
62
+ args,
63
+ previous = {},
64
+ { allowStrokeRemoval = false, strokeDetails = {} } = {}
65
+ ) {
51
66
  let config = {
52
67
  ...previous,
68
+ ...strokeDetails,
53
69
  ...(Object.hasOwn(args, "innerRadius")
54
70
  ? { innerRadius: validateInnerRadius(args.innerRadius), innerRadiusExplicit: true }
55
71
  : {}),
@@ -93,6 +109,7 @@ const createArcMark = action(
93
109
  },
94
110
  function (args = {}) {
95
111
  validateMarkOptions(args, CREATE_OPTIONS, "createArcMark");
112
+ const strokeDetails = requestedStrokeDetails(args, "createArcMark");
96
113
  const id = resolveMarkId(this, args.id, {
97
114
  defaultId: "arc",
98
115
  label: "Arc mark id",
@@ -112,7 +129,7 @@ const createArcMark = action(
112
129
  opacity: 1,
113
130
  stroke: "#ffffff",
114
131
  strokeWidth: 1
115
- });
132
+ }, { strokeDetails });
116
133
  let created = this
117
134
  .editSemantic({ property: `layer[${id}].mark.type`, value: "arc" })
118
135
  .editSemantic({ property: `layer[${id}].data`, value: data });
@@ -148,6 +165,9 @@ const rematerializeArcMark = action(
148
165
  },
149
166
  function (args = {}) {
150
167
  validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeArcMark");
168
+ if (args.scales !== undefined && typeof args.scales !== "boolean") {
169
+ throw new TypeError("rematerializeArcMark scales must be a boolean.");
170
+ }
151
171
  const id = validateUserId(args.id, "Arc mark id");
152
172
  const highlighted = rematerializeHighlightBaseline(this, {
153
173
  target: id,
@@ -163,15 +183,21 @@ const rematerializeArcMark = action(
163
183
  const thetaScaleId = layer.encoding.theta.scale;
164
184
  const radiusScaleId = layer.encoding?.radius?.scale;
165
185
  const colorScaleId = layer.encoding?.color?.scale;
166
- let resolved = this.rematerializeScale({ id: thetaScaleId });
167
- if (radiusScaleId !== undefined) {
186
+ const strokeScaleId = layer.encoding?.stroke?.scale;
187
+ let resolved = args.scales === false
188
+ ? this
189
+ : this.rematerializeScale({ id: thetaScaleId });
190
+ if (radiusScaleId !== undefined && args.scales !== false) {
168
191
  resolved = resolved.rematerializeScale({ id: radiusScaleId });
169
192
  }
170
- if (colorScaleId !== undefined) {
193
+ if (colorScaleId !== undefined && args.scales !== false) {
171
194
  resolved = resolved.rematerializeScale({ id: colorScaleId });
172
195
  }
196
+ if (strokeScaleId !== undefined && args.scales !== false) {
197
+ resolved = resolved.rematerializeScale({ id: strokeScaleId });
198
+ }
173
199
  const config = resolved.markConfigs[id] ?? {};
174
- const frame = resolvePolarFrame(resolveGraphicBounds(resolved));
200
+ const frame = resolveCoordinatePolarFrame(resolved, layer.coordinate);
175
201
  const derived = deriveArcSectors(dataset.values, layer, {
176
202
  thetaScale: resolved.resolvedScales[thetaScaleId],
177
203
  ...(radiusScaleId === undefined
@@ -195,14 +221,37 @@ const rematerializeArcMark = action(
195
221
  resolved.resolvedScales[colorScaleId].domain,
196
222
  resolved.resolvedScales[colorScaleId].range
197
223
  );
224
+ const strokeEncoding = layer.encoding?.stroke;
225
+ const strokeValues = strokeEncoding === undefined ? undefined : derived.sectors.map(
226
+ sector => {
227
+ const values = readScaleField(
228
+ sector.sourceIndices.map(index => dataset.values[index]),
229
+ strokeEncoding.field,
230
+ strokeEncoding.fieldType,
231
+ { temporalUnit: strokeEncoding.temporalUnit }
232
+ );
233
+ if (new Set(values).size !== 1) {
234
+ throw new Error("Arc stroke requires one value within each sector.");
235
+ }
236
+ return values[0];
237
+ }
238
+ );
239
+ const strokes = strokeValues === undefined ? undefined : mapScaleConsumerValues(
240
+ strokeValues,
241
+ resolved.resolvedScales[strokeScaleId],
242
+ "stroke"
243
+ );
198
244
  return editMarkGraphic(resolved, id, {
199
245
  length: commands.length,
200
246
  commands,
201
247
  fill: fills,
202
248
  opacity: config.opacity ?? 1,
203
- stroke: config.stroke === false ? "transparent" : config.stroke ?? "#ffffff",
249
+ stroke: strokes ?? (
250
+ config.stroke === false ? "transparent" : config.stroke ?? "#ffffff"
251
+ ),
204
252
  strokeWidth: config.stroke === false ? 0 : config.strokeWidth ?? 1,
205
- strokeDash: commands.map(() => [])
253
+ strokeDash: commands.map(() => []),
254
+ ...requestedStrokeDetails(config, "Arc mark")
206
255
  });
207
256
  }
208
257
  );
@@ -214,6 +263,7 @@ const editArcMark = action(
214
263
  },
215
264
  function (args = {}) {
216
265
  validateMarkOptions(args, EDIT_OPTIONS, "editArcMark");
266
+ const strokeDetails = requestedStrokeDetails(args, "editArcMark");
217
267
  if (Object.keys(args).every(key => key === "target")) {
218
268
  throw new Error(
219
269
  "editArcMark requires innerRadius, padAngle, fill, opacity, stroke, or strokeWidth."
@@ -230,6 +280,11 @@ const editArcMark = action(
230
280
  if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
231
281
  throw new Error("editArcMark fill cannot be combined with a color encoding.");
232
282
  }
283
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
284
+ throw new Error(
285
+ "editArcMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
286
+ );
287
+ }
233
288
  if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
234
289
  throw new Error(
235
290
  "editArcMark cannot set strokeWidth while removing stroke."
@@ -245,15 +300,18 @@ const editArcMark = action(
245
300
  const next = this._withMarkConfig(
246
301
  layer.id,
247
302
  normalizeConfig(args, this.markConfigs[layer.id], {
248
- allowStrokeRemoval: true
303
+ allowStrokeRemoval: true,
304
+ strokeDetails
249
305
  })
250
306
  );
251
- if (!canMaterializeArc(next, layer)) return next;
252
- let rematerialized = next.rematerializeArcMark({ id: layer.id });
253
- for (const step of getSourceDependentMarkSteps(rematerialized, layer.id)) {
254
- rematerialized = rematerialized[step.op](step.args);
307
+ let materialized = next;
308
+ if (canMaterializeArc(next, layer)) {
309
+ materialized = next.rematerializeArcMark({ id: layer.id });
310
+ for (const step of getSourceDependentMarkSteps(materialized, layer.id)) {
311
+ materialized = materialized[step.op](step.args);
312
+ }
255
313
  }
256
- return rematerialized;
314
+ return rematerializeExistingLegend(materialized);
257
315
  }
258
316
  );
259
317
 
@@ -22,14 +22,20 @@ import { findLayer } from "../../../selectors/layers.js";
22
22
  import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
23
23
  import { resolveEligibleLayer } from "../../../selectors/layers.js";
24
24
  import { canMaterializeArea } from "../../../materialization/marks/index.js";
25
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
25
26
  import { findUpstreamTransform } from "../../../materialization/dataProvenance.js";
26
27
  import { resolveMarkGraphicPlacement } from
27
28
  "../../../materialization/graphicHierarchy.js";
28
29
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
29
30
  import { resolveAreaMaterialization } from "./materialize.js";
31
+ import {
32
+ requestedStrokeDetails,
33
+ STROKE_STYLE_PROPERTIES
34
+ } from "../../../grammar/strokeStyle.js";
30
35
 
31
36
  const AREA_OPTIONS = Object.freeze([
32
- "fill", "opacity", "stroke", "strokeWidth", "curve"
37
+ "fill", "opacity", "stroke", "strokeWidth", "curve",
38
+ ...STROKE_STYLE_PROPERTIES
33
39
  ]);
34
40
  const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...AREA_OPTIONS]);
35
41
  const EDIT_OPTIONS = Object.freeze(["target", "missing", ...AREA_OPTIONS]);
@@ -58,6 +64,7 @@ const createAreaMark = action(
58
64
  },
59
65
  function (args = {}) {
60
66
  validateMarkOptions(args, CREATE_OPTIONS, "createAreaMark");
67
+ const strokeDetails = requestedStrokeDetails(args, "createAreaMark");
61
68
  const id = resolveMarkId(this, args.id, {
62
69
  defaultId: "area",
63
70
  label: "Area mark id",
@@ -95,7 +102,8 @@ const createAreaMark = action(
95
102
  fill,
96
103
  opacity,
97
104
  ...(Object.hasOwn(args, "curve") ? { curve } : {}),
98
- ...(stroke === undefined ? {} : { stroke, strokeWidth })
105
+ ...(stroke === undefined ? {} : { stroke, strokeWidth }),
106
+ ...strokeDetails
99
107
  });
100
108
  return materializeInheritedMark(created, id);
101
109
  }
@@ -179,6 +187,7 @@ const rematerializeAreaMark = action(
179
187
  );
180
188
  }
181
189
  const colorEncoding = layer.encoding?.color;
190
+ const strokeEncoding = layer.encoding?.stroke;
182
191
  let resolved = args.scales === false
183
192
  ? this
184
193
  : this
@@ -187,8 +196,11 @@ const rematerializeAreaMark = action(
187
196
  if (args.scales !== false && colorEncoding?.scale !== undefined) {
188
197
  resolved = resolved.rematerializeScale({ id: colorEncoding.scale });
189
198
  }
199
+ if (args.scales !== false && strokeEncoding?.scale !== undefined) {
200
+ resolved = resolved.rematerializeScale({ id: strokeEncoding.scale });
201
+ }
190
202
  const config = this.markConfigs[id];
191
- const { paths, fills } = resolveAreaMaterialization({
203
+ const { paths, fills, strokes } = resolveAreaMaterialization({
192
204
  rows: dataset.values,
193
205
  layer,
194
206
  densityTransform,
@@ -196,7 +208,8 @@ const rematerializeAreaMark = action(
196
208
  config
197
209
  });
198
210
  const existingChildren = resolved.graphicSpec.objects[id].items ?? [];
199
- const hasOutline = config.stroke !== undefined || config.strokeFromFill === true;
211
+ const hasOutline = strokes !== undefined || config.stroke !== undefined ||
212
+ config.strokeFromFill === true;
200
213
  const removesOutline = !hasOutline && existingChildren.some(
201
214
  child => child.properties.stroke !== undefined
202
215
  );
@@ -210,9 +223,10 @@ const rematerializeAreaMark = action(
210
223
  fill: fills,
211
224
  opacity: config.opacity,
212
225
  ...(hasOutline ? {
213
- stroke: config.strokeFromFill === true ? fills : config.stroke,
214
- strokeWidth: config.strokeWidth
215
- } : {})
226
+ stroke: strokes ?? (config.strokeFromFill === true ? fills : config.stroke),
227
+ strokeWidth: config.strokeWidth ?? (strokes === undefined ? undefined : 1)
228
+ } : {}),
229
+ ...requestedStrokeDetails(config, "Area mark")
216
230
  });
217
231
  }
218
232
  );
@@ -224,7 +238,11 @@ const editAreaMark = action(
224
238
  },
225
239
  function (args = {}) {
226
240
  validateMarkOptions(args, EDIT_OPTIONS, "editAreaMark");
227
- const changes = ["fill", "opacity", "stroke", "strokeWidth", "curve", "missing"];
241
+ const strokeDetails = requestedStrokeDetails(args, "editAreaMark");
242
+ const changes = [
243
+ "fill", "opacity", "stroke", "strokeWidth", "curve", "missing",
244
+ ...STROKE_STYLE_PROPERTIES
245
+ ];
228
246
  if (!changes.some(key => Object.hasOwn(args, key))) {
229
247
  throw new Error(
230
248
  "editAreaMark requires fill, opacity, stroke, strokeWidth, or curve."
@@ -243,11 +261,17 @@ const editAreaMark = action(
243
261
  "editAreaMark fill cannot be combined with a color encoding."
244
262
  );
245
263
  }
264
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
265
+ throw new Error(
266
+ "editAreaMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
267
+ );
268
+ }
246
269
  if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
247
270
  throw new Error("editAreaMark cannot set strokeWidth while removing stroke.");
248
271
  }
249
272
 
250
273
  let config = { ...this.markConfigs[layer.id] };
274
+ Object.assign(config, strokeDetails);
251
275
  if (Object.hasOwn(args, "fill")) {
252
276
  config.fill = validateNonEmptyString(args.fill, "Area fill");
253
277
  if (config.errorBand !== undefined) config.errorBand = { ...config.errorBand, fill: config.fill };
@@ -298,9 +322,10 @@ const editAreaMark = action(
298
322
  }
299
323
  next = next.editSemantic({ property: `layer[${layer.id}].mark.missing`, value: missing });
300
324
  }
301
- return canMaterializeArea(next, layer)
325
+ const materialized = canMaterializeArea(next, layer)
302
326
  ? next.rematerializeAreaMark({ id: layer.id })
303
327
  : next;
328
+ return rematerializeExistingLegend(materialized);
304
329
  }
305
330
  );
306
331
 
@@ -8,6 +8,8 @@ import {
8
8
  mapContinuousScaleValues,
9
9
  mapOrdinalValues
10
10
  } from "../../../grammar/scales/index.js";
11
+ import { mapScaleConsumerValues } from
12
+ "../../../materialization/scales/map.js";
11
13
  import { buildAreaCurvePathCommands } from
12
14
  "../../../grammar/curveCommands.js";
13
15
  import { buildLinearPathCommands } from
@@ -171,5 +173,19 @@ export function resolveAreaMaterialization({
171
173
  resolvedScales[colorEncoding.scale].domain,
172
174
  resolvedScales[colorEncoding.scale].range
173
175
  );
174
- return { paths, fills };
176
+ const strokeEncoding = layer.encoding?.stroke;
177
+ const strokes = strokeEncoding?.scale === undefined
178
+ ? undefined
179
+ : mapScaleConsumerValues(
180
+ derivePathSeriesFieldValues(
181
+ rows,
182
+ derived.series,
183
+ strokeEncoding.field,
184
+ "stroke",
185
+ strokeEncoding
186
+ ),
187
+ resolvedScales[strokeEncoding.scale],
188
+ "stroke"
189
+ );
190
+ return { paths, fills, strokes };
175
191
  }
@@ -10,9 +10,14 @@ import {
10
10
  } from "../shared.js";
11
11
  import { resolveMarkGraphicPlacement } from
12
12
  "../../../materialization/graphicHierarchy.js";
13
+ import { requestedRectStyleDetails } from
14
+ "../../../grammar/roundedRect.js";
15
+ import { STROKE_STYLE_PROPERTIES } from
16
+ "../../../grammar/strokeStyle.js";
13
17
 
14
18
  const CREATE_OPTIONS = Object.freeze([
15
- "id", "data", "fill", "opacity", "stroke", "strokeWidth"
19
+ "id", "data", "fill", "opacity", "stroke", "strokeWidth",
20
+ "cornerRadius", ...STROKE_STYLE_PROPERTIES
16
21
  ]);
17
22
 
18
23
  export const createBarMark = action(
@@ -22,6 +27,7 @@ export const createBarMark = action(
22
27
  },
23
28
  function (args = {}) {
24
29
  validateMarkOptions(args, CREATE_OPTIONS, "createBarMark");
30
+ requestedRectStyleDetails(args, "createBarMark");
25
31
  const id = resolveMarkId(this, args.id, {
26
32
  defaultId: "bar",
27
33
  label: "Bar mark id",
@@ -56,7 +62,10 @@ export const createBarMark = action(
56
62
  });
57
63
  created = materializeInheritedMark(created, id);
58
64
  const appearance = Object.fromEntries(
59
- ["fill", "opacity", "stroke", "strokeWidth"]
65
+ [
66
+ "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
67
+ ...STROKE_STYLE_PROPERTIES
68
+ ]
60
69
  .filter(property => Object.hasOwn(args, property))
61
70
  .map(property => [property, args[property]])
62
71
  );
@@ -9,9 +9,15 @@ import { canMaterializeBar } from "../../../materialization/marks/index.js";
9
9
  import { DEFAULT_BAR_STROKE_WIDTH } from "../../../materialization/bars/resolve.js";
10
10
  import { resolveEligibleLayer } from "../../../selectors/layers.js";
11
11
  import { validateMarkOptions } from "../shared.js";
12
+ import { requestedRectStyleDetails } from
13
+ "../../../grammar/roundedRect.js";
14
+ import { STROKE_STYLE_PROPERTIES } from
15
+ "../../../grammar/strokeStyle.js";
16
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
12
17
 
13
18
  const EDIT_OPTIONS = Object.freeze([
14
- "target", "fill", "opacity", "stroke", "strokeWidth"
19
+ "target", "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
20
+ ...STROKE_STYLE_PROPERTIES
15
21
  ]);
16
22
 
17
23
  export const editBarMark = action(
@@ -21,7 +27,11 @@ export const editBarMark = action(
21
27
  },
22
28
  function (args = {}) {
23
29
  validateMarkOptions(args, EDIT_OPTIONS, "editBarMark");
24
- const changes = ["fill", "opacity", "stroke", "strokeWidth"];
30
+ const styleDetails = requestedRectStyleDetails(args, "editBarMark");
31
+ const changes = [
32
+ "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
33
+ ...STROKE_STYLE_PROPERTIES
34
+ ];
25
35
  if (!changes.some(key => Object.hasOwn(args, key))) {
26
36
  throw new Error(
27
37
  "editBarMark requires fill, opacity, stroke, or strokeWidth."
@@ -40,6 +50,11 @@ export const editBarMark = action(
40
50
  "editBarMark fill cannot be combined with a color encoding."
41
51
  );
42
52
  }
53
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
54
+ throw new Error(
55
+ "editBarMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
56
+ );
57
+ }
43
58
  if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
44
59
  throw new Error(
45
60
  "editBarMark cannot set strokeWidth while removing stroke."
@@ -48,6 +63,7 @@ export const editBarMark = action(
48
63
 
49
64
  const config = { ...this.markConfigs[layer.id] };
50
65
  const appearance = { ...config.barAppearance };
66
+ Object.assign(appearance, styleDetails);
51
67
  if (Object.hasOwn(args, "fill")) {
52
68
  appearance.fill = validateNonEmptyString(args.fill, "Bar fill");
53
69
  }
@@ -83,8 +99,9 @@ export const editBarMark = action(
83
99
  ...config,
84
100
  barAppearance: appearance
85
101
  });
86
- return canMaterializeBar(next, layer)
102
+ const materialized = canMaterializeBar(next, layer)
87
103
  ? next.rematerializeBarMark({ id: layer.id })
88
104
  : next;
105
+ return rematerializeExistingLegend(materialized);
89
106
  }
90
107
  );
@@ -12,10 +12,26 @@ import {
12
12
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
13
13
  import { offsetCategoryRectangles } from
14
14
  "../../../materialization/categorySlotOffset.js";
15
+ import { materializeRectItem } from "../../../grammar/roundedRect.js";
16
+ import { STROKE_STYLE_PROPERTIES } from
17
+ "../../../grammar/strokeStyle.js";
18
+ import { replaceGraphicItems } from "../../primitives/editGraphics.js";
15
19
 
16
20
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
17
21
 
18
22
  function editRectangles(program, id, rectangles) {
23
+ const appearance = program.markConfigs[id]?.barAppearance ?? {};
24
+ const hasShapeStyle = ["cornerRadius", ...STROKE_STYLE_PROPERTIES]
25
+ .some(property => Object.hasOwn(appearance, property));
26
+ if (hasShapeStyle) {
27
+ const radius = appearance.cornerRadius ?? 0;
28
+ return replaceGraphicItems(
29
+ program,
30
+ id,
31
+ "rect",
32
+ rectangles.map(rectangle => materializeRectItem(rectangle, radius))
33
+ );
34
+ }
19
35
  let next = program
20
36
  .editGraphics({ target: id, property: "length", value: rectangles.length })
21
37
  .editGraphics({
@@ -81,8 +97,8 @@ export const rematerializeBarMark = action(
81
97
  const highlighted = rematerializeHighlightBaseline(this, {
82
98
  target: id,
83
99
  operation: "rematerializeBarMark",
84
- resetProperty: "length",
85
- resetValue: 0
100
+ resetProperty: "items",
101
+ resetValue: []
86
102
  });
87
103
  if (highlighted !== undefined) return highlighted;
88
104
  const required = requireCompleteBar(this, id);
@@ -97,6 +113,10 @@ export const rematerializeBarMark = action(
97
113
  if (colorScaleId !== undefined && args.scales !== false) {
98
114
  resolved = resolved.rematerializeScale({ id: colorScaleId });
99
115
  }
116
+ const strokeScaleId = required.layer.encoding?.stroke?.scale;
117
+ if (strokeScaleId !== undefined && args.scales !== false) {
118
+ resolved = resolved.rematerializeScale({ id: strokeScaleId });
119
+ }
100
120
 
101
121
  if (required.materialization === "aggregate") {
102
122
  const offsetChannel = resolveBarOffsetChannel(required.layer);
@@ -1,5 +1,10 @@
1
1
  import { removeMark } from "./remove.js";
2
- import { createReferenceLine, createReferenceBand } from "./references.js";
2
+ import {
3
+ createReferenceLine,
4
+ createReferenceBand,
5
+ materializeStatisticalReferenceData,
6
+ rematerializeStatisticalReference
7
+ } from "./references.js";
3
8
  import { registerRuleMarkActions } from "./rule/index.js";
4
9
  import { registerAreaMarkActions } from "./area/index.js";
5
10
  import { registerTickMarkActions } from "./tick/index.js";
@@ -29,4 +34,8 @@ export function registerMarkActions(ProgramClass) {
29
34
  ProgramClass.prototype.removeMark = removeMark;
30
35
  ProgramClass.prototype.createReferenceLine = createReferenceLine;
31
36
  ProgramClass.prototype.createReferenceBand = createReferenceBand;
37
+ ProgramClass.prototype.materializeStatisticalReferenceData =
38
+ materializeStatisticalReferenceData;
39
+ ProgramClass.prototype.rematerializeStatisticalReference =
40
+ rematerializeStatisticalReference;
32
41
  }