ggaction 0.0.2 → 0.0.4

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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -1,10 +1,13 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { isPlainObject } from "../../../core/immutable.js";
3
3
  import { validateUserId } from "../../../core/identifiers.js";
4
- import { validateKeys } from "../../../core/validation.js";
4
+ import { validateKeys, validateOptionObject } from "../../../core/validation.js";
5
5
  import { findCoordinate } from "../../../selectors/coordinates.js";
6
+ import { resolvePolarGuideResources } from "../polar/resolve.js";
6
7
 
7
- const TOP_OPTIONS = Object.freeze(["coordinate", "x", "y"]);
8
+ const TOP_OPTIONS = Object.freeze([
9
+ "coordinate", "x", "y", "theta", "radius"
10
+ ]);
8
11
  const COORDINATE_OPTIONS = Object.freeze(["id", "type"]);
9
12
  const COORDINATE_API_TYPES = new Set(["auto", "cartesian", "polar"]);
10
13
 
@@ -15,13 +18,11 @@ function validateAxisOption(value, channel) {
15
18
  }
16
19
 
17
20
  function validateArgs(args) {
18
- if (!isPlainObject(args)) {
19
- throw new TypeError("createAxes options must be a plain object.");
20
- }
21
-
22
- validateKeys(args, TOP_OPTIONS, "createAxes");
21
+ validateOptionObject(args, TOP_OPTIONS, "createAxes");
23
22
  validateAxisOption(args.x, "x");
24
23
  validateAxisOption(args.y, "y");
24
+ validateAxisOption(args.theta, "theta");
25
+ validateAxisOption(args.radius, "radius");
25
26
 
26
27
  const coordinate = args.coordinate ?? {};
27
28
  if (!isPlainObject(coordinate)) {
@@ -60,14 +61,10 @@ function inspectChannels(layers) {
60
61
  );
61
62
  }
62
63
 
63
- return { cartesianLayers, hasPolar: polarLayers.length > 0 };
64
+ return { cartesianLayers, polarLayers };
64
65
  }
65
66
 
