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,269 @@
1
+ import { validateUserId } from "../../../core/identifiers.js";
2
+ import {
3
+ validateNonEmptyString,
4
+ validateNonNegativeFinite,
5
+ validatePositiveFinite
6
+ } from "../../../core/validation.js";
7
+ import { resolvePolarFrame } from "../../../grammar/polar.js";
8
+ import {
9
+ formatTransformedTick,
10
+ isTransformedScaleType,
11
+ mapContinuousScaleValues,
12
+ mapOrdinalPositionValues
13
+ } from "../../../grammar/scales/index.js";
14
+ import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
15
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
16
+ import { findCoordinate } from "../../../selectors/coordinates.js";
17
+ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
18
+ "../../../theme/defaults.js";
19
+ import {
20
+ findGraphicParent,
21
+ walkGraphicDrawOrder
22
+ } from "../../../grammar/schemas/graphicTree.js";
23
+ import { valuesFromTickConfig } from "../tickValues.js";
24
+ import { formatAxisValue, validateAxisFormat } from "../axes/policy.js";
25
+
26
+ export const POLAR_AXIS_DEFAULTS = Object.freeze({
27
+ angle: 90,
28
+ line: Object.freeze({ color: "#475569", lineWidth: 1.25 }),
29
+ ticks: Object.freeze({
30
+ thetaCount: 6,
31
+ radiusCount: 5,
32
+ length: 6,
33
+ radialLength: 8,
34
+ color: "#475569",
35
+ lineWidth: 1
36
+ }),
37
+ labels: Object.freeze({
38
+ thetaOffset: 18,
39
+ radiusOffset: 10,
40
+ format: "auto",
41
+ color: DEFAULT_COLORS.text,
42
+ fontSize: 11,
43
+ fontFamily: DEFAULT_FONT_FAMILY,
44
+ fontWeight: "normal"
45
+ }),
46
+ title: Object.freeze({
47
+ thetaOffset: 42,
48
+ radiusOffset: 8,
49
+ color: DEFAULT_COLORS.strongText,
50
+ fontSize: 13,
51
+ fontFamily: DEFAULT_FONT_FAMILY,
52
+ fontWeight: 600
53
+ })
54
+ });
55
+
56
+ export const POLAR_GRID_DEFAULTS = Object.freeze({
57
+ color: "#d7e0ea",
58
+ lineWidth: 1,
59
+ strokeDash: Object.freeze([]),
60
+ thetaCount: 6,
61
+ radiusCount: 5
62
+ });
63
+
64
+ export function polarGuideNames(kind) {
65
+ if (kind === "theta") {
66
+ return Object.freeze({
67
+ channel: "theta",
68
+ axis: "thetaAxis",
69
+ line: "thetaAxisLine",
70
+ ticks: "thetaAxisTicks",
71
+ labels: "thetaAxisLabels",
72
+ title: "thetaAxisTitle",
73
+ grid: "thetaGridLines"
74
+ });
75
+ }
76
+ if (kind === "radial" || kind === "radius") {
77
+ return Object.freeze({
78
+ channel: "radius",
79
+ axis: "radiusAxis",
80
+ line: "radialAxisLine",
81
+ ticks: "radialAxisTicks",
82
+ labels: "radialAxisLabels",
83
+ title: "radialAxisTitle",
84
+ grid: "radialGridCircles"
85
+ });
86
+ }
87
+ throw new Error(`Unknown Polar guide kind "${kind}".`);
88
+ }
89
+
90
+ export function resolvePolarGuideResources(program, kind, args, operation) {
91
+ const { channel } = polarGuideNames(kind);
92
+ const requestedCoordinate = args.coordinate === undefined
93
+ ? undefined
94
+ : validateUserId(args.coordinate, `${operation} coordinate id`);
95
+ const requestedScale = args.scale === undefined
96
+ ? undefined
97
+ : validateUserId(args.scale, `${operation} scale id`);
98
+ let layers = program.semanticSpec.layers.filter(layer =>
99
+ layer.encoding?.[channel]?.scale !== undefined
100
+ );
101
+ if (requestedCoordinate !== undefined) {
102
+ layers = layers.filter(layer => layer.coordinate === requestedCoordinate);
103
+ }
104
+ if (requestedScale !== undefined) {
105
+ layers = layers.filter(
106
+ layer => layer.encoding[channel].scale === requestedScale
107
+ );
108
+ }
109
+ if (layers.length === 0) {
110
+ throw new Error(`${operation} requires a stored ${channel} encoding.`);
111
+ }
112
+ if (layers.some(layer => layer.coordinate === undefined)) {
113
+ throw new Error(`${operation} requires a stored Polar coordinate.`);
114
+ }
115
+ const coordinates = [...new Set(layers.map(layer => layer.coordinate))];
116
+ if (coordinates.length > 1) {
117
+ throw new Error(
118
+ `${operation} found multiple Polar coordinates; provide coordinate explicitly.`
119
+ );
120
+ }
121
+ const coordinate = requestedCoordinate ?? coordinates[0];
122
+ if (findCoordinate(program, coordinate)?.type !== "polar") {
123
+ throw new Error(`${operation} requires a Polar coordinate.`);
124
+ }
125
+ const scales = [...new Set(
126
+ layers.map(layer => layer.encoding[channel].scale)
127
+ )];
128
+ if (scales.length > 1) {
129
+ throw new Error(
130
+ `${operation} found multiple ${channel} scales; provide scale explicitly.`
131
+ );
132
+ }
133
+ const scale = requestedScale ?? scales[0];
134
+ const resolved = program.resolvedScales[scale];
135
+ const discrete = ["ordinal", "band", "point"].includes(resolved?.type);
136
+ const continuous = ["linear", "time"].includes(resolved?.type) ||
137
+ isTransformedScaleType(resolved?.type);
138
+ if ((channel === "theta" && !continuous && !discrete) ||
139
+ (channel === "radius" && !continuous)) {
140
+ throw new Error(
141
+ `${operation} requires a supported resolved ${channel} scale "${scale}".`
142
+ );
143
+ }
144
+ const related = new Set(layers.map(layer => layer.id));
145
+ let before;
146
+ walkGraphicDrawOrder(program.graphicSpec, ({ id }) => {
147
+ if (before === undefined && related.has(id)) before = id;
148
+ });
149
+ if (before === undefined) {
150
+ throw new Error(`${operation} requires related materialized marks.`);
151
+ }
152
+ const owner = findGraphicParent(program.graphicSpec, before);
153
+ return Object.freeze({
154
+ channel,
155
+ coordinate,
156
+ scale,
157
+ before,
158
+ ...(owner?.kind === "parent" ? { parent: owner.id } : {})
159
+ });
160
+ }
161
+
162
+ export function resolvePolarFrameForProgram(program) {
163
+ const bounds = resolveGraphicBounds(program);
164
+ if (bounds === undefined) {
165
+ throw new Error("Polar guides require graphical Canvas bounds.");
166
+ }
167
+ return resolvePolarFrame(bounds);
168
+ }
169
+
170
+ export function resolvePolarTickValues(program, config) {
171
+ const scale = program.resolvedScales[config.scale];
172
+ const discrete = ["ordinal", "band", "point"].includes(scale?.type);
173
+ if (discrete) {
174
+ if (config.mode !== "values") {
175
+ throw new Error("Discrete theta guides require explicit or inferred values.");
176
+ }
177
+ const domain = new Set(scale.domain);
178
+ if (!config.values.every(value => domain.has(value))) {
179
+ throw new RangeError("Polar guide values must be inside the scale domain.");
180
+ }
181
+ return config.values;
182
+ }
183
+ const values = valuesFromTickConfig(program, config);
184
+ const low = Math.min(...scale.domain);
185
+ const high = Math.max(...scale.domain);
186
+ if (!values.every(value => value >= low && value <= high)) {
187
+ throw new RangeError("Polar guide values must be inside the scale domain.");
188
+ }
189
+ return values;
190
+ }
191
+
192
+ export function mapPolarGuideValues(program, config) {
193
+ const scale = program.resolvedScales[config.scale];
194
+ const values = resolvePolarTickValues(program, config);
195
+ const positions = ["ordinal", "band", "point"].includes(scale.type)
196
+ ? mapOrdinalPositionValues(values, scale)
197
+ : mapContinuousScaleValues(values, scale);
198
+ return Object.freeze({ values, positions });
199
+ }
200
+
201
+ export function formatPolarGuideValues(program, config, values) {
202
+ const scale = program.resolvedScales[config.scale];
203
+ if (scale.type === "time" && config.format === "auto") {
204
+ return formatTimeTicks(values, scale.domain);
205
+ }
206
+ return values.map(value => formatAxisValue(
207
+ value,
208
+ scale.type,
209
+ config.format,
210
+ item => scale.type === "time"
211
+ ? formatTimeTick(item, scale.domain)
212
+ : isTransformedScaleType(scale.type)
213
+ ? formatTransformedTick(scale.type, item)
214
+ : String(item)
215
+ ));
216
+ }
217
+
218
+ export function normalizePolarTickMode(program, scale, args, defaultCount) {
219
+ const resolved = program.resolvedScales[scale];
220
+ if (Object.hasOwn(args, "values")) {
221
+ return { mode: "values", values: args.values, inferredValues: false };
222
+ }
223
+ if (Object.hasOwn(args, "count")) {
224
+ return { mode: "count", count: args.count, inferredValues: false };
225
+ }
226
+ if (["ordinal", "band", "point"].includes(resolved?.type)) {
227
+ return {
228
+ mode: "values",
229
+ values: resolved.domain,
230
+ inferredValues: true
231
+ };
232
+ }
233
+ return { mode: "count", count: defaultCount, inferredValues: true };
234
+ }
235
+
236
+ export function validatePolarTickConfig(config, label) {
237
+ if (config.mode === "count" &&
238
+ (!Number.isInteger(config.count) || config.count <= 0)) {
239
+ throw new RangeError(`${label} count must be a positive integer.`);
240
+ }
241
+ if (config.mode === "values" && (
242
+ !Array.isArray(config.values) || config.values.length === 0 ||
243
+ !config.values.every(value =>
244
+ typeof value === "string" || typeof value === "boolean" ||
245
+ (typeof value === "number" && Number.isFinite(value))
246
+ )
247
+ )) {
248
+ throw new TypeError(`${label} values must be a non-empty domain-value array.`);
249
+ }
250
+ }
251
+
252
+ export function validatePolarLineStyle(config, label) {
253
+ validateNonEmptyString(config.color, `${label} color`);
254
+ validateNonNegativeFinite(config.lineWidth, `${label} lineWidth`);
255
+ }
256
+
257
+ export function validatePolarTextStyle(config, label) {
258
+ validateNonEmptyString(config.color, `${label} color`);
259
+ validatePositiveFinite(config.fontSize, `${label} fontSize`);
260
+ validateNonEmptyString(config.fontFamily, `${label} fontFamily`);
261
+ if (typeof config.fontWeight !== "string" &&
262
+ !Number.isFinite(config.fontWeight)) {
263
+ throw new TypeError(`${label} fontWeight must be a string or number.`);
264
+ }
265
+ }
266
+
267
+ export function validatePolarLabelFormat(format) {
268
+ return validateAxisFormat(format);
269
+ }
@@ -3,7 +3,7 @@ import {
3
3
  isTransformedScaleType,
4
4
  readQuantitativeField,
5
5
  transformedTicks
6
- } from "../../grammar/scales.js";
6
+ } from "../../grammar/scales/index.js";
7
7
  import { niceTicks, timeTicks } from "../../grammar/ticks.js";
