ggaction 0.0.13 → 0.0.16

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 (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  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/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -14,9 +14,11 @@ import {
14
14
  isColorScaleType,
15
15
  isContinuousColorScaleType,
16
16
  isDiscretizedColorScaleType,
17
+ normalizeSizeScaleDefinition,
17
18
  normalizeScaleDefinition
18
19
  } from "../../grammar/scales/index.js";
19
20
  import { findSemanticScale } from "../../selectors/scales.js";
21
+ import { findScaleConsumers } from "./consumers/index.js";
20
22
 
21
23
  const CREATE_SCALE_OPTIONS = Object.freeze([
22
24
  "id",
@@ -75,7 +77,7 @@ function assertEquivalentScale(existing, expected) {
75
77
  }
76
78
  }
77
79
 
78
- export const createScale = action(
80
+ export const createScale = /* @__PURE__ */ action(
79
81
  {
80
82
  op: "createScale",
81
83
  description: "Create a named semantic scale."
@@ -97,28 +99,58 @@ export const createScale = action(
97
99
  const requestedRange = args.palette === undefined
98
100
  ? args.range
99
101
  : { palette: args.palette };
100
- const definition = normalizeScaleDefinition({
101
- type,
102
- patch: {
103
- ...args,
104
- ...(requestedRange === undefined ? {} : { range: requestedRange })
105
- },
106
- validateDomain: (scaleType, value) =>
107
- isDiscretizedColorScaleType(scaleType)
108
- ? validateDiscretizedColorDomain(scaleType, value)
109
- : hasOrdinalDomain(scaleType)
110
- ? validateOrdinalDomain(value)
111
- : validateScaleDomain(value),
112
- validateRange: (scaleType, value) =>
113
- isDiscretizedColorScaleType(scaleType)
114
- ? validateDiscretizedColorRange(value)
115
- : isContinuousColorScaleType(scaleType)
116
- ? validateSequentialColorRange(value)
117
- : scaleType === "ordinal"
118
- ? validateOrdinalRange(value)
119
- : validateScaleRange(value)
120
- });
121
- if (Object.hasOwn(args, "unknown")) definition.unknown = args.unknown;
102
+ const consumers = findScaleConsumers(this, id);
103
+ const offsetScale = consumers.length > 0 && consumers.every(
104
+ consumer => ["xOffset", "yOffset"].includes(consumer.channel)
105
+ );
106
+ const sizeScale = consumers.length > 0 && consumers.every(
107
+ consumer => consumer.channel === "size"
108
+ );
109
+ if (offsetScale && type !== "ordinal") {
110
+ throw new Error(`Scale type "${type}" is not valid for offset positions.`);
111
+ }
112
+ if (
113
+ offsetScale &&
114
+ Object.hasOwn(args, "range") &&
115
+ args.range !== "auto"
116
+ ) {
117
+ throw new Error(
118
+ "Offset scale range is derived from its parent categorical slot."
119
+ );
120
+ }
121
+ const definition = sizeScale
122
+ ? normalizeSizeScaleDefinition({
123
+ patch: {
124
+ ...args,
125
+ type,
126
+ ...(requestedRange === undefined ? {} : { range: requestedRange })
127
+ }
128
+ })
129
+ : normalizeScaleDefinition({
130
+ type,
131
+ patch: {
132
+ ...args,
133
+ ...(requestedRange === undefined ? {} : { range: requestedRange })
134
+ },
135
+ allowOrdinalBandParameters: offsetScale,
136
+ validateDomain: (scaleType, value) =>
137
+ isDiscretizedColorScaleType(scaleType)
138
+ ? validateDiscretizedColorDomain(scaleType, value)
139
+ : hasOrdinalDomain(scaleType)
140
+ ? validateOrdinalDomain(value)
141
+ : validateScaleDomain(value),
142
+ validateRange: (scaleType, value) =>
143
+ isDiscretizedColorScaleType(scaleType)
144
+ ? validateDiscretizedColorRange(value)
145
+ : isContinuousColorScaleType(scaleType)
146
+ ? validateSequentialColorRange(value)
147
+ : scaleType === "ordinal"
148
+ ? validateOrdinalRange(value)
149
+ : validateScaleRange(value)
150
+ });
151
+ if (Object.hasOwn(args, "unknown") && !sizeScale) {
152
+ definition.unknown = args.unknown;
153
+ }
122
154
  const existing = findSemanticScale(this, id);
123
155
 
124
156
  if (existing !== undefined) {
@@ -13,7 +13,6 @@ import {
13
13
  validateScaleDomain,
14
14
  validateScaleRange,
15
15
  validateShapeRange,
16
- validateSizeRange,
17
16
  validateStrokeDashRange,
18
17
  validateStrokeWidthRange,
19
18
  validateSequentialColorRange,
@@ -25,6 +24,7 @@ import {
25
24
  validateScalePropertyForType,
26
25
  validateScaleTypeForRole,
27
26
  isDiscretePositionScaleType,
27
+ normalizeSizeScaleDefinition,
28
28
  withScaleUnknown
29
29
  } from "../../grammar/scales/index.js";
30
30
  import {
@@ -32,6 +32,10 @@ import {
32
32
  validateThetaRange
33
33
  } from "../../grammar/polar.js";
34
34
  import { findSemanticScale } from "../../selectors/scales.js";
35
+ import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
36
+ import { resolveRequestedOffsetPolicy } from
37
+ "../../materialization/scales/policies/offset.js";
38
+ import { findScaleConsumers } from "./consumers/index.js";
35
39
 
36
40
  const BASE_OPTIONS = ["id", "type", "domain", "range"];
37
41
  const UNKNOWN_OPTIONS = [...BASE_OPTIONS, "unknown"];
@@ -58,6 +62,9 @@ const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
58
62
  const STROKE_WIDTH_OPTIONS = [
59
63
  ...BASE_OPTIONS, ...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS
60
64
  ];
65
+ const SIZE_OPTIONS = [
66
+ ...UNKNOWN_OPTIONS, "clamp", "reverse", "base", "exponent"
67
+ ];
61
68
 
62
69
  function optionsObject(options) {
63
70
  if (!isPlainObject(options)) {
@@ -151,21 +158,22 @@ export function resolvePositionScaleDefinition(
151
158
  return withScaleUnknown(scale, { ...existing, ...options }, channel);
152
159
  }
153
160
 
154
- export function resolveColorScaleDefinition(program, options) {
161
+ export function resolveColorScaleDefinition(program, options, channel = "color") {
155
162
  optionsObject(options);
156
163
  validateKeys(options, COLOR_OPTIONS, "scale");
157
164
  validatePaletteRange(options);
158
- const id = validateUserId(options.id ?? "color", "Scale id");
165
+ const id = validateUserId(options.id ?? channel, "Scale id");
159
166
  const existing = findSemanticScale(program, id);
167
+ const previous = existing?.type === "ordinal" ? existing : undefined;
160
168
  const range = options.palette === undefined
161
169
  ? options.range
162
170
  : { palette: options.palette };
163
171
  return withScaleUnknown({
164
172
  id,
165
- type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
166
- domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
167
- range: validateColorRange(range ?? existing?.range ?? "auto")
168
- }, { ...existing, ...options }, "color");
173
+ type: validateOrdinalScaleType(options.type ?? previous?.type ?? "ordinal"),
174
+ domain: validateOrdinalDomain(options.domain ?? previous?.domain ?? "auto"),
175
+ range: validateColorRange(range ?? previous?.range ?? "auto")
176
+ }, { ...previous, ...options }, channel);
169
177
  }
170
178
 
171
179
  function continuousDomain(value, fieldType) {
@@ -186,14 +194,16 @@ function continuousDomain(value, fieldType) {
186
194
  export function resolveSequentialColorScaleDefinition(
187
195
  program,
188
196
  fieldType,
189
- options
197
+ options,
198
+ channel = "color"
190
199
  ) {
191
200
  optionsObject(options);
192
201
  validateKeys(options, SEQUENTIAL_COLOR_OPTIONS, "scale");
193
202
  validatePaletteRange(options);
194
- const id = validateUserId(options.id ?? "color", "Scale id");
203
+ const id = validateUserId(options.id ?? channel, "Scale id");
195
204
  const existing = findSemanticScale(program, id);
196
- const type = options.type ?? existing?.type ?? "sequential";
205
+ const previous = existing?.type === "sequential" ? existing : undefined;
206
+ const type = options.type ?? previous?.type ?? "sequential";
197
207
  if (type !== "sequential") {
198
208
  throw new Error(`Unsupported continuous color scale type "${type}".`);
199
209
  }
@@ -204,16 +214,16 @@ export function resolveSequentialColorScaleDefinition(
204
214
  const scale = {
205
215
  id,
206
216
  type,
207
- domain: continuousDomain(options.domain ?? existing?.domain ?? "auto", fieldType),
217
+ domain: continuousDomain(options.domain ?? previous?.domain ?? "auto", fieldType),
208
218
  range: validateSequentialColorRange(
209
- requestedRange ?? existing?.range ?? { palette: "viridis" }
219
+ requestedRange ?? previous?.range ?? { palette: "viridis" }
210
220
  ),
211
221
  interpolate: validateContinuousColorInterpolation(
212
- options.interpolate ?? existing?.interpolate ?? "rgb"
222
+ options.interpolate ?? previous?.interpolate ?? "rgb"
213
223
  )
214
224
  };
215
225
  const midpoint = validateSequentialMidpoint(
216
- Object.hasOwn(options, "midpoint") ? options.midpoint : existing?.midpoint, type, scale.domain
226
+ Object.hasOwn(options, "midpoint") ? options.midpoint : previous?.midpoint, type, scale.domain
217
227
  );
218
228
  if (midpoint !== undefined) {
219
229
  if (fieldType !== "quantitative") {
@@ -222,30 +232,34 @@ export function resolveSequentialColorScaleDefinition(
222
232
  scale.midpoint = midpoint;
223
233
  }
224
234
  return withScaleUnknown(
225
- assignOptions(scale, options, existing, CLAMP_REVERSE),
226
- { ...existing, ...options },
227
- "color"
235
+ assignOptions(scale, options, previous, CLAMP_REVERSE),
236
+ { ...previous, ...options },
237
+ channel
228
238
  );
229
239
  }
230
240
 
231
241
  export function resolveQuantitativeColorScaleDefinition(
232
242
  program,
233
243
  fieldType,
234
- options
244
+ options,
245
+ channel = "color"
235
246
  ) {
236
247
  optionsObject(options);
237
- const type = options.type ?? findSemanticScale(
238
- program,
239
- options.id ?? "color"
240
- )?.type ?? "sequential";
241
- const existing = findSemanticScale(program, options.id ?? "color");
242
- if (existing !== undefined && existing.type !== type) {
248
+ const existing = findSemanticScale(program, options.id ?? channel);
249
+ const existingContinuous = existing !== undefined && (
250
+ existing.type === "sequential" ||
251
+ ["quantize", "quantile", "threshold"].includes(existing.type)
252
+ );
253
+ const type = options.type ?? (
254
+ existingContinuous ? existing.type : "sequential"
255
+ );
256
+ if (existingContinuous && existing.type !== type) {
243
257
  validateKeys(options, type === "sequential" ? SEQUENTIAL_COLOR_OPTIONS : [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
244
258
  if (fieldType !== "quantitative") throw new Error("Color scale type transition requires quantitative color.");
245
- return { id: existing.id, ...prepareScaleEdit(program, existing, "color", [], options) };
259
+ return { id: existing.id, ...prepareScaleEdit(program, existing, channel, [], options) };
246
260
  }
247
261
  if (type === "sequential") {
248
- return resolveSequentialColorScaleDefinition(program, fieldType, options);
262
+ return resolveSequentialColorScaleDefinition(program, fieldType, options, channel);
249
263
  }
250
264
  validateKeys(options, [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
251
265
  if (fieldType !== "quantitative") {
@@ -254,7 +268,7 @@ export function resolveQuantitativeColorScaleDefinition(
254
268
  validateScaleTypeForRole(type, SCALE_ROLES.discretizedColor);
255
269
  validatePaletteRange(options);
256
270
  validateBooleanOptions(options, CLAMP_REVERSE, type);
257
- const id = validateUserId(options.id ?? "color", "Scale id");
271
+ const id = validateUserId(options.id ?? channel, "Scale id");
258
272
  const previous = existing?.type === type ? existing : undefined;
259
273
  const requestedRange = options.palette === undefined
260
274
  ? options.range
@@ -273,7 +287,7 @@ export function resolveQuantitativeColorScaleDefinition(
273
287
  return withScaleUnknown(
274
288
  assignOptions(scale, options, previous, CLAMP_REVERSE),
275
289
  { ...existing, ...options },
276
- "color"
290
+ channel
277
291
  );
278
292
  }
279
293
 
@@ -298,21 +312,23 @@ export function resolveStrokeDashScaleDefinition(program, options) {
298
312
 
299
313
  export function resolveAppearanceScaleDefinition(program, channel, options) {
300
314
  optionsObject(options);
315
+ if (channel === "size") {
316
+ validateKeys(options, SIZE_OPTIONS, "scale");
317
+ const id = validateUserId(options.id ?? channel, "Scale id");
318
+ const existing = findSemanticScale(program, id);
319
+ return {
320
+ id,
321
+ ...normalizeSizeScaleDefinition({ previous: existing, patch: options })
322
+ };
323
+ }
301
324
  validateKeys(options, UNKNOWN_OPTIONS, "scale");
302
325
  const id = validateUserId(options.id ?? channel, "Scale id");
303
326
  const existing = findSemanticScale(program, id);
304
- const shape = channel === "shape";
305
327
  return withScaleUnknown({
306
328
  id,
307
- type: shape
308
- ? validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal")
309
- : validateLinearScaleType(options.type ?? existing?.type ?? "linear"),
310
- domain: shape
311
- ? validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto")
312
- : validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
313
- range: shape
314
- ? validateShapeRange(options.range ?? existing?.range ?? "auto")
315
- : validateSizeRange(options.range ?? existing?.range ?? "auto")
329
+ type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
330
+ domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
331
+ range: validateShapeRange(options.range ?? existing?.range ?? "auto")
316
332
  }, { ...existing, ...options }, channel);
317
333
  }
318
334
 
@@ -365,7 +381,45 @@ export function resolveStrokeWidthScaleDefinition(program, options) {
365
381
  }
366
382
 
367
383
  export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
368
- return resolveOrdinalScaleDefinition(
369
- program, options, channel, validateScaleRange
384
+ optionsObject(options);
385
+ validateKeys(options, [
386
+ "id", "type", "domain", "range", "reverse",
387
+ "padding", "paddingInner", "paddingOuter", "align"
388
+ ], "scale");
389
+ const id = validateUserId(options.id ?? channel, "Scale id");
390
+ const existing = findSemanticScale(program, id);
391
+ const type = options.type ?? existing?.type ?? "ordinal";
392
+ if (type !== "ordinal") {
393
+ throw new Error(`Scale type "${type}" is not valid for ${channel}.`);
394
+ }
395
+ if (Object.hasOwn(options, "range") && options.range !== "auto") {
396
+ throw new Error(
397
+ `${channel} scale range is derived from its parent categorical slot.`
398
+ );
399
+ }
400
+ const consumers = findScaleConsumers(program, id).filter(
401
+ consumer => consumer.channel === channel
370
402
  );
403
+ const currentPolicy = resolveRequestedOffsetPolicy({
404
+ scale: existing,
405
+ consumers,
406
+ markConfigs: program.markConfigs,
407
+ id,
408
+ channel
409
+ });
410
+ const policy = normalizeOffsetScalePolicy(options, currentPolicy, channel);
411
+ const patch = Object.fromEntries(Object.entries(options).filter(
412
+ ([property]) => !["id", "padding"].includes(property)
413
+ ));
414
+ return {
415
+ id,
416
+ ...normalizeScaleDefinition({
417
+ type,
418
+ previous: existing,
419
+ patch: { ...patch, ...policy },
420
+ allowOrdinalBandParameters: true,
421
+ validateDomain: (_scaleType, value) => validateOrdinalDomain(value),
422
+ validateRange: (_scaleType, value) => validateScaleRange(value)
423
+ })
424
+ };
371
425
  }
@@ -1,12 +1,18 @@
1
1
  import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
2
- import { planColorLegendTransition, applyColorLegendTransition } from "../guides/legends/transition.js";
2
+ import {
3
+ applyColorLegendTransition,
4
+ planColorLegendTransitions
5
+ } from "../guides/legends/transition.js";
3
6
  import { action } from "../../core/action.js";
4
7
  import { validateUserId } from "../../core/identifiers.js";
5
8
  import { validateOptionObject } from "../../core/validation.js";
6
9
  import { getMarkMaterializationStep, getSourceDependentMarkSteps } from "../../materialization/marks/index.js";
7
10
  import {
8
- applyMaterializationPlan
11
+ applyMaterializationPlan,
12
+ planCoordinateRematerialization
9
13
  } from "../../materialization/dependencies.js";
14
+ import { findDataAspectCoordinatesForScale } from
15
+ "../../materialization/coordinateBounds.js";
10
16
  import {
11
17
  findSemanticScale,
12
18
  requireSemanticScale
@@ -16,6 +22,8 @@ import {
16
22
  prepareScaleEdit,
17
23
  resolveScaleConsumerChannel
18
24
  } from "./editPolicy.js";
25
+ import { hasExactLegendSamplingForScale } from
26
+ "../guides/legends/sampling.js";
19
27
 
20
28
  const OPTIONS = Object.freeze([
21
29
  "id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
@@ -71,9 +79,21 @@ function planMarkRematerialization(program, consumers) {
71
79
  ];
72
80
  }
73
81
 
74
- function applyScaleEdit(program, { id, scale, consumers, definition, legendTransition }) {
75
- let next = legendTransition === undefined ? program
76
- : program.removeLegend({ target: legendTransition.args.target, channels: ["color"] });
82
+ function applyScaleEdit(program, {
83
+ id,
84
+ scale,
85
+ channel,
86
+ consumers,
87
+ definition,
88
+ legendTransitions
89
+ }) {
90
+ let next = program;
91
+ for (const transition of legendTransitions) {
92
+ next = next.removeLegend({
93
+ target: transition.args.target,
94
+ channels: [transition.channel]
95
+ });
96
+ }
77
97
  for (const property of EDITABLE) {
78
98
  if (
79
99
  Object.hasOwn(scale, property) &&
@@ -97,12 +117,35 @@ function applyScaleEdit(program, { id, scale, consumers, definition, legendTrans
97
117
  }
98
118
  if (consumers.length === 0) return next;
99
119
 
100
- next = next.rematerializeScale({ id });
101
- next = applyMaterializationPlan(next, planMarkRematerialization(next, consumers));
102
- return legendTransition === undefined ? next : applyColorLegendTransition(next, legendTransition);
120
+ const aspectCoordinates = findDataAspectCoordinatesForScale(next, id);
121
+ if (aspectCoordinates.length > 0) {
122
+ for (const coordinate of aspectCoordinates) {
123
+ next = applyMaterializationPlan(
124
+ next,
125
+ planCoordinateRematerialization(next, coordinate.id)
126
+ );
127
+ }
128
+ } else {
129
+ next = next.rematerializeScale({ id });
130
+ next = applyMaterializationPlan(
131
+ next,
132
+ planMarkRematerialization(next, consumers)
133
+ );
134
+ }
135
+ if (["xOffset", "yOffset"].includes(channel)) {
136
+ for (const consumer of consumers) {
137
+ next = next._withoutMaterializationConfig([
138
+ "marks", consumer.layer.id, channel
139
+ ]);
140
+ }
141
+ }
142
+ for (const transition of legendTransitions) {
143
+ next = applyColorLegendTransition(next, transition);
144
+ }
145
+ return next;
103
146
  }
104
147
 
105
- export const editScale = action(
148
+ export const editScale = /* @__PURE__ */ action(
106
149
  {
107
150
  op: "editScale",
108
151
  description: "Edit an existing scale and rematerialize its consumers."
@@ -118,10 +161,22 @@ export const editScale = action(
118
161
  const channel = resolveScaleConsumerChannel(consumers, id);
119
162
  const definition = prepareScaleEdit(this, scale, channel, consumers, args);
120
163
 
121
- const legendTransition = planColorLegendTransition(this, scale, definition.type);
122
- const proposal = { id, scale, consumers, definition, legendTransition };
164
+ const legendTransitions = planColorLegendTransitions(this, scale, definition.type);
165
+ const proposal = {
166
+ id,
167
+ scale,
168
+ channel,
169
+ consumers,
170
+ definition,
171
+ legendTransitions
172
+ };
123
173
  // Preflight every dependent mark and guide on a discarded immutable branch.
124
- if (scale.type !== definition.type) applyScaleEdit(this, proposal);
174
+ if (
175
+ scale.type !== definition.type ||
176
+ hasExactLegendSamplingForScale(this, id)
177
+ ) {
178
+ applyScaleEdit(this, proposal);
179
+ }
125
180
  return applyScaleEdit(this, proposal);
126
181
  })
127
182
  );
@@ -4,6 +4,7 @@ import {
4
4
  isDiscretizedColorScaleType,
5
5
  isDiscretePositionScaleType,
6
6
  isTransformedScaleType,
7
+ normalizeSizeScaleDefinition,
7
8
  normalizeScaleDefinition,
8
9
  SCALE_ROLES,
9
10
  validateColorRange,
@@ -26,11 +27,20 @@ import {
26
27
  validateRadialRange,
27
28
  validateThetaRange
28
29
  } from "../../grammar/polar.js";
29
- import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
30
+ import {
31
+ normalizePositionScaleChannel,
32
+ OFFSET_POSITION_CHANNELS
33
+ } from "../../core/vocabulary.js";
34
+ import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
35
+ import { resolveRequestedOffsetPolicy } from
36
+ "../../materialization/scales/policies/offset.js";
30
37
 
31
38
  export function resolveScaleConsumerChannel(consumers, id) {
32
39
  const channels = new Set(
33
- consumers.map(consumer => normalizePositionScaleChannel(consumer.channel))
40
+ consumers.map(consumer => {
41
+ const channel = normalizePositionScaleChannel(consumer.channel);
42
+ return channel === "stroke" ? "color" : channel;
43
+ })
34
44
  );
35
45
  if (channels.size > 1) {
36
46
  throw new Error(`Scale "${id}" cannot be shared across channels.`);
@@ -40,6 +50,7 @@ export function resolveScaleConsumerChannel(consumers, id) {
40
50
 
41
51
  function validateRangeForChannel(scale, channel, value) {
42
52
  if (value === "auto") return value;
53
+ if (channel === "size") return validateSizeRange(value);
43
54
  if (channel === "theta") return validateThetaRange(value);
44
55
  if (channel === "radius") return validateRadialRange(value);
45
56
  if (scale.type === "sequential") {
@@ -49,7 +60,7 @@ function validateRangeForChannel(scale, channel, value) {
49
60
  return validateDiscretizedColorRange(value);
50
61
  }
51
62
  if (scale.type !== "ordinal") return validateScaleRange(value);
52
- if (channel === "color") return validateColorRange(value);
63
+ if (["color", "stroke"].includes(channel)) return validateColorRange(value);
53
64
  if (channel === "shape") return validateShapeRange(value);
54
65
  if (channel === "strokeDash") return validateStrokeDashRange(value);
55
66
  if (channel === "size") return validateSizeRange(value);
@@ -66,7 +77,23 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
66
77
  throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
67
78
  }
68
79
  for (const consumer of consumers) {
69
- validateContinuousColorConsumer(consumer.layer, consumer.encoding, { type: nextType });
80
+ validateContinuousColorConsumer(
81
+ consumer.layer,
82
+ consumer.encoding,
83
+ { type: nextType },
84
+ { channel: consumer.channel }
85
+ );
86
+ }
87
+ return;
88
+ }
89
+ if (nextType === "ordinal" && channel === "color") {
90
+ if (consumers.some(consumer =>
91
+ !["color", "stroke"].includes(consumer.channel) ||
92
+ !["nominal", "ordinal"].includes(consumer.encoding.fieldType)
93
+ )) {
94
+ throw new Error(
95
+ `Scale "${scale.id}" has a consumer incompatible with type "ordinal".`
96
+ );
70
97
  }
71
98
  return;
72
99
  }
@@ -127,23 +154,65 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
127
154
  }
128
155
  }
129
156
 
130
- function normalizeDefinition(scale, channel, consumers, patch) {
157
+ function normalizeDefinition(program, scale, channel, consumers, patch) {
131
158
  if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
132
159
  patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
133
160
  throw new Error("Remove measured radius encodings before clearing their scale mapping.");
134
161
  }
162
+ if (channel === "size") {
163
+ if (consumers.some(consumer =>
164
+ consumer.layer.mark?.type !== "point" ||
165
+ consumer.encoding.fieldType !== "quantitative"
166
+ )) {
167
+ throw new Error(
168
+ `Scale "${scale.id}" has a consumer incompatible with size mapping.`
169
+ );
170
+ }
171
+ return normalizeSizeScaleDefinition({ previous: scale, patch });
172
+ }
135
173
  const type = patch.type ?? scale.type;
136
174
  validateTypeTransition(scale, type, channel, consumers);
175
+ const offset = OFFSET_POSITION_CHANNELS.includes(channel);
176
+ if (offset && Object.hasOwn(patch, "range")) {
177
+ throw new Error(
178
+ `${channel} scale range is derived from its parent categorical slot.`
179
+ );
180
+ }
181
+ if (offset && Object.hasOwn(patch, "unknown")) {
182
+ throw new Error(`${channel} scale unknown is not supported for offset positions.`);
183
+ }
137
184
  if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
138
185
  (["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
139
186
  throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
140
187
  }
188
+ const offsetPolicy = offset
189
+ ? normalizeOffsetScalePolicy(
190
+ patch,
191
+ resolveRequestedOffsetPolicy({
192
+ scale,
193
+ consumers,
194
+ markConfigs: program.markConfigs,
195
+ id: scale.id,
196
+ channel
197
+ }),
198
+ channel
199
+ )
200
+ : undefined;
201
+ const normalizedPatch = offset
202
+ ? {
203
+ ...Object.fromEntries(Object.entries(patch).filter(
204
+ ([property]) => property !== "padding"
205
+ )),
206
+ ...offsetPolicy
207
+ }
208
+ : patch;
141
209
  const definition = normalizeScaleDefinition({
142
210
  type,
143
211
  previous: scale,
144
- patch,
212
+ patch: normalizedPatch,
145
213
  retainCoreOnTypeChange: true,
146
214
  retainCompatibleOnTypeChange: true,
215
+ allowOrdinalBandParameters: offset,
147
216
  validateDomain: (scaleType, value) =>
148
217
  isDiscretizedColorScaleType(scaleType)
149
218
  ? validateDiscretizedColorDomain(scaleType, value)
@@ -162,13 +231,14 @@ function normalizeDefinition(scale, channel, consumers, patch) {
162
231
  }
163
232
  validateSequentialMidpoint(definition.midpoint, type, definition.domain);
164
233
  if (definition.midpoint !== undefined && consumers.some(
165
- consumer => consumer.channel !== "color" || consumer.encoding.fieldType !== "quantitative"
234
+ consumer => !["color", "stroke"].includes(consumer.channel) ||
235
+ consumer.encoding.fieldType !== "quantitative"
166
236
  )) {
167
237
  throw new Error("Scale midpoint requires quantitative color consumers.");
168
238
  }
169
239
  const typeChanged = type !== scale.type;
170
- const unknown = Object.hasOwn(patch, "unknown")
171
- ? patch.unknown
240
+ const unknown = Object.hasOwn(normalizedPatch, "unknown")
241
+ ? normalizedPatch.unknown
172
242
  : typeChanged ? undefined : scale.unknown;
173
243
  if (unknown !== undefined) {
174
244
  if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
@@ -188,7 +258,7 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
188
258
  }
189
259
  if (
190
260
  hasPalette &&
191
- channel !== "color" &&
261
+ !["color", "stroke"].includes(channel) &&
192
262
  scale.type !== "sequential" &&
193
263
  !isDiscretizedColorScaleType(scale.type)
194
264
  ) {
@@ -200,5 +270,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
200
270
  ...Object.entries(args).filter(([key]) => key !== "palette"),
201
271
  ["range", { palette: args.palette }]
202
272
  ]);
203
- return normalizeDefinition(scale, channel, consumers, patch);
273
+ return normalizeDefinition(program, scale, channel, consumers, patch);
204
274
  }
@@ -2,9 +2,15 @@ import { createScale } from "./create.js";
2
2
  import { editScale } from "./edit.js";
3
3
  import { rematerializeScale } from "./materialize.js";
4
4
  import { setQuantitativeColorScale } from "./quantitativeColor.js";
5
+ import { registerChannelScaleActions } from "./channels.js";
6
+ import { editParallelScale } from "./parallel.js";
7
+ import { registerOffsetScaleActions } from "./offset.js";
5
8
 
6
9
  export function registerScaleActions(ProgramClass) {
7
10
  ProgramClass.prototype.editScale = editScale;
11
+ ProgramClass.prototype.editParallelScale = editParallelScale;
12
+ registerOffsetScaleActions(ProgramClass);
13
+ registerChannelScaleActions(ProgramClass);
8
14
  registerBasicScaleActions(ProgramClass);
9
15
  }
10
16