ggaction 0.0.16 → 0.0.18

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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -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 +10 -4
  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 +16 -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 +4 -4
  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/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  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 +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  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/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -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{resolveConcreteGraphicBounds as C}from"../grammar/schemas/graphicBounds.js";import{textBoundsFitCanvas as G,textBoundsIntersect as m,resolveTextBounds as F}from"../core/textMetrics.js";import{axisGraphicIds as k,allLegendGraphicIds as L}from"./guides/resources.js";import{resolveGraphicBounds as u}from"../layout/canvas.js";import{resolvePlacedPlotBounds as j}from"../layout/composition.js";import{alignedTextAnchor as P,buildTitleReadingBlock as z,paintedTitleComponent as T,resolveTitleComponentBounds as B,titleTextProperties as E}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 O}from"./facetGuides/layout.js";import{attachSnapshotObject as H,clearCompositionChildren as I,compositionChildDescriptor as q}from"./composition.js";import{materializeFacetHeaders as A,prepareFacetHeaders as R}from"./facetHeaders.js";const _={top:0,right:0,bottom:0,left:0};function W(e){const n=e.materializationConfigs.facets?.[e.compositionSpec.id];if(n===void 0)throw new Error(`Facet "${e.compositionSpec.id}" requires materialization config.`);return n}function D(e){if(e.semanticSpec.title.text===void 0)return{height:0};const n=e.titleConfig;if(n===void 0)throw new Error("Facet title requires chart title configuration.");if(n.position!=="top")throw new Error("Facet parent title currently supports only top position.");const i=z({text:e.semanticSpec.title.text,subtitle:e.semanticSpec.title.subtitle},n),o=8+n.offset;if(o<0)throw new Error("Facet parent title offset places text outside the Canvas.");return{config:n,block:i,top:o,height:Math.ceil(o+i.height)}}function w(e,n,i,o,l,d,s){if(i.length===0)return e;const r=P(d.x,d.width,e.titleConfig.align)+(e.titleConfig.alongOffset??0),t=T({x:r,y:o.map(h=>s+h),lines:i,textAlign:e.titleConfig.align}),c=B(t,l,e.materializationConfigs.textMetrics);if(!G(c,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:n,type:"text",...a>1?{length:a}:{},parent:"canvas"});for(const[h,f]of Object.entries(E(t,l)))p=p.editGraphics({target:n,property:h,value:f});return p}function N(e,n,i){let o=w(e,"chartTitle",i.block.titleLines,i.block.titleCenters,i.config.titleStyle,n,i.top);return i.block.subtitleLines.length>0&&(o=w(o,"chartSubtitle",i.block.subtitleLines,i.block.subtitleCenters,i.config.subtitleStyle,n,i.top)),o}function Z(e,n){if(e._assertCompositionProgram("resolveFacetProgramLayout"),e.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");n??=v(e);const i=D(e),o=e.compositionSpec,l=R(e,W(e).headers),d=new Map((o.facet.grid?.cells??[]).map(c=>[c.id,c]));let s=$({children:o.children.map((c,a)=>({...q(c,e.children[c]),value:o.facet.values[a],...d.has(c)?{row:d.get(c).row,column:d.get(c).column}:{}})),spacing:o.spacing,plots:o.children.map(c=>({id:c,...u(e.children[c])})),columns:o.columns,gap:o.gap,align:o.align,padding:o.padding,titleHeight:i.height,headerLayout:l.headerLayout,sharedLegend:o.facet.guides.legend==="shared"&&n?.mode!=="promoted",...n===void 0?{}:{sharedLegendGap:n.reservation.gap,sharedLegendWidth:n.reservation.width,sharedLegendHeight:n.reservation.height,sharedLegendPosition:n.reservation.position}});const r=o.children.map(c=>({id:c,...u(e.children[c])}));let t=j({placements:s.children,plots:r});return{layout:s,plot:t}=O(e,{layout:s,plot:t,plots:r,title:i,preparedHeaders:l,prepared:n}),{layout:s,title:i,plot:t,plots:r,preparedHeaders:l}}function J(e,n,i){const o=[],l=n.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 n.children){const r=e.children[s.id],t=[...["x","y"].flatMap(k).filter(c=>/Labels|Title/.test(c)),...L(Object.keys(r.guideConfigs.legend??{}))];for(const c of t){const a=b(`${e.compositionSpec.id}-${s.id}`,c);if(e.graphicSpec.objects[a]===void 0)continue;const p=C(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:F(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 se(e){const n=v(e),{layout:i,title:o,plot:l,plots:d,preparedHeaders:s}=Z(e,n),r=e.graphicSpec.objects.canvas?.properties?.background??"white";let t=I(e);t.graphicSpec.objects.canvas===void 0&&(t=t.createGraphics({id:"canvas",type:"canvas"}));for(const[c,a]of Object.entries({width:i.width,height:i.height,background:r}))t=t.editGraphics({target:"canvas",property:c,value:a});for(const c of i.children){const a=e.children[c.id],p=M(a.graphicSpec,{namespace:`${e.compositionSpec.id}-${c.id}`,x:c.x,y:c.y});if(t=H(t,p,p.order[0],"canvas"),i.spacing==="plot"){const h=p.order[0],f=b(`${e.compositionSpec.id}-${c.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=A(t,i,d,s),t=t.composeFacetGuides({layout:i,plot:l}),i.spacing==="plot"&&J(t,i,d),o.height>0&&(t=N(t,l,o)),t._withCanvasConfig({margin:_,size:{width:"auto",height:"auto"}})}export{se as materializeFacetGraphics,Z as resolveFacetProgramLayout};
@@ -56,7 +56,7 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
56
56
  : undefined;
57
57
  if (transform?.type !== "regression") {
58
58
  const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
59
- id => /^(x|y)Axis/.test(id)
59
+ id => id === "plotBorder" || /^(x|y)Axis/.test(id)
60
60
  );
61
61
  return resolvePlotGraphicPlacement(
62
62
  program,
@@ -1,57 +1 @@
1
- import { assertGuideCollisionBlocks } from "../../layout/guideCollisions.js";
2
- import { unionConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
3
- import { axisGraphicIds, legendResourcePolicies } from "./resources.js";
4
-
5
- // Only domain-owned guides participate. Extension primitives may deliberately
6
- // overlap, and a combined categorical/size legend is one occupied group.
7
- export function resolveGuideCollisionBlocks(graphicSpec, guideConfigs, titleConfig, profile) {
8
- const blocks = [];
9
- const append = (id, kind, position, ids) => {
10
- if (position === undefined) return;
11
- const existing = ids.filter(id => graphicSpec.objects[id] !== undefined);
12
- const bounds = unionConcreteGraphicBounds(graphicSpec, existing, profile);
13
- if (bounds !== undefined) blocks.push({ id, kind, position, bounds });
14
- };
15
- append("chart title", "title", titleConfig?.position, ["chartTitle", "chartSubtitle"]);
16
- for (const channel of ["x", "y"]) {
17
- for (const id of axisGraphicIds(channel)) {
18
- const component = id.slice(`${channel}Axis`.length).toLowerCase();
19
- append(`${channel}-axis ${component}`, "axis", guideConfigs.axis?.[channel]?.[component]?.position, [id]);
20
- }
21
- }
22
- const policies = legendResourcePolicies();
23
- const configs = guideConfigs.legend ?? {};
24
- const categorical = policies.find(policy => policy.family === "categorical" && configs[policy.kind] !== undefined);
25
- const combined = categorical !== undefined && configs.size !== undefined &&
26
- configs[categorical.kind].target === configs.size.target;
27
- for (const policy of policies) {
28
- const config = configs[policy.kind];
29
- if (config === undefined || combined && policy.kind === "size") continue;
30
- const ids = combined && policy === categorical
31
- ? [...policy.graphicIds, ...policies.find(item => item.kind === "size").graphicIds]
32
- : policy.graphicIds;
33
- append(`${policy.kind}${combined && policy === categorical ? "+size" : ""} legend`, "legend", config.position, ids);
34
- }
35
- return blocks;
36
- }
37
-
38
-
39
- // Defer cross-guide checks while sibling materializers still carry old
40
- // coordinates. The scope is transient and never survives a returned program.
41
- export function withGuideLayoutTransaction(program, apply) {
42
- if (program.context.deferGuideLayoutValidation === true) return apply(program);
43
- const result = apply(program._withContext({ deferGuideLayoutValidation: true }));
44
- const { deferGuideLayoutValidation: _deferred, ...context } = result.context;
45
- if (Object.hasOwn(program.context, "deferGuideLayoutValidation")) {
46
- context.deferGuideLayoutValidation = program.context.deferGuideLayoutValidation;
47
- }
48
- const next = result._clone({ context });
49
- assertGuideCollisionBlocks(resolveGuideCollisionBlocks(next.graphicSpec, next.guideConfigs, next.titleConfig, next.materializationConfigs.textMetrics));
50
- return next;
51
- }
52
-
53
- export function withGuideLayoutValidation(implementation) {
54
- return function (args) {
55
- return withGuideLayoutTransaction(this, program => implementation.call(program, args));
56
- };
57
- }
1
+ import{needsLegendAxisReflow as c}from"./resources.js";import{getErrorDetails as u}from"../../core/diagnostics.js";import{planCanvasRematerialization as m}from"../layout.js";import{applyMaterializationPlan as p}from"../planner.js";import{assertGuideCollisionBlocks as g}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as h}from"./resources.js";import{resolveGuideCollisionBlocks as k}from"./resources.js";function C(i,d){if(i.context.deferGuideLayoutValidation===!0)return d(i);const n=i.materializationConfigs.canvas?.plot;let o=i.materializationConfigs.canvas?.margin,e;for(let f=0;;f+=1)try{let t=i._withContext({deferGuideLayoutValidation:!0});f>0&&(t=t.editGraphics({target:"canvas",property:"width",value:n.width+o.left+o.right}).editGraphics({target:"canvas",property:"height",value:n.height+o.top+o.bottom})._withCanvasConfig({...t.materializationConfigs.canvas,margin:o}),t=p(t,m(t))),e=d(t),e.compositionSpec===void 0&&Object.values(e.guideConfigs.legend??{}).some(r=>["left","right"].includes(r.position))&&(e=e.rematerializeSideLegendLane()),e.compositionSpec===void 0&&c(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&Object.keys(e.guideConfigs.legendGroups??{}).length>0&&(e=e.rematerializeLegendGroups()),n!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle());break}catch(t){const r=u(t)?.canvasOverflow;if(n===void 0||r===void 0||f>=16)throw t;o=Object.fromEntries(Object.keys(o).map(l=>[l,r[l]>0?Math.ceil((o[l]+r[l])*4)/4:o[l]]))}const{deferGuideLayoutValidation:v,...s}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(s.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const a=e._clone({context:s});return g(h(a.graphicSpec,a.guideConfigs,a.titleConfig,a.materializationConfigs.textMetrics)),a}function b(i){return function(d){return C(this,n=>i.call(n,d))}}export{k as resolveGuideCollisionBlocks,C as withGuideLayoutTransaction,b as withGuideLayoutValidation};
@@ -1,123 +1 @@
1
- const AXIS_COMPONENTS = Object.freeze(["Line", "Ticks", "Labels", "Title"]);
2
-
3
- const CATEGORICAL_COMPONENTS = Object.freeze([
4
- "Symbols", "SymbolLines", "SymbolPoints", "SymbolSwatches",
5
- "Labels", "Title", "Background"
6
- ]);
7
-
8
- const LEGEND_RESOURCE_POLICIES = Object.freeze({
9
- series: Object.freeze({
10
- semanticKind: "series",
11
- family: "categorical",
12
- rematerializeOp: undefined,
13
- graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
14
- component => `seriesLegend${component}`
15
- ))
16
- }),
17
- color: Object.freeze({
18
- semanticKind: "color",
19
- family: "categorical",
20
- rematerializeOp: undefined,
21
- graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
22
- component => `colorLegend${component}`
23
- ))
24
- }),
25
- stroke: Object.freeze({
26
- semanticKind: "stroke",
27
- family: "categorical",
28
- rematerializeOp: undefined,
29
- graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
30
- component => `strokeLegend${component}`
31
- ))
32
- }),
33
- size: Object.freeze({
34
- semanticKind: "size",
35
- family: "size",
36
- rematerializeOp: "rematerializeSizeLegend",
37
- graphicIds: Object.freeze([
38
- "sizeLegendBackground", "sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
39
- ])
40
- }),
41
- gradient: Object.freeze({
42
- semanticKind: "color",
43
- family: "continuous",
44
- rematerializeOp: "rematerializeGradientLegend",
45
- graphicIds: Object.freeze([
46
- "colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
47
- "colorGradientLabels", "colorGradientTitle"
48
- ])
49
- }),
50
- interval: Object.freeze({
51
- semanticKind: "color",
52
- family: "interval",
53
- rematerializeOp: "rematerializeIntervalLegend",
54
- graphicIds: Object.freeze([
55
- "colorLegendBackground", "colorLegendSymbols", "colorLegendLabels",
56
- "colorLegendTitle"
57
- ])
58
- }),
59
- strokeGradient: Object.freeze({
60
- semanticKind: "stroke",
61
- family: "continuous",
62
- rematerializeOp: "rematerializeStrokeGradientLegend",
63
- graphicIds: Object.freeze([
64
- "strokeGradientBackground", "strokeGradientStrips", "strokeGradientTicks",
65
- "strokeGradientLabels", "strokeGradientTitle"
66
- ])
67
- }),
68
- strokeInterval: Object.freeze({
69
- semanticKind: "stroke",
70
- family: "interval",
71
- rematerializeOp: "rematerializeStrokeIntervalLegend",
72
- graphicIds: Object.freeze([
73
- "strokeIntervalBackground", "strokeIntervalSymbols",
74
- "strokeIntervalLabels", "strokeIntervalTitle"
75
- ])
76
- }),
77
- opacity: Object.freeze({
78
- semanticKind: "opacity",
79
- family: "continuous",
80
- rematerializeOp: "rematerializeOpacityLegend",
81
- graphicIds: Object.freeze([
82
- "opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
83
- "opacityLegendTitle"
84
- ])
85
- }),
86
- strokeWidth: Object.freeze({
87
- semanticKind: "strokeWidth",
88
- family: "strokeWidth",
89
- rematerializeOp: "rematerializeStrokeWidthLegend",
90
- graphicIds: Object.freeze([
91
- "strokeWidthLegendBackground", "strokeWidthLegendSymbols", "strokeWidthLegendLabels",
92
- "strokeWidthLegendTitle"
93
- ])
94
- })
95
- });
96
-
97
- export function axisGraphicIds(channel) {
98
- const prefix = channel === "radius" ? "radial" : channel;
99
- return AXIS_COMPONENTS.map(component => `${prefix}Axis${component}`);
100
- }
101
-
102
- export function legendGraphicIds(kind) {
103
- return legendResourcePolicy(kind).graphicIds;
104
- }
105
-
106
- export function legendResourcePolicy(kind) {
107
- const policy = LEGEND_RESOURCE_POLICIES[kind];
108
- if (policy === undefined) {
109
- throw new Error(`Unknown legend graphic kind "${kind}".`);
110
- }
111
- return policy;
112
- }
113
-
114
- export function legendResourcePolicies() {
115
- return Object.entries(LEGEND_RESOURCE_POLICIES).map(([kind, policy]) => ({
116
- kind,
117
- ...policy
118
- }));
119
- }
120
-
121
- export function allLegendGraphicIds(kinds) {
122
- return [...new Set(kinds.flatMap(legendGraphicIds))];
123
- }
1
+ import{unionConcreteGraphicBounds as h}from"../../grammar/schemas/graphicBounds.js";function z(e,t,o,n){const r=[],s=(i,a,c,f)=>{if(c===void 0)return;const k=f.filter(L=>e.objects[L]!==void 0),b=h(e,k,n);b!==void 0&&r.push({id:i,kind:a,position:c,bounds:b})};s("chart title","title",o?.position,["chartTitle","chartSubtitle"]);for(const i of["x","y"])for(const a of I(i)){const c=a.slice(`${i}Axis`.length).toLowerCase();s(`${i}-axis ${c}`,"axis",t.axis?.[i]?.[c]?.position,[a])}const l=S(),d=t.legend??{},p=l.find(i=>i.family==="categorical"&&d[i.kind]!==void 0),g=p!==void 0&&d.size!==void 0&&d[p.kind].target===d.size.target&&(d.size.inheritPosition!==!1||d[p.kind].position===d.size.position),m=t.legendGroups??{};for(const[i,a]of Object.entries(m)){const c=l.filter(f=>d[f.kind]?.target===i).flatMap(f=>f.graphicIds);s(`${i} legend group`,"legend",a.placement.position??"free",c)}for(const i of l){const a=d[i.kind];if(m[a?.target]!==void 0||a===void 0||g&&i.kind==="size")continue;const c=g&&i===p?[...i.graphicIds,...l.find(f=>f.kind==="size").graphicIds]:i.graphicIds;s(`${i.kind}${g&&i===p?"+size":""} legend`,"legend",a.position,c)}return r}const y=Object.freeze(["Line","Ticks","Labels","Title"]),u=Object.freeze(["Symbols","SymbolLines","SymbolPoints","SymbolSwatches","Labels","Title","Background"]),O=Object.freeze({series:Object.freeze({semanticKind:"series",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`strokeLegend${e}`))}),size:Object.freeze({semanticKind:"size",family:"size",rematerializeOp:"rematerializeSizeLegend",graphicIds:Object.freeze(["sizeLegendBackground","sizeLegendSymbols","sizeLegendLabels","sizeLegendTitle"])}),gradient:Object.freeze({semanticKind:"color",family:"continuous",rematerializeOp:"rematerializeGradientLegend",graphicIds:Object.freeze(["colorGradientBackground","colorGradientStrips","colorGradientTicks","colorGradientSymbols","colorGradientLabels","colorGradientTitle"])}),interval:Object.freeze({semanticKind:"color",family:"interval",rematerializeOp:"rematerializeIntervalLegend",graphicIds:Object.freeze(["colorLegendBackground","colorLegendSymbols","colorLegendLabels","colorLegendTitle"])}),strokeGradient:Object.freeze({semanticKind:"stroke",family:"continuous",rematerializeOp:"rematerializeStrokeGradientLegend",graphicIds:Object.freeze(["strokeGradientBackground","strokeGradientStrips","strokeGradientTicks","strokeGradientLabels","strokeGradientTitle"])}),strokeInterval:Object.freeze({semanticKind:"stroke",family:"interval",rematerializeOp:"rematerializeStrokeIntervalLegend",graphicIds:Object.freeze(["strokeIntervalBackground","strokeIntervalSymbols","strokeIntervalLabels","strokeIntervalTitle"])}),opacity:Object.freeze({semanticKind:"opacity",family:"continuous",rematerializeOp:"rematerializeOpacityLegend",graphicIds:Object.freeze(["opacityLegendBackground","opacityLegendSymbols","opacityLegendLabels","opacityLegendTitle"])}),strokeWidth:Object.freeze({semanticKind:"strokeWidth",family:"strokeWidth",rematerializeOp:"rematerializeStrokeWidthLegend",graphicIds:Object.freeze(["strokeWidthLegendBackground","strokeWidthLegendSymbols","strokeWidthLegendLabels","strokeWidthLegendTitle"])})});function I(e){const t=e==="radius"?"radial":e;return y.map(o=>`${t}Axis${o}`)}function j(e){return x(e).graphicIds}function x(e){const t=O[e];if(t===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return t}function S(){return Object.entries(O).map(([e,t])=>({kind:e,...t}))}function v(e){return[...new Set(e.flatMap(j))]}function C(e,t,o){if(t.inferredOffset!==!0||!["top","bottom"].includes(t.position)||t.layout==="legacy-bottom")return t.offset;const n=z(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(s=>s.kind==="axis"&&s.position===t.position),r=t.position==="bottom"?Math.max(0,...n.map(s=>s.bounds.bottom-o.y-o.height)):Math.max(0,...n.map(s=>o.y-s.bounds.top));return t.offset+r}function G(e,t,o){if(t!=="size"||o.inheritPosition===!1)return o;const n=["series","color","stroke"].map(r=>e.guideConfigs.legend?.[r]).find(r=>r?.target===o.target);return n===void 0?o:{...o,position:n.position,offset:n.offset,inferredOffset:n.inferredOffset}}function $(e,t){return[e,t].some(n=>Object.entries(n.guideConfigs.legend??{}).some(([r,s])=>{const l=G(n,r,s);return l.inferredOffset===!0&&["top","bottom"].includes(l.position)&&l.layout!=="legacy-bottom"})||Object.values(n.guideConfigs.legendGroups??{}).some(r=>r.inferredOffset===!0&&["top","bottom"].includes(r.placement.position)))&&[e,t].some(n=>z(n.graphicSpec,n.guideConfigs,void 0,n.materializationConfigs.textMetrics).some(r=>r.kind==="axis"&&["top","bottom"].includes(r.position)))}export{v as allLegendGraphicIds,I as axisGraphicIds,G as effectiveLegendOffsetConfig,j as legendGraphicIds,S as legendResourcePolicies,x as legendResourcePolicy,$ as needsLegendAxisReflow,z as resolveGuideCollisionBlocks,C as resolveLegendAxisOffset};
@@ -1,4 +1,57 @@
1
+ import { canDeferScaleConsumerApplication, getLayerScaleIds, getMarkMaterializationStep } from "./marks/index.js";
2
+ import { buildMaterializationPlan } from "./planner.js";
3
+ import { hasMaterializedLegend } from "./legends.js";
4
+ import { needsCanvasScaleRematerialization, planScaleGuideRematerialization } from "./scaleGuideDependencies.js";
5
+
6
+ export function planCanvasRematerialization(program) {
7
+ const marks = [];
8
+ for (const layer of program.semanticSpec.layers) {
9
+ const step = getMarkMaterializationStep(program, layer);
10
+ if (step !== undefined) marks.push(step);
11
+ }
12
+ const deferredMarkIds = new Set(
13
+ marks
14
+ .map(step => step.args.id)
15
+ );
16
+ const scales = [];
17
+ for (const scale of program.semanticSpec.scales) {
18
+ if (needsCanvasScaleRematerialization(program, scale)) {
19
+ const deferredConsumers = program.semanticSpec.layers.filter(layer =>
20
+ canDeferScaleConsumerApplication(layer) &&
21
+ getLayerScaleIds(layer).includes(scale.id)
22
+ );
23
+ const canDeferMarks = deferredConsumers.length === 0 ||
24
+ deferredConsumers.every(layer => deferredMarkIds.has(layer.id));
25
+ scales.push({
26
+ op: "rematerializeScale",
27
+ args: {
28
+ id: scale.id,
29
+ guides: false,
30
+ ...(canDeferMarks ? { marks: false } : {})
31
+ }
32
+ });
33
+ }
34
+ }
35
+ const guides = program.semanticSpec.scales.flatMap(scale =>
36
+ needsCanvasScaleRematerialization(program, scale)
37
+ ? planScaleGuideRematerialization(program, scale.id)
38
+ : []
39
+ );
40
+ if (hasMaterializedLegend(program)) {
41
+ guides.push({ op: "rematerializeLegend" });
42
+ }
43
+ const layout = planLayoutRematerialization(program);
44
+ return buildMaterializationPlan({ scales, marks, guides, layout });
45
+ }
46
+
47
+
1
48
  const LAYOUT_CONSUMER_POLICIES = Object.freeze([
49
+ Object.freeze({
50
+ applies(program) {
51
+ return program.compositionSpec === undefined && program.materializationConfigs?.canvas?.plotBorder !== undefined;
52
+ },
53
+ step: Object.freeze({ op: "rematerializePlotBorder" })
54
+ }),
2
55
  Object.freeze({
3
56
  applies(program) {
4
57
  return program.semanticSpec.title.text !== undefined &&
@@ -0,0 +1 @@
1
+ import{PLOT_LEGEND_POSITIONS as u}from"../layout/legend.js";function p(r,t){return r.guideConfigs.legendGroups?.[t]}function c(r,t){const n=p(r,t.target);if(n===void 0)return t;const o=n.placement.position,e=u.includes(o),i=e?t.direction==="horizontal"?"top":"right":o??(u.includes(t.position)?t.direction==="horizontal"?"top":"right":t.position??"right");return{...t,position:i,offset:0,align:"left",groupIntrinsic:!0,...e?{groupCorner:!0}:{},...i==="right"?{align:"center"}:{}}}export{c as intrinsicLegendConfig,p as legendGroup};
@@ -0,0 +1 @@
1
+ import{materializeTextLines as o}from"./textLines.js";import{resolveLegendTitleText as l}from"../layout/legendText.js";function m(e,i,t,n){const{lines:r}=l(t,n,e.materializationConfigs.textMetrics);return o(e,i,r)}export{m as materializeLegendTitleLines};
@@ -2,7 +2,7 @@ import { findLayer } from "../selectors/layers.js";
2
2
  import { LEGEND_CONFIG_KINDS } from "../core/vocabulary.js";
3
3
 
4
4
  export function isOpacityLegendLayer(layer) {
5
- return ["point", "line"].includes(layer.mark?.type) &&
5
+ return ["point", "line", "bar"].includes(layer.mark?.type) &&
6
6
  layer.encoding?.opacity?.scale !== undefined;
7
7
  }
8
8
 
@@ -60,10 +60,10 @@ export function canMaterializeLine(program, layer) {
60
60
  layer.mark?.type === "line" &&
61
61
  hasCartesianPositionScales(layer) &&
62
62
  ((isAggregate(layer.encoding.y.aggregate) &&
63
- (layer.encoding.x.fieldType === "temporal" ||
63
+ (["temporal", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) ||
64
64
  (layer.encoding.x.fieldType === "quantitative" &&
65
65
  layer.encoding.x.bin !== undefined))) ||
66
- (layer.encoding.x.fieldType === "quantitative" &&
66
+ (["quantitative", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) &&
67
67
  layer.encoding.y.fieldType === "quantitative") ||
68
68
  ((interval || layer.encoding.y.aggregate === undefined) &&
69
69
  ["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
@@ -114,15 +114,14 @@ export function canMaterializeArc(_program, layer) {
114
114
  }
115
115
  if (layer.encoding.theta.fieldType === "quantitative") {
116
116
  return (
117
- layer.encoding.theta.aggregate === undefined &&
118
- layer.encoding?.radius === undefined
117
+ layer.encoding.theta.aggregate === undefined
119
118
  );
120
119
  }
121
120
  if (!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)) {
122
121
  return false;
123
122
  }
124
123
  if (["count", "sum"].includes(layer.encoding.theta.aggregate)) {
125
- return layer.encoding?.radius === undefined;
124
+ return true;
126
125
  }
127
126
  return (
128
127
  layer.encoding.theta.aggregate === undefined &&
@@ -136,7 +135,7 @@ export function canMaterializeBar(program, layer) {
136
135
  return false;
137
136
  }
138
137
  const grain = resolveBarGrain(layer);
139
- if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain)) {
138
+ if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(grain)) {
140
139
  return false;
141
140
  }
142
141
  if (
@@ -144,7 +144,7 @@ export function resolveRectRows(program, layer, dataset) {
144
144
  }
145
145
 
146
146
  export function resolveRectGraphicItems(program, layer, dataset) {
147
- const radius = program.markConfigs[layer.id]?.cornerRadius ?? 0;
147
+ const radius = program.markConfigs[layer.id] ?? {};
148
148
  return resolveRectRows(program, layer, dataset).map(item =>
149
149
  materializeRectItem(item.properties, radius)
150
150
  );
@@ -0,0 +1,19 @@
1
+ let activeReplacements;
2
+
3
+ export function withLogicalDataReplacements(replacements, operation) {
4
+ const previous = activeReplacements;
5
+ activeReplacements = replacements;
6
+ try {
7
+ return operation();
8
+ } finally {
9
+ activeReplacements = previous;
10
+ }
11
+ }
12
+
13
+ export function comparableDataIdentity(id) {
14
+ if (activeReplacements === undefined) return id;
15
+ for (const [before, after] of activeReplacements) {
16
+ if (after === id) return before;
17
+ }
18
+ return id;
19
+ }
@@ -64,7 +64,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
64
64
  usesRadialScale(program, scale.id) ||
65
65
  semanticGuideUsesScale(program, scale.id)) &&
66
66
  (program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
67
- usesPositionalScale(program, scale.id)
67
+ (usesPositionalScale(program, scale.id) || semanticGuideUsesScale(program, scale.id))
68
68
  );
69
69
  }
70
70
 
@@ -20,6 +20,9 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
20
20
  resolvedScale.domain,
21
21
  resolvedScale.range,
22
22
  {
23
+ type: resolvedScale.type,
24
+ base: resolvedScale.base,
25
+ constant: resolvedScale.constant,
23
26
  interpolation: resolvedScale.interpolate,
24
27
  midpoint: resolvedScale.midpoint,
25
28
  clamp: resolvedScale.clamp ?? false,
@@ -1,3 +1,4 @@
1
+ import { resolveArcInnerRadius } from "../../../grammar/arcs.js";
1
2
  import { validateMeasuredRadiusScale } from "../../../grammar/scales/radial.js";
2
3
  export function resolveArcAutoPositionRange({
3
4
  consumers,
@@ -15,16 +16,16 @@ export function resolveArcAutoPositionRange({
15
16
  return range;
16
17
  }
17
18
  if (channel === "radius") {
18
- const ratios = consumers.map(
19
- consumer => markConfigs[consumer.layer.id]?.innerRadius ?? 0
20
- );
21
- if (new Set(ratios).size !== 1) {
19
+ const outer = Math.max(...range);
20
+ const inner = consumers.map(consumer => resolveArcInnerRadius(
21
+ markConfigs[consumer.layer.id]?.innerRadius ?? 0, outer
22
+ ));
23
+ if (new Set(inner).size !== 1) {
22
24
  throw new Error(
23
25
  `Shared arc radius scale "${scale.id}" requires one innerRadius policy.`
24
26
  );
25
27
  }
26
- const outer = Math.max(...range);
27
- return [outer * ratios[0], outer];
28
+ return [inner[0], outer];
28
29
  }
29
30
  if (
30
31
  channel === "theta" &&
@@ -67,7 +68,7 @@ export function validateMeasuredRadiusConsumers({ scale, domain, range, consumer
67
68
  const config = markConfigs[layer.id] ?? {};
68
69
  if ((config.padAngle ?? 0) !== 0) throw new Error("Measured Arc requires padAngle 0.");
69
70
  if (scale.range !== "auto" && config.innerRadiusExplicit === true &&
70
- Math.abs(config.innerRadius - range[0] / range[1]) > Number.EPSILON * 8) {
71
+ Math.abs(resolveArcInnerRadius(config.innerRadius, range[1]) / range[1] - range[0] / range[1]) > Number.EPSILON * 8) {
71
72
  throw new Error("Measured Arc innerRadius must agree with its explicit radius range.");
72
73
  }
73
74
  }