ggaction 0.0.11 → 0.0.13

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 (289) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +4 -2
  11. package/src/actions/basic.js +2 -0
  12. package/src/actions/boxPlots/materialize.js +2 -1
  13. package/src/actions/canvas/actions.js +3 -1
  14. package/src/actions/canvas/fitting.js +211 -0
  15. package/src/actions/canvas/index.js +2 -0
  16. package/src/actions/categoryOrder/index.js +3 -2
  17. package/src/actions/charts/area.js +47 -0
  18. package/src/actions/charts/bar.js +11 -5
  19. package/src/actions/charts/beeswarm.js +70 -0
  20. package/src/actions/charts/density.js +44 -0
  21. package/src/actions/charts/ecdf.js +183 -0
  22. package/src/actions/charts/endpoints.js +451 -0
  23. package/src/actions/charts/heatmap.js +2 -2
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +71 -0
  26. package/src/actions/charts/index.js +37 -0
  27. package/src/actions/charts/interval-regression.js +169 -0
  28. package/src/actions/charts/line.js +5 -5
  29. package/src/actions/charts/parallel.js +1 -1
  30. package/src/actions/charts/pie.js +44 -0
  31. package/src/actions/charts/polar.js +123 -0
  32. package/src/actions/charts/radar.js +299 -0
  33. package/src/actions/charts/radial.js +42 -0
  34. package/src/actions/charts/raincloud.js +422 -0
  35. package/src/actions/charts/rug-strip.js +279 -0
  36. package/src/actions/charts/scatter.js +9 -3
  37. package/src/actions/charts/shared.js +69 -10
  38. package/src/actions/composition/actions.js +118 -1
  39. package/src/actions/data/bin.js +29 -0
  40. package/src/actions/data/computed.js +24 -0
  41. package/src/actions/data/derived.js +66 -0
  42. package/src/actions/data/ecdf.js +38 -0
  43. package/src/actions/data/filter.js +212 -28
  44. package/src/actions/data/fold.js +24 -0
  45. package/src/actions/data/index.js +24 -0
  46. package/src/actions/data/interval.js +2 -0
  47. package/src/actions/data/intervalEdit.js +86 -4
  48. package/src/actions/data/regression.js +5 -2
  49. package/src/actions/data/stack.js +26 -0
  50. package/src/actions/data/summary.js +26 -0
  51. package/src/actions/data/timeUnit.js +1 -1
  52. package/src/actions/encodings/appearance.js +32 -13
  53. package/src/actions/encodings/barWidth.js +8 -0
  54. package/src/actions/encodings/basic.js +2 -0
  55. package/src/actions/encodings/color/continuous.js +19 -59
  56. package/src/actions/encodings/color/index.js +28 -22
  57. package/src/actions/encodings/color/layout.js +3 -97
  58. package/src/actions/encodings/color/policy.js +5 -8
  59. package/src/actions/encodings/density/resolve.js +37 -5
  60. package/src/actions/encodings/density.js +120 -33
  61. package/src/actions/encodings/horizon/resolve.js +8 -4
  62. package/src/actions/encodings/horizon.js +18 -8
  63. package/src/actions/encodings/index.js +2 -0
  64. package/src/actions/encodings/offset.js +33 -27
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +40 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +37 -2
  69. package/src/actions/encodings/position/policies/area.js +4 -42
  70. package/src/actions/encodings/position/policies/bar.js +48 -13
  71. package/src/actions/encodings/position/resolve.js +79 -24
  72. package/src/actions/encodings/ranged.js +123 -97
  73. package/src/actions/encodings/remove.js +37 -32
  74. package/src/actions/encodings/ruleAppearance.js +23 -9
  75. package/src/actions/encodings/seriesLayout.js +104 -0
  76. package/src/actions/encodings/shared.js +25 -23
  77. package/src/actions/encodings/strokeDash.js +9 -6
  78. package/src/actions/encodings/temporal.js +7 -0
  79. package/src/actions/encodings/text.js +30 -15
  80. package/src/actions/errorBands/create.js +18 -5
  81. package/src/actions/errorBands/edit.js +341 -21
  82. package/src/actions/errorBars/create.js +153 -11
  83. package/src/actions/errorBars/edit.js +365 -19
  84. package/src/actions/facets/actions.js +410 -12
  85. package/src/actions/facets/derive.js +57 -8
  86. package/src/actions/gradientPlots/components.js +33 -11
  87. package/src/actions/gradientPlots/materialize.js +5 -7
  88. package/src/actions/guides/applicability.js +20 -17
  89. package/src/actions/guides/axes/axes.js +4 -3
  90. package/src/actions/guides/axes/axis.js +18 -18
  91. package/src/actions/guides/axes/components.js +43 -0
  92. package/src/actions/guides/axes/edit.js +16 -51
  93. package/src/actions/guides/axes/labels.js +129 -19
  94. package/src/actions/guides/axes/lines.js +7 -5
  95. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  96. package/src/actions/guides/axes/parallel/policy.js +137 -0
  97. package/src/actions/guides/axes/parallel/resolve.js +44 -23
  98. package/src/actions/guides/axes/parallel.js +93 -217
  99. package/src/actions/guides/axes/policy.js +21 -67
  100. package/src/actions/guides/axes/tickGroups.js +14 -8
  101. package/src/actions/guides/axes/ticks.js +5 -4
  102. package/src/actions/guides/axes/titles.js +21 -8
  103. package/src/actions/guides/basic.js +2 -0
  104. package/src/actions/guides/facade.js +106 -0
  105. package/src/actions/guides/facadeAxes.js +200 -0
  106. package/src/actions/guides/facadeLegend.js +63 -0
  107. package/src/actions/guides/grids/grid.js +37 -33
  108. package/src/actions/guides/grids/resolve.js +10 -2
  109. package/src/actions/guides/guides.js +35 -28
  110. package/src/actions/guides/legends/categorical/actions.js +193 -249
  111. package/src/actions/guides/legends/categorical/components.js +4 -5
  112. package/src/actions/guides/legends/categorical/layout.js +36 -349
  113. package/src/actions/guides/legends/categorical/options.js +30 -14
  114. package/src/actions/guides/legends/categorical/recipes.js +14 -0
  115. package/src/actions/guides/legends/categorical/resolve.js +21 -10
  116. package/src/actions/guides/legends/categorical/symbols.js +2 -3
  117. package/src/actions/guides/legends/continuous/common.js +69 -14
  118. package/src/actions/guides/legends/continuous/gradient.js +111 -83
  119. package/src/actions/guides/legends/continuous/index.js +6 -2
  120. package/src/actions/guides/legends/continuous/interval.js +71 -92
  121. package/src/actions/guides/legends/continuous/opacity.js +105 -84
  122. package/src/actions/guides/legends/creation.js +27 -0
  123. package/src/actions/guides/legends/edit.js +198 -66
  124. package/src/actions/guides/legends/focused.js +2 -2
  125. package/src/actions/guides/legends/lane.js +98 -86
  126. package/src/actions/guides/legends/lifecycle.js +132 -0
  127. package/src/actions/guides/legends/remove.js +31 -76
  128. package/src/actions/guides/legends/size.js +149 -105
  129. package/src/actions/guides/legends/strokeWidth.js +106 -64
  130. package/src/actions/guides/legends/target.js +12 -0
  131. package/src/actions/guides/legends/transition.js +43 -0
  132. package/src/actions/guides/polar/axes/facade.js +77 -62
  133. package/src/actions/guides/polar/axes/labels.js +5 -4
  134. package/src/actions/guides/polar/axes/lines.js +2 -1
  135. package/src/actions/guides/polar/axes/shared.js +6 -0
  136. package/src/actions/guides/polar/axes/ticks.js +5 -4
  137. package/src/actions/guides/polar/axes/titles.js +5 -4
  138. package/src/actions/guides/polar/grids.js +7 -7
  139. package/src/actions/guides/reuse.js +44 -0
  140. package/src/actions/guides/tickValues.js +19 -21
  141. package/src/actions/index.js +2 -0
  142. package/src/actions/intervals/resolve.js +35 -15
  143. package/src/actions/marks/arc/actions.js +1 -1
  144. package/src/actions/marks/area/actions.js +18 -4
  145. package/src/actions/marks/area/materialize.js +5 -2
  146. package/src/actions/marks/bar/materialize.js +11 -7
  147. package/src/actions/marks/index.js +5 -0
  148. package/src/actions/marks/line/actions.js +43 -108
  149. package/src/actions/marks/line/materialize.js +34 -52
  150. package/src/actions/marks/point/edit.js +11 -15
  151. package/src/actions/marks/point/index.js +1 -0
  152. package/src/actions/marks/point/jitter.js +3 -0
  153. package/src/actions/marks/point/materialize.js +138 -14
  154. package/src/actions/marks/point/packing.js +68 -0
  155. package/src/actions/marks/references.js +87 -0
  156. package/src/actions/marks/remove.js +17 -4
  157. package/src/actions/marks/rule/actions.js +60 -4
  158. package/src/actions/marks/rule/appearance.js +26 -0
  159. package/src/actions/marks/shared.js +12 -12
  160. package/src/actions/marks/text/actions.js +224 -6
  161. package/src/actions/marks/text/layout.js +2 -2
  162. package/src/actions/primitives/createGraphics.js +3 -1
  163. package/src/actions/primitives/semantic.js +1 -1
  164. package/src/actions/primitives/semanticAction.js +39 -2
  165. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  166. package/src/actions/primitives/semanticValidation/layer.js +18 -1
  167. package/src/actions/primitives/semanticValidation/scale.js +10 -1
  168. package/src/actions/regression/create.js +1 -1
  169. package/src/actions/regression/edit.js +19 -6
  170. package/src/actions/regression/resolve.js +1 -1
  171. package/src/actions/scales/consumers/common.js +20 -53
  172. package/src/actions/scales/consumers/families.js +27 -14
  173. package/src/actions/scales/consumers/index.js +23 -2
  174. package/src/actions/scales/consumers/seriesLayout.js +33 -2
  175. package/src/actions/scales/create.js +4 -4
  176. package/src/actions/scales/definitions.js +19 -3
  177. package/src/actions/scales/edit.js +46 -34
  178. package/src/actions/scales/editPolicy.js +21 -31
  179. package/src/actions/scales/materialize.js +10 -46
  180. package/src/actions/scales/patch.js +1 -1
  181. package/src/actions/scales/preview.js +39 -0
  182. package/src/actions/scales/quantitativeColor.js +9 -5
  183. package/src/actions/theme/actions.js +43 -0
  184. package/src/actions/theme/index.js +9 -0
  185. package/src/actions/theme/reconcile.js +882 -0
  186. package/src/actions/titles/actions.js +11 -10
  187. package/src/actions/titles/resolve.js +1 -40
  188. package/src/actions/violinPlots/create.js +86 -42
  189. package/src/actions/violinPlots/edit.js +191 -0
  190. package/src/actions/violinPlots/index.js +2 -0
  191. package/src/core/ChartProgram.js +7 -0
  192. package/src/core/action.js +22 -2
  193. package/src/core/compositionState.js +136 -8
  194. package/src/core/vocabulary.js +2 -2
  195. package/src/grammar/aggregate.js +66 -23
  196. package/src/grammar/arcs.js +213 -65
  197. package/src/grammar/areaEndpoints.js +46 -0
  198. package/src/grammar/areaSeries.js +87 -41
  199. package/src/grammar/bars/aggregate.js +33 -12
  200. package/src/grammar/bars/policy.js +9 -7
  201. package/src/grammar/bin.js +181 -0
  202. package/src/grammar/categoryOrder.js +34 -0
  203. package/src/grammar/computed.js +150 -0
  204. package/src/grammar/ecdf.js +198 -0
  205. package/src/grammar/facets/index.js +117 -6
  206. package/src/grammar/fold.js +123 -0
  207. package/src/grammar/horizon.js +6 -4
  208. package/src/grammar/interval.js +37 -20
  209. package/src/grammar/lineSeries.js +20 -63
  210. package/src/grammar/markFilter.js +22 -4
  211. package/src/grammar/markLabels.js +93 -0
  212. package/src/grammar/parallelAxisTitles.js +16 -0
  213. package/src/grammar/pathSeries.js +91 -0
  214. package/src/grammar/pointPacking.js +183 -0
  215. package/src/grammar/positionCompatibility.js +1 -1
  216. package/src/grammar/positionDatum.js +12 -0
  217. package/src/grammar/rects.js +9 -1
  218. package/src/grammar/regression/derive.js +4 -0
  219. package/src/grammar/regression/index.js +7 -2
  220. package/src/grammar/regression/models.js +22 -7
  221. package/src/grammar/regression/parameters.js +40 -13
  222. package/src/grammar/rotation.js +29 -0
  223. package/src/grammar/rules.js +9 -21
  224. package/src/grammar/scales/color.js +23 -1
  225. package/src/grammar/scales/colorConsumers.js +38 -0
  226. package/src/grammar/scales/definition.js +15 -1
  227. package/src/grammar/scales/discretized.js +11 -5
  228. package/src/grammar/scales/fields.js +38 -8
  229. package/src/grammar/scales/mapping.js +2 -0
  230. package/src/grammar/scales/ordinal.js +23 -0
  231. package/src/grammar/scales/radial.js +55 -0
  232. package/src/grammar/scales/types.js +1 -0
  233. package/src/grammar/schemas/semanticPath.js +17 -3
  234. package/src/grammar/stack.js +170 -0
  235. package/src/grammar/statistics/confidenceInterval.js +101 -0
  236. package/src/grammar/summary.js +183 -0
  237. package/src/grammar/text.js +22 -16
  238. package/src/grammar/timeUnit.js +21 -11
  239. package/src/grammar/transformTopology.js +6 -0
  240. package/src/grammar/transforms.js +36 -0
  241. package/src/grammar/valueFormat.js +112 -0
  242. package/src/grammar/window.js +13 -7
  243. package/src/layout/facets.js +86 -23
  244. package/src/layout/guideCollisions.js +12 -0
  245. package/src/layout/legend.js +2 -10
  246. package/src/layout/legendItems.js +102 -0
  247. package/src/layout/legendLane.js +100 -11
  248. package/src/materialization/bars/aggregate.js +38 -152
  249. package/src/materialization/bars/histogram.js +24 -0
  250. package/src/materialization/categorySlotOffset.js +40 -0
  251. package/src/materialization/compositionSnapshot.js +1 -1
  252. package/src/materialization/dependencies.js +57 -16
  253. package/src/materialization/encodings.js +1 -1
  254. package/src/materialization/facetGuides/legacyCategorical.js +7 -1
  255. package/src/materialization/facetGuides/placement.js +16 -1
  256. package/src/materialization/facetGuides/preparation.js +26 -1
  257. package/src/materialization/facets.js +11 -2
  258. package/src/materialization/guides/layout.js +57 -0
  259. package/src/materialization/guides/resources.js +2 -2
  260. package/src/materialization/legends.js +10 -1
  261. package/src/materialization/marks/capabilities.js +30 -7
  262. package/src/materialization/marks/grouping.js +52 -0
  263. package/src/materialization/marks/index.js +63 -25
  264. package/src/materialization/marks/policies.js +2 -0
  265. package/src/materialization/rect.js +33 -33
  266. package/src/materialization/rowEncoding.js +70 -14
  267. package/src/materialization/scaleGuideDependencies.js +8 -5
  268. package/src/materialization/scales/map.js +1 -0
  269. package/src/materialization/scales/policies/arc.js +42 -0
  270. package/src/materialization/scales/policies/bar.js +0 -43
  271. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  272. package/src/materialization/scales/policies/offset.js +54 -0
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +48 -18
  275. package/src/materialization/selection/compatibility.js +11 -0
  276. package/src/materialization/selection/items/bar.js +12 -68
  277. package/src/materialization/selection/items/common.js +8 -2
  278. package/src/materialization/selection/items/path.js +3 -13
  279. package/src/materialization/selection/items/rect.js +7 -4
  280. package/src/materialization/text.js +64 -5
  281. package/src/mcp/adapter.js +2 -2
  282. package/src/selectors/datasets.js +13 -0
  283. package/src/selectors/layers.js +3 -2
  284. package/src/selectors/scales.js +6 -0
  285. package/src/theme/defaults.js +39 -0
  286. package/types/basic.d.ts +11 -2
  287. package/types/index.d.ts +126 -0
  288. package/types/program.d.ts +1327 -124
  289. package/src/materialization/bars/grouped.js +0 -144
