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
@@ -19,7 +19,11 @@ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
19
19
  import { findDataset } from "../../../selectors/datasets.js";
20
20
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
21
21
  import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
22
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
22
+ import {
23
+ resolveCoordinateBounds,
24
+ resolveCoordinatePolarFrame
25
+ } from "../../../materialization/coordinateBounds.js";
26
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
23
27
  import { canMaterializeLine } from "../../../materialization/marks/index.js";
24
28
  import { resolveMarkGraphicPlacement } from
25
29
  "../../../materialization/graphicHierarchy.js";
@@ -31,14 +35,20 @@ import {
31
35
  } from "./materialize.js";
32
36
  import { resolveLineBins } from "../../../grammar/lineSeries.js";
33
37
  import { requireSemanticScale } from "../../../selectors/scales.js";
38
+ import {
39
+ requestedStrokeDetails,
40
+ STROKE_STYLE_PROPERTIES
41
+ } from "../../../grammar/strokeStyle.js";
34
42
 
35
43
  const DEFAULT_LINE_STROKE = DEFAULT_COLORS.mark;
36
44
  const DEFAULT_LINE_WIDTH = 2;
37
45
  const CREATE_OPTIONS = Object.freeze([
38
- "id", "data", "stroke", "strokeWidth", "opacity", "curve", "closed"
46
+ "id", "data", "stroke", "strokeWidth", "opacity", "curve", "closed",
47
+ ...STROKE_STYLE_PROPERTIES
39
48
  ]);
40
49
  const EDIT_OPTIONS = Object.freeze([
41
- "target", "stroke", "strokeWidth", "opacity", "curve", "closed"
50
+ "target", "stroke", "strokeWidth", "opacity", "curve", "closed",
51
+ ...STROKE_STYLE_PROPERTIES
42
52
  ]);
43
53
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
44
54
 
@@ -71,14 +81,15 @@ function validatePolarLineConfig(layer, config) {
71
81
  }
72
82
  }
73
83
 
