ggaction 0.0.17 → 0.0.19

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 (315) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +20 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +8 -7
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/derived.js +11 -1
  35. package/src/actions/data/edit.js +3 -1
  36. package/src/actions/data/index.js +4 -0
  37. package/src/actions/data/regression.js +25 -16
  38. package/src/actions/data/regressionStatistics.js +73 -0
  39. package/src/actions/data/shared.js +8 -4
  40. package/src/actions/data/stack.js +3 -1
  41. package/src/actions/distributions/revision.js +24 -18
  42. package/src/actions/encodings/appearance.js +37 -29
  43. package/src/actions/encodings/barWidth.js +36 -11
  44. package/src/actions/encodings/channels.js +0 -1
  45. package/src/actions/encodings/color/continuous.js +1 -1
  46. package/src/actions/encodings/color/index.js +1 -1
  47. package/src/actions/encodings/color/policy.js +10 -3
  48. package/src/actions/encodings/density/resolve.js +1 -6
  49. package/src/actions/encodings/position/policies/arc.js +12 -10
  50. package/src/actions/encodings/position/policies/area.js +3 -3
  51. package/src/actions/encodings/position/policies/bar.js +15 -6
  52. package/src/actions/encodings/position/policies/line.js +17 -6
  53. package/src/actions/encodings/position/resolve.js +3 -2
  54. package/src/actions/encodings/ranged.js +17 -0
  55. package/src/actions/encodings/remove.js +14 -8
  56. package/src/actions/encodings/seriesLayout.js +1 -0
  57. package/src/actions/encodings/shared.js +26 -24
  58. package/src/actions/encodings/text.js +6 -3
  59. package/src/actions/errorBands/create.js +2 -2
  60. package/src/actions/errorBands/edit.js +2 -1
  61. package/src/actions/facets/actions.js +176 -247
  62. package/src/actions/facets/guides.js +2 -1
  63. package/src/actions/gradientPlots/resolve.js +2 -17
  64. package/src/actions/guides/applicability.js +1 -1
  65. package/src/actions/guides/axes/axes.js +30 -11
  66. package/src/actions/guides/axes/axis.js +72 -10
  67. package/src/actions/guides/axes/components.js +13 -13
  68. package/src/actions/guides/axes/edit.js +36 -24
  69. package/src/actions/guides/axes/labels.js +155 -71
  70. package/src/actions/guides/axes/lines.js +27 -24
  71. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  72. package/src/actions/guides/axes/parallel/policy.js +8 -7
  73. package/src/actions/guides/axes/remove.js +13 -8
  74. package/src/actions/guides/axes/tickGroups.js +11 -7
  75. package/src/actions/guides/axes/ticks.js +36 -24
  76. package/src/actions/guides/axes/titles.js +107 -69
  77. package/src/actions/guides/basic.js +2 -0
  78. package/src/actions/guides/facadeAxes.js +33 -24
  79. package/src/actions/guides/facadeLegend.js +12 -4
  80. package/src/actions/guides/grids/grid.js +8 -2
  81. package/src/actions/guides/grids/resolve.js +102 -58
  82. package/src/actions/guides/legends/blocks.js +46 -20
  83. package/src/actions/guides/legends/categorical/actions.js +95 -19
  84. package/src/actions/guides/legends/categorical/components.js +15 -18
  85. package/src/actions/guides/legends/categorical/index.js +8 -0
  86. package/src/actions/guides/legends/categorical/layout.js +45 -20
  87. package/src/actions/guides/legends/categorical/options.js +39 -22
  88. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  89. package/src/actions/guides/legends/categorical/resolve.js +11 -4
  90. package/src/actions/guides/legends/categorical/symbols.js +38 -18
  91. package/src/actions/guides/legends/categorical/validation.js +1 -6
  92. package/src/actions/guides/legends/colorSize.js +110 -0
  93. package/src/actions/guides/legends/combine.js +37 -0
  94. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  95. package/src/actions/guides/legends/continuous/common.js +1 -447
  96. package/src/actions/guides/legends/continuous/gradient.js +73 -46
  97. package/src/actions/guides/legends/continuous/interval.js +4 -4
  98. package/src/actions/guides/legends/continuous/opacity.js +33 -21
  99. package/src/actions/guides/legends/creation.js +2 -0
  100. package/src/actions/guides/legends/edit.js +1 -604
  101. package/src/actions/guides/legends/focused.js +8 -7
  102. package/src/actions/guides/legends/group.js +1 -0
  103. package/src/actions/guides/legends/lane.js +1 -516
  104. package/src/actions/guides/legends/lifecycle.js +2 -1
  105. package/src/actions/guides/legends/remove.js +9 -3
  106. package/src/actions/guides/legends/sampling.js +1 -1
  107. package/src/actions/guides/legends/size.js +65 -47
  108. package/src/actions/guides/legends/strokeWidth.js +13 -29
  109. package/src/actions/guides/legends/target.js +2 -79
  110. package/src/actions/guides/legends/transition.js +44 -11
  111. package/src/actions/guides/polar/axes/labels.js +4 -2
  112. package/src/actions/guides/polar/axes/titles.js +7 -5
  113. package/src/actions/guides/polar/resolve.js +6 -14
  114. package/src/actions/guides/tickValues.js +8 -5
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +10 -4
  120. package/src/actions/marks/bar/edit.js +29 -14
  121. package/src/actions/marks/bar/materialize.js +15 -7
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/edit.js +1 -1
  125. package/src/actions/marks/point/materialize.js +5 -1
  126. package/src/actions/marks/rect/actions.js +3 -3
  127. package/src/actions/marks/remove.js +12 -10
  128. package/src/actions/marks/rule/actions.js +7 -4
  129. package/src/actions/marks/text/actions.js +17 -5
  130. package/src/actions/marks/text/layout.js +3 -0
  131. package/src/actions/primitives/createGraphics.js +3 -2
  132. package/src/actions/primitives/editGraphics.js +15 -11
  133. package/src/actions/primitives/graphicProperties.js +1 -6
  134. package/src/actions/primitives/semanticAction.js +27 -1
  135. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  136. package/src/actions/primitives/semanticValidation/layer.js +8 -0
  137. package/src/actions/regression/create.js +1 -1
  138. package/src/actions/regression/edit.js +18 -6
  139. package/src/actions/regression/resolve.js +2 -6
  140. package/src/actions/scales/channels.js +9 -4
  141. package/src/actions/scales/consumers/families.js +16 -5
  142. package/src/actions/scales/create.js +10 -5
  143. package/src/actions/scales/definitions.js +1 -425
  144. package/src/actions/scales/edit.js +3 -3
  145. package/src/actions/scales/editPolicy.js +12 -3
  146. package/src/actions/scales/materialize.js +1 -1
  147. package/src/actions/scales/patch.js +1 -1
  148. package/src/actions/scales/preview.js +21 -1
  149. package/src/actions/scales/quantitativeColor.js +8 -3
  150. package/src/actions/selection/actions.js +1 -13
  151. package/src/actions/theme/reconcile.js +126 -45
  152. package/src/actions/theme/state.js +12 -0
  153. package/src/actions/titles/actions.js +5 -18
  154. package/src/actions/titles/resolve.js +41 -19
  155. package/src/actions/violinPlots/edit.js +3 -20
  156. package/src/core/compositionState.js +6 -3
  157. package/src/core/font.js +9 -2
  158. package/src/core/graphicProperties.js +1 -0
  159. package/src/core/optionRequirements.js +1 -0
  160. package/src/core/programState.js +4 -0
  161. package/src/core/resourceReferences.js +5 -2
  162. package/src/core/textMetricProfile.js +3 -2
  163. package/src/core/textMetrics.js +41 -4
  164. package/src/core/validation.js +35 -0
  165. package/src/core/vocabulary.js +7 -0
  166. package/src/grammar/aggregate.js +4 -18
  167. package/src/grammar/arcs.js +1 -386
  168. package/src/grammar/areaPaint.js +10 -0
  169. package/src/grammar/areaSeries.js +18 -10
  170. package/src/grammar/axisTextPlacement.js +1 -0
  171. package/src/grammar/bars/geometry.js +30 -3
  172. package/src/grammar/bars/policy.js +12 -1
  173. package/src/grammar/bin.js +2 -12
  174. package/src/grammar/bin2d.js +1 -6
  175. package/src/grammar/boxPlot.js +56 -9
  176. package/src/grammar/categoryOrder.js +1 -6
  177. package/src/grammar/colorSizeLegend.js +39 -0
  178. package/src/grammar/complete.js +15 -19
  179. package/src/grammar/computed.js +115 -26
  180. package/src/grammar/curveCommands.js +29 -16
  181. package/src/grammar/datasetSchema.js +13 -9
  182. package/src/grammar/displayLabels.js +36 -0
  183. package/src/grammar/ecdf.js +2 -9
  184. package/src/grammar/facets/dependencies.js +6 -2
  185. package/src/grammar/facets/guides.js +58 -30
  186. package/src/grammar/facets/index.js +2 -5
  187. package/src/grammar/facets/legendIntent.js +65 -0
  188. package/src/grammar/fold.js +2 -12
  189. package/src/grammar/gradientProfile.js +1 -6
  190. package/src/grammar/guideDash.js +1 -0
  191. package/src/grammar/impute.js +21 -48
  192. package/src/grammar/interval.js +2 -9
  193. package/src/grammar/legendBlocks.js +1 -0
  194. package/src/grammar/legendBorder.js +9 -0
  195. package/src/grammar/legendClip.js +19 -0
  196. package/src/grammar/legendCombinations.js +45 -0
  197. package/src/grammar/legendGradient.js +27 -0
  198. package/src/grammar/legendGroups.js +1 -0
  199. package/src/grammar/legendOpacity.js +6 -0
  200. package/src/grammar/legendTitleStyle.js +22 -0
  201. package/src/grammar/lineSeries.js +32 -17
  202. package/src/grammar/markLabels.js +5 -2
  203. package/src/grammar/normalize.js +4 -54
  204. package/src/grammar/numeric.js +1 -290
  205. package/src/grammar/parallelCoordinates.js +1 -6
  206. package/src/grammar/parseTime.js +41 -0
  207. package/src/grammar/pathCommands.js +11 -4
  208. package/src/grammar/pointShapes.js +16 -2
  209. package/src/grammar/polar.js +1 -184
  210. package/src/grammar/positionCompatibility.js +3 -3
  211. package/src/grammar/regression/derive.js +16 -2
  212. package/src/grammar/regression/models.js +142 -54
  213. package/src/grammar/regression/parameters.js +23 -4
  214. package/src/grammar/regression/statistics.js +64 -0
  215. package/src/grammar/roundedRect.js +42 -54
  216. package/src/grammar/scales/appearance.js +1 -206
  217. package/src/grammar/scales/color.js +1 -382
  218. package/src/grammar/scales/colorConsumers.js +5 -2
  219. package/src/grammar/scales/definition.js +1 -197
  220. package/src/grammar/scales/discretized.js +1 -195
  221. package/src/grammar/scales/size.js +1 -456
  222. package/src/grammar/scales/temporal.js +2 -13
  223. package/src/grammar/scales/transformed.js +1 -383
  224. package/src/grammar/scales/types.js +1 -1
  225. package/src/grammar/scales/validation.js +1 -66
  226. package/src/grammar/schemas/concreteGraphic.js +14 -0
  227. package/src/grammar/schemas/graphic.js +6 -0
  228. package/src/grammar/schemas/graphicBounds.js +31 -7
  229. package/src/grammar/schemas/semanticPath.js +19 -3
  230. package/src/grammar/stack.js +2 -10
  231. package/src/grammar/statisticalReference.js +1 -6
  232. package/src/grammar/summary.js +4 -27
  233. package/src/grammar/text.js +19 -0
  234. package/src/grammar/timeUnit.js +22 -11
  235. package/src/grammar/transformKeys.js +47 -0
  236. package/src/grammar/transformTopology.js +1 -0
  237. package/src/grammar/transforms.js +15 -2
  238. package/src/grammar/valueFormat.js +17 -5
  239. package/src/grammar/weightedStatistics.js +1 -6
  240. package/src/grammar/window.js +4 -58
  241. package/src/inspectionDescriptors.js +1 -1
  242. package/src/layout/canvas.js +1 -110
  243. package/src/layout/facets.js +1 -244
  244. package/src/layout/guideCollisions.js +9 -1
  245. package/src/layout/labels.js +1 -0
  246. package/src/layout/legend.js +13 -0
  247. package/src/layout/legendGroup.js +1 -0
  248. package/src/layout/legendItems.js +59 -15
  249. package/src/layout/legendLane.js +1 -441
  250. package/src/layout/legendText.js +1 -0
  251. package/src/layout/title.js +35 -13
  252. package/src/materialization/bars/aggregate.js +11 -5
  253. package/src/materialization/bars/histogram.js +4 -1
  254. package/src/materialization/bars/ranged.js +19 -6
  255. package/src/materialization/bars/resolve.js +22 -1
  256. package/src/materialization/compositionSnapshot.js +2 -7
  257. package/src/materialization/coordinateBounds.js +21 -0
  258. package/src/materialization/dependencies.js +4 -44
  259. package/src/materialization/facetGuides/edit.js +30 -0
  260. package/src/materialization/facetGuides/layout.js +1 -0
  261. package/src/materialization/facetGuides/owner.js +125 -0
  262. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  263. package/src/materialization/facetGuides/placement.js +71 -14
  264. package/src/materialization/facetGuides/preparation.js +22 -7
  265. package/src/materialization/facetHeaders.js +1 -347
  266. package/src/materialization/facets.js +1 -185
  267. package/src/materialization/graphicHierarchy.js +5 -1
  268. package/src/materialization/graphicTranslation.js +73 -0
  269. package/src/materialization/guides/layout.js +1 -57
  270. package/src/materialization/guides/resources.js +1 -123
  271. package/src/materialization/layout.js +53 -0
  272. package/src/materialization/legendBorder.js +28 -0
  273. package/src/materialization/legendClip.js +76 -0
  274. package/src/materialization/legendGroups.js +1 -0
  275. package/src/materialization/legendText.js +1 -0
  276. package/src/materialization/legends.js +2 -2
  277. package/src/materialization/marks/capabilities.js +5 -6
  278. package/src/materialization/rect.js +1 -1
  279. package/src/materialization/scaleGuideDependencies.js +14 -15
  280. package/src/materialization/scales/map.js +6 -0
  281. package/src/materialization/scales/policies/arc.js +8 -7
  282. package/src/materialization/scales/policies/bar.js +19 -7
  283. package/src/materialization/scales/policies/series.js +2 -1
  284. package/src/materialization/scales/resolve.js +7 -3
  285. package/src/materialization/selection/items/arc.js +1 -1
  286. package/src/materialization/selection/items/bar.js +7 -6
  287. package/src/materialization/selection/items/path.js +7 -6
  288. package/src/materialization/selection/items/rect.js +2 -2
  289. package/src/materialization/text.js +13 -4
  290. package/src/materialization/textLines.js +15 -0
  291. package/src/materialization/typography.js +10 -2
  292. package/src/persistence/codec.js +37 -6
  293. package/src/persistence/validation.js +220 -0
  294. package/src/persistence.js +19 -6
  295. package/src/renderers/canvas/circle.js +6 -3
  296. package/src/renderers/canvas/fill.js +7 -1
  297. package/src/renderers/canvas/index.js +22 -11
  298. package/src/renderers/canvas/isolated.js +166 -0
  299. package/src/renderers/canvas/limits.js +3 -0
  300. package/src/renderers/canvas/line.js +1 -0
  301. package/src/renderers/canvas/path.js +1 -1
  302. package/src/renderers/canvas/rect.js +11 -8
  303. package/src/renderers/canvas/stroke.js +2 -1
  304. package/src/renderers/canvas/text.js +5 -3
  305. package/src/renderers/pdf.js +2 -2
  306. package/src/renderers/png.js +2 -2
  307. package/src/renderers/svg.js +14 -4
  308. package/src/selectors/axes.js +1 -0
  309. package/src/selectors/graphicCapacity.js +38 -0
  310. package/src/selectors/layers.js +19 -0
  311. package/src/theme/defaults.js +8 -2
  312. package/src/version.js +1 -1
  313. package/types/basic.d.ts +3 -0
  314. package/types/index.d.ts +12 -0
  315. package/types/program.d.ts +536 -129