@@ -51,7 +51,7 @@ function normalizeConfig(args, previous = {}, { allowStrokeRemoval = false } = {
51
51
  let config = {
52
52
  ...previous,
53
53
  ...(Object.hasOwn(args, "innerRadius")
54
- ? { innerRadius: validateInnerRadius(args.innerRadius) }
54
+ ? { innerRadius: validateInnerRadius(args.innerRadius), innerRadiusExplicit: true }
55
55
  : {}),
56
56
  ...(Object.hasOwn(args, "padAngle")
57
57
  ? { padAngle: validateNonNegativeFinite(args.padAngle, "Arc padAngle") }
@@ -1,3 +1,4 @@
1
+ import { validateAreaMissing } from "../../../grammar/areaEndpoints.js";
1
2
  import { action } from "../../../core/action.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import {
@@ -30,8 +31,8 @@ import { resolveAreaMaterialization } from "./materialize.js";
30
31
  const AREA_OPTIONS = Object.freeze([
31
32
  "fill", "opacity", "stroke", "strokeWidth", "curve"
32
33
  ]);
33
- const CREATE_OPTIONS = Object.freeze(["id", "data", ...AREA_OPTIONS]);
34
- const EDIT_OPTIONS = Object.freeze(["target", ...AREA_OPTIONS]);
34
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...AREA_OPTIONS]);
35
+ const EDIT_OPTIONS = Object.freeze(["target", "missing", ...AREA_OPTIONS]);
35
36
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
36
37
  const STROKE_FROM_FILL_OPTIONS = Object.freeze(["id", "strokeWidth"]);
37
38
 
@@ -69,6 +70,10 @@ const createAreaMark = action(
69
70
  ...(args.data === undefined && this.context.currentData === undefined &&
70
71
  inherited?.data !== undefined ? { data: inherited.data } : {})
71
72
  });
73
+ const missing = validateAreaMissing(args.missing);
74
+ if (missing === "break" && ["density", "horizon"].some(type => findUpstreamTransform(this, findDataset(this, data), type))) {
75
+ throw new Error("Area missing:break does not reinterpret density or horizon data.");
76
+ }
72
77
  const fill = validateNonEmptyString(args.fill ?? DEFAULT_COLORS.mark, "Area fill");
73
78
  const opacity = validateUnitInterval(args.opacity ?? 0.2, "Area opacity");
74
79
  const curve = validateCurveInterpolation(args.curve ?? "linear");
@@ -77,6 +82,7 @@ const createAreaMark = action(
77
82
  let created = this
78
83
  .editSemantic({ property: `layer[${id}].mark.type`, value: "area" })
79
84
  .editSemantic({ property: `layer[${id}].data`, value: data });
85
+ if (Object.hasOwn(args, "missing")) created = created.editSemantic({ property: `layer[${id}].mark.missing`, value: missing });
80
86
  created = applyLayeredMarkInheritance(created, id, inherited);
81
87
  created = created
82
88
  .createGraphics({
@@ -218,7 +224,7 @@ const editAreaMark = action(
218
224
  },
219
225
  function (args = {}) {
220
226
  validateMarkOptions(args, EDIT_OPTIONS, "editAreaMark");
221
- const changes = ["fill", "opacity", "stroke", "strokeWidth", "curve"];
227
+ const changes = ["fill", "opacity", "stroke", "strokeWidth", "curve", "missing"];
222
228
  if (!changes.some(key => Object.hasOwn(args, key))) {
223
229
  throw new Error(
224
230
  "editAreaMark requires fill, opacity, stroke, strokeWidth, or curve."
@@ -244,6 +250,7 @@ const editAreaMark = action(
244
250
  let config = { ...this.markConfigs[layer.id] };
245
251
  if (Object.hasOwn(args, "fill")) {
246
252
  config.fill = validateNonEmptyString(args.fill, "Area fill");
253
+ if (config.errorBand !== undefined) config.errorBand = { ...config.errorBand, fill: config.fill };
247
254
  }
248
255
  if (Object.hasOwn(args, "opacity")) {
249
256
  config.opacity = validateUnitInterval(args.opacity, "Area opacity");
@@ -283,7 +290,14 @@ const editAreaMark = action(
283
290
  );
284
291
  }
285
292
 
286
- const next = this._withMarkConfig(layer.id, config);
293
+ let next = this._withMarkConfig(layer.id, config);
294
+ if (Object.hasOwn(args, "missing")) {
295
+ const missing = validateAreaMissing(args.missing);
296
+ if (missing === "break" && ["density", "horizon"].some(type => findUpstreamTransform(this, findDataset(this, layer.data), type))) {
297
+ throw new Error("Area missing:break does not reinterpret density or horizon data.");
298
+ }
299
+ next = next.editSemantic({ property: `layer[${layer.id}].mark.missing`, value: missing });
300
+ }
287
301
  return canMaterializeArea(next, layer)
288
302
  ? next.rematerializeAreaMark({ id: layer.id })
289
303
  : next;
@@ -14,6 +14,7 @@ import { buildLinearPathCommands } from
14
14
  "../../../grammar/pathCommands.js";
15
15
  import { buildCategoricalDensityPaths } from
16
16
  "../../../grammar/categoricalDensity.js";
17
+ import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
17
18
 
18
19
  function resolveAreaPaths({
19
20
  derived,
@@ -135,7 +136,7 @@ export function resolveAreaMaterialization({
135
136
  : deriveAreaSeries(rows, layer)
136
137
  : deriveDensityAreaSeries(rows, layer, densityTransform);
137
138
  const colorEncoding = layer.encoding?.color;
138
- const layout = colorEncoding?.layout ?? "overlay";
139
+ const layout = layer.layout?.mode ?? colorEncoding?.layout ?? "overlay";
139
140
  const categoricalDensity = densityTransform?.placement?.type === "category";
140
141
  const derived = densityTransform === undefined || categoricalDensity
141
142
  ? rawDerived
@@ -164,7 +165,9 @@ export function resolveAreaMaterialization({
164
165
  : colorEncoding?.scale === undefined
165
166
  ? paths.map(() => config.fill)
166
167
  : mapOrdinalValues(
167
- derived.series.map(series => series.key[colorEncoding.field]),
168
+ densityTransform !== undefined || centered
169
+ ? derived.series.map(series => series.key[colorEncoding.field])
170
+ : derivePathSeriesFieldValues(rows, derived.series, colorEncoding.field, "color"),
168
171
  resolvedScales[colorEncoding.scale].domain,
169
172
  resolvedScales[colorEncoding.scale].range
170
173
  );
@@ -10,6 +10,8 @@ import {
10
10
  resolveBarOffsetChannel
11
11
  } from "../../../grammar/bars/policy.js";
12
12
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
13
+ import { offsetCategoryRectangles } from
14
+ "../../../materialization/categorySlotOffset.js";
13
15
 
14
16
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
15
17
 
@@ -103,22 +105,24 @@ export const rematerializeBarMark = action(
103
105
  resolved = resolved.rematerializeScale({ id: offsetScaleId });
104
106
  }
105
107
  const width = resolved.markConfigs[id]?.barWidth;
106
- return editRectangles(
107
- resolved,
108
- id,
109
- deriveAggregateRectangles(required, resolved, width)
110
- );
108
+ return editRectangles(resolved, id, offsetCategoryRectangles(
109
+ resolved, required.layer, deriveAggregateRectangles(required, resolved, width)
110
+ ));
111
111
  }
112
112
 
113
113
  if (required.materialization === BAR_GRAINS.ranged) {
114
114
  const width = resolved.markConfigs[id]?.barWidth;
115
- return editRectangles(resolved, id, deriveRangedRectangles(required, resolved, width));
115
+ return editRectangles(resolved, id, offsetCategoryRectangles(
116
+ resolved, required.layer, deriveRangedRectangles(required, resolved, width)
117
+ ));
116
118
  }
117
119
 
118
120
  return editRectangles(
119
121
  resolved,
120
122
  id,
121
- deriveHistogramRectangles(required, resolved)
123
+ offsetCategoryRectangles(
124
+ resolved, required.layer, deriveHistogramRectangles(required, resolved)
125
+ )
122
126
  );
123
127
  }
124
128
  );
@@ -1,4 +1,5 @@
1
1
  import { removeMark } from "./remove.js";
2
+ import { createReferenceLine, createReferenceBand } from "./references.js";
2
3
  import { registerRuleMarkActions } from "./rule/index.js";
3
4
  import { registerAreaMarkActions } from "./area/index.js";
4
5
  import { registerTickMarkActions } from "./tick/index.js";
@@ -8,6 +9,7 @@ import { registerArcMarkActions } from "./arc/index.js";
8
9
  import { registerRectMarkActions } from "./rect/index.js";
9
10
  import {
10
11
  registerPointJitterActions,
12
+ registerPointPackingActions,
11
13
  registerPointMarkActions
12
14
  } from "./point/index.js";
13
15
  import { registerTextMarkActions } from "./text/index.js";
@@ -15,6 +17,7 @@ import { registerTextMarkActions } from "./text/index.js";
15
17
  export function registerMarkActions(ProgramClass) {
16
18
  registerPointMarkActions(ProgramClass);
17
19
  registerPointJitterActions(ProgramClass);
20
+ registerPointPackingActions(ProgramClass);
18
21
  registerRectMarkActions(ProgramClass);
19
22
  registerAreaMarkActions(ProgramClass);
20
23
  registerArcMarkActions(ProgramClass);
@@ -24,4 +27,6 @@ export function registerMarkActions(ProgramClass) {
24
27
  registerTextMarkActions(ProgramClass);
25
28
  registerTickMarkActions(ProgramClass);
26
29
  ProgramClass.prototype.removeMark = removeMark;
30
+ ProgramClass.prototype.createReferenceLine = createReferenceLine;
31
+ ProgramClass.prototype.createReferenceBand = createReferenceBand;
27
32
  }
@@ -71,14 +71,14 @@ function validatePolarLineConfig(layer, config) {
71
71
  }
72
72
  }
73
73
 
74
- function applyLineMaterialization(program, id, materialization, opacity) {
74
+ function applyLineMaterialization(program, id, materialization) {
75
75
  return editMarkGraphic(program, id, {
76
76
  length: materialization.commands.length,
77
77
  commands: materialization.commands,
78
78
  stroke: materialization.strokes,
79
79
  strokeWidth: materialization.strokeWidths,
80
80
  strokeDash: materialization.strokeDashes,
81
- ...(opacity === undefined ? {} : { opacity })
81
+ ...(materialization.opacities === undefined ? {} : { opacity: materialization.opacities })
82
82
  });
83
83
  }
84
84
 
@@ -205,71 +205,34 @@ const rematerializeLineMark = action(
205
205
 
206
206
  if (parallel !== undefined) {
207
207
  validateParallelRows(dataset.values, parallel.dimensions, parallel);
208
- let resolved = this;
209
- if (args.scales !== false) {
210
- for (const dimension of parallel.dimensions) {
211
- resolved = resolved.rematerializeScale({ id: dimension.scale });
212
- }
213
- }
214
- for (const channel of args.scales === false
215
- ? []
216
- : ["color", "strokeDash", "strokeWidth"]) {
217
- const scaleId = layer.encoding?.[channel]?.scale;
218
- if (scaleId !== undefined) {
219
- resolved = resolved.rematerializeScale({ id: scaleId });
220
- }
221
- }
222
- const materialization = resolveParallelLineMaterialization({
223
- rows: dataset.values,
224
- parallel,
225
- layer,
226
- resolvedScales: resolved.resolvedScales,
227
- bounds: resolveGraphicBounds(resolved),
228
- config,
229
- existingChildren,
230
- defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
231
- });
232
- return applyLineMaterialization(
233
- resolved,
234
- id,
235
- materialization,
236
- config.opacity
237
- );
238
208
  }
239
-
240
- if (
241
- polar
242
- ? thetaScaleId === undefined || radiusScaleId === undefined
243
- : xScaleId === undefined || yScaleId === undefined
244
- ) {
245
- throw new Error(
246
- polar
247
- ? `Line mark "${id}" requires theta and radius scales.`
248
- : `Line mark "${id}" requires x and y scales.`
249
- );
209
+ const positions = parallel !== undefined
210
+ ? parallel.dimensions.map(dimension => dimension.scale)
211
+ : polar ? [thetaScaleId, radiusScaleId] : [xScaleId, yScaleId];
212
+ if (positions.some(scale => scale === undefined)) {
213
+ throw new Error(`Line mark "${id}" requires ${polar ? "theta and radius" : "x and y"} scales.`);
250
214
  }
251
-
252
- let resolved = args.scales === false
253
- ? this
254
- : polar
255
- ? this
256
- .rematerializeScale({ id: thetaScaleId })
257
- .rematerializeScale({ id: radiusScaleId })
258
- : this
259
- .rematerializeScale({ id: xScaleId })
260
- .rematerializeScale({ id: yScaleId });
261
-
262
- for (const channel of args.scales === false ? [] : ["color", "strokeDash", "strokeWidth"]) {
263
- const scaleId = layer.encoding?.[channel]?.scale;
264
- if (scaleId !== undefined) {
265
- resolved = resolved.rematerializeScale({ id: scaleId });
215
+ let resolved = this;
216
+ if (args.scales !== false) {
217
+ const appearance = ["color", "strokeDash", "strokeWidth", "opacity"]
218
+ .map(channel => layer.encoding?.[channel]?.scale).filter(scale => scale !== undefined);
219
+ for (const scale of [...positions, ...appearance]) {
220
+ resolved = resolved.rematerializeScale({ id: scale });
266
221
  }
267
222
  }
223
+ const shared = {
224
+ rows: dataset.values, layer, config, existingChildren,
225
+ resolvedScales: resolved.resolvedScales,
226
+ bounds: resolveGraphicBounds(resolved),
227
+ defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
228
+ };
229
+ if (parallel !== undefined) {
230
+ return applyLineMaterialization(resolved, id,
231
+ resolveParallelLineMaterialization({ ...shared, parallel }));
232
+ }
268
233
 
269
234
  const materialization = resolvePositionedLineMaterialization({
270
- rows: dataset.values,
271
- layer,
272
- resolvedScales: resolved.resolvedScales,
235
+ ...shared,
273
236
  xBinBoundaries: layer.encoding?.x?.bin === undefined
274
237
  ? undefined
275
238
  : resolveLineBins(
@@ -277,18 +240,13 @@ const rematerializeLineMark = action(
277
240
  layer,
278
241
  requireSemanticScale(resolved, xScaleId)
279
242
  ).boundaries,
280
- bounds: resolveGraphicBounds(resolved),
281
- config,
282
- existingChildren,
283
- polar,
284
- defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
243
+ polar
285
244
  });
286
245
 
287
246
  return applyLineMaterialization(
288
247
  resolved,
289
248
  id,
290
- materialization,
291
- config.opacity
249
+ materialization
292
250
  );
293
251
  }
294
252
  );
@@ -300,16 +258,8 @@ const editLineMark = action(
300
258
  },
301
259
  function (args = {}) {
302
260
  validateMarkOptions(args, EDIT_OPTIONS, "editLineMark");
303
- if (
304
- !Object.hasOwn(args, "stroke") &&
305
- !Object.hasOwn(args, "strokeWidth") &&
306
- !Object.hasOwn(args, "opacity") &&
307
- !Object.hasOwn(args, "curve") &&
308
- !Object.hasOwn(args, "closed")
309
- ) {
310
- throw new Error(
311
- "editLineMark requires stroke, strokeWidth, opacity, curve, or closed."
312
- );
261
+ if (!EDIT_OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
262
+ throw new Error("editLineMark requires stroke, strokeWidth, opacity, curve, or closed.");
313
263
  }
314
264
  const target = Object.hasOwn(args, "target")
315
265
  ? validateUserId(args.target, "Line mark id")
@@ -324,41 +274,26 @@ const editLineMark = action(
324
274
  "editLineMark stroke cannot be combined with a color encoding."
325
275
  );
326
276
  }
277
+ for (const channel of ["strokeWidth", "opacity"]) {
278
+ if (Object.hasOwn(args, channel) && layer.encoding?.[channel]?.field !== undefined) {
279
+ throw new Error(`editLineMark ${channel} conflicts with a field encoding; use its encoder with value to replace it.`);
280
+ }
281
+ }
327
282
  if (Object.hasOwn(args, "closed") && args.closed === true &&
328
283
  (layer.encoding?.x !== undefined || isParallelLine(layer))) {
329
284
  throw new Error("Line closed requires theta/radius Polar position encodings.");
330
285
  }
331
- if (Object.hasOwn(args, "curve") && isPolarLine(layer) &&
332
- args.curve !== "linear") {
333
- throw new Error("Polar line position currently requires curve \"linear\".");
286
+ const config = { ...this.markConfigs[layer.id] };
287
+ for (const [key, validate] of Object.entries({
288
+ stroke: validateNonEmptyString,
289
+ strokeWidth: validateNonNegativeFinite,
290
+ opacity: validateUnitInterval,
291
+ curve: validateCurveInterpolation,
292
+ closed: validateClosed
293
+ })) {
294
+ if (Object.hasOwn(args, key)) config[key] = validate(args[key], `Line ${key}`);
334
295
  }
335
- if (Object.hasOwn(args, "curve") && isParallelLine(layer) &&
336
- args.curve !== "linear") {
337
- throw new Error("Parallel lines require curve \"linear\".");
338
- }
339
- const config = {
340
- ...this.markConfigs[layer.id],
341
- ...(Object.hasOwn(args, "stroke")
342
- ? { stroke: validateNonEmptyString(args.stroke, "Line stroke") }
343
- : {}),
344
- ...(Object.hasOwn(args, "strokeWidth")
345
- ? {
346
- strokeWidth: validateNonNegativeFinite(
347
- args.strokeWidth,
348
- "Line strokeWidth"
349
- )
350
- }
351
- : {}),
352
- ...(Object.hasOwn(args, "opacity")
353
- ? { opacity: validateUnitInterval(args.opacity, "Line opacity") }
354
- : {}),
355
- ...(Object.hasOwn(args, "curve")
356
- ? { curve: validateCurveInterpolation(args.curve) }
357
- : {}),
358
- ...(Object.hasOwn(args, "closed")
359
- ? { closed: validateClosed(args.closed) }
360
- : {})
361
- };
296
+ validatePolarLineConfig(layer, config);
362
297
  const next = this._withMarkConfig(layer.id, config);
363
298
  return canMaterializeLine(next, layer)
364
299
  ? next.rematerializeLineMark({ id: layer.id })
@@ -1,10 +1,8 @@
1
1
  import {
2
- deriveLineSeries,
3
- deriveLineSeriesFieldValues
2
+ deriveLineSeries
4
3
  } from "../../../grammar/lineSeries.js";
5
4
  import {
6
5
  mapContinuousScaleValues,
7
- mapOrdinalValues,
8
6
  normalizeStrokeDashPattern
9
7
  } from "../../../grammar/scales/index.js";
10
8
  import { buildCurvePathCommands } from
@@ -12,6 +10,7 @@ import { buildCurvePathCommands } from
12
10
  import { buildPolarLinePathCommands } from
13
11
  "../../../grammar/polarLineCommands.js";
14
12
  import { resolvePolarFrame } from "../../../grammar/polar.js";
13
+ import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
15
14
  import { materializeParallelRows } from
16
15
  "../../../grammar/parallelCoordinates.js";
17
16
  import { mapScaleConsumerValues } from
@@ -21,6 +20,17 @@ function existingValue(children, index, property, fallback) {
21
20
  return children[index]?.properties[property] ?? fallback;
22
21
  }
23
22
 
23
+ function appearanceMapper(layer, scales, fieldValues) {
24
+ return (channel, fallback) => {
25
+ const encoding = layer.encoding?.[channel];
26
+ if (encoding?.datum !== undefined) {
27
+ return fallback.map(() => normalizeStrokeDashPattern(encoding.datum));
28
+ }
29
+ if (encoding?.scale === undefined) return fallback;
30
+ return mapScaleConsumerValues(fieldValues(encoding.field, channel), scales[encoding.scale], channel);
31
+ };
32
+ }
33
+
24
34
  export function resolveParallelLineMaterialization({
25
35
  rows,
26
36
  parallel,
@@ -39,36 +49,25 @@ export function resolveParallelLineMaterialization({
39
49
  parallel
40
50
  );
41
51
  const sourceRows = items.map(item => rows[item.sourceRowIndex]);
42
- const mapAppearance = (channel, fallback) => {
43
- const encoding = layer.encoding?.[channel];
44
- if (encoding?.datum !== undefined) {
45
- return items.map(() => channel === "strokeDash"
46
- ? normalizeStrokeDashPattern(encoding.datum)
47
- : encoding.datum);
48
- }
49
- if (encoding?.scale === undefined) return items.map(fallback);
50
- return mapScaleConsumerValues(
51
- sourceRows.map(row => row[encoding.field]),
52
- resolvedScales[encoding.scale],
53
- channel
54
- );
55
- };
52
+ const mapAppearance = appearanceMapper(layer, resolvedScales,
53
+ field => sourceRows.map(row => row[field]));
56
54
  return {
57
55
  commands: items.map(item => item.commands),
58
56
  strokes: mapAppearance(
59
57
  "color",
60
- (_, index) => config.stroke ??
61
- existingValue(existingChildren, index, "stroke", defaults.stroke)
58
+ items.map((_, index) => config.stroke ??
59
+ existingValue(existingChildren, index, "stroke", defaults.stroke))
62
60
  ),
63
61
  strokeWidths: mapAppearance(
64
62
  "strokeWidth",
65
- (_, index) => config.strokeWidth ??
66
- existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth)
63
+ items.map((_, index) => config.strokeWidth ??
64
+ existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))
67
65
  ),
68
66
  strokeDashes: mapAppearance(
69
67
  "strokeDash",
70
- (_, index) => existingValue(existingChildren, index, "strokeDash", [])
71
- )
68
+ items.map((_, index) => existingValue(existingChildren, index, "strokeDash", []))
69
+ ),
70
+ opacities: mapAppearance("opacity", config.opacity)
72
71
  };
73
72
  }
74
73
 
@@ -116,33 +115,16 @@ export function resolvePositionedLineMaterialization({
116
115
  config.curve ?? "linear"
117
116
  );
118
117
  });
119
- const colorEncoding = layer.encoding?.color;
120
- const dashEncoding = layer.encoding?.strokeDash;
121
- const widthEncoding = layer.encoding?.strokeWidth;
122
- const strokes = colorEncoding?.scale === undefined
123
- ? commands.map((_, index) => config.stroke ??
124
- existingValue(existingChildren, index, "stroke", defaults.stroke))
125
- : mapOrdinalValues(
126
- derived.series.map(series => series.key[colorEncoding.field]),
127
- resolvedScales[colorEncoding.scale].domain,
128
- resolvedScales[colorEncoding.scale].range
129
- );
130
- const strokeWidths = widthEncoding?.scale === undefined
131
- ? commands.map((_, index) => config.strokeWidth ??
132
- existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))
133
- : mapContinuousScaleValues(
134
- deriveLineSeriesFieldValues(rows, layer, derived, widthEncoding.field),
135
- resolvedScales[widthEncoding.scale]
136
- );
137
- const strokeDashes = dashEncoding?.datum !== undefined
138
- ? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
139
- : dashEncoding?.scale === undefined
140
- ? commands.map((_, index) =>
141
- existingValue(existingChildren, index, "strokeDash", []))
142
- : mapOrdinalValues(
143
- derived.series.map(series => series.key[dashEncoding.field]),
144
- resolvedScales[dashEncoding.scale].domain,
145
- resolvedScales[dashEncoding.scale].range
146
- );
147
- return { commands, strokes, strokeWidths, strokeDashes };
118
+ const appearance = appearanceMapper(layer, resolvedScales,
119
+ (field, channel) => derivePathSeriesFieldValues(rows, derived.series, field, channel));
120
+ return {
121
+ commands,
122
+ strokes: appearance("color", commands.map((_, index) => config.stroke ??
123
+ existingValue(existingChildren, index, "stroke", defaults.stroke))),
124
+ strokeWidths: appearance("strokeWidth", commands.map((_, index) => config.strokeWidth ??
125
+ existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))),
126
+ strokeDashes: appearance("strokeDash", commands.map((_, index) =>
127
+ existingValue(existingChildren, index, "strokeDash", []))),
128
+ opacities: appearance("opacity", config.opacity)
129
+ };
148
130
  }
@@ -6,7 +6,7 @@ import {
6
6
  validateUnitInterval
7
7
  } from "../../../core/validation.js";
8
8
  import { validatePointShape } from "../../../grammar/pointShapes.js";
9
- import { findLayer } from "../../../selectors/layers.js";
9
+ import { resolveEligibleLayer } from "../../../selectors/layers.js";
10
10
  import { rematerializeExistingLegend } from "../../encodings/shared.js";
11
11
  import { validateMarkOptions } from "../shared.js";
12
12
 
@@ -27,20 +27,13 @@ export const editPointMark = action(
27
27
  "editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
28
28
  );
29
29
  }
30
- const candidates = this.semanticSpec.layers.filter(
31
- layer => layer.mark?.type === "point"
32
- );
33
- const current = findLayer(this, this.context.currentMark);
34
- const requested = args.target ??
35
- (current?.mark?.type === "point" ? current.id : undefined);
36
- const inferred = requested ?? (candidates.length === 1
37
- ? candidates[0].id
38
- : undefined);
39
- const id = validateUserId(inferred, "Point mark id");
40
- const layer = findLayer(this, id);
41
- if (layer?.mark?.type !== "point") {
42
- throw new Error(`Unknown point mark "${id}".`);
43
- }
30
+ if (args.target !== undefined) validateUserId(args.target, "Point mark id");
31
+ const layer = resolveEligibleLayer(this, {
32
+ target: args.target,
33
+ predicate: candidate => candidate.mark?.type === "point",
34
+ label: "point mark"
35
+ });
36
+ const id = layer.id;
44
37
  if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
45
38
  throw new Error(
46
39
  "editPointMark shape cannot be combined with a shape encoding."
@@ -51,6 +44,9 @@ export const editPointMark = action(
51
44
  "editPointMark fill cannot be combined with a color encoding."
52
45
  );
53
46
  }
47
+ if (Object.hasOwn(args, "opacity") && layer.encoding?.opacity?.field !== undefined) {
48
+ throw new Error("editPointMark opacity conflicts with a field encoding; use encodeOpacity with value to replace it.");
49
+ }
54
50
  const config = { ...this.markConfigs[id] };
55
51
  if (Object.hasOwn(args, "shape")) {
56
52
  config.shape = validatePointShape(args.shape);
@@ -3,6 +3,7 @@ import { editPointMark } from "./edit.js";
3
3
  import { rematerializePointMark } from "./materialize.js";
4
4
 
5
5
  export { registerPointJitterActions } from "./jitter.js";
6
+ export { registerPointPackingActions } from "./packing.js";
6
7
 
7
8
  export function registerPointMarkActions(ProgramClass) {
8
9
  ProgramClass.prototype.editPointMark = editPointMark;
@@ -37,6 +37,9 @@ export const jitterPoints = action(
37
37
  validateKeys(args, JITTER_OPTIONS, "jitterPoints");
38
38
  const policy = normalizePointJitterPolicy(args);
39
39
  const layer = resolveJitterTarget(this, args.target, policy.channel);
40
+ if (this.materializationConfigs.pointPacking?.[layer.id] !== undefined) {
41
+ throw new Error(`Point jitter on "${layer.id}" conflicts with point packing.`);
42
+ }
40
43
  return this
41
44
  ._withMaterializationConfig(["jitters", layer.id], policy)
42
45
  .rematerializePointMark({ id: layer.id });