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,101 +1,7 @@
1
- import {
2
- BAR_GRAINS,
3
- resolveBarChannels,
4
- resolveBarGrain,
5
- resolveBarOffsetChannel
6
- } from "../../../grammar/bars/policy.js";
7
- import { resolveOffsetScaleDefinition } from "../../scales/definitions.js";
8
- import { isRangedArea } from "./policy.js";
9
-
10
1
  export function applyColorLayoutCompanion(
11
2
  program,
12
- { target, layer, layout, scale, field }
3
+ { target, layer, layout, horizon }
13
4
  ) {
14
- if (layout === undefined || layer.mark.type === "arc") return program;
15
- if (
16
- layer.mark.type === "bar" &&
17
- resolveBarGrain(layer) === BAR_GRAINS.ranged
18
- ) {
19
- return program;
20
- }
21
- if (
22
- layer.mark.type === "area" &&
23
- [layer.encoding?.x, layer.encoding?.y].some(
24
- encoding => ["nominal", "ordinal"].includes(encoding?.fieldType)
25
- )
26
- ) {
27
- return program;
28
- }
29
- if (isRangedArea(layer)) return program;
30
- const channels = layer.mark.type === "bar"
31
- ? resolveBarChannels(layer)
32
- : undefined;
33
- const measureChannel = channels?.measure ?? "y";
34
- const measure = layer.encoding?.[measureChannel];
35
- if (measure?.field === undefined || measure.scale === undefined) {
36
- throw new Error(`Color layout on mark "${target}" requires a measure encoding.`);
37
- }
38
- const stack = layout === "fill"
39
- ? "normalize"
40
- : layout === "center"
41
- ? "center"
42
- : layout === "overlay" || layout === "group"
43
- ? null
44
- : "zero";
45
- let next = program;
46
- if (layout === "group") {
47
- const offsetChannel = resolveBarOffsetChannel(layer);
48
- const method = offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
49
- next = next[method]({
50
- field,
51
- target,
52
- scale: {
53
- ...(layer.encoding?.[offsetChannel]?.scale === undefined
54
- ? {}
55
- : { id: layer.encoding[offsetChannel].scale }),
56
- domain: scale.domain
57
- }
58
- });
59
- }
60
- return next[measureChannel === "x" ? "encodeX" : "encodeY"]({
61
- target,
62
- field: measure.field,
63
- fieldType: measure.fieldType,
64
- ...(measure.aggregate === undefined ? {} : { aggregate: measure.aggregate }),
65
- stack
66
- });
67
- }
68
-
69
- export function preSynchronizeGroupedOffset(
70
- program,
71
- { target, layer, layout, field, fieldType, scale, requestedScale }
72
- ) {
73
- if (
74
- layer.mark.type !== "bar" ||
75
- layout !== "group" ||
76
- layer.encoding?.color?.scale !== scale.id ||
77
- !Object.keys(requestedScale).some(key => key !== "id")
78
- ) {
79
- return program;
80
- }
81
- const offsetChannel = resolveBarOffsetChannel(layer);
82
- const offset = layer.encoding?.[offsetChannel];
83
- if (offset?.scale === undefined) return program;
84
- const offsetScale = resolveOffsetScaleDefinition(program, {
85
- id: offset.scale,
86
- domain: scale.domain
87
- }, offsetChannel);
88
- return program
89
- .editSemantic({
90
- property: `layer[${target}].encoding.${offsetChannel}.field`,
91
- value: field
92
- })
93
- .editSemantic({
94
- property: `layer[${target}].encoding.${offsetChannel}.fieldType`,
95
- value: fieldType
96
- })
97
- .editSemantic({
98
- property: `scale[${offsetScale.id}].domain`,
99
- value: offsetScale.domain
100
- });
5
+ if (layout === undefined || layer.mark.type === "arc" || horizon) return program;
6
+ return program.layoutSeries({ target, mode: layout });
101
7
  }
