ggaction 0.0.20 → 0.0.22

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 (254) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +6 -2
  7. package/src/BasicChartProgram.js +1 -1
  8. package/src/accessibility.js +1 -0
  9. package/src/actions/boxPlots/components.js +1 -96
  10. package/src/actions/boxPlots/create.js +6 -1
  11. package/src/actions/boxPlots/edit.js +8 -0
  12. package/src/actions/boxPlots/materialize.js +3 -0
  13. package/src/actions/boxPlots/options.js +1 -1
  14. package/src/actions/canvas/actions.js +2 -1
  15. package/src/actions/canvas/index.js +3 -0
  16. package/src/actions/canvas/plotBorder.js +2 -1
  17. package/src/actions/canvas/plotOrder.js +14 -0
  18. package/src/actions/charts/area.js +1 -1
  19. package/src/actions/charts/bar.js +4 -4
  20. package/src/actions/charts/density.js +1 -1
  21. package/src/actions/charts/ecdf.js +1 -1
  22. package/src/actions/charts/endpoints.js +54 -5
  23. package/src/actions/charts/heatmap.js +1 -1
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +1 -1
  26. package/src/actions/charts/index.js +2 -1
  27. package/src/actions/charts/interval-regression.js +1 -1
  28. package/src/actions/charts/line.js +1 -1
  29. package/src/actions/charts/radar.js +63 -3
  30. package/src/actions/charts/raincloud.js +37 -6
  31. package/src/actions/charts/rug-strip.js +2 -2
  32. package/src/actions/charts/scatter.js +3 -3
  33. package/src/actions/charts/text.js +1 -1
  34. package/src/actions/composition/actions.js +10 -3
  35. package/src/actions/coordinates/actions.js +2 -0
  36. package/src/actions/data/basic.js +6 -0
  37. package/src/actions/data/derived.js +6 -2
  38. package/src/actions/data/edit.js +21 -35
  39. package/src/actions/data/filter.js +1 -376
  40. package/src/actions/data/flatten.js +19 -0
  41. package/src/actions/data/index.js +6 -0
  42. package/src/actions/data/lookup.js +50 -0
  43. package/src/actions/data/raincloudFilter.js +56 -0
  44. package/src/actions/data/shared.js +10 -0
  45. package/src/actions/distributions/revision.js +3 -1
  46. package/src/actions/encodings/angle.js +2 -1
  47. package/src/actions/encodings/appearance.js +38 -13
  48. package/src/actions/encodings/channels.js +14 -3
  49. package/src/actions/encodings/color/continuous.js +26 -10
  50. package/src/actions/encodings/color/index.js +50 -20
  51. package/src/actions/encodings/color/policy.js +57 -4
  52. package/src/actions/encodings/parallel.js +4 -0
  53. package/src/actions/encodings/position/policies/bar.js +10 -6
  54. package/src/actions/encodings/position/policies/line.js +1 -152
  55. package/src/actions/encodings/position/resolve.js +4 -1
  56. package/src/actions/encodings/ranged.js +46 -0
  57. package/src/actions/encodings/remove.js +1 -1
  58. package/src/actions/encodings/ruleAppearance.js +7 -6
  59. package/src/actions/encodings/shared.js +14 -2
  60. package/src/actions/encodings/stroke.js +20 -14
  61. package/src/actions/encodings/strokeDash.js +9 -11
  62. package/src/actions/encodings/text.js +30 -2
  63. package/src/actions/errorBars/edit.js +24 -4
  64. package/src/actions/facets/actions.js +1 -793
  65. package/src/actions/facets/derive.js +34 -5
  66. package/src/actions/facets/replay.js +1 -38
  67. package/src/actions/gradientPlots/components.js +21 -9
  68. package/src/actions/gradientPlots/create.js +4 -2
  69. package/src/actions/gradientPlots/edit.js +18 -1
  70. package/src/actions/gradientPlots/materialize.js +35 -36
  71. package/src/actions/gradientPlots/options.js +1 -1
  72. package/src/actions/gradientPlots/paint.js +13 -7
  73. package/src/actions/guides/applicability.js +2 -0
  74. package/src/actions/guides/axes/axes.js +39 -0
  75. package/src/actions/guides/axes/axis.js +9 -5
  76. package/src/actions/guides/axes/edit.js +11 -8
  77. package/src/actions/guides/axes/labels.js +24 -8
  78. package/src/actions/guides/axes/parallel/policy.js +3 -5
  79. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  80. package/src/actions/guides/axes/policy.js +22 -0
  81. package/src/actions/guides/axes/tickGroups.js +4 -2
  82. package/src/actions/guides/axes/ticks.js +22 -4
  83. package/src/actions/guides/axes/titles.js +19 -6
  84. package/src/actions/guides/facadeAxes.js +3 -0
  85. package/src/actions/guides/grids/grid.js +3 -1
  86. package/src/actions/guides/grids/resolve.js +7 -11
  87. package/src/actions/guides/guides.js +21 -1
  88. package/src/actions/guides/legends/categorical/actions.js +1 -515
  89. package/src/actions/guides/legends/categorical/layout.js +5 -2
  90. package/src/actions/guides/legends/categorical/options.js +4 -0
  91. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  92. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  93. package/src/actions/guides/legends/colorSize.js +2 -0
  94. package/src/actions/guides/legends/continuous/common.js +1 -1
  95. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  96. package/src/actions/guides/legends/continuous/interval.js +1 -1
  97. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  98. package/src/actions/guides/legends/edit.js +1 -1
  99. package/src/actions/guides/legends/focused.js +1 -1
  100. package/src/actions/guides/legends/lane.js +1 -1
  101. package/src/actions/guides/legends/sampling.js +1 -140
  102. package/src/actions/guides/legends/strokeWidth.js +3 -2
  103. package/src/actions/guides/legends/transition.js +19 -2
  104. package/src/actions/guides/polar/axes/facade.js +5 -4
  105. package/src/actions/guides/polar/axes/labels.js +5 -4
  106. package/src/actions/guides/polar/axes/titles.js +5 -4
  107. package/src/actions/guides/polar/resolve.js +2 -1
  108. package/src/actions/guides/tickValues.js +7 -7
  109. package/src/actions/marks/arc/actions.js +5 -7
  110. package/src/actions/marks/area/actions.js +9 -3
  111. package/src/actions/marks/bar/create.js +4 -2
  112. package/src/actions/marks/bar/edit.js +9 -6
  113. package/src/actions/marks/bar/materialize.js +5 -3
  114. package/src/actions/marks/dependentReferences.js +87 -0
  115. package/src/actions/marks/index.js +2 -0
  116. package/src/actions/marks/lifecycle.js +2 -1
  117. package/src/actions/marks/line/actions.js +24 -6
  118. package/src/actions/marks/line/materialize.js +7 -3
  119. package/src/actions/marks/point/create.js +5 -4
  120. package/src/actions/marks/point/edit.js +6 -3
  121. package/src/actions/marks/point/jitter.js +6 -6
  122. package/src/actions/marks/point/materialize.js +36 -39
  123. package/src/actions/marks/rect/actions.js +16 -4
  124. package/src/actions/marks/references.js +183 -35
  125. package/src/actions/marks/rule/actions.js +20 -9
  126. package/src/actions/marks/shared.js +2 -1
  127. package/src/actions/marks/text/actions.js +10 -3
  128. package/src/actions/marks/tick/actions.js +11 -2
  129. package/src/actions/primitives/editGraphics.js +28 -3
  130. package/src/actions/primitives/index.js +9 -0
  131. package/src/actions/primitives/semanticAction.js +13 -7
  132. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  133. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  134. package/src/actions/scales/channels.js +1 -1
  135. package/src/actions/scales/consumers/common.js +19 -1
  136. package/src/actions/scales/consumers/families.js +2 -2
  137. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  138. package/src/actions/scales/edit.js +11 -0
  139. package/src/actions/scales/editPolicy.js +5 -3
  140. package/src/actions/scales/preview.js +2 -1
  141. package/src/actions/scales/quantitativeColor.js +2 -4
  142. package/src/actions/selection/actions.js +10 -1
  143. package/src/actions/theme/reconcile.js +37 -9
  144. package/src/core/compositionState.js +21 -4
  145. package/src/core/datasetReferences.js +17 -0
  146. package/src/core/immutable.js +1 -0
  147. package/src/core/resourceReferences.js +5 -3
  148. package/src/core/validation.js +4 -0
  149. package/src/core/vocabulary.js +24 -2
  150. package/src/grammar/aggregate.js +1 -0
  151. package/src/grammar/arcs.js +1 -1
  152. package/src/grammar/areaSeries.js +1 -1
  153. package/src/grammar/axisTextPlacement.js +1 -1
  154. package/src/grammar/bars/policy.js +2 -2
  155. package/src/grammar/datasetDependencies.js +97 -0
  156. package/src/grammar/datasetSchema.js +31 -1
  157. package/src/grammar/facetHeaders.js +1 -0
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/legendIntent.js +6 -2
  160. package/src/grammar/facets/scales.js +27 -7
  161. package/src/grammar/flatten.js +85 -0
  162. package/src/grammar/histogram.js +13 -1
  163. package/src/grammar/horizon.js +1 -494
  164. package/src/grammar/interval.js +4 -3
  165. package/src/grammar/jitter.js +2 -2
  166. package/src/grammar/legendSpacing.js +45 -0
  167. package/src/grammar/lineMissing.js +90 -0
  168. package/src/grammar/lineSeries.js +1 -376
  169. package/src/grammar/lookup.js +97 -0
  170. package/src/grammar/markFilter.js +4 -2
  171. package/src/grammar/markLabels.js +8 -3
  172. package/src/grammar/numeric.js +1 -1
  173. package/src/grammar/pathCommands.js +1 -1
  174. package/src/grammar/pathSeries.js +1 -109
  175. package/src/grammar/pointPolygon.js +81 -0
  176. package/src/grammar/pointShapes.js +15 -15
  177. package/src/grammar/rects.js +1 -1
  178. package/src/grammar/regression/derive.js +1 -195
  179. package/src/grammar/scales/appearance.js +1 -1
  180. package/src/grammar/scales/color.js +1 -1
  181. package/src/grammar/scales/colorConsumers.js +1 -63
  182. package/src/grammar/scales/continuous.js +58 -1
  183. package/src/grammar/scales/definition.js +1 -1
  184. package/src/grammar/scales/ordinal.js +5 -2
  185. package/src/grammar/scales/policies.js +3 -3
  186. package/src/grammar/schemas/graphicBounds.js +40 -12
  187. package/src/grammar/schemas/graphicClip.js +23 -0
  188. package/src/grammar/schemas/graphicTree.js +16 -0
  189. package/src/grammar/statisticalReference.js +22 -0
  190. package/src/grammar/text.js +65 -1
  191. package/src/grammar/ticks.js +92 -1
  192. package/src/grammar/transformTopology.js +3 -1
  193. package/src/grammar/transforms.js +24 -0
  194. package/src/inspection.js +1 -495
  195. package/src/inspectionDescriptors.js +1 -1
  196. package/src/layout/canvas.js +1 -1
  197. package/src/layout/facetContentFlow.js +101 -0
  198. package/src/layout/facetHeaderTitles.js +1 -0
  199. package/src/layout/facetTopLane.js +29 -0
  200. package/src/layout/facets.js +1 -1
  201. package/src/layout/legendGroup.js +1 -1
  202. package/src/layout/legendItems.js +26 -10
  203. package/src/layout/legendSpacing.js +66 -0
  204. package/src/layout/plotDrawOrder.js +17 -0
  205. package/src/materialization/bars/histogram.js +8 -2
  206. package/src/materialization/bars/resolve.js +1 -1
  207. package/src/materialization/composition.js +1 -0
  208. package/src/materialization/compositionSnapshot.js +22 -2
  209. package/src/materialization/dataProvenance.js +1 -1
  210. package/src/materialization/facetGuides/layout.js +1 -1
  211. package/src/materialization/facetGuides/placement.js +23 -11
  212. package/src/materialization/facetGuides/preparation.js +43 -24
  213. package/src/materialization/facetHeaders.js +1 -1
  214. package/src/materialization/facetTopLane.js +25 -0
  215. package/src/materialization/facets.js +1 -1
  216. package/src/materialization/graphicHierarchy.js +5 -0
  217. package/src/materialization/graphicTranslation.js +2 -0
  218. package/src/materialization/guides/layout.js +1 -1
  219. package/src/materialization/guides/resources.js +1 -1
  220. package/src/materialization/legends.js +1 -1
  221. package/src/materialization/markClip.js +26 -0
  222. package/src/materialization/marks/capabilities.js +3 -2
  223. package/src/materialization/marks/grouping.js +7 -5
  224. package/src/materialization/marks/index.js +4 -2
  225. package/src/materialization/marks/policies.js +4 -0
  226. package/src/materialization/plotDrawOrder.js +44 -0
  227. package/src/materialization/rect.js +11 -3
  228. package/src/materialization/scaleGuideDependencies.js +13 -5
  229. package/src/materialization/selection/items/common.js +1 -1
  230. package/src/materialization/selection/items/path.js +1 -0
  231. package/src/materialization/selection/policies/index.js +2 -1
  232. package/src/materialization/text.js +10 -5
  233. package/src/mcp/cli.js +0 -0
  234. package/src/persistence/validation.js +108 -18
  235. package/src/persistence.js +4 -2
  236. package/src/renderers/canvas/index.js +16 -0
  237. package/src/renderers/canvas/isolated.js +11 -1
  238. package/src/renderers/canvas/native.js +7 -0
  239. package/src/renderers/canvas/scene.js +66 -0
  240. package/src/renderers/canvas/text.js +3 -2
  241. package/src/renderers/svg.js +1 -617
  242. package/src/renderers/svgSerialization.js +643 -0
  243. package/src/renderers/transitionScene.js +67 -0
  244. package/src/scene.js +89 -0
  245. package/src/selectors/axes.js +1 -1
  246. package/src/selectors/datasets.js +1 -0
  247. package/src/selectors/grids.js +9 -0
  248. package/src/selectors/markClip.js +28 -0
  249. package/src/selectors/raincloud.js +7 -0
  250. package/src/version.js +1 -1
  251. package/types/basic.d.ts +7 -0
  252. package/types/index.d.ts +8 -0
  253. package/types/program.d.ts +270 -60
  254. package/types/scene.d.ts +35 -0
