ggaction 0.0.1

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 (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,144 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateKeys, validatePositiveFinite } from "../../core/validation.js";
3
+ import {
4
+ normalizeStrokeDashPattern,
5
+ validateOpacityValue
6
+ } from "../../grammar/scales.js";
7
+ import {
8
+ validateRuleStroke,
9
+ validateRuleStrokeWidth
10
+ } from "../../grammar/ruleAppearance.js";
11
+ import { findLayer } from "../../selectors/layers.js";
12
+ import { DEFAULT_COLORS } from "../../theme/defaults.js";
13
+ import { resolveErrorBar } from "./resolve.js";
14
+
15
+ const OPTIONS = Object.freeze([
16
+ "id",
17
+ "target",
18
+ "data",
19
+ "x",
20
+ "y",
21
+ "groupBy",
22
+ "coordinate",
23
+ "caps",
24
+ "capSize",
25
+ "stroke",
26
+ "strokeWidth",
27
+ "strokeDash",
28
+ "opacity"
29
+ ]);
30
+
31
+ function resolveAppearance(args) {
32
+ const caps = args.caps ?? true;
33
+ if (typeof caps !== "boolean") {
34
+ throw new TypeError("createErrorBar caps must be a boolean.");
35
+ }
36
+ const capSize = args.capSize ?? 8;
37
+ validatePositiveFinite(capSize, "createErrorBar capSize");
38
+ const stroke = validateRuleStroke(
39
+ args.stroke ?? DEFAULT_COLORS.mark,
40
+ "createErrorBar stroke"
41
+ );
42
+ const strokeWidth = validateRuleStrokeWidth(
43
+ args.strokeWidth ?? 1.5,
44
+ "createErrorBar strokeWidth"
45
+ );
46
+ const strokeDash = args.strokeDash ?? "solid";
47
+ normalizeStrokeDashPattern(strokeDash);
48
+ const opacity = validateOpacityValue(args.opacity ?? 1, "createErrorBar opacity");
49
+ return { caps, capSize, stroke, strokeWidth, strokeDash, opacity };
50
+ }
51
+
52
+ function positionArgs(resolved) {
53
+ return {
54
+ target: resolved.id,
55
+ field: resolved.position.field,
56
+ fieldType: resolved.position.fieldType,
57
+ coordinate: resolved.coordinate,
58
+ scale: resolved.position.scale
59
+ };
60
+ }
61
+
62
+ function intervalArgs(resolved, field) {
63
+ return {
64
+ target: resolved.id,
65
+ field,
66
+ fieldType: "quantitative",
67
+ coordinate: resolved.coordinate,
68
+ scale: resolved.interval.scale
69
+ };
70
+ }
71
+
72
+ export const createErrorBar = action(
73
+ {
74
+ op: "createErrorBar",
75
+ description: "Create a statistical or explicit vertical or horizontal interval."
76
+ },
77
+ function (args = {}) {
78
+ validateKeys(args, OPTIONS, "createErrorBar");
79
+ const resolved = resolveErrorBar(this, args);
80
+ const appearance = resolveAppearance(args);
81
+ let next = this;
82
+
83
+ if (resolved.interval.mode === "statistical") {
84
+ next = next.createIntervalData({
85
+ id: resolved.dataId,
86
+ source: resolved.source,
87
+ field: resolved.interval.field,
88
+ groupBy: resolved.groupBy,
89
+ center: resolved.interval.center,
90
+ extent: resolved.interval.extent,
91
+ level: resolved.interval.level,
92
+ as: resolved.fields
93
+ });
94
+ }
95
+
96
+ next = next.createRuleMark({ id: resolved.id, data: resolved.dataId });
97
+ const positionAction = resolved.position.channel === "x" ? "encodeX" : "encodeY";
98
+ const intervalAction = resolved.interval.channel === "x" ? "encodeX" : "encodeY";
99
+ const secondaryAction = resolved.interval.channel === "x" ? "encodeX2" : "encodeY2";
100
+ next = next[positionAction](positionArgs(resolved));
101
+ next = next[intervalAction](intervalArgs(resolved, resolved.fields.lower));
102
+ if (resolved.interval.mode === "explicit") {
103
+ next = next.editSemantic({
104
+ property: `layer[${resolved.id}].encoding.${resolved.interval.channel}.title`,
105
+ value: resolved.interval.title
106
+ });
107
+ }
108
+ next = next[secondaryAction]({
109
+ target: resolved.id,
110
+ field: resolved.fields.upper,
111
+ fieldType: "quantitative"
112
+ })
113
+ .encodeStroke({ target: resolved.id, value: appearance.stroke })
114
+ .encodeStrokeWidth({ target: resolved.id, value: appearance.strokeWidth })
115
+ .encodeStrokeDash({ target: resolved.id, value: appearance.strokeDash })
116
+ .encodeOpacity({ target: resolved.id, value: appearance.opacity });
117
+
118
+ if (!appearance.caps) return next;
119
+
120
+ const intervalLayer = findLayer(next, resolved.id);
121
+ for (const [id, field] of [
122
+ [resolved.lowerCapId, resolved.fields.lower],
123
+ [resolved.upperCapId, resolved.fields.upper]
124
+ ]) {
125
+ next = next.createErrorBarCap({
126
+ id,
127
+ data: resolved.dataId,
128
+ orientation: resolved.orientation,
129
+ positionField: resolved.position.field,
130
+ positionFieldType: resolved.position.fieldType,
131
+ intervalField: field,
132
+ coordinate: resolved.coordinate,
133
+ positionScale: intervalLayer.encoding[resolved.position.channel].scale,
134
+ intervalScale: intervalLayer.encoding[resolved.interval.channel].scale,
135
+ capSize: appearance.capSize,
136
+ stroke: appearance.stroke,
137
+ strokeWidth: appearance.strokeWidth,
138
+ strokeDash: appearance.strokeDash,
139
+ opacity: appearance.opacity
140
+ });
141
+ }
142
+ return next;
143
+ }
144
+ );
@@ -0,0 +1,7 @@
1
+ import { createErrorBarCap } from "./components.js";
2
+ import { createErrorBar } from "./create.js";
3
+
4
+ export function registerErrorBarActions(ProgramClass) {
5
+ ProgramClass.prototype.createErrorBarCap = createErrorBarCap;
6
+ ProgramClass.prototype.createErrorBar = createErrorBar;
7
+ }
@@ -0,0 +1,25 @@
1
+ import { resolveIntervalComposite } from "../intervals/resolve.js";
2
+
3
+ const ERROR_BAR_POLICY = Object.freeze({
4
+ operation: "createErrorBar",
5
+ resourceLabel: "error-bar",
6
+ defaultId: "errorBar",
7
+ ownerLabel: "Error-bar id",
8
+ positionTypes: Object.freeze(["nominal", "ordinal", "temporal"]),
9
+ defaultPositionType: "nominal",
10
+ defaultIntervalChannel: "y",
11
+ scaleDefaults: () => ({}),
12
+ intervalScaleDefaults: Object.freeze({ nice: true, zero: false }),
13
+ allowExplicitGrouping: false,
14
+ ambiguousMessage:
15
+ "createErrorBar requires one quantitative interval axis and one nominal, ordinal, or temporal position axis."
16
+ });
17
+
18
+ export function resolveErrorBar(program, args) {
19
+ const resolved = resolveIntervalComposite(program, args, ERROR_BAR_POLICY);
20
+ return {
21
+ ...resolved,
22
+ lowerCapId: `${resolved.id}LowerCap`,
23
+ upperCapId: `${resolved.id}UpperCap`
24
+ };
25
+ }
@@ -0,0 +1,204 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { isPlainObject } from "../../../core/immutable.js";
3
+ import { validateUserId } from "../../../core/identifiers.js";
4
+ import { validateKeys } from "../../../core/validation.js";
5
+ import { findCoordinate } from "../../../selectors/coordinates.js";
6
+
7
+ const TOP_OPTIONS = Object.freeze(["coordinate", "x", "y"]);
8
+ const COORDINATE_OPTIONS = Object.freeze(["id", "type"]);
9
+ const COORDINATE_API_TYPES = new Set(["auto", "cartesian", "polar"]);
10
+
11
+ function validateAxisOption(value, channel) {
12
+ if (value !== undefined && value !== false && !isPlainObject(value)) {
13
+ throw new TypeError(`createAxes ${channel} must be false or a plain object.`);
14
+ }
15
+ }
16
+
17
+ 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");
23
+ validateAxisOption(args.x, "x");
24
+ validateAxisOption(args.y, "y");
25
+
26
+ const coordinate = args.coordinate ?? {};
27
+ if (!isPlainObject(coordinate)) {
28
+ throw new TypeError("createAxes coordinate must be a plain object.");
29
+ }
30
+
31
+ validateKeys(coordinate, COORDINATE_OPTIONS, "createAxes.coordinate");
32
+ const type = coordinate.type ?? "auto";
33
+
34
+ if (!COORDINATE_API_TYPES.has(type)) {
35
+ throw new Error(`Unknown createAxes coordinate type "${type}".`);
36
+ }
37
+
38
+ return coordinate;
39
+ }
40
+
41
+ function inspectChannels(layers) {
42
+ const cartesianLayers = layers.filter(
43
+ layer => layer.encoding?.x !== undefined || layer.encoding?.y !== undefined
44
+ );
45
+ const polarLayers = layers.filter(
46
+ layer =>
47
+ layer.encoding?.theta !== undefined || layer.encoding?.radius !== undefined
48
+ );
49
+ const hasMixedLayer = layers.some(layer => {
50
+ const hasCartesian =
51
+ layer.encoding?.x !== undefined || layer.encoding?.y !== undefined;
52
+ const hasPolar =
53
+ layer.encoding?.theta !== undefined || layer.encoding?.radius !== undefined;
54
+ return hasCartesian && hasPolar;
55
+ });
56
+
57
+ if (hasMixedLayer) {
58
+ throw new Error(
59
+ "createAxes cannot infer a coordinate from mixed Cartesian and Polar channels."
60
+ );
61
+ }
62
+
63
+ return { cartesianLayers, hasPolar: polarLayers.length > 0 };
64
+ }
65
+
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
+
71
+ if (cartesianLayers.length === 0) {
72
+ throw new Error("createAxes requires an x or y encoding.");
73
+ }
74
+
75
+ const referencedIds = [
76
+ ...new Set(
77
+ cartesianLayers
78
+ .map(layer => layer.coordinate)
79
+ .filter(id => id !== undefined)
80
+ )
81
+ ];
82
+
83
+ if (cartesianLayers.some(layer => layer.coordinate === undefined)) {
84
+ throw new Error(
85
+ "createAxes requires every positional layer to have a stored coordinate."
86
+ );
87
+ }
88
+
89
+ if (descriptor.id === undefined && referencedIds.length > 1) {
90
+ throw new Error(
91
+ "createAxes found multiple coordinates; provide coordinate.id explicitly."
92
+ );
93
+ }
94
+
95
+ const id = validateUserId(descriptor.id ?? referencedIds[0], "Coordinate id");
96
+ const existing = findCoordinate(program, id);
97
+
98
+ if (existing === undefined) {
99
+ throw new Error(`Unknown coordinate "${id}".`);
100
+ }
101
+
102
+ if (
103
+ descriptor.type !== undefined &&
104
+ descriptor.type !== "auto" &&
105
+ descriptor.type !== existing.type
106
+ ) {
107
+ throw new Error(
108
+ `Coordinate "${id}" has type "${existing.type}", not "${descriptor.type}".`
109
+ );
110
+ }
111
+
112
+ const layers = cartesianLayers.filter(
113
+ layer => layer.coordinate === id
114
+ );
115
+
116
+ if (layers.length === 0) {
117
+ throw new Error(`createAxes found no layers for coordinate "${id}".`);
118
+ }
119
+
120
+ if (existing.type !== "cartesian") {
121
+ throw new Error("createAxes does not yet support Polar axes.");
122
+ }
123
+
124
+ return { id, layers };
125
+ }
126
+
127
+ function hasChannel(layers, channel) {
128
+ return layers.some(layer => layer.encoding?.[channel] !== undefined);
129
+ }
130
+
131
+ function resolveAxisArgs(layers, channel, option) {
132
+ const selected = option === false
133
+ ? false
134
+ : option !== undefined || hasChannel(layers, channel);
135
+
136
+ if (!selected) return undefined;
137
+
138
+ const args = option ?? {};
139
+ const encodedScaleIds = [
140
+ ...new Set(
141
+ layers
142
+ .map(layer => layer.encoding?.[channel]?.scale)
143
+ .filter(id => id !== undefined)
144
+ )
145
+ ];
146
+ let scale = args.scale;
147
+
148
+ if (scale === undefined) {
149
+ if (encodedScaleIds.length === 0) {
150
+ throw new Error(`createAxes cannot infer the ${channel}-axis scale.`);
151
+ }
152
+
153
+ if (encodedScaleIds.length > 1) {
154
+ throw new Error(
155
+ `createAxes found multiple ${channel}-axis scales; provide ${channel}.scale explicitly.`
156
+ );
157
+ }
158
+
159
+ [scale] = encodedScaleIds;
160
+ }
161
+
162
+ validateUserId(scale, `${channel}-axis scale id`);
163
+ return { ...args, scale };
164
+ }
165
+
166
+ const createAxes = action(
167
+ {
168
+ op: "createAxes",
169
+ description: "Read a semantic coordinate and create its Cartesian axes."
170
+ },
171
+ function (args = {}) {
172
+ const coordinateDescriptor = validateArgs(args);
173
+ const { cartesianLayers, hasPolar } = inspectChannels(
174
+ this.semanticSpec.layers
175
+ );
176
+ const coordinate = resolveCoordinate(
177
+ this,
178
+ coordinateDescriptor,
179
+ cartesianLayers,
180
+ hasPolar
181
+ );
182
+ const x = resolveAxisArgs(coordinate.layers, "x", args.x);
183
+ const y = resolveAxisArgs(coordinate.layers, "y", args.y);
184
+
185
+ if (x === undefined && y === undefined) {
186
+ throw new Error("createAxes requires at least one selected axis.");
187
+ }
188
+
189
+ let program = this;
190
+
191
+ if (x !== undefined) {
192
+ program = program.createXAxis({ ...x, coordinate: coordinate.id });
193
+ }
194
+ if (y !== undefined) {
195
+ program = program.createYAxis({ ...y, coordinate: coordinate.id });
196
+ }
197
+
198
+ return program;
199
+ }
200
+ );
201
+
202
+ export function registerAxisCollectionActions(ProgramClass) {
203
+ ProgramClass.prototype.createAxes = createAxes;
204
+ }
@@ -0,0 +1,131 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { isPlainObject } from "../../../core/immutable.js";
3
+ import { validateUserId } from "../../../core/identifiers.js";
4
+ import { validateKeys } from "../../../core/validation.js";
5
+ import { hasCoordinate } from "../../../selectors/index.js";
6
+
7
+ const TOP_OPTIONS = Object.freeze([
8
+ "scale",
9
+ "coordinate",
10
+ "position",
11
+ "line",
12
+ "ticksAndLabels",
13
+ "title"
14
+ ]);
15
+ const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
16
+ const TICK_GROUP_OPTIONS = Object.freeze([
17
+ "count",
18
+ "values",
19
+ "ticks",
20
+ "labels"
21
+ ]);
22
+ const TITLE_OPTIONS = Object.freeze([
23
+ "text",
24
+ "at",
25
+ "offset",
26
+ "rotation",
27
+ "color",
28
+ "fontSize",
29
+ "fontFamily",
30
+ "fontWeight"
31
+ ]);
32
+
33
+ function validateNested(value, supported, label) {
34
+ if (!isPlainObject(value)) {
35
+ throw new TypeError(`${label} must be a plain object.`);
36
+ }
37
+
38
+ validateKeys(value, supported, label);
39
+ }
40
+
41
+ function validateArgs(args, operation) {
42
+ validateKeys(args, TOP_OPTIONS, operation);
43
+
44
+ if (Object.hasOwn(args, "line")) {
45
+ validateNested(args.line, LINE_OPTIONS, `${operation}.line`);
46
+ }
47
+
48
+ if (Object.hasOwn(args, "ticksAndLabels")) {
49
+ validateNested(
50
+ args.ticksAndLabels,
51
+ TICK_GROUP_OPTIONS,
52
+ `${operation}.ticksAndLabels`
53
+ );
54
+ }
55
+
56
+ if (Object.hasOwn(args, "title")) {
57
+ validateNested(args.title, TITLE_OPTIONS, `${operation}.title`);
58
+ }
59
+ }
60
+
61
+ function names(channel) {
62
+ const prefix = channel === "x" ? "X" : "Y";
63
+ return {
64
+ create: `create${prefix}Axis`,
65
+ line: `create${prefix}AxisLine`,
66
+ ticksAndLabels: `create${prefix}AxisTicksAndLabels`,
67
+ title: `create${prefix}AxisTitle`
68
+ };
69
+ }
70
+
71
+ function makeCreateAxis(channel) {
72
+ const operation = names(channel);
73
+
74
+ return action(
75
+ {
76
+ op: operation.create,
77
+ description: `Create the complete ${channel}-axis.`
78
+ },
79
+ function (args = {}) {
80
+ validateArgs(args, operation.create);
81
+ const shared = {};
82
+ if (Object.hasOwn(args, "scale")) shared.scale = args.scale;
83
+ if (Object.hasOwn(args, "position")) shared.position = args.position;
84
+ let next = this;
85
+
86
+ if (Object.hasOwn(args, "coordinate")) {
87
+ const coordinate = validateUserId(args.coordinate, "Coordinate id");
88
+ const scale = args.scale ?? channel;
89
+ const exists = hasCoordinate(next, coordinate);
90
+ const hasConsumer = next.semanticSpec.layers.some(
91
+ layer =>
92
+ layer.coordinate === coordinate &&
93
+ layer.encoding?.[channel]?.scale === scale
94
+ );
95
+
96
+ if (!exists) {
97
+ throw new Error(`Unknown coordinate "${coordinate}".`);
98
+ }
99
+ if (!hasConsumer) {
100
+ throw new Error(
101
+ `${operation.create} found no ${channel} encoding for coordinate "${coordinate}" and scale "${scale}".`
102
+ );
103
+ }
104
+
105
+ next = next.editSemantic({
106
+ property: `guide.axis.${channel}.coordinate`,
107
+ value: coordinate
108
+ });
109
+ }
110
+
111
+ return next[operation.line]({
112
+ ...shared,
113
+ ...(args.line ?? {})
114
+ })[operation.ticksAndLabels]({
115
+ ...shared,
116
+ ...(args.ticksAndLabels ?? {})
117
+ })[operation.title]({
118
+ ...shared,
119
+ ...(args.title ?? {})
120
+ });
121
+ }
122
+ );
123
+ }
124
+
125
+ const createXAxis = makeCreateAxis("x");
126
+ const createYAxis = makeCreateAxis("y");
127
+
128
+ export function registerAxisActions(Class) {
129
+ Class.prototype.createXAxis = createXAxis;
130
+ Class.prototype.createYAxis = createYAxis;
131
+ }
@@ -0,0 +1,17 @@
1
+ import { registerAxisCollectionActions } from "./axes.js";
2
+ import { registerAxisLabelActions } from "./labels.js";
3
+ import { registerAxisLineActions } from "./lines.js";
4
+ import { registerAxisTickGroupActions } from "./tickGroups.js";
5
+ import { registerAxisTickActions } from "./ticks.js";
6
+ import { registerAxisTitleActions } from "./titles.js";
7
+ import { registerAxisActions } from "./axis.js";
8
+
9
+ export function registerGuideAxisActions(ProgramClass) {
10
+ registerAxisLineActions(ProgramClass);
11
+ registerAxisTickActions(ProgramClass);
12
+ registerAxisLabelActions(ProgramClass);
13
+ registerAxisTickGroupActions(ProgramClass);
14
+ registerAxisTitleActions(ProgramClass);
15
+ registerAxisActions(ProgramClass);
16
+ registerAxisCollectionActions(ProgramClass);
17
+ }