@@ -1,347 +1 @@
1
- import { isPlainObject } from "../core/immutable.js";
2
- import {
3
- formatVisibleText,
4
- measureTextWidth,
5
- resolveTextBounds,
6
- textBoundsIntersect
7
- } from "../core/textMetrics.js";
8
- import { resolveDisplayLabel } from "../grammar/displayLabels.js";
9
- import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../theme/defaults.js";
10
-
11
- const SIDES = Object.freeze(["top", "right", "bottom", "left"]);
12
- const ZERO_SIDES = Object.freeze({ top: 0, right: 0, bottom: 0, left: 0 });
13
-
14
- export function createDefaultFacetHeaders() {
15
- return {
16
- mode: "legacy",
17
- common: {
18
- fontSize: 12,
19
- fontFamily: DEFAULT_FONT_FAMILY,
20
- fontWeight: 600,
21
- color: DEFAULT_COLORS.strongText,
22
- offset: 10,
23
- align: "center"
24
- },
25
- row: { side: "left" },
26
- column: { side: "top" }
27
- };
28
- }
29
-
30
- export function normalizeFacetHeadersConfig(value) {
31
- if (!isPlainObject(value)) {
32
- throw new TypeError("Facet headers config must be a plain object.");
33
- }
34
- if (value.common === undefined && value.mode === undefined) {
35
- return {
36
- ...createDefaultFacetHeaders(),
37
- common: {
38
- ...createDefaultFacetHeaders().common,
39
- ...value
40
- }
41
- };
42
- }
43
- if (!["legacy", "roles"].includes(value.mode) ||
44
- !isPlainObject(value.common) || !isPlainObject(value.row) ||
45
- !isPlainObject(value.column)) {
46
- throw new Error("Facet headers config must contain canonical mode and role objects.");
47
- }
48
- return {
49
- mode: value.mode,
50
- common: { ...value.common },
51
- row: { ...value.row },
52
- column: { ...value.column }
53
- };
54
- }
55
-
56
- export function resolveFacetHeaderConfig(headers, role) {
57
- const normalized = normalizeFacetHeadersConfig(headers);
58
- return role === "all"
59
- ? { ...normalized.common }
60
- : { ...normalized.common, ...normalized[role] };
61
- }
62
-
63
- function mappedText(value, config) {
64
- return resolveDisplayLabel(value, config.labelMap, formatVisibleText);
65
- }
66
-
67
- function gridCellById(spec) {
68
- return new Map((spec.facet.grid?.cells ?? []).map(cell => [cell.id, cell]));
69
- }
70
-
71
- function legacyDescriptors(program, headers) {
72
- const spec = program.compositionSpec;
73
- const config = resolveFacetHeaderConfig(headers, "all");
74
- const cells = gridCellById(spec);
75
- return spec.children.map((id, index) => {
76
- const cell = cells.get(id);
77
- const text = cell === undefined
78
- ? mappedText(spec.facet.values[index], config)
79
- : `${mappedText(cell.rowValue, config)} · ${mappedText(cell.columnValue, config)}`;
80
- return { topology: "legacy", role: "column", id, text, config };
81
- });
82
- }
83
-
84
- function uniqueGridGroups(spec, role) {
85
- const coordinate = role === "column" ? "column" : "row";
86
- const valueKey = role === "column" ? "columnValue" : "rowValue";
87
- const groups = new Map();
88
- for (const cell of spec.facet.grid.cells) {
89
- const existing = groups.get(cell[coordinate]);
90
- if (existing === undefined) {
91
- groups.set(cell[coordinate], {
92
- coordinate: cell[coordinate],
93
- value: cell[valueKey],
94
- ids: [cell.id]
95
- });
96
- } else {
97
- if (existing.value !== cell[valueKey]) {
98
- throw new Error(`Facet ${role} header values conflict at coordinate ${cell[coordinate]}.`);
99
- }
100
- existing.ids.push(cell.id);
101
- }
102
- }
103
- return [...groups.values()].sort((left, right) =>
104
- left.coordinate - right.coordinate
105
- );
106
- }
107
-
108
- function roleDescriptors(program, headers) {
109
- const spec = program.compositionSpec;
110
- const column = resolveFacetHeaderConfig(headers, "column");
111
- if (spec.facet.grid === undefined) {
112
- return spec.children.map((id, index) => ({
113
- topology: "cell-column",
114
- role: "column",
115
- id,
116
- ids: [id],
117
- row: Math.floor(index / spec.columns),
118
- text: mappedText(spec.facet.values[index], column),
119
- config: column
120
- }));
121
- }
122
- const row = resolveFacetHeaderConfig(headers, "row");
123
- const columns = uniqueGridGroups(spec, "column").map(group => ({
124
- topology: "grid-column",
125
- role: "column",
126
- ids: group.ids,
127
- coordinate: group.coordinate,
128
- text: mappedText(group.value, column),
129
- config: column
130
- }));
131
- const rows = uniqueGridGroups(spec, "row").map(group => ({
132
- topology: "grid-row",
133
- role: "row",
134
- ids: group.ids,
135
- coordinate: group.coordinate,
136
- text: mappedText(group.value, row),
137
- config: row
138
- }));
139
- return [...columns, ...rows];
140
- }
141
-
142
- function laneThickness(descriptors, role, profile) {
143
- const visible = descriptors.filter(item => item.role === role && item.text !== "");
144
- if (visible.length === 0) return 0;
145
- if (role === "column") {
146
- return Math.max(...visible.map(item => item.config.fontSize + item.config.offset));
147
- }
148
- return Math.max(...visible.map(item => measureTextWidth(item.text, item.config, profile) +
149
- item.config.offset));
150
- }
151
-
152
- export function prepareFacetHeaders(program, headersConfig) {
153
- const headers = normalizeFacetHeadersConfig(headersConfig);
154
- const descriptors = headers.mode === "legacy"
155
- ? legacyDescriptors(program, headers)
156
- : roleDescriptors(program, headers);
157
- const rows = program.compositionSpec.facet.grid === undefined
158
- ? Math.ceil(program.compositionSpec.children.length / program.compositionSpec.columns)
159
- : Math.max(...program.compositionSpec.facet.grid.cells.map(cell => cell.row)) + 1;
160
- const headerLayout = {
161
- outer: { ...ZERO_SIDES },
162
- cellRows: { top: Array(rows).fill(0), bottom: Array(rows).fill(0) }
163
- };
164
- if (headers.mode === "roles") {
165
- const column = resolveFacetHeaderConfig(headers, "column");
166
- const columnThickness = laneThickness(descriptors, "column", program.materializationConfigs.textMetrics);
167
- if (program.compositionSpec.facet.grid === undefined) {
168
- for (const descriptor of descriptors) {
169
- if (descriptor.text !== "") {
170
- headerLayout.cellRows[column.side][descriptor.row] = Math.max(
171
- headerLayout.cellRows[column.side][descriptor.row],
172
- descriptor.config.fontSize + descriptor.config.offset
173
- );
174
- }
175
- }
176
- } else {
177
- headerLayout.outer[column.side] = columnThickness;
178
- const row = resolveFacetHeaderConfig(headers, "row");
179
- headerLayout.outer[row.side] = laneThickness(descriptors, "row", program.materializationConfigs.textMetrics);
180
- }
181
- }
182
- return { headers, descriptors, headerLayout };
183
- }
184
-
185
- function horizontalAnchor(bounds, align) {
186
- if (align === "start") return { x: bounds.left, textAlign: "left" };
187
- if (align === "end") return { x: bounds.right, textAlign: "right" };
188
- return { x: (bounds.left + bounds.right) / 2, textAlign: "center" };
189
- }
190
-
191
- function verticalAnchor(bounds, align) {
192
- if (align === "start") return { y: bounds.top, textBaseline: "top" };
193
- if (align === "end") return { y: bounds.bottom, textBaseline: "bottom" };
194
- return { y: (bounds.top + bounds.bottom) / 2, textBaseline: "middle" };
195
- }
196
-
197
- function union(items) {
198
- return {
199
- left: Math.min(...items.map(item => item.left)),
200
- right: Math.max(...items.map(item => item.right)),
201
- top: Math.min(...items.map(item => item.top)),
202
- bottom: Math.max(...items.map(item => item.bottom))
203
- };
204
- }
205
-
206
- function placedPlot(cell, plot) {
207
- return {
208
- left: cell.x + plot.x,
209
- right: cell.x + plot.x + plot.width,
210
- top: cell.y + plot.y,
211
- bottom: cell.y + plot.y + plot.height
212
- };
213
- }
214
-
215
- function placedCell(cell) {
216
- return {
217
- left: cell.x,
218
- right: cell.x + cell.width,
219
- top: cell.y,
220
- bottom: cell.y + cell.height
221
- };
222
- }
223
-
224
- function roleItem(descriptor, cells, plots) {
225
- const selectedCells = descriptor.ids.map(id => cells.get(id));
226
- const missingCell = descriptor.ids.find((id, index) =>
227
- selectedCells[index] === undefined
228
- );
229
- const missingPlot = descriptor.ids.find(id => plots.get(id) === undefined);
230
- if (missingCell !== undefined || missingPlot !== undefined) {
231
- throw new Error(
232
- `Facet header requires cell and plot bounds for "${missingCell ?? missingPlot}".`
233
- );
234
- }
235
- const selectedPlots = descriptor.ids.map((id, index) =>
236
- placedPlot(selectedCells[index], plots.get(id))
237
- );
238
- const plot = union(selectedPlots);
239
- const snapshots = union(selectedCells.map(placedCell));
240
- const config = descriptor.config;
241
- let anchor;
242
- if (descriptor.role === "column") {
243
- anchor = horizontalAnchor(plot, config.align);
244
- const top = config.side === "top";
245
- anchor.y = top
246
- ? snapshots.top - config.offset - config.fontSize / 2
247
- : snapshots.bottom + config.offset + config.fontSize / 2;
248
- anchor.textBaseline = "middle";
249
- } else {
250
- anchor = verticalAnchor(plot, config.align);
251
- const left = config.side === "left";
252
- anchor.x = left
253
- ? snapshots.left - config.offset
254
- : snapshots.right + config.offset;
255
- anchor.textAlign = left ? "right" : "left";
256
- }
257
- return { descriptor, snapshots, properties: anchor };
258
- }
259
-
260
- function legacyItem(descriptor, cell, plot) {
261
- if (cell === undefined || plot === undefined) {
262
- throw new Error(`Facet header requires cell and plot bounds for "${descriptor.id}".`);
263
- }
264
- return {
265
- descriptor,
266
- snapshots: placedCell(cell),
267
- properties: {
268
- ...horizontalAnchor(placedPlot(cell, plot), descriptor.config.align),
269
- y: cell.y + descriptor.config.offset,
270
- textBaseline: "middle"
271
- }
272
- };
273
- }
274
-
275
- function styledItem(item) {
276
- const { config } = item.descriptor;
277
- return {
278
- type: "text",
279
- properties: {
280
- ...item.properties,
281
- text: item.descriptor.text,
282
- fill: config.color,
283
- fontSize: config.fontSize,
284
- fontFamily: config.fontFamily,
285
- fontWeight: config.fontWeight
286
- }
287
- };
288
- }
289
-
290
- function assertRoleHeadersFit(records, items, layout, profile) {
291
- const bounds = items.map((item, index) => records[index].descriptor.text === ""
292
- ? undefined
293
- : resolveTextBounds(item.properties, profile));
294
- for (const [index, current] of bounds.entries()) {
295
- if (current === undefined) continue;
296
- if (current.left < -1e-9 || current.right > layout.width + 1e-9 ||
297
- current.top < -1e-9 || current.bottom > layout.height + 1e-9 ||
298
- bounds.some((other, otherIndex) => otherIndex < index &&
299
- other !== undefined && textBoundsIntersect(other, current)) ||
300
- layout.children.some(cell => textBoundsIntersect(current, placedCell(cell)))) {
301
- throw new Error(
302
- "Facet role headers require sufficient non-overlapping reserved space."
303
- );
304
- }
305
- }
306
- }
307
-
308
- function assertLegacyHeadersFit(items, layout, plotById, profile) {
309
- const previous = [];
310
- for (let index = 0; index < items.length; index += 1) {
311
- if (items[index].properties.text === "") continue;
312
- const bounds = resolveTextBounds(items[index].properties, profile);
313
- const cell = layout.children[index];
314
- const plot = plotById.get(cell.id);
315
- if (bounds.left < 0 || bounds.right > layout.width ||
316
- bounds.top < 0 || bounds.bottom > layout.height ||
317
- previous.some(item => textBoundsIntersect(item, bounds)) ||
318
- textBoundsIntersect(bounds, placedPlot(cell, plot))) {
319
- throw new Error(
320
- "Facet headers require sufficient non-overlapping space above every child plot."
321
- );
322
- }
323
- previous.push(bounds);
324
- }
325
- }
326
-
327
- export function materializeFacetHeaders(program, layout, plots, prepared) {
328
- const profile = program.materializationConfigs.textMetrics;
329
- const cells = new Map(layout.children.map(cell => [cell.id, cell]));
330
- const plotById = new Map(plots.map(plot => [plot.id, plot]));
331
- const records = prepared.descriptors.map(descriptor => {
332
- if (descriptor.topology === "legacy") {
333
- return legacyItem(descriptor, cells.get(descriptor.id), plotById.get(descriptor.id));
334
- }
335
- return roleItem(descriptor, cells, plotById);
336
- });
337
- const items = records.map(styledItem);
338
- if (prepared.headers.mode === "legacy") {
339
- assertLegacyHeadersFit(items, layout, plotById, profile);
340
- } else {
341
- assertRoleHeadersFit(records, items, layout, profile);
342
- }
343
- const id = `${program.compositionSpec.id}-headers`;
344
- return program
345
- .createGraphics({ id, type: "collection", parent: "canvas" })
346
- .editGraphics({ target: id, property: "items", value: items });
347
- }
1
+ import{isPlainObject as x}from"../core/immutable.js";import{formatVisibleText as E,measureTextWidth as S,resolveTextBounds as b,textBoundsIntersect as p}from"../core/textMetrics.js";import{resolveDisplayLabel as T}from"../grammar/displayLabels.js";import{DEFAULT_COLORS as I,DEFAULT_FONT_FAMILY as B}from"../theme/defaults.js";const K=Object.freeze(["top","right","bottom","left"]),D=Object.freeze({top:0,right:0,bottom:0,left:0});function F(){return{mode:"legacy",common:{fontSize:12,fontFamily:B,fontWeight:600,color:I.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function M(e){if(!x(e))throw new TypeError("Facet headers config must be a plain object.");if(e.common===void 0&&e.mode===void 0)return{...F(),common:{...F().common,...e}};if(!["legacy","roles"].includes(e.mode)||!x(e.common)||!x(e.row)||!x(e.column))throw new Error("Facet headers config must contain canonical mode and role objects.");return{mode:e.mode,common:{...e.common},row:{...e.row},column:{...e.column}}}function g(e,t){const n=M(e);return t==="all"?{...n.common}:{...n.common,...n[t]}}function u(e,t){return T(e,t.labelMap,E)}function H(e){return new Map((e.facet.grid?.cells??[]).map(t=>[t.id,t]))}function O(e,t){const n=e.compositionSpec,s=g(t,"all"),c=H(n);return n.children.map((i,o)=>{const r=c.get(i),f=r===void 0?u(n.facet.values[o],s):`${u(r.rowValue,s)} \xB7 ${u(r.columnValue,s)}`;return{topology:"legacy",role:"column",id:i,text:f,config:s}})}function z(e,t){const n=t==="column"?"column":"row",s=t==="column"?"columnValue":"rowValue",c=new Map;for(const i of e.facet.grid.cells){const o=c.get(i[n]);if(o===void 0)c.set(i[n],{coordinate:i[n],value:i[s],ids:[i.id]});else{if(o.value!==i[s])throw new Error(`Facet ${t} header values conflict at coordinate ${i[n]}.`);o.ids.push(i.id)}}return[...c.values()].sort((i,o)=>i.coordinate-o.coordinate)}function L(e,t){const n=e.compositionSpec,s=g(t,"column");if(n.facet.grid===void 0)return n.children.map((r,f)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(f/n.columns),column:f%n.columns,text:u(n.facet.values[f],s),config:s}));const c=g(t,"row"),i=z(n,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,text:u(r.value,s),config:s})),o=z(n,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,text:u(r.value,c),config:c}));return[...i,...o]}function v(e,t,n){const s=e.filter(c=>c.role===t&&c.text!=="");return s.length===0?0:Math.max(...t==="column"?s.map(c=>c.config.fontSize+c.config.offset):s.map(c=>S(c.text,c.config,n)+c.config.offset))}function N(e,t){const n=M(t),s=n.mode==="legacy"?O(e,n):L(e,n),c=e.compositionSpec.facet.grid===void 0?Math.ceil(e.compositionSpec.children.length/e.compositionSpec.columns):Math.max(...e.compositionSpec.facet.grid.cells.map(o=>o.row))+1,i={outer:{...D},cellRows:{top:Array(c).fill(0),bottom:Array(c).fill(0)}};if(n.mode==="legacy"&&e.compositionSpec.spacing==="plot")for(const o of s){if(o.text==="")continue;const r=e.compositionSpec.children.indexOf(o.id),f=e.compositionSpec.facet.grid?.cells[r]?.row??Math.floor(r/e.compositionSpec.columns);i.cellRows.top[f]=Math.max(i.cellRows.top[f],o.config.offset+o.config.fontSize)}if(n.mode==="roles"){const o=g(n,"column"),r=v(s,"column",e.materializationConfigs.textMetrics);if(e.compositionSpec.facet.grid===void 0){const f=["left","right"].includes(o.side);f&&(i.cellColumns={left:Array(e.compositionSpec.columns).fill(0),right:Array(e.compositionSpec.columns).fill(0)});for(const l of s)if(l.text!==""){const a=f?i.cellColumns:i.cellRows,m=f?l.column:l.row,d=f?S(l.text,l.config,e.materializationConfigs.textMetrics):l.config.fontSize;a[o.side][m]=Math.max(a[o.side][m],d+l.config.offset)}}else{i.outer[o.side]=r;const f=g(n,"row");i.outer[f.side]=v(s,"row",e.materializationConfigs.textMetrics)}}return{headers:n,descriptors:s,headerLayout:i}}function A(e,t){return t==="start"?{x:e.left,textAlign:"left"}:t==="end"?{x:e.right,textAlign:"right"}:{x:(e.left+e.right)/2,textAlign:"center"}}function R(e,t){return t==="start"?{y:e.top,textBaseline:"top"}:t==="end"?{y:e.bottom,textBaseline:"bottom"}:{y:(e.top+e.bottom)/2,textBaseline:"middle"}}function C(e){return{left:Math.min(...e.map(t=>t.left)),right:Math.max(...e.map(t=>t.right)),top:Math.min(...e.map(t=>t.top)),bottom:Math.max(...e.map(t=>t.bottom))}}function h(e,t){return{left:e.x+t.x,right:e.x+t.x+t.width,top:e.y+t.y,bottom:e.y+t.y+t.height}}function y(e){return{left:e.x,right:e.x+e.width,top:e.y,bottom:e.y+e.height}}function $(e,t,n,s){const c=e.ids.map(d=>t.get(d)),i=e.ids.find((d,w)=>c[w]===void 0),o=e.ids.find(d=>n.get(d)===void 0);if(i!==void 0||o!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${i??o}".`);const r=e.ids.map((d,w)=>h(c[w],n.get(d))),f=C(r),l=s==="plot"?f:C(c.map(y)),a=e.config;let m;if(["top","bottom"].includes(a.side)){m=A(f,a.align);const d=a.side==="top";m.y=d?l.top-a.offset-a.fontSize/2:l.bottom+a.offset+a.fontSize/2,m.textBaseline="middle"}else{m=R(f,a.align);const d=a.side==="left";m.x=d?l.left-a.offset:l.right+a.offset,m.textAlign=d?"right":"left"}return{descriptor:e,snapshots:l,properties:m}}function j(e,t,n,s){if(t===void 0||n===void 0)throw new Error(`Facet header requires cell and plot bounds for "${e.id}".`);return{descriptor:e,snapshots:y(t),properties:{...A(h(t,n),e.config.align),y:s==="plot"?t.y+n.y-e.config.offset-e.config.fontSize/2:t.y+e.config.offset,textBaseline:"middle"}}}function q(e){const{config:t}=e.descriptor;return{type:"text",properties:{...e.properties,text:e.descriptor.text,fill:t.color,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight}}}function V(e,t,n,s,c){const i=t.map((o,r)=>e[r].descriptor.text===""?void 0:b(o.properties,s));for(const[o,r]of i.entries())if(r!==void 0&&(r.left<-1e-9||r.right>n.width+1e-9||r.top<-1e-9||r.bottom>n.height+1e-9||i.some((f,l)=>l<o&&f!==void 0&&p(f,r))||n.children.some(f=>p(r,n.spacing==="plot"?h(f,c.get(f.id)):y(f)))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function G(e,t,n,s){const c=[];for(let i=0;i<e.length;i+=1){if(e[i].properties.text==="")continue;const o=b(e[i].properties,s),r=t.children[i],f=n.get(r.id);if(o.left<0||o.right>t.width||o.top<0||o.bottom>t.height||c.some(l=>p(l,o))||(t.spacing==="plot"?t.children.some(l=>p(o,h(l,n.get(l.id)))):p(o,h(r,f))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");c.push(o)}}function P(e,t,n){const s=new Map(e.children.map(o=>[o.id,o])),c=new Map(t.map(o=>[o.id,o])),i=n.descriptors.map(o=>o.topology==="legacy"?j(o,s.get(o.id),c.get(o.id),e.spacing):$(o,s,c,e.spacing));return{records:i,items:i.map(q),plotById:c}}function Y(e,t,n,s){const c=e.materializationConfigs.textMetrics,{records:i,items:o,plotById:r}=P(t,n,s);s.headers.mode==="legacy"?G(o,t,r,c):V(i,o,t,c,r);const f=`${e.compositionSpec.id}-headers`;return e.createGraphics({id:f,type:"collection",parent:"canvas"}).editGraphics({target:f,property:"items",value:o})}export{F as createDefaultFacetHeaders,Y as materializeFacetHeaders,M as normalizeFacetHeadersConfig,N as prepareFacetHeaders,g as resolveFacetHeaderConfig,P as resolveFacetHeaderItems};
@@ -1,185 +1 @@
1
- import { resolveGraphicBounds } from "../layout/canvas.js";
2
- import { resolvePlacedPlotBounds } from "../layout/composition.js";
3
- import {
4
- alignedTextAnchor,
5
- buildTitleReadingBlock
6
- } from "../layout/title.js";
7
- import { resolveFacetLayout } from "../layout/facets.js";
8
- import { namespaceGraphicSnapshot } from "./compositionSnapshot.js";
9
- import { prepareSharedFacetLegend } from "./facetGuides/index.js";
10
- import {
11
- attachSnapshotObject,
12
- clearCompositionChildren,
13
- compositionChildDescriptor
14
- } from "./composition.js";
15
- import {
16
- materializeFacetHeaders,
17
- prepareFacetHeaders
18
- } from "./facetHeaders.js";
19
-
20
- const ZERO_MARGIN = { top: 0, right: 0, bottom: 0, left: 0 };
21
-
22
- function facetConfig(program) {
23
- const config = program.materializationConfigs.facets?.[program.compositionSpec.id];
24
- if (config === undefined) {
25
- throw new Error(`Facet "${program.compositionSpec.id}" requires materialization config.`);
26
- }
27
- return config;
28
- }
29
-
30
- function titleLayout(program) {
31
- if (program.semanticSpec.title.text === undefined) {
32
- return { height: 0 };
33
- }
34
- const config = program.titleConfig;
35
- if (config === undefined) {
36
- throw new Error("Facet title requires chart title configuration.");
37
- }
38
- if (config.position !== "top") {
39
- throw new Error("Facet parent title currently supports only top position.");
40
- }
41
- const block = buildTitleReadingBlock({
42
- text: program.semanticSpec.title.text,
43
- subtitle: program.semanticSpec.title.subtitle
44
- }, config);
45
- const top = 8 + config.offset;
46
- if (top < 0) {
47
- throw new Error("Facet parent title offset places text outside the Canvas.");
48
- }
49
- return { config, block, top, height: Math.ceil(top + block.height) };
50
- }
51
-
52
- function materializeTitleComponent(program, id, lines, centers, style, plot, top) {
53
- if (lines.length === 0) return program;
54
- const x = alignedTextAnchor(plot.x, plot.width, program.titleConfig.align);
55
- const count = lines.length;
56
- let next = program.createGraphics({
57
- id,
58
- type: "text",
59
- ...(count > 1 ? { length: count } : {}),
60
- parent: "canvas"
61
- });
62
- for (const [property, value] of Object.entries({
63
- x,
64
- y: count === 1 ? top + centers[0] : centers.map(center => top + center),
65
- text: count === 1 ? lines[0] : lines,
66
- fill: style.color,
67
- fontSize: style.fontSize,
68
- fontFamily: style.fontFamily,
69
- fontWeight: style.fontWeight,
70
- textAlign: program.titleConfig.align,
71
- textBaseline: "middle"
72
- })) {
73
- next = next.editGraphics({ target: id, property, value });
74
- }
75
- return next;
76
- }
77
-
78
- function materializeTitle(program, plot, title) {
79
- let next = materializeTitleComponent(
80
- program,
81
- "chartTitle",
82
- title.block.titleLines,
83
- title.block.titleCenters,
84
- title.config.titleStyle,
85
- plot,
86
- title.top
87
- );
88
- if (title.block.subtitleLines.length > 0) {
89
- next = materializeTitleComponent(
90
- next,
91
- "chartSubtitle",
92
- title.block.subtitleLines,
93
- title.block.subtitleCenters,
94
- title.config.subtitleStyle,
95
- plot,
96
- title.top
97
- );
98
- }
99
- return next;
100
- }
101
-
102
- export function resolveFacetProgramLayout(program, preparedLegend) {
103
- program._assertCompositionProgram("resolveFacetProgramLayout");
104
- if (program.compositionSpec.type !== "facet") {
105
- throw new Error("resolveFacetProgramLayout requires a facet composition.");
106
- }
107
- const title = titleLayout(program);
108
- const spec = program.compositionSpec;
109
- const preparedHeaders = prepareFacetHeaders(
110
- program,
111
- facetConfig(program).headers
112
- );
113
- const gridCells = new Map(
114
- (spec.facet.grid?.cells ?? []).map(cell => [cell.id, cell])
115
- );
116
- const layout = resolveFacetLayout({
117
- children: spec.children.map((id, index) => ({
118
- ...compositionChildDescriptor(id, program.children[id]),
119
- value: spec.facet.values[index],
120
- ...(gridCells.has(id) ? {
121
- row: gridCells.get(id).row,
122
- column: gridCells.get(id).column
123
- } : {})
124
- })),
125
- columns: spec.columns,
126
- gap: spec.gap,
127
- align: spec.align,
128
- padding: spec.padding,
129
- titleHeight: title.height,
130
- headerLayout: preparedHeaders.headerLayout,
131
- sharedLegend: spec.facet.guides.legend === "shared",
132
- ...(preparedLegend === undefined ? {} : {
133
- sharedLegendGap: preparedLegend.reservation.gap,
134
- sharedLegendWidth: preparedLegend.reservation.width,
135
- sharedLegendHeight: preparedLegend.reservation.height,
136
- sharedLegendPosition: preparedLegend.reservation.position
137
- })
138
- });
139
- const plots = spec.children.map(id => ({
140
- id,
141
- ...resolveGraphicBounds(program.children[id])
142
- }));
143
- const plot = resolvePlacedPlotBounds({
144
- placements: layout.children,
145
- plots
146
- });
147
- return { layout, title, plot, plots, preparedHeaders };
148
- }
149
-
150
- export function materializeFacetGraphics(program) {
151
- const preparedLegend = prepareSharedFacetLegend(program);
152
- const { layout, title, plot, plots, preparedHeaders } = resolveFacetProgramLayout(
153
- program,
154
- preparedLegend
155
- );
156
- const background = program.graphicSpec.objects.canvas?.properties
157
- ?.background ?? "white";
158
- let next = clearCompositionChildren(program);
159
- if (next.graphicSpec.objects.canvas === undefined) {
160
- next = next.createGraphics({ id: "canvas", type: "canvas" });
161
- }
162
- for (const [property, value] of Object.entries({
163
- width: layout.width,
164
- height: layout.height,
165
- background
166
- })) {
167
- next = next.editGraphics({ target: "canvas", property, value });
168
- }
169
- for (const placement of layout.children) {
170
- const child = program.children[placement.id];
171
- const snapshot = namespaceGraphicSnapshot(child.graphicSpec, {
172
- namespace: `${program.compositionSpec.id}-${placement.id}`,
173
- x: placement.x,
174
- y: placement.y
175
- });
176
- next = attachSnapshotObject(next, snapshot, snapshot.order[0], "canvas");
177
- }
178
- next = materializeFacetHeaders(next, layout, plots, preparedHeaders);
179
- next = next.composeFacetGuides({ layout, plot });
180
- if (title.height > 0) next = materializeTitle(next, plot, title);
181
- return next._withCanvasConfig({
182
- margin: ZERO_MARGIN,
183
- size: { width: "auto", height: "auto" }
184
- });
185
- }
1
+ import{cartesianAxisOwners as C}from"../selectors/axes.js";import{resolveConcreteGraphicBounds as G}from"../grammar/schemas/graphicBounds.js";import{textBoundsFitCanvas as F,textBoundsIntersect as m,resolveTextBounds as k}from"../core/textMetrics.js";import{axisGraphicIds as L,allLegendGraphicIds as j}from"./guides/resources.js";import{resolveGraphicBounds as u}from"../layout/canvas.js";import{resolvePlacedPlotBounds as P}from"../layout/composition.js";import{alignedTextAnchor as z,buildTitleReadingBlock as T,paintedTitleComponent as B,resolveTitleComponentBounds as E,titleTextProperties as O}from"../layout/title.js";import{resolveFacetLayout as $}from"../layout/facets.js";import{namespaceGraphicId as b,namespaceGraphicSnapshot as M}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as v}from"./facetGuides/index.js";import{resolvePromotedFacetLegendLayout as H}from"./facetGuides/layout.js";import{attachSnapshotObject as I,clearCompositionChildren as A,compositionChildDescriptor as q}from"./composition.js";import{materializeFacetHeaders as R,prepareFacetHeaders as _}from"./facetHeaders.js";const W={top:0,right:0,bottom:0,left:0};function D(e){const c=e.materializationConfigs.facets?.[e.compositionSpec.id];if(c===void 0)throw new Error(`Facet "${e.compositionSpec.id}" requires materialization config.`);return c}function N(e){if(e.semanticSpec.title.text===void 0)return{height:0};const c=e.titleConfig;if(c===void 0)throw new Error("Facet title requires chart title configuration.");if(c.position!=="top")throw new Error("Facet parent title currently supports only top position.");const i=T({text:e.semanticSpec.title.text,subtitle:e.semanticSpec.title.subtitle},c),o=8+c.offset;if(o<0)throw new Error("Facet parent title offset places text outside the Canvas.");return{config:c,block:i,top:o,height:Math.ceil(o+i.height)}}function w(e,c,i,o,l,d,s){if(i.length===0)return e;const r=z(d.x,d.width,e.titleConfig.align)+(e.titleConfig.alongOffset??0),t=B({x:r,y:o.map(h=>s+h),lines:i,textAlign:e.titleConfig.align}),n=E(t,l,e.materializationConfigs.textMetrics);if(!F(n,e.graphicSpec.objects.canvas.properties))throw new Error("Facet parent title places text outside the Canvas.");const a=t.lines.length;let p=e.createGraphics({id:c,type:"text",...a>1?{length:a}:{},parent:"canvas"});for(const[h,f]of Object.entries(O(t,l)))p=p.editGraphics({target:c,property:h,value:f});return p}function Z(e,c,i){let o=w(e,"chartTitle",i.block.titleLines,i.block.titleCenters,i.config.titleStyle,c,i.top);return i.block.subtitleLines.length>0&&(o=w(o,"chartSubtitle",i.block.subtitleLines,i.block.subtitleCenters,i.config.subtitleStyle,c,i.top)),o}function J(e,c){if(e._assertCompositionProgram("resolveFacetProgramLayout"),e.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");c??=v(e);const i=N(e),o=e.compositionSpec,l=_(e,D(e).headers),d=new Map((o.facet.grid?.cells??[]).map(n=>[n.id,n]));let s=$({children:o.children.map((n,a)=>({...q(n,e.children[n]),value:o.facet.values[a],...d.has(n)?{row:d.get(n).row,column:d.get(n).column}:{}})),spacing:o.spacing,plots:o.children.map(n=>({id:n,...u(e.children[n])})),columns:o.columns,gap:o.gap,align:o.align,padding:o.padding,titleHeight:i.height,headerLayout:l.headerLayout,sharedLegend:o.facet.guides.legend==="shared"&&c?.mode!=="promoted",...c===void 0?{}:{sharedLegendGap:c.reservation.gap,sharedLegendWidth:c.reservation.width,sharedLegendHeight:c.reservation.height,sharedLegendPosition:c.reservation.position}});const r=o.children.map(n=>({id:n,...u(e.children[n])}));let t=P({placements:s.children,plots:r});return{layout:s,plot:t}=H(e,{layout:s,plot:t,plots:r,title:i,preparedHeaders:l,prepared:c}),{layout:s,title:i,plot:t,plots:r,preparedHeaders:l}}function K(e,c,i){const o=[],l=c.children.map((s,r)=>({id:s.id,left:s.x+i[r].x,right:s.x+i[r].x+i[r].width,top:s.y+i[r].y,bottom:s.y+i[r].y+i[r].height}));for(const s of c.children){const r=e.children[s.id],t=[...C(r).flatMap(n=>L(n.channel,n.id).slice(2)),...j(Object.keys(r.guideConfigs.legend??{}))];for(const n of t){const a=b(`${e.compositionSpec.id}-${s.id}`,n);if(e.graphicSpec.objects[a]===void 0)continue;const p=G(e.graphicSpec,a,e.materializationConfigs.textMetrics);p!==void 0&&o.push({owner:s.id,bounds:p})}}const d=e.graphicSpec.objects[`${e.compositionSpec.id}-headers`];for(const s of d?.items??[])s.properties.text!==""&&o.push({owner:"headers",bounds:k(s.properties,e.materializationConfigs.textMetrics)});for(let s=0;s<o.length;s+=1){const r=o[s];if(l.some(t=>t.id!==r.owner&&m(r.bounds,t))||o.slice(0,s).some(t=>t.owner!==r.owner&&m(r.bounds,t.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function ae(e){const c=v(e),{layout:i,title:o,plot:l,plots:d,preparedHeaders:s}=J(e,c),r=e.graphicSpec.objects.canvas?.properties?.background??"white";let t=A(e);t.graphicSpec.objects.canvas===void 0&&(t=t.createGraphics({id:"canvas",type:"canvas"}));for(const[n,a]of Object.entries({width:i.width,height:i.height,background:r}))t=t.editGraphics({target:"canvas",property:n,value:a});for(const n of i.children){const a=e.children[n.id],p=M(a.graphicSpec,{namespace:`${e.compositionSpec.id}-${n.id}`,x:n.x,y:n.y});if(t=I(t,p,p.order[0],"canvas"),i.spacing==="plot"){const h=p.order[0],f=b(`${e.compositionSpec.id}-${n.id}`,"plot-background"),y=u(a),g=t.graphicSpec.objects[h].children?.[0];t=t.editGraphics({target:h,property:"background",value:"transparent"}).createGraphics({id:f,type:"rect",parent:h,...g===void 0?{}:{before:g}});for(const[x,S]of Object.entries({...y,stroke:"none",strokeWidth:0,fill:a.graphicSpec.objects.canvas.properties.background??"transparent"}))t=t.editGraphics({target:f,property:x,value:S})}}return t=R(t,i,d,s),t=t.composeFacetGuides({layout:i,plot:l}),i.spacing==="plot"&&K(t,i,d),o.height>0&&(t=Z(t,l,o)),t._withCanvasConfig({margin:W,size:{width:"auto",height:"auto"}})}export{ae as materializeFacetGraphics,J as resolveFacetProgramLayout};
@@ -1,3 +1,5 @@
1
+ import { cartesianAxisOwners } from "../selectors/axes.js";
2
+ import { axisGraphicIds } from "./guides/resources.js";
1
3
  import {
2
4
  findGraphicParent,
3
5
  graphicSiblings
@@ -55,8 +57,10 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
55
57
  ? dataset.transform[0]
56
58
  : undefined;
57
59
  if (transform?.type !== "regression") {
60
+ const named = new Set(cartesianAxisOwners(program).filter(owner => owner.named)
61
+ .flatMap(owner => axisGraphicIds(owner.channel, owner.id)));
58
62
  const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
59
- id => /^(x|y)Axis/.test(id)
63
+ id => id === "plotBorder" || /^(x|y)Axis/.test(id) || named.has(id)
60
64
  );
61
65
  return resolvePlotGraphicPlacement(
62
66
  program,
@@ -0,0 +1,73 @@
1
+ function translateCommands(commands, dx, dy) {
2
+ return commands.map(command => {
3
+ if (command.op === "Z") return { ...command };
4
+ const next = {
5
+ ...command,
6
+ x: command.x + dx,
7
+ y: command.y + dy
8
+ };
9
+ if (command.op === "C") {
10
+ next.x1 = command.x1 + dx;
11
+ next.y1 = command.y1 + dy;
12
+ next.x2 = command.x2 + dx;
13
+ next.y2 = command.y2 + dy;
14
+ }
15
+ return next;
16
+ });
17
+ }
18
+
19
+ export function translatedProperties(type, properties, dx, dy) {
20
+ const next = { ...properties };
21
+ if (["circle", "rect", "text", "canvas"].includes(type)) {
22
+ next.x = (next.x ?? 0) + dx;
23
+ next.y = (next.y ?? 0) + dy;
24
+ } else if (type === "line") {
25
+ next.x1 += dx;
26
+ next.x2 += dx;
27
+ next.y1 += dy;
28
+ next.y2 += dy;
29
+ } else if (type === "path") {
30
+ next.commands = translateCommands(next.commands, dx, dy);
31
+ }
32
+ return next;
33
+ }
34
+
35
+ export function translateGraphic(program, id, dx, dy) {
36
+ const graphic = program.graphicSpec.objects[id];
37
+ if (dx === 0 && dy === 0) return program;
38
+ if (graphic.items !== undefined) {
39
+ let next = program.editGraphics({
40
+ target: id,
41
+ property: "items",
42
+ value: graphic.items.map(item => ({
43
+ type: item.type ?? graphic.type,
44
+ properties: translatedProperties(
45
+ item.type ?? graphic.type,
46
+ item.properties,
47
+ dx,
48
+ dy
49
+ )
50
+ }))
51
+ });
52
+ if (graphic.type !== "canvas") {
53
+ for (const child of graphic.children ?? []) next = translateGraphic(next, child, dx, dy);
54
+ }
55
+ return next;
56
+ }
57
+ let next = program;
58
+ const properties = translatedProperties(
59
+ graphic.type,
60
+ graphic.properties,
61
+ dx,
62
+ dy
63
+ );
64
+ for (const [property, value] of Object.entries(properties)) {
65
+ if (value !== graphic.properties[property]) {
66
+ next = next.editGraphics({ target: id, property, value });
67
+ }
68
+ }
69
+ if (graphic.type !== "canvas") {
70
+ for (const child of graphic.children ?? []) next = translateGraphic(next, child, dx, dy);
71
+ }
72
+ return next;
73
+ }