ggaction 0.0.17 → 0.0.19

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 (315) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +20 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +8 -7
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/derived.js +11 -1
  35. package/src/actions/data/edit.js +3 -1
  36. package/src/actions/data/index.js +4 -0
  37. package/src/actions/data/regression.js +25 -16
  38. package/src/actions/data/regressionStatistics.js +73 -0
  39. package/src/actions/data/shared.js +8 -4
  40. package/src/actions/data/stack.js +3 -1
  41. package/src/actions/distributions/revision.js +24 -18
  42. package/src/actions/encodings/appearance.js +37 -29
  43. package/src/actions/encodings/barWidth.js +36 -11
  44. package/src/actions/encodings/channels.js +0 -1
  45. package/src/actions/encodings/color/continuous.js +1 -1
  46. package/src/actions/encodings/color/index.js +1 -1
  47. package/src/actions/encodings/color/policy.js +10 -3
  48. package/src/actions/encodings/density/resolve.js +1 -6
  49. package/src/actions/encodings/position/policies/arc.js +12 -10
  50. package/src/actions/encodings/position/policies/area.js +3 -3
  51. package/src/actions/encodings/position/policies/bar.js +15 -6
  52. package/src/actions/encodings/position/policies/line.js +17 -6
  53. package/src/actions/encodings/position/resolve.js +3 -2
  54. package/src/actions/encodings/ranged.js +17 -0
  55. package/src/actions/encodings/remove.js +14 -8
  56. package/src/actions/encodings/seriesLayout.js +1 -0
  57. package/src/actions/encodings/shared.js +26 -24
  58. package/src/actions/encodings/text.js +6 -3
  59. package/src/actions/errorBands/create.js +2 -2
  60. package/src/actions/errorBands/edit.js +2 -1
  61. package/src/actions/facets/actions.js +176 -247
  62. package/src/actions/facets/guides.js +2 -1
  63. package/src/actions/gradientPlots/resolve.js +2 -17
  64. package/src/actions/guides/applicability.js +1 -1
  65. package/src/actions/guides/axes/axes.js +30 -11
  66. package/src/actions/guides/axes/axis.js +72 -10
  67. package/src/actions/guides/axes/components.js +13 -13
  68. package/src/actions/guides/axes/edit.js +36 -24
  69. package/src/actions/guides/axes/labels.js +155 -71
  70. package/src/actions/guides/axes/lines.js +27 -24
  71. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  72. package/src/actions/guides/axes/parallel/policy.js +8 -7
  73. package/src/actions/guides/axes/remove.js +13 -8
  74. package/src/actions/guides/axes/tickGroups.js +11 -7
  75. package/src/actions/guides/axes/ticks.js +36 -24
  76. package/src/actions/guides/axes/titles.js +107 -69
  77. package/src/actions/guides/basic.js +2 -0
  78. package/src/actions/guides/facadeAxes.js +33 -24
  79. package/src/actions/guides/facadeLegend.js +12 -4
  80. package/src/actions/guides/grids/grid.js +8 -2
  81. package/src/actions/guides/grids/resolve.js +102 -58
  82. package/src/actions/guides/legends/blocks.js +46 -20
  83. package/src/actions/guides/legends/categorical/actions.js +95 -19
  84. package/src/actions/guides/legends/categorical/components.js +15 -18
  85. package/src/actions/guides/legends/categorical/index.js +8 -0
  86. package/src/actions/guides/legends/categorical/layout.js +45 -20
  87. package/src/actions/guides/legends/categorical/options.js +39 -22
  88. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  89. package/src/actions/guides/legends/categorical/resolve.js +11 -4
  90. package/src/actions/guides/legends/categorical/symbols.js +38 -18
  91. package/src/actions/guides/legends/categorical/validation.js +1 -6
  92. package/src/actions/guides/legends/colorSize.js +110 -0
  93. package/src/actions/guides/legends/combine.js +37 -0
  94. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  95. package/src/actions/guides/legends/continuous/common.js +1 -447
  96. package/src/actions/guides/legends/continuous/gradient.js +73 -46
  97. package/src/actions/guides/legends/continuous/interval.js +4 -4
  98. package/src/actions/guides/legends/continuous/opacity.js +33 -21
  99. package/src/actions/guides/legends/creation.js +2 -0
  100. package/src/actions/guides/legends/edit.js +1 -604
  101. package/src/actions/guides/legends/focused.js +8 -7
  102. package/src/actions/guides/legends/group.js +1 -0
  103. package/src/actions/guides/legends/lane.js +1 -516
  104. package/src/actions/guides/legends/lifecycle.js +2 -1
  105. package/src/actions/guides/legends/remove.js +9 -3
  106. package/src/actions/guides/legends/sampling.js +1 -1
  107. package/src/actions/guides/legends/size.js +65 -47
  108. package/src/actions/guides/legends/strokeWidth.js +13 -29
  109. package/src/actions/guides/legends/target.js +2 -79
  110. package/src/actions/guides/legends/transition.js +44 -11
  111. package/src/actions/guides/polar/axes/labels.js +4 -2
  112. package/src/actions/guides/polar/axes/titles.js +7 -5
  113. package/src/actions/guides/polar/resolve.js +6 -14
  114. package/src/actions/guides/tickValues.js +8 -5
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +10 -4
  120. package/src/actions/marks/bar/edit.js +29 -14
  121. package/src/actions/marks/bar/materialize.js +15 -7
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/edit.js +1 -1
  125. package/src/actions/marks/point/materialize.js +5 -1
  126. package/src/actions/marks/rect/actions.js +3 -3
  127. package/src/actions/marks/remove.js +12 -10
  128. package/src/actions/marks/rule/actions.js +7 -4
  129. package/src/actions/marks/text/actions.js +17 -5
  130. package/src/actions/marks/text/layout.js +3 -0
  131. package/src/actions/primitives/createGraphics.js +3 -2
  132. package/src/actions/primitives/editGraphics.js +15 -11
  133. package/src/actions/primitives/graphicProperties.js +1 -6
  134. package/src/actions/primitives/semanticAction.js +27 -1
  135. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  136. package/src/actions/primitives/semanticValidation/layer.js +8 -0
  137. package/src/actions/regression/create.js +1 -1
  138. package/src/actions/regression/edit.js +18 -6
  139. package/src/actions/regression/resolve.js +2 -6
  140. package/src/actions/scales/channels.js +9 -4
  141. package/src/actions/scales/consumers/families.js +16 -5
  142. package/src/actions/scales/create.js +10 -5
  143. package/src/actions/scales/definitions.js +1 -425
  144. package/src/actions/scales/edit.js +3 -3
  145. package/src/actions/scales/editPolicy.js +12 -3
  146. package/src/actions/scales/materialize.js +1 -1
  147. package/src/actions/scales/patch.js +1 -1
  148. package/src/actions/scales/preview.js +21 -1
  149. package/src/actions/scales/quantitativeColor.js +8 -3
  150. package/src/actions/selection/actions.js +1 -13
  151. package/src/actions/theme/reconcile.js +126 -45
  152. package/src/actions/theme/state.js +12 -0
  153. package/src/actions/titles/actions.js +5 -18
  154. package/src/actions/titles/resolve.js +41 -19
  155. package/src/actions/violinPlots/edit.js +3 -20
  156. package/src/core/compositionState.js +6 -3
  157. package/src/core/font.js +9 -2
  158. package/src/core/graphicProperties.js +1 -0
  159. package/src/core/optionRequirements.js +1 -0
  160. package/src/core/programState.js +4 -0
  161. package/src/core/resourceReferences.js +5 -2
  162. package/src/core/textMetricProfile.js +3 -2
  163. package/src/core/textMetrics.js +41 -4
  164. package/src/core/validation.js +35 -0
  165. package/src/core/vocabulary.js +7 -0
  166. package/src/grammar/aggregate.js +4 -18
  167. package/src/grammar/arcs.js +1 -386
  168. package/src/grammar/areaPaint.js +10 -0
  169. package/src/grammar/areaSeries.js +18 -10
  170. package/src/grammar/axisTextPlacement.js +1 -0
  171. package/src/grammar/bars/geometry.js +30 -3
  172. package/src/grammar/bars/policy.js +12 -1
  173. package/src/grammar/bin.js +2 -12
  174. package/src/grammar/bin2d.js +1 -6
  175. package/src/grammar/boxPlot.js +56 -9
  176. package/src/grammar/categoryOrder.js +1 -6
  177. package/src/grammar/colorSizeLegend.js +39 -0
  178. package/src/grammar/complete.js +15 -19
  179. package/src/grammar/computed.js +115 -26
  180. package/src/grammar/curveCommands.js +29 -16
  181. package/src/grammar/datasetSchema.js +13 -9
  182. package/src/grammar/displayLabels.js +36 -0
  183. package/src/grammar/ecdf.js +2 -9
  184. package/src/grammar/facets/dependencies.js +6 -2
  185. package/src/grammar/facets/guides.js +58 -30
  186. package/src/grammar/facets/index.js +2 -5
  187. package/src/grammar/facets/legendIntent.js +65 -0
  188. package/src/grammar/fold.js +2 -12
  189. package/src/grammar/gradientProfile.js +1 -6
  190. package/src/grammar/guideDash.js +1 -0
  191. package/src/grammar/impute.js +21 -48
  192. package/src/grammar/interval.js +2 -9
  193. package/src/grammar/legendBlocks.js +1 -0
  194. package/src/grammar/legendBorder.js +9 -0
  195. package/src/grammar/legendClip.js +19 -0
  196. package/src/grammar/legendCombinations.js +45 -0
  197. package/src/grammar/legendGradient.js +27 -0
  198. package/src/grammar/legendGroups.js +1 -0
  199. package/src/grammar/legendOpacity.js +6 -0
  200. package/src/grammar/legendTitleStyle.js +22 -0
  201. package/src/grammar/lineSeries.js +32 -17
  202. package/src/grammar/markLabels.js +5 -2
  203. package/src/grammar/normalize.js +4 -54
  204. package/src/grammar/numeric.js +1 -290
  205. package/src/grammar/parallelCoordinates.js +1 -6
  206. package/src/grammar/parseTime.js +41 -0
  207. package/src/grammar/pathCommands.js +11 -4
  208. package/src/grammar/pointShapes.js +16 -2
  209. package/src/grammar/polar.js +1 -184
  210. package/src/grammar/positionCompatibility.js +3 -3
  211. package/src/grammar/regression/derive.js +16 -2
  212. package/src/grammar/regression/models.js +142 -54
  213. package/src/grammar/regression/parameters.js +23 -4
  214. package/src/grammar/regression/statistics.js +64 -0
  215. package/src/grammar/roundedRect.js +42 -54
  216. package/src/grammar/scales/appearance.js +1 -206
  217. package/src/grammar/scales/color.js +1 -382
  218. package/src/grammar/scales/colorConsumers.js +5 -2
  219. package/src/grammar/scales/definition.js +1 -197
  220. package/src/grammar/scales/discretized.js +1 -195
  221. package/src/grammar/scales/size.js +1 -456
  222. package/src/grammar/scales/temporal.js +2 -13
  223. package/src/grammar/scales/transformed.js +1 -383
  224. package/src/grammar/scales/types.js +1 -1
  225. package/src/grammar/scales/validation.js +1 -66
  226. package/src/grammar/schemas/concreteGraphic.js +14 -0
  227. package/src/grammar/schemas/graphic.js +6 -0
  228. package/src/grammar/schemas/graphicBounds.js +31 -7
  229. package/src/grammar/schemas/semanticPath.js +19 -3
  230. package/src/grammar/stack.js +2 -10
  231. package/src/grammar/statisticalReference.js +1 -6
  232. package/src/grammar/summary.js +4 -27
  233. package/src/grammar/text.js +19 -0
  234. package/src/grammar/timeUnit.js +22 -11
  235. package/src/grammar/transformKeys.js +47 -0
  236. package/src/grammar/transformTopology.js +1 -0
  237. package/src/grammar/transforms.js +15 -2
  238. package/src/grammar/valueFormat.js +17 -5
  239. package/src/grammar/weightedStatistics.js +1 -6
  240. package/src/grammar/window.js +4 -58
  241. package/src/inspectionDescriptors.js +1 -1
  242. package/src/layout/canvas.js +1 -110
  243. package/src/layout/facets.js +1 -244
  244. package/src/layout/guideCollisions.js +9 -1
  245. package/src/layout/labels.js +1 -0
  246. package/src/layout/legend.js +13 -0
  247. package/src/layout/legendGroup.js +1 -0
  248. package/src/layout/legendItems.js +59 -15
  249. package/src/layout/legendLane.js +1 -441
  250. package/src/layout/legendText.js +1 -0
  251. package/src/layout/title.js +35 -13
  252. package/src/materialization/bars/aggregate.js +11 -5
  253. package/src/materialization/bars/histogram.js +4 -1
  254. package/src/materialization/bars/ranged.js +19 -6
  255. package/src/materialization/bars/resolve.js +22 -1
  256. package/src/materialization/compositionSnapshot.js +2 -7
  257. package/src/materialization/coordinateBounds.js +21 -0
  258. package/src/materialization/dependencies.js +4 -44
  259. package/src/materialization/facetGuides/edit.js +30 -0
  260. package/src/materialization/facetGuides/layout.js +1 -0
  261. package/src/materialization/facetGuides/owner.js +125 -0
  262. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  263. package/src/materialization/facetGuides/placement.js +71 -14
  264. package/src/materialization/facetGuides/preparation.js +22 -7
  265. package/src/materialization/facetHeaders.js +1 -347
  266. package/src/materialization/facets.js +1 -185
  267. package/src/materialization/graphicHierarchy.js +5 -1
  268. package/src/materialization/graphicTranslation.js +73 -0
  269. package/src/materialization/guides/layout.js +1 -57
  270. package/src/materialization/guides/resources.js +1 -123
  271. package/src/materialization/layout.js +53 -0
  272. package/src/materialization/legendBorder.js +28 -0
  273. package/src/materialization/legendClip.js +76 -0
  274. package/src/materialization/legendGroups.js +1 -0
  275. package/src/materialization/legendText.js +1 -0
  276. package/src/materialization/legends.js +2 -2
  277. package/src/materialization/marks/capabilities.js +5 -6
  278. package/src/materialization/rect.js +1 -1
  279. package/src/materialization/scaleGuideDependencies.js +14 -15
  280. package/src/materialization/scales/map.js +6 -0
  281. package/src/materialization/scales/policies/arc.js +8 -7
  282. package/src/materialization/scales/policies/bar.js +19 -7
  283. package/src/materialization/scales/policies/series.js +2 -1
  284. package/src/materialization/scales/resolve.js +7 -3
  285. package/src/materialization/selection/items/arc.js +1 -1
  286. package/src/materialization/selection/items/bar.js +7 -6
  287. package/src/materialization/selection/items/path.js +7 -6
  288. package/src/materialization/selection/items/rect.js +2 -2
  289. package/src/materialization/text.js +13 -4
  290. package/src/materialization/textLines.js +15 -0
  291. package/src/materialization/typography.js +10 -2
  292. package/src/persistence/codec.js +37 -6
  293. package/src/persistence/validation.js +220 -0
  294. package/src/persistence.js +19 -6
  295. package/src/renderers/canvas/circle.js +6 -3
  296. package/src/renderers/canvas/fill.js +7 -1
  297. package/src/renderers/canvas/index.js +22 -11
  298. package/src/renderers/canvas/isolated.js +166 -0
  299. package/src/renderers/canvas/limits.js +3 -0
  300. package/src/renderers/canvas/line.js +1 -0
  301. package/src/renderers/canvas/path.js +1 -1
  302. package/src/renderers/canvas/rect.js +11 -8
  303. package/src/renderers/canvas/stroke.js +2 -1
  304. package/src/renderers/canvas/text.js +5 -3
  305. package/src/renderers/pdf.js +2 -2
  306. package/src/renderers/png.js +2 -2
  307. package/src/renderers/svg.js +14 -4
  308. package/src/selectors/axes.js +1 -0
  309. package/src/selectors/graphicCapacity.js +38 -0
  310. package/src/selectors/layers.js +19 -0
  311. package/src/theme/defaults.js +8 -2
  312. package/src/version.js +1 -1
  313. package/types/basic.d.ts +3 -0
  314. package/types/index.d.ts +12 -0
  315. package/types/program.d.ts +536 -129
