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,164 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { validateNonNegativeFinite } from "../../../../core/validation.js";
3
+ import {
4
+ resolveRadialAxisTicks,
5
+ resolveThetaAxisTicks
6
+ } from "../../../../grammar/polarGuides.js";
7
+ import { resolvePlotGraphicPlacement } from
8
+ "../../../../materialization/graphicHierarchy.js";
9
+ import {
10
+ mapPolarGuideValues,
11
+ normalizePolarTickMode,
12
+ POLAR_AXIS_DEFAULTS,
13
+ polarGuideNames,
14
+ resolvePolarFrameForProgram,
15
+ validatePolarLineStyle,
16
+ validatePolarTickConfig
17
+ } from "../resolve.js";
18
+ import {
19
+ componentResources,
20
+ operations,
21
+ prefix,
22
+ resolveAngle,
23
+ TICK_CREATE_OPTIONS,
24
+ TICK_EDIT_OPTIONS,
25
+ validateModeOptions,
26
+ validateObject,
27
+ withAxisSemantics
28
+ } from "./shared.js";
29
+
30
+ function tickGeometry(program, kind, config) {
31
+ const frame = resolvePolarFrameForProgram(program);
32
+ const mapped = mapPolarGuideValues(program, config);
33
+ return {
34
+ values: mapped.values,
35
+ ...(kind === "theta"
36
+ ? resolveThetaAxisTicks({
37
+ frame,
38
+ angles: mapped.positions,
39
+ length: config.length
40
+ })
41
+ : resolveRadialAxisTicks({
42
+ frame,
43
+ angle: resolveAngle(program, kind, {}),
44
+ radii: mapped.positions,
45
+ length: config.length
46
+ }))
47
+ };
48
+ }
49
+
50
+ function resolveTickConfig(program, kind, args, resources, previous) {
51
+ const count = kind === "theta"
52
+ ? POLAR_AXIS_DEFAULTS.ticks.thetaCount
53
+ : POLAR_AXIS_DEFAULTS.ticks.radiusCount;
54
+ const explicitMode = Object.hasOwn(args, "count") ||
55
+ Object.hasOwn(args, "values");
56
+ const mode = explicitMode
57
+ ? normalizePolarTickMode(program, resources.scale, args, count)
58
+ : previous?.inferredValues === false
59
+ ? previous.mode === "values"
60
+ ? { mode: "values", values: previous.values, inferredValues: false }
61
+ : { mode: "count", count: previous.count, inferredValues: false }
62
+ : normalizePolarTickMode(program, resources.scale, {}, count);
63
+ const config = {
64
+ ...(previous ?? {}),
65
+ scale: resources.scale,
66
+ coordinate: resources.coordinate,
67
+ ...mode,
68
+ length: args.length ?? previous?.length ?? (kind === "theta"
69
+ ? POLAR_AXIS_DEFAULTS.ticks.length
70
+ : POLAR_AXIS_DEFAULTS.ticks.radialLength),
71
+ color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.ticks.color,
72
+ lineWidth: args.lineWidth ?? previous?.lineWidth ??
73
+ POLAR_AXIS_DEFAULTS.ticks.lineWidth
74
+ };
75
+ validatePolarTickConfig(config, `${kind}-axis ticks`);
76
+ validateNonNegativeFinite(config.length, "Polar axis tick length");
77
+ validatePolarLineStyle(config, `${prefix(kind)} axis ticks`);
78
+ return config;
79
+ }
80
+
81
+ function makeEditTicks(kind) {
82
+ const operation = operations(kind, "Ticks");
83
+ return action({
84
+ op: operation.edit,
85
+ description: `Edit the Polar ${kind}-axis ticks.`
86
+ }, function (args = {}) {
87
+ validateObject(args, TICK_EDIT_OPTIONS, operation.edit);
88
+ validateModeOptions(args, operation.edit);
89
+ const names = polarGuideNames(kind);
90
+ const previous = this.guideConfigs.axis?.[kind]?.ticks;
91
+ if (this.graphicSpec.objects[names.ticks]?.type !== "line" ||
92
+ previous === undefined) {
93
+ throw new Error(`${operation.edit} requires existing axis ticks.`);
94
+ }
95
+ const resources = {
96
+ scale: previous.scale,
97
+ coordinate: previous.coordinate
98
+ };
99
+ const config = resolveTickConfig(this, kind, args, resources, previous);
100
+ const geometry = tickGeometry(this, kind, config);
101
+ let next = this._withGuideConfig(kind, "ticks", config)
102
+ .editGraphics({
103
+ target: names.ticks,
104
+ property: "length",
105
+ value: geometry.values.length
106
+ });
107
+ for (const property of ["x1", "y1", "x2", "y2"]) {
108
+ next = next.editGraphics({
109
+ target: names.ticks,
110
+ property,
111
+ value: geometry[property]
112
+ });
113
+ }
114
+ return next
115
+ .editGraphics({
116
+ target: names.ticks,
117
+ property: "stroke",
118
+ value: config.color
119
+ })
120
+ .editGraphics({
121
+ target: names.ticks,
122
+ property: "strokeWidth",
123
+ value: config.lineWidth
124
+ });
125
+ });
126
+ }
127
+
128
+ function makeCreateTicks(kind) {
129
+ const operation = operations(kind, "Ticks");
130
+ return action({
131
+ op: operation.create,
132
+ description: `Create the Polar ${kind}-axis ticks.`
133
+ }, function (args = {}) {
134
+ validateObject(args, TICK_CREATE_OPTIONS, operation.create);
135
+ validateModeOptions(args, operation.create);
136
+ const names = polarGuideNames(kind);
137
+ if (this.graphicSpec.objects[names.ticks] !== undefined) {
138
+ throw new Error(`${operation.create} requires missing axis ticks.`);
139
+ }
140
+ const resources = componentResources(this, kind, args, operation.create);
141
+ const angle = resolveAngle(this, kind, args);
142
+ const config = resolveTickConfig(this, kind, args, resources);
143
+ tickGeometry(this, kind, config);
144
+ let next = withAxisSemantics(this, kind, resources);
145
+ if (kind === "radius" &&
146
+ next.guideConfigs.axis?.radius?.layout === undefined) {
147
+ next = next._withGuideConfig("radius", "layout", { angle });
148
+ }
149
+ return next
150
+ .createGraphics({
151
+ id: names.ticks,
152
+ type: "line",
153
+ length: 0,
154
+ ...resolvePlotGraphicPlacement(next)
155
+ })
156
+ ._withGuideConfig(kind, "ticks", config)
157
+ [operation.edit]();
158
+ });
159
+ }
160
+
161
+ export const createThetaAxisTicks = makeCreateTicks("theta");
162
+ export const createRadialAxisTicks = makeCreateTicks("radius");
163
+ export const editThetaAxisTicks = makeEditTicks("theta");
164
+ export const editRadialAxisTicks = makeEditTicks("radius");
@@ -0,0 +1,165 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import {
3
+ validateNonEmptyString,
4
+ validateNonNegativeFinite
5
+ } from "../../../../core/validation.js";
6
+ import {
7
+ resolveRadialAxisTitle,
8
+ resolveThetaAxisTitle
9
+ } from "../../../../grammar/polarGuides.js";
10
+ import { resolvePlotGraphicPlacement } from
11
+ "../../../../materialization/graphicHierarchy.js";
12
+ import { inferAxisTitleText } from "../../axes/titles.js";
13
+ import {
14
+ POLAR_AXIS_DEFAULTS,
15
+ polarGuideNames,
16
+ resolvePolarFrameForProgram,
17
+ validatePolarTextStyle
18
+ } from "../resolve.js";
19
+ import {
20
+ componentResources,
21
+ operations,
22
+ prefix,
23
+ resolveAngle,
24
+ TITLE_CREATE_OPTIONS,
25
+ TITLE_EDIT_OPTIONS,
26
+ validateObject,
27
+ withAxisSemantics
28
+ } from "./shared.js";
29
+
30
+ function titleGeometry(program, kind, config) {
31
+ const frame = resolvePolarFrameForProgram(program);
32
+ return kind === "theta"
33
+ ? resolveThetaAxisTitle({ frame, offset: config.offset })
34
+ : resolveRadialAxisTitle({
35
+ frame,
36
+ angle: resolveAngle(program, kind, {}),
37
+ offset: config.offset,
38
+ position: config.position
39
+ });
40
+ }
41
+
42
+ function resolveTitleConfig(kind, args, resources, previous) {
43
+ const config = {
44
+ ...(previous ?? {}),
45
+ scale: resources.scale,
46
+ coordinate: resources.coordinate,
47
+ inferredText: previous?.inferredText ?? !Object.hasOwn(args, "text"),
48
+ offset: args.offset ?? previous?.offset ?? (kind === "theta"
49
+ ? POLAR_AXIS_DEFAULTS.title.thetaOffset
50
+ : POLAR_AXIS_DEFAULTS.title.radiusOffset),
51
+ color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.title.color,
52
+ fontSize: args.fontSize ?? previous?.fontSize ??
53
+ POLAR_AXIS_DEFAULTS.title.fontSize,
54
+ fontFamily: args.fontFamily ?? previous?.fontFamily ??
55
+ POLAR_AXIS_DEFAULTS.title.fontFamily,
56
+ fontWeight: args.fontWeight ?? previous?.fontWeight ??
57
+ POLAR_AXIS_DEFAULTS.title.fontWeight
58
+ };
59
+ if (kind === "theta" && Object.hasOwn(args, "position")) {
60
+ throw new Error("Theta axis title does not support position.");
61
+ }
62
+ if (kind === "radius") {
63
+ config.position = args.position ?? previous?.position ?? "inside";
64
+ if (!["inside", "outside"].includes(config.position)) {
65
+ throw new Error(
66
+ `Unknown radial-axis title position "${config.position}".`
67
+ );
68
+ }
69
+ }
70
+ if (Object.hasOwn(args, "text")) config.inferredText = false;
71
+ validateNonNegativeFinite(config.offset, "Polar axis title offset");
72
+ validatePolarTextStyle(config, `${prefix(kind)} axis title`);
73
+ return config;
74
+ }
75
+
76
+ function makeEditTitle(kind) {
77
+ const operation = operations(kind, "Title");
78
+ return action({
79
+ op: operation.edit,
80
+ description: `Edit the Polar ${kind}-axis title.`
81
+ }, function (args = {}) {
82
+ validateObject(args, TITLE_EDIT_OPTIONS, operation.edit);
83
+ const names = polarGuideNames(kind);
84
+ const previous = this.guideConfigs.axis?.[kind]?.title;
85
+ if (this.graphicSpec.objects[names.title]?.type !== "text" ||
86
+ previous === undefined) {
87
+ throw new Error(`${operation.edit} requires an existing axis title.`);
88
+ }
89
+ const resources = {
90
+ scale: previous.scale,
91
+ coordinate: previous.coordinate
92
+ };
93
+ const config = resolveTitleConfig(kind, args, resources, previous);
94
+ const text = Object.hasOwn(args, "text")
95
+ ? validateNonEmptyString(args.text, "Polar axis title text")
96
+ : config.inferredText
97
+ ? inferAxisTitleText(this, polarGuideNames(kind).channel, config.scale)
98
+ : this.semanticSpec.guides.axis?.[kind]?.title;
99
+ let next = this;
100
+ if (text !== this.semanticSpec.guides.axis?.[kind]?.title) {
101
+ next = next.editSemantic({
102
+ property: `guide.axis.${kind}.title`,
103
+ value: text
104
+ });
105
+ }
106
+ const geometry = titleGeometry(next, kind, config);
107
+ next = next._withGuideConfig(kind, "title", config);
108
+ const properties = {
109
+ ...geometry,
110
+ text,
111
+ fill: config.color,
112
+ fontSize: config.fontSize,
113
+ fontFamily: config.fontFamily,
114
+ fontWeight: config.fontWeight
115
+ };
116
+ for (const [property, value] of Object.entries(properties)) {
117
+ next = next.editGraphics({ target: names.title, property, value });
118
+ }
119
+ return next;
120
+ });
121
+ }
122
+
123
+ function makeCreateTitle(kind) {
124
+ const operation = operations(kind, "Title");
125
+ return action({
126
+ op: operation.create,
127
+ description: `Create the Polar ${kind}-axis title.`
128
+ }, function (args = {}) {
129
+ validateObject(args, TITLE_CREATE_OPTIONS, operation.create);
130
+ const names = polarGuideNames(kind);
131
+ if (this.graphicSpec.objects[names.title] !== undefined) {
132
+ throw new Error(`${operation.create} requires a missing axis title.`);
133
+ }
134
+ const resources = componentResources(this, kind, args, operation.create);
135
+ const angle = resolveAngle(this, kind, args);
136
+ const config = resolveTitleConfig(kind, args, resources);
137
+ const text = validateNonEmptyString(
138
+ args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
139
+ "Polar axis title text"
140
+ );
141
+ titleGeometry(this, kind, config);
142
+ let next = withAxisSemantics(this, kind, resources);
143
+ if (kind === "radius" &&
144
+ next.guideConfigs.axis?.radius?.layout === undefined) {
145
+ next = next._withGuideConfig("radius", "layout", { angle });
146
+ }
147
+ next = next.editSemantic({
148
+ property: `guide.axis.${kind}.title`,
149
+ value: text
150
+ });
151
+ return next
152
+ .createGraphics({
153
+ id: names.title,
154
+ type: "text",
155
+ ...resolvePlotGraphicPlacement(next)
156
+ })
157
+ ._withGuideConfig(kind, "title", config)
158
+ [operation.edit]();
159
+ });
160
+ }
161
+
162
+ export const createThetaAxisTitle = makeCreateTitle("theta");
163
+ export const createRadialAxisTitle = makeCreateTitle("radius");
164
+ export const editThetaAxisTitle = makeEditTitle("theta");
165
+ export const editRadialAxisTitle = makeEditTitle("radius");
@@ -0,0 +1,252 @@
1
+ import { action } from "../../../core/action.js";
2
+ import {
3
+ noOptions,
4
+ validateOptionObject,
5
+ validateNonEmptyString,
6
+ validateNonNegativeFinite
7
+ } from "../../../core/validation.js";
8
+ import {
9
+ resolveRadialCircles,
10
+ resolveThetaSpokes
11
+ } from "../../../grammar/polarGuides.js";
12
+ import {
13
+ POLAR_GRID_DEFAULTS,
14
+ mapPolarGuideValues,
15
+ normalizePolarTickMode,
16
+ polarGuideNames,
17
+ resolvePolarFrameForProgram,
18
+ resolvePolarGuideResources,
19
+ validatePolarTickConfig
20
+ } from "./resolve.js";
21
+
22
+ const CREATE_OPTIONS = Object.freeze([
23
+ "scale", "coordinate", "count", "values", "color", "lineWidth",
24
+ "strokeDash"
25
+ ]);
26
+ const EDIT_OPTIONS = Object.freeze([
27
+ "count", "values", "color", "lineWidth", "strokeDash"
28
+ ]);
29
+
30
+ function operationNames(kind) {
31
+ const prefix = kind === "theta" ? "Theta" : "Radial";
32
+ return {
33
+ create: `create${prefix}Grid`,
34
+ edit: `edit${prefix}Grid`,
35
+ rematerialize: `rematerialize${prefix}Grid`
36
+ };
37
+ }
38
+
39
+ function validateStrokeDash(value) {
40
+ if (!Array.isArray(value) || value.length % 2 !== 0 ||
41
+ !value.every(item => Number.isFinite(item) && item >= 0)) {
42
+ throw new TypeError(
43
+ "Polar grid strokeDash must be an even-length array of non-negative finite numbers."
44
+ );
45
+ }
46
+ }
47
+
48
+ function validateOptions(args, operation, create) {
49
+ validateOptionObject(
50
+ args,
51
+ create ? CREATE_OPTIONS : EDIT_OPTIONS,
52
+ operation,
53
+ { allowEmpty: create, emptyError: Error }
54
+ );
55
+ if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
56
+ throw new Error(`${operation} cannot use count and values together.`);
57
+ }
58
+ if (Object.hasOwn(args, "color")) {
59
+ validateNonEmptyString(args.color, "Polar grid color");
60
+ }
61
+ if (Object.hasOwn(args, "lineWidth")) {
62
+ validateNonNegativeFinite(args.lineWidth, "Polar grid lineWidth");
63
+ }
64
+ if (Object.hasOwn(args, "strokeDash")) validateStrokeDash(args.strokeDash);
65
+ }
66
+
67
+ function resolveConfig(program, kind, args, resources, previous) {
68
+ const defaults = kind === "theta"
69
+ ? POLAR_GRID_DEFAULTS.thetaCount
70
+ : POLAR_GRID_DEFAULTS.radiusCount;
71
+ const explicitMode = Object.hasOwn(args, "count") ||
72
+ Object.hasOwn(args, "values");
73
+ const mode = explicitMode
74
+ ? normalizePolarTickMode(program, resources.scale, args, defaults)
75
+ : previous === undefined
76
+ ? normalizePolarTickMode(program, resources.scale, args, defaults)
77
+ : previous.inferredValues === true
78
+ ? normalizePolarTickMode(program, resources.scale, {}, defaults)
79
+ : previous.mode === "values"
80
+ ? { mode: "values", values: previous.values, inferredValues: false }
81
+ : { mode: "count", count: previous.count, inferredValues: false };
82
+ const config = {
83
+ ...(previous ?? {}),
84
+ kind,
85
+ scale: resources.scale,
86
+ coordinate: resources.coordinate,
87
+ ...mode,
88
+ color: args.color ?? previous?.color ?? POLAR_GRID_DEFAULTS.color,
89
+ lineWidth: args.lineWidth ?? previous?.lineWidth ??
90
+ POLAR_GRID_DEFAULTS.lineWidth,
91
+ strokeDash: args.strokeDash ?? previous?.strokeDash ??
92
+ POLAR_GRID_DEFAULTS.strokeDash
93
+ };
94
+ validatePolarTickConfig(config, `${kind} grid`);
95
+ validateNonEmptyString(config.color, "Polar grid color");
96
+ validateNonNegativeFinite(config.lineWidth, "Polar grid lineWidth");
97
+ validateStrokeDash(config.strokeDash);
98
+ return config;
99
+ }
100
+
101
+ function resolveGeometry(program, kind, config) {
102
+ const frame = resolvePolarFrameForProgram(program);
103
+ const mapped = mapPolarGuideValues(program, config);
104
+ if (kind === "theta") {
105
+ return { values: mapped.values, ...resolveThetaSpokes({
106
+ frame,
107
+ angles: mapped.positions
108
+ }) };
109
+ }
110
+ const circles = resolveRadialCircles({
111
+ frame,
112
+ radii: mapped.positions
113
+ });
114
+ return {
115
+ values: mapped.values.filter((_, index) => mapped.positions[index] > 0),
116
+ commands: circles.commands
117
+ };
118
+ }
119
+
120
+ function editConcrete(program, kind, config, geometry) {
121
+ const graphic = polarGuideNames(kind).grid;
122
+ const properties = kind === "theta"
123
+ ? {
124
+ x1: geometry.x1,
125
+ y1: geometry.y1,
126
+ x2: geometry.x2,
127
+ y2: geometry.y2
128
+ }
129
+ : { commands: geometry.commands };
130
+ let next = program.editGraphics({
131
+ target: graphic,
132
+ property: "length",
133
+ value: geometry.values.length
134
+ });
135
+ for (const [property, value] of Object.entries(properties)) {
136
+ next = next.editGraphics({ target: graphic, property, value });
137
+ }
138
+ return next
139
+ .editGraphics({ target: graphic, property: "stroke", value: config.color })
140
+ .editGraphics({
141
+ target: graphic,
142
+ property: "strokeWidth",
143
+ value: config.lineWidth
144
+ })
145
+ .editGraphics({
146
+ target: graphic,
147
+ property: "strokeDash",
148
+ value: geometry.values.map(() => config.strokeDash)
149
+ });
150
+ }
151
+
152
+ function makeRematerialize(kind) {
153
+ const operations = operationNames(kind);
154
+ return action({
155
+ op: operations.rematerialize,
156
+ description: `Recompute concrete Polar ${kind} grid geometry.`
157
+ }, function (args = {}) {
158
+ noOptions(args, operations.rematerialize);
159
+ const config = this.guideConfigs.grid?.[kind];
160
+ const semantic = this.semanticSpec.guides.grid?.[kind];
161
+ const graphic = this.graphicSpec.objects[polarGuideNames(kind).grid];
162
+ const expectedType = kind === "theta" ? "line" : "path";
163
+ if (config === undefined || semantic?.scale !== config.scale ||
164
+ semantic.coordinate !== config.coordinate || graphic?.type !== expectedType) {
165
+ throw new Error(`${operations.rematerialize} requires an existing grid.`);
166
+ }
167
+ const resources = { scale: config.scale, coordinate: config.coordinate };
168
+ const refreshed = resolveConfig(this, kind, {}, resources, config);
169
+ const geometry = resolveGeometry(this, kind, refreshed);
170
+ return editConcrete(
171
+ this._withGridConfig(kind, refreshed),
172
+ kind,
173
+ refreshed,
174
+ geometry
175
+ );
176
+ });
177
+ }
178
+
179
+ function makeCreate(kind) {
180
+ const operations = operationNames(kind);
181
+ return action({
182
+ op: operations.create,
183
+ description: `Create a semantic and concrete Polar ${kind} grid.`
184
+ }, function (args = {}) {
185
+ validateOptions(args, operations.create, true);
186
+ const graphic = polarGuideNames(kind).grid;
187
+ if (this.semanticSpec.guides.grid?.[kind] !== undefined ||
188
+ this.graphicSpec.objects[graphic] !== undefined) {
189
+ throw new Error(`${operations.create} requires a missing grid.`);
190
+ }
191
+ const resources = resolvePolarGuideResources(
192
+ this,
193
+ kind,
194
+ args,
195
+ operations.create
196
+ );
197
+ const config = resolveConfig(this, kind, args, resources);
198
+ resolveGeometry(this, kind, config);
199
+ return this
200
+ .editSemantic({
201
+ property: `guide.grid.${kind}.scale`,
202
+ value: resources.scale
203
+ })
204
+ .editSemantic({
205
+ property: `guide.grid.${kind}.coordinate`,
206
+ value: resources.coordinate
207
+ })
208
+ .createGraphics({
209
+ id: graphic,
210
+ type: kind === "theta" ? "line" : "path",
211
+ length: 0,
212
+ ...(resources.parent === undefined ? {} : { parent: resources.parent }),
213
+ before: resources.before
214
+ })
215
+ ._withGridConfig(kind, config)
216
+ [operations.rematerialize]();
217
+ });
218
+ }
219
+
220
+ function makeEdit(kind) {
221
+ const operations = operationNames(kind);
222
+ return action({
223
+ op: operations.edit,
224
+ description: `Edit the existing Polar ${kind} grid.`
225
+ }, function (args = {}) {
226
+ validateOptions(args, operations.edit, false);
227
+ const previous = this.guideConfigs.grid?.[kind];
228
+ if (previous === undefined) {
229
+ throw new Error(`${operations.edit} requires an existing grid.`);
230
+ }
231
+ const resources = { scale: previous.scale, coordinate: previous.coordinate };
232
+ const config = resolveConfig(this, kind, args, resources, previous);
233
+ resolveGeometry(this, kind, config);
234
+ return this._withGridConfig(kind, config)[operations.rematerialize]();
235
+ });
236
+ }
237
+
238
+ const createThetaGrid = makeCreate("theta");
239
+ const createRadialGrid = makeCreate("radial");
240
+ const editThetaGrid = makeEdit("theta");
241
+ const editRadialGrid = makeEdit("radial");
242
+ const rematerializeThetaGrid = makeRematerialize("theta");
243
+ const rematerializeRadialGrid = makeRematerialize("radial");
244
+
245
+ export function registerPolarGridActions(ProgramClass) {
246
+ ProgramClass.prototype.createThetaGrid = createThetaGrid;
247
+ ProgramClass.prototype.createRadialGrid = createRadialGrid;
248
+ ProgramClass.prototype.editThetaGrid = editThetaGrid;
249
+ ProgramClass.prototype.editRadialGrid = editRadialGrid;
250
+ ProgramClass.prototype.rematerializeThetaGrid = rematerializeThetaGrid;
251
+ ProgramClass.prototype.rematerializeRadialGrid = rematerializeRadialGrid;
252
+ }
@@ -0,0 +1,2 @@
1
+ export { registerPolarGridActions } from "./grids.js";
2
+ export { registerPolarAxisActions } from "./axes/index.js";