8
8
  import { findDataset } from "../../selectors/datasets.js";
9
9
  import { findSemanticScale } from "../../selectors/scales.js";
@@ -1,9 +1,11 @@
1
1
  import { registerCanvasActions } from "./canvas/index.js";
2
2
  import { registerCoordinateActions } from "./coordinates/index.js";
3
+ import { registerCompositionActions } from "./composition/index.js";
3
4
  import { registerDataActions } from "./data/index.js";
4
5
  import { registerEncodingActions } from "./encodings/index.js";
5
6
  import { registerErrorBandActions } from "./errorBands/index.js";
6
7
  import { registerErrorBarActions } from "./errorBars/index.js";
8
+ import { registerFacetActions } from "./facets/index.js";
7
9
  import { registerBoxPlotActions } from "./boxPlots/index.js";
8
10
  import { registerGuideActions } from "./guides/index.js";
9
11
  import { registerMarkActions } from "./marks/index.js";
@@ -15,6 +17,8 @@ import { registerSelectionActions } from "./selection/index.js";
15
17
 
16
18
  export function registerActions(ProgramClass) {
17
19
  registerPrimitiveActions(ProgramClass);
20
+ registerCompositionActions(ProgramClass);
21
+ registerFacetActions(ProgramClass);
18
22
  registerCanvasActions(ProgramClass);
19
23
  registerDataActions(ProgramClass);
20
24
  registerMarkActions(ProgramClass);
@@ -3,7 +3,7 @@ import {
3
3
  resolveOptionalUserId,
4
4
  validateUserId
5
5
  } from "../../core/identifiers.js";
6
- import { readQuantitativeField } from "../../grammar/scales.js";
6
+ import { readQuantitativeField } from "../../grammar/scales/index.js";
7
7
  import { findDataset } from "../../selectors/datasets.js";
8
8
  import { hasLayer, resolveEligibleLayer } from "../../selectors/layers.js";
9
9
  import { findSemanticScale } from "../../selectors/scales.js";
@@ -0,0 +1,239 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import {
4
+ validateNonEmptyString,
5
+ validateNonNegativeFinite,
6
+ validateUnitInterval
7
+ } from "../../../core/validation.js";
8
+ import { deriveArcSectors } from "../../../grammar/arcs.js";
9
+ import { buildAnnularSectorCommands } from "../../../grammar/polarPaths.js";
10
+ import { resolvePolarFrame } from "../../../grammar/polar.js";
11
+ import { mapOrdinalValues } from "../../../grammar/scales/index.js";
12
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
13
+ import { canMaterializeArc } from "../../../materialization/marks/index.js";
14
+ import { findDataset } from "../../../selectors/datasets.js";
15
+ import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
16
+ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
17
+ import { resolveMarkGraphicPlacement } from
18
+ "../../../materialization/graphicHierarchy.js";
19
+ import { rematerializeHighlightBaseline } from "../lifecycle.js";
20
+ import {
21
+ assertMarkAvailable,
22
+ applyLayeredMarkInheritance,
23
+ materializeInheritedMark,
24
+ resolveLayeredMarkInheritance,
25
+ resolveMarkData,
26
+ resolveMarkId,
27
+ validateMarkOptions
28
+ } from "../shared.js";
29
+
30
+ const CREATE_OPTIONS = Object.freeze([
31
+ "id", "data", "innerRadius", "padAngle", "fill", "opacity", "stroke",
32
+ "strokeWidth"
33
+ ]);
34
+ const EDIT_OPTIONS = Object.freeze([
35
+ "target", "innerRadius", "padAngle", "fill", "opacity", "stroke",
36
+ "strokeWidth"
37
+ ]);
38
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
39
+
40
+ function validateInnerRadius(value) {
41
+ if (!Number.isFinite(value) || value < 0 || value >= 1) {
42
+ throw new RangeError(
43
+ "Arc innerRadius must be from 0 (inclusive) to 1 (exclusive)."
44
+ );
45
+ }
46
+ return value;
47
+ }
48
+
49
+ function normalizeConfig(args, previous = {}) {
50
+ return {
51
+ ...previous,
52
+ ...(Object.hasOwn(args, "innerRadius")
53
+ ? { innerRadius: validateInnerRadius(args.innerRadius) }
54
+ : {}),
55
+ ...(Object.hasOwn(args, "padAngle")
56
+ ? { padAngle: validateNonNegativeFinite(args.padAngle, "Arc padAngle") }
57
+ : {}),
58
+ ...(Object.hasOwn(args, "fill")
59
+ ? { fill: validateNonEmptyString(args.fill, "Arc fill") }
60
+ : {}),
61
+ ...(Object.hasOwn(args, "opacity")
62
+ ? { opacity: validateUnitInterval(args.opacity, "Arc opacity") }
63
+ : {}),
64
+ ...(Object.hasOwn(args, "stroke")
65
+ ? { stroke: validateNonEmptyString(args.stroke, "Arc stroke") }
66
+ : {}),
67
+ ...(Object.hasOwn(args, "strokeWidth")
68
+ ? {
69
+ strokeWidth: validateNonNegativeFinite(
70
+ args.strokeWidth,
71
+ "Arc strokeWidth"
72
+ )
73
+ }
74
+ : {})
75
+ };
76
+ }
77
+
78
+ const createArcMark = action(
79
+ {
80
+ op: "createArcMark",
81
+ description: "Create a semantic arc mark and empty path collection."
82
+ },
83
+ function (args = {}) {
84
+ validateMarkOptions(args, CREATE_OPTIONS, "createArcMark");
85
+ const id = resolveMarkId(this, args.id, {
86
+ defaultId: "arc",
87
+ label: "Arc mark id",
88
+ markType: "arc",
89
+ operation: "createArcMark"
90
+ });
91
+ const inherited = resolveLayeredMarkInheritance(this, args, "arc");
92
+ const { data } = resolveMarkData(this, {
93
+ ...args,
94
+ ...(args.data === undefined && this.context.currentData === undefined &&
95
+ inherited?.data !== undefined ? { data: inherited.data } : {})
96
+ });
97
+ assertMarkAvailable(this, id);
98
+ const config = normalizeConfig(args, {
99
+ innerRadius: 0,
100
+ padAngle: 0,
101
+ opacity: 1,
102
+ stroke: "#ffffff",
103
+ strokeWidth: 1
104
+ });
105
+ let created = this
106
+ .editSemantic({ property: `layer[${id}].mark.type`, value: "arc" })
107
+ .editSemantic({ property: `layer[${id}].data`, value: data });
108
+ created = applyLayeredMarkInheritance(created, id, inherited);
109
+ created = created
110
+ .createGraphics({
111
+ id,
112
+ type: "path",
113
+ length: 0,
114
+ ...resolveMarkGraphicPlacement(created, { data, markType: "arc" })
115
+ })
116
+ ._withMarkConfig(id, config);
117
+ return materializeInheritedMark(created, id);
118
+ }
119
+ );
120
+
121
+ function requireArc(program, id) {
122
+ const layer = findLayer(program, id);
123
+ const dataset = findDataset(program, layer?.data);
124
+ if (layer?.mark?.type !== "arc" || program.graphicSpec.objects[id]?.type !== "path") {
125
+ throw new Error(`Unknown arc mark "${id}".`);
126
+ }
127
+ if (dataset === undefined) {
128
+ throw new Error(`Arc mark "${id}" requires an existing dataset.`);
129
+ }
130
+ return { layer, dataset };
131
+ }
132
+
133
+ const rematerializeArcMark = action(
134
+ {
135
+ op: "rematerializeArcMark",
136
+ description: "Recompute concrete annular-sector paths."
137
+ },
138
+ function (args = {}) {
139
+ validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeArcMark");
140
+ const id = validateUserId(args.id, "Arc mark id");
141
+ const highlighted = rematerializeHighlightBaseline(this, {
142
+ target: id,
143
+ operation: "rematerializeArcMark",
144
+ resetProperty: "length",
145
+ resetValue: 0
146
+ });
147
+ if (highlighted !== undefined) return highlighted;
148
+ const { layer, dataset } = requireArc(this, id);
149
+ if (!canMaterializeArc(this, layer)) {
150
+ throw new Error(`Arc mark "${id}" does not have a complete encoding.`);
151
+ }
152
+ const thetaScaleId = layer.encoding.theta.scale;
153
+ const radiusScaleId = layer.encoding?.radius?.scale;
154
+ const colorScaleId = layer.encoding?.color?.scale;
155
+ let resolved = this.rematerializeScale({ id: thetaScaleId });
156
+ if (radiusScaleId !== undefined) {
157
+ resolved = resolved.rematerializeScale({ id: radiusScaleId });
158
+ }
159
+ if (colorScaleId !== undefined) {
160
+ resolved = resolved.rematerializeScale({ id: colorScaleId });
161
+ }
162
+ const config = resolved.markConfigs[id] ?? {};
163
+ const derived = deriveArcSectors(dataset.values, layer, {
164
+ thetaScale: resolved.resolvedScales[thetaScaleId],
165
+ ...(radiusScaleId === undefined
166
+ ? {}
167
+ : { radiusScale: resolved.resolvedScales[radiusScaleId] }),
168
+ frame: resolvePolarFrame(resolveGraphicBounds(resolved)),
169
+ innerRadiusRatio: config.innerRadius ?? 0
170
+ });
171
+ const frame = resolvePolarFrame(resolveGraphicBounds(resolved));
172
+ const commands = derived.sectors.map(sector => buildAnnularSectorCommands({
173
+ frame,
174
+ startTheta: sector.startTheta,
175
+ endTheta: sector.endTheta,
176
+ innerRadius: sector.innerRadius,
177
+ outerRadius: sector.outerRadius,
178
+ padAngle: config.padAngle ?? 0
179
+ }));
180
+ const fills = colorScaleId === undefined
181
+ ? commands.map(() => config.fill ?? DEFAULT_COLORS.mark)
182
+ : mapOrdinalValues(
183
+ derived.sectors.map(sector => sector.color),
184
+ resolved.resolvedScales[colorScaleId].domain,
185
+ resolved.resolvedScales[colorScaleId].range
186
+ );
187
+ return resolved
188
+ .editGraphics({ target: id, property: "length", value: commands.length })
189
+ .editGraphics({ target: id, property: "commands", value: commands })
190
+ .editGraphics({ target: id, property: "fill", value: fills })
191
+ .editGraphics({ target: id, property: "opacity", value: config.opacity ?? 1 })
192
+ .editGraphics({ target: id, property: "stroke", value: config.stroke ?? "#ffffff" })
193
+ .editGraphics({ target: id, property: "strokeWidth", value: config.strokeWidth ?? 1 })
194
+ .editGraphics({
195
+ target: id,
196
+ property: "strokeDash",
197
+ value: commands.map(() => [])
198
+ });
199
+ }
200
+ );
201
+
202
+ const editArcMark = action(
203
+ {
204
+ op: "editArcMark",
205
+ description: "Edit arc geometry and appearance."
206
+ },
207
+ function (args = {}) {
208
+ validateMarkOptions(args, EDIT_OPTIONS, "editArcMark");
209
+ if (Object.keys(args).every(key => key === "target")) {
210
+ throw new Error(
211
+ "editArcMark requires innerRadius, padAngle, fill, opacity, stroke, or strokeWidth."
212
+ );
213
+ }
214
+ const target = Object.hasOwn(args, "target")
215
+ ? validateUserId(args.target, "Arc mark id")
216
+ : undefined;
217
+ const layer = resolveEligibleLayer(this, {
218
+ target,
219
+ predicate: candidate => candidate.mark?.type === "arc",
220
+ label: "arc mark"
221
+ });
222
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
223
+ throw new Error("editArcMark fill cannot be combined with a color encoding.");
224
+ }
225
+ const next = this._withMarkConfig(
226
+ layer.id,
227
+ normalizeConfig(args, this.markConfigs[layer.id])
228
+ );
229
+ return canMaterializeArc(next, layer)
230
+ ? next.rematerializeArcMark({ id: layer.id })
231
+ : next;
232
+ }
233
+ );
234
+
235
+ export function registerArcMarkActions(ProgramClass) {
236
+ ProgramClass.prototype.createArcMark = createArcMark;
237
+ ProgramClass.prototype.editArcMark = editArcMark;
238
+ ProgramClass.prototype.rematerializeArcMark = rematerializeArcMark;
239
+ }
@@ -0,0 +1 @@
1
+ export { registerArcMarkActions } from "./actions.js";