@@ -1,19 +1,27 @@
1
+ import { AXIS_RESOURCE_CHANNELS } from "../../../core/vocabulary.js";
2
+ import { cartesianAxisOwners, axisTargetArgs, assertCartesianAxisSideAvailable } from "../../../selectors/axes.js";
3
+ import { axisGraphicIds } from "../../../materialization/guides/resources.js";
4
+ import { defaultAxisPosition, validateAxisPosition } from "./policy.js";
1
5
  import { isSourceOwnedText } from "../../../grammar/text.js";
2
6
  import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
3
7
  import { action } from "../../../core/action.js";
4
8
  import { isPlainObject } from "../../../core/immutable.js";
5
9
  import { validateUserId } from "../../../core/identifiers.js";
6
- import { validateKeys } from "../../../core/validation.js";
10
+ import { validateKeys, validateNonNegativeFinite } from "../../../core/validation.js";
7
11
  import { hasCoordinate } from "../../../selectors/index.js";
8
12
  import { validateEnabledAxisComponents } from "./components.js";
13
+ import { requireSemanticScale } from "../../../selectors/scales.js";
14
+ import { requireCoordinate } from "../../../selectors/coordinates.js";
9
15
 
10
16
  const TOP_OPTIONS = Object.freeze([
17
+ "id",
11
18
  "scale",
12
19
  "coordinate",
13
20
  "position",
14
21
  "line",
15
22
  "ticksAndLabels",
16
- "title"
23
+ "title",
24
+ "offset"
17
25
  ]);