66
- function resolveCoordinate(program, descriptor, cartesianLayers, hasPolar) {
67
- if (hasPolar && cartesianLayers.length === 0) {
68
- throw new Error("createAxes does not yet support Polar axes.");
69
- }
70
-
67
+ function resolveCoordinate(program, descriptor, cartesianLayers) {
71
68
  if (cartesianLayers.length === 0) {
72
69
  throw new Error("createAxes requires an x or y encoding.");
73
70
  }
@@ -124,6 +121,24 @@ function resolveCoordinate(program, descriptor, cartesianLayers, hasPolar) {
124
121
  return { id, layers };
125
122
  }
126
123
 
124
+ function resolvePolarAxisArgs(program, layers, channel, option, descriptor) {
125
+ const selected = option === false
126
+ ? false
127
+ : option !== undefined || hasChannel(layers, channel);
128
+ if (!selected) return undefined;
129
+ const args = option ?? {};
130
+ const kind = channel === "theta" ? "theta" : "radius";
131
+ const resource = resolvePolarGuideResources(program, kind, {
132
+ ...args,
133
+ ...(descriptor.id === undefined ? {} : { coordinate: descriptor.id })
134
+ }, "createAxes");
135
+ return {
136
+ ...args,
137
+ scale: resource.scale,
138
+ coordinate: resource.coordinate
139
+ };
140
+ }
141
+
127
142
  function hasChannel(layers, channel) {
128
143
  return layers.some(layer => layer.encoding?.[channel] !== undefined);
129
144
  }
@@ -170,14 +185,53 @@ const createAxes = action(
170
185
  },
171
186
  function (args = {}) {
172
187
  const coordinateDescriptor = validateArgs(args);
173
- const { cartesianLayers, hasPolar } = inspectChannels(
188
+ const { cartesianLayers, polarLayers } = inspectChannels(
174
189
  this.semanticSpec.layers
175
190
  );
191
+ const requestedCoordinateType = coordinateDescriptor.id === undefined
192
+ ? undefined
193
+ : findCoordinate(this, coordinateDescriptor.id)?.type;
194
+ const explicitlyPolar = coordinateDescriptor.type === "polar" ||
195
+ requestedCoordinateType === "polar" ||
196
+ args.theta !== undefined || args.radius !== undefined;
197
+ if (polarLayers.length > 0 &&
198
+ (cartesianLayers.length === 0 || explicitlyPolar)) {
199
+ if (coordinateDescriptor.type === "cartesian") {
200
+ throw new Error("createAxes coordinate type conflicts with Polar encodings.");
201
+ }
202
+ if (args.x !== undefined || args.y !== undefined) {
203
+ throw new Error("createAxes x/y options require Cartesian encodings.");
204
+ }
205
+ const theta = resolvePolarAxisArgs(
206
+ this,
207
+ polarLayers,
208
+ "theta",
209
+ args.theta,
210
+ coordinateDescriptor
211
+ );
212
+ const radius = resolvePolarAxisArgs(
213
+ this,
214
+ polarLayers,
215
+ "radius",
216
+ args.radius,
217
+ coordinateDescriptor
218
+ );
219
+ if (theta === undefined && radius === undefined) {
220
+ throw new Error("createAxes requires at least one selected axis.");
221
+ }
222
+ let polarProgram = this;
223
+ if (theta !== undefined) {
224
+ polarProgram = polarProgram.createThetaAxis(theta);
225
+ }
226
+ if (radius !== undefined) {
227
+ polarProgram = polarProgram.createRadialAxis(radius);
228
+ }
229
+ return polarProgram;
230
+ }
176
231
  const coordinate = resolveCoordinate(
177
232
  this,
178
233
  coordinateDescriptor,
179
- cartesianLayers,
180
- hasPolar
234
+ cartesianLayers
181
235
  );
182
236
  const x = resolveAxisArgs(coordinate.layers, "x", args.x);
183
237
  const y = resolveAxisArgs(coordinate.layers, "y", args.y);
@@ -0,0 +1,126 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { isPlainObject } from "../../../core/immutable.js";
3
+ import { validateKeys, validateOptionObject } from "../../../core/validation.js";
4
+
5
+ const OPTIONS = Object.freeze([
6
+ "position", "line", "ticks", "labels", "ticksAndLabels", "title"
7
+ ]);
8
+ const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
9
+ const TICK_OPTIONS = Object.freeze([
10
+ "count", "values", "length", "color", "lineWidth"
11
+ ]);
12
+ const LABEL_OPTIONS = Object.freeze([
13
+ "count", "values", "offset", "format", "color", "fontSize",
14
+ "fontFamily", "fontWeight"
15
+ ]);
16
+ const GROUP_OPTIONS = Object.freeze(["count", "values", "ticks", "labels"]);
17
+ const TITLE_OPTIONS = Object.freeze([
18
+ "text", "at", "offset", "rotation", "color", "fontSize",
19
+ "fontFamily", "fontWeight"
20
+ ]);
21
+
22
+ function validateNested(args, key, options, operation) {
23
+ if (!Object.hasOwn(args, key)) return;
24
+ if (!isPlainObject(args[key])) {
25
+ throw new TypeError(`${operation}.${key} must be a plain object.`);
26
+ }
27
+ validateKeys(args[key], options, `${operation}.${key}`);
28
+ }
29
+
30
+ function validateArgs(args, operation) {
31
+ validateOptionObject(args, OPTIONS, operation, {
32
+ allowEmpty: false,
33
+ emptyMessage: `${operation} requires at least one axis change.`,
34
+ emptyError: Error
35
+ });
36
+ validateNested(args, "line", LINE_OPTIONS, operation);
37
+ validateNested(args, "ticks", TICK_OPTIONS, operation);
38
+ validateNested(args, "labels", LABEL_OPTIONS, operation);
39
+ validateNested(args, "ticksAndLabels", GROUP_OPTIONS, operation);
40
+ validateNested(args, "title", TITLE_OPTIONS, operation);
41
+ if (args.ticksAndLabels !== undefined && (
42
+ args.ticks !== undefined || args.labels !== undefined
43
+ )) {
44
+ throw new Error(
45
+ `${operation} cannot combine ticksAndLabels with ticks or labels.`
46
+ );
47
+ }
48
+ if (args.ticksAndLabels?.ticks !== undefined) {
49
+ validateNested(args.ticksAndLabels, "ticks", ["length", "color", "lineWidth"], `${operation}.ticksAndLabels`);
50
+ }
51
+ if (args.ticksAndLabels?.labels !== undefined) {
52
+ validateNested(args.ticksAndLabels, "labels", [
53
+ "offset", "format", "color", "fontSize", "fontFamily", "fontWeight"
54
+ ], `${operation}.ticksAndLabels`);
55
+ }
56
+ }
57
+
58
+ function names(channel) {
59
+ const prefix = channel === "x" ? "X" : "Y";
60
+ return {
61
+ operation: `edit${prefix}Axis`,
62
+ line: `edit${prefix}AxisLine`,
63
+ ticks: `edit${prefix}AxisTicks`,
64
+ labels: `edit${prefix}AxisLabels`,
65
+ group: `edit${prefix}AxisTicksAndLabels`,
66
+ title: `edit${prefix}AxisTitle`
67
+ };
68
+ }
69
+
70
+ function makeEditAxis(channel) {
71
+ const operation = names(channel);
72
+ return action(
73
+ {
74
+ op: operation.operation,
75
+ description: `Edit selected existing ${channel}-axis components.`
76
+ },
77
+ function (args = {}) {
78
+ validateArgs(args, operation.operation);
79
+ const shared = Object.hasOwn(args, "position")
80
+ ? { position: args.position }
81
+ : {};
82
+ const has = id => this.graphicSpec.objects[id] !== undefined;
83
+ let next = this;
84
+
85
+ if (args.line !== undefined || (args.position !== undefined && has(`${channel}AxisLine`))) {
86
+ next = next[operation.line]({ ...shared, ...(args.line ?? {}) });
87
+ }
88
+
89
+ if (args.ticksAndLabels !== undefined) {
90
+ next = next[operation.group]({
91
+ ...shared,
92
+ ...args.ticksAndLabels
93
+ });
94
+ } else if (args.ticks !== undefined || args.labels !== undefined) {
95
+ if (args.ticks !== undefined) {
96
+ next = next[operation.ticks]({ ...shared, ...args.ticks });
97
+ }
98
+ if (args.labels !== undefined) {
99
+ next = next[operation.labels]({ ...shared, ...args.labels });
100
+ }
101
+ } else if (args.position !== undefined) {
102
+ const hasTicks = has(`${channel}AxisTicks`);
103
+ const hasLabels = has(`${channel}AxisLabels`);
104
+ if (hasTicks && hasLabels) {
105
+ next = next[operation.group](shared);
106
+ } else {
107
+ if (hasTicks) next = next[operation.ticks](shared);
108
+ if (hasLabels) next = next[operation.labels](shared);
109
+ }
110
+ }
111
+
112
+ if (args.title !== undefined || (args.position !== undefined && has(`${channel}AxisTitle`))) {
113
+ next = next[operation.title]({ ...shared, ...(args.title ?? {}) });
114
+ }
115
+ return next;
116
+ }
117
+ );
118
+ }
119
+
120
+ const editXAxis = makeEditAxis("x");
121
+ const editYAxis = makeEditAxis("y");
122
+
123
+ export function registerCompleteAxisEditActions(ProgramClass) {
124
+ ProgramClass.prototype.editXAxis = editXAxis;
125
+ ProgramClass.prototype.editYAxis = editYAxis;
126
+ }
@@ -5,6 +5,13 @@ import { registerAxisTickGroupActions } from "./tickGroups.js";
5
5
  import { registerAxisTickActions } from "./ticks.js";
6
6
  import { registerAxisTitleActions } from "./titles.js";
7
7
  import { registerAxisActions } from "./axis.js";
8
+ import { registerCompleteAxisEditActions } from "./edit.js";
9
+ import {
10
+ removeRadialAxis,
11
+ removeThetaAxis,
12
+ removeXAxis,
13
+ removeYAxis
14
+ } from "./remove.js";
8
15
 
9
16
  export function registerGuideAxisActions(ProgramClass) {
10
17
  registerAxisLineActions(ProgramClass);
@@ -14,4 +21,9 @@ export function registerGuideAxisActions(ProgramClass) {
14
21
  registerAxisTitleActions(ProgramClass);
15
22
  registerAxisActions(ProgramClass);
16
23
  registerAxisCollectionActions(ProgramClass);
24
+ registerCompleteAxisEditActions(ProgramClass);
25
+ ProgramClass.prototype.removeXAxis = removeXAxis;
26
+ ProgramClass.prototype.removeYAxis = removeYAxis;
27
+ ProgramClass.prototype.removeThetaAxis = removeThetaAxis;
28
+ ProgramClass.prototype.removeRadialAxis = removeRadialAxis;
17
29
  }
@@ -2,6 +2,7 @@ import { action } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
3
  import {
4
4
  sameOrderedValues,
5
+ validateOptionObject,
5
6
  validateNonEmptyString,
6
7
  validateNonNegativeFinite,
7
8
  validatePositiveFinite
@@ -12,8 +13,8 @@ import {
12
13
  mapContinuousScaleValues,
13
14
  formatTransformedTick,
14
15
  mapOrdinalPositionValues
15
- } from "../../../grammar/scales.js";
16
- import { formatTimeTick } from "../../../grammar/ticks.js";
16
+ } from "../../../grammar/scales/index.js";
17
+ import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
17
18
  import { valuesFromTickConfig } from "../tickValues.js";
18
19
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
19
20
  "../../../theme/defaults.js";
@@ -24,7 +25,7 @@ import {
24
25
  validateAxisFormat,
25
26
  validateAxisPosition
26
27
  } from "./policy.js";
27
- import { resolvePlotGraphicPlacement } from
28
+ import { findCanvasGraphic, resolvePlotGraphicPlacement } from
28
29
  "../../../materialization/graphicHierarchy.js";
29
30
 
30
31
  const OPTIONS = Object.freeze([
@@ -41,11 +42,11 @@ const DEFAULTS = Object.freeze({
41
42
  });
42
43
 
43
44
  function validateOptions(args, operation, create) {
44
- for (const key of Object.keys(args)) {
45
- if (!OPTIONS.includes(key) || (!create && key === "scale")) {
46
- throw new Error(`Unknown ${operation} option "${key}".`);
47
- }
48
- }
45
+ validateOptionObject(
46
+ args,
47
+ create ? OPTIONS : OPTIONS.filter(key => key !== "scale"),
48
+ operation
49
+ );
49
50
  if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
50
51
  throw new Error(`${operation} cannot use count and values together.`);
51
52
  }
@@ -97,16 +98,18 @@ function resolve(program, channel, config) {
97
98
  const positions = discrete
98
99
  ? mapOrdinalPositionValues(values, scale)
99
100
  : mapContinuousScaleValues(values, scale);
100
- const text = values.map(value => formatAxisValue(
101
- value,
102
- scale.type,
103
- config.format,
104
- item => scale.type === "time"
105
- ? formatTimeTick(item, scale.domain)
106
- : isTransformedScaleType(scale.type)
107
- ? formatTransformedTick(scale.type, item)
108
- : String(item)
109
- ));
101
+ const text = scale.type === "time" && config.format === "auto"
102
+ ? formatTimeTicks(values, scale.domain)
103
+ : values.map(value => formatAxisValue(
104
+ value,
105
+ scale.type,
106
+ config.format,
107
+ item => scale.type === "time"
108
+ ? formatTimeTick(item, scale.domain)
109
+ : isTransformedScaleType(scale.type)
110
+ ? formatTransformedTick(scale.type, item)
111
+ : String(item)
112
+ ));
110
113
  const resolved = {
111
114
  values,
112
115
  text,
@@ -118,7 +121,7 @@ function resolve(program, channel, config) {
118
121
  offset: config.offset
119
122
  })
120
123
  };
121
- const canvas = program.graphicSpec.objects.canvas?.properties;
124
+ const canvas = findCanvasGraphic(program)?.properties;
122
125
  const maximumTextWidth = Math.max(
123
126
  0,
124
127
  ...text.map(value => [...value].length * config.fontSize * 0.6)
@@ -0,0 +1,60 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateKeys } from "../../../core/validation.js";
3
+ import { axisGraphicIds } from
4
+ "../../../materialization/guides/resources.js";
5
+
6
+ const OPTIONS = Object.freeze(["coordinate", "scale"]);
7
+
8
+ function makeRemoveAxis(channel) {
9
+ const prefix = channel === "x"
10
+ ? "X"
11
+ : channel === "y"
12
+ ? "Y"
13
+ : channel === "theta"
14
+ ? "Theta"
15
+ : "Radial";
16
+ const operation = `remove${prefix}Axis`;
17
+ const graphicIds = axisGraphicIds(channel);
18
+ return action(
19
+ {
20
+ op: operation,
21
+ description: `Remove the complete ${channel}-axis resource.`
22
+ },
23
+ function (args = {}) {
24
+ validateKeys(args, OPTIONS, operation);
25
+ const semantic = this.semanticSpec.guides.axis?.[channel];
26
+ const config = this.guideConfigs.axis?.[channel];
27
+ const hasGraphic = graphicIds.some(
28
+ id => this.graphicSpec.objects[id] !== undefined
29
+ );
30
+ if (semantic === undefined && config === undefined && !hasGraphic) {
31
+ throw new Error(`${operation} requires an existing ${channel}-axis.`);
32
+ }
33
+ if (args.scale !== undefined && semantic?.scale !== args.scale) {
34
+ throw new Error(`${operation} found no axis for scale "${args.scale}".`);
35
+ }
36
+ if (args.coordinate !== undefined && semantic?.coordinate !== args.coordinate) {
37
+ throw new Error(
38
+ `${operation} found no axis for coordinate "${args.coordinate}".`
39
+ );
40
+ }
41
+ let next = semantic === undefined
42
+ ? this
43
+ : this.editSemantic({
44
+ property: `guide.axis.${channel}`,
45
+ remove: true
46
+ });
47
+ for (const id of graphicIds) {
48
+ if (next.graphicSpec.objects[id] !== undefined) {
49
+ next = next.editGraphics({ target: id, remove: true });
50
+ }
51
+ }
52
+ return next._withoutMaterializationConfig(["guides", "axis", channel]);
53
+ }
54
+ );
55
+ }
56
+
57
+ export const removeXAxis = makeRemoveAxis("x");
58
+ export const removeYAxis = makeRemoveAxis("y");
59
+ export const removeThetaAxis = makeRemoveAxis("theta");
60
+ export const removeRadialAxis = makeRemoveAxis("radius");
@@ -1,6 +1,6 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { isPlainObject } from "../../../core/immutable.js";
3
- import { validateKeys } from "../../../core/validation.js";
3
+ import { validateKeys, validateOptionObject } from "../../../core/validation.js";
4
4
 
5
5
  const CREATE_OPTIONS = Object.freeze([
6
6
  "scale",
@@ -38,7 +38,12 @@ function validateNested(value, supported, label) {
38
38
  }
39
39
 
40
40
  function validateArgs(args, operation, create) {
41
- validateKeys(args, create ? CREATE_OPTIONS : EDIT_OPTIONS, operation);
41
+ validateOptionObject(
42
+ args,
43
+ create ? CREATE_OPTIONS : EDIT_OPTIONS,
44
+ operation,
45
+ { allowEmpty: create }
46
+ );
42
47
 
43
48
  if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
44
49
  throw new Error(`${operation} cannot use count and values together.`);
@@ -52,9 +57,6 @@ function validateArgs(args, operation, create) {
52
57
  validateNested(args.labels, LABEL_OPTIONS, `${operation}.labels`);
53
58
  }
54
59
 
55
- if (!create && Object.keys(args).length === 0) {
56
- throw new TypeError(`${operation} requires at least one option.`);
57
- }
58
60
  }
59
61
 
60
62
  function select(source, keys) {
@@ -2,14 +2,15 @@ import { action } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
3
  import {
4
4
  validateNonEmptyString,
5
- validateNonNegativeFinite
5
+ validateNonNegativeFinite,
6
+ validateOptionObject
6
7
  } from "../../../core/validation.js";
7
8
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
8
9
  import {
9
10
  isTransformedScaleType,
10
11
  mapContinuousScaleValues,
11
12
  mapOrdinalPositionValues
12
- } from "../../../grammar/scales.js";
13
+ } from "../../../grammar/scales/index.js";
13
14
  import {
14
15
  DEFAULT_TICK_COUNT,
15
16
  inferHistogramBoundaries,
@@ -21,7 +22,7 @@ import {
21
22
  resolveAxisTickGeometry,
22
23
  validateAxisPosition
23
24
  } from "./policy.js";
24
- import { resolvePlotGraphicPlacement } from
25
+ import { findCanvasGraphic, resolvePlotGraphicPlacement } from
25
26
  "../../../materialization/graphicHierarchy.js";
26
27
 
27
28
  const OPTIONS = Object.freeze(["scale", "position", "count", "values", "length", "color", "lineWidth"]);
@@ -33,7 +34,11 @@ const DEFAULTS = Object.freeze({
33
34
  });
34
35
 
35
36
  function validateOptions(args, operation, create) {
36
- for (const key of Object.keys(args)) if (!OPTIONS.includes(key) || (!create && key === "scale")) throw new Error(`Unknown ${operation} option "${key}".`);
37
+ validateOptionObject(
38
+ args,
39
+ create ? OPTIONS : OPTIONS.filter(key => key !== "scale"),
40
+ operation
41
+ );
37
42
  if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) throw new Error(`${operation} cannot use count and values together.`);
38
43
  }
39
44
 
@@ -84,7 +89,7 @@ function geometry(program, channel, config) {
84
89
  length: config.length
85
90
  })
86
91
  };
87
- const canvas = program.graphicSpec.objects.canvas?.properties;
92
+ const canvas = findCanvasGraphic(program)?.properties;
88
93
  const coordinates = channel === "x"
89
94
  ? [...resolved.x1, ...resolved.x2, resolved.y1, resolved.y2]
90
95
  : [resolved.x1, resolved.x2, ...resolved.y1, ...resolved.y2];
@@ -11,7 +11,7 @@ import {
11
11
  isTransformedScaleType,
12
12
  mapContinuousScaleValues,
13
13
  mapOrdinalPositionValues
14
- } from "../../../grammar/scales.js";
14
+ } from "../../../grammar/scales/index.js";
15
15
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
16
16
  "../../../theme/defaults.js";
17
17
  import { findDataset } from "../../../selectors/datasets.js";
@@ -22,7 +22,7 @@ import {
22
22
  resolveAxisTitleGeometry,
23
23
  validateAxisPosition
24
24
  } from "./policy.js";
25
- import { resolvePlotGraphicPlacement } from
25
+ import { findCanvasGraphic, resolvePlotGraphicPlacement } from
26
26
  "../../../materialization/graphicHierarchy.js";
27
27
 
28
28
  const CREATE_OPTIONS = Object.freeze([
@@ -52,7 +52,7 @@ function validateConfig(channel, config) {
52
52
  if (typeof config.fontWeight !== "string" && !Number.isFinite(config.fontWeight)) throw new TypeError("Axis title fontWeight must be a string or number.");
53
53
  }
54
54
 
55
- function inferText(program, channel, scaleId) {
55
+ export function inferAxisTitleText(program, channel, scaleId) {
56
56
  const titles = new Set();
57
57
  const primaryTitles = new Set();
58
58
  for (const layer of program.semanticSpec.layers) {
@@ -138,7 +138,7 @@ function resolveGeometry(program, channel, config) {
138
138
  const sine = Math.sin(config.rotation);
139
139
  const extentX = Math.abs(cosine) * width / 2 + Math.abs(sine) * height / 2;
140
140
  const extentY = Math.abs(sine) * width / 2 + Math.abs(cosine) * height / 2;
141
- const canvas = program.graphicSpec.objects.canvas?.properties;
141
+ const canvas = findCanvasGraphic(program)?.properties;
142
142
  const newEdgeFits = config.position === "top"
143
143
  ? geometry.y - extentY >= 0
144
144
  : config.position === "right"
@@ -184,7 +184,7 @@ function makeEdit(channel) {
184
184
  const resolvedTitle = explicitText
185
185
  ? validateText(text)
186
186
  : config.inferredText === true
187
- ? inferText(this, channel, config.scale)
187
+ ? inferAxisTitleText(this, channel, config.scale)
188
188
  : this.semanticSpec.guides.axis?.[channel]?.title;
189
189
  if (resolvedTitle !== this.semanticSpec.guides.axis?.[channel]?.title) {
190
190
  next = next.editSemantic({
@@ -218,7 +218,9 @@ function makeCreate(channel) {
218
218
  if (guideScale && guideScale !== scale) throw new Error(`${operation.create} conflicts with the existing axis scale.`);
219
219
  if (this.graphicSpec.objects[operation.graphic]) throw new Error(`${operation.create} requires a missing axis title.`);
220
220
  const inferredText = !Object.hasOwn(args, "text");
221
- const text = validateText(args.text ?? inferText(this, channel, scale));
221
+ const text = validateText(
222
+ args.text ?? inferAxisTitleText(this, channel, scale)
223
+ );
222
224
  const position = args.position ?? defaultAxisPosition(channel);
223
225
  const inferredRotation = !Object.hasOwn(args, "rotation");
224
226
  const config = {