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
@@ -1,9 +1,7 @@
1
- import {
2
- readNominalField,
3
- readQuantitativeField,
4
- readScaleField,
5
- readTemporalField
6
- } from "../../../grammar/scales/index.js";
1
+ import { isSourceOwnedText } from "../../../grammar/text.js";
2
+ import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
3
+ import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
4
+ import { readScaleField } from "../../../grammar/scales/index.js";
7
5
  import { findDataset } from "../../../selectors/datasets.js";
8
6
  import { requireSemanticScale } from "../../../selectors/scales.js";
9
7
  import { SCALED_ENCODING_CHANNELS } from "../../../core/vocabulary.js";
@@ -15,6 +13,7 @@ export function findScale(program, id) {
15
13
  export function findScaleConsumers(program, id) {
16
14
  const consumers = [];
17
15
  for (const layer of program.semanticSpec.layers) {
16
+ if (isSourceOwnedText(layer)) continue;
18
17
  for (const channel of SCALED_ENCODING_CHANNELS) {
19
18
  const encoding = layer.encoding?.[channel];
20
19
  if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
@@ -46,7 +45,7 @@ export function requireConsumerDataset(program, consumer) {
46
45
  export function isDirectCategoricalConsumer(consumer) {
47
46
  return ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(
48
47
  consumer.channel
49
- ) && consumer.encoding.fieldType === "nominal";
48
+ ) && ["nominal", "ordinal"].includes(consumer.encoding.fieldType);
50
49
  }
51
50
 
52
51
  export function readConsumerFieldValues(
@@ -55,55 +54,23 @@ export function readConsumerFieldValues(
55
54
  dataset,
56
55
  scale = findScale(program, consumer.encoding.scale)
57
56
  ) {
58
- const allowUnknown = Object.hasOwn(scale, "unknown");
59
- if (consumer.role === "parallelDimension") {
60
- return readScaleField(
61
- dataset.values,
62
- consumer.encoding.field,
63
- consumer.encoding.fieldType,
64
- { allowUnknown: true }
65
- );
66
- }
67
- if (isDirectCategoricalConsumer(consumer)) {
68
- return allowUnknown
69
- ? readScaleField(
70
- dataset.values,
71
- consumer.encoding.field,
72
- consumer.encoding.fieldType,
73
- { allowUnknown: true }
74
- )
75
- : readNominalField(dataset.values, consumer.encoding.field);
76
- }
77
- if (consumer.encoding.fieldType === "temporal") {
78
- return allowUnknown
79
- ? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
80
- allowUnknown: true
81
- })
82
- : readTemporalField(dataset.values, consumer.encoding.field);
57
+ const { field, fieldType, temporalUnit } = consumer.encoding;
58
+ if (consumer.layer.mark.type === "bar" && consumer.layer.encoding?.group !== undefined && ["xOffset", "yOffset"].includes(consumer.channel)) {
59
+ return readSeriesIdentity(dataset.values, consumer.layer).values;
83
60
  }
84
- if (["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
85
- if (!["ordinal", "band", "point"].includes(scale.type)) {
86
- throw new Error(
87
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
88
- );
89
- }
90
- return allowUnknown
91
- ? readScaleField(
92
- dataset.values,
93
- consumer.encoding.field,
94
- consumer.encoding.fieldType,
95
- { allowUnknown: true }
96
- )
97
- : readNominalField(dataset.values, consumer.encoding.field);
98
- }
99
- if (consumer.encoding.fieldType !== "quantitative") {
61
+ const parallel = consumer.role === "parallelDimension";
62
+ if (!parallel && !isDirectCategoricalConsumer(consumer) &&
63
+ fieldType !== "quantitative" && fieldType !== "temporal" &&
64
+ (!["nominal", "ordinal"].includes(fieldType) ||
65
+ !["ordinal", "band", "point"].includes(scale.type))) {
100
66
  throw new Error(
101
67
  `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
102
68
  );
103
69
  }
104
- return allowUnknown
105
- ? readScaleField(dataset.values, consumer.encoding.field, "quantitative", {
106
- allowUnknown: true
107
- })
108
- : readQuantitativeField(dataset.values, consumer.encoding.field);
70
+ if (consumer.layer.mark.type === "area" && ["x", "y", "x2", "y2"].includes(consumer.channel) && fieldType === "quantitative") {
71
+ return readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing).filter(value => value != null);
72
+ }
73
+ return readScaleField(dataset.values, field, fieldType, {
74
+ allowUnknown: parallel || Object.hasOwn(scale, "unknown"), temporalUnit
75
+ });
109
76
  }
@@ -1,3 +1,5 @@
1
+ import { isPendingMeasuredRadiusConsumer } from "../../../materialization/scales/policies/arc.js";
2
+ import { deriveMeasuredArcValues } from "../../../grammar/arcs.js";
1
3
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
4
  import {
3
5
  BAR_GRAINS,
@@ -5,9 +7,9 @@ import {
5
7
  } from "../../../grammar/bars/policy.js";
6
8
  import {
7
9
  deriveLineSeries,
8
- deriveLineSeriesFieldValues,
9
10
  resolveLineBins
10
11
  } from "../../../grammar/lineSeries.js";
12
+ import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
11
13
  import { isAggregate } from "../../../grammar/aggregate.js";
12
14
  import {
13
15
  resolveRectConsumerValues
@@ -26,6 +28,11 @@ function lineDerivationOptions(program, consumer, dataset) {
26
28
  }
27
29
 
28
30
  export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
31
+ if (consumer.layer.mark?.type === "arc" && consumer.channel === "radius" &&
32
+ findScale(program, consumer.encoding.scale).radialMapping !== undefined) {
33
+ return { matched: true, values: isPendingMeasuredRadiusConsumer(consumer) ? [] : deriveMeasuredArcValues(dataset.values, consumer.layer).map(item => item.radius) };
34
+ }
35
+
29
36
  if (consumer.layer.mark?.type === "rect") {
30
37
  return {
31
38
  matched: true,
@@ -36,29 +43,35 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
36
43
  )
37
44
  };
38
45
  }
46
+ const { layer, channel } = consumer;
47
+ const encoding = layer.encoding ?? {};
48
+ const appearance = ["strokeWidth", "opacity"].includes(channel);
39
49
  if (
40
- consumer.layer.mark?.type === "line" &&
41
- ((consumer.layer.encoding?.x !== undefined &&
42
- isAggregate(consumer.layer.encoding?.y?.aggregate) &&
43
- !(consumer.channel === "x" &&
44
- consumer.layer.encoding?.x?.bin !== undefined)) ||
45
- consumer.channel === "strokeWidth")
50
+ layer.mark?.type === "line" &&
51
+ ((["x", "y"].includes(channel) && encoding.x !== undefined &&
52
+ isAggregate(encoding.y?.aggregate) &&
53
+ !(channel === "x" &&
54
+ encoding.x?.bin !== undefined)) ||
55
+ appearance) &&
56
+ encoding.parallel === undefined &&
57
+ ((encoding.x !== undefined && encoding.y !== undefined) ||
58
+ (encoding.theta !== undefined && encoding.radius !== undefined))
46
59
  ) {
47
60
  const derived = deriveLineSeries(
48
61
  dataset.values,
49
- consumer.layer,
62
+ layer,
50
63
  lineDerivationOptions(program, consumer, dataset)
51
64
  );
52
65
  return {
53
66
  matched: true,
54
- values: consumer.channel === "strokeWidth"
55
- ? deriveLineSeriesFieldValues(
67
+ values: appearance
68
+ ? derivePathSeriesFieldValues(
56
69
  dataset.values,
57
- consumer.layer,
58
- derived,
59
- consumer.encoding.field
70
+ derived.series,
71
+ consumer.encoding.field,
72
+ channel
60
73
  )
61
- : consumer.channel === "x" ? derived.xValues : derived.yValues
74
+ : channel === "x" ? derived.xValues : derived.yValues
62
75
  };
63
76
  }
64
77
  if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {
@@ -1,4 +1,6 @@
1
+ import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
1
2
  import { normalizeRuleDatum } from "../../../grammar/rules.js";
3
+ import { normalizePositionDatum } from "../../../grammar/positionDatum.js";
2
4
  import {
3
5
  findScale,
4
6
  isDirectCategoricalConsumer,
@@ -6,7 +8,7 @@ import {
6
8
  requireConsumerDataset
7
9
  } from "./common.js";
8
10
  import { resolveMarkFamilyConsumerValues } from "./families.js";
9
- import { resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
11
+ import { CATEGORY_ORDER_CHANNELS, resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
10
12
 
11
13
  export { findScale, findScaleConsumers } from "./common.js";
12
14
  export {
@@ -16,11 +18,26 @@ export {
16
18
 
17
19
  export function resolveConsumerValues(program, consumer) {
18
20
  const dataset = requireConsumerDataset(program, consumer);
21
+ if (consumer.layer.mark.type === "rect") return resolveMarkFamilyConsumerValues(program, consumer, dataset).values;
19
22
  if (Object.hasOwn(consumer.encoding, "datum")) {
23
+ if (consumer.layer.mark.type === "area") {
24
+ readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing);
25
+ return [consumer.encoding.datum];
26
+ }
27
+ if (consumer.layer.mark.type === "text") {
28
+ return [normalizePositionDatum(
29
+ consumer.encoding.datum,
30
+ consumer.encoding.fieldType,
31
+ consumer.channel,
32
+ consumer.encoding.temporalUnit,
33
+ "Text"
34
+ )];
35
+ }
20
36
  return [normalizeRuleDatum(
21
37
  consumer.encoding.datum,
22
38
  consumer.encoding.fieldType,
23
- consumer.channel
39
+ consumer.channel,
40
+ consumer.encoding.temporalUnit
24
41
  )];
25
42
  }
26
43
  if (
@@ -44,6 +61,10 @@ export function resolveConsumerValues(program, consumer) {
44
61
  export function resolveConsumerCategoryOrder(program, consumer) {
45
62
  const order = consumer.encoding.categoryOrder;
46
63
  if (order === undefined) return undefined;
64
+ if (!CATEGORY_ORDER_CHANNELS.includes(consumer.channel) ||
65
+ !["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
66
+ throw new Error("Remove category order before assigning a non-categorical position.");
67
+ }
47
68
  const dataset = requireConsumerDataset(program, consumer);
48
69
  return resolveCategoryOrder(dataset.values, consumer.encoding.field, order);
49
70
  }
@@ -1,3 +1,5 @@
1
+ import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
2
+ import { deriveAreaSeries } from "../../../grammar/areaSeries.js";
1
3
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
4
  import { validateGeneratedItemLimit } from "../../../core/validation.js";
3
5
  import {
@@ -67,6 +69,15 @@ function resolveHistogramPartitions(program, consumer) {
67
69
  zero: xScale.zero ?? false
68
70
  });
69
71
  const colorEncoding = layer.encoding?.color;
72
+ if (layer.encoding?.group !== undefined) {
73
+ const identity = readSeriesIdentity(dataset.values, layer);
74
+ validateGeneratedItemLimit((bins.boundaries.length - 1) * identity.domain.length, "Histogram layout cell count");
75
+ const counts = bins.boundaries.slice(0, -1).map(() => identity.domain.map(() => 0));
76
+ const groups = new Map(identity.domain.map((value, index) => [value, index]));
77
+ xValues.forEach((value, index) => { const bin = findHistogramBinIndex(value, bins.boundaries);
78
+ if (bin !== -1) counts[bin][groups.get(identity.values[index])] += 1; });
79
+ return counts;
80
+ }
70
81
  if (colorEncoding?.scale === undefined) {
71
82
  return countHistogramBins(xValues, bins.boundaries).map(value => [value]);
72
83
  }
@@ -101,6 +112,12 @@ function resolveAggregatePartitions(program, consumer) {
101
112
  : derived.yValues;
102
113
  const categoryDomain = resolveOrdinalDomain(categoryScale.domain, categoryValues);
103
114
  const colorEncoding = layer.encoding?.color;
115
+ if (layer.encoding?.group !== undefined) {
116
+ const series = readSeriesIdentity(dataset.values, layer).domain;
117
+ validateGeneratedItemLimit(categoryDomain.length * series.length, "Aggregate layout cell count");
118
+ const cells = new Map(derived.values.map(value => [JSON.stringify([value[channels.category], value.series]), value[channels.measure]]));
119
+ return categoryDomain.map(category => series.map(key => cells.get(JSON.stringify([category, key])) ?? 0));
120
+ }
104
121
  if (
105
122
  colorEncoding?.scale === undefined ||
106
123
  colorEncoding.fieldType === "quantitative"
@@ -181,11 +198,25 @@ export function resolveSeriesLayoutScaleValues(program, consumer) {
181
198
  values: resolveSeriesLayoutDomainValues(partitions, layout)
182
199
  };
183
200
  }
201
+ if (consumer.layer.mark?.type === "area" && consumer.layer.layout?.mode !== undefined) {
202
+ const layer = consumer.layer;
203
+ const dataset = findDataset(program, layer.data);
204
+ const density = dataset.transform?.some(transform => transform.type === "density");
205
+ if (!density && (layer.encoding?.x === undefined || layer.encoding?.y === undefined || (layer.encoding?.x2 === undefined && layer.encoding?.y2 === undefined))) return undefined;
206
+ if (!density && layer.encoding?.x !== undefined && layer.encoding?.y !== undefined && (layer.encoding?.x2 !== undefined || layer.encoding?.y2 !== undefined)) {
207
+ const derived = deriveAreaSeries(dataset.values, layer);
208
+ const measure = derived.orientation === "vertical" ? "y" : "x";
209
+ if (![measure, `${measure}2`].includes(consumer.channel)) return undefined;
210
+ const layout = layer.layout.mode;
211
+ if (layout === "overlay") return undefined;
212
+ return { layout, values: layout === "fill" ? [0, 1] : derived[`${measure}Values`] };
213
+ }
214
+ }
184
215
  if (consumer.channel !== "y") return undefined;
185
216
  if (consumer.layer.mark?.type === "area") {
186
- const layout = consumer.layer.encoding?.y?.stack === "center"
217
+ const layout = consumer.layer.layout?.mode ?? (consumer.layer.encoding?.y?.stack === "center"
187
218
  ? "center"
188
- : consumer.layer.encoding?.color?.layout;
219
+ : consumer.layer.encoding?.color?.layout);
189
220
  if (layout === undefined || layout === "overlay") return undefined;
190
221
  const partitions = resolveAreaPartitions(program, consumer);
191
222
  if (partitions === undefined) {
@@ -35,8 +35,8 @@ const CREATE_SCALE_OPTIONS = Object.freeze([
35
35
  "padding",
36
36
  "align",
37
37
  "palette",
38
- "interpolate",
39
- "unknown"
38
+ "interpolate", "midpoint",
39
+ "radialMapping", "unknown"
40
40
  ]);
41
41
 
42
42
  function validateOptions(args) {
@@ -133,8 +133,8 @@ export const createScale = action(
133
133
 
134
134
  for (const property of [
135
135
  "nice", "zero", "clamp", "reverse", "base", "exponent", "constant",
136
- "paddingInner", "paddingOuter", "padding", "align", "interpolate",
137
- "unknown"
136
+ "paddingInner", "paddingOuter", "padding", "align", "interpolate", "midpoint",
137
+ "radialMapping", "unknown"
138
138
  ]) {
139
139
  if (definition[property] === undefined) continue;
140
140
  next = next.editSemantic({
@@ -1,9 +1,11 @@
1
+ import { prepareScaleEdit } from "./editPolicy.js";
1
2
  import { validateUserId } from "../../core/identifiers.js";
2
3
  import { isPlainObject } from "../../core/immutable.js";
3
4
  import { validateKeys } from "../../core/validation.js";
4
5
  import {
5
6
  validateColorRange,
6
7
  validateContinuousColorInterpolation,
8
+ validateSequentialMidpoint,
7
9
  validateLinearScaleType,
8
10
  validateOpacityRange,
9
11
  validateOrdinalDomain,
@@ -49,7 +51,7 @@ const POSITION_OPTIONS = [
49
51
  const COLOR_OPTIONS = [...BASE_OPTIONS, "palette", "unknown"];
50
52
  const SEQUENTIAL_COLOR_OPTIONS = [
51
53
  ...COLOR_OPTIONS,
52
- "interpolate",
54
+ "interpolate", "midpoint",
53
55
  ...CLAMP_REVERSE
54
56
  ];
55
57
  const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
@@ -121,7 +123,7 @@ export function resolvePositionScaleDefinition(
121
123
  ...normalizeScaleDefinition({
122
124
  type,
123
125
  previous: existing,
124
- patch: options,
126
+ patch: { ...options, ...(defaults.radialMapping === undefined ? {} : { radialMapping: defaults.radialMapping }) },
125
127
  defaults: {
126
128
  ...(existing === undefined && defaults.nice !== undefined
127
129
  ? { nice: defaults.nice }
@@ -210,6 +212,15 @@ export function resolveSequentialColorScaleDefinition(
210
212
  options.interpolate ?? existing?.interpolate ?? "rgb"
211
213
  )
212
214
  };
215
+ const midpoint = validateSequentialMidpoint(
216
+ Object.hasOwn(options, "midpoint") ? options.midpoint : existing?.midpoint, type, scale.domain
217
+ );
218
+ if (midpoint !== undefined) {
219
+ if (fieldType !== "quantitative") {
220
+ throw new Error("Scale midpoint requires quantitative color consumers.");
221
+ }
222
+ scale.midpoint = midpoint;
223
+ }
213
224
  return withScaleUnknown(
214
225
  assignOptions(scale, options, existing, CLAMP_REVERSE),
215
226
  { ...existing, ...options },
@@ -227,6 +238,12 @@ export function resolveQuantitativeColorScaleDefinition(
227
238
  program,
228
239
  options.id ?? "color"
229
240
  )?.type ?? "sequential";
241
+ const existing = findSemanticScale(program, options.id ?? "color");
242
+ if (existing !== undefined && existing.type !== type) {
243
+ validateKeys(options, type === "sequential" ? SEQUENTIAL_COLOR_OPTIONS : [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
244
+ if (fieldType !== "quantitative") throw new Error("Color scale type transition requires quantitative color.");
245
+ return { id: existing.id, ...prepareScaleEdit(program, existing, "color", [], options) };
246
+ }
230
247
  if (type === "sequential") {
231
248
  return resolveSequentialColorScaleDefinition(program, fieldType, options);
232
249
  }
@@ -238,7 +255,6 @@ export function resolveQuantitativeColorScaleDefinition(
238
255
  validatePaletteRange(options);
239
256
  validateBooleanOptions(options, CLAMP_REVERSE, type);
240
257
  const id = validateUserId(options.id ?? "color", "Scale id");
241
- const existing = findSemanticScale(program, id);
242
258
  const previous = existing?.type === type ? existing : undefined;
243
259
  const requestedRange = options.palette === undefined
244
260
  ? options.range
@@ -1,7 +1,9 @@
1
+ import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
2
+ import { planColorLegendTransition, applyColorLegendTransition } from "../guides/legends/transition.js";
1
3
  import { action } from "../../core/action.js";
2
4
  import { validateUserId } from "../../core/identifiers.js";
3
5
  import { validateOptionObject } from "../../core/validation.js";
4
- import { getMarkMaterializationStep } from "../../materialization/marks/index.js";
6
+ import { getMarkMaterializationStep, getSourceDependentMarkSteps } from "../../materialization/marks/index.js";
5
7
  import {
6
8
  applyMaterializationPlan
7
9
  } from "../../materialization/dependencies.js";
@@ -18,7 +20,7 @@ import {
18
20
  const OPTIONS = Object.freeze([
19
21
  "id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
20
22
  "base", "exponent", "constant", "paddingInner", "paddingOuter", "padding",
21
- "align", "interpolate", "unknown", "palette"
23
+ "align", "interpolate", "midpoint", "unknown", "palette", "radialMapping"
22
24
  ]);
23
25
  const EDITABLE = Object.freeze(
24
26
  OPTIONS.filter(option => !["id", "palette"].includes(option))
@@ -63,7 +65,41 @@ function planMarkRematerialization(program, consumers) {
63
65
  seen.add(consumer.layer.id);
64
66
  plan.push(step);
65
67
  }
66
- return plan;
68
+ return [
69
+ ...plan,
70
+ ...consumers.flatMap(consumer => getSourceDependentMarkSteps(program, consumer.layer.id))
71
+ ];
72
+ }
73
+
74
+ function applyScaleEdit(program, { id, scale, consumers, definition, legendTransition }) {
75
+ let next = legendTransition === undefined ? program
76
+ : program.removeLegend({ target: legendTransition.args.target, channels: ["color"] });
77
+ for (const property of EDITABLE) {
78
+ if (
79
+ Object.hasOwn(scale, property) &&
80
+ !Object.hasOwn(definition, property)
81
+ ) {
82
+ next = next.editSemantic({
83
+ property: `scale[${id}].${property}`,
84
+ remove: true
85
+ });
86
+ }
87
+ }
88
+ for (const property of EDITABLE) {
89
+ if (
90
+ !Object.hasOwn(definition, property) ||
91
+ sameValue(scale[property], definition[property])
92
+ ) continue;
93
+ next = next.editSemantic({
94
+ property: `scale[${id}].${property}`,
95
+ value: definition[property]
96
+ });
97
+ }
98
+ if (consumers.length === 0) return next;
99
+
100
+ next = next.rematerializeScale({ id });
101
+ next = applyMaterializationPlan(next, planMarkRematerialization(next, consumers));
102
+ return legendTransition === undefined ? next : applyColorLegendTransition(next, legendTransition);
67
103
  }
68
104
 
69
105
  export const editScale = action(
@@ -71,7 +107,7 @@ export const editScale = action(
71
107
  op: "editScale",
72
108
  description: "Edit an existing scale and rematerialize its consumers."
73
109
  },
74
- function (args = {}) {
110
+ withGuideLayoutValidation(function (args = {}) {
75
111
  validateOptionObject(args, OPTIONS, "editScale");
76
112
  if (!REQUESTED_CHANGES.some(property => Object.hasOwn(args, property))) {
77
113
  throw new Error("editScale requires at least one editable property.");
@@ -82,34 +118,10 @@ export const editScale = action(
82
118
  const channel = resolveScaleConsumerChannel(consumers, id);
83
119
  const definition = prepareScaleEdit(this, scale, channel, consumers, args);
84
120
 
85
- let next = this;
86
- for (const property of EDITABLE) {
87
- if (
88
- Object.hasOwn(scale, property) &&
89
- !Object.hasOwn(definition, property)
90
- ) {
91
- next = next.editSemantic({
92
- property: `scale[${id}].${property}`,
93
- remove: true
94
- });
95
- }
96
- }
97
- for (const property of EDITABLE) {
98
- if (
99
- !Object.hasOwn(definition, property) ||
100
- sameValue(scale[property], definition[property])
101
- ) continue;
102
- next = next.editSemantic({
103
- property: `scale[${id}].${property}`,
104
- value: definition[property]
105
- });
106
- }
107
- if (consumers.length === 0) return next;
108
-
109
- next = next.rematerializeScale({ id });
110
- return applyMaterializationPlan(
111
- next,
112
- planMarkRematerialization(next, consumers)
113
- );
114
- }
121
+ const legendTransition = planColorLegendTransition(this, scale, definition.type);
122
+ const proposal = { id, scale, consumers, definition, legendTransition };
123
+ // Preflight every dependent mark and guide on a discarded immutable branch.
124
+ if (scale.type !== definition.type) applyScaleEdit(this, proposal);
125
+ return applyScaleEdit(this, proposal);
126
+ })
115
127
  );
@@ -1,3 +1,4 @@
1
+ import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
1
2
  import {
2
3
  hasOrdinalDomain,
3
4
  isDiscretizedColorScaleType,
@@ -14,6 +15,7 @@ import {
14
15
  validateScaleType,
15
16
  validateScaleTypeForRole,
16
17
  validateScaleUnknown,
18
+ validateSequentialMidpoint,
17
19
  validateSequentialColorRange,
18
20
  validateShapeRange,
19
21
  validateSizeRange,
@@ -60,18 +62,11 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
60
62
  validateScaleType(nextType);
61
63
  if (consumers.length === 0) return;
62
64
  if (nextType === "sequential" || isDiscretizedColorScaleType(nextType)) {
63
- const discretized = isDiscretizedColorScaleType(nextType);
64
- if (channel !== "color" || consumers.some(consumer =>
65
- ["nominal", "ordinal"].includes(consumer.encoding.fieldType) ||
66
- (discretized && (
67
- consumer.encoding.fieldType !== "quantitative" ||
68
- consumer.layer.mark?.type !== "point"
69
- )) ||
70
- (!discretized && !["point", "bar"].includes(consumer.layer.mark?.type))
71
- )) {
72
- throw new Error(
73
- `Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
74
- );
65
+ if (channel !== "color") {
66
+ throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
67
+ }
68
+ for (const consumer of consumers) {
69
+ validateContinuousColorConsumer(consumer.layer, consumer.encoding, { type: nextType });
75
70
  }
76
71
  return;
77
72
  }
@@ -132,27 +127,17 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
132
127
  }
133
128
  }
134
129
 
135
- function validateLegendTypeTransition(program, scale, nextType) {
136
- if (nextType === scale.type) return;
137
- const legends = program.guideConfigs.legend ?? {};
138
- if (legends.gradient?.scale === scale.id && nextType !== "sequential") {
139
- throw new Error(
140
- `Scale "${scale.id}" cannot change type while its gradient legend is active.`
141
- );
142
- }
143
- if (
144
- legends.interval?.scale === scale.id &&
145
- !isDiscretizedColorScaleType(nextType)
146
- ) {
147
- throw new Error(
148
- `Scale "${scale.id}" cannot change type while its interval legend is active.`
149
- );
150
- }
151
- }
152
-
153
130
  function normalizeDefinition(scale, channel, consumers, patch) {
131
+ if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
132
+ patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
133
+ throw new Error("Remove measured radius encodings before clearing their scale mapping.");
134
+ }
154
135
  const type = patch.type ?? scale.type;
155
136
  validateTypeTransition(scale, type, channel, consumers);
137
+ if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
138
+ (["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
139
+ throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
140
+ }
156
141
  const definition = normalizeScaleDefinition({
157
142
  type,
158
143
  previous: scale,
@@ -175,6 +160,12 @@ function normalizeDefinition(scale, channel, consumers, patch) {
175
160
  ) {
176
161
  throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
177
162
  }
163
+ validateSequentialMidpoint(definition.midpoint, type, definition.domain);
164
+ if (definition.midpoint !== undefined && consumers.some(
165
+ consumer => consumer.channel !== "color" || consumer.encoding.fieldType !== "quantitative"
166
+ )) {
167
+ throw new Error("Scale midpoint requires quantitative color consumers.");
168
+ }
178
169
  const typeChanged = type !== scale.type;
179
170
  const unknown = Object.hasOwn(patch, "unknown")
180
171
  ? patch.unknown
@@ -209,6 +200,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
209
200
  ...Object.entries(args).filter(([key]) => key !== "palette"),
210
201
  ["range", { palette: args.palette }]
211
202
  ]);
212
- validateLegendTypeTransition(program, scale, args.type ?? scale.type);
213
203
  return normalizeDefinition(scale, channel, consumers, patch);
214
204
  }