@@ -5,7 +5,7 @@ import {
5
5
  import { validateColorLayout } from "../../../grammar/seriesLayout.js";
6
6
 
7
7
  export const COLOR_ENCODING_OPTIONS = Object.freeze([
8
- "field", "target", "fieldType", "scale", "palette", "layout", "aggregate"
8
+ "field", "target", "fieldType", "scale", "palette", "layout", "aggregate", "temporalUnit"
9
9
  ]);
10
10
 
11
11
  export function resolveColorScaleOptions(args) {
@@ -24,6 +24,7 @@ export function assertNoConstantColor(program, layer) {
24
24
  const hasConstant = {
25
25
  point: config?.fill !== undefined,
26
26
  line: config?.stroke !== undefined,
27
+ area: config?.errorBand?.fill !== undefined,
27
28
  bar: config?.barAppearance?.fill !== undefined,
28
29
  arc: config?.fill !== undefined,
29
30
  rect: config?.fillExplicit === true
@@ -53,12 +54,7 @@ export function resolveColorLayout(layer, requested, barGrain) {
53
54
  : undefined
54
55
  );
55
56
  if (requested !== undefined) validateColorLayout(requested);
56
- if (existing !== undefined && requested !== undefined && requested !== existing) {
57
- throw new Error(
58
- `Color layout transition from "${existing}" to "${requested}" is not supported.`
59
- );
60
- }
61
- const layout = requested ?? existing ?? (
57
+ const layout = requested ?? layer.layout?.mode ?? existing ?? (
62
58
  layer.mark.type === "bar"
63
59
  ? barGrain === BAR_GRAINS.histogram
64
60
  ? "stack"
@@ -84,7 +80,8 @@ export function resolveColorLayout(layer, requested, barGrain) {
84
80
  ) {
85
81
  throw new Error('Arc color layout currently supports only "overlay".');
86
82
  }
87
- if (isRangedArea(layer) && layout !== "overlay") {
83
+ if (isRangedArea(layer) && layer.encoding?.x2?.datum === undefined && layer.encoding?.y2?.datum === undefined &&
84
+ layer.encoding?.x?.datum === undefined && layer.encoding?.y?.datum === undefined && layout !== "overlay") {
88
85
  throw new Error('Ranged area color encoding supports only "overlay" layout.');
89
86
  }
90
87
  return layout;
@@ -46,20 +46,52 @@ export function resolveDensityTransitionScaleDefinitions(
46
46
  layer,
47
47
  transform,
48
48
  placement,
49
- rawPlacement
49
+ rawPlacement,
50
+ rawValueScale
50
51
  ) {
51
52
  const valueScale = valueScaleForTransition(program, layer, transform);
52
53
  if (valueScale === undefined) {
53
54
  throw new Error(`Density area "${layer.id}" requires its value scale.`);
54
55
  }
55
- const { id: _valueId, range: _valueRange, ...valueDefinition } = valueScale;
56
+ if (rawValueScale?.id !== undefined && rawValueScale.id !== valueScale.id) {
57
+ throw new Error("editDensity value scale cannot change its id.");
58
+ }
59
+ const { id: _valueId, range: _valueRange, ...storedValueDefinition } = valueScale;
56
60
  void _valueId;
57
61
  void _valueRange;
62
+ const valueDefinition = resolveDensityScaleOptions(
63
+ rawValueScale,
64
+ storedValueDefinition,
65
+ "Density valueScale"
66
+ );
67
+ delete valueDefinition.id;
68
+ delete valueDefinition.range;
58
69
  const valueChannel = placement?.channel === "x" ? "y" : "x";
59
70
  const companionChannel = valueChannel === "x" ? "y" : "x";
60
- const companionDefinition = placement === undefined
61
- ? { type: "linear", domain: "auto", range: "auto", nice: true, zero: true }
62
- : resolveDensityCategoryScaleOptions(rawPlacement.scale);
71
+ let companionDefinition;
72
+ if (placement === undefined) {
73
+ companionDefinition = {
74
+ type: "linear", domain: "auto", range: "auto", nice: true, zero: true
75
+ };
76
+ } else {
77
+ const currentCategoryChannel = transform.placement?.channel;
78
+ const currentCategoryScale = currentCategoryChannel === undefined
79
+ ? undefined
80
+ : findSemanticScale(program, layer.encoding[currentCategoryChannel].scale);
81
+ const { id: _categoryId, range: _categoryRange, ...storedCategoryDefinition } =
82
+ currentCategoryScale ?? {};
83
+ void _categoryId;
84
+ void _categoryRange;
85
+ companionDefinition = resolveDensityCategoryScaleOptions({
86
+ domain: "auto",
87
+ range: "auto",
88
+ paddingInner: 0,
89
+ paddingOuter: 0,
90
+ align: 0.5,
91
+ ...storedCategoryDefinition,
92
+ ...(rawPlacement?.scale ?? {})
93
+ });
94
+ }
63
95
  return {
64
96
  [valueChannel]: { ...valueDefinition, range: "auto" },
65
97
  [companionChannel]: companionDefinition
@@ -16,6 +16,8 @@ import { validateOptions } from "./shared.js";
16
16
  import { normalizeDensityPlacement } from "../../grammar/density.js";
17
17
  import { scaleEditPatch } from "../scales/patch.js";
18
18
  import { removeOwnedColorLegends } from "../guides/legends/remove.js";
19
+ import { rebindDistributionGuides } from "../distributions/revision.js";
20
+ import { findSemanticScale } from "../../selectors/scales.js";
19
21
  import {
20
22
  requireDensityField,
21
23
  resolveDensityCategoryScaleOptions,
@@ -26,14 +28,55 @@ import {
26
28
 
27
29
  const OPTIONS = Object.freeze([
28
30
  "field", "target", "source", "groupBy", "bandwidth", "extent", "steps",
29
- "kernel", "normalization", "as", "densityChannel", "coordinate",
31
+ "kernel", "normalization", "weight", "as", "densityChannel", "coordinate",
30
32
  "valueScale", "densityScale", "placement"
31
33
  ]);
32
34
  const EDIT_OPTIONS = Object.freeze([
33
35
  "target", "source", "field", "groupBy", "bandwidth", "extent", "steps",
34
- "kernel", "normalization", "placement"
36
+ "kernel", "normalization", "weight", "densityChannel", "valueScale", "placement"
35
37
  ]);
36
38
  const EDITABLE = Object.freeze(EDIT_OPTIONS.filter(option => option !== "target"));
39
+ const SCALE_PROPERTIES = Object.freeze([
40
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
41
+ "base", "exponent", "constant", "paddingInner", "paddingOuter",
42
+ "padding", "align", "interpolate", "midpoint", "unknown"
43
+ ]);
44
+
45
+ function replaceScaleDefinition(program, id, definition) {
46
+ const current = findSemanticScale(program, id);
47
+ let next = program;
48
+ for (const property of SCALE_PROPERTIES) {
49
+ if (Object.hasOwn(current, property) && !Object.hasOwn(definition, property)) {
50
+ next = next.editSemantic({ property: `scale[${id}].${property}`, remove: true });
51
+ }
52
+ }
53
+ for (const property of SCALE_PROPERTIES) {
54
+ if (!Object.hasOwn(definition, property)) continue;
55
+ next = next.editSemantic({
56
+ property: `scale[${id}].${property}`,
57
+ value: definition[property]
58
+ });
59
+ }
60
+ return next;
61
+ }
62
+
63
+ function replaceDensityPositions(program, layerId, definition) {
64
+ let next = program;
65
+ for (const channel of ["x", "y"]) {
66
+ const value = definition[channel];
67
+ for (const [property, propertyValue] of Object.entries({
68
+ field: value.field,
69
+ fieldType: value.fieldType,
70
+ scale: typeof value.scale === "string" ? value.scale : value.scale.id
71
+ })) {
72
+ next = next.editSemantic({
73
+ property: `layer[${layerId}].encoding.${channel}.${property}`,
74
+ value: propertyValue
75
+ });
76
+ }
77
+ }
78
+ return next;
79
+ }
37
80
 
38
81
  function findArea(program, requested) {
39
82
  const areas = program.semanticSpec.layers.filter(layer => layer.mark?.type === "area");
@@ -80,7 +123,7 @@ const encodeDensity = action(
80
123
  validateOptions(args, OPTIONS, "encodeDensity");
81
124
  const layer = findArea(this, args.target);
82
125
  const field = requireDensityField(args.field, "Density field");
83
- const groupBy = args.groupBy === undefined
126
+ const groupBy = args.groupBy === undefined || args.groupBy === false
84
127
  ? undefined
85
128
  : requireDensityField(args.groupBy, "Density groupBy");
86
129
  const source = validateUserId(
@@ -150,6 +193,7 @@ const encodeDensity = action(
150
193
  ...(args.normalization === undefined
151
194
  ? {}
152
195
  : { normalization: args.normalization }),
196
+ ...(args.weight === undefined ? {} : { weight: args.weight }),
153
197
  ...(args.as === undefined ? {} : { as: args.as }),
154
198
  ...(placement === undefined ? {} : { placement })
155
199
  };
@@ -241,17 +285,34 @@ const editDensity = action(
241
285
  : transform.groupBy;
242
286
  const changesGroup = groupBy !== transform.groupBy;
243
287
  assertGroupingSelectionCompatibility(this, layer, changesGroup);
288
+ const currentDensityChannel = transform.placement?.channel ?? (
289
+ layer.encoding.x.field === transform.as[1] ? "x" : "y"
290
+ );
291
+ const changesPlacementMode = Object.hasOwn(args, "placement") &&
292
+ (transform.placement === undefined) !==
293
+ (args.placement?.type === "baseline");
294
+ const densityChannel = Object.hasOwn(args, "densityChannel")
295
+ ? args.densityChannel
296
+ : changesPlacementMode
297
+ ? currentDensityChannel === "x" ? "y" : "x"
298
+ : currentDensityChannel;
299
+ if (!["x", "y"].includes(densityChannel)) {
300
+ throw new Error(`Unsupported densityChannel "${densityChannel}".`);
301
+ }
302
+ const changesDensityChannel = densityChannel !== currentDensityChannel;
244
303
  const categoryField = densityCategoryField(layer, transform, groupBy);
245
304
  const requestedPlacement = Object.hasOwn(args, "placement")
246
305
  ? normalizeDensityPlacement(args.placement, {
247
- densityChannel: transform.placement?.channel ?? "x",
306
+ densityChannel,
248
307
  groupBy,
249
308
  categoryField: categoryField ?? groupBy ??
250
309
  `${layer.id}DensityCategory`
251
310
  })
252
- : transform.placement === undefined || !changesGroup
311
+ : transform.placement === undefined
253
312
  ? transform.placement
254
- : normalizeDensityPlacement({
313
+ : !changesGroup && !changesDensityChannel
314
+ ? transform.placement
315
+ : normalizeDensityPlacement({
255
316
  type: "category",
256
317
  ...(transform.placement.side === undefined
257
318
  ? {}
@@ -261,7 +322,7 @@ const editDensity = action(
261
322
  ? {}
262
323
  : { split: transform.placement.split })
263
324
  }, {
264
- densityChannel: transform.placement.channel,
325
+ densityChannel,
265
326
  groupBy,
266
327
  categoryField
267
328
  });
@@ -290,6 +351,7 @@ const editDensity = action(
290
351
  const option = property => Object.hasOwn(args, property)
291
352
  ? args[property]
292
353
  : transform[property];
354
+ const weight = option("weight");
293
355
 
294
356
  const dataArgs = {
295
357
  id: revision.id,
@@ -304,12 +366,12 @@ const editDensity = action(
304
366
  kernel: option("kernel") ?? "gaussian",
305
367
  normalization: option("normalization") ?? "unit",
306
368
  as: transform.as,
369
+ ...(weight === undefined || weight === false ? {} : { weight }),
307
370
  ...(requestedPlacement === undefined
308
371
  ? {}
309
372
  : { placement: requestedPlacement })
310
373
  };
311
- const changesPlacementMode = Object.hasOwn(args, "placement") &&
312
- (transform.placement === undefined) !== (requestedPlacement === undefined);
374
+ const rebuildsPositions = changesPlacementMode || changesDensityChannel;
313
375
  const applyEdit = program => {
314
376
  let next = requestedPlacement === undefined
315
377
  ? program.createDensityData(dataArgs)
@@ -360,7 +422,7 @@ const editDensity = action(
360
422
  }
361
423
  }
362
424
 
363
- if (changesPlacementMode) {
425
+ if (rebuildsPositions) {
364
426
  const xScaleId = layer.encoding?.x?.scale;
365
427
  const yScaleId = layer.encoding?.y?.scale;
366
428
  const scaleDefinitions = resolveDensityTransitionScaleDefinitions(
@@ -368,24 +430,18 @@ const editDensity = action(
368
430
  layer,
369
431
  transform,
370
432
  requestedPlacement,
371
- args.placement
372
- );
373
- next = next
374
- .editSemantic({
375
- property: `layer[${layer.id}].encoding.x`,
376
- remove: true
377
- })
378
- .editSemantic({
379
- property: `layer[${layer.id}].encoding.y`,
380
- remove: true
381
- });
382
- next = next
383
- .editScale(scaleEditPatch(xScaleId, scaleDefinitions.x))
384
- .editScale(scaleEditPatch(yScaleId, scaleDefinitions.y));
385
- const densityChannel = requestedPlacement?.channel ?? (
386
- layer.encoding.x.field === transform.as[1] ? "x" : "y"
433
+ args.placement,
434
+ args.valueScale
387
435
  );
388
- const valueChannel = requestedPlacement?.channel === "x" ? "y" : "x";
436
+ if (
437
+ args.placement?.scale?.id !== undefined &&
438
+ args.placement.scale.id !== layer.encoding[requestedPlacement.channel].scale
439
+ ) {
440
+ throw new Error("editDensity placement scale cannot change its id.");
441
+ }
442
+ next = replaceScaleDefinition(next, xScaleId, scaleDefinitions.x);
443
+ next = replaceScaleDefinition(next, yScaleId, scaleDefinitions.y);
444
+ const valueChannel = densityChannel === "x" ? "y" : "x";
389
445
  const definition = resolveDensityPositionDefinition({
390
446
  layer: { densityChannel },
391
447
  output: transform.as,
@@ -393,23 +449,46 @@ const editDensity = action(
393
449
  placement: requestedPlacement,
394
450
  coordinate: layer.coordinate,
395
451
  valueScale: {
396
- id: valueChannel === "y" ? yScaleId : xScaleId,
397
- ...scaleDefinitions[valueChannel]
452
+ ...scaleDefinitions[valueChannel],
453
+ id: valueChannel === "y" ? yScaleId : xScaleId
398
454
  },
399
455
  densityScale: requestedPlacement === undefined
400
456
  ? {
401
- id: densityChannel === "x" ? xScaleId : yScaleId,
402
- ...scaleDefinitions[densityChannel]
457
+ ...scaleDefinitions[densityChannel],
458
+ id: densityChannel === "x" ? xScaleId : yScaleId
403
459
  }
404
460
  : undefined,
405
461
  placementScale: requestedPlacement === undefined
406
462
  ? undefined
407
463
  : {
408
- id: requestedPlacement.channel === "x" ? xScaleId : yScaleId,
409
- ...scaleDefinitions[requestedPlacement.channel]
464
+ ...scaleDefinitions[requestedPlacement.channel],
465
+ id: requestedPlacement.channel === "x" ? xScaleId : yScaleId
410
466
  }
411
467
  });
412
- next = applyDensityPosition(next, layer.id, definition, groupBy);
468
+ next = replaceDensityPositions(next, layer.id, definition);
469
+ next = next.rematerializeScale({ id: xScaleId, marks: false, guides: false })
470
+ .rematerializeScale({ id: yScaleId, marks: false, guides: false });
471
+ const oldValueChannel = currentDensityChannel === "x" ? "y" : "x";
472
+ next = rebindDistributionGuides(next, {
473
+ oldXScale: xScaleId,
474
+ oldYScale: yScaleId,
475
+ newXScale: xScaleId,
476
+ newYScale: yScaleId,
477
+ oldXTitle: oldValueChannel === "x"
478
+ ? transform.field
479
+ : transform.placement?.categoryField ?? transform.as[1],
480
+ oldYTitle: oldValueChannel === "y"
481
+ ? transform.field
482
+ : transform.placement?.categoryField ?? transform.as[1],
483
+ newXTitle: valueChannel === "x"
484
+ ? field
485
+ : requestedPlacement?.categoryField ?? transform.as[1],
486
+ newYTitle: valueChannel === "y"
487
+ ? field
488
+ : requestedPlacement?.categoryField ?? transform.as[1],
489
+ oldMeasureChannel: oldValueChannel,
490
+ newMeasureChannel: valueChannel
491
+ });
413
492
  } else if (
414
493
  requestedPlacement !== undefined &&
415
494
  Object.hasOwn(args.placement ?? {}, "scale")
@@ -425,6 +504,17 @@ const editDensity = action(
425
504
  }
426
505
  next = next.editScale(scaleEditPatch(scaleId, requestedScale));
427
506
  }
507
+ if (!rebuildsPositions && Object.hasOwn(args, "valueScale")) {
508
+ const valueChannel = densityChannel === "x" ? "y" : "x";
509
+ const scaleId = layer.encoding[valueChannel].scale;
510
+ if (args.valueScale?.id !== undefined && args.valueScale.id !== scaleId) {
511
+ throw new Error("editDensity value scale cannot change its id.");
512
+ }
513
+ next = next.editScale(scaleEditPatch(scaleId, {
514
+ ...args.valueScale,
515
+ id: scaleId
516
+ }));
517
+ }
428
518
 
429
519
  next = applyMaterializationPlan(
430
520
  next,
@@ -3,7 +3,7 @@ import { validateOptions } from "./shared.js";
3
3
 
4
4
  const HISTOGRAM_OPTIONS = Object.freeze([
5
5
  "field", "target", "coordinate", "maxBins", "binStep", "binBoundaries",
6
- "stack", "xScale", "yScale"
6
+ "stack", "xScale", "yScale", "weight"
7
7
  ]);
8
8
 
9
9
  const encodeHistogram = action(
@@ -28,7 +28,8 @@ const encodeHistogram = action(
28
28
  : { maxBins: args.maxBins ?? 10 };
29
29
  const x = {
30
30
  field: args.field,
31
- bin
31
+ bin,
32
+ ...(args.weight === undefined ? {} : { weight: args.weight })
32
33
  };
33
34
  const y = {
34
35
  stack: Object.hasOwn(args, "stack") ? args.stack : "zero"
@@ -4,6 +4,7 @@ import { validateOptionObject } from "../../../core/validation.js";
4
4
  import { resolvePalette } from "../../../grammar/palettes.js";
5
5
  import {
6
6
  normalizeTemporalValue,
7
+ resolveTemporalUnit,
7
8
  readNominalField
8
9
  } from "../../../grammar/scales/index.js";
9
10
  import {
@@ -42,7 +43,7 @@ function fieldOption(value, role) {
42
43
  }
43
44
  validateOptionObject(
44
45
  value,
45
- ["field", "fieldType", "scale"],
46
+ ["field", "fieldType", "scale", "temporalUnit"],
46
47
  `Horizon ${role}`
47
48
  );
48
49
  return value;
@@ -80,6 +81,7 @@ function uniqueStoredEncoding(program, dataset, role) {
80
81
  JSON.stringify([
81
82
  encoding.field,
82
83
  encoding.fieldType,
84
+ encoding.temporalUnit,
83
85
  encoding.scale,
84
86
  encoding.title
85
87
  ]),
@@ -103,6 +105,7 @@ export function resolveHorizonField(program, layer, dataset, requested, role) {
103
105
  return {
104
106
  field: inferred.field,
105
107
  fieldType: inferred.fieldType,
108
+ ...(inferred.temporalUnit === undefined ? {} : { temporalUnit: inferred.temporalUnit }),
106
109
  ...(role === "x" && inferred.scale !== undefined
107
110
  ? { scale: { id: inferred.scale } }
108
111
  : {}),
@@ -127,7 +130,9 @@ export function resolveHorizonField(program, layer, dataset, requested, role) {
127
130
  `Horizon ${role} fieldType must be ${role === "x" ? "quantitative or temporal" : "quantitative"}.`
128
131
  );
129
132
  }
133
+ const temporalUnit = resolveTemporalUnit(option, fieldType, matching);
130
134
  return {
135
+ ...(temporalUnit === undefined ? {} : { temporalUnit }),
131
136
  field: option.field,
132
137
  fieldType,
133
138
  ...(option.scale !== undefined
@@ -140,9 +145,7 @@ export function resolveHorizonField(program, layer, dataset, requested, role) {
140
145
  }
141
146
 
142
147
  export function resolveHorizonGroupBy(layer, dataset, requested) {
143
- if (requested === false) {
144
- throw new Error("encodeHorizon groupBy must be a field string when provided.");
145
- }
148
+ if (requested === false) return undefined;
146
149
  const value = requested ?? layer.encoding?.group?.field;
147
150
  if (value === undefined) return undefined;
148
151
  if (typeof value !== "string" || value.length === 0) {
@@ -262,6 +265,7 @@ export function resolveEditedHorizonField(
262
265
  [role]: {
263
266
  field: prior.field,
264
267
  fieldType: prior.fieldType,
268
+ ...(prior.temporalUnit === undefined ? {} : { temporalUnit: prior.temporalUnit }),
265
269
  scale: layer.encoding[role].scale,
266
270
  ...(role === "x" && layer.encoding.x.title !== undefined
267
271
  ? { title: layer.encoding.x.title }
@@ -42,6 +42,8 @@ import {
42
42
  validateUnusedHorizonEncodings
43
43
  } from "./horizon/resolve.js";
44
44
 
45
+ import { applyTemporalUnit } from "./temporal.js";
46
+
45
47
  const OPTIONS = Object.freeze([
46
48
  "target", "source", "x", "y", "groupBy", "bands", "baseline", "extent",
47
49
  "resolve", "missing", "overflow", "palette"
@@ -69,16 +71,17 @@ function applyHorizonEncoding(program, {
69
71
  colorScale
70
72
  }) {
71
73
  let next = clearOwnedEncodings(program, layer)
74
+ .editSemantic({
75
+ property: `layer[${layer.id}].encoding.x.title`,
76
+ value: layer.encoding?.x?.title ?? inferHorizonTitle(transform.x.field)
77
+ })
72
78
  .encodeX({
73
79
  target: layer.id,
74
80
  field: transform.as.x,
75
81
  fieldType: transform.x.fieldType,
82
+ ...(transform.x.fieldType === "temporal" ? { temporalUnit: "timestamp" } : {}),
76
83
  scale: xScale
77
84
  })
78
- .editSemantic({
79
- property: `layer[${layer.id}].encoding.x.title`,
80
- value: layer.encoding?.x?.title ?? inferHorizonTitle(transform.x.field)
81
- })
82
85
  .encodeY({
83
86
  target: layer.id,
84
87
  field: transform.as.lower,
@@ -113,6 +116,9 @@ const editHorizon = action(
113
116
  },
114
117
  function (args = {}) {
115
118
  validateOptionObject(args, EDIT_OPTIONS, "editHorizon");
119
+ if (Object.hasOwn(args, "groupBy") && args.groupBy === undefined) {
120
+ throw new Error("editHorizon groupBy requires a field string or false.");
121
+ }
116
122
  if (!EDITABLE.some(option => Object.hasOwn(args, option))) {
117
123
  throw new Error("editHorizon requires at least one Horizon option.");
118
124
  }
@@ -145,7 +151,8 @@ const editHorizon = action(
145
151
  );
146
152
  const candidate = validateHorizonTransform({
147
153
  type: "horizon",
148
- x: { field: x.field, fieldType: x.fieldType },
154
+ x: { field: x.field, fieldType: x.fieldType,
155
+ ...(x.temporalUnit === undefined ? {} : { temporalUnit: x.temporalUnit }) },
149
156
  y: { field: y.field, fieldType: y.fieldType },
150
157
  ...(groupBy === undefined ? {} : { groupBy }),
151
158
  bands: args.bands ?? prior.bands,
@@ -189,6 +196,8 @@ const editHorizon = action(
189
196
  value: x.fieldType
190
197
  });
191
198
  }
199
+ next = applyTemporalUnit(next, layer.id, "x",
200
+ x.fieldType === "temporal" ? "timestamp" : undefined, layer.encoding.x);
192
201
  if (Object.hasOwn(args, "x")) {
193
202
  next = next.editSemantic({
194
203
  property: `layer[${layer.id}].encoding.x.title`,
@@ -210,7 +219,7 @@ const editHorizon = action(
210
219
  requestedXScale.domain === undefined &&
211
220
  currentX.domain === "auto"
212
221
  ? source.values.map((row, index) => x.fieldType === "temporal"
213
- ? normalizeTemporalValue(row[x.field], x.field, index)
222
+ ? normalizeTemporalValue(row[x.field], x.field, index, x.temporalUnit)
214
223
  : row[x.field])
215
224
  : undefined;
216
225
  const nextX = resolvePositionScaleDefinition(next, "x", x.fieldType, {
@@ -285,7 +294,8 @@ const encodeHorizon = action(
285
294
  const as = horizonOutputFields(layer.id);
286
295
  const transform = validateHorizonTransform({
287
296
  type: "horizon",
288
- x: { field: x.field, fieldType: x.fieldType },
297
+ x: { field: x.field, fieldType: x.fieldType,
298
+ ...(x.temporalUnit === undefined ? {} : { temporalUnit: x.temporalUnit }) },
289
299
  y: { field: y.field, fieldType: y.fieldType },
290
300
  ...(groupBy === undefined ? {} : { groupBy }),
291
301
  bands: args.bands ?? 3,
@@ -308,7 +318,7 @@ const encodeHorizon = action(
308
318
  ? {
309
319
  domain: source.values.map((row, index) =>
310
320
  x.fieldType === "temporal"
311
- ? normalizeTemporalValue(row[x.field], x.field, index)
321
+ ? normalizeTemporalValue(row[x.field], x.field, index, x.temporalUnit)
312
322
  : row[x.field]
313
323
  )
314
324
  }
@@ -1,3 +1,4 @@
1
+ import { registerSeriesLayoutAction } from "./seriesLayout.js";
1
2
  import { registerDensityEncodingAction } from "./density.js";
2
3
  import { registerPathOrderEncodingActions } from "./pathOrder.js";
3
4
  import { registerHorizonEncodingAction } from "./horizon.js";
@@ -12,10 +13,13 @@ import { registerTextEncodingAction } from "./text.js";
12
13
  import { registerAppearanceEncodingAction } from "./appearance.js";
13
14
  import { registerStrokeDashEncodingActions } from "./strokeDash.js";
14
15
  import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
16
+ import { registerStrokeEncodingAction } from "./stroke.js";
15
17
  import { registerBarWidthEncodingAction } from "./barWidth.js";
16
18
  import { registerEncodingRemovalAction } from "./remove.js";
19
+ import { registerAtomicEncodingAction } from "./channels.js";
17
20
 
18
21
  export function registerEncodingActions(ProgramClass) {
22
+ registerSeriesLayoutAction(ProgramClass);
19
23
  registerPositionEncodingActions(ProgramClass);
20
24
  registerRangedEncodingActions(ProgramClass);
21
25
  registerOffsetEncodingAction(ProgramClass);
@@ -29,7 +33,9 @@ export function registerEncodingActions(ProgramClass) {
29
33
  registerAppearanceEncodingAction(ProgramClass);
30
34
  registerAngleEncodingAction(ProgramClass);
31
35
  registerRuleAppearanceEncodingActions(ProgramClass);
36
+ registerStrokeEncodingAction(ProgramClass);
32
37
  registerTextEncodingAction(ProgramClass);
33
38
  registerParallelEncodingAction(ProgramClass);
34
39
  registerEncodingRemovalAction(ProgramClass);
40
+ registerAtomicEncodingAction(ProgramClass);
35
41
  }