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
@@ -0,0 +1,219 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { polarGuideNames, resolvePolarGuideResources } from "../resolve.js";
3
+ import {
4
+ LABEL_EDIT_OPTIONS,
5
+ LINE_EDIT_OPTIONS,
6
+ prefix,
7
+ resolveAngle,
8
+ TICK_EDIT_OPTIONS,
9
+ TITLE_EDIT_OPTIONS,
10
+ validateAngle,
11
+ validateModeOptions,
12
+ validateObject
13
+ } from "./shared.js";
14
+
15
+ const AXIS_OPTIONS = Object.freeze([
16
+ "scale", "coordinate", "angle", "line", "ticksAndLabels", "title"
17
+ ]);
18
+ const TICK_GROUP_OPTIONS = Object.freeze([
19
+ "count", "values", "ticks", "labels"
20
+ ]);
21
+ const AXIS_EDIT_OPTIONS = Object.freeze([
22
+ "angle", "line", "ticks", "labels", "ticksAndLabels", "title"
23
+ ]);
24
+
25
+ function validateAxisArgs(kind, args, operation) {
26
+ validateObject(args, AXIS_OPTIONS, operation);
27
+ if (Object.hasOwn(args, "line")) {
28
+ validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
29
+ }
30
+ if (Object.hasOwn(args, "ticksAndLabels")) {
31
+ validateObject(
32
+ args.ticksAndLabels,
33
+ TICK_GROUP_OPTIONS,
34
+ `${operation}.ticksAndLabels`
35
+ );
36
+ validateModeOptions(args.ticksAndLabels, `${operation}.ticksAndLabels`);
37
+ if (Object.hasOwn(args.ticksAndLabels, "ticks")) {
38
+ validateObject(
39
+ args.ticksAndLabels.ticks,
40
+ ["length", "color", "lineWidth"],
41
+ `${operation}.ticksAndLabels.ticks`
42
+ );
43
+ }
44
+ if (Object.hasOwn(args.ticksAndLabels, "labels")) {
45
+ validateObject(
46
+ args.ticksAndLabels.labels,
47
+ LABEL_EDIT_OPTIONS.filter(key => !["count", "values"].includes(key)),
48
+ `${operation}.ticksAndLabels.labels`
49
+ );
50
+ }
51
+ }
52
+ if (Object.hasOwn(args, "title")) {
53
+ if (args.title !== false) {
54
+ validateObject(
55
+ args.title,
56
+ kind === "theta"
57
+ ? TITLE_EDIT_OPTIONS.filter(option => option !== "position")
58
+ : TITLE_EDIT_OPTIONS,
59
+ `${operation}.title`
60
+ );
61
+ }
62
+ }
63
+ }
64
+
65
+ function makeCreateAxis(kind) {
66
+ const operation = `create${prefix(kind)}Axis`;
67
+ return action({
68
+ op: operation,
69
+ description: `Create the complete Polar ${kind} axis.`
70
+ }, function (args = {}) {
71
+ validateAxisArgs(kind, args, operation);
72
+ const resources = resolvePolarGuideResources(this, kind, args, operation);
73
+ const angle = resolveAngle(this, kind, args);
74
+ const shared = {
75
+ scale: resources.scale,
76
+ coordinate: resources.coordinate,
77
+ ...(kind === "radius" ? { angle } : {})
78
+ };
79
+ const group = args.ticksAndLabels ?? {};
80
+ const mode = {
81
+ ...(Object.hasOwn(group, "count") ? { count: group.count } : {}),
82
+ ...(Object.hasOwn(group, "values") ? { values: group.values } : {})
83
+ };
84
+ let next = this;
85
+ if (kind === "radius") {
86
+ next = next._withGuideConfig("radius", "layout", { angle });
87
+ }
88
+ next = next
89
+ [`create${prefix(kind)}AxisLine`]({ ...shared, ...(args.line ?? {}) })
90
+ [`create${prefix(kind)}AxisTicks`]({
91
+ ...shared,
92
+ ...mode,
93
+ ...(group.ticks ?? {})
94
+ })
95
+ [`create${prefix(kind)}AxisLabels`]({
96
+ ...shared,
97
+ ...mode,
98
+ ...(group.labels ?? {})
99
+ });
100
+ return args.title === false
101
+ ? next
102
+ : next[`create${prefix(kind)}AxisTitle`]({
103
+ ...shared,
104
+ ...(args.title ?? {})
105
+ });
106
+ });
107
+ }
108
+
109
+ function validateAxisEditArgs(kind, args, operation) {
110
+ validateObject(
111
+ args,
112
+ kind === "theta"
113
+ ? AXIS_EDIT_OPTIONS.filter(option => option !== "angle")
114
+ : AXIS_EDIT_OPTIONS,
115
+ operation
116
+ );
117
+ if (Object.keys(args).length === 0) {
118
+ throw new Error(`${operation} requires at least one axis change.`);
119
+ }
120
+ if (Object.hasOwn(args, "angle")) validateAngle(args.angle);
121
+ if (Object.hasOwn(args, "line")) {
122
+ validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
123
+ }
124
+ if (Object.hasOwn(args, "ticks")) {
125
+ validateObject(args.ticks, TICK_EDIT_OPTIONS, `${operation}.ticks`);
126
+ validateModeOptions(args.ticks, `${operation}.ticks`);
127
+ }
128
+ if (Object.hasOwn(args, "labels")) {
129
+ validateObject(args.labels, LABEL_EDIT_OPTIONS, `${operation}.labels`);
130
+ validateModeOptions(args.labels, `${operation}.labels`);
131
+ }
132
+ if (Object.hasOwn(args, "ticksAndLabels")) {
133
+ validateObject(
134
+ args.ticksAndLabels,
135
+ TICK_GROUP_OPTIONS,
136
+ `${operation}.ticksAndLabels`
137
+ );
138
+ validateModeOptions(args.ticksAndLabels, `${operation}.ticksAndLabels`);
139
+ if (args.ticks !== undefined || args.labels !== undefined) {
140
+ throw new Error(
141
+ `${operation} cannot combine ticksAndLabels with ticks or labels.`
142
+ );
143
+ }
144
+ if (args.ticksAndLabels.ticks !== undefined) {
145
+ validateObject(
146
+ args.ticksAndLabels.ticks,
147
+ ["length", "color", "lineWidth"],
148
+ `${operation}.ticksAndLabels.ticks`
149
+ );
150
+ }
151
+ if (args.ticksAndLabels.labels !== undefined) {
152
+ validateObject(
153
+ args.ticksAndLabels.labels,
154
+ LABEL_EDIT_OPTIONS.filter(key => !["count", "values"].includes(key)),
155
+ `${operation}.ticksAndLabels.labels`
156
+ );
157
+ }
158
+ }
159
+ if (Object.hasOwn(args, "title")) {
160
+ validateObject(
161
+ args.title,
162
+ kind === "theta"
163
+ ? TITLE_EDIT_OPTIONS.filter(option => option !== "position")
164
+ : TITLE_EDIT_OPTIONS,
165
+ `${operation}.title`
166
+ );
167
+ }
168
+ }
169
+
170
+ function makeEditAxis(kind) {
171
+ const operation = `edit${prefix(kind)}Axis`;
172
+ return action({
173
+ op: operation,
174
+ description: `Edit selected Polar ${kind}-axis components.`
175
+ }, function (args = {}) {
176
+ validateAxisEditArgs(kind, args, operation);
177
+ const names = polarGuideNames(kind);
178
+ const angleChanged = Object.hasOwn(args, "angle");
179
+ let next = angleChanged
180
+ ? this._withGuideConfig("radius", "layout", { angle: args.angle })
181
+ : this;
182
+ const has = component =>
183
+ next.graphicSpec.objects[names[component]] !== undefined;
184
+ if (args.line !== undefined || (angleChanged && has("line"))) {
185
+ next = next[`edit${prefix(kind)}AxisLine`](args.line ?? {});
186
+ }
187
+ if (args.ticksAndLabels !== undefined) {
188
+ const group = args.ticksAndLabels;
189
+ const mode = {
190
+ ...(Object.hasOwn(group, "count") ? { count: group.count } : {}),
191
+ ...(Object.hasOwn(group, "values") ? { values: group.values } : {})
192
+ };
193
+ next = next[`edit${prefix(kind)}AxisTicks`]({
194
+ ...mode,
195
+ ...(group.ticks ?? {})
196
+ });
197
+ next = next[`edit${prefix(kind)}AxisLabels`]({
198
+ ...mode,
199
+ ...(group.labels ?? {})
200
+ });
201
+ } else {
202
+ if (args.ticks !== undefined || (angleChanged && has("ticks"))) {
203
+ next = next[`edit${prefix(kind)}AxisTicks`](args.ticks ?? {});
204
+ }
205
+ if (args.labels !== undefined || (angleChanged && has("labels"))) {
206
+ next = next[`edit${prefix(kind)}AxisLabels`](args.labels ?? {});
207
+ }
208
+ }
209
+ if (args.title !== undefined || (angleChanged && has("title"))) {
210
+ next = next[`edit${prefix(kind)}AxisTitle`](args.title ?? {});
211
+ }
212
+ return next;
213
+ });
214
+ }
215
+
216
+ export const createThetaAxis = makeCreateAxis("theta");
217
+ export const createRadialAxis = makeCreateAxis("radius");
218
+ export const editThetaAxis = makeEditAxis("theta");
219
+ export const editRadialAxis = makeEditAxis("radius");
@@ -0,0 +1,55 @@
1
+ import {
2
+ createRadialAxis,
3
+ createThetaAxis,
4
+ editRadialAxis,
5
+ editThetaAxis
6
+ } from "./facade.js";
7
+ import {
8
+ createRadialAxisLabels,
9
+ createThetaAxisLabels,
10
+ editRadialAxisLabels,
11
+ editThetaAxisLabels
12
+ } from "./labels.js";
13
+ import {
14
+ createRadialAxisLine,
15
+ createThetaAxisLine,
16
+ editRadialAxisLine,
17
+ editThetaAxisLine
18
+ } from "./lines.js";
19
+ import {
20
+ createRadialAxisTicks,
21
+ createThetaAxisTicks,
22
+ editRadialAxisTicks,
23
+ editThetaAxisTicks
24
+ } from "./ticks.js";
25
+ import {
26
+ createRadialAxisTitle,
27
+ createThetaAxisTitle,
28
+ editRadialAxisTitle,
29
+ editThetaAxisTitle
30
+ } from "./titles.js";
31
+
32
+ export function registerPolarAxisActions(ProgramClass) {
33
+ Object.assign(ProgramClass.prototype, {
34
+ createThetaAxisLine,
35
+ createRadialAxisLine,
36
+ editThetaAxisLine,
37
+ editRadialAxisLine,
38
+ createThetaAxisTicks,
39
+ createRadialAxisTicks,
40
+ editThetaAxisTicks,
41
+ editRadialAxisTicks,
42
+ createThetaAxisLabels,
43
+ createRadialAxisLabels,
44
+ editThetaAxisLabels,
45
+ editRadialAxisLabels,
46
+ createThetaAxisTitle,
47
+ createRadialAxisTitle,
48
+ editThetaAxisTitle,
49
+ editRadialAxisTitle,
50
+ createThetaAxis,
51
+ createRadialAxis,
52
+ editThetaAxis,
53
+ editRadialAxis
54
+ });
55
+ }
@@ -0,0 +1,188 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { validateNonNegativeFinite } from "../../../../core/validation.js";
3
+ import {
4
+ resolveRadialAxisLabels,
5
+ resolveThetaAxisLabels
6
+ } from "../../../../grammar/polarGuides.js";
7
+ import { resolvePlotGraphicPlacement } from
8
+ "../../../../materialization/graphicHierarchy.js";
9
+ import {
10
+ formatPolarGuideValues,
11
+ mapPolarGuideValues,
12
+ normalizePolarTickMode,
13
+ POLAR_AXIS_DEFAULTS,
14
+ polarGuideNames,
15
+ resolvePolarFrameForProgram,
16
+ validatePolarLabelFormat,
17
+ validatePolarTextStyle,
18
+ validatePolarTickConfig
19
+ } from "../resolve.js";
20
+ import {
21
+ componentResources,
22
+ LABEL_CREATE_OPTIONS,
23
+ LABEL_EDIT_OPTIONS,
24
+ operations,
25
+ prefix,
26
+ resolveAngle,
27
+ validateModeOptions,
28
+ validateObject,
29
+ withAxisSemantics
30
+ } from "./shared.js";
31
+
32
+ function labelGeometry(program, kind, config) {
33
+ const frame = resolvePolarFrameForProgram(program);
34
+ const mapped = mapPolarGuideValues(program, config);
35
+ const text = formatPolarGuideValues(program, config, mapped.values);
36
+ return {
37
+ values: mapped.values,
38
+ text,
39
+ ...(kind === "theta"
40
+ ? resolveThetaAxisLabels({
41
+ frame,
42
+ angles: mapped.positions,
43
+ offset: config.offset
44
+ })
45
+ : resolveRadialAxisLabels({
46
+ frame,
47
+ angle: resolveAngle(program, kind, {}),
48
+ radii: mapped.positions,
49
+ offset: config.offset
50
+ }))
51
+ };
52
+ }
53
+
54
+ function resolveLabelConfig(program, kind, args, resources, previous) {
55
+ const ticks = program.guideConfigs.axis?.[kind]?.ticks;
56
+ const count = kind === "theta"
57
+ ? POLAR_AXIS_DEFAULTS.ticks.thetaCount
58
+ : POLAR_AXIS_DEFAULTS.ticks.radiusCount;
59
+ const explicitMode = Object.hasOwn(args, "count") ||
60
+ Object.hasOwn(args, "values");
61
+ let mode;
62
+ if (explicitMode) {
63
+ mode = normalizePolarTickMode(program, resources.scale, args, count);
64
+ } else if (ticks !== undefined) {
65
+ mode = ticks.mode === "values"
66
+ ? {
67
+ mode: "values",
68
+ values: ticks.values,
69
+ inferredValues: ticks.inferredValues
70
+ }
71
+ : {
72
+ mode: "count",
73
+ count: ticks.count,
74
+ inferredValues: ticks.inferredValues
75
+ };
76
+ } else if (previous?.inferredValues === false) {
77
+ mode = previous.mode === "values"
78
+ ? { mode: "values", values: previous.values, inferredValues: false }
79
+ : { mode: "count", count: previous.count, inferredValues: false };
80
+ } else {
81
+ mode = normalizePolarTickMode(program, resources.scale, {}, count);
82
+ }
83
+ const config = {
84
+ ...(previous ?? {}),
85
+ scale: resources.scale,
86
+ coordinate: resources.coordinate,
87
+ ...mode,
88
+ offset: args.offset ?? previous?.offset ?? (kind === "theta"
89
+ ? POLAR_AXIS_DEFAULTS.labels.thetaOffset
90
+ : POLAR_AXIS_DEFAULTS.labels.radiusOffset),
91
+ format: args.format ?? previous?.format ??
92
+ POLAR_AXIS_DEFAULTS.labels.format,
93
+ color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.labels.color,
94
+ fontSize: args.fontSize ?? previous?.fontSize ??
95
+ POLAR_AXIS_DEFAULTS.labels.fontSize,
96
+ fontFamily: args.fontFamily ?? previous?.fontFamily ??
97
+ POLAR_AXIS_DEFAULTS.labels.fontFamily,
98
+ fontWeight: args.fontWeight ?? previous?.fontWeight ??
99
+ POLAR_AXIS_DEFAULTS.labels.fontWeight
100
+ };
101
+ validatePolarTickConfig(config, `${kind}-axis labels`);
102
+ validateNonNegativeFinite(config.offset, "Polar axis label offset");
103
+ validatePolarLabelFormat(config.format);
104
+ validatePolarTextStyle(config, `${prefix(kind)} axis labels`);
105
+ return config;
106
+ }
107
+
108
+ function makeEditLabels(kind) {
109
+ const operation = operations(kind, "Labels");
110
+ return action({
111
+ op: operation.edit,
112
+ description: `Edit the Polar ${kind}-axis labels.`
113
+ }, function (args = {}) {
114
+ validateObject(args, LABEL_EDIT_OPTIONS, operation.edit);
115
+ validateModeOptions(args, operation.edit);
116
+ const names = polarGuideNames(kind);
117
+ const previous = this.guideConfigs.axis?.[kind]?.labels;
118
+ if (this.graphicSpec.objects[names.labels]?.type !== "text" ||
119
+ previous === undefined) {
120
+ throw new Error(`${operation.edit} requires existing axis labels.`);
121
+ }
122
+ const resources = {
123
+ scale: previous.scale,
124
+ coordinate: previous.coordinate
125
+ };
126
+ const config = resolveLabelConfig(this, kind, args, resources, previous);
127
+ const geometry = labelGeometry(this, kind, config);
128
+ let next = this._withGuideConfig(kind, "labels", config)
129
+ .editGraphics({
130
+ target: names.labels,
131
+ property: "length",
132
+ value: geometry.values.length
133
+ });
134
+ const properties = {
135
+ x: geometry.x,
136
+ y: geometry.y,
137
+ text: geometry.text,
138
+ textAlign: geometry.textAlign,
139
+ textBaseline: geometry.textBaseline,
140
+ fill: config.color,
141
+ fontSize: config.fontSize,
142
+ fontFamily: config.fontFamily,
143
+ fontWeight: config.fontWeight
144
+ };
145
+ for (const [property, value] of Object.entries(properties)) {
146
+ next = next.editGraphics({ target: names.labels, property, value });
147
+ }
148
+ return next;
149
+ });
150
+ }
151
+
152
+ function makeCreateLabels(kind) {
153
+ const operation = operations(kind, "Labels");
154
+ return action({
155
+ op: operation.create,
156
+ description: `Create the Polar ${kind}-axis labels.`
157
+ }, function (args = {}) {
158
+ validateObject(args, LABEL_CREATE_OPTIONS, operation.create);
159
+ validateModeOptions(args, operation.create);
160
+ const names = polarGuideNames(kind);
161
+ if (this.graphicSpec.objects[names.labels] !== undefined) {
162
+ throw new Error(`${operation.create} requires missing axis labels.`);
163
+ }
164
+ const resources = componentResources(this, kind, args, operation.create);
165
+ const angle = resolveAngle(this, kind, args);
166
+ const config = resolveLabelConfig(this, kind, args, resources);
167
+ labelGeometry(this, kind, config);
168
+ let next = withAxisSemantics(this, kind, resources);
169
+ if (kind === "radius" &&
170
+ next.guideConfigs.axis?.radius?.layout === undefined) {
171
+ next = next._withGuideConfig("radius", "layout", { angle });
172
+ }
173
+ return next
174
+ .createGraphics({
175
+ id: names.labels,
176
+ type: "text",
177
+ length: 0,
178
+ ...resolvePlotGraphicPlacement(next)
179
+ })
180
+ ._withGuideConfig(kind, "labels", config)
181
+ [operation.edit]();
182
+ });
183
+ }
184
+
185
+ export const createThetaAxisLabels = makeCreateLabels("theta");
186
+ export const createRadialAxisLabels = makeCreateLabels("radius");
187
+ export const editThetaAxisLabels = makeEditLabels("theta");
188
+ export const editRadialAxisLabels = makeEditLabels("radius");
@@ -0,0 +1,113 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import {
3
+ buildPolarCircleCommands,
4
+ resolveRadialAxisLine
5
+ } from "../../../../grammar/polarGuides.js";
6
+ import { resolvePlotGraphicPlacement } from
7
+ "../../../../materialization/graphicHierarchy.js";
8
+ import {
9
+ POLAR_AXIS_DEFAULTS,
10
+ polarGuideNames,
11
+ resolvePolarFrameForProgram,
12
+ validatePolarLineStyle
13
+ } from "../resolve.js";
14
+ import {
15
+ componentResources,
16
+ LINE_CREATE_OPTIONS,
17
+ LINE_EDIT_OPTIONS,
18
+ operations,
19
+ prefix,
20
+ resolveAngle,
21
+ validateObject,
22
+ withAxisSemantics
23
+ } from "./shared.js";
24
+
25
+ function lineGeometry(program, kind, angle, scale) {
26
+ const frame = resolvePolarFrameForProgram(program);
27
+ if (kind === "theta") {
28
+ return { commands: buildPolarCircleCommands(frame, frame.availableRadius) };
29
+ }
30
+ const range = program.resolvedScales[scale]?.range;
31
+ const startRadius = Array.isArray(range) && range.length > 0
32
+ ? Math.min(...range)
33
+ : 0;
34
+ return resolveRadialAxisLine({ frame, angle, startRadius });
35
+ }
36
+
37
+ function makeEditLine(kind) {
38
+ const operation = operations(kind, "Line");
39
+ return action({
40
+ op: operation.edit,
41
+ description: `Edit the Polar ${kind}-axis baseline.`
42
+ }, function (args = {}) {
43
+ validateObject(args, LINE_EDIT_OPTIONS, operation.edit);
44
+ const names = polarGuideNames(kind);
45
+ const expected = kind === "theta" ? "path" : "line";
46
+ const graphic = this.graphicSpec.objects[names.line];
47
+ const previous = this.guideConfigs.axis?.[kind]?.line;
48
+ if (graphic?.type !== expected || previous === undefined) {
49
+ throw new Error(`${operation.edit} requires an existing axis line.`);
50
+ }
51
+ const config = { ...previous, ...args };
52
+ validatePolarLineStyle(config, `${prefix(kind)} axis line`);
53
+ const angle = resolveAngle(this, kind, {});
54
+ const geometry = lineGeometry(this, kind, angle, previous.scale);
55
+ let next = this._withGuideConfig(kind, "line", config);
56
+ for (const [property, value] of Object.entries(geometry)) {
57
+ next = next.editGraphics({ target: names.line, property, value });
58
+ }
59
+ return next
60
+ .editGraphics({
61
+ target: names.line,
62
+ property: "stroke",
63
+ value: config.color
64
+ })
65
+ .editGraphics({
66
+ target: names.line,
67
+ property: "strokeWidth",
68
+ value: config.lineWidth
69
+ });
70
+ });
71
+ }
72
+
73
+ function makeCreateLine(kind) {
74
+ const operation = operations(kind, "Line");
75
+ return action({
76
+ op: operation.create,
77
+ description: `Create the Polar ${kind}-axis baseline.`
78
+ }, function (args = {}) {
79
+ validateObject(args, LINE_CREATE_OPTIONS, operation.create);
80
+ const names = polarGuideNames(kind);
81
+ if (this.graphicSpec.objects[names.line] !== undefined) {
82
+ throw new Error(`${operation.create} requires a missing axis line.`);
83
+ }
84
+ const resources = componentResources(this, kind, args, operation.create);
85
+ const angle = resolveAngle(this, kind, args);
86
+ const config = {
87
+ scale: resources.scale,
88
+ coordinate: resources.coordinate,
89
+ color: args.color ?? POLAR_AXIS_DEFAULTS.line.color,
90
+ lineWidth: args.lineWidth ?? POLAR_AXIS_DEFAULTS.line.lineWidth
91
+ };
92
+ validatePolarLineStyle(config, `${prefix(kind)} axis line`);
93
+ lineGeometry(this, kind, angle, resources.scale);
94
+ let next = withAxisSemantics(this, kind, resources);
95
+ if (kind === "radius" &&
96
+ next.guideConfigs.axis?.radius?.layout === undefined) {
97
+ next = next._withGuideConfig("radius", "layout", { angle });
98
+ }
99
+ return next
100
+ .createGraphics({
101
+ id: names.line,
102
+ type: kind === "theta" ? "path" : "line",
103
+ ...resolvePlotGraphicPlacement(next)
104
+ })
105
+ ._withGuideConfig(kind, "line", config)
106
+ [operation.edit]();
107
+ });
108
+ }
109
+
110
+ export const createThetaAxisLine = makeCreateLine("theta");
111
+ export const createRadialAxisLine = makeCreateLine("radius");
112
+ export const editThetaAxisLine = makeEditLine("theta");
113
+ export const editRadialAxisLine = makeEditLine("radius");
@@ -0,0 +1,112 @@
1
+ import { validateOptionObject } from "../../../../core/validation.js";
2
+ import {
3
+ POLAR_AXIS_DEFAULTS,
4
+ resolvePolarGuideResources
5
+ } from "../resolve.js";
6
+
7
+ export const LINE_CREATE_OPTIONS = Object.freeze([
8
+ "scale", "coordinate", "angle", "color", "lineWidth"
9
+ ]);
10
+ export const LINE_EDIT_OPTIONS = Object.freeze(["color", "lineWidth"]);
11
+ export const TICK_CREATE_OPTIONS = Object.freeze([
12
+ "scale", "coordinate", "angle", "count", "values", "length", "color",
13
+ "lineWidth"
14
+ ]);
15
+ export const TICK_EDIT_OPTIONS = Object.freeze([
16
+ "count", "values", "length", "color", "lineWidth"
17
+ ]);
18
+ export const LABEL_CREATE_OPTIONS = Object.freeze([
19
+ "scale", "coordinate", "angle", "count", "values", "offset", "format",
20
+ "color", "fontSize", "fontFamily", "fontWeight"
21
+ ]);
22
+ export const LABEL_EDIT_OPTIONS = Object.freeze([
23
+ "count", "values", "offset", "format", "color", "fontSize",
24
+ "fontFamily", "fontWeight"
25
+ ]);
26
+ export const TITLE_CREATE_OPTIONS = Object.freeze([
27
+ "scale", "coordinate", "angle", "text", "offset", "color", "fontSize",
28
+ "fontFamily", "fontWeight", "position"
29
+ ]);
30
+ export const TITLE_EDIT_OPTIONS = Object.freeze([
31
+ "text", "offset", "color", "fontSize", "fontFamily", "fontWeight",
32
+ "position"
33
+ ]);
34
+
35
+ export function prefix(kind) {
36
+ return kind === "theta" ? "Theta" : "Radial";
37
+ }
38
+
39
+ export function operations(kind, component) {
40
+ const name = `${prefix(kind)}Axis${component}`;
41
+ return { create: `create${name}`, edit: `edit${name}` };
42
+ }
43
+
44
+ export function validateObject(args, supported, operation) {
45
+ validateOptionObject(args, supported, operation);
46
+ }
47
+
48
+ export function validateModeOptions(args, operation) {
49
+ if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
50
+ throw new Error(`${operation} cannot use count and values together.`);
51
+ }
52
+ }
53
+
54
+ export function validateAngle(value) {
55
+ if (!Number.isFinite(value)) {
56
+ throw new TypeError("Radial-axis angle must be finite degrees.");
57
+ }
58
+ return value;
59
+ }
60
+
61
+ export function resolveAngle(program, kind, args) {
62
+ if (kind === "theta") return undefined;
63
+ const previous = program.guideConfigs.axis?.radius?.layout?.angle;
64
+ const angle = validateAngle(
65
+ args.angle ?? previous ?? POLAR_AXIS_DEFAULTS.angle
66
+ );
67
+ if (previous !== undefined && Object.hasOwn(args, "angle") &&
68
+ previous !== angle) {
69
+ throw new Error(
70
+ "Polar radial-axis components must share one aggregate angle."
71
+ );
72
+ }
73
+ return angle;
74
+ }
75
+
76
+ export function withAxisSemantics(program, kind, resources) {
77
+ const existing = program.semanticSpec.guides.axis?.[kind];
78
+ if (existing?.scale !== undefined && existing.scale !== resources.scale) {
79
+ throw new Error(`${prefix(kind)} axis conflicts with its existing scale.`);
80
+ }
81
+ if (existing?.coordinate !== undefined &&
82
+ existing.coordinate !== resources.coordinate) {
83
+ throw new Error(`${prefix(kind)} axis conflicts with its coordinate.`);
84
+ }
85
+ let next = program;
86
+ if (existing?.scale === undefined) {
87
+ next = next.editSemantic({
88
+ property: `guide.axis.${kind}.scale`,
89
+ value: resources.scale
90
+ });
91
+ }
92
+ if (existing?.coordinate === undefined) {
93
+ next = next.editSemantic({
94
+ property: `guide.axis.${kind}.coordinate`,
95
+ value: resources.coordinate
96
+ });
97
+ }
98
+ return next;
99
+ }
100
+
101
+ export function componentResources(program, kind, args, operation) {
102
+ const stored = program.semanticSpec.guides.axis?.[kind];
103
+ return resolvePolarGuideResources(program, kind, {
104
+ ...args,
105
+ ...(args.scale === undefined && stored?.scale !== undefined
106
+ ? { scale: stored.scale }
107
+ : {}),
108
+ ...(args.coordinate === undefined && stored?.coordinate !== undefined
109
+ ? { coordinate: stored.coordinate }
110
+ : {})
111
+ }, operation);
112
+ }