ggaction 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,66 @@
1
+ import { cloneAndFreeze } from "../../core/immutable.js";
2
+ import { POSITION_CHANNELS } from "../../core/vocabulary.js";
3
+ import { validateCompleteScaleType } from "./types.js";
4
+
5
+ export function validatePair(value, label) {
6
+ if (
7
+ !Array.isArray(value) ||
8
+ value.length !== 2 ||
9
+ !value.every(Number.isFinite)
10
+ ) {
11
+ throw new TypeError(`${label} must be "auto" or two finite numbers.`);
12
+ }
13
+ return cloneAndFreeze(value);
14
+ }
15
+
16
+ export function validatePositionChannel(channel) {
17
+ if (!POSITION_CHANNELS.includes(channel)) {
18
+ throw new Error(`Unknown position channel "${channel}".`);
19
+ }
20
+ return channel;
21
+ }
22
+
23
+ export function validateScaleType(type) {
24
+ return validateCompleteScaleType(type);
25
+ }
26
+
27
+ export function validateSemanticScaleType(type) {
28
+ return validateCompleteScaleType(type);
29
+ }
30
+
31
+ export function validateLinearScaleType(type) {
32
+ if (type !== "linear") {
33
+ throw new Error(`Unsupported position scale type "${type}".`);
34
+ }
35
+ return type;
36
+ }
37
+
38
+ export function validateTimeScaleType(type) {
39
+ if (type !== "time") {
40
+ throw new Error(`Unsupported temporal scale type "${type}".`);
41
+ }
42
+ return type;
43
+ }
44
+
45
+ export function validateOrdinalScaleType(type) {
46
+ if (type !== "ordinal") {
47
+ throw new Error(`Unsupported color scale type "${type}".`);
48
+ }
49
+ return type;
50
+ }
51
+
52
+ export function validateScaleDomain(domain) {
53
+ return domain === "auto" ? domain : validatePair(domain, "Scale domain");
54
+ }
55
+
56
+ export function validateScaleRange(range) {
57
+ return range === "auto" ? range : validatePair(range, "Scale range");
58
+ }
59
+
60
+ export function validateSemanticScaleDomain(domain) {
61
+ if (domain === "auto") return domain;
62
+ if (!Array.isArray(domain) || domain.length === 0) {
63
+ throw new TypeError("Scale domain must be \"auto\" or a non-empty array.");
64
+ }
65
+ return cloneAndFreeze(domain);
66
+ }
@@ -0,0 +1 @@
1
+ export * from "./scales/index.js";
@@ -0,0 +1,73 @@
1
+ import { isPlainObject } from "../../core/immutable.js";
2
+ import { validatePathCommands } from "../pathCommands.js";
3
+ import { validateGraphicProperty } from "./graphic.js";
4
+
5
+ const FINITE_PROPERTIES = new Set([
6
+ "x", "y", "x1", "y1", "x2", "y2", "width", "height", "radius",
7
+ "strokeWidth", "fontSize", "rotation", "opacity", "gap"
8
+ ]);
9
+ const NON_NEGATIVE_PROPERTIES = new Set([
10
+ "width", "height", "radius", "strokeWidth", "fontSize", "gap"
11
+ ]);
12
+ const STRING_PROPERTIES = new Set([
13
+ "background", "fill", "stroke", "fontFamily", "text", "textAlign",
14
+ "textBaseline"
15
+ ]);
16
+ const TEXT_ALIGNS = new Set(["left", "right", "center", "start", "end"]);
17
+ const TEXT_BASELINES = new Set([
18
+ "top", "hanging", "middle", "alphabetic", "ideographic", "bottom"
19
+ ]);
20
+
21
+ export function validateConcreteGraphicValue(type, property, value) {
22
+ if (FINITE_PROPERTIES.has(property) && !Number.isFinite(value)) {
23
+ throw new TypeError(`${type}.${property} must be a finite number.`);
24
+ }
25
+ if (NON_NEGATIVE_PROPERTIES.has(property) && value < 0) {
26
+ throw new RangeError(`${type}.${property} must not be negative.`);
27
+ }
28
+ if (type === "text" && property === "fontSize" && value === 0) {
29
+ throw new RangeError("text.fontSize must be positive.");
30
+ }
31
+ if (property === "opacity" && (value < 0 || value > 1)) {
32
+ throw new RangeError(`${type}.opacity must be between 0 and 1.`);
33
+ }
34
+ if (STRING_PROPERTIES.has(property) && (
35
+ typeof value !== "string" || value.length === 0
36
+ )) {
37
+ throw new TypeError(`${type}.${property} must be a non-empty string.`);
38
+ }
39
+ if (property === "fontWeight" && !(
40
+ (typeof value === "string" && value.length > 0) || Number.isFinite(value)
41
+ )) {
42
+ throw new TypeError("text.fontWeight must be a non-empty string or finite number.");
43
+ }
44
+ if (property === "textAlign" && !TEXT_ALIGNS.has(value)) {
45
+ throw new Error(`Unsupported text.textAlign "${value}".`);
46
+ }
47
+ if (property === "textBaseline" && !TEXT_BASELINES.has(value)) {
48
+ throw new Error(`Unsupported text.textBaseline "${value}".`);
49
+ }
50
+ if (property === "strokeDash" && (
51
+ !Array.isArray(value) ||
52
+ !value.every(item => Number.isFinite(item) && item >= 0)
53
+ )) {
54
+ throw new TypeError(
55
+ `${type}.strokeDash must be an array of non-negative finite numbers.`
56
+ );
57
+ }
58
+ if (type === "path" && property === "commands") {
59
+ validatePathCommands(value);
60
+ }
61
+ return value;
62
+ }
63
+
64
+ export function validateConcreteGraphicProperties(type, properties) {
65
+ if (!isPlainObject(properties)) {
66
+ throw new TypeError(`${type}.properties must be a plain object.`);
67
+ }
68
+ for (const [property, value] of Object.entries(properties)) {
69
+ validateGraphicProperty(type, property);
70
+ validateConcreteGraphicValue(type, property, value);
71
+ }
72
+ return properties;
73
+ }
@@ -0,0 +1,131 @@
1
+ const PRIMITIVE_DRAWABLE_TYPES = new Set([
2
+ "circle",
3
+ "rect",
4
+ "line",
5
+ "text",
6
+ "path"
7
+ ]);
8
+ const DRAWABLE_TYPES = new Set([
9
+ ...PRIMITIVE_DRAWABLE_TYPES,
10
+ "collection"
11
+ ]);
12
+ const STRUCTURAL_TYPES = new Set(["canvas"]);
13
+ const GRAPHIC_PROPERTIES = Object.freeze({
14
+ canvas: new Set(["width", "height", "background"]),
15
+ collection: new Set([
16
+ "items",
17
+ "x",
18
+ "y",
19
+ "x1",
20
+ "y1",
21
+ "x2",
22
+ "y2",
23
+ "width",
24
+ "height",
25
+ "radius",
26
+ "fill",
27
+ "stroke",
28
+ "strokeWidth",
29
+ "strokeDash",
30
+ "fontSize",
31
+ "fontFamily",
32
+ "fontWeight",
33
+ "text",
34
+ "textAlign",
35
+ "textBaseline",
36
+ "rotation",
37
+ "commands",
38
+ "opacity"
39
+ ]),
40
+ circle: new Set([
41
+ "x",
42
+ "y",
43
+ "radius",
44
+ "fill",
45
+ "stroke",
46
+ "strokeWidth",
47
+ "opacity",
48
+ "length"
49
+ ]),
50
+ rect: new Set([
51
+ "x",
52
+ "y",
53
+ "width",
54
+ "height",
55
+ "fill",
56
+ "stroke",
57
+ "strokeWidth",
58
+ "opacity",
59
+ "length"
60
+ ]),
61
+ line: new Set([
62
+ "x1",
63
+ "y1",
64
+ "x2",
65
+ "y2",
66
+ "stroke",
67
+ "strokeWidth",
68
+ "strokeDash",
69
+ "opacity",
70
+ "length"
71
+ ]),
72
+ text: new Set([
73
+ "x",
74
+ "y",
75
+ "text",
76
+ "fill",
77
+ "fontSize",
78
+ "fontFamily",
79
+ "fontWeight",
80
+ "textAlign",
81
+ "textBaseline",
82
+ "rotation",
83
+ "opacity",
84
+ "length"
85
+ ]),
86
+ path: new Set([
87
+ "commands",
88
+ "fill",
89
+ "stroke",
90
+ "strokeWidth",
91
+ "strokeDash",
92
+ "opacity",
93
+ "length"
94
+ ])
95
+ });
96
+
97
+ export function validateGraphicType(type) {
98
+ if (!DRAWABLE_TYPES.has(type) && !STRUCTURAL_TYPES.has(type)) {
99
+ throw new Error(`Unknown graphic type "${type}".`);
100
+ }
101
+
102
+ return type;
103
+ }
104
+
105
+ export function isDrawableGraphicType(type) {
106
+ return DRAWABLE_TYPES.has(type);
107
+ }
108
+
109
+ export function isPrimitiveDrawableGraphicType(type) {
110
+ return PRIMITIVE_DRAWABLE_TYPES.has(type);
111
+ }
112
+
113
+ export function isStructuralGraphicType(type) {
114
+ return STRUCTURAL_TYPES.has(type);
115
+ }
116
+
117
+ export function isGraphicContainerType(type) {
118
+ return type === "canvas" || type === "collection";
119
+ }
120
+
121
+ export function validateGraphicProperty(type, property) {
122
+ if (typeof property !== "string" || property.length === 0) {
123
+ throw new TypeError("editGraphics requires a non-empty property string.");
124
+ }
125
+
126
+ if (!GRAPHIC_PROPERTIES[type]?.has(property)) {
127
+ throw new Error(`Unknown ${type} graphic property "${property}".`);
128
+ }
129
+
130
+ return property;
131
+ }
@@ -0,0 +1,146 @@
1
+ import { measureTextWidth } from "../../core/textMetrics.js";
2
+ import { findGraphic, requireGraphic } from "./graphicTree.js";
3
+
4
+ function unionBounds(bounds) {
5
+ if (bounds.length === 0) return undefined;
6
+ return {
7
+ left: Math.min(...bounds.map(item => item.left)),
8
+ right: Math.max(...bounds.map(item => item.right)),
9
+ top: Math.min(...bounds.map(item => item.top)),
10
+ bottom: Math.max(...bounds.map(item => item.bottom))
11
+ };
12
+ }
13
+
14
+ function primitiveBounds(type, properties = {}) {
15
+ const strokeExtent = (properties.strokeWidth ?? 0) / 2;
16
+ if (type === "circle") {
17
+ if (![properties.x, properties.y, properties.radius].every(Number.isFinite)) {
18
+ return undefined;
19
+ }
20
+ const radius = properties.radius + strokeExtent;
21
+ return {
22
+ left: properties.x - radius,
23
+ right: properties.x + radius,
24
+ top: properties.y - radius,
25
+ bottom: properties.y + radius
26
+ };
27
+ }
28
+ if (type === "rect") {
29
+ if (![properties.x, properties.y, properties.width, properties.height]
30
+ .every(Number.isFinite)) return undefined;
31
+ return {
32
+ left: properties.x - strokeExtent,
33
+ right: properties.x + properties.width + strokeExtent,
34
+ top: properties.y - strokeExtent,
35
+ bottom: properties.y + properties.height + strokeExtent
36
+ };
37
+ }
38
+ if (type === "line") {
39
+ if (![properties.x1, properties.y1, properties.x2, properties.y2]
40
+ .every(Number.isFinite)) return undefined;
41
+ return {
42
+ left: Math.min(properties.x1, properties.x2) - strokeExtent,
43
+ right: Math.max(properties.x1, properties.x2) + strokeExtent,
44
+ top: Math.min(properties.y1, properties.y2) - strokeExtent,
45
+ bottom: Math.max(properties.y1, properties.y2) + strokeExtent
46
+ };
47
+ }
48
+ if (type === "text") {
49
+ if (
50
+ !Number.isFinite(properties.x) ||
51
+ !Number.isFinite(properties.y) ||
52
+ !Number.isFinite(properties.fontSize) ||
53
+ typeof properties.text !== "string"
54
+ ) return undefined;
55
+ const width = measureTextWidth(properties.text, properties);
56
+ const align = properties.textAlign ?? "left";
57
+ const left = align === "center" ? -width / 2 :
58
+ ["right", "end"].includes(align) ? -width : 0;
59
+ const baseline = properties.textBaseline ?? "alphabetic";
60
+ const [top, bottom] = baseline === "middle"
61
+ ? [-properties.fontSize / 2, properties.fontSize / 2]
62
+ : ["top", "hanging"].includes(baseline)
63
+ ? [0, properties.fontSize]
64
+ : ["bottom", "ideographic"].includes(baseline)
65
+ ? [-properties.fontSize, 0]
66
+ : [-properties.fontSize * 0.8, properties.fontSize * 0.2];
67
+ const rotation = properties.rotation ?? 0;
68
+ const cosine = Math.cos(rotation);
69
+ const sine = Math.sin(rotation);
70
+ const corners = [
71
+ [left, top],
72
+ [left + width, top],
73
+ [left + width, bottom],
74
+ [left, bottom]
75
+ ].map(([x, y]) => ({
76
+ x: properties.x + x * cosine - y * sine,
77
+ y: properties.y + x * sine + y * cosine
78
+ }));
79
+ return {
80
+ left: Math.min(...corners.map(point => point.x)),
81
+ right: Math.max(...corners.map(point => point.x)),
82
+ top: Math.min(...corners.map(point => point.y)),
83
+ bottom: Math.max(...corners.map(point => point.y))
84
+ };
85
+ }
86
+ if (type === "path") {
87
+ const commands = properties.commands;
88
+ if (!Array.isArray(commands)) return undefined;
89
+ const points = commands.flatMap(command => {
90
+ if (command.op === "M" || command.op === "L") return [{ x: command.x, y: command.y }];
91
+ if (command.op === "C") return [
92
+ { x: command.x1, y: command.y1 },
93
+ { x: command.x2, y: command.y2 },
94
+ { x: command.x, y: command.y }
95
+ ];
96
+ return [];
97
+ }).filter(point => Number.isFinite(point.x) && Number.isFinite(point.y));
98
+ if (points.length === 0) return undefined;
99
+ return {
100
+ left: Math.min(...points.map(point => point.x)) - strokeExtent,
101
+ right: Math.max(...points.map(point => point.x)) + strokeExtent,
102
+ top: Math.min(...points.map(point => point.y)) - strokeExtent,
103
+ bottom: Math.max(...points.map(point => point.y)) + strokeExtent
104
+ };
105
+ }
106
+ return undefined;
107
+ }
108
+
109
+ function objectBounds(graphicSpec, id, object, ancestors) {
110
+ if (ancestors.includes(id)) {
111
+ throw new Error(`Graphic attachment cycle includes "${id}".`);
112
+ }
113
+ const own = object.items === undefined
114
+ ? primitiveBounds(object.type, object.properties)
115
+ : unionBounds(object.items.flatMap(item => {
116
+ const bounds = primitiveBounds(item.type ?? object.type, item.properties);
117
+ return bounds === undefined ? [] : [bounds];
118
+ }));
119
+ const descendants = (object.children ?? []).flatMap(childId => {
120
+ const child = findGraphic(graphicSpec, childId);
121
+ if (child?.kind !== "object") {
122
+ throw new Error(`Unknown attached graphic "${childId}".`);
123
+ }
124
+ const bounds = objectBounds(graphicSpec, childId, child.object, [...ancestors, id]);
125
+ return bounds === undefined ? [] : [bounds];
126
+ });
127
+ return unionBounds([...(own === undefined ? [] : [own]), ...descendants]);
128
+ }
129
+
130
+ export function resolveConcreteGraphicBounds(graphicSpec, target) {
131
+ const found = requireGraphic(graphicSpec, target);
132
+ if (found.kind === "item") {
133
+ return primitiveBounds(found.object.type ?? found.owner.type, found.object.properties);
134
+ }
135
+ return objectBounds(graphicSpec, found.id, found.object, []);
136
+ }
137
+
138
+ export function unionConcreteGraphicBounds(graphicSpec, targets) {
139
+ if (!Array.isArray(targets)) {
140
+ throw new TypeError("Graphic bounds targets must be an array.");
141
+ }
142
+ return unionBounds(targets.flatMap(target => {
143
+ const bounds = resolveConcreteGraphicBounds(graphicSpec, target);
144
+ return bounds === undefined ? [] : [bounds];
145
+ }));
146
+ }
@@ -0,0 +1,133 @@
1
+ import { isPlainObject } from "../../core/immutable.js";
2
+
3
+ function requireGraphicSpec(graphicSpec) {
4
+ if (
5
+ !isPlainObject(graphicSpec) ||
6
+ !isPlainObject(graphicSpec.objects) ||
7
+ !Array.isArray(graphicSpec.order)
8
+ ) {
9
+ throw new TypeError("A graphic tree requires objects and order.");
10
+ }
11
+ return graphicSpec;
12
+ }
13
+
14
+ export function findGraphic(graphicSpec, target) {
15
+ requireGraphicSpec(graphicSpec);
16
+ const object = graphicSpec.objects[target];
17
+ if (object !== undefined) {
18
+ return { id: target, object, kind: "object" };
19
+ }
20
+ for (const [ownerId, owner] of Object.entries(graphicSpec.objects)) {
21
+ const itemIndex = owner.items?.findIndex(item => item.id === target);
22
+ if (itemIndex !== undefined && itemIndex !== -1) {
23
+ return {
24
+ id: target,
25
+ object: owner.items[itemIndex],
26
+ kind: "item",
27
+ ownerId,
28
+ owner,
29
+ itemIndex
30
+ };
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ export function requireGraphic(graphicSpec, target) {
37
+ const found = findGraphic(graphicSpec, target);
38
+ if (found === undefined) {
39
+ throw new Error(`Unknown graphic target "${target}".`);
40
+ }
41
+ return found;
42
+ }
43
+
44
+ export function findGraphicParent(graphicSpec, target) {
45
+ requireGraphicSpec(graphicSpec);
46
+ const found = findGraphic(graphicSpec, target);
47
+ if (found?.kind === "item") {
48
+ return {
49
+ id: found.ownerId,
50
+ object: found.owner,
51
+ kind: "itemOwner",
52
+ index: found.itemIndex
53
+ };
54
+ }
55
+ for (const [id, candidate] of Object.entries(graphicSpec.objects)) {
56
+ const index = candidate.children?.indexOf(target);
57
+ if (index !== undefined && index !== -1) {
58
+ return { id, object: candidate, kind: "parent", index };
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+
64
+ export function graphicSiblings(graphicSpec, target) {
65
+ const parent = findGraphicParent(graphicSpec, target);
66
+ if (parent?.kind === "parent") return parent.object.children;
67
+ if (parent?.kind === "itemOwner") {
68
+ return parent.object.items.map(item => item.id);
69
+ }
70
+ return graphicSpec.order;
71
+ }
72
+
73
+ function visitNamed(graphicSpec, id, visitor, path, visited) {
74
+ if (path.includes(id)) {
75
+ throw new Error(`Graphic attachment cycle includes "${id}".`);
76
+ }
77
+ const object = graphicSpec.objects[id];
78
+ if (object === undefined) {
79
+ throw new Error(`Unknown attached graphic "${id}".`);
80
+ }
81
+ if (visited.has(id)) {
82
+ throw new Error(`Graphic "${id}" is attached more than once.`);
83
+ }
84
+ visited.add(id);
85
+ visitor({ id, object, kind: "object", depth: path.length });
86
+ for (const item of object.items ?? []) {
87
+ visitor({
88
+ id: item.id,
89
+ object: item,
90
+ kind: "item",
91
+ ownerId: id,
92
+ depth: path.length + 1
93
+ });
94
+ }
95
+ for (const childId of object.children ?? []) {
96
+ visitNamed(graphicSpec, childId, visitor, [...path, id], visited);
97
+ }
98
+ }
99
+
100
+ export function walkGraphicTree(graphicSpec, visitor) {
101
+ requireGraphicSpec(graphicSpec);
102
+ if (typeof visitor !== "function") {
103
+ throw new TypeError("walkGraphicTree requires a visitor function.");
104
+ }
105
+ const visited = new Set();
106
+ for (const id of graphicSpec.order) {
107
+ visitNamed(graphicSpec, id, visitor, [], visited);
108
+ }
109
+ const orphan = Object.keys(graphicSpec.objects).find(id => !visited.has(id));
110
+ if (orphan !== undefined) {
111
+ throw new Error(`Graphic "${orphan}" is not attached to the graphic tree.`);
112
+ }
113
+ }
114
+
115
+ export function walkGraphicDrawOrder(graphicSpec, visitor) {
116
+ walkGraphicTree(graphicSpec, entry => {
117
+ if (entry.kind === "object") visitor(entry);
118
+ });
119
+ }
120
+
121
+ export function collectGraphicSubtreeIds(graphicSpec, target) {
122
+ const found = requireGraphic(graphicSpec, target);
123
+ if (found.kind === "item") {
124
+ throw new Error("Generated graphic items do not own named subtrees.");
125
+ }
126
+ const ids = [];
127
+ const visit = id => {
128
+ ids.push(id);
129
+ for (const childId of graphicSpec.objects[id].children ?? []) visit(childId);
130
+ };
131
+ visit(target);
132
+ return ids;
133
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ getPointGraphicType,
3
+ validatePointShape
4
+ } from "../pointShapes.js";