@@ -1,617 +1 @@
1
- import { isOwned } from "../core/immutable.js";
2
- import {
3
- requireSingleOrderedGraphicByType,
4
- walkGraphicTreeEvents
5
- } from "../grammar/schemas/graphicTree.js";
6
- import {
7
- validateConcreteGraphicProperties
8
- } from "../grammar/schemas/concreteGraphic.js";
9
- import { resolvePathCommandBounds } from
10
- "../grammar/schemas/graphicBounds.js";
11
- import {
12
- isLinearGradientPaint,
13
- resolveLinearGradientCoordinates,
14
- validateFillPaint
15
- } from "../grammar/paint.js";
16
- import {
17
- requireFiniteProperty,
18
- requireStringProperty
19
- } from "./canvas/validation.js";
20
- import { normalizeRendererFontWeight } from "./text.js";
21
- import { validateRendererOptions } from "./options.js";
22
-
23
- const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
24
- const SVG_OPTIONS = new Set(["title", "description", "resourceNamespace"]);
25
- const RESOURCE_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
26
- const TEXT_ANCHORS = Object.freeze({
27
- left: "start",
28
- start: "start",
29
- center: "middle",
30
- right: "end",
31
- end: "end"
32
- });
33
- const DOMINANT_BASELINES = Object.freeze({
34
- top: "text-before-edge",
35
- hanging: "hanging",
36
- middle: "middle",
37
- alphabetic: "alphabetic",
38
- ideographic: "ideographic",
39
- bottom: "text-after-edge"
40
- });
41
-
42
- const namespaceByGraphic = new WeakMap();
43
-
44
- function resourceNamespace(graphicSpec) {
45
- const cached = namespaceByGraphic.get(graphicSpec);
46
- if (cached !== undefined) return cached;
47
- const serialized = JSON.stringify(graphicSpec);
48
- let hash = 0xcbf29ce484222325n;
49
- for (let index = 0; index < serialized.length; index += 1) {
50
- hash ^= BigInt(serialized.charCodeAt(index));
51
- hash = BigInt.asUintN(64, hash * 0x100000001b3n);
52
- }
53
- const namespace = hash.toString(36);
54
- if (isOwned(graphicSpec)) namespaceByGraphic.set(graphicSpec, namespace);
55
- return namespace;
56
- }
57
-
58
- function escapeText(value) {
59
- for (const character of value) {
60
- const point = character.codePointAt(0);
61
- if (!(
62
- point === 9 || point === 10 || point === 13 ||
63
- (point >= 0x20 && point <= 0xd7ff) ||
64
- (point >= 0xe000 && point <= 0xfffd) ||
65
- (point >= 0x10000 && point <= 0x10ffff)
66
- )) {
67
- throw new TypeError("SVG output requires XML 1.0-compatible strings.");
68
- }
69
- }
70
- return value
71
- .replaceAll("&", "&amp;")
72
- .replaceAll("<", "&lt;")
73
- .replaceAll(">", "&gt;");
74
- }
75
-
76
- function escapeAttribute(value) {
77
- return escapeText(String(value))
78
- .replaceAll("\"", "&quot;")
79
- .replaceAll("'", "&apos;");
80
- }
81
-
82
- function formatNumber(value) {
83
- if (!Number.isFinite(value)) {
84
- throw new TypeError("SVG renderer requires finite numeric output.");
85
- }
86
- return Object.is(value, -0) ? "0" : String(value);
87
- }
88
-
89
- function attributes(entries) {
90
- return entries
91
- .filter(([, value]) => value !== undefined)
92
- .map(([name, value]) => `${name}="${escapeAttribute(value)}"`)
93
- .join(" ");
94
- }
95
-
96
- function element(name, entries, content) {
97
- const serialized = attributes(entries);
98
- const opening = serialized.length === 0 ? `<${name}` : `<${name} ${serialized}`;
99
- if (content === undefined) return `${opening}/>`;
100
- return `${opening}>${content}</${name}>`;
101
- }
102
-
103
- function requireOpacity(properties, graphicId) {
104
- const opacity = properties.opacity ?? 1;
105
- if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) {
106
- throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
107
- }
108
- return opacity;
109
- }
110
-
111
- function requireStrokeDash(properties, graphicId) {
112
- const strokeDash = properties.strokeDash ?? [];
113
- if (
114
- !Array.isArray(strokeDash) ||
115
- !strokeDash.every(value => Number.isFinite(value) && value >= 0)
116
- ) {
117
- throw new Error(
118
- `Graphic "${graphicId}" requires a non-negative finite strokeDash array.`
119
- );
120
- }
121
- return strokeDash;
122
- }
123
-
124
- function strokeDetailAttributes(properties, strokeWidth) {
125
- if (typeof properties.stroke !== "string" || !(strokeWidth > 0)) return [];
126
- return [
127
- ["stroke-linecap", Object.hasOwn(properties, "lineCap")
128
- ? properties.lineCap
129
- : undefined],
130
- ["stroke-linejoin", Object.hasOwn(properties, "lineJoin")
131
- ? properties.lineJoin
132
- : undefined],
133
- ["stroke-miterlimit", Object.hasOwn(properties, "miterLimit")
134
- ? formatNumber(properties.miterLimit)
135
- : undefined]
136
- ];
137
- }
138
-
139
- function requireCanvasGeometry(id, canvas, { nested }) {
140
- const properties = canvas.properties ?? {};
141
- const width = requireFiniteProperty(properties, "width", id);
142
- const height = requireFiniteProperty(properties, "height", id);
143
- if (width < 0 || height < 0) {
144
- const label = nested ? `Nested Canvas "${id}"` : "Canvas";
145
- throw new Error(`${label} width and height must not be negative.`);
146
- }
147
- return {
148
- ...(nested ? {
149
- x: requireFiniteProperty(properties, "x", id),
150
- y: requireFiniteProperty(properties, "y", id)
151
- } : {}),
152
- width,
153
- height
154
- };
155
- }
156
-
157
- function requireCanvasBackground(id, canvas) {
158
- const background = canvas.properties?.background;
159
- if (background === undefined) return undefined;
160
- if (typeof background !== "string") {
161
- throw new Error(
162
- `Graphic "${id}" requires a string background property.`
163
- );
164
- }
165
- return background;
166
- }
167
-
168
- function addLinearGradient(state, fill, bounds, graphicId) {
169
- const coordinates = resolveLinearGradientCoordinates(fill, bounds);
170
- const id =
171
- `ggaction-gradient-${state.resourceNamespace}-${state.gradientCount}`;
172
- state.gradientCount += 1;
173
- const stops = fill.stops.map(stop => element("stop", [
174
- ["offset", formatNumber(stop.offset)],
175
- ["stop-color", stop.color]
176
- ])).join("");
177
- state.definitions.push(element("linearGradient", [
178
- ["id", id],
179
- ["gradientUnits", "userSpaceOnUse"],
180
- ["x1", formatNumber(coordinates.from.x)],
181
- ["y1", formatNumber(coordinates.from.y)],
182
- ["x2", formatNumber(coordinates.to.x)],
183
- ["y2", formatNumber(coordinates.to.y)]
184
- ], stops));
185
- return `url(#${id})`;
186
- }
187
-
188
- function serializeFill(state, fill, bounds, graphicId) {
189
- validateFillPaint(fill, `${graphicId}.fill`);
190
- if (!isLinearGradientPaint(fill)) return fill;
191
- return addLinearGradient(state, fill, bounds, graphicId);
192
- }
193
-
194
- function serializeCircle(state, graphicId, properties) {
195
- validateConcreteGraphicProperties("circle", properties);
196
- const x = requireFiniteProperty(properties, "x", graphicId);
197
- const y = requireFiniteProperty(properties, "y", graphicId);
198
- const radius = requireFiniteProperty(properties, "radius", graphicId);
199
- if (radius < 0) {
200
- throw new Error(`Graphic "${graphicId}" requires a non-negative radius.`);
201
- }
202
- if (typeof properties.fill !== "string") {
203
- throw new Error(`Graphic "${graphicId}" requires a string fill property.`);
204
- }
205
- let strokeWidth;
206
- if (properties.stroke !== undefined) {
207
- if (typeof properties.stroke !== "string") {
208
- throw new Error(`Graphic "${graphicId}" requires a string stroke property.`);
209
- }
210
- strokeWidth = requireFiniteProperty(properties, "strokeWidth", graphicId);
211
- if (strokeWidth < 0) {
212
- throw new Error(
213
- `Graphic "${graphicId}" requires a non-negative strokeWidth.`
214
- );
215
- }
216
- }
217
- return element("circle", [
218
- ["cx", formatNumber(x)],
219
- ["cy", formatNumber(y)],
220
- ["r", formatNumber(radius)],
221
- ["fill", properties.fill],
222
- ["stroke", properties.stroke],
223
- ["stroke-width", strokeWidth === undefined
224
- ? undefined
225
- : formatNumber(strokeWidth)],
226
- ...strokeDetailAttributes(properties, strokeWidth),
227
- ["opacity", formatNumber(requireOpacity(properties, graphicId))]
228
- ]);
229
- }
230
-
231
- function serializeRect(state, graphicId, properties) {
232
- validateConcreteGraphicProperties("rect", properties);
233
- const x = requireFiniteProperty(properties, "x", graphicId);
234
- const y = requireFiniteProperty(properties, "y", graphicId);
235
- const width = requireFiniteProperty(properties, "width", graphicId);
236
- const height = requireFiniteProperty(properties, "height", graphicId);
237
- const strokeWidth = requireFiniteProperty(
238
- properties,
239
- "strokeWidth",
240
- graphicId
241
- );
242
- if (width < 0 || height < 0 || strokeWidth < 0) {
243
- throw new Error(
244
- `Graphic "${graphicId}" requires non-negative rect dimensions and strokeWidth.`
245
- );
246
- }
247
- if (properties.fill === undefined) {
248
- throw new Error(`Graphic "${graphicId}" requires a fill property.`);
249
- }
250
- if (typeof properties.stroke !== "string") {
251
- throw new Error(`Graphic "${graphicId}" requires a string stroke property.`);
252
- }
253
- const fill = serializeFill(state, properties.fill, {
254
- left: x,
255
- right: x + width,
256
- top: y,
257
- bottom: y + height
258
- }, graphicId);
259
- return element("rect", [
260
- ["x", formatNumber(x)],
261
- ["y", formatNumber(y)],
262
- ["width", formatNumber(width)],
263
- ["height", formatNumber(height)],
264
- ["fill", fill],
265
- ["stroke", properties.stroke],
266
- ["stroke-width", formatNumber(strokeWidth)],
267
- ...strokeDetailAttributes(properties, strokeWidth),
268
- ["opacity", formatNumber(requireOpacity(properties, graphicId))],
269
- ["style", properties.blend === "difference" ? "mix-blend-mode:difference" : undefined]
270
- ]);
271
- }
272
-
273
- function serializeLine(state, graphicId, properties) {
274
- validateConcreteGraphicProperties("line", properties);
275
- const strokeWidth = requireFiniteProperty(
276
- properties,
277
- "strokeWidth",
278
- graphicId
279
- );
280
- if (strokeWidth < 0) {
281
- throw new Error(
282
- `Graphic "${graphicId}" requires a non-negative strokeWidth.`
283
- );
284
- }
285
- if (typeof properties.stroke !== "string") {
286
- throw new Error(`Graphic "${graphicId}" requires a string stroke property.`);
287
- }
288
- const strokeDash = requireStrokeDash(properties, graphicId);
289
- return element("line", [
290
- ["x1", formatNumber(requireFiniteProperty(properties, "x1", graphicId))],
291
- ["y1", formatNumber(requireFiniteProperty(properties, "y1", graphicId))],
292
- ["x2", formatNumber(requireFiniteProperty(properties, "x2", graphicId))],
293
- ["y2", formatNumber(requireFiniteProperty(properties, "y2", graphicId))],
294
- ["fill", "none"],
295
- ["stroke", properties.stroke],
296
- ["stroke-width", formatNumber(strokeWidth)],
297
- ...strokeDetailAttributes(properties, strokeWidth),
298
- ["stroke-dasharray", strokeDash.length === 0
299
- ? undefined
300
- : strokeDash.map(formatNumber).join(" ")],
301
- ["opacity", formatNumber(requireOpacity(properties, graphicId))]
302
- ]);
303
- }
304
-
305
- function serializePathData(commands) {
306
- return commands.map(command => {
307
- if (command.op === "M" || command.op === "L") {
308
- return `${command.op}${formatNumber(command.x)} ${formatNumber(command.y)}`;
309
- }
310
- if (command.op === "C") {
311
- return `C${formatNumber(command.x1)} ${formatNumber(command.y1)} ` +
312
- `${formatNumber(command.x2)} ${formatNumber(command.y2)} ` +
313
- `${formatNumber(command.x)} ${formatNumber(command.y)}`;
314
- }
315
- return "Z";
316
- }).join(" ");
317
- }
318
-
319
- function serializePath(state, graphicId, properties) {
320
- validateConcreteGraphicProperties("path", properties);
321
- const commands = properties.commands;
322
- if (!Array.isArray(commands)) {
323
- throw new Error(
324
- `Graphic "${graphicId}" requires concrete path commands.`
325
- );
326
- }
327
- const hasFill = properties.fill !== undefined;
328
- const hasStroke = properties.stroke !== undefined;
329
- if (!hasFill && !hasStroke) {
330
- throw new Error(`Graphic "${graphicId}" requires a fill or stroke property.`);
331
- }
332
- if (hasFill && commands.at(-1).op !== "Z") {
333
- throw new Error(`Graphic "${graphicId}" requires a final Z command when filled.`);
334
- }
335
- if (hasStroke && typeof properties.stroke !== "string") {
336
- throw new Error(`Graphic "${graphicId}" requires a string stroke property.`);
337
- }
338
- const strokeWidth = hasStroke
339
- ? requireFiniteProperty(properties, "strokeWidth", graphicId)
340
- : undefined;
341
- if (hasStroke && strokeWidth < 0) {
342
- throw new Error(
343
- `Graphic "${graphicId}" requires a non-negative strokeWidth.`
344
- );
345
- }
346
- const strokeDash = requireStrokeDash(properties, graphicId);
347
- let fill = "none";
348
- if (hasFill) {
349
- const bounds = resolvePathCommandBounds(commands);
350
- if (bounds === undefined) {
351
- throw new Error(`Graphic "${graphicId}" requires finite path fill bounds.`);
352
- }
353
- fill = serializeFill(state, properties.fill, bounds, graphicId);
354
- }
355
- return element("path", [
356
- ["d", serializePathData(commands)],
357
- ["fill", fill],
358
- ["stroke", properties.stroke],
359
- ["stroke-width", strokeWidth === undefined
360
- ? undefined
361
- : formatNumber(strokeWidth)],
362
- ...strokeDetailAttributes(properties, strokeWidth),
363
- ["stroke-dasharray", strokeDash.length === 0
364
- ? undefined
365
- : strokeDash.map(formatNumber).join(" ")],
366
- ["opacity", formatNumber(requireOpacity(properties, graphicId))],
367
- ["style", properties.blend === "difference" ? "mix-blend-mode:difference" : undefined]
368
- ]);
369
- }
370
-
371
- function serializeText(state, graphicId, properties) {
372
- validateConcreteGraphicProperties("text", properties);
373
- const x = requireFiniteProperty(properties, "x", graphicId);
374
- const y = requireFiniteProperty(properties, "y", graphicId);
375
- const fontSize = requireFiniteProperty(properties, "fontSize", graphicId);
376
- const rotation = properties.rotation ?? 0;
377
- const opacity = requireOpacity(properties, graphicId);
378
- const text = requireStringProperty(properties, "text", graphicId);
379
- const fill = requireStringProperty(properties, "fill", graphicId);
380
- const fontFamily = requireStringProperty(
381
- properties,
382
- "fontFamily",
383
- graphicId
384
- );
385
- const textAlign = requireStringProperty(
386
- properties,
387
- "textAlign",
388
- graphicId
389
- );
390
- const textBaseline = requireStringProperty(
391
- properties,
392
- "textBaseline",
393
- graphicId
394
- );
395
- const fontWeight = properties.fontWeight ?? "normal";
396
- if (fontSize <= 0) {
397
- throw new Error(`Graphic "${graphicId}" requires a positive fontSize.`);
398
- }
399
- if (!Number.isFinite(rotation)) {
400
- throw new Error(`Graphic "${graphicId}" requires a finite rotation.`);
401
- }
402
- if (
403
- !(
404
- (typeof fontWeight === "string" && fontWeight.length > 0) ||
405
- Number.isFinite(fontWeight)
406
- )
407
- ) {
408
- throw new Error(`Graphic "${graphicId}" requires a valid fontWeight.`);
409
- }
410
- if (TEXT_ANCHORS[textAlign] === undefined) {
411
- throw new Error(`Graphic "${graphicId}" has invalid textAlign.`);
412
- }
413
- if (DOMINANT_BASELINES[textBaseline] === undefined) {
414
- throw new Error(`Graphic "${graphicId}" has invalid textBaseline.`);
415
- }
416
- const rotationDegrees = rotation * 180 / Math.PI;
417
- return element("text", [
418
- ["x", formatNumber(x)],
419
- ["y", formatNumber(y)],
420
- ["fill", fill],
421
- ["font-family", fontFamily],
422
- ["font-size", formatNumber(fontSize)],
423
- ["font-weight", normalizeRendererFontWeight(fontWeight)],
424
- ["font-style", properties.fontStyle],
425
- ["text-anchor", TEXT_ANCHORS[textAlign]],
426
- ["dominant-baseline", DOMINANT_BASELINES[textBaseline]],
427
- ["opacity", formatNumber(opacity)],
428
- ["transform", rotation === 0
429
- ? undefined
430
- : `rotate(${formatNumber(rotationDegrees)} ${formatNumber(x)} ${formatNumber(y)})`]
431
- ], properties.lines === undefined ? escapeText(text) : properties.lines.map(line =>
432
- element("tspan", [["x", formatNumber(x + line.x)], ["y", formatNumber(y + line.y)]], escapeText(line.text))
433
- ).join(""));
434
- }
435
-
436
- function serializeConcreteGraphic(state, id, graphic) {
437
- if (graphic.type === "collection") {
438
- const items = (graphic.items ?? []).map(item =>
439
- serializeConcreteGraphic(state, item.id ?? id, item)
440
- ).join("");
441
- return element("g", [], items);
442
- }
443
- const properties = graphic.properties ?? {};
444
- if (graphic.type === "circle") {
445
- return serializeCircle(state, id, properties);
446
- }
447
- if (graphic.type === "rect") {
448
- return serializeRect(state, id, properties);
449
- }
450
- if (graphic.type === "line") {
451
- return serializeLine(state, id, properties);
452
- }
453
- if (graphic.type === "path") {
454
- return serializePath(state, id, properties);
455
- }
456
- if (graphic.type === "text") {
457
- return serializeText(state, id, properties);
458
- }
459
- throw new Error(`SVG renderer does not support "${graphic.type}" yet.`);
460
- }
461
-
462
- function openNestedCanvas(state, id, canvas) {
463
- const { x, y, width, height } = requireCanvasGeometry(
464
- id,
465
- canvas,
466
- { nested: true }
467
- );
468
- const clipId = `ggaction-clip-${state.resourceNamespace}-${state.clipCount}`;
469
- state.clipCount += 1;
470
- state.definitions.push(element("clipPath", [["id", clipId]], element(
471
- "rect",
472
- [
473
- ["x", "0"],
474
- ["y", "0"],
475
- ["width", formatNumber(width)],
476
- ["height", formatNumber(height)]
477
- ]
478
- )));
479
- const background = requireCanvasBackground(id, canvas);
480
- const backgroundRect = background === undefined
481
- ? ""
482
- : element("rect", [
483
- ["x", "0"],
484
- ["y", "0"],
485
- ["width", formatNumber(width)],
486
- ["height", formatNumber(height)],
487
- ["fill", background]
488
- ]);
489
- return `<g ${attributes([
490
- ["transform", `translate(${formatNumber(x)} ${formatNumber(y)})`],
491
- ["clip-path", `url(#${clipId})`]
492
- ])}>${backgroundRect}`;
493
- }
494
-
495
- function serializeBody(graphicSpec, rootId, rootCanvas, width, height, state) {
496
- const output = [];
497
- const background = requireCanvasBackground(rootId, rootCanvas);
498
- if (background !== undefined) {
499
- output.push(element("rect", [
500
- ["x", "0"],
501
- ["y", "0"],
502
- ["width", formatNumber(width)],
503
- ["height", formatNumber(height)],
504
- ["fill", background]
505
- ]));
506
- }
507
- walkGraphicTreeEvents(graphicSpec, {
508
- enter({ id, object }) {
509
- if (id === rootId) return;
510
- if (object.type === "canvas") {
511
- output.push(openNestedCanvas(state, id, object));
512
- return;
513
- }
514
- if (object.type === "collection") {
515
- output.push("<g>");
516
- return;
517
- }
518
- if (!Array.isArray(object.items)) {
519
- output.push(serializeConcreteGraphic(state, id, object));
520
- }
521
- },
522
- item({ id, object, owner }) {
523
- output.push(serializeConcreteGraphic(state, id, {
524
- ...object,
525
- type: object.type ?? owner.type
526
- }));
527
- },
528
- exit({ id, object }) {
529
- if (
530
- id !== rootId &&
531
- (object.type === "collection" || object.type === "canvas")
532
- ) {
533
- output.push("</g>");
534
- }
535
- }
536
- });
537
- return output.join("");
538
- }
539
-
540
- function requireSVGOptions(options) {
541
- validateRendererOptions(options, SVG_OPTIONS, "renderToSVG options");
542
- for (const key of SVG_OPTIONS) {
543
- if (
544
- options[key] !== undefined &&
545
- (typeof options[key] !== "string" || options[key].length === 0)
546
- ) {
547
- throw new TypeError(`renderToSVG ${key} must be a non-empty string.`);
548
- }
549
- }
550
- if (
551
- options.resourceNamespace !== undefined &&
552
- !RESOURCE_NAMESPACE.test(options.resourceNamespace)
553
- ) {
554
- throw new TypeError(
555
- "renderToSVG resourceNamespace must start with a letter and contain only letters, numbers, _ or -."
556
- );
557
- }
558
- return options;
559
- }
560
-
561
- export function renderToSVG(program, options = {}) {
562
- const resolvedOptions = requireSVGOptions(options);
563
- const graphicSpec = program?.graphicSpec;
564
- if (
565
- graphicSpec === null ||
566
- typeof graphicSpec !== "object" ||
567
- graphicSpec.objects === null ||
568
- typeof graphicSpec.objects !== "object" ||
569
- !Array.isArray(graphicSpec.order)
570
- ) {
571
- throw new TypeError("renderToSVG requires a program with a graphicSpec.");
572
- }
573
- const { id: rootId, object: rootCanvas } =
574
- requireSingleOrderedGraphicByType(graphicSpec, "canvas");
575
- const { width, height } = requireCanvasGeometry(
576
- rootId,
577
- rootCanvas,
578
- { nested: false }
579
- );
580
- const state = {
581
- clipCount: 1,
582
- definitions: [],
583
- gradientCount: 1,
584
- resourceNamespace:
585
- resolvedOptions.resourceNamespace ?? resourceNamespace(graphicSpec)
586
- };
587
- walkGraphicTreeEvents(graphicSpec, {
588
- enter({object}) { if (object.properties?.blend === "difference") state.hasBlend = true; },
589
- item({object}) { if (object.properties?.blend === "difference") state.hasBlend = true; }
590
- });
591
- const body = serializeBody(
592
- graphicSpec,
593
- rootId,
594
- rootCanvas,
595
- width,
596
- height,
597
- state
598
- );
599
- const accessible = [
600
- resolvedOptions.title === undefined
601
- ? ""
602
- : element("title", [], escapeText(resolvedOptions.title)),
603
- resolvedOptions.description === undefined
604
- ? ""
605
- : element("desc", [], escapeText(resolvedOptions.description))
606
- ].join("");
607
- const definitions = state.definitions.length === 0
608
- ? ""
609
- : element("defs", [], state.definitions.join(""));
610
- return element("svg", [
611
- ["xmlns", SVG_NAMESPACE],
612
- ["width", formatNumber(width)],
613
- ["height", formatNumber(height)],
614
- ["viewBox", `0 0 ${formatNumber(width)} ${formatNumber(height)}`],
615
- ["style", state.hasBlend ? "isolation:isolate" : undefined]
616
- ], `${accessible}${definitions}${body}`);
617
- }
1
+ export { renderToSVG } from "./svgSerialization.js";