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,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{resetLegendClipGraphics as u,hasLegendClipGraphics as c}from"../legendClip.js";import{localLegendPosition as g}from"../legendGroups.js";import{needsLegendAxisReflow as m}from"./resources.js";import{getErrorDetails as p}from"../../core/diagnostics.js";import{planCanvasRematerialization as h}from"../layout.js";import{applyMaterializationPlan as C}from"../planner.js";import{assertGuideCollisionBlocks as L}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as v}from"./resources.js";import{resolveGuideCollisionBlocks as M}from"./resources.js";function G(i,d){if(i.context.deferGuideLayoutValidation===!0)return d(i);const a=i.materializationConfigs.canvas?.plot;let n=i.materializationConfigs.canvas?.margin,e;for(let s=0;;s+=1)try{let t=u(i)._withContext({deferGuideLayoutValidation:!0});s>0&&(t=t.editGraphics({target:"canvas",property:"width",value:a.width+n.left+n.right}).editGraphics({target:"canvas",property:"height",value:a.height+n.top+n.bottom})._withCanvasConfig({...t.materializationConfigs.canvas,margin:n}),t=C(t,h(t))),e=d(t),c(i)&&!c(e)&&Object.values(e.guideConfigs.legend??{}).some(o=>o.clipHeight!==void 0)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&Object.values(e.guideConfigs.legend??{}).some(o=>["left","right"].includes(o.position))&&(e=e.rematerializeSideLegendLane()),e.compositionSpec===void 0&&m(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&(Object.keys(e.guideConfigs.legendGroups??{}).length>0||Object.values(e.guideConfigs.legend??{}).some(o=>g(o)!==void 0))&&(e=e.rematerializeLegendGroups()),a!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle());break}catch(t){const o=p(t)?.canvasOverflow;if(a===void 0||o===void 0||s>=16)throw t;n=Object.fromEntries(Object.keys(n).map(l=>[l,o[l]>0?Math.ceil((n[l]+o[l])*4)/4:n[l]]))}const{deferGuideLayoutValidation:y,...f}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(f.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const r=e._clone({context:f});return L(v(r.graphicSpec,r.guideConfigs,r.titleConfig,r.materializationConfigs.textMetrics)),r}function S(i){return function(d){return G(this,a=>i.call(a,d))}}export{M as resolveGuideCollisionBlocks,G as withGuideLayoutTransaction,S 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{axisGraphicIds as h}from"../../selectors/axes.js";import{localLegendPosition as m}from"../legendGroups.js";import{resolveConcreteGraphicBounds as G,unionConcreteGraphicBounds as v}from"../../grammar/schemas/graphicBounds.js";function y(e,t,s,o){const n=[],r=(i,a,l,c)=>{if(l===void 0)return;const p=c.filter(b=>e.objects[b]!==void 0),g=v(e,p,o);g!==void 0&&n.push({id:i,kind:a,position:l,bounds:g})};r("chart title","title",s?.position,["chartTitle","chartSubtitle"]);for(const[i,a]of Object.entries(t.axis??{})){const l=["line","ticks","labels","title"].map(p=>a[p]?.position).find(Boolean),c=["x","y"].includes(i)?i:["top","bottom"].includes(l)?"x":["left","right"].includes(l)?"y":void 0;if(c!==void 0)for(const[p,g]of["line","ticks","labels","title"].entries()){const b=h(c,i)[p],I=n.length;if(r(`${i}-axis ${g}`,"axis",a[g]?.position,[b]),n.length===I)continue;const z=n.at(-1);if(z.axisId=i,z.namedAxis=i!==c,["labels","title"].includes(g)){const x=e.objects[b];z.textBounds=x.items?.map(S=>G(e,S.id,o))??[z.bounds]}}}const f=T(),d=t.legend??{},u=f.find(i=>i.family==="categorical"&&d[i.kind]!==void 0),k=u!==void 0&&d.size!==void 0&&d[u.kind].target===d.size.target&&m(d[u.kind])===void 0&&m(d.size)===void 0&&(d.size.inheritPosition!==!1||d[u.kind].position===d.size.position),L=t.legendGroups??{};for(const[i,a]of Object.entries(L)){const l=f.filter(c=>d[c.kind]?.target===i&&m(d[c.kind])===void 0).flatMap(c=>c.graphicIds);r(`${i} legend group`,"legend",a.placement.position??"free",l)}for(const i of f){const a=d[i.kind];if(L[a?.target]!==void 0&&m(a)===void 0||a===void 0||k&&i.kind==="size")continue;const l=k&&i===u?[...i.graphicIds,...f.find(c=>c.kind==="size").graphicIds]:i.graphicIds;r(`${i.kind}${k&&i===u?"+size":""} legend`,"legend",m(a)??a.position,l)}return n}const O=Object.freeze(["Symbols","SymbolLines","SymbolPoints","SymbolSwatches","Labels","Title","Background"]),j=Object.freeze({colorSize:Object.freeze({semanticKind:"colorSize",family:"sampled",rematerializeOp:void 0,graphicIds:Object.freeze(["colorSizeLegendBackground","colorSizeLegendSymbols","colorSizeLegendLabels","colorSizeLegendTitle"])}),series:Object.freeze({semanticKind:"series",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.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 B(e){return C(e).graphicIds}function C(e){const t=j[e];if(t===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return t}function T(){return Object.entries(j).map(([e,t])=>({kind:e,...t}))}function W(e){return[...new Set(e.flatMap(B))]}function w(e,t,s){if(t.inferredOffset!==!0||!["top","bottom"].includes(t.position)||t.layout==="legacy-bottom")return t.offset;const o=y(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(r=>r.kind==="axis"&&r.position===t.position),n=t.position==="bottom"?Math.max(0,...o.map(r=>r.bounds.bottom-s.y-s.height)):Math.max(0,...o.map(r=>s.y-r.bounds.top));return t.offset+n}function K(e,t,s){if(t!=="size"||s.inheritPosition===!1)return s;const o=["series","color","stroke"].map(n=>e.guideConfigs.legend?.[n]).find(n=>n?.target===s.target);return o===void 0?s:{...s,position:o.position,offset:o.offset,inferredOffset:o.inferredOffset}}function M(e,t){return[e,t].some(o=>Object.entries(o.guideConfigs.legend??{}).some(([n,r])=>{const f=K(o,n,r);return f.inferredOffset===!0&&["top","bottom"].includes(f.position)&&f.layout!=="legacy-bottom"})||Object.values(o.guideConfigs.legendGroups??{}).some(n=>n.inferredOffset===!0&&["top","bottom"].includes(n.placement.position)))&&[e,t].some(o=>y(o.graphicSpec,o.guideConfigs,void 0,o.materializationConfigs.textMetrics).some(n=>n.kind==="axis"&&["top","bottom"].includes(n.position)))}export{W as allLegendGraphicIds,h as axisGraphicIds,K as effectiveLegendOffsetConfig,B as legendGraphicIds,T as legendResourcePolicies,C as legendResourcePolicy,M as needsLegendAxisReflow,y as resolveGuideCollisionBlocks,w 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,28 @@
1
+ import { preserveGraphicPlacement } from './graphicHierarchy.js';
2
+ import { editGraphicProperties } from '../core/graphicProperties.js';
3
+
4
+ function borderCommands({x,y,width,height}) {
5
+ return [{op:'M',x,y},{op:'L',x:x+width,y},{op:'L',x:x+width,y:y+height},
6
+ {op:'L',x,y:y+height},{op:'Z'}];
7
+ }
8
+
9
+ export function materializeLegendBorder(program,id,bounds,border) {
10
+ const dashed=border.strokeDash?.length>0,type=dashed?'path':'rect';
11
+ let next=program;
12
+ if(program.graphicSpec.objects[id]?.type!==type) {
13
+ const placement=preserveGraphicPlacement(program,id);
14
+ next=next.editGraphics({target:id,remove:true}).createGraphics({id,type,...placement});
15
+ }
16
+ return editGraphicProperties(next,id,{
17
+ ...(dashed?{commands:borderCommands(bounds)}:bounds),
18
+ fill:border.background,stroke:border.color,strokeWidth:border.lineWidth,
19
+ ...(dashed?{strokeDash:border.strokeDash}:{})
20
+ });
21
+ }
22
+
23
+ export function editLegendBorderBounds(program,id,bounds) {
24
+ if(program.graphicSpec.objects[id]?.type==='path') {
25
+ return program.editGraphics({target:id,property:'commands',value:borderCommands(bounds)});
26
+ }
27
+ return editGraphicProperties(program,id,{x:bounds.x,y:bounds.y,width:bounds.width,height:bounds.height});
28
+ }
@@ -0,0 +1,76 @@
1
+ import{cloneAndFreeze}from'../core/immutable.js';
2
+ import{namespaceGraphicId}from'../selectors/graphicCapacity.js';
3
+
4
+ const clipPrefix=namespaceGraphicId('legendSampleClip','');
5
+ import{categoricalLegendSymbolId}from'../grammar/legendBlocks.js';
6
+ import{validateLegendClip}from'../grammar/legendClip.js';
7
+ import{hasRectRadius}from'../grammar/roundedRect.js';
8
+ import{resolvePrimitiveGraphicBounds}from'../grammar/schemas/graphicBounds.js';
9
+ import{translatedProperties}from'./graphicTranslation.js';
10
+
11
+ function roles(kind,config){
12
+ if(['series','color','stroke'].includes(kind))return config.symbol.layers.map(layer=>({
13
+ id:categoricalLegendSymbolId(config,layer.type),point:layer.type==='point',
14
+ type:layer.type==='line'?'line':layer.type==='point'?config.channels.includes('shape')?'collection':'circle':hasRectRadius(layer)?'collection':'rect'
15
+ }));
16
+ const prefix=kind==='gradient'?'colorGradient':`${kind}Legend`;
17
+ return [{id:`${prefix}Symbols`,type:'circle',point:true}];
18
+ }
19
+ function wrapped(owner){return owner?.children?.some(id=>id.startsWith(clipPrefix));}
20
+ export function hasLegendClipGraphics(program){
21
+ return Object.values(program.graphicSpec.objects).some(wrapped);
22
+ }
23
+ function copyGraphic(program,objects){return program._clone({graphicSpec:cloneAndFreeze({...program.graphicSpec,objects})});}
24
+ export function resetLegendClipGraphics(program){
25
+ if(!hasLegendClipGraphics(program))return program;
26
+ const objects={...program.graphicSpec.objects};
27
+ function remove(id){const object=objects[id];for(const child of object?.children??[])remove(child);delete objects[id];}
28
+ for(const[kind,config]of Object.entries(program.guideConfigs.legend??{})){
29
+ const descriptors=roles(kind,config);
30
+ if(!descriptors.some(role=>wrapped(objects[role.id])))continue;
31
+ for(const role of descriptors){
32
+ const owner=objects[role.id];if(!owner)continue;
33
+ for(const child of owner.children??[])remove(child);
34
+ objects[role.id]={type:role.type,items:[]};
35
+ }
36
+ }
37
+ return copyGraphic(program,objects);
38
+ }
39
+ function union(bounds){return bounds.reduce((a,b)=>a===undefined?b:{left:Math.min(a.left,b.left),right:Math.max(a.right,b.right),top:Math.min(a.top,b.top),bottom:Math.max(a.bottom,b.bottom)},undefined);}
40
+ function anchorY(type,p){
41
+ if(type==='circle')return p.y;
42
+ if(type==='rect')return p.y+p.height/2;
43
+ throw new Error('Path samples require their authored glyph anchor.');
44
+ }
45
+ export function applyLegendClipGraphics(program,{force=false,anchors={}}={}){
46
+ if(program.context.deferLegendClip===true&&!force)return program;
47
+ let next=program;
48
+ for(const[kind,config]of Object.entries(program.guideConfigs.legend??{})){
49
+ validateLegendClip(kind,config);if(config.clipHeight===undefined)continue;
50
+ if (["series","color","stroke"].includes(kind) && anchors[kind] === undefined && !force) continue;
51
+ const descriptors=roles(kind,config),owners=descriptors.map(role=>next.graphicSpec.objects[role.id]);
52
+ if(owners.some(wrapped)||owners.some(owner=>!owner?.items))continue;
53
+ const count=owners[0].items.length;if(count===0||owners.some(owner=>owner.items.length!==count))continue;
54
+ const objects={...next.graphicSpec.objects},primary=descriptors[0].id,children=[];
55
+ for(let index=0;index<count;index++){
56
+ const samples=owners.map((owner,j)=>({item:owner.items[index],type:owner.items[index].type??owner.type,role:descriptors[j]}));
57
+ const point=samples.find(sample=>sample.role.point),y=anchors[kind]?.[index]??anchorY(point.type,point.item.properties);
58
+ const b=union(samples.map(sample=>resolvePrimitiveGraphicBounds(sample.type,sample.item.properties)));
59
+ const x=b.left,top=y-config.clipHeight/2,id=namespaceGraphicId('legendSampleClip',namespaceGraphicId(`${primary}:${index}`,'window')),glyphs=[];
60
+ for(const sample of samples){
61
+ const glyph=namespaceGraphicId(sample.item.id,'glyph');glyphs.push(glyph);
62
+ objects[glyph]={type:sample.type,properties:translatedProperties(sample.type,sample.item.properties,-x,-top)};
63
+ }
64
+ objects[id]={type:'canvas',properties:{x,y:top,width:b.right-b.left,height:config.clipHeight},children:glyphs};children.push(id);
65
+ }
66
+ for(const descriptor of descriptors)objects[descriptor.id]={type:'collection',items:[],...(descriptor.id===primary?{children}:{})};
67
+ next=copyGraphic(next,objects);
68
+ }
69
+ return next;
70
+ }
71
+
72
+ export function restoreLegendClipContext(program, previous) {
73
+ const {deferLegendClip: _deferred, ...context} = program.context;
74
+ if (previous !== undefined) context.deferLegendClip = previous;
75
+ return program._clone({context});
76
+ }
@@ -0,0 +1 @@
1
+ import{localLegendPosition as c,localLegendPlacement as s}from"../grammar/legendGroups.js";import"../core/vocabulary.js";import{PLOT_LEGEND_POSITIONS as l}from"../layout/legend.js";function u(n,t){return n.guideConfigs.legendGroups?.[t]}function f(n,t){const e=s(t),o=e===void 0?u(n,t.target)??(n.context.facetLegendIntrinsic===!0?{placement:{position:t.position}}:void 0):{placement:e};if(o===void 0)return t;const i=o.placement.position,r=l.includes(i),p=r?t.direction==="horizontal"?"top":"right":i??(l.includes(t.position)?t.direction==="horizontal"?"top":"right":t.position??"right");return{...t,...e===void 0?{}:{blockLocalPosition:e.position},position:p,offset:0,align:"left",groupIntrinsic:!0,...r?{groupCorner:!0}:{},...p==="right"?{align:"center"}:{}}}export{f as intrinsicLegendConfig,u as legendGroup,s as localLegendPlacement,c as localLegendPosition};
@@ -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
 
@@ -24,6 +24,6 @@ export function materializedLegendUsesScale(program, id) {
24
24
  const order = program.semanticSpec.guides.legend?.[kind]?.order;
25
25
  const linkedScale = order?.channel === undefined ? undefined
26
26
  : findLayer(program, config.target)?.encoding?.[order.channel]?.scale;
27
- return config.scale === id || config.scales?.includes(id) || linkedScale === id;
27
+ return config.colorScale === id || config.sizeScale === id || config.scale === id || config.scales?.includes(id) || linkedScale === id;
28
28
  });
29
29
  }
@@ -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
  );
@@ -1,12 +1,10 @@
1
+ import { cartesianAxisOwners, axisTargetArgs } from "../selectors/axes.js";
2
+ import { axisGraphicIds } from "./guides/resources.js";
1
3
  import { isSourceOwnedText } from "../grammar/text.js";
2
4
  import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
3
5
  import { materializedLegendUsesScale } from "./legends.js";
4
6
  import { buildMaterializationPlan } from "./planner.js";
5
7
 
6
- const CARTESIAN_AXES = Object.freeze([
7
- Object.freeze({ channel: "x", prefix: "X", lineObject: "xAxisLine" }),
8
- Object.freeze({ channel: "y", prefix: "Y", lineObject: "yAxisLine" })
9
- ]);
10
8
  const POLAR_AXES = Object.freeze([
11
9
  Object.freeze({ channel: "theta", prefix: "Theta" }),
12
10
  Object.freeze({ channel: "radius", prefix: "Radial" })
@@ -18,8 +16,8 @@ const GRIDS = Object.freeze([
18
16
  Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
19
17
  ]);
20
18
 
21
- export function planAxisTickGridRematerialization(program, channel) {
22
- const ticks = program.guideConfigs.axis?.[channel]?.ticks;
19
+ export function planAxisTickGridRematerialization(program, channel, axisId = channel) {
20
+ const ticks = program.guideConfigs.axis?.[axisId]?.ticks;
23
21
  const role = channel === "x" ? "vertical" : "horizontal";
24
22
  const config = program.guideConfigs.grid?.[role];
25
23
  return buildMaterializationPlan({ guides:
@@ -48,9 +46,7 @@ function usesRadialScale(program, id) {
48
46
 
49
47
  function semanticGuideUsesScale(program, id) {
50
48
  return [
51
- ...CARTESIAN_AXES.map(({ channel }) =>
52
- program.semanticSpec.guides.axis?.[channel]
53
- ),
49
+ ...cartesianAxisOwners(program).map(owner => owner.semantic),
54
50
  ...POLAR_AXES.map(({ channel }) =>
55
51
  program.semanticSpec.guides.axis?.[channel]
56
52
  ),
@@ -64,7 +60,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
64
60
  usesRadialScale(program, scale.id) ||
65
61
  semanticGuideUsesScale(program, scale.id)) &&
66
62
  (program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
67
- usesPositionalScale(program, scale.id)
63
+ (usesPositionalScale(program, scale.id) || semanticGuideUsesScale(program, scale.id))
68
64
  );
69
65
  }
70
66
 
@@ -83,16 +79,19 @@ export function planScaleGuideRematerialization(program, id) {
83
79
  args: { target: guideConfigs.axis.parallel.axes.target }
84
80
  });
85
81
  }
86
- for (const axis of CARTESIAN_AXES) {
82
+ for (const owner of cartesianAxisOwners(program)) {
83
+ const axis = { channel: owner.channel, prefix: owner.channel.toUpperCase(),
84
+ lineObject: axisGraphicIds(owner.channel, owner.id)[0] };
85
+ const target = owner.named ? { args: axisTargetArgs(owner.id, owner.channel) } : {};
87
86
  if (
88
87
  objects[axis.lineObject] &&
89
- program.semanticSpec.guides.axis?.[axis.channel]?.scale === id
88
+ owner.scale === id
90
89
  ) {
91
- guides.push({ op: componentEditOperation(axis.prefix, "line") });
90
+ guides.push({ op: componentEditOperation(axis.prefix, "line"), ...target });
92
91
  }
93
92
  for (const component of ["ticks", "labels", "title"]) {
94
- if (guideConfigs.axis?.[axis.channel]?.[component]?.scale === id) {
95
- guides.push({ op: componentEditOperation(axis.prefix, component) });
93
+ if (guideConfigs.axis?.[owner.id]?.[component]?.scale === id) {
94
+ guides.push({ op: componentEditOperation(axis.prefix, component), ...target });
96
95
  }
97
96
  }
98
97
  }
@@ -20,7 +20,13 @@ 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
+ exponent: resolvedScale.exponent,
26
+ constant: resolvedScale.constant,
23
27
  interpolation: resolvedScale.interpolate,
28
+ whitePoint: resolvedScale.whitePoint,
29
+ gamma: resolvedScale.gamma,
24
30
  midpoint: resolvedScale.midpoint,
25
31
  clamp: resolvedScale.clamp ?? false,
26
32
  ...(Object.hasOwn(resolvedScale, "unknown")
@@ -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
  }
@@ -7,14 +7,19 @@ import {
7
7
  interpolateNumber,
8
8
  inverseLerp
9
9
  } from "../../../grammar/numeric.js";
10
- export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
10
+ export function resolveTemporalBarBand(valuesByConsumer, domain, range, markConfigs = {}, preserveRange = false) {
11
11
  const temporalBars = valuesByConsumer.filter(({ consumer }) => {
12
12
  const channels = resolveBarChannels(consumer.layer);
13
- return resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate &&
13
+ return [BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(resolveBarGrain(consumer.layer)) &&
14
14
  channels?.category === consumer.channel &&
15
15
  consumer.encoding.fieldType === "temporal";
16
16
  });
17
17
  if (temporalBars.length === 0) return undefined;
18
+ const intervalBars = temporalBars.filter(({consumer}) => markConfigs[consumer.layer.id]?.barWidth?.interval !== undefined);
19
+ if (intervalBars.length > 0) {
20
+ if (intervalBars.length !== temporalBars.length) throw new Error("Temporal interval and centered bar widths cannot share a position scale.");
21
+ return undefined;
22
+ }
18
23
  const temporalFields = new Set(
19
24
  temporalBars.map(({ consumer }) => consumer.encoding.field)
20
25
  );
@@ -31,6 +36,11 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
31
36
  "A temporal bar position scale requires compatible bar, line, rule, or text consumers of one field."
32
37
  );
33
38
  }
39
+ // Explicit pixel widths do not need an inferred half-slot inset. Keep one
40
+ // common range when a shared scale also has band-width bar consumers.
41
+ preserveRange ||= temporalBars.every(({ consumer }) =>
42
+ markConfigs[consumer.layer.id]?.barWidth?.pixels !== undefined
43
+ );
34
44
  const values = temporalBars.flatMap(item => item.values);
35
45
  const ordered = [...new Set(values)].sort((left, right) => left - right);
36
46
  if (ordered.length < 2) {
@@ -59,7 +69,7 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
59
69
  interpolateNumber(range[0], range[1], bandwidthFraction) - range[0]
60
70
  )
61
71
  : directBandwidth;
62
- const resolvedRange = stable
72
+ const resolvedRange = preserveRange ? [...range] : stable
63
73
  ? [
64
74
  interpolateNumber(range[0], range[1], bandwidthFraction / 2),
65
75
  interpolateNumber(range[0], range[1], 1 - bandwidthFraction / 2)
@@ -79,10 +89,12 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
79
89
  });
80
90
  let bandwidth = Infinity;
81
91
  for (let index = 1; index < positions.length; index += 1) {
82
- bandwidth = Math.min(
83
- bandwidth,
84
- Math.abs(positions[index] - positions[index - 1])
85
- );
92
+ const gap = Math.abs(positions[index] - positions[index - 1]);
93
+ // Keep a finite conservative slot when an explicitly requested span itself
94
+ // exceeds the representable difference of two finite endpoints.
95
+ bandwidth = Math.min(bandwidth,
96
+ preserveRange && !Number.isFinite(gap) ? estimatedBandwidth : gap);
97
+
86
98
  }
87
99
  if (![estimatedBandwidth, bandwidth, ...resolvedRange].every(Number.isFinite)) {
88
100
  throw new RangeError("Temporal bar position exceeds the finite numeric range.");
@@ -27,11 +27,13 @@ export function resolveSeriesLayoutDomain({
27
27
  seriesLayouts[index] === undefined &&
28
28
  !isSourceOwnedText(consumer.layer)
29
29
  );
30
+ const layout = activeLayouts[0].layout;
30
31
  const compatibleDirect = directConsumers.every(({ consumer }) =>
31
32
  layoutConsumers.every(({ consumer: layoutConsumer }) => {
32
33
  const aggregate = layoutConsumer.encoding.aggregate;
33
34
  const directEncoding = consumer.encoding;
34
35
  return directEncoding.fieldType === "quantitative" && (
36
+ layout === "overlay" ||
35
37
  Object.hasOwn(directEncoding, "datum") ||
36
38
  (
37
39
  aggregate !== "count" &&
@@ -45,7 +47,6 @@ export function resolveSeriesLayoutDomain({
45
47
  `Series layout scale "${id}" cannot be shared with another policy.`
46
48
  );
47
49
  }
48
- const layout = activeLayouts[0].layout;
49
50
  const values = [
50
51
  ...activeLayouts.flatMap(item => item.values),
51
52
  ...directConsumers.flatMap(item => item.values)