74
- function applyLineMaterialization(program, id, materialization) {
84
+ function applyLineMaterialization(program, id, materialization, config) {
75
85
  return editMarkGraphic(program, id, {
76
86
  length: materialization.commands.length,
77
87
  commands: materialization.commands,
78
88
  stroke: materialization.strokes,
79
89
  strokeWidth: materialization.strokeWidths,
80
90
  strokeDash: materialization.strokeDashes,
81
- ...(materialization.opacities === undefined ? {} : { opacity: materialization.opacities })
91
+ ...(materialization.opacities === undefined ? {} : { opacity: materialization.opacities }),
92
+ ...requestedStrokeDetails(config, "Line mark")
82
93
  });
83
94
  }
84
95
 
@@ -89,6 +100,7 @@ const createLineMark = action(
89
100
  },
90
101
  function (args = {}) {
91
102
  validateMarkOptions(args, CREATE_OPTIONS, "createLineMark");
103
+ const strokeDetails = requestedStrokeDetails(args, "createLineMark");
92
104
  const id = resolveMarkId(this, args.id, {
93
105
  defaultId: "line",
94
106
  label: "Line mark id",
@@ -139,7 +151,8 @@ const createLineMark = action(
139
151
  {
140
152
  ...(Object.hasOwn(args, "strokeWidth") ? { strokeWidth } : {}),
141
153
  ...(Object.hasOwn(args, "curve") ? { curve } : {}),
142
- ...(Object.hasOwn(args, "closed") ? { closed } : {})
154
+ ...(Object.hasOwn(args, "closed") ? { closed } : {}),
155
+ ...strokeDetails
143
156
  }
144
157
  );
145
158
  created = materializeInheritedMark(created, id);
@@ -214,7 +227,7 @@ const rematerializeLineMark = action(
214
227
  }
215
228
  let resolved = this;
216
229
  if (args.scales !== false) {
217
- const appearance = ["color", "strokeDash", "strokeWidth", "opacity"]
230
+ const appearance = ["color", "stroke", "strokeDash", "strokeWidth", "opacity"]
218
231
  .map(channel => layer.encoding?.[channel]?.scale).filter(scale => scale !== undefined);
219
232
  for (const scale of [...positions, ...appearance]) {
220
233
  resolved = resolved.rematerializeScale({ id: scale });
@@ -223,12 +236,12 @@ const rematerializeLineMark = action(
223
236
  const shared = {
224
237
  rows: dataset.values, layer, config, existingChildren,
225
238
  resolvedScales: resolved.resolvedScales,
226
- bounds: resolveGraphicBounds(resolved),
239
+ bounds: resolveCoordinateBounds(resolved, layer.coordinate),
227
240
  defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
228
241
  };
229
242
  if (parallel !== undefined) {
230
243
  return applyLineMaterialization(resolved, id,
231
- resolveParallelLineMaterialization({ ...shared, parallel }));
244
+ resolveParallelLineMaterialization({ ...shared, parallel }), config);
232
245
  }
233
246
 
234
247
  const materialization = resolvePositionedLineMaterialization({
@@ -240,13 +253,17 @@ const rematerializeLineMark = action(
240
253
  layer,
241
254
  requireSemanticScale(resolved, xScaleId)
242
255
  ).boundaries,
243
- polar
256
+ polar,
257
+ ...(polar
258
+ ? { polarFrame: resolveCoordinatePolarFrame(resolved, layer.coordinate) }
259
+ : {})
244
260
  });
245
261
 
246
262
  return applyLineMaterialization(
247
263
  resolved,
248
264
  id,
249
- materialization
265
+ materialization,
266
+ config
250
267
  );
251
268
  }
252
269
  );
@@ -258,6 +275,7 @@ const editLineMark = action(
258
275
  },
259
276
  function (args = {}) {
260
277
  validateMarkOptions(args, EDIT_OPTIONS, "editLineMark");
278
+ const strokeDetails = requestedStrokeDetails(args, "editLineMark");
261
279
  if (!EDIT_OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
262
280
  throw new Error("editLineMark requires stroke, strokeWidth, opacity, curve, or closed.");
263
281
  }
@@ -269,9 +287,12 @@ const editLineMark = action(
269
287
  predicate: candidate => candidate.mark?.type === "line",
270
288
  label: "line mark"
271
289
  });
272
- if (Object.hasOwn(args, "stroke") && layer.encoding?.color !== undefined) {
290
+ if (Object.hasOwn(args, "stroke") && (
291
+ layer.encoding?.color !== undefined ||
292
+ layer.encoding?.stroke !== undefined
293
+ )) {
273
294
  throw new Error(
274
- "editLineMark stroke cannot be combined with a color encoding."
295
+ "editLineMark stroke cannot be combined with a color encoding or a stroke encoding; use encodeStroke with value to replace it."
275
296
  );
276
297
  }
277
298
  for (const channel of ["strokeWidth", "opacity"]) {
@@ -284,6 +305,7 @@ const editLineMark = action(
284
305
  throw new Error("Line closed requires theta/radius Polar position encodings.");
285
306
  }
286
307
  const config = { ...this.markConfigs[layer.id] };
308
+ Object.assign(config, strokeDetails);
287
309
  for (const [key, validate] of Object.entries({
288
310
  stroke: validateNonEmptyString,
289
311
  strokeWidth: validateNonNegativeFinite,
@@ -295,9 +317,10 @@ const editLineMark = action(
295
317
  }
296
318
  validatePolarLineConfig(layer, config);
297
319
  const next = this._withMarkConfig(layer.id, config);
298
- return canMaterializeLine(next, layer)
320
+ const materialized = canMaterializeLine(next, layer)
299
321
  ? next.rematerializeLineMark({ id: layer.id })
300
322
  : next;
323
+ return rematerializeExistingLegend(materialized);
301
324
  }
302
325
  );
303
326
 
@@ -27,7 +27,11 @@ function appearanceMapper(layer, scales, fieldValues) {
27
27
  return fallback.map(() => normalizeStrokeDashPattern(encoding.datum));
28
28
  }
29
29
  if (encoding?.scale === undefined) return fallback;
30
- return mapScaleConsumerValues(fieldValues(encoding.field, channel), scales[encoding.scale], channel);
30
+ return mapScaleConsumerValues(
31
+ fieldValues(encoding.field, channel, encoding),
32
+ scales[encoding.scale],
33
+ channel
34
+ );
31
35
  };
32
36
  }
33
37
 
@@ -53,11 +57,15 @@ export function resolveParallelLineMaterialization({
53
57
  field => sourceRows.map(row => row[field]));
54
58
  return {
55
59
  commands: items.map(item => item.commands),
56
- strokes: mapAppearance(
57
- "color",
58
- items.map((_, index) => config.stroke ??
59
- existingValue(existingChildren, index, "stroke", defaults.stroke))
60
- ),
60
+ strokes: layer.encoding?.stroke?.scale !== undefined
61
+ ? mapAppearance("stroke", items.map(() => defaults.stroke))
62
+ : config.stroke !== undefined
63
+ ? items.map(() => config.stroke)
64
+ : mapAppearance(
65
+ "color",
66
+ items.map((_, index) =>
67
+ existingValue(existingChildren, index, "stroke", defaults.stroke))
68
+ ),
61
69
  strokeWidths: mapAppearance(
62
70
  "strokeWidth",
63
71
  items.map((_, index) => config.strokeWidth ??
@@ -80,6 +88,7 @@ export function resolvePositionedLineMaterialization({
80
88
  config,
81
89
  existingChildren,
82
90
  polar,
91
+ polarFrame,
83
92
  defaults
84
93
  }) {
85
94
  const xScaleId = layer.encoding?.x?.scale;
@@ -98,7 +107,7 @@ export function resolvePositionedLineMaterialization({
98
107
  thetaFieldType: derived.thetaFieldType,
99
108
  thetaScale: resolvedScales[thetaScaleId],
100
109
  radiusScale: resolvedScales[radiusScaleId],
101
- frame: resolvePolarFrame(bounds),
110
+ frame: polarFrame ?? resolvePolarFrame(bounds),
102
111
  closed: config.closed ?? false
103
112
  }))
104
113
  : derived.series.map(series => {
@@ -116,11 +125,21 @@ export function resolvePositionedLineMaterialization({
116
125
  );
117
126
  });
118
127
  const appearance = appearanceMapper(layer, resolvedScales,
119
- (field, channel) => derivePathSeriesFieldValues(rows, derived.series, field, channel));
128
+ (field, channel, encoding) => derivePathSeriesFieldValues(
129
+ rows,
130
+ derived.series,
131
+ field,
132
+ channel,
133
+ encoding
134
+ ));
120
135
  return {
121
136
  commands,
122
- strokes: appearance("color", commands.map((_, index) => config.stroke ??
123
- existingValue(existingChildren, index, "stroke", defaults.stroke))),
137
+ strokes: layer.encoding?.stroke?.scale !== undefined
138
+ ? appearance("stroke", commands.map(() => defaults.stroke))
139
+ : config.stroke !== undefined
140
+ ? commands.map(() => config.stroke)
141
+ : appearance("color", commands.map((_, index) =>
142
+ existingValue(existingChildren, index, "stroke", defaults.stroke))),
124
143
  strokeWidths: appearance("strokeWidth", commands.map((_, index) => config.strokeWidth ??
125
144
  existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))),
126
145
  strokeDashes: appearance("strokeDash", commands.map((_, index) =>
@@ -1,6 +1,10 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validatePointShape } from "../../../grammar/pointShapes.js";
3
3
  import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
4
+ import {
5
+ requestedStrokeDetails,
6
+ STROKE_STYLE_PROPERTIES
7
+ } from "../../../grammar/strokeStyle.js";
4
8
  import { resolveMarkGraphicPlacement } from
5
9
  "../../../materialization/graphicHierarchy.js";
6
10
  import {
@@ -14,7 +18,8 @@ import {
14
18
  } from "../shared.js";
15
19
 
16
20
  const OPTIONS = Object.freeze([
17
- "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
21
+ "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth",
22
+ ...STROKE_STYLE_PROPERTIES
18
23
  ]);
19
24
 
20
25
  export const createPointMark = action(
@@ -24,6 +29,7 @@ export const createPointMark = action(
24
29
  },
25
30
  function (args = {}) {
26
31
  validateMarkOptions(args, OPTIONS, "createPointMark");
32
+ requestedStrokeDetails(args, "createPointMark");
27
33
  const id = resolveMarkId(this, args.id, {
28
34
  defaultId: "point",
29
35
  label: "Point mark id",
@@ -65,7 +71,7 @@ export const createPointMark = action(
65
71
  ._withMarkConfig(id, { shape });
66
72
  const created = materializeInheritedMark(next, id);
67
73
  const appearance = Object.fromEntries(
68
- ["fill", "opacity", "stroke", "strokeWidth"]
74
+ ["fill", "opacity", "stroke", "strokeWidth", ...STROKE_STYLE_PROPERTIES]
69
75
  .filter(property => Object.hasOwn(args, property))
70
76
  .map(property => [property, args[property]])
71
77
  );
@@ -6,12 +6,17 @@ import {
6
6
  validateUnitInterval
7
7
  } from "../../../core/validation.js";
8
8
  import { validatePointShape } from "../../../grammar/pointShapes.js";
9
+ import {
10
+ requestedStrokeDetails,
11
+ STROKE_STYLE_PROPERTIES
12
+ } from "../../../grammar/strokeStyle.js";
9
13
  import { resolveEligibleLayer } from "../../../selectors/layers.js";
10
14
  import { rematerializeExistingLegend } from "../../encodings/shared.js";
11
15
  import { validateMarkOptions } from "../shared.js";
12
16
 
13
17
  const OPTIONS = Object.freeze([
14
- "target", "shape", "fill", "opacity", "stroke", "strokeWidth"
18
+ "target", "shape", "fill", "opacity", "stroke", "strokeWidth",
19
+ ...STROKE_STYLE_PROPERTIES
15
20
  ]);
16
21
 
17
22
  export const editPointMark = action(
@@ -21,7 +26,11 @@ export const editPointMark = action(
21
26
  },
22
27
  function (args = {}) {
23
28
  validateMarkOptions(args, OPTIONS, "editPointMark");
24
- const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
29
+ const strokeDetails = requestedStrokeDetails(args, "editPointMark");
30
+ const editable = [
31
+ "shape", "fill", "opacity", "stroke", "strokeWidth",
32
+ ...STROKE_STYLE_PROPERTIES
33
+ ];
25
34
  if (!editable.some(property => Object.hasOwn(args, property))) {
26
35
  throw new Error(
27
36
  "editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
@@ -44,10 +53,16 @@ export const editPointMark = action(
44
53
  "editPointMark fill cannot be combined with a color encoding."
45
54
  );
46
55
  }
56
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
57
+ throw new Error(
58
+ "editPointMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
59
+ );
60
+ }
47
61
  if (Object.hasOwn(args, "opacity") && layer.encoding?.opacity?.field !== undefined) {
48
62
  throw new Error("editPointMark opacity conflicts with a field encoding; use encodeOpacity with value to replace it.");
49
63
  }
50
64
  const config = { ...this.markConfigs[id] };
65
+ Object.assign(config, strokeDetails);
51
66
  if (Object.hasOwn(args, "shape")) {
52
67
  config.shape = validatePointShape(args.shape);
53
68
  }
@@ -85,9 +100,6 @@ export const editPointMark = action(
85
100
  const next = this
86
101
  ._withMarkConfig(id, config)
87
102
  .rematerializePointMark({ id });
88
- const legend = next.guideConfigs.legend?.series;
89
- return legend?.target === id && legend.channels.includes("shape")
90
- ? rematerializeExistingLegend(next)
91
- : next;
103
+ return rematerializeExistingLegend(next);
92
104
  }
93
105
  );
@@ -15,9 +15,12 @@ import {
15
15
  normalizePointPackingPolicy,
16
16
  resolvePointPacking
17
17
  } from "../../../grammar/pointPacking.js";
18
- import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
18
+ import { polarToCartesian } from "../../../grammar/polar.js";
19
19
  import { resolveDirectionValues } from "../../../grammar/direction.js";
20
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
20
+ import {
21
+ resolveCoordinateBounds,
22
+ resolveCoordinatePolarFrame
23
+ } from "../../../materialization/coordinateBounds.js";
21
24
  import { validateMarkOptions } from "../shared.js";
22
25
  import {
23
26
  DEFAULT_COLORS,
@@ -34,6 +37,8 @@ import {
34
37
  "../../../materialization/rowEncoding.js";
35
38
  import { offsetCategoryPositions } from
36
39
  "../../../materialization/categorySlotOffset.js";
40
+ import { requestedStrokeDetails } from
41
+ "../../../grammar/strokeStyle.js";
37
42
 
38
43
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
39
44
  const DEFAULT_POINT_FILL = DEFAULT_COLORS.mark;
@@ -52,7 +57,7 @@ function resolvePointPositions(program, layer, dataset) {
52
57
  if (theta === undefined || radius === undefined) {
53
58
  return { x: undefined, y: undefined };
54
59
  }
55
- const frame = resolvePolarFrame(resolveGraphicBounds(program));
60
+ const frame = resolveCoordinatePolarFrame(program, layer.coordinate);
56
61
  const positions = theta.map((angle, index) => {
57
62
  const distance = radius[index];
58
63
  return Number.isFinite(angle) && Number.isFinite(distance)
@@ -76,7 +81,11 @@ function incompleteShapeGraphic(shape, properties, angle) {
76
81
  ? "path"
77
82
  : getPointGraphicType(shape);
78
83
  const incomplete = type === "path"
79
- ? { fill: properties.fill, opacity: properties.opacity }
84
+ ? {
85
+ fill: properties.fill,
86
+ opacity: properties.opacity,
87
+ ...requestedStrokeDetails(properties, "Point mark")
88
+ }
80
89
  : properties;
81
90
  return { type, properties: compactProperties(incomplete) };
82
91
  }
@@ -180,7 +189,7 @@ function applyPointJitter(program, {
180
189
  `Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
181
190
  );
182
191
  }
183
- const bounds = resolveGraphicBounds(program);
192
+ const bounds = resolveCoordinateBounds(program, layer.coordinate);
184
193
  const entries = resolveJitterEntries({
185
194
  policy,
186
195
  dataset,
@@ -270,7 +279,7 @@ function applyPointPacking(program, {
270
279
  fixedHalfExtent: extent[policy.channel === "x" ? "y" : "x"]
271
280
  }];
272
281
  });
273
- const bounds = resolveGraphicBounds(program);
282
+ const bounds = resolveCoordinateBounds(program, layer.coordinate);
274
283
  const resolution = resolvePointPacking({
275
284
  target: id,
276
285
  policy,
@@ -338,6 +347,7 @@ export const rematerializePointMark = action(
338
347
 
339
348
  const positions = resolvePointPositions(resolved, layer, dataset);
340
349
  const mappedFill = resolveRowEncodingValues(resolved, layer, dataset, "color");
350
+ const mappedStroke = resolveRowEncodingValues(resolved, layer, dataset, "stroke");
341
351
  const area = resolveRowEncodingValues(resolved, layer, dataset, "size");
342
352
  const encodedShape = resolveRowEncodingValues(resolved, layer, dataset, "shape");
343
353
  const encodedOpacity = resolveRowEncodingValues(
@@ -348,6 +358,7 @@ export const rematerializePointMark = action(
348
358
  );
349
359
  const angles = resolveDirectionValues(dataset.values, layer.encoding?.angle);
350
360
  const config = resolved.markConfigs[id] ?? {};
361
+ const strokeDetails = requestedStrokeDetails(config, "Point mark");
351
362
  const fill = mappedFill ?? config.fill ?? DEFAULT_POINT_FILL;
352
363
  const shapes = encodedShape ?? dataset.values.map(() => config.shape ?? "circle");
353
364
  const existingChildren = graphic.items ?? [];
@@ -417,7 +428,8 @@ export const rematerializePointMark = action(
417
428
  x: centerX,
418
429
  y: centerY,
419
430
  fill: color,
420
- opacity
431
+ opacity,
432
+ ...strokeDetails
421
433
  }, angle);
422
434
  }
423
435
  return createPointShapeGraphic({
@@ -426,12 +438,15 @@ export const rematerializePointMark = action(
426
438
  y: centerY,
427
439
  area: resolvedArea,
428
440
  fill: color,
429
- ...(typeof config.stroke === "string" ? { stroke: config.stroke } : {}),
430
- ...(config.strokeWidth === undefined
431
- ? {}
432
- : { strokeWidth: config.strokeWidth }),
441
+ ...(typeof (mappedStroke?.[index] ?? config.stroke) === "string"
442
+ ? { stroke: mappedStroke?.[index] ?? config.stroke }
443
+ : {}),
444
+ ...((mappedStroke !== undefined || config.strokeWidth !== undefined)
445
+ ? { strokeWidth: config.strokeWidth ?? 1 }
446
+ : {}),
433
447
  opacity,
434
- angle
448
+ angle,
449
+ ...strokeDetails
435
450
  });
436
451
  });
437
452
  return jittered.program.editGraphics({
@@ -503,15 +518,31 @@ export const rematerializePointMark = action(
503
518
  next = next
504
519
  .editGraphics({ target: id, property: "width", value: sides })
505
520
  .editGraphics({ target: id, property: "height", value: sides })
506
- .editGraphics({ target: id, property: "stroke", value: fill ?? DEFAULT_POINT_FILL })
507
- .editGraphics({ target: id, property: "strokeWidth", value: 0 });
521
+ .editGraphics({
522
+ target: id,
523
+ property: "stroke",
524
+ value: mappedStroke ?? fill ?? DEFAULT_POINT_FILL
525
+ })
526
+ .editGraphics({
527
+ target: id,
528
+ property: "strokeWidth",
529
+ value: mappedStroke === undefined ? 0 : config.strokeWidth ?? 1
530
+ });
508
531
  }
509
532
  if (config.opacity !== undefined) {
510
533
  next = next.editGraphics({ target: id, property: "opacity", value: config.opacity });
511
534
  } else if (encodedOpacity !== undefined) {
512
535
  next = next.editGraphics({ target: id, property: "opacity", value: encodedOpacity });
513
536
  }
514
- if (config.stroke === false) {
537
+ if (mappedStroke !== undefined) {
538
+ next = next
539
+ .editGraphics({ target: id, property: "stroke", value: mappedStroke })
540
+ .editGraphics({
541
+ target: id,
542
+ property: "strokeWidth",
543
+ value: config.strokeWidth ?? 1
544
+ });
545
+ } else if (config.stroke === false) {
515
546
  next = next
516
547
  .editGraphics({ target: id, property: "stroke", value: "transparent" })
517
548
  .editGraphics({ target: id, property: "strokeWidth", value: 0 });
@@ -523,6 +554,9 @@ export const rematerializePointMark = action(
523
554
  value: config.strokeWidth
524
555
  });
525
556
  }
557
+ for (const [property, value] of Object.entries(strokeDetails)) {
558
+ next = next.editGraphics({ target: id, property, value });
559
+ }
526
560
  return next;
527
561
  }
528
562
  );
@@ -19,12 +19,20 @@ import {
19
19
  resolveMarkId,
20
20
  validateMarkOptions
21
21
  } from "../shared.js";
22
+ import { replaceGraphicItems } from "../../primitives/editGraphics.js";
22
23
  import { resolveMarkGraphicPlacement } from
23
24
  "../../../materialization/graphicHierarchy.js";
25
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
24
26
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
27
+ import {
28
+ requestedRectStyleDetails
29
+ } from "../../../grammar/roundedRect.js";
30
+ import { STROKE_STYLE_PROPERTIES } from
31
+ "../../../grammar/strokeStyle.js";
25
32
 
26
33
  const STYLE_OPTIONS = Object.freeze([
27
- "fill", "opacity", "stroke", "strokeWidth"
34
+ "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
35
+ ...STROKE_STYLE_PROPERTIES
28
36
  ]);
29
37
  const CREATE_OPTIONS = Object.freeze(["id", "data", ...STYLE_OPTIONS]);
30
38
  const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
@@ -45,6 +53,7 @@ const createRectMark = action(
45
53
  { op: "createRectMark", description: "Create a semantic rectangular cell layer." },
46
54
  function (args = {}) {
47
55
  validateMarkOptions(args, CREATE_OPTIONS, "createRectMark");
56
+ requestedRectStyleDetails(args, "createRectMark");
48
57
  const id = resolveMarkId(this, args.id, {
49
58
  defaultId: "rect",
50
59
  label: "Rect mark id",
@@ -99,7 +108,13 @@ const rematerializeRectMark = action(
99
108
  const layer = findLayer(this, id);
100
109
  const graphic = this.graphicSpec.objects[id];
101
110
  if (layer?.mark?.type !== "rect") throw new Error(`Unknown rect mark "${id}".`);
102
- if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
111
+ const roundedCollection = graphic?.type === "collection" &&
112
+ Object.hasOwn(this.markConfigs[id] ?? {}, "cornerRadius") &&
113
+ graphic.items?.every(item => ["rect", "path"].includes(item.type));
114
+ if (
115
+ (graphic?.type !== "rect" && !roundedCollection) ||
116
+ !Array.isArray(graphic.items)
117
+ ) {
103
118
  throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
104
119
  }
105
120
  if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
@@ -110,13 +125,11 @@ const rematerializeRectMark = action(
110
125
  throw new Error(`Rect mark "${id}" requires an existing dataset.`);
111
126
  }
112
127
  if (!canMaterializeRect(this, layer)) {
113
- return graphic.items.length === 0
114
- ? this
115
- : this.editGraphics({ target: id, property: "items", value: [] });
128
+ return replaceGraphicItems(this, id, "rect", []);
116
129
  }
117
130
  let next = this;
118
131
  if (args.scales !== false) {
119
- const ids = new Set(["x", "y", "x2", "y2", "color"].flatMap(channel => {
132
+ const ids = new Set(["x", "y", "x2", "y2", "color", "stroke"].flatMap(channel => {
120
133
  const scale = layer.encoding?.[channel]?.scale;
121
134
  return scale === undefined ? [] : [scale];
122
135
  }));
@@ -124,11 +137,12 @@ const rematerializeRectMark = action(
124
137
  next = next.rematerializeScale({ id: scale, marks: false });
125
138
  }
126
139
  }
127
- return next.editGraphics({
128
- target: id,
129
- property: "items",
130
- value: resolveRectGraphicItems(next, layer, dataset)
131
- });
140
+ return replaceGraphicItems(
141
+ next,
142
+ id,
143
+ "rect",
144
+ resolveRectGraphicItems(next, layer, dataset)
145
+ );
132
146
  }
133
147
  );
134
148
 
@@ -136,6 +150,7 @@ const editRectMark = action(
136
150
  { op: "editRectMark", description: "Edit rectangular cell appearance." },
137
151
  function (args = {}) {
138
152
  validateMarkOptions(args, EDIT_OPTIONS, "editRectMark");
153
+ requestedRectStyleDetails(args, "editRectMark");
139
154
  if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
140
155
  throw new Error("editRectMark requires at least one editable property.");
141
156
  }
@@ -143,6 +158,11 @@ const editRectMark = action(
143
158
  if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
144
159
  throw new Error("editRectMark fill cannot be combined with a color encoding.");
145
160
  }
161
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
162
+ throw new Error(
163
+ "editRectMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
164
+ );
165
+ }
146
166
  const next = this._withMarkConfig(
147
167
  layer.id,
148
168
  {
@@ -155,9 +175,10 @@ const editRectMark = action(
155
175
  : this.markConfigs[layer.id]?.fillExplicit ?? false
156
176
  }
157
177
  );
158
- return canMaterializeRect(next, layer)
178
+ const materialized = canMaterializeRect(next, layer)
159
179
  ? next.rematerializeRectMark({ id: layer.id })
160
180
  : next;
181
+ return rematerializeExistingLegend(materialized);
161
182
  }
162
183
  );
163
184