18
26
  const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
19
27
  const TICK_GROUP_OPTIONS = Object.freeze([
@@ -30,7 +38,8 @@ const TITLE_OPTIONS = Object.freeze([
30
38
  "color",
31
39
  "fontSize",
32
40
  "fontFamily",
33
- "fontWeight"
41
+ "fontWeight",
42
+ "alongOffset", "align", "baseline"
34
43
  ]);
35
44
 
36
45
  function validateNested(value, supported, label) {
@@ -43,6 +52,7 @@ function validateNested(value, supported, label) {
43
52
 
44
53
  export function validateAxisArgs(args, operation) {
45
54
  validateKeys(args, TOP_OPTIONS, operation);
55
+ if (Object.hasOwn(args, "offset")) validateNonNegativeFinite(args.offset, "Axis frame offset");
46
56
 
47
57
  validateEnabledAxisComponents(args, operation);
48
58
  if (Object.hasOwn(args, "line") && args.line !== false) {
@@ -82,10 +92,52 @@ function makeCreateAxis(channel) {
82
92
  },
83
93
  withGuideLayoutValidation(function (args = {}) {
84
94
  validateAxisArgs(args, operation.create);
85
- const shared = {};
95
+ const axisId = validateUserId(args.id ?? channel, "Axis id");
96
+ if (AXIS_RESOURCE_CHANNELS.includes(axisId) && axisId !== channel) {
97
+ throw new Error(`Axis id "${axisId}" is reserved for another axis channel.`);
98
+ }
99
+ const named = axisId !== channel;
100
+ let source = this;
101
+ if (named) {
102
+ if (cartesianAxisOwners(this).some(owner => owner.id === axisId) ||
103
+ axisGraphicIds(channel, axisId).some(id => this.graphicSpec.objects[id] !== undefined)) {
104
+ throw new Error(`Axis "${axisId}" requires a missing resource and missing concrete components.`);
105
+ }
106
+ const layers = this.semanticSpec.layers.filter(layer => !isSourceOwnedText(layer) &&
107
+ layer.encoding?.[channel]?.scale !== undefined &&
108
+ (args.coordinate === undefined || layer.coordinate === args.coordinate) &&
109
+ (args.scale === undefined || layer.encoding[channel].scale === args.scale));
110
+ const scales = [...new Set(layers.map(layer => layer.encoding[channel].scale))];
111
+ if (args.scale === undefined && scales.length !== 1) throw new Error("Named axes require an explicit scale when the channel binding is ambiguous.");
112
+ const scale = args.scale ?? scales[0];
113
+ const coordinates = [...new Set(layers.map(layer => layer.coordinate))];
114
+ if (args.coordinate === undefined && coordinates.length !== 1) throw new Error("Named axes require an explicit coordinate when the binding is ambiguous.");
115
+ const coordinate = args.coordinate ?? coordinates[0];
116
+ if (requireCoordinate(this, coordinate).type !== "cartesian") throw new Error("Named Cartesian axes require a Cartesian coordinate.");
117
+ const position = validateAxisPosition(channel, args.position ?? defaultAxisPosition(channel));
118
+ assertCartesianAxisSideAvailable(this, { id: axisId, channel, coordinate, position });
119
+ args = { ...args, scale, coordinate, position };
120
+ source = source.editSemantic({ property: `guide.axis.${axisId}.channel`, value: channel })
121
+ .editSemantic({ property: `guide.axis.${axisId}.position`, value: position })
122
+ .editSemantic({ property: `guide.axis.${axisId}.scale`, value: scale })
123
+ .editSemantic({ property: `guide.axis.${axisId}.coordinate`, value: coordinate });
124
+ } else if (cartesianAxisOwners(this).some(owner => owner.named)) {
125
+ const scale = args.scale ?? channel;
126
+ const coordinates = [...new Set(this.semanticSpec.layers.filter(layer =>
127
+ layer.encoding?.[channel]?.scale === scale).map(layer => layer.coordinate))];
128
+ const coordinate = args.coordinate ?? (coordinates.length === 1 ? coordinates[0] : undefined);
129
+ assertCartesianAxisSideAvailable(this, { id: axisId, channel, coordinate,
130
+ position: validateAxisPosition(channel, args.position ?? defaultAxisPosition(channel)) });
131
+ if (!cartesianAxisOwners(this).some(owner => owner.id === channel)) {
132
+ source = source.editSemantic({ property: `guide.axis.${axisId}.scale`, value: scale });
133
+ if (coordinate !== undefined) source = source.editSemantic({ property: `guide.axis.${axisId}.coordinate`, value: coordinate });
134
+ }
135
+ }
136
+ const shared = axisTargetArgs(axisId, channel);
86
137
  if (Object.hasOwn(args, "scale")) shared.scale = args.scale;
87
138
  if (Object.hasOwn(args, "position")) shared.position = args.position;
88
- let next = this;
139
+ let next = Object.hasOwn(args, "offset")
140
+ ? source._withGuideConfig(axisId, "frame", { offset: args.offset }) : source;
89
141
 
90
142
  if (Object.hasOwn(args, "coordinate")) {
91
143
  const coordinate = validateUserId(args.coordinate, "Coordinate id");
@@ -100,16 +152,26 @@ function makeCreateAxis(channel) {
100
152
  if (!exists) {
101
153
  throw new Error(`Unknown coordinate "${coordinate}".`);
102
154
  }
103
- if (!hasConsumer) {
104
- throw new Error(
105
- `${operation.create} found no ${channel} encoding for coordinate "${coordinate}" and scale "${scale}".`
106
- );
155
+ const standalone = !hasConsumer;
156
+ if (standalone && (!Object.hasOwn(args, "scale") ||
157
+ next.semanticSpec.layers.some(layer => Object.values(layer.encoding ?? {}).some(encoding => encoding?.scale === scale)))) {
158
+ throw new Error(`${operation.create} found no ${channel} encoding for coordinate "${coordinate}" and scale "${scale}"; standalone binding requires an explicit unused scale.`);
159
+ }
160
+ if (standalone) {
161
+ const definition = requireSemanticScale(next, scale);
162
+ if (!Array.isArray(definition.domain) || definition.domain.length === 0 ||
163
+ requireCoordinate(next, coordinate).type !== "cartesian") {
164
+ throw new Error("Standalone axes require a Cartesian coordinate and explicit nonempty scale domain.");
165
+ }
107
166
  }
108
167
 
109
168
  next = next.editSemantic({
110
- property: `guide.axis.${channel}.coordinate`,
169
+ property: `guide.axis.${axisId}.coordinate`,
111
170
  value: coordinate
112
171
  });
172
+ if (standalone) next = next._withGuideConfig(axisId, "binding", { standalone: true })
173
+ .editSemantic({ property: `guide.axis.${axisId}.scale`, value: scale })
174
+ .rematerializeScale({ id: scale, guides: false, marks: false });
113
175
  }
114
176
 
115
177
  for (const component of ["line", "ticksAndLabels", "title"]) {
@@ -2,8 +2,8 @@ import { axisGraphicIds } from "../../../materialization/guides/resources.js";
2
2
 
3
3
  export const AXIS_COMPONENTS = Object.freeze(["line", "ticks", "labels", "title"]);
4
4
 
5
- export function axisComponentId(channel, component) {
6
- return axisGraphicIds(channel)[AXIS_COMPONENTS.indexOf(component)];
5
+ export function axisComponentId(channel, component, axisId = channel) {
6
+ return axisGraphicIds(channel, axisId)[AXIS_COMPONENTS.indexOf(component)];
7
7
  }
8
8
 
9
9
  export function axisComponentDisabled(args, component) {
@@ -17,27 +17,27 @@ export function validateEnabledAxisComponents(args, operation) {
17
17
  }
18
18
  }
19
19
 
20
- export function hasAxisComponent(program, channel, component) {
21
- return program.graphicSpec.objects[axisComponentId(channel, component)] !== undefined ||
22
- program.guideConfigs.axis?.[channel]?.[component] !== undefined ||
20
+ export function hasAxisComponent(program, channel, component, axisId = channel) {
21
+ return program.graphicSpec.objects[axisComponentId(channel, component, axisId)] !== undefined ||
22
+ program.guideConfigs.axis?.[axisId]?.[component] !== undefined ||
23
23
  (component === "title" &&
24
- program.semanticSpec.guides.axis?.[channel]?.title !== undefined);
24
+ program.semanticSpec.guides.axis?.[axisId]?.title !== undefined);
25
25
  }
26
26
 
27
- export function assertRemovableAxisComponent(program, channel, component, operation) {
28
- if (!hasAxisComponent(program, channel, component)) {
27
+ export function assertRemovableAxisComponent(program, channel, component, operation, axisId = channel) {
28
+ if (!hasAxisComponent(program, channel, component, axisId)) {
29
29
  throw new Error(`${operation}.${component} requires an existing ${channel}-axis ${component}.`);
30
30
  }
31
31
  }
32
32
 
33
- export function removeAxisComponent(program, channel, component) {
34
- const id = axisComponentId(channel, component);
33
+ export function removeAxisComponent(program, channel, component, axisId = channel) {
34
+ const id = axisComponentId(channel, component, axisId);
35
35
  let next = program;
36
- if (component === "title" && next.semanticSpec.guides.axis?.[channel]?.title !== undefined) {
37
- next = next.editSemantic({ property: `guide.axis.${channel}.title`, remove: true });
36
+ if (component === "title" && next.semanticSpec.guides.axis?.[axisId]?.title !== undefined) {
37
+ next = next.editSemantic({ property: `guide.axis.${axisId}.title`, remove: true });
38
38
  }
39
39
  if (next.graphicSpec.objects[id] !== undefined) {
40
40
  next = next.editGraphics({ target: id, remove: true });
41
41
  }
42
- return next._withoutMaterializationConfig(["guides", "axis", channel, component]);
42
+ return next._withoutMaterializationConfig(["guides", "axis", axisId, component]);
43
43
  }
@@ -1,7 +1,9 @@
1
+ import { selectCartesianAxis, axisTargetArgs, assertCartesianAxisSideAvailable } from "../../../selectors/axes.js";
2
+ import { validateAxisPosition } from "./policy.js";
1
3
  import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
2
4
  import { action } from "../../../core/action.js";
3
5
  import { isPlainObject } from "../../../core/immutable.js";
4
- import { validateKeys, validateOptionObject } from "../../../core/validation.js";
6
+ import { validateKeys, validateOptionObject, validateNonNegativeFinite } from "../../../core/validation.js";
5
7
 
6
8
  import {
7
9
  AXIS_COMPONENTS,
@@ -12,21 +14,21 @@ import {
12
14
  } from "./components.js";
13
15
 
14
16
  const OPTIONS = Object.freeze([
15
- "position", "line", "ticks", "labels", "ticksAndLabels", "title"
17
+ "target", "position", "line", "ticks", "labels", "ticksAndLabels", "title", "offset"
16
18
  ]);
17
19
  const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
18
20
  const TICK_OPTIONS = Object.freeze([
19
- "count", "values", "length", "color", "lineWidth"
21
+ "count", "values", "length", "color", "lineWidth", "alongOffset", "strokeDash", "strokeDashMap"
20
22
  ]);
21
23
  const LABEL_OPTIONS = Object.freeze([
22
24
  "count", "values", "offset", "format", "color", "fontSize",
23
25
  "fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
24
- "lineHeight", "overlap"
26
+ "lineHeight", "overlap", "alongOffset", "labelMap", "flush", "colorMap", "align"
25
27
  ]);
26
28
  const GROUP_OPTIONS = Object.freeze(["count", "values", "ticks", "labels"]);
27
29
  const TITLE_OPTIONS = Object.freeze([
28
30
  "text", "at", "offset", "rotation", "color", "fontSize",
29
- "fontFamily", "fontWeight"
31
+ "fontFamily", "fontWeight", "alongOffset", "align", "baseline"
30
32
  ]);
31
33
 
32
34
  function validateNested(args, key, options, operation, { allowFalse = false } = {}) {
@@ -40,6 +42,7 @@ function validateNested(args, key, options, operation, { allowFalse = false } =
40
42
  }
41
43
 
42
44
  function validateArgs(args, operation) {
45
+ if (Object.hasOwn(args, "offset")) validateNonNegativeFinite(args.offset, "Axis frame offset");
43
46
  validateOptionObject(args, OPTIONS, operation, {
44
47
  allowEmpty: false,
45
48
  emptyMessage: `${operation} requires at least one axis change.`,
@@ -59,12 +62,12 @@ function validateArgs(args, operation) {
59
62
  );
60
63
  }
61
64
  if (args.ticksAndLabels !== false && args.ticksAndLabels?.ticks !== undefined) {
62
- validateNested(args.ticksAndLabels, "ticks", ["length", "color", "lineWidth"], `${operation}.ticksAndLabels`);
65
+ validateNested(args.ticksAndLabels, "ticks", ["length", "color", "lineWidth", "alongOffset", "strokeDash", "strokeDashMap"], `${operation}.ticksAndLabels`);
63
66
  }
64
67
  if (args.ticksAndLabels !== false && args.ticksAndLabels?.labels !== undefined) {
65
68
  validateNested(args.ticksAndLabels, "labels", [
66
69
  "offset", "format", "color", "fontSize", "fontFamily", "fontWeight",
67
- "rotation", "maxWidth", "wrap", "lineHeight", "overlap"
70
+ "rotation", "maxWidth", "wrap", "lineHeight", "overlap", "alongOffset", "labelMap", "flush", "colorMap", "align"
68
71
  ], `${operation}.ticksAndLabels`);
69
72
  }
70
73
  }
@@ -82,27 +85,26 @@ function names(channel) {
82
85
  };
83
86
  }
84
87
 
85
- function buildPlan(program, channel, args, operation) {
86
- const shared = Object.hasOwn(args, "position")
87
- ? { position: args.position }
88
- : {};
88
+ function buildPlan(program, channel, args, operation, axisId = channel) {
89
+ const shared = { ...axisTargetArgs(axisId, channel),
90
+ ...(Object.hasOwn(args, "position") ? { position: args.position } : {}) };
89
91
  const plan = [];
90
92
  const addLeaf = (component, edit) => {
91
93
  const value = args[component];
92
94
  if (value === false) {
93
- assertRemovable(program, channel, component, operation.operation);
95
+ assertRemovable(program, channel, component, operation.operation, axisId);
94
96
  plan.push({ kind: "remove", component });
95
97
  } else if (value !== undefined) {
96
98
  plan.push({ kind: "edit", operation: edit, args: { ...shared, ...value } });
97
- } else if (args.position !== undefined && hasComponent(program, channel, component)) {
99
+ } else if ((args.position !== undefined || Object.hasOwn(args, "offset")) && hasComponent(program, channel, component, axisId)) {
98
100
  plan.push({ kind: "edit", operation: edit, args: shared });
99
101
  }
100
102
  };
101
103
 
102
104
  addLeaf("line", operation.line);
103
105
  if (args.ticksAndLabels === false) {
104
- assertRemovable(program, channel, "ticks", operation.operation);
105
- assertRemovable(program, channel, "labels", operation.operation);
106
+ assertRemovable(program, channel, "ticks", operation.operation, axisId);
107
+ assertRemovable(program, channel, "labels", operation.operation, axisId);
106
108
  plan.push(
107
109
  { kind: "remove", component: "ticks" },
108
110
  { kind: "remove", component: "labels" }
@@ -125,22 +127,22 @@ function buildPlan(program, channel, args, operation) {
125
127
  return plan;
126
128
  }
127
129
 
128
- function applyPlan(program, channel, plan, operation) {
130
+ function applyPlan(program, channel, plan, operation, axisId = channel) {
129
131
  let next = program;
130
132
  for (const step of plan) {
131
133
  next = step.kind === "remove"
132
- ? removeComponent(next, channel, step.component)
134
+ ? removeComponent(next, channel, step.component, axisId)
133
135
  : next[step.operation](step.args);
134
136
  }
135
137
 
136
138
  const hasRetainedGraphic = AXIS_COMPONENTS.some(
137
- component => next.graphicSpec.objects[graphicId(channel, component)] !== undefined
139
+ component => next.graphicSpec.objects[graphicId(channel, component, axisId)] !== undefined
138
140
  );
139
141
  if (!hasRetainedGraphic && (
140
- next.semanticSpec.guides.axis?.[channel] !== undefined ||
141
- next.guideConfigs.axis?.[channel] !== undefined
142
+ next.semanticSpec.guides.axis?.[axisId] !== undefined ||
143
+ next.guideConfigs.axis?.[axisId] !== undefined
142
144
  )) {
143
- next = next[operation.remove]();
145
+ next = next[operation.remove](axisId === channel ? undefined : axisTargetArgs(axisId, channel));
144
146
  }
145
147
  return next;
146
148
  }
@@ -154,12 +156,22 @@ function makeEditAxis(channel) {
154
156
  },
155
157
  withGuideLayoutValidation(function (args = {}) {
156
158
  validateArgs(args, operation.operation);
157
- const plan = buildPlan(this, channel, args, operation);
159
+ const selected = selectCartesianAxis(this, channel, args, { whole: true });
160
+ const axisId = selected.id; args = selected.options;
161
+ let source = this;
162
+ if (selected.owner?.named && args.position !== undefined) {
163
+ const position = validateAxisPosition(channel, args.position);
164
+ assertCartesianAxisSideAvailable(this, { id: axisId, channel,
165
+ coordinate: selected.owner.coordinate, position });
166
+ source = source.editSemantic({ property: `guide.axis.${axisId}.position`, value: position });
167
+ }
168
+ if (Object.hasOwn(args, "offset")) source = source._withGuideConfig(axisId, "frame", { offset: args.offset });
169
+ const plan = buildPlan(source, channel, args, operation, axisId);
158
170
 
159
171
  // Run the complete proposal against an immutable speculative branch so a
160
172
  // later leaf failure cannot begin the returned action trace or state.
161
- applyPlan(this, channel, plan, operation);
162
- return applyPlan(this, channel, plan, operation);
173
+ applyPlan(source, channel, plan, operation, axisId);
174
+ return applyPlan(source, channel, plan, operation, axisId);
163
175
  })
164
176
  );
165
177
  }