ggaction 0.0.12 → 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 (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  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 +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -1,8 +1,10 @@
1
+ import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
1
2
  import {
2
3
  hasOrdinalDomain,
3
4
  isDiscretizedColorScaleType,
4
5
  isDiscretePositionScaleType,
5
6
  isTransformedScaleType,
7
+ normalizeSizeScaleDefinition,
6
8
  normalizeScaleDefinition,
7
9
  SCALE_ROLES,
8
10
  validateColorRange,
@@ -14,6 +16,7 @@ import {
14
16
  validateScaleType,
15
17
  validateScaleTypeForRole,
16
18
  validateScaleUnknown,
19
+ validateSequentialMidpoint,
17
20
  validateSequentialColorRange,
18
21
  validateShapeRange,
19
22
  validateSizeRange,
@@ -24,11 +27,20 @@ import {
24
27
  validateRadialRange,
25
28
  validateThetaRange
26
29
  } from "../../grammar/polar.js";
27
- 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";
28
37
 
29
38
  export function resolveScaleConsumerChannel(consumers, id) {
30
39
  const channels = new Set(
31
- consumers.map(consumer => normalizePositionScaleChannel(consumer.channel))
40
+ consumers.map(consumer => {
41
+ const channel = normalizePositionScaleChannel(consumer.channel);
42
+ return channel === "stroke" ? "color" : channel;
43
+ })
32
44
  );
33
45
  if (channels.size > 1) {
34
46
  throw new Error(`Scale "${id}" cannot be shared across channels.`);
@@ -38,6 +50,7 @@ export function resolveScaleConsumerChannel(consumers, id) {
38
50
 
39
51
  function validateRangeForChannel(scale, channel, value) {
40
52
  if (value === "auto") return value;
53
+ if (channel === "size") return validateSizeRange(value);
41
54
  if (channel === "theta") return validateThetaRange(value);
42
55
  if (channel === "radius") return validateRadialRange(value);
43
56
  if (scale.type === "sequential") {
@@ -47,7 +60,7 @@ function validateRangeForChannel(scale, channel, value) {
47
60
  return validateDiscretizedColorRange(value);
48
61
  }
49
62
  if (scale.type !== "ordinal") return validateScaleRange(value);
50
- if (channel === "color") return validateColorRange(value);
63
+ if (["color", "stroke"].includes(channel)) return validateColorRange(value);
51
64
  if (channel === "shape") return validateShapeRange(value);
52
65
  if (channel === "strokeDash") return validateStrokeDashRange(value);
53
66
  if (channel === "size") return validateSizeRange(value);
@@ -60,17 +73,26 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
60
73
  validateScaleType(nextType);
61
74
  if (consumers.length === 0) return;
62
75
  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))
76
+ if (channel !== "color") {
77
+ throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
78
+ }
79
+ for (const consumer of consumers) {
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)
71
93
  )) {
72
94
  throw new Error(
73
- `Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
95
+ `Scale "${scale.id}" has a consumer incompatible with type "ordinal".`
74
96
  );
75
97
  }
76
98
  return;
@@ -132,33 +154,65 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
132
154
  }
133
155
  }
134
156
 
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
- );
157
+ function normalizeDefinition(program, scale, channel, consumers, patch) {
158
+ if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
159
+ patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
160
+ throw new Error("Remove measured radius encodings before clearing their scale mapping.");
142
161
  }
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
- );
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 });
150
172
  }
151
- }
152
-
153
- function normalizeDefinition(scale, channel, consumers, patch) {
154
173
  const type = patch.type ?? scale.type;
155
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
+ }
184
+ if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
185
+ (["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
186
+ throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
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;
156
209
  const definition = normalizeScaleDefinition({
157
210
  type,
158
211
  previous: scale,
159
- patch,
212
+ patch: normalizedPatch,
160
213
  retainCoreOnTypeChange: true,
161
214
  retainCompatibleOnTypeChange: true,
215
+ allowOrdinalBandParameters: offset,
162
216
  validateDomain: (scaleType, value) =>
163
217
  isDiscretizedColorScaleType(scaleType)
164
218
  ? validateDiscretizedColorDomain(scaleType, value)
@@ -175,9 +229,16 @@ function normalizeDefinition(scale, channel, consumers, patch) {
175
229
  ) {
176
230
  throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
177
231
  }
232
+ validateSequentialMidpoint(definition.midpoint, type, definition.domain);
233
+ if (definition.midpoint !== undefined && consumers.some(
234
+ consumer => !["color", "stroke"].includes(consumer.channel) ||
235
+ consumer.encoding.fieldType !== "quantitative"
236
+ )) {
237
+ throw new Error("Scale midpoint requires quantitative color consumers.");
238
+ }
178
239
  const typeChanged = type !== scale.type;
179
- const unknown = Object.hasOwn(patch, "unknown")
180
- ? patch.unknown
240
+ const unknown = Object.hasOwn(normalizedPatch, "unknown")
241
+ ? normalizedPatch.unknown
181
242
  : typeChanged ? undefined : scale.unknown;
182
243
  if (unknown !== undefined) {
183
244
  if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
@@ -197,7 +258,7 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
197
258
  }
198
259
  if (
199
260
  hasPalette &&
200
- channel !== "color" &&
261
+ !["color", "stroke"].includes(channel) &&
201
262
  scale.type !== "sequential" &&
202
263
  !isDiscretizedColorScaleType(scale.type)
203
264
  ) {
@@ -209,6 +270,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
209
270
  ...Object.entries(args).filter(([key]) => key !== "palette"),
210
271
  ["range", { palette: args.palette }]
211
272
  ]);
212
- validateLegendTypeTransition(program, scale, args.type ?? scale.type);
213
- return normalizeDefinition(scale, channel, consumers, patch);
273
+ return normalizeDefinition(program, scale, channel, consumers, patch);
214
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
 
@@ -1,8 +1,9 @@
1
+ import { isPendingMeasuredRadiusConsumer } from "../../materialization/scales/policies/arc.js";
2
+ import { findScale, findScaleConsumers } from "./consumers/index.js";
3
+ import { resolveScalePreview, validatePendingMeasuredScale } from "./preview.js";
1
4
  import { action } from "../../core/action.js";
2
5
  import { validateUserId } from "../../core/identifiers.js";
3
- import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
4
6
  import { validateKeys } from "../../core/validation.js";
5
- import { resolveGraphicBounds } from "../../layout/canvas.js";
6
7
  import {
7
8
  applyMaterializationPlan,
8
9
  planScaleGuideRematerialization
@@ -14,16 +15,6 @@ import {
14
15
  } from "../../materialization/marks/index.js";
15
16
  import { mapScaleConsumerValues } from
16
17
  "../../materialization/scales/map.js";
17
- import { resolveScaleMaterialization } from
18
- "../../materialization/scales/resolve.js";
19
- import {
20
- findScale,
21
- findScaleConsumers,
22
- resolveConsumerCategoryOrder,
23
- resolveConsumerValues,
24
- resolveSeriesLayoutScaleValues
25
- } from "./consumers/index.js";
26
-
27
18
  const OPTIONS = Object.freeze(["id", "guides", "marks"]);
28
19
 
29
20
  function validateOptions(args) {
@@ -36,16 +27,6 @@ function validateOptions(args) {
36
27
  }
37
28
  }
38
29
 
39
- function resolveScaleChannel(id, consumers) {
40
- const channels = new Set(
41
- consumers.map(consumer => normalizePositionScaleChannel(consumer.channel))
42
- );
43
- if (channels.size !== 1) {
44
- throw new Error(`Scale "${id}" cannot be shared across channels.`);
45
- }
46
- return normalizePositionScaleChannel(consumers[0].channel);
47
- }
48
-
49
30
  export const rematerializeScale = action(
50
31
  {
51
32
  op: "rematerializeScale",
@@ -54,31 +35,14 @@ export const rematerializeScale = action(
54
35
  function (args = {}) {
55
36
  validateOptions(args);
56
37
  const id = validateUserId(args.id, "Scale id");
57
- const scale = findScale(this, id);
38
+ const semanticScale = findScale(this, id);
58
39
  const consumers = findScaleConsumers(this, id);
59
- if (consumers.length === 0) {
60
- throw new Error(`Scale "${id}" has no supported consumers.`);
40
+ if (semanticScale.radialMapping !== undefined && consumers.length > 0 &&
41
+ consumers.every(isPendingMeasuredRadiusConsumer)) {
42
+ validatePendingMeasuredScale(this, semanticScale, consumers);
43
+ return this._withoutResolvedScale(id);
61
44
  }
62
- const channel = resolveScaleChannel(id, consumers);
63
- const valuesByConsumer = consumers.map(consumer => ({
64
- consumer,
65
- values: resolveConsumerValues(this, consumer),
66
- categoryOrder: resolveConsumerCategoryOrder(this, consumer),
67
- seriesLayout: resolveSeriesLayoutScaleValues(this, consumer)
68
- }));
69
- const resolvedScale = resolveScaleMaterialization({
70
- id,
71
- scale,
72
- channel,
73
- consumers,
74
- valuesByConsumer,
75
- bounds: ["color", "strokeDash", "strokeWidth", "shape", "size", "opacity", "xOffset", "yOffset"]
76
- .includes(channel)
77
- ? undefined
78
- : resolveGraphicBounds(this),
79
- resolvedScales: this.resolvedScales,
80
- markConfigs: this.markConfigs
81
- });
45
+ const { channel, valuesByConsumer, resolvedScale } = resolveScalePreview(this, id);
82
46
  let next = this._withResolvedScale(id, resolvedScale);
83
47
 
84
48
  for (const { consumer, values } of valuesByConsumer) {
@@ -88,13 +52,14 @@ export const rematerializeScale = action(
88
52
  ) {
89
53
  continue;
90
54
  }
55
+ const consumerChannel = consumer.channel;
91
56
  const materializationMode = getScaleConsumerMaterializationMode(
92
57
  consumer.layer,
93
- channel
58
+ consumerChannel
94
59
  );
95
60
  if (materializationMode === "rematerialize") {
96
61
  if (args.marks !== false) {
97
- const step = getMarkRematerializationStep(consumer.layer);
62
+ const step = getMarkRematerializationStep(this, consumer.layer);
98
63
  if (step !== undefined) next = next[step.op](step.args);
99
64
  }
100
65
  continue;
@@ -107,8 +72,8 @@ export const rematerializeScale = action(
107
72
  }
108
73
  next = next.editGraphics({
109
74
  target: consumer.layer.id,
110
- property: channel === "color" ? "fill" : channel,
111
- value: mapScaleConsumerValues(values, resolvedScale, channel)
75
+ property: consumerChannel === "color" ? "fill" : consumerChannel,
76
+ value: mapScaleConsumerValues(values, resolvedScale, consumerChannel)
112
77
  });
113
78
  }
114
79
 
@@ -0,0 +1,48 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { requireLayer } from "../../selectors/layers.js";
5
+ import { requireSemanticScale } from "../../selectors/scales.js";
6
+
7
+ const EDITABLE = Object.freeze([
8
+ "domain", "reverse", "padding", "paddingInner", "paddingOuter", "align"
9
+ ]);
10
+
11
+ function createOffsetScaleEditor(channel) {
12
+ const operation = channel === "xOffset"
13
+ ? "editXOffsetScale"
14
+ : "editYOffsetScale";
15
+ return action(
16
+ {
17
+ op: operation,
18
+ description: `Edit the nested ${channel} scale for one mark.`
19
+ },
20
+ function (args = {}) {
21
+ validateOptionObject(args, ["target", ...EDITABLE], operation);
22
+ const target = validateUserId(args.target, `${operation} target`);
23
+ if (!EDITABLE.some(property => Object.hasOwn(args, property))) {
24
+ throw new Error(`${operation} requires at least one editable property.`);
25
+ }
26
+ const layer = requireLayer(this, target, `${operation} target "${target}"`);
27
+ const scaleId = layer.encoding?.[channel]?.scale;
28
+ if (typeof scaleId !== "string") {
29
+ throw new Error(
30
+ `${operation} target "${target}" has no ${channel} scale.`
31
+ );
32
+ }
33
+ requireSemanticScale(this, scaleId);
34
+ const patch = Object.fromEntries(Object.entries(args).filter(
35
+ ([property]) => property !== "target"
36
+ ));
37
+ return this.editScale({ id: scaleId, ...patch });
38
+ }
39
+ );
40
+ }
41
+
42
+ export const editXOffsetScale = createOffsetScaleEditor("xOffset");
43
+ export const editYOffsetScale = createOffsetScaleEditor("yOffset");
44
+
45
+ export function registerOffsetScaleActions(ProgramClass) {
46
+ ProgramClass.prototype.editXOffsetScale = editXOffsetScale;
47
+ ProgramClass.prototype.editYOffsetScale = editYOffsetScale;
48
+ }
@@ -0,0 +1,71 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { findCoordinate } from "../../selectors/coordinates.js";
5
+ import { requireLayer } from "../../selectors/layers.js";
6
+ import { requireSemanticScale } from "../../selectors/scales.js";
7
+
8
+ const QUANTITATIVE_OPTIONS = Object.freeze([
9
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
10
+ "base", "exponent", "constant", "unknown"
11
+ ]);
12
+ const ORDINAL_OPTIONS = Object.freeze([
13
+ "type", "domain", "range", "reverse", "padding", "align", "unknown"
14
+ ]);
15
+ const ALL_OPTIONS = Object.freeze([
16
+ "target", "dimension",
17
+ ...new Set([...QUANTITATIVE_OPTIONS, ...ORDINAL_OPTIONS])
18
+ ]);
19
+
20
+ function resolveParallelDimension(program, args) {
21
+ validateOptionObject(args, ALL_OPTIONS, "editParallelScale");
22
+ const target = validateUserId(args.target, "Parallel scale target");
23
+ if (typeof args.dimension !== "string" || args.dimension.length === 0) {
24
+ throw new TypeError("Parallel scale dimension must be a non-empty field string.");
25
+ }
26
+ const layer = requireLayer(program, target, `Parallel scale target "${target}"`);
27
+ if (
28
+ findCoordinate(program, layer.coordinate)?.type !== "parallel" ||
29
+ !Array.isArray(layer.encoding?.parallel?.dimensions)
30
+ ) {
31
+ throw new Error(`Parallel scale target "${target}" is not a Parallel layer.`);
32
+ }
33
+ const matches = layer.encoding.parallel.dimensions.filter(
34
+ dimension => dimension.field === args.dimension
35
+ );
36
+ if (matches.length !== 1) {
37
+ throw new Error(
38
+ matches.length === 0
39
+ ? `Parallel scale target "${target}" has no dimension field "${args.dimension}".`
40
+ : `Parallel scale dimension field "${args.dimension}" is ambiguous on target "${target}".`
41
+ );
42
+ }
43
+ const dimension = matches[0];
44
+ requireSemanticScale(program, dimension.scale);
45
+ return dimension;
46
+ }
47
+
48
+ export const editParallelScale = action(
49
+ {
50
+ op: "editParallelScale",
51
+ description: "Edit a Parallel-coordinate dimension scale by field identity."
52
+ },
53
+ function (args = {}) {
54
+ const dimension = resolveParallelDimension(this, args);
55
+ const editable = dimension.fieldType === "quantitative"
56
+ ? QUANTITATIVE_OPTIONS
57
+ : ORDINAL_OPTIONS;
58
+ validateOptionObject(
59
+ args,
60
+ ["target", "dimension", ...editable],
61
+ "editParallelScale"
62
+ );
63
+ if (!editable.some(property => Object.hasOwn(args, property))) {
64
+ throw new Error("editParallelScale requires at least one editable property.");
65
+ }
66
+ const patch = Object.fromEntries(Object.entries(args).filter(
67
+ ([property]) => !["target", "dimension"].includes(property)
68
+ ));
69
+ return this.editScale({ id: dimension.scale, ...patch });
70
+ }
71
+ );
@@ -1,7 +1,7 @@
1
1
  const SCALE_EDIT_PROPERTIES = Object.freeze([
2
2
  "type", "domain", "range", "nice", "zero", "clamp", "reverse",
3
3
  "base", "exponent", "constant", "paddingInner", "paddingOuter",
4
- "padding", "align", "interpolate", "unknown"
4
+ "padding", "align", "interpolate", "midpoint", "unknown"
5
5
  ]);
6
6
 
7
7
  function sameValue(left, right) {
@@ -0,0 +1,140 @@
1
+ import { resolveScaleRange } from "../../grammar/scales/index.js";
2
+ import { resolvePolarFrame } from "../../grammar/polar.js";
3
+ import { resolveArcAutoPositionRange, validateMeasuredRadiusConsumers } from "../../materialization/scales/policies/arc.js";
4
+ import { findSemanticScale } from "../../selectors/scales.js";
5
+ import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
6
+ import { resolveGraphicBounds } from "../../layout/canvas.js";
7
+ import {
8
+ resolveDataAspectScalePair,
9
+ resolveScaleConsumerBounds
10
+ } from "../../materialization/coordinateBounds.js";
11
+ import { requireCoordinate } from "../../selectors/coordinates.js";
12
+ import { resolveScaleMaterialization } from "../../materialization/scales/resolve.js";
13
+ import { findScale, findScaleConsumers, resolveConsumerCategoryOrder,
14
+ resolveConsumerValues, resolveSeriesLayoutScaleValues } from "./consumers/index.js";
15
+
16
+ function resolveScalePreviewAtBounds(program, id, bounds) {
17
+ const scale = findScale(program, id);
18
+ const consumers = findScaleConsumers(program, id);
19
+ if (consumers.length === 0) throw new Error(`Scale "${id}" has no supported consumers.`);
20
+ const consumerChannels = new Set(consumers.map(
21
+ consumer => normalizePositionScaleChannel(consumer.channel)
22
+ ));
23
+ const families = new Set([...consumerChannels].map(
24
+ channel => channel === "stroke" ? "color" : channel
25
+ ));
26
+ if (families.size !== 1) {
27
+ throw new Error(`Scale "${id}" cannot be shared across channels.`);
28
+ }
29
+ const channel = consumerChannels.size === 1
30
+ ? consumerChannels.values().next().value
31
+ : families.values().next().value;
32
+ const valuesByConsumer = consumers.map(consumer => {
33
+ if (
34
+ program.markConfigs?.[consumer.layer.id]?.markFilter?.empty === true ||
35
+ program.markConfigs?.[consumer.layer.id]?.statisticalReference !== undefined
36
+ ) {
37
+ return {
38
+ consumer,
39
+ values: [],
40
+ categoryOrder: undefined,
41
+ seriesLayout: undefined,
42
+ ...(program.markConfigs?.[consumer.layer.id]?.statisticalReference === undefined
43
+ ? {}
44
+ : { scalePolicy: false })
45
+ };
46
+ }
47
+ return { consumer,
48
+ values: resolveConsumerValues(program, consumer),
49
+ categoryOrder: resolveConsumerCategoryOrder(program, consumer),
50
+ seriesLayout: resolveSeriesLayoutScaleValues(program, consumer) };
51
+ });
52
+ const polarFrame = channel === "radius"
53
+ ? resolveSharedRadiusFrame(program, consumers, bounds)
54
+ : undefined;
55
+ const resolvedScale = resolveScaleMaterialization({ id, scale, channel, consumers, valuesByConsumer,
56
+ bounds: ["color", "stroke", "strokeDash", "strokeWidth", "shape", "size", "opacity", "xOffset", "yOffset"].includes(channel)
57
+ ? undefined : bounds,
58
+ resolvedScales: program.resolvedScales, markConfigs: program.markConfigs, polarFrame,
59
+ thetaScales: scale.radialMapping === undefined ? undefined : Object.fromEntries(consumers.map(({ layer }) =>
60
+ [layer.id, findSemanticScale(program, layer.encoding?.theta?.scale)])) });
61
+ return { channel, consumers, valuesByConsumer, resolvedScale };
62
+ }
63
+
64
+ function resolveSharedRadiusFrame(program, consumers, bounds) {
65
+ const coordinateIds = [...new Set(consumers.map(({ layer }) => layer.coordinate))];
66
+ if (coordinateIds.some(id => id === undefined)) {
67
+ throw new Error("Radius scale consumers require stored Polar coordinates.");
68
+ }
69
+ const frames = coordinateIds.map(id => {
70
+ const coordinate = requireCoordinate(program, id);
71
+ if (coordinate.type !== "polar") {
72
+ throw new Error("Radius scale consumers require Polar coordinates.");
73
+ }
74
+ return resolvePolarFrame(bounds, coordinate.polarFrame);
75
+ });
76
+ if (frames.slice(1).some(
77
+ frame => Math.abs(frame.availableRadius - frames[0].availableRadius) > 1e-9
78
+ )) {
79
+ throw new Error(
80
+ "A radius scale cannot be shared across coordinates with different Polar frames."
81
+ );
82
+ }
83
+ return frames[0];
84
+ }
85
+
86
+ function dataAspectScaleIds(program) {
87
+ const ids = new Set();
88
+ for (const coordinate of program.semanticSpec.coordinates) {
89
+ if (coordinate.aspect?.mode !== "data") continue;
90
+ const pair = resolveDataAspectScalePair(program, coordinate);
91
+ ids.add(pair.x);
92
+ ids.add(pair.y);
93
+ }
94
+ return ids;
95
+ }
96
+
97
+ export function resolveScalePreview(program, id) {
98
+ const requestedConsumers = findScaleConsumers(program, id);
99
+ const positionChannels = new Set([
100
+ "x", "y", "x2", "y2", "theta", "radius"
101
+ ]);
102
+ const positional = requestedConsumers.some(consumer =>
103
+ consumer.role === "parallelDimension" ||
104
+ positionChannels.has(consumer.channel)
105
+ );
106
+ const allocated = positional ? resolveGraphicBounds(program) : undefined;
107
+ const provisional = resolveScalePreviewAtBounds(program, id, allocated);
108
+ if (!positional) return provisional;
109
+ const resolvedScales = {
110
+ ...program.resolvedScales,
111
+ [id]: provisional.resolvedScale
112
+ };
113
+ for (const scaleId of dataAspectScaleIds(program)) {
114
+ if (scaleId === id) continue;
115
+ resolvedScales[scaleId] = resolveScalePreviewAtBounds(
116
+ program,
117
+ scaleId,
118
+ allocated
119
+ ).resolvedScale;
120
+ }
121
+ const bounds = resolveScaleConsumerBounds(
122
+ program,
123
+ provisional.consumers,
124
+ { resolvedScales }
125
+ );
126
+ return resolveScalePreviewAtBounds(program, id, bounds);
127
+ }
128
+
129
+ export function validatePendingMeasuredScale(program, scale, consumers) {
130
+ const bounds = resolveScaleConsumerBounds(program, consumers);
131
+ const range = resolveArcAutoPositionRange({ consumers, scale, channel: "radius",
132
+ range: resolveScaleRange(
133
+ scale.range,
134
+ "radius",
135
+ bounds,
136
+ resolveSharedRadiusFrame(program, consumers, bounds)
137
+ ), markConfigs: program.markConfigs });
138
+ validateMeasuredRadiusConsumers({ scale, domain: scale.domain, range, consumers,
139
+ markConfigs: program.markConfigs, thetaScales: {} });
140
+ }
@@ -7,7 +7,7 @@ const OPTIONS = Object.freeze([
7
7
  "type",
8
8
  "domain",
9
9
  "range",
10
- "interpolate",
10
+ "interpolate", "midpoint",
11
11
  "clamp",
12
12
  "reverse",
13
13
  "unknown"
@@ -25,13 +25,17 @@ export const setQuantitativeColorScale = action(
25
25
  }
26
26
  const existing = findSemanticScale(this, args.id);
27
27
  if (existing !== undefined && existing.type !== args.type) {
28
- throw new Error(
29
- `Scale "${args.id}" cannot change type from "${existing.type}" to "${args.type}".`
30
- );
28
+ if (typeof this.editScale !== "function") {
29
+ throw new Error("Color scale type transitions require the Full ChartProgram; use a new scale id in Basic.");
30
+ }
31
+ return this.editScale(args);
31
32
  }
32
33
  let next = this;
34
+ if (existing?.midpoint !== undefined && args.midpoint === undefined) {
35
+ next = next.editSemantic({ property: `scale[${args.id}].midpoint`, remove: true });
36
+ }
33
37
  for (const property of [
34
- "type", "domain", "range", "interpolate", "clamp", "reverse", "unknown"
38
+ "type", "domain", "range", "interpolate", "midpoint", "clamp", "reverse", "unknown"
35
39
  ]) {
36
40
  if (!Object.hasOwn(args, property)) continue;
37
41
  if (existing?.[property] === args[property]) continue;