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
@@ -150,7 +150,8 @@ const encodeColor = action(
150
150
  });
151
151
  }
152
152
  next = applyEncodingScale(next, scale, requestedScale, {
153
- reassignment: layer.encoding?.color?.scale === scale.id
153
+ reassignment: layer.encoding?.color?.scale === scale.id,
154
+ allowTypeChange: true
154
155
  });
155
156
  next = applyColorLayoutCompanion(next, {
156
157
  target,
@@ -28,12 +28,12 @@ import {
28
28
 
29
29
  const OPTIONS = Object.freeze([
30
30
  "field", "target", "source", "groupBy", "bandwidth", "extent", "steps",
31
- "kernel", "normalization", "as", "densityChannel", "coordinate",
31
+ "kernel", "normalization", "weight", "as", "densityChannel", "coordinate",
32
32
  "valueScale", "densityScale", "placement"
33
33
  ]);
34
34
  const EDIT_OPTIONS = Object.freeze([
35
35
  "target", "source", "field", "groupBy", "bandwidth", "extent", "steps",
36
- "kernel", "normalization", "densityChannel", "valueScale", "placement"
36
+ "kernel", "normalization", "weight", "densityChannel", "valueScale", "placement"
37
37
  ]);
38
38
  const EDITABLE = Object.freeze(EDIT_OPTIONS.filter(option => option !== "target"));
39
39
  const SCALE_PROPERTIES = Object.freeze([
@@ -193,6 +193,7 @@ const encodeDensity = action(
193
193
  ...(args.normalization === undefined
194
194
  ? {}
195
195
  : { normalization: args.normalization }),
196
+ ...(args.weight === undefined ? {} : { weight: args.weight }),
196
197
  ...(args.as === undefined ? {} : { as: args.as }),
197
198
  ...(placement === undefined ? {} : { placement })
198
199
  };
@@ -350,6 +351,7 @@ const editDensity = action(
350
351
  const option = property => Object.hasOwn(args, property)
351
352
  ? args[property]
352
353
  : transform[property];
354
+ const weight = option("weight");
353
355
 
354
356
  const dataArgs = {
355
357
  id: revision.id,
@@ -364,6 +366,7 @@ const editDensity = action(
364
366
  kernel: option("kernel") ?? "gaussian",
365
367
  normalization: option("normalization") ?? "unit",
366
368
  as: transform.as,
369
+ ...(weight === undefined || weight === false ? {} : { weight }),
367
370
  ...(requestedPlacement === undefined
368
371
  ? {}
369
372
  : { placement: requestedPlacement })
@@ -3,7 +3,7 @@ import { validateOptions } from "./shared.js";
3
3
 
4
4
  const HISTOGRAM_OPTIONS = Object.freeze([
5
5
  "field", "target", "coordinate", "maxBins", "binStep", "binBoundaries",
6
- "stack", "xScale", "yScale"
6
+ "stack", "xScale", "yScale", "weight"
7
7
  ]);
8
8
 
9
9
  const encodeHistogram = action(
@@ -28,7 +28,8 @@ const encodeHistogram = action(
28
28
  : { maxBins: args.maxBins ?? 10 };
29
29
  const x = {
30
30
  field: args.field,
31
- bin
31
+ bin,
32
+ ...(args.weight === undefined ? {} : { weight: args.weight })
32
33
  };
33
34
  const y = {
34
35
  stack: Object.hasOwn(args, "stack") ? args.stack : "zero"
@@ -13,8 +13,10 @@ import { registerTextEncodingAction } from "./text.js";
13
13
  import { registerAppearanceEncodingAction } from "./appearance.js";
14
14
  import { registerStrokeDashEncodingActions } from "./strokeDash.js";
15
15
  import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
16
+ import { registerStrokeEncodingAction } from "./stroke.js";
16
17
  import { registerBarWidthEncodingAction } from "./barWidth.js";
17
18
  import { registerEncodingRemovalAction } from "./remove.js";
19
+ import { registerAtomicEncodingAction } from "./channels.js";
18
20
 
19
21
  export function registerEncodingActions(ProgramClass) {
20
22
  registerSeriesLayoutAction(ProgramClass);
@@ -31,7 +33,9 @@ export function registerEncodingActions(ProgramClass) {
31
33
  registerAppearanceEncodingAction(ProgramClass);
32
34
  registerAngleEncodingAction(ProgramClass);
33
35
  registerRuleAppearanceEncodingActions(ProgramClass);
36
+ registerStrokeEncodingAction(ProgramClass);
34
37
  registerTextEncodingAction(ProgramClass);
35
38
  registerParallelEncodingAction(ProgramClass);
36
39
  registerEncodingRemovalAction(ProgramClass);
40
+ registerAtomicEncodingAction(ProgramClass);
37
41
  }
@@ -18,7 +18,6 @@ import {
18
18
  resolveBarChannels,
19
19
  resolveBarGrain
20
20
  } from "../../grammar/bars/policy.js";
21
- import { normalizeOffsetPadding } from "../../grammar/bars/geometry.js";
22
21
 
23
22
  const ENCODING_OPTIONS = Object.freeze([
24
23
  "field", "target", "fieldType", "scale", "paddingInner", "paddingOuter"
@@ -69,30 +68,38 @@ function createOffsetEncoding(channel) {
69
68
  layer.encoding?.[channel],
70
69
  args.scale ?? {}
71
70
  );
72
- const scale = resolveOffsetScaleDefinition(this, requestedScale, channel);
71
+ const scaleOptions = {
72
+ ...requestedScale,
73
+ ...Object.fromEntries(
74
+ ["paddingInner", "paddingOuter"]
75
+ .filter(property => Object.hasOwn(args, property))
76
+ .map(property => [property, args[property]])
77
+ )
78
+ };
79
+ const scale = resolveOffsetScaleDefinition(this, scaleOptions, channel);
73
80
  if (Object.hasOwn(scale, "unknown")) {
74
81
  throw new Error(
75
82
  `${channel} scale unknown is not supported for offset positions.`
76
83
  );
77
84
  }
78
- const padding = normalizeOffsetPadding(
79
- args,
80
- this.markConfigs[target]?.[channel],
81
- channel
82
- );
83
85
 
84
86
  const grouped = layer.mark.type === "bar" ? inferSeriesGroup(this, layer, args.field, "offset") : this;
85
87
  let next = setEncodingProperties(grouped, target, channel, {
86
88
  field: args.field,
87
89
  fieldType,
88
90
  scale: scale.id
89
- })._withMarkConfig(target, {
90
- ...this.markConfigs[target],
91
- [channel]: padding
92
- });
93
- next = applyEncodingScale(next, scale, requestedScale, {
94
- reassignment: layer.encoding?.[channel]?.scale === scale.id
95
91
  });
92
+ next = applyEncodingScale(next, scale, {
93
+ ...Object.fromEntries(Object.entries(scaleOptions).filter(
94
+ ([property]) => property !== "range"
95
+ )),
96
+ paddingInner: scale.paddingInner,
97
+ paddingOuter: scale.paddingOuter,
98
+ align: scale.align
99
+ }, {
100
+ reassignment: findSemanticScale(this, scale.id) !== undefined
101
+ });
102
+ next = next._withoutMaterializationConfig(["marks", target, channel]);
96
103
  if (layer.mark.type === "bar") {
97
104
  if (findSemanticScale(this, scale.id) === undefined && args.scale?.id === undefined) next = next._withMarkConfig(target, { ...next.markConfigs[target], seriesOffsetScale: scale.id });
98
105
  if (layer.layout?.mode !== "group" || layer.encoding?.group?.field !== args.field) return next.layoutSeries({ target, mode: "group" });
@@ -66,18 +66,35 @@ function applyBin(program, target, channel, previous, bin) {
66
66
  });
67
67
  }
68
68
 
69
- function applyArcThetaWeight(program, target, channel, previous, aggregate, weight) {
70
- if (channel !== "theta") return program;
71
- if (aggregate !== "sum") {
69
+ function applyPositionWeight(
70
+ program,
71
+ target,
72
+ channel,
73
+ previous,
74
+ bin,
75
+ aggregate,
76
+ weight
77
+ ) {
78
+ const histogram = channel === "x" && bin !== undefined;
79
+ const arcSum = channel === "theta" && aggregate === "sum";
80
+ if (!histogram && !arcSum) {
81
+ return previous?.weight === undefined
82
+ ? program
83
+ : program.editSemantic({
84
+ property: `layer[${target}].encoding.${channel}.weight`,
85
+ remove: true
86
+ });
87
+ }
88
+ if (weight === undefined) {
72
89
  return previous?.weight === undefined
73
90
  ? program
74
91
  : program.editSemantic({
75
- property: `layer[${target}].encoding.theta.weight`,
92
+ property: `layer[${target}].encoding.${channel}.weight`,
76
93
  remove: true
77
94
  });
78
95
  }
79
96
  return program.editSemantic({
80
- property: `layer[${target}].encoding.theta.weight`,
97
+ property: `layer[${target}].encoding.${channel}.weight`,
81
98
  value: weight
82
99
  });
83
100
  }
@@ -104,6 +121,26 @@ function removeStaleArcThetaAggregate(
104
121
  });
105
122
  }
106
123
 
124
+ function removeMeasuredRadiusAggregate(
125
+ program,
126
+ target,
127
+ channel,
128
+ previous,
129
+ clearRadialMapping
130
+ ) {
131
+ if (
132
+ channel !== "radius" ||
133
+ !clearRadialMapping ||
134
+ previous?.aggregate === undefined
135
+ ) {
136
+ return program;
137
+ }
138
+ return program.editSemantic({
139
+ property: `layer[${target}].encoding.radius.aggregate`,
140
+ remove: true
141
+ });
142
+ }
143
+
107
144
  export function applyPositionSemantics(program, {
108
145
  target,
109
146
  channel,
@@ -117,7 +154,8 @@ export function applyPositionSemantics(program, {
117
154
  bin,
118
155
  aggregate,
119
156
  stack,
120
- weight
157
+ weight,
158
+ clearRadialMapping
121
159
  }) {
122
160
  let next = reconcileInheritedRulePosition(program, {
123
161
  target,
@@ -160,11 +198,19 @@ export function applyPositionSemantics(program, {
160
198
  fieldType,
161
199
  aggregate
162
200
  );
163
- return applyArcThetaWeight(
201
+ next = removeMeasuredRadiusAggregate(
202
+ next,
203
+ target,
204
+ channel,
205
+ previous,
206
+ clearRadialMapping
207
+ );
208
+ return applyPositionWeight(
164
209
  next,
165
210
  target,
166
211
  channel,
167
212
  previous,
213
+ bin,
168
214
  aggregate,
169
215
  weight
170
216
  );
@@ -31,7 +31,8 @@ function encodePosition(program, channel, args, operation) {
31
31
  aggregate,
32
32
  stack,
33
33
  weight,
34
- companion
34
+ companion,
35
+ clearRadialMapping
35
36
  } = resolvePositionEncoding(program, channel, args, operation);
36
37
 
37
38
  let next = program
@@ -53,13 +54,20 @@ function encodePosition(program, channel, args, operation) {
53
54
  bin,
54
55
  aggregate,
55
56
  stack,
56
- weight
57
+ weight,
58
+ clearRadialMapping
57
59
  });
58
60
 
59
61
  next = next.editSemantic({
60
62
  property: `layer[${target}].encoding.${channel}.scale`,
61
63
  value: scale.id
62
64
  });
65
+ if (
66
+ clearRadialMapping &&
67
+ findSemanticScale(next, scale.id)?.radialMapping !== undefined
68
+ ) {
69
+ next = next.editScale({ id: scale.id, radialMapping: undefined });
70
+ }
63
71
  next = applyEncodingScale(next, scale, requestedScale, {
64
72
  reassignment: previous?.scale === scale.id,
65
73
  allowTypeChange: layer.mark.type === "area"
@@ -10,6 +10,8 @@ import {
10
10
  resolveBarOrientation
11
11
  } from "../../../../grammar/bars/policy.js";
12
12
  import { resolveBin, validateStack } from "./common.js";
13
+ import { normalizeStatisticalWeight } from
14
+ "../../../../grammar/weightedStatistics.js";
13
15
 
14
16
  function resolveBarChannelPolicy({
15
17
  program,
@@ -37,6 +39,10 @@ function resolveBarChannelPolicy({
37
39
  throw new Error("Binned bar x encoding does not support aggregate or stack.");
38
40
  }
39
41
  bin = resolveBin(args.bin);
42
+ const weight = args.weight === undefined
43
+ ? undefined
44
+ : normalizeStatisticalWeight(args.weight, "Histogram weight");
45
+ return { bin, aggregate, stack, weight };
40
46
  } else if (
41
47
  fieldType === "quantitative" &&
42
48
  channel === "y" &&
@@ -89,6 +95,9 @@ function resolveBarChannelPolicy({
89
95
  );
90
96
  }
91
97
 
98
+ if (args.weight !== undefined) {
99
+ throw new Error("Bar weight is supported only for a binned x encoding.");
100
+ }
92
101
  return { bin, aggregate, stack };
93
102
  }
94
103
 
@@ -97,19 +97,26 @@ export function resolvePositionEncoding(program, channel, args, operation) {
97
97
  args.target,
98
98
  getPositionChannelDefinition(channel).markTypes
99
99
  );
100
+ const previous = layer.encoding?.[channel];
100
101
  if (isSourceOwnedText(layer)) {
101
102
  throw new Error(`${operation} cannot replace source-owned Text positions; edit the source, use dx/dy, or create independent Text with data.`);
102
103
  }
103
104
  if (
104
105
  Object.hasOwn(args, "weight") &&
105
- !(layer.mark.type === "arc" && channel === "theta")
106
+ !(layer.mark.type === "arc" && channel === "theta") &&
107
+ !(layer.mark.type === "bar" && channel === "x" &&
108
+ (args.bin !== undefined || previous?.bin !== undefined))
106
109
  ) {
107
- throw new Error(`${operation} weight is supported only for arc theta encoding.`);
110
+ throw new Error(
111
+ `${operation} weight is supported only for arc theta encoding or histogram x encoding.`
112
+ );
108
113
  }
109
114
  if (Object.hasOwn(args, "mapping") && (layer.mark.type !== "arc" || channel !== "radius")) {
110
115
  throw new Error(`${operation} mapping requires Arc radius.`);
111
116
  }
112
117
  validateCoordinateFamily(layer, channel, operation);
118
+ const clearRadialMapping =
119
+ layer.mark.type === "arc" && channel === "radius" && args.mapping === false;
113
120
  const hasField = Object.hasOwn(args, "field");
114
121
  const hasDatum = Object.hasOwn(args, "datum");
115
122
  if (["rule", "rect", "text", "point", "tick"].includes(layer.mark.type)) {
@@ -128,9 +135,10 @@ export function resolvePositionEncoding(program, channel, args, operation) {
128
135
  } else if (hasDatum) {
129
136
  throw new Error(`${operation} does not support datum for a ${layer.mark.type} mark.`);
130
137
  }
131
- const previous = layer.encoding?.[channel];
132
138
  const mapping = channel === "radius" && layer.mark.type === "arc"
133
- ? args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
139
+ ? clearRadialMapping
140
+ ? undefined
141
+ : args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
134
142
  : undefined;
135
143
  const countRadius = mapping !== undefined && (args.aggregate ?? previous?.aggregate) === "count";
136
144
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
@@ -165,7 +173,9 @@ export function resolvePositionEncoding(program, channel, args, operation) {
165
173
  validateAggregateFieldValues(dataset.values, field, fieldType);
166
174
  } else readScaleField(dataset.values, field, fieldType, { temporalUnit });
167
175
  }
168
- const effectiveArgs = { ...args, ...(mapping === undefined ? {} : { mapping }) };
176
+ const effectiveArgs = { ...args };
177
+ if (clearRadialMapping) delete effectiveArgs.mapping;
178
+ else if (mapping !== undefined) effectiveArgs.mapping = mapping;
169
179
  const directQuantitativeArcTheta =
170
180
  layer.mark.type === "arc" &&
171
181
  channel === "theta" &&
@@ -173,7 +183,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
173
183
  for (const property of ["aggregate", "bin", "stack"]) {
174
184
  if (!Object.hasOwn(effectiveArgs, property) && previous !== undefined &&
175
185
  Object.hasOwn(previous, property) &&
176
- !(directQuantitativeArcTheta && property === "aggregate")) {
186
+ !(directQuantitativeArcTheta && property === "aggregate") &&
187
+ !(clearRadialMapping && property === "aggregate")) {
177
188
  effectiveArgs[property] = previous[property];
178
189
  }
179
190
  }
@@ -201,7 +212,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
201
212
  previous,
202
213
  Object.hasOwn(args, "scale") ? args.scale : {}
203
214
  );
204
- const scale = resolvePositionScaleDefinition(
215
+ const resolvedScale = resolvePositionScaleDefinition(
205
216
  program,
206
217
  channel,
207
218
  aggregateOutput ? "quantitative" : fieldType,
@@ -229,6 +240,11 @@ export function resolvePositionEncoding(program, channel, args, operation) {
229
240
  }
230
241
  : {}
231
242
  );
243
+ const scale = clearRadialMapping
244
+ ? Object.fromEntries(
245
+ Object.entries(resolvedScale).filter(([property]) => property !== "radialMapping")
246
+ )
247
+ : resolvedScale;
232
248
  if (
233
249
  layer.mark.type === "bar" &&
234
250
  program.markConfigs[target]?.boxPlot === undefined &&
@@ -310,6 +326,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
310
326
  temporalUnit,
311
327
  scale,
312
328
  coordinate: resolveCoordinate(program, channel, layer, args.coordinate),
329
+ clearRadialMapping,
313
330
  ...policy
314
331
  };
315
332
  }
@@ -25,11 +25,12 @@ import { applyDetachedScaleRematerialization } from "../../materialization/depen
25
25
  import { findDataset } from "../../selectors/datasets.js";
26
26
  import { assertPathGroupCompatible, validatePathGroupAppearance } from "../../materialization/marks/grouping.js";
27
27
  import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
28
+ import { planLegendBlockTransitions } from "../guides/legends/transition.js";
28
29
 
29
30
  const OPTIONS = Object.freeze(["target", "channel"]);
30
31
  const REMOVABLE_CHANNELS = Object.freeze([
31
32
  "x", "y", "x2", "y2", "xOffset", "yOffset", "theta", "radius",
32
- "color", "strokeDash", "strokeWidth", "size", "shape", "group",
33
+ "color", "stroke", "strokeDash", "strokeWidth", "size", "shape", "group",
33
34
  "angle", "opacity", "text"
34
35
  ]);
35
36
  const REMOVE_AXIS = Object.freeze({
@@ -40,6 +41,7 @@ const REMOVE_AXIS = Object.freeze({
40
41
  });
41
42
  const SPECIALIZED_LEGEND_KIND = Object.freeze({
42
43
  color: Object.freeze(["gradient", "interval"]),
44
+ stroke: Object.freeze(["strokeGradient", "strokeInterval"]),
43
45
  size: Object.freeze(["size"]),
44
46
  opacity: Object.freeze(["opacity"]),
45
47
  strokeWidth: Object.freeze(["strokeWidth"])
@@ -157,7 +159,7 @@ function cleanupPositionGuides(program, removed) {
157
159
  }
158
160
 
159
161
  function reconcileCategoricalLegend(program, target, channels) {
160
- const entry = ["series", "color"]
162
+ const entry = ["series", "color", "stroke"]
161
163
  .map(kind => [kind, program.guideConfigs.legend?.[kind]])
162
164
  .find(([, config]) =>
163
165
  config?.target === target &&
@@ -172,7 +174,10 @@ function reconcileCategoricalLegend(program, target, channels) {
172
174
  if (remaining.length === 0) {
173
175
  return removeLegendKinds(program, [kind]);
174
176
  }
175
- const revision = resolveCategoricalLegendRevision(program, kind, config, remaining);
177
+ const resolved = resolveCategoricalLegendRevision(program, kind, config, remaining);
178
+ const revision = planLegendBlockTransitions(program, target, [
179
+ { ...resolved, kind: resolved.config.kind }
180
+ ])[0];
176
181
  const next = removeLegendKinds(program, [kind]);
177
182
  return createCategoricalLegendFromConfig(next, revision.config, revision.order);
178
183
  }
@@ -218,7 +223,7 @@ export const removeEncoding = action(
218
223
  }
219
224
  const channels = activeCascade(layer, args.channel);
220
225
  assertEncodingSelectionCompatibility(this, layer.id, channels);
221
- for (const kind of ["series", "color"]) {
226
+ for (const kind of ["series", "color", "stroke"]) {
222
227
  const config = this.guideConfigs.legend?.[kind];
223
228
  const order = this.semanticSpec.guides.legend?.[kind]?.order;
224
229
  if (config?.target === layer.id && channels.includes(order?.channel) &&
@@ -1,8 +1,5 @@
1
1
  import { action } from "../../core/action.js";
2
- import {
3
- validateRuleStroke,
4
- validateRuleStrokeWidth
5
- } from "../../grammar/ruleAppearance.js";
2
+ import { validateRuleStrokeWidth } from "../../grammar/ruleAppearance.js";
6
3
  import { readQuantitativeField } from "../../grammar/scales/index.js";
7
4
  import { resolveStrokeWidthScaleDefinition } from "../scales/definitions.js";
8
5
  import {
@@ -18,28 +15,10 @@ import { findLayer } from "../../selectors/layers.js";
18
15
  import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
19
16
  import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
20
17
 
21
- const STROKE_OPTIONS = Object.freeze(["target", "value"]);
22
18
  const WIDTH_OPTIONS = Object.freeze([
23
19
  "target", "value", "field", "fieldType", "scale"
24
20
  ]);
25
21
 
26
- const encodeStroke = action(
27
- {
28
- op: "encodeStroke",
29
- description: "Set a constant graphical stroke on a rule mark."
30
- },
31
- function (args = {}) {
32
- validateOptions(args, STROKE_OPTIONS, "encodeStroke");
33
- const { id } = resolveTarget(this, args.target, ["rule"], "rule mark");
34
- return this
35
- ._withMarkConfig(id, {
36
- ...this.markConfigs[id],
37
- stroke: validateRuleStroke(args.value)
38
- })
39
- .rematerializeRuleMark({ id });
40
- }
41
- );
42
-
43
22
  const encodeStrokeWidth = action(
44
23
  {
45
24
  op: "encodeStrokeWidth",
@@ -119,6 +98,5 @@ const encodeStrokeWidth = action(
119
98
  );
120
99
 
121
100
  export function registerRuleAppearanceEncodingActions(ProgramClass) {
122
- ProgramClass.prototype.encodeStroke = encodeStroke;
123
101
  ProgramClass.prototype.encodeStrokeWidth = encodeStrokeWidth;
124
102
  }
@@ -85,6 +85,7 @@ export const layoutSeries = action({ op: "layoutSeries", description: "Assign se
85
85
  next = offset.field === undefined
86
86
  ? setEncodingProperties(next, target, offsetChannel, offset).createScale(offsetScale)
87
87
  : next[offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset"]({ target, field: offset.field, fieldType: offset.fieldType, scale: offsetScale });
88
+ next = next._withoutMaterializationConfig(["marks", target, offsetChannel]);
88
89
  if (findSemanticScale(this, offsetScale.id) === undefined) next = next._withMarkConfig(target, { ...next.markConfigs[target], seriesOffsetScale: offsetScale.id });
89
90
  } else {
90
91
  const owned = next.markConfigs[target]?.seriesOffsetScale;
@@ -8,6 +8,7 @@ import { planEncodingRematerialization } from "../../materialization/encodings.j
8
8
  import { findSemanticScale } from "../../selectors/scales.js";
9
9
  import { validateOptionObject } from "../../core/validation.js";
10
10
  import { getMarkGraphicTypes, getPositionChannelDefinition } from "../../core/vocabulary.js";
11
+ import { DEFAULT_TICK_COUNT } from "../guides/tickValues.js";
11
12
 
12
13
  export function validateOptions(args, supported, operation) {
13
14
  validateOptionObject(args, supported, operation);
@@ -105,33 +106,100 @@ export function rebindPositionGuides(
105
106
  const direction = definition.gridDirection;
106
107
  const grid = program.semanticSpec.guides.grid?.[direction];
107
108
  const ownsAxis = axis?.scale === previousScale;
109
+ const refreshesReboundAxis = axis?.scale === nextScale;
108
110
  const ownsGrid = grid?.scale === previousScale;
109
- if (!ownsAxis && !ownsGrid) return program;
111
+ if (!ownsAxis && !ownsGrid && !refreshesReboundAxis) return program;
110
112
 
111
113
  const remaining = program.semanticSpec.layers.some(layer =>
112
114
  layer.id !== target && !isSourceOwnedText(layer) && layer.encoding?.[channel]?.scale === previousScale
113
115
  );
114
- if (remaining) {
116
+ if ((ownsAxis || ownsGrid) && remaining) {
115
117
  throw new Error(
116
118
  `Cannot rebind ${channel} guides from shared scale "${previousScale}" while it has other consumers.`
117
119
  );
118
120
  }
119
121
 
120
122
  let next = program;
121
- if (ownsAxis) {
122
- next = next.editSemantic({
123
- property: `guide.axis.${definition.guideChannel}.scale`,
124
- value: nextScale
125
- });
126
- for (const component of ["line", "ticks", "labels", "title"]) {
127
- const config = next.guideConfigs.axis?.[definition.guideChannel]?.[component];
128
- if (config?.scale === previousScale) {
129
- next = next._withGuideConfig(definition.guideChannel, component, {
130
- ...config,
131
- scale: nextScale
132
- });
123
+ if (ownsAxis || refreshesReboundAxis) {
124
+ const previousAxisConfig = next.guideConfigs.axis?.[definition.guideChannel];
125
+ const previousTicks = previousAxisConfig?.ticks;
126
+ const previousLabels = previousAxisConfig?.labels;
127
+ const labelsFollowTicks = previousTicks !== undefined &&
128
+ previousLabels !== undefined &&
129
+ previousTicks.mode === previousLabels.mode &&
130
+ (previousTicks.mode === "count"
131
+ ? previousTicks.count === previousLabels.count
132
+ : previousTicks.values?.length === previousLabels.values?.length &&
133
+ previousTicks.values.every(
134
+ (value, index) => Object.is(value, previousLabels.values[index])
135
+ ));
136
+ if (ownsAxis) {
137
+ next = next.editSemantic({
138
+ property: `guide.axis.${definition.guideChannel}.scale`,
139
+ value: nextScale
140
+ });
141
+ for (const component of ["line", "ticks", "labels", "title"]) {
142
+ const config = next.guideConfigs.axis?.[definition.guideChannel]?.[component];
143
+ if (config?.scale === previousScale) {
144
+ next = next._withGuideConfig(definition.guideChannel, component, {
145
+ ...config,
146
+ scale: nextScale
147
+ });
148
+ }
133
149
  }
134
150
  }
151
+ const resolved = next.resolvedScales[nextScale] ??
152
+ findSemanticScale(next, nextScale);
153
+ const discrete = ["ordinal", "band", "point"].includes(resolved?.type);
154
+ let ticks = next.guideConfigs.axis?.[definition.guideChannel]?.ticks;
155
+ if (ticks !== undefined && discrete && ticks.mode !== "values") {
156
+ ticks = {
157
+ ...ticks,
158
+ mode: "values",
159
+ values: resolved.domain,
160
+ inferredValues: true
161
+ };
162
+ delete ticks.count;
163
+ next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
164
+ } else if (
165
+ ticks !== undefined &&
166
+ !discrete &&
167
+ ticks.mode === "values" &&
168
+ ticks.inferredValues === true
169
+ ) {
170
+ ticks = {
171
+ ...ticks,
172
+ mode: "count",
173
+ count: DEFAULT_TICK_COUNT,
174
+ inferredValues: true
175
+ };
176
+ delete ticks.values;
177
+ next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
178
+ } else if (
179
+ ticks !== undefined &&
180
+ discrete &&
181
+ ticks.inferredValues === true
182
+ ) {
183
+ ticks = { ...ticks, values: resolved.domain };
184
+ next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
185
+ }
186
+ if (previousLabels !== undefined && labelsFollowTicks && ticks !== undefined) {
187
+ const labels = {
188
+ ...next.guideConfigs.axis[definition.guideChannel].labels,
189
+ mode: ticks.mode,
190
+ inferredValues: ticks.inferredValues === true,
191
+ ...(ticks.mode === "values"
192
+ ? { values: ticks.values }
193
+ : { count: ticks.count })
194
+ };
195
+ if (ticks.mode === "values") delete labels.count;
196
+ else delete labels.values;
197
+ next = next._withGuideConfig(
198
+ definition.guideChannel,
199
+ "labels",
200
+ labels
201
+ );
202
+ }
135
203
  }
136
204
  if (ownsGrid) {
137
205
  next = next