ggaction 0.0.24 → 0.0.26

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 (217) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +2 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-resolver.js +1 -1
  7. package/package.json +1 -1
  8. package/src/accessibility.js +13 -0
  9. package/src/actions/canvas/actions.js +2 -1
  10. package/src/actions/canvas/fitting.js +68 -13
  11. package/src/actions/charts/beeswarm.js +2 -1
  12. package/src/actions/charts/ecdf.js +2 -1
  13. package/src/actions/charts/endpoints.js +25 -16
  14. package/src/actions/charts/heatmap.js +5 -3
  15. package/src/actions/charts/missing.js +17 -0
  16. package/src/actions/charts/radar.js +70 -33
  17. package/src/actions/charts/raincloud.js +18 -4
  18. package/src/actions/charts/rug-strip.js +2 -2
  19. package/src/actions/charts/shared.js +17 -0
  20. package/src/actions/composition/actions.js +24 -7
  21. package/src/actions/composition/adjacent.js +90 -0
  22. package/src/actions/composition/index.js +11 -1
  23. package/src/actions/composition/layer.js +42 -0
  24. package/src/actions/composition/layerPlan.js +176 -0
  25. package/src/actions/composition/targets.js +105 -0
  26. package/src/actions/data/derived.js +3 -0
  27. package/src/actions/data/filter.js +1 -1
  28. package/src/actions/data/raincloudFilter.js +3 -1
  29. package/src/actions/encodings/appearance.js +26 -7
  30. package/src/actions/encodings/channels.js +1 -0
  31. package/src/actions/encodings/color/continuous.js +37 -34
  32. package/src/actions/encodings/color/index.js +57 -43
  33. package/src/actions/encodings/color/policy.js +15 -0
  34. package/src/actions/encodings/density/resolve.js +3 -2
  35. package/src/actions/encodings/density.js +2 -2
  36. package/src/actions/encodings/offset.js +2 -0
  37. package/src/actions/encodings/pathOrder.js +2 -0
  38. package/src/actions/encodings/position/areaAlignment.js +42 -0
  39. package/src/actions/encodings/position/index.js +37 -3
  40. package/src/actions/encodings/position/policies/line.js +1 -1
  41. package/src/actions/encodings/ranged.js +2 -0
  42. package/src/actions/encodings/remove.js +13 -10
  43. package/src/actions/encodings/ruleAppearance.js +14 -2
  44. package/src/actions/facets/actions.js +1 -1
  45. package/src/actions/guides/axes/axes.js +2 -1
  46. package/src/actions/guides/axes/edit.js +1 -1
  47. package/src/actions/guides/axes/labels.js +2 -1
  48. package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
  49. package/src/actions/guides/axes/parallel/policy.js +5 -2
  50. package/src/actions/guides/axes/parallel/resolve.js +3 -1
  51. package/src/actions/guides/axes/parallel.js +9 -4
  52. package/src/actions/guides/axes/titles.js +1 -1
  53. package/src/actions/guides/facadeLegend.js +3 -2
  54. package/src/actions/guides/grids/grid.js +3 -2
  55. package/src/actions/guides/grids/resolve.js +2 -1
  56. package/src/actions/guides/guides.js +3 -2
  57. package/src/actions/guides/legends/automatic.js +28 -0
  58. package/src/actions/guides/legends/blocks.js +15 -21
  59. package/src/actions/guides/legends/categorical/actions.js +1 -1
  60. package/src/actions/guides/legends/categorical/components.js +4 -3
  61. package/src/actions/guides/legends/categorical/layout.js +5 -2
  62. package/src/actions/guides/legends/categorical/options.js +9 -14
  63. package/src/actions/guides/legends/categorical/recipes.js +3 -0
  64. package/src/actions/guides/legends/categorical/resolve.js +7 -5
  65. package/src/actions/guides/legends/colorSize.js +51 -14
  66. package/src/actions/guides/legends/continuous/common.js +1 -1
  67. package/src/actions/guides/legends/continuous/gradient.js +1 -1
  68. package/src/actions/guides/legends/continuous/opacity.js +2 -0
  69. package/src/actions/guides/legends/edit.js +1 -1
  70. package/src/actions/guides/legends/focused.js +2 -2
  71. package/src/actions/guides/legends/group.js +1 -1
  72. package/src/actions/guides/legends/lane.js +1 -1
  73. package/src/actions/guides/legends/lifecycle.js +2 -2
  74. package/src/actions/guides/legends/remove.js +18 -4
  75. package/src/actions/guides/legends/size.js +1 -1
  76. package/src/actions/guides/legends/transition.js +15 -6
  77. package/src/actions/guides/polar/axes/facade.js +4 -2
  78. package/src/actions/guides/polar/axes/labels.js +6 -1
  79. package/src/actions/guides/polar/axes/shared.js +4 -4
  80. package/src/actions/guides/polar/axes/titles.js +13 -2
  81. package/src/actions/guides/polar/grids.js +6 -3
  82. package/src/actions/guides/tickValues.js +7 -1
  83. package/src/actions/index.js +2 -0
  84. package/src/actions/marks/area/actions.js +9 -2
  85. package/src/actions/marks/area/materialize.js +1 -1
  86. package/src/actions/marks/bar/materialize.js +4 -4
  87. package/src/actions/marks/lifecycle.js +14 -0
  88. package/src/actions/marks/line/actions.js +3 -0
  89. package/src/actions/marks/line/materialize.js +4 -2
  90. package/src/actions/marks/point/edit.js +2 -0
  91. package/src/actions/marks/point/jitter.js +2 -0
  92. package/src/actions/marks/point/materialize.js +5 -4
  93. package/src/actions/marks/point/packing.js +2 -0
  94. package/src/actions/marks/references.js +5 -3
  95. package/src/actions/marks/remove.js +16 -0
  96. package/src/actions/marks/shared.js +2 -1
  97. package/src/actions/marks/text/actions.js +5 -2
  98. package/src/actions/overlays/index.js +168 -0
  99. package/src/actions/overlays/snapshot.js +45 -0
  100. package/src/actions/primitives/editGraphics.js +21 -27
  101. package/src/actions/primitives/semanticAction.js +4 -5
  102. package/src/actions/primitives/semanticValidation/guide.js +1 -0
  103. package/src/actions/primitives/semanticValidation/layer.js +7 -0
  104. package/src/actions/scales/consumers/common.js +10 -0
  105. package/src/actions/scales/consumers/families.js +1 -1
  106. package/src/actions/scales/preview.js +1 -1
  107. package/src/actions/selection/actions.js +63 -11
  108. package/src/actions/titles/actions.js +3 -14
  109. package/src/actions/titles/resolve.js +12 -9
  110. package/src/core/action.js +41 -25
  111. package/src/core/compositionScales.js +12 -0
  112. package/src/core/compositionState.js +16 -2
  113. package/src/core/immutable.js +11 -6
  114. package/src/core/programState.js +16 -0
  115. package/src/core/resourceReferences.js +19 -3
  116. package/src/core/validation.js +7 -0
  117. package/src/core/vocabulary.js +8 -2
  118. package/src/grammar/actionTargets.js +24 -0
  119. package/src/grammar/bin2d.js +7 -0
  120. package/src/grammar/colorSizeLegend.js +56 -5
  121. package/src/grammar/displayLabels.js +10 -0
  122. package/src/grammar/facetHeaders.js +1 -1
  123. package/src/grammar/facets/dependencies.js +10 -3
  124. package/src/grammar/facets/guides.js +2 -1
  125. package/src/grammar/facets/legendIntent.js +7 -5
  126. package/src/grammar/legendBlocks.js +1 -1
  127. package/src/grammar/legendCombinations.js +1 -1
  128. package/src/grammar/legendGroups.js +1 -1
  129. package/src/grammar/legendItemsOffset.js +12 -0
  130. package/src/grammar/legendOverflow.js +25 -0
  131. package/src/grammar/lineSeries.js +1 -1
  132. package/src/grammar/markSelection.js +23 -0
  133. package/src/grammar/overlay.js +51 -0
  134. package/src/grammar/parallelCoordinates.js +1 -208
  135. package/src/grammar/pathCommands.js +8 -2
  136. package/src/grammar/pathSeries.js +1 -1
  137. package/src/grammar/plotComposition.js +152 -0
  138. package/src/grammar/pointShapes.js +18 -0
  139. package/src/grammar/scales/transformed.js +1 -1
  140. package/src/grammar/schemas/concreteGraphic.js +8 -1
  141. package/src/grammar/schemas/graphicBounds.js +8 -1
  142. package/src/grammar/schemas/graphicTree.js +18 -2
  143. package/src/grammar/schemas/semanticPath.js +3 -0
  144. package/src/grammar/strokeStyle.js +14 -8
  145. package/src/grammar/text.js +72 -0
  146. package/src/inspection.js +1 -1
  147. package/src/inspectionDescriptors.js +1 -1
  148. package/src/layout/composition.js +30 -4
  149. package/src/layout/compositionLegend.js +91 -0
  150. package/src/layout/facetHeaderTitles.js +1 -1
  151. package/src/layout/labels.js +55 -27
  152. package/src/layout/legend.js +3 -2
  153. package/src/layout/legendGroup.js +1 -1
  154. package/src/layout/legendItems.js +7 -6
  155. package/src/layout/legendLane.js +1 -1
  156. package/src/layout/legendText.js +1 -1
  157. package/src/materialization/adjacentSource.js +27 -0
  158. package/src/materialization/composition.js +165 -52
  159. package/src/materialization/compositionLegends.js +120 -0
  160. package/src/materialization/compositionScales.js +125 -0
  161. package/src/materialization/compositionTitle.js +96 -0
  162. package/src/materialization/facetGuides/edit.js +22 -2
  163. package/src/materialization/facetGuides/layout.js +1 -1
  164. package/src/materialization/facetGuides/owner.js +4 -3
  165. package/src/materialization/facetGuides/ownerLayout.js +12 -6
  166. package/src/materialization/facetGuides/placement.js +19 -22
  167. package/src/materialization/facetGuides/preparation.js +17 -5
  168. package/src/materialization/facetHeaders.js +5 -1
  169. package/src/materialization/facets.js +1 -1
  170. package/src/materialization/guides/layout.js +1 -1
  171. package/src/materialization/guides/resources.js +1 -1
  172. package/src/materialization/guides/visibility.js +105 -0
  173. package/src/materialization/legendClip.js +1 -1
  174. package/src/materialization/legendGroups.js +1 -1
  175. package/src/materialization/legendItemsOffset.js +68 -0
  176. package/src/materialization/legendSuppression.js +22 -0
  177. package/src/materialization/legends.js +3 -2
  178. package/src/materialization/marks/capabilities.js +4 -0
  179. package/src/materialization/marks/index.js +5 -1
  180. package/src/materialization/marks/pathOrder.js +1 -1
  181. package/src/materialization/marks/policies.js +2 -1
  182. package/src/materialization/overlayItems.js +154 -0
  183. package/src/materialization/scales/policies/binnedPosition.js +43 -5
  184. package/src/materialization/scales/policies/series.js +12 -4
  185. package/src/materialization/scales/resolve.js +2 -0
  186. package/src/materialization/selection/items/bar.js +21 -3
  187. package/src/materialization/selection/items/common.js +18 -5
  188. package/src/materialization/selection/items/overlay.js +15 -0
  189. package/src/materialization/selection/items/path.js +1 -0
  190. package/src/materialization/selection/items/point.js +2 -3
  191. package/src/materialization/selection/items/tick.js +3 -12
  192. package/src/materialization/selection/path.js +2 -2
  193. package/src/materialization/selection/policies/bar.js +2 -0
  194. package/src/materialization/selection/policies/index.js +35 -1
  195. package/src/materialization/selection/policies/point.js +2 -0
  196. package/src/materialization/selection/policies/text.js +17 -0
  197. package/src/materialization/selection/rows.js +103 -0
  198. package/src/materialization/selection/state.js +19 -3
  199. package/src/materialization/selection/styles.js +8 -0
  200. package/src/materialization/text.js +6 -489
  201. package/src/materialization/textGeometry.js +503 -0
  202. package/src/materialization/textLines.js +20 -0
  203. package/src/persistence/validation.js +97 -24
  204. package/src/renderers/canvas/index.js +12 -7
  205. package/src/renderers/canvas/text.js +5 -5
  206. package/src/renderers/nativeBackend.js +25 -0
  207. package/src/renderers/pdf.js +3 -3
  208. package/src/renderers/png.js +4 -3
  209. package/src/renderers/svgSerialization.js +1 -643
  210. package/src/scene.js +5 -3
  211. package/src/selectors/plotComposition.js +115 -0
  212. package/src/selectors/selectionTargets.js +19 -0
  213. package/src/version.js +1 -1
  214. package/types/basic.d.ts +2 -2
  215. package/types/index.d.ts +11 -0
  216. package/types/inspection.d.ts +34 -0
  217. package/types/program.d.ts +553 -365
@@ -1,6 +1,8 @@
1
1
  import { prepareSharedFacetLegend } from "./preparation.js";
2
2
  import { facetLegendIntent, intrinsicFacetLegendSource, restoreFacetLegendIntent,
3
3
  prepareAuthoredFacetLegend } from "./owner.js";
4
+ import { legendBlockChannels } from "../../grammar/legendBlocks.js";
5
+ import { suppressLegendChannels } from "../legendSuppression.js";
4
6
 
5
7
  export function editFacetParentLegend(program, operation, args) {
6
8
  if (program.compositionSpec?.type !== "facet") {
@@ -9,6 +11,7 @@ export function editFacetParentLegend(program, operation, args) {
9
11
  const current = program.compositionSpec.facet.guides.legend;
10
12
  if (current === false) throw new Error(`${operation} requires an existing facet parent legend.`);
11
13
  const prepared = current === "shared" ? prepareSharedFacetLegend(program) : prepareAuthoredFacetLegend(program);
14
+ if (prepared === undefined) throw new Error(`${operation} requires an existing facet parent legend.`);
12
15
  let source;
13
16
  if (prepared.source !== undefined) {
14
17
  source = restoreFacetLegendIntent(intrinsicFacetLegendSource(program), facetLegendIntent(prepared.source));
@@ -21,10 +24,27 @@ export function editFacetParentLegend(program, operation, args) {
21
24
  ...(prepared.legend.mark === "point" ? { symbol: { layers: [{ type: "point", size: 5.5, stroke: "#ffffff", strokeWidth: 0.35 }] } } : {}) });
22
25
  }
23
26
  source = source[operation](args);
24
- const intent = Object.keys(source.guideConfigs.legend ?? {}).length === 0 ? false : facetLegendIntent(source);
27
+ const suppress = operation === "removeLegend" && args.suppress === true;
28
+ const intent = suppress && (current === "shared" || Object.keys(source.guideConfigs.legend ?? {}).length === 0) ? "shared"
29
+ : Object.keys(source.guideConfigs.legend ?? {}).length === 0 ? false : facetLegendIntent(source);
30
+ let children = program.children;
31
+ if (suppress) {
32
+ children = Object.fromEntries(Object.entries(children).map(([id, child]) => {
33
+ let next = child;
34
+ for (const [target, { channels }] of Object.entries(source.guideConfigs.suppressedLegends ?? {})) {
35
+ const existing = new Set(Object.entries(next.guideConfigs.legend ?? {})
36
+ .filter(([, config]) => config.target === target)
37
+ .flatMap(([kind, config]) => legendBlockChannels(kind, config)));
38
+ const removed = channels.filter(channel => existing.has(channel));
39
+ if (removed.length) next = next.removeLegend({ target, channels: removed, suppress: true });
40
+ next = suppressLegendChannels(next, target, channels);
41
+ }
42
+ return [id, next];
43
+ }));
44
+ }
25
45
  const spec = program.compositionSpec;
26
46
  return program._clone({ trace: source.trace, actionStack: source.actionStack, actionSequence: source._actionSequence })
27
- ._withCompositionState({ children: program.children,
47
+ ._withCompositionState({ children,
28
48
  compositionSpec: { ...spec, facet: { ...spec.facet, guides: { ...spec.facet.guides, legend: intent } } } })
29
49
  .materializeComposition();
30
50
  }
@@ -1 +1 @@
1
- import{cartesianAxisOwners as O,axisGraphicIds as S}from"../../selectors/axes.js";import{resolveAuthoredFacetLegendLayout as B}from"./ownerLayout.js";import{PLOT_LEGEND_POSITIONS as F}from"../../layout/legend.js";import{resolveConcreteGraphicBounds as P}from"../../grammar/schemas/graphicBounds.js";import{resolveTextBounds as y,textBoundsIntersect as C}from"../../core/textMetrics.js";import{resolveFacetHeaderItems as E,retainedFacetGuideIds as T}from"../facetHeaders.js";function w(n,t,e){return{left:n.left+t,right:n.right+t,top:n.top+e,bottom:n.bottom+e}}function M(n,t,e,m){const f=n.materializationConfigs.textMetrics,h=[];for(const o of t.children){const c=n.children[o.id],g=new Set(o.footerShift===void 0?[]:O(c).filter(d=>d.channel==="x").flatMap(d=>["line","ticks","labels","title"].flatMap((a,l)=>d.config?.[a]?.position==="bottom"?[S(d.channel,d.id)[l]]:[])));for(const d of T(n,o.id)){const a=P(c.graphicSpec,d,f);a!==void 0&&h.push(w(a,o.x,o.y+(g.has(d)?o.footerShift:0)))}}const{items:i}=E(t,e,m);for(const o of i)o.properties.text!==""&&h.push(y(o.properties,f));return h}function b(n,t,e,m,f){const h=n.right-n.left,i=n.bottom-n.top,o=e==="right"?t.x+t.width+f:e==="left"?t.x-f-h:m==="left"?t.x:m==="right"?t.x+t.width-h:t.x+(t.width-h)/2,c=["left","right"].includes(e)?t.y:e==="top"?t.y-f-i:t.y+t.height+f;return{left:o,right:o+h,top:c,bottom:c+i}}function j(n,{layout:t,plot:e,plots:m,title:f,preparedHeaders:h,prepared:i}){if(i?.mode==="authored")return B(n,{layout:t,plot:e,title:f,prepared:i,guides:M(n,t,m,h)});if(i?.mode!=="promoted")return{layout:t,plot:e};const o=i.occupiedGroup;if(o?.placement.at!==void 0||F.includes(o?.placement.position))return{layout:t,plot:e};const c=i.reservation.position,g=i.reservation.align,d=i.kinds.map(r=>i.source.guideConfigs.legend[r]),a=i.reservation.gap,l=o===void 0?d.every(r=>r.inferredOffset===!0):o.inferredOffset===!0;let s=b(i.bounds,e,c,g,a);const v=M(n,t,m,h),I=v.map(r=>c==="right"?r.right-(e.x+e.width):c==="left"?e.x-r.left:c==="top"?e.y-r.top:r.bottom-(e.y+e.height));if(l)s=b(i.bounds,e,c,g,Math.max(a,...I.map(r=>r+a)));else if(v.some(r=>C(s,r)))throw new Error("Facet shared legend overlaps retained axes or headers.");const x=Math.max(0,t.padding.left-s.left),u=Math.max(0,f.height+t.padding.top-s.top),G=t.children.map(r=>({...r,x:r.x+x,y:r.y+u})),L={...e,x:e.x+x,y:e.y+u};return s=w(s,x,u),{plot:L,layout:{...t,children:G,width:Math.ceil(Math.max(t.width+x,s.right+t.padding.right)),height:Math.ceil(Math.max(t.height+u,s.bottom+t.padding.bottom)),legend:{position:c,x:s.left,y:s.top,width:s.right-s.left,height:s.bottom-s.top,translation:{x:s.left-i.bounds.left,y:s.top-i.bounds.top}}}}}export{j as resolvePromotedFacetLegendLayout};
1
+ import{cartesianAxisOwners as S,axisGraphicIds as B}from"../../selectors/axes.js";import{resolveAuthoredFacetLegendLayout as F}from"./ownerLayout.js";import{PLOT_LEGEND_POSITIONS as y}from"../../layout/legend.js";import{resolveConcreteGraphicBounds as C}from"../../grammar/schemas/graphicBounds.js";import{resolveTextBounds as P,textBoundsIntersect as k}from"../../core/textMetrics.js";import{resolveFacetHeaderItems as E,retainedFacetGuideIds as T}from"../facetHeaders.js";function M(n,t,e){return{left:n.left+t,right:n.right+t,top:n.top+e,bottom:n.bottom+e}}function b(n,t,e,g){const f=n.materializationConfigs.textMetrics,d=[];for(const o of t.children){const r=n.children[o.id],a=new Set(o.footerShift===void 0?[]:S(r).filter(h=>h.channel==="x").flatMap(h=>["line","ticks","labels","title"].flatMap((m,l)=>h.config?.[m]?.position==="bottom"?[B(h.channel,h.id)[l]]:[])));for(const h of T(n,o.id)){const m=C(r.graphicSpec,h,f);m!==void 0&&d.push(M(m,o.x,o.y+(a.has(h)?o.footerShift:0)))}}const{items:i}=E(t,e,g);for(const o of i)o.properties.text!==""&&d.push(P(o.properties,f));return d}function I(n,t,e,g,f,d=0){const i=n.right-n.left,o=n.bottom-n.top;let r=e==="right"?t.x+t.width+f:e==="left"?t.x-f-i:g==="left"?t.x:g==="right"?t.x+t.width-i:t.x+(t.width-i)/2,a=["left","right"].includes(e)?t.y:e==="top"?t.y-f-o:t.y+t.height+f;return["left","right"].includes(e)?a+=d:r+=d,{left:r,right:r+i,top:a,bottom:a+o}}function q(n,{layout:t,plot:e,plots:g,title:f,preparedHeaders:d,prepared:i}){if(i?.mode==="authored")return F(n,{layout:t,plot:e,title:f,prepared:i,guides:b(n,t,g,d)});if(i?.mode!=="promoted")return{layout:t,plot:e};const o=i.occupiedGroup;if(o?.placement.at!==void 0||y.includes(o?.placement.position))return{layout:t,plot:e};const r=i.reservation.position,a=i.reservation.align,h=i.kinds.map(s=>i.source.guideConfigs.legend[s]),m=i.reservation.gap,l=o===void 0?h.every(s=>s.inferredOffset===!0):o.inferredOffset===!0,v=o?.placement.alongOffset??0;let c=I(i.bounds,e,r,a,m,v);const w=b(n,t,g,d),L=w.map(s=>r==="right"?s.right-(e.x+e.width):r==="left"?e.x-s.left:r==="top"?e.y-s.top:s.bottom-(e.y+e.height));if(l)c=I(i.bounds,e,r,a,Math.max(m,...L.map(s=>s+m)),v);else if(!i.kinds.every(s=>n.context.compositionCollectedLegends?.includes(s))&&w.some(s=>k(c,s)))throw new Error("Facet shared legend overlaps retained axes or headers.");const x=Math.max(0,t.padding.left-c.left),u=Math.max(0,f.height+t.padding.top-c.top),O=t.children.map(s=>({...s,x:s.x+x,y:s.y+u})),G={...e,x:e.x+x,y:e.y+u};return c=M(c,x,u),{plot:G,layout:{...t,children:O,width:Math.ceil(Math.max(t.width+x,c.right+t.padding.right)),height:Math.ceil(Math.max(t.height+u,c.bottom+t.padding.bottom)),legend:{position:r,x:c.left,y:c.top,width:c.right-c.left,height:c.bottom-c.top,translation:{x:c.left-i.bounds.left,y:c.top-i.bounds.top}}}}}export{q as resolvePromotedFacetLegendLayout};
@@ -42,7 +42,7 @@ export function restoreFacetLegendIntent(source, intent) {
42
42
  const descriptor = describeLegendBlock(source, kind, config);
43
43
  const entry = { target: config.target, channels: [...descriptor.channels] };
44
44
  if (descriptor.channels.includes("shape") && descriptor.channels.includes("size")) entry.combine = "size-shape";
45
- if (kind === "colorSize") entry.combine = "color-size";
45
+ if (kind === "colorSize") { entry.combine = config.combine; if(config.shapeScale!==undefined)entry.values=config.sampling.values; }
46
46
  if (descriptor.family === "categorical" && config.inferredSymbol === false && entry.combine === undefined) entry.symbol = config.symbol;
47
47
  if (kind === "gradient" && config.representation === "symbols") entry.representation = "symbols";
48
48
  scaffolds.push(entry);
@@ -67,12 +67,13 @@ export function restoreFacetLegendIntent(source, intent) {
67
67
  }
68
68
  }
69
69
  source = source._withMaterializationConfig(["guides", "legendGroups"], intent.groups);
70
- return source.rematerializeLegend();
70
+ return source.rematerializeLegend().rematerializeSideLegendLane();
71
71
  }
72
72
 
73
73
  export function prepareAuthoredFacetLegend(program, intent = program.compositionSpec.facet.guides.legend) {
74
74
  let source = intrinsicFacetLegendSource(program);
75
- source = isAuthoredFacetLegend(intent) ? restoreFacetLegendIntent(source, intent) : source.createLegend(intent);
75
+ source = isAuthoredFacetLegend(intent) ? restoreFacetLegendIntent(source, intent)
76
+ : source.createLegend(intent).rematerializeSideLegendLane();
76
77
  const kinds = Object.keys(source.guideConfigs.legend ?? {});
77
78
  for (const [kind, config] of Object.entries(source.guideConfigs.legend)) {
78
79
  const descriptor = describeLegendBlock(source, kind, config);
@@ -1,4 +1,4 @@
1
- import { localLegendPlacement } from "../../grammar/legendGroups.js";
1
+ import { localLegendPlacement, retainsLegendSlot } from "../../grammar/legendGroups.js";
2
2
  import { PLOT_LEGEND_POSITIONS } from "../../core/vocabulary.js";
3
3
  import { resolveLegendGroupPlacement } from "../../layout/legendGroup.js";
4
4
  import { textBoundsIntersect } from "../../core/textMetrics.js";
@@ -20,8 +20,10 @@ export function resolveAuthoredFacetLegendLayout(program, { layout, plot, title,
20
20
  const targets = [...new Set(prepared.blocks.map(block => block.config.target))];
21
21
  for (const target of targets) {
22
22
  const owned = prepared.blocks.filter(block => block.config.target === target);
23
- const inherited = owned.filter(block => localLegendPlacement(block.config) === undefined);
24
- if (inherited.length) partitions.push({ blocks: inherited,
23
+ const retainedGroup = prepared.source.guideConfigs.legendGroups?.[target];
24
+ const anchor = retainedGroup ? retainedGroup.placement.position ?? null : undefined;
25
+ const inherited = owned.filter(block => localLegendPlacement(block.config) === undefined || retainsLegendSlot(block.config, anchor));
26
+ if (inherited.length) partitions.push({ blocks: inherited, inherited: true,
25
27
  group: prepared.source.guideConfigs.legendGroups?.[target] ?? nativeGroup(inherited) });
26
28
  for (const block of owned) {
27
29
  const placement = localLegendPlacement(block.config);
@@ -30,7 +32,7 @@ export function resolveAuthoredFacetLegendLayout(program, { layout, plot, title,
30
32
  }
31
33
  }
32
34
  const placements = [];
33
- for (const { blocks, group } of partitions) {
35
+ for (const { blocks, group, inherited } of partitions) {
34
36
  const position = group.placement.position;
35
37
  const inside = group.placement.at !== undefined || PLOT_LEGEND_POSITIONS.includes(position);
36
38
  let effective = group;
@@ -45,15 +47,19 @@ export function resolveAuthoredFacetLegendLayout(program, { layout, plot, title,
45
47
  group: effective, plot, canvas: layout, deferCanvasFit: true });
46
48
  for (const plan of plans) {
47
49
  const block = blocks.find(block => block.kind === plan.id);
50
+ // Keep a fine-positioned block in packing only; its local partition below
51
+ // paints it once at its explicit anchor.
52
+ if (localLegendPlacement(block.config) !== undefined && inherited && retainsLegendSlot(block.config, group.placement.position)) continue;
48
53
  const bounds = shift(block.bounds, plan.dx, plan.dy);
49
54
  if (inside && (bounds.left < plot.x || bounds.right > plot.x + plot.width ||
50
55
  bounds.top < plot.y || bounds.bottom > plot.y + plot.height)) {
51
56
  throw new Error("Facet parent legend does not fit the final plot union.");
52
57
  }
53
- if (guides.some(bound => textBoundsIntersect(bounds, bound))) {
58
+ const hidden = program.context.compositionCollectedLegends ?? [];
59
+ if (!hidden.includes(block.kind) && guides.some(bound => textBoundsIntersect(bounds, bound))) {
54
60
  throw new Error("Facet parent legend overlaps retained axes or headers.");
55
61
  }
56
- if (placements.some(other => textBoundsIntersect(bounds, other.bounds))) {
62
+ if (!hidden.includes(block.kind) && placements.some(other => !hidden.includes(other.kind) && textBoundsIntersect(bounds, other.bounds))) {
57
63
  throw new Error("Facet parent legend blocks overlap.");
58
64
  }
59
65
  placements.push({ kind: block.kind, dx: plan.dx, dy: plan.dy, bounds, inside });
@@ -27,16 +27,6 @@ function copyLegendConfig(program, source) {
27
27
  return next;
28
28
  }
29
29
 
30
- function removeNamespacedGraphics(program, namespace, ids) {
31
- let next = program;
32
- for (const id of ids) {
33
- const target = namespaceGraphicId(namespace, id);
34
- if (next.graphicSpec.objects[target] !== undefined) {
35
- next = next.editGraphics({ target, remove: true });
36
- }
37
- }
38
- return next;
39
- }
40
30
 
41
31
  function legendTranslation(prepared, plot, layout) {
42
32
  if (layout.legend?.translation !== undefined) return layout.legend.translation;
@@ -161,7 +151,7 @@ export function prepareFacetGuideOwnership(program, placements, prepared) {
161
151
  placements,
162
152
  children,
163
153
  axes: program.compositionSpec.facet.guides.axes,
164
- legend: program.compositionSpec.facet.guides.legend,
154
+ legend: prepared === undefined ? false : program.compositionSpec.facet.guides.legend,
165
155
  ...(prepared === undefined ? {} : { sharedLegends: prepared.compatibility })
166
156
  });
167
157
  }
@@ -175,7 +165,13 @@ export function applyFacetGuideComposition(program, { layout, plot, synchronizeC
175
165
  }
176
166
  const prepared = prepareSharedFacetLegend(program);
177
167
  const ownership = prepareFacetGuideOwnership(program, layout.children, prepared);
178
- let next = program;
168
+ const removals = new Set();
169
+ const remove = (namespace, ids) => {
170
+ for (const id of ids) {
171
+ const qualified = namespaceGraphicId(namespace, id);
172
+ if (program.graphicSpec.objects[qualified]) removals.add(qualified);
173
+ }
174
+ };
179
175
  for (const cell of layout.children) {
180
176
  const namespace = `${program.compositionSpec.id}-${cell.id}`;
181
177
  const childPlan = ownership.children[cell.id];
@@ -184,19 +180,27 @@ export function applyFacetGuideComposition(program, { layout, plot, synchronizeC
184
180
  // policy still removes each local copy while retaining hue legends.
185
181
  for (const [owner, config] of Object.entries(program.children[cell.id].markConfigs)) {
186
182
  if (!config.gradientPlot) continue;
187
- next = removeNamespacedGraphics(next, namespace,
183
+ remove(namespace,
188
184
  ["DensityLegend", "DensityLegendLabels", "DensityLegendTitle"].map(suffix => `${owner}${suffix}`));
189
185
  }
190
186
  const axisOwners = cartesianAxisOwners(program.children[cell.id]);
191
187
  for (const axisId of childPlan.removeAxes) {
192
188
  const owner = axisOwners.find(owner => owner.id === axisId);
193
- next = removeNamespacedGraphics(next, namespace, axisGraphicIds(owner.channel, axisId));
189
+ remove(namespace, axisGraphicIds(owner.channel, axisId));
194
190
  }
195
191
  for (const [axisId, components] of Object.entries(childPlan.removeAxisComponents ?? {})) {
196
192
  const owner = axisOwners.find(owner => owner.id === axisId);
197
193
  const ids = axisGraphicIds(owner.channel, axisId);
198
- next = removeNamespacedGraphics(next, namespace, components.map(component => ids[["line", "ticks", "labels", "title"].indexOf(component)]));
194
+ remove(namespace, components.map(component => ids[["line", "ticks", "labels", "title"].indexOf(component)]));
199
195
  }
196
+ if (childPlan.removeLegends.length > 0) remove(namespace, allLegendGraphicIds(childPlan.removeLegends));
197
+ }
198
+ // All ownership removals are independent. Rebuild the immutable graphic map
199
+ // once, rather than copying every panel for each hidden guide component.
200
+ let next = removals.size ? program.editGraphics({ target: [...removals], remove: true }) : program;
201
+ for (const cell of layout.children) {
202
+ const namespace = `${program.compositionSpec.id}-${cell.id}`;
203
+ const axisOwners = cartesianAxisOwners(program.children[cell.id]);
200
204
  if (cell.footerShift !== undefined) {
201
205
  for (const owner of axisOwners.filter(owner => owner.channel === "x")) {
202
206
  for (const [index, component] of ["line", "ticks", "labels", "title"].entries()) {
@@ -213,13 +217,6 @@ export function applyFacetGuideComposition(program, { layout, plot, synchronizeC
213
217
  }
214
218
  }
215
219
  }
216
- if (childPlan.removeLegends.length > 0) {
217
- next = removeNamespacedGraphics(
218
- next,
219
- namespace,
220
- allLegendGraphicIds(childPlan.removeLegends)
221
- );
222
- }
223
220
  }
224
221
  if (prepared === undefined) {
225
222
  if (next.semanticSpec.guides.legend?.colorSize !== undefined) {
@@ -1,4 +1,5 @@
1
1
  import { localLegendPosition } from "../../grammar/legendGroups.js";
2
+ import { restoreLegendItemsOffset } from "../legendItemsOffset.js";
2
3
  import { prepareAuthoredFacetLegend, facetLegendIntent } from "./owner.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
4
5
  import { PLOT_LEGEND_POSITIONS } from "../../layout/legend.js";
@@ -31,9 +32,11 @@ export function legendKinds(program) {
31
32
 
32
33
  function legendCandidates(program) {
33
34
  const layers = program.semanticSpec.layers;
35
+ const eligible = (layer, channel) => layer.encoding?.[channel]?.scale !== undefined &&
36
+ !program.guideConfigs.suppressedLegends?.[layer.id]?.channels.includes(channel);
34
37
  const color = layers.filter(layer => {
35
38
  const encoding = layer.encoding?.color;
36
- return encoding?.scale !== undefined &&
39
+ return eligible(layer, "color") &&
37
40
  findSemanticScale(program, encoding.scale) !== undefined;
38
41
  });
39
42
  if (color.length === 1) {
@@ -41,7 +44,7 @@ function legendCandidates(program) {
41
44
  return {
42
45
  target: layer.id,
43
46
  channels: ["color", "stroke", "shape", "strokeDash"].filter(
44
- channel => layer.encoding?.[channel]?.scale !== undefined
47
+ channel => eligible(layer, channel)
45
48
  )
46
49
  };
47
50
  }
@@ -52,7 +55,7 @@ function legendCandidates(program) {
52
55
  }
53
56
  for (const channel of ["stroke", "shape", "strokeDash", "size", "opacity"]) {
54
57
  const candidates = layers.filter(
55
- layer => layer.encoding?.[channel]?.scale !== undefined
58
+ layer => eligible(layer, channel)
56
59
  );
57
60
  if (candidates.length === 1) {
58
61
  return { target: candidates[0].id, channels: [channel] };
@@ -63,6 +66,7 @@ function legendCandidates(program) {
63
66
  );
64
67
  }
65
68
  }
69
+ if (Object.keys(program.guideConfigs.suppressedLegends ?? {}).length) return undefined;
66
70
  throw new Error("Shared facet legend requires one eligible encoded scale.");
67
71
  }
68
72
 
@@ -85,6 +89,7 @@ function continuousValues(domain, count) {
85
89
 
86
90
  function legendRequests(program) {
87
91
  const request = legendCandidates(program);
92
+ if (request === undefined) return [];
88
93
  const layer = findLayer(program, request.target);
89
94
  const separate = request.channels.filter(channel => {
90
95
  if (!["color", "stroke"].includes(channel)) return false;
@@ -100,7 +105,12 @@ function legendRequests(program) {
100
105
  }
101
106
 
102
107
  function prepareAutoLegendSource(child) {
108
+ // Facet inference has already selected explicit eligible owner/channel pairs
109
+ // in legendCandidates, including suppression. Invoke the public creator with
110
+ // those requests; importing its action planner here reverses the dependency
111
+ // direction and repeats the same policy through a circular action graph.
103
112
  const requests = legendRequests(child);
113
+ if (!requests.length) return child;
104
114
  const canvas = child.graphicSpec.objects.canvas;
105
115
  let source = child
106
116
  ._withCanvasConfig({
@@ -207,12 +217,14 @@ export function prepareSharedFacetLegend(program) {
207
217
  const firstId = program.compositionSpec.children[0];
208
218
  const first = program.children[firstId];
209
219
  const inferred = legendKinds(first).length === 0;
210
- if (inferred) {
220
+ if (inferred && !Object.keys(first.guideConfigs.suppressedLegends ?? {}).length) {
211
221
  const legacy = resolveLegacyCategoricalLegend(program);
212
222
  if (legacy !== undefined) return legacy;
213
223
  }
214
- const source = inferred ? prepareAutoLegendSource(first) : first;
224
+ // Shared guides receive the offset once in the parent's final placement.
225
+ const source = restoreLegendItemsOffset(inferred ? prepareAutoLegendSource(first) : first);
215
226
  const kinds = legendKinds(source);
227
+ if (!kinds.length && Object.keys(first.guideConfigs.suppressedLegends ?? {}).length) return undefined;
216
228
  if (kinds.some(kind => localLegendPosition(source.guideConfigs.legend[kind]) !== undefined)) {
217
229
  return { ...prepareAuthoredFacetLegend(program, facetLegendIntent(source)), keepShared: true };
218
230
  }
@@ -1 +1,5 @@
1
- import{measureFacetContentChildren as V}from"./facetContentBounds.js";import{facetAxisMode as P,hasOuterFacetAxes as W}from"../core/facetAxisPolicy.js";import{isPlainObject as v}from"../core/immutable.js";import{formatVisibleText as _,measureTextWidth as U,resolveTextBounds as z,textBoundsIntersect as S}from"../core/textMetrics.js";import{validateFacetHeaderRotations as K}from"../grammar/facetHeaders.js";import{resolveDisplayLabel as N}from"../grammar/displayLabels.js";import{resolveGraphicBounds as F}from"../layout/canvas.js";import{unionConcreteGraphicBounds as Y}from"../grammar/schemas/graphicBounds.js";import{intersectClipBounds as Z}from"../grammar/schemas/graphicClip.js";import{DEFAULT_COLORS as J,DEFAULT_FONT_FAMILY as Q}from"../theme/defaults.js";import{prepareFacetHeaderTitles as X}from"../layout/facetHeaderTitles.js";import{cartesianAxisOwners as tt,axisGraphicIds as E}from"../selectors/axes.js";import{resolveFacetOuterSides as et}from"../grammar/facets/guides.js";import{resolveConcreteGraphicBounds as ot}from"../grammar/schemas/graphicBounds.js";import{allLegendGraphicIds as nt}from"./guides/resources.js";const it=Object.freeze(["top","right","bottom","left"]),B=Object.freeze({top:0,right:0,bottom:0,left:0});function ct(t){return`${t}-headers`}function T(){return{mode:"legacy",common:{fontSize:12,fontFamily:Q,fontWeight:600,color:J.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function j(t){if(!v(t))throw new TypeError("Facet headers config must be a plain object.");if(K(t),t.common===void 0&&t.mode===void 0)return{...T(),common:{...T().common,...t}};if(!["legacy","roles"].includes(t.mode)||!v(t.common)||!v(t.row)||!v(t.column))throw new Error("Facet headers config must contain canonical mode and role objects.");return{mode:t.mode,common:{...t.common},row:{...t.row},column:{...t.column}}}function y(t,e){const o=j(t);return e==="all"?{...o.common}:{...o.common,...o[e]}}function b(t,e){return N(t,e.labelMap,_)}function st(t){return new Map((t.facet.grid?.cells??[]).map(e=>[e.id,e]))}function rt(t,e){const o=t.compositionSpec,c=y(e,"all"),s=st(o);return o.children.map((i,n)=>{const r=s.get(i),f=r===void 0?b(o.facet.values[n],c):`${b(r.rowValue,c)} \xB7 ${b(r.columnValue,c)}`;return{topology:"legacy",role:"column",id:i,text:f,config:c}})}function G(t,e){const o=e==="column"?"column":"row",c=e==="column"?"columnValue":"rowValue",s=new Map;for(const i of t.facet.grid.cells){const n=s.get(i[o]);if(n===void 0)s.set(i[o],{coordinate:i[o],value:i[c],ids:[i.id]});else{if(n.value!==i[c])throw new Error(`Facet ${e} header values conflict at coordinate ${i[o]}.`);n.ids.push(i.id)}}return[...s.values()].sort((i,n)=>i.coordinate-n.coordinate)}function ft(t,e){const o=t.compositionSpec,c=y(e,"column");if(o.facet.grid===void 0)return o.children.map((r,f)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(f/o.columns),column:f%o.columns,text:b(o.facet.values[f],c),config:c}));const s=y(e,"row"),i=G(o,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,text:b(r.value,c),config:c})),n=G(o,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,text:b(r.value,s),config:s}));return[...i,...n]}function x(t){return t.rotation!==void 0&&t.rotation!==0}function H(t,e){return z({x:0,y:0,text:t.text,...t.config,textAlign:"center",textBaseline:"middle"},e)}function at(t,e,o){if(x(t.config)){const c=H(t,o);return["left","right"].includes(e)?c.right-c.left:c.bottom-c.top}return["left","right"].includes(e)?U(t.text,t.config,o):t.config.fontSize}function lt(t){const e={...B};if(!["plot","content"].includes(t.compositionSpec.spacing))return e;for(const o of t.compositionSpec.children){const c=t.children[o],s=F(c),i=c.graphicSpec.objects.canvas.properties;e.left=Math.max(e.left,s.x),e.top=Math.max(e.top,s.y),e.right=Math.max(e.right,i.width-s.x-s.width),e.bottom=Math.max(e.bottom,i.height-s.y-s.height)}return e}function O(t,e,o,c,s){const i=at(t,e,o)+t.config.offset+(t.clearance?.[e]??0);return x(t.config)&&s?Math.max(0,i-c[e]):i}function dt(t,e,o){if(!e.some(n=>["content","view"].includes(n.config.anchor)))return;if(!["plot","content"].includes(t.compositionSpec.spacing))throw new Error("Facet header content anchor requires plot spacing or content spacing.");const c=new Map,s=e.some(n=>n.config.anchor==="view")?new Map(V(t).map(n=>[n.id,n])):void 0;for(const n of t.compositionSpec.children){const r=t.children[n],f=F(r),d={left:f.x,right:f.x+f.width,top:f.y,bottom:f.y+f.height},l=r.semanticSpec.layers.map(u=>u.id).filter(u=>r.graphicSpec.objects[u]!==void 0),h=Y(r.graphicSpec,l,o),a=r.graphicSpec.objects.canvas.properties,m=h===void 0?void 0:Z(h,{left:0,right:a.width,top:0,bottom:a.height});c.set(n,m===void 0||m.left>m.right||m.top>m.bottom?d:C([d,m]))}const i=s===void 0?void 0:new Map([...s].map(([n,r])=>[n,{left:r.bounds.left+r.plot.x,right:r.bounds.right+r.plot.x,top:r.bounds.top+r.plot.y,bottom:r.bounds.bottom+r.plot.y}]));for(const n of e)if(["content","view"].includes(n.config.anchor)){n.config.anchor==="view"&&(n.contentById=i),n.clearance={...B};for(const r of n.ids??[n.id]){const f=F(t.children[r]),d=(n.contentById??c).get(r);for(const[l,h]of Object.entries({left:f.x-d.left,right:d.right-f.x-f.width,top:f.y-d.top,bottom:d.bottom-f.y-f.height}))n.clearance[l]=Math.max(n.clearance[l],h)}}return c}function R(t,e,o,c){const s=t.filter(i=>i.role===e&&i.text!=="");return s.length===0?0:Math.max(...s.map(i=>O(i,i.config.side,o,c,!0)))}function ut(t,e){const o=t.compositionSpec,c=t.children[e],s=W(o.facet.guides.axes)?et({placements:o.facet.grid?.cells??o.children.map((n,r)=>({id:n,row:Math.floor(r/o.columns),column:r%o.columns}))}):void 0,i=tt(c).flatMap(n=>E(n.channel,n.id).filter((r,f)=>f>=2&&(s===void 0||P(o.facet.guides.axes,n.channel)==="each"||s[n.config?.[["line","ticks","labels","title"][f]]?.position]?.includes(e))));for(const n of["theta","radius","parallel"])c.guideConfigs.axis?.[n]!==void 0&&i.push(...E(n).slice(2));return o.facet.guides.legend===!1&&i.push(...nt(Object.keys(c.guideConfigs.legend??{}))),i.filter(n=>c.graphicSpec.objects[n]!==void 0)}function mt(t,e){const o=t.children[e],c=F(o),s=ut(t,e).map(i=>ot(o.graphicSpec,i,o.materializationConfigs.textMetrics)).filter(Boolean);return{top:Math.max(0,...s.map(i=>c.y-i.top)),bottom:Math.max(0,...s.map(i=>i.bottom-c.y-c.height)),left:Math.max(0,...s.map(i=>c.x-i.left)),right:Math.max(0,...s.map(i=>i.right-c.x-c.width))}}function Rt(t,e){const o=j(e),c=o.mode==="legacy"?rt(t,o):ft(t,o),s=t.compositionSpec.facet.grid===void 0?Math.ceil(t.compositionSpec.children.length/t.compositionSpec.columns):Math.max(...t.compositionSpec.facet.grid.cells.map(a=>a.row))+1,i=t.materializationConfigs.textMetrics,n=["column",...t.compositionSpec.facet.grid===void 0?[]:["row"]].map(a=>[a,y(o,a)]),r=n.some(([,a])=>a.title!==void 0);if(["plot","content"].includes(t.compositionSpec.spacing)){const a=new Map(t.compositionSpec.children.map(m=>[m,mt(t,m)]));for(const m of c)m.clearance=Object.fromEntries(it.map(u=>{const p=Math.max(0,...(m.ids??[m.id]).map(g=>a.get(g)[u]));return[u,p>m.config.offset?p:0]}))}const f=dt(t,c,i),d=r||c.some(a=>x(a.config))?lt(t):B,l={outer:{...B},cellRows:{top:Array(s).fill(0),bottom:Array(s).fill(0)}};if(o.mode==="legacy"&&["plot","content"].includes(t.compositionSpec.spacing))for(const a of c){if(a.text==="")continue;const m=t.compositionSpec.children.indexOf(a.id),u=t.compositionSpec.facet.grid?.cells[m]?.row??Math.floor(m/t.compositionSpec.columns);l.cellRows.top[u]=Math.max(l.cellRows.top[u],O(a,"top",i,d,u===0))}if(o.mode==="roles"){const a=y(o,"column"),m=R(c,"column",i,d);if(t.compositionSpec.facet.grid===void 0){const u=["left","right"].includes(a.side);u&&(l.cellColumns={left:Array(t.compositionSpec.columns).fill(0),right:Array(t.compositionSpec.columns).fill(0)});for(const p of c)if(p.text!==""){const g=u?l.cellColumns:l.cellRows,M=u?p.column:p.row,$=u?t.compositionSpec.columns-1:s-1,q=["top","left"].includes(a.side)?M===0:M===$;g[a.side][M]=Math.max(g[a.side][M],O(p,a.side,i,d,q))}}else{l.outer[a.side]=m;const u=y(o,"row");l.outer[u.side]=R(c,"row",i,d)}}const h=X({roles:n,descriptors:c,space:d,headerLayout:l,profile:i,ids:t.compositionSpec.children});return{headers:o,descriptors:c,titles:h,headerLayout:l,...f===void 0?{}:{contentById:f},...(r||c.some(a=>x(a.config)))&&i!==void 0?{profile:i}:{}}}function D(t,e){return e==="start"?{x:t.left,textAlign:"left"}:e==="end"?{x:t.right,textAlign:"right"}:{x:(t.left+t.right)/2,textAlign:"center"}}function pt(t,e){return e==="start"?{y:t.top,textBaseline:"top"}:e==="end"?{y:t.bottom,textBaseline:"bottom"}:{y:(t.top+t.bottom)/2,textBaseline:"middle"}}function C(t){return{left:Math.min(...t.map(e=>e.left)),right:Math.max(...t.map(e=>e.right)),top:Math.min(...t.map(e=>e.top)),bottom:Math.max(...t.map(e=>e.bottom))}}function w(t,e){return{left:t.x+e.x,right:t.x+e.x+e.width,top:t.y+e.y,bottom:t.y+e.y+e.height}}function A(t){return{left:t.x,right:t.x+t.width,top:t.y,bottom:t.y+t.height}}function I(t,e){return{left:t.x+e.left,right:t.x+e.right,top:t.y+e.top,bottom:t.y+e.bottom}}function L(t,e,o,c,s){return s==="start"?t-o:s==="end"?e-c:(t+e-o-c)/2}function k(t,e,o,c){const{config:s}=t,i=H(t,c),n={textAlign:"center",textBaseline:"middle",rotation:s.rotation};return["top","bottom"].includes(s.side)?{...n,x:L(e.left,e.right,i.left,i.right,s.align),y:s.side==="top"?o.top-s.offset-i.bottom:o.bottom+s.offset-i.top}:{...n,x:s.side==="left"?o.left-s.offset-i.right:o.right+s.offset-i.left,y:L(e.top,e.bottom,i.top,i.bottom,s.align)}}function ht(t,e,o,c,s,i){i=t.contentById??i;const n=t.ids.map(p=>e.get(p)),r=t.ids.find((p,g)=>n[g]===void 0),f=t.ids.find(p=>o.get(p)===void 0);if(r!==void 0||f!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${r??f}".`);const d=t.ids.map((p,g)=>w(n[g],o.get(p))),l=C(d),h=["content","view"].includes(t.config.anchor)?C(t.ids.map((p,g)=>I(n[g],i.get(p)))):["plot","content"].includes(c)?l:C(n.map(A)),a=t.config;if(x(a)||t.topology==="role-title"){const p=k(t,l,h,s),g=["left","right"].includes(a.side)?"y":"x";return a.alongOffset!==void 0&&(p[g]+=a.alongOffset),{descriptor:t,snapshots:h,properties:p}}const m=["content","view"].includes(a.anchor)?0:t.clearance?.[a.side]??0;let u;if(["top","bottom"].includes(a.side)){u=D(l,a.align);const p=a.side==="top";u.y=p?h.top-m-a.offset-a.fontSize/2:h.bottom+m+a.offset+a.fontSize/2,u.textBaseline="middle"}else{u=pt(l,a.align);const p=a.side==="left";u.x=p?h.left-m-a.offset:h.right+m+a.offset,u.textAlign=p?"right":"left"}return{descriptor:t,snapshots:h,properties:u}}function gt(t,e,o,c,s,i){if(i=t.contentById??i,e===void 0||o===void 0)throw new Error(`Facet header requires cell and plot bounds for "${t.id}".`);const n=["content","view"].includes(t.config.anchor)?I(e,i.get(t.id)):w(e,o);if(x(t.config)&&["plot","content"].includes(c)){const r=w(e,o);return{descriptor:t,snapshots:A(e),properties:k({...t,config:{...t.config,side:"top"}},r,n,s)}}return{descriptor:t,snapshots:A(e),properties:{...D(w(e,o),t.config.align),y:["plot","content"].includes(c)?e.y+o.y-(t.clearance?.top??0)-t.config.offset-t.config.fontSize/2:e.y+t.config.offset,textBaseline:"middle",...x(t.config)?{rotation:t.config.rotation}:{}}}}function xt(t){const{config:e}=t.descriptor;return{type:"text",properties:{...t.properties,text:t.descriptor.text,fill:e.color,fontSize:e.fontSize,fontFamily:e.fontFamily,fontWeight:e.fontWeight}}}function wt(t,e,o,c,s,i){const n=e.map((r,f)=>t[f].descriptor.text===""?void 0:z(r.properties,c));for(const[r,f]of n.entries())if(f!==void 0&&(f.left<-1e-9||f.right>o.width+1e-9||f.top<-1e-9||f.bottom>o.height+1e-9||n.some((d,l)=>l<r&&d!==void 0&&S(d,f))||o.children.some(d=>S(f,i===void 0?["plot","content"].includes(o.spacing)?w(d,s.get(d.id)):A(d):I(d,i.get(d.id))))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function yt(t,e,o,c,s){const i=[];for(let n=0;n<t.length;n+=1){if(t[n].properties.text==="")continue;const r=z(t[n].properties,c),f=e.children[n],d=o.get(f.id);if(r.left<0||r.right>e.width||r.top<0||r.bottom>e.height||i.some(l=>S(l,r))||(["plot","content"].includes(e.spacing)?e.children.some(l=>S(r,s===void 0?w(l,o.get(l.id)):I(l,s.get(l.id)))):S(r,w(f,d))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");i.push(r)}}function bt(t,e,o){const c=new Map(t.children.map(n=>[n.id,n])),s=new Map(e.map(n=>[n.id,n])),i=[...o.descriptors,...o.titles??[]].map(n=>n.topology==="legacy"?gt(n,c.get(n.id),s.get(n.id),t.spacing,o.profile,o.contentById):ht(n,c,s,t.spacing,o.profile,o.contentById));return{records:i,items:i.map(xt),plotById:s}}function Dt(t,e,o,c){const s=t.materializationConfigs.textMetrics,{records:i,items:n,plotById:r}=bt(e,o,c);c.headers.mode==="legacy"?yt(n,e,r,s,c.contentById):wt(i,n,e,s,r,c.contentById);const f=ct(t.compositionSpec.id);return t.createGraphics({id:f,type:"collection",parent:"canvas"}).editGraphics({target:f,property:"items",value:n})}export{T as createDefaultFacetHeaders,ct as facetHeaderGraphicId,Dt as materializeFacetHeaders,j as normalizeFacetHeadersConfig,Rt as prepareFacetHeaders,y as resolveFacetHeaderConfig,bt as resolveFacetHeaderItems,ut as retainedFacetGuideIds};
1
+ import{resolveLegendTitleText as x}from"../layout/legendText.js";import{measureFacetContentChildren as _}from"./facetContentBounds.js";import{facetAxisMode as U,hasOuterFacetAxes as K}from"../core/facetAxisPolicy.js";import{isPlainObject as F}from"../core/immutable.js";import{formatVisibleText as N,resolveTextBounds as T,textBoundsIntersect as v}from"../core/textMetrics.js";import{validateFacetHeaderRotations as Y}from"../grammar/facetHeaders.js";import{resolveDisplayLabel as Z}from"../grammar/displayLabels.js";import{resolveGraphicBounds as B}from"../layout/canvas.js";import{unionConcreteGraphicBounds as J}from"../grammar/schemas/graphicBounds.js";import{intersectClipBounds as Q}from"../grammar/schemas/graphicClip.js";import{DEFAULT_COLORS as X,DEFAULT_FONT_FAMILY as tt}from"../theme/defaults.js";import{prepareFacetHeaderTitles as et}from"../layout/facetHeaderTitles.js";import{cartesianAxisOwners as nt,axisGraphicIds as j}from"../selectors/axes.js";import{resolveFacetOuterSides as ot}from"../grammar/facets/guides.js";import{resolveConcreteGraphicBounds as it}from"../grammar/schemas/graphicBounds.js";import{allLegendGraphicIds as ct}from"./guides/resources.js";const st=Object.freeze(["top","right","bottom","left"]),C=Object.freeze({top:0,right:0,bottom:0,left:0});function rt(t){return`${t}-headers`}function H(){return{mode:"legacy",common:{fontSize:12,fontFamily:tt,fontWeight:600,color:X.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function O(t){if(!F(t))throw new TypeError("Facet headers config must be a plain object.");if(Y(t),t.common===void 0&&t.mode===void 0)return{...H(),common:{...H().common,...t}};if(!["legacy","roles"].includes(t.mode)||!F(t.common)||!F(t.row)||!F(t.column))throw new Error("Facet headers config must contain canonical mode and role objects.");return{mode:t.mode,common:{...t.common},row:{...t.row},column:{...t.column}}}function w(t,e){const n=O(t);return e==="all"?{...n.common}:{...n.common,...n[e]}}function S(t,e){return Z(t,e.labelMap,N)}function ft(t){return new Map((t.facet.grid?.cells??[]).map(e=>[e.id,e]))}function G(t,e){const n=t.compositionSpec,c=w(e,"all"),s=ft(n);return n.children.map((o,i)=>{const r=s.get(o),a=r===void 0?S(n.facet.values[i],c):`${S(r.rowValue,c)} \xB7 ${S(r.columnValue,c)}`;return{topology:"legacy",role:"column",id:o,text:a,config:c,value:r===void 0?n.facet.values[i]:void 0}})}function R(t,e){const n=e==="column"?"column":"row",c=e==="column"?"columnValue":"rowValue",s=new Map;for(const o of t.facet.grid.cells){const i=s.get(o[n]);if(i===void 0)s.set(o[n],{coordinate:o[n],value:o[c],ids:[o.id]});else{if(i.value!==o[c])throw new Error(`Facet ${e} header values conflict at coordinate ${o[n]}.`);i.ids.push(o.id)}}return[...s.values()].sort((o,i)=>o.coordinate-i.coordinate)}function L(t,e){const n=t.compositionSpec,c=w(e,"column");if(n.facet.grid===void 0)return n.children.map((r,a)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(a/n.columns),column:a%n.columns,value:n.facet.values[a],text:S(n.facet.values[a],c),config:c}));const s=w(e,"row"),o=R(n,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,value:r.value,text:S(r.value,c),config:c})),i=R(n,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,value:r.value,text:S(r.value,s),config:s}));return[...o,...i]}function y(t){return t.rotation!==void 0&&t.rotation!==0}function D(t,e){return T({x:0,y:0,text:t.text,lines:x(t.text,t.config,e).lines,...t.config,textAlign:"center",textBaseline:"middle"},e)}function at(t,e,n){if(y(t.config)){const c=D(t,n);return["left","right"].includes(e)?c.right-c.left:c.bottom-c.top}return["left","right"].includes(e)?x(t.text,t.config,n).width:t.text.includes(`
2
+ `)?x(t.text,t.config,n).height:t.config.fontSize}function lt(t){const e={...C};if(!["plot","content"].includes(t.compositionSpec.spacing))return e;for(const n of t.compositionSpec.children){const c=t.children[n],s=B(c),o=c.graphicSpec.objects.canvas.properties;e.left=Math.max(e.left,s.x),e.top=Math.max(e.top,s.y),e.right=Math.max(e.right,o.width-s.x-s.width),e.bottom=Math.max(e.bottom,o.height-s.y-s.height)}return e}function E(t,e,n,c,s){const o=at(t,e,n)+t.config.offset+(t.clearance?.[e]??0);return y(t.config)&&s?Math.max(0,o-c[e]):o}function dt(t,e,n){if(!e.some(i=>["content","view"].includes(i.config.anchor)))return;if(!["plot","content"].includes(t.compositionSpec.spacing))throw new Error("Facet header content anchor requires plot spacing or content spacing.");const c=new Map,s=e.some(i=>i.config.anchor==="view")?new Map(_(t).map(i=>[i.id,i])):void 0;for(const i of t.compositionSpec.children){const r=t.children[i],a=B(r),d={left:a.x,right:a.x+a.width,top:a.y,bottom:a.y+a.height},l=r.semanticSpec.layers.map(u=>u.id).filter(u=>r.graphicSpec.objects[u]!==void 0),p=J(r.graphicSpec,l,n),f=r.graphicSpec.objects.canvas.properties,m=p===void 0?void 0:Q(p,{left:0,right:f.width,top:0,bottom:f.height});c.set(i,m===void 0||m.left>m.right||m.top>m.bottom?d:A([d,m]))}const o=s===void 0?void 0:new Map([...s].map(([i,r])=>[i,{left:r.bounds.left+r.plot.x,right:r.bounds.right+r.plot.x,top:r.bounds.top+r.plot.y,bottom:r.bounds.bottom+r.plot.y}]));for(const i of e)if(["content","view"].includes(i.config.anchor)){i.config.anchor==="view"&&(i.contentById=o),i.clearance={...C};for(const r of i.ids??[i.id]){const a=B(t.children[r]),d=(i.contentById??c).get(r);for(const[l,p]of Object.entries({left:a.x-d.left,right:d.right-a.x-a.width,top:a.y-d.top,bottom:d.bottom-a.y-a.height}))i.clearance[l]=Math.max(i.clearance[l],p)}}return c}function k(t,e,n,c){const s=t.filter(o=>o.role===e&&o.text!=="");return s.length===0?0:Math.max(...s.map(o=>E(o,o.config.side,n,c,!0)))}function ut(t,e){const n=t.compositionSpec,c=t.children[e],s=K(n.facet.guides.axes)?ot({placements:n.facet.grid?.cells??n.children.map((i,r)=>({id:i,row:Math.floor(r/n.columns),column:r%n.columns}))}):void 0,o=nt(c).flatMap(i=>j(i.channel,i.id).filter((r,a)=>a>=2&&(s===void 0||U(n.facet.guides.axes,i.channel)==="each"||s[i.config?.[["line","ticks","labels","title"][a]]?.position]?.includes(e))));for(const i of["theta","radius","parallel"])c.guideConfigs.axis?.[i]!==void 0&&o.push(...j(i).slice(2));return n.facet.guides.legend===!1&&o.push(...ct(Object.keys(c.guideConfigs.legend??{}))),o.filter(i=>c.graphicSpec.objects[i]!==void 0)}function mt(t,e){const n=t.children[e],c=B(n),s=ut(t,e).map(o=>it(n.graphicSpec,o,n.materializationConfigs.textMetrics)).filter(Boolean);return{top:Math.max(0,...s.map(o=>c.y-o.top)),bottom:Math.max(0,...s.map(o=>o.bottom-c.y-c.height)),left:Math.max(0,...s.map(o=>c.x-o.left)),right:Math.max(0,...s.map(o=>o.right-c.x-c.width))}}function Dt(t,e){const n=O(e),c=n.mode==="legacy"?G(t,n):L(t,n),s=t.compositionSpec.facet.grid===void 0?Math.ceil(t.compositionSpec.children.length/t.compositionSpec.columns):Math.max(...t.compositionSpec.facet.grid.cells.map(f=>f.row))+1,o=t.materializationConfigs.textMetrics,i=["column",...t.compositionSpec.facet.grid===void 0?[]:["row"]].map(f=>[f,w(n,f)]),r=i.some(([,f])=>f.title!==void 0);if(["plot","content"].includes(t.compositionSpec.spacing)){const f=new Map(t.compositionSpec.children.map(m=>[m,mt(t,m)]));for(const m of c)m.clearance=Object.fromEntries(st.map(u=>{const h=Math.max(0,...(m.ids??[m.id]).map(g=>f.get(g)[u]));return[u,h>m.config.offset?h:0]}))}const a=dt(t,c,o),d=r||c.some(f=>y(f.config))?lt(t):C,l={outer:{...C},cellRows:{top:Array(s).fill(0),bottom:Array(s).fill(0)}};if(n.mode==="legacy"&&["plot","content"].includes(t.compositionSpec.spacing))for(const f of c){if(f.text==="")continue;const m=t.compositionSpec.children.indexOf(f.id),u=t.compositionSpec.facet.grid?.cells[m]?.row??Math.floor(m/t.compositionSpec.columns);l.cellRows.top[u]=Math.max(l.cellRows.top[u],E(f,"top",o,d,u===0))}if(n.mode==="roles"){const f=w(n,"column"),m=k(c,"column",o,d);if(t.compositionSpec.facet.grid===void 0){const u=["left","right"].includes(f.side);u&&(l.cellColumns={left:Array(t.compositionSpec.columns).fill(0),right:Array(t.compositionSpec.columns).fill(0)});for(const h of c)if(h.text!==""){const g=u?l.cellColumns:l.cellRows,M=u?h.column:h.row,P=u?t.compositionSpec.columns-1:s-1,W=["top","left"].includes(f.side)?M===0:M===P;g[f.side][M]=Math.max(g[f.side][M],E(h,f.side,o,d,W))}}else{l.outer[f.side]=m;const u=w(n,"row");l.outer[u.side]=k(c,"row",o,d)}}const p=et({roles:i,descriptors:c,space:d,headerLayout:l,profile:o,ids:t.compositionSpec.children});return{headers:n,descriptors:c,titles:p,headerLayout:l,...a===void 0?{}:{contentById:a},...(r||c.some(f=>y(f.config)))&&o!==void 0?{profile:o}:{}}}function $(t,e){return e==="start"?{x:t.left,textAlign:"left"}:e==="end"?{x:t.right,textAlign:"right"}:{x:(t.left+t.right)/2,textAlign:"center"}}function ht(t,e){return e==="start"?{y:t.top,textBaseline:"top"}:e==="end"?{y:t.bottom,textBaseline:"bottom"}:{y:(t.top+t.bottom)/2,textBaseline:"middle"}}function A(t){return{left:Math.min(...t.map(e=>e.left)),right:Math.max(...t.map(e=>e.right)),top:Math.min(...t.map(e=>e.top)),bottom:Math.max(...t.map(e=>e.bottom))}}function b(t,e){return{left:t.x+e.x,right:t.x+e.x+e.width,top:t.y+e.y,bottom:t.y+e.y+e.height}}function I(t){return{left:t.x,right:t.x+t.width,top:t.y,bottom:t.y+t.height}}function z(t,e){return{left:t.x+e.left,right:t.x+e.right,top:t.y+e.top,bottom:t.y+e.bottom}}function q(t,e,n,c,s){return s==="start"?t-n:s==="end"?e-c:(t+e-n-c)/2}function V(t,e,n,c){const{config:s}=t,o=D(t,c),i={textAlign:"center",textBaseline:"middle",rotation:s.rotation};return["top","bottom"].includes(s.side)?{...i,x:q(e.left,e.right,o.left,o.right,s.align),y:s.side==="top"?n.top-s.offset-o.bottom:n.bottom+s.offset-o.top}:{...i,x:s.side==="left"?n.left-s.offset-o.right:n.right+s.offset-o.left,y:q(e.top,e.bottom,o.top,o.bottom,s.align)}}function pt(t,e,n,c,s,o){o=t.contentById??o;const i=t.ids.map(h=>e.get(h)),r=t.ids.find((h,g)=>i[g]===void 0),a=t.ids.find(h=>n.get(h)===void 0);if(r!==void 0||a!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${r??a}".`);const d=t.ids.map((h,g)=>b(i[g],n.get(h))),l=A(d),p=["content","view"].includes(t.config.anchor)?A(t.ids.map((h,g)=>z(i[g],o.get(h)))):["plot","content"].includes(c)?l:A(i.map(I)),f=t.config;if(y(f)||t.topology==="role-title"){const h=V(t,l,p,s),g=["left","right"].includes(f.side)?"y":"x";return f.alongOffset!==void 0&&(h[g]+=f.alongOffset),{descriptor:t,snapshots:p,properties:h}}const m=["content","view"].includes(f.anchor)?0:t.clearance?.[f.side]??0;let u;if(["top","bottom"].includes(f.side)){u=$(l,f.align);const h=f.side==="top";u.y=h?p.top-m-f.offset-(t.text.includes(`
3
+ `)?x(t.text,f,s).height:f.fontSize)/2:p.bottom+m+f.offset+(t.text.includes(`
4
+ `)?x(t.text,f,s).height:f.fontSize)/2,u.textBaseline="middle"}else{u=ht(l,f.align);const h=f.side==="left";u.x=h?p.left-m-f.offset:p.right+m+f.offset,u.textAlign=h?"right":"left"}return{descriptor:t,snapshots:p,properties:u}}function gt(t,e,n,c,s,o){if(o=t.contentById??o,e===void 0||n===void 0)throw new Error(`Facet header requires cell and plot bounds for "${t.id}".`);const i=["content","view"].includes(t.config.anchor)?z(e,o.get(t.id)):b(e,n);if(y(t.config)&&["plot","content"].includes(c)){const r=b(e,n);return{descriptor:t,snapshots:I(e),properties:V({...t,config:{...t.config,side:"top"}},r,i,s)}}return{descriptor:t,snapshots:I(e),properties:{...$(b(e,n),t.config.align),y:["plot","content"].includes(c)?e.y+n.y-(t.clearance?.top??0)-t.config.offset-(t.text.includes(`
5
+ `)?x(t.text,t.config,s).height:t.config.fontSize)/2:e.y+t.config.offset,textBaseline:"middle",...y(t.config)?{rotation:t.config.rotation}:{}}}}function xt(t){const{config:e}=t.descriptor;return{type:"text",properties:{...t.properties,text:t.descriptor.text,...x(t.descriptor.text,e).lines?{lines:x(t.descriptor.text,e).lines}:{},fill:e.color,fontSize:e.fontSize,fontFamily:e.fontFamily,fontWeight:e.fontWeight}}}function wt(t,e,n,c,s,o){const i=e.map((r,a)=>t[a].descriptor.text===""?void 0:T(r.properties,c));for(const[r,a]of i.entries())if(a!==void 0&&(a.left<-1e-9||a.right>n.width+1e-9||a.top<-1e-9||a.bottom>n.height+1e-9||i.some((d,l)=>l<r&&d!==void 0&&v(d,a))||n.children.some(d=>v(a,o===void 0?["plot","content"].includes(n.spacing)?b(d,s.get(d.id)):I(d):z(d,o.get(d.id))))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function yt(t,e,n,c,s){const o=[];for(let i=0;i<t.length;i+=1){if(t[i].properties.text==="")continue;const r=T(t[i].properties,c),a=e.children[i],d=n.get(a.id);if(r.left<0||r.right>e.width||r.top<0||r.bottom>e.height||o.some(l=>v(l,r))||(["plot","content"].includes(e.spacing)?e.children.some(l=>v(r,s===void 0?b(l,n.get(l.id)):z(l,s.get(l.id)))):v(r,b(a,d))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");o.push(r)}}function bt(t,e,n){const c=new Map(t.children.map(i=>[i.id,i])),s=new Map(e.map(i=>[i.id,i])),o=[...n.descriptors,...n.titles??[]].map(i=>i.topology==="legacy"?gt(i,c.get(i.id),s.get(i.id),t.spacing,n.profile,n.contentById):pt(i,c,s,t.spacing,n.profile,n.contentById));return{records:o,items:o.map(xt),plotById:s}}function kt(t,e,n,c){const s=t.materializationConfigs.textMetrics,{records:o,items:i,plotById:r}=bt(e,n,c);c.headers.mode==="legacy"?yt(i,e,r,s,c.contentById):wt(o,i,e,s,r,c.contentById);const a=rt(t.compositionSpec.id);return t.createGraphics({id:a,type:"collection",parent:"canvas"}).editGraphics({target:a,property:"items",value:i})}function $t(t){const e=O(t.materializationConfigs.facets[t.compositionSpec.id].headers),n=e.mode==="legacy"?G(t,e):L(t,e),c=e.mode==="legacy"?[]:["column",...t.compositionSpec.facet.grid?["row"]:[]].flatMap(s=>{const o=w(e,s);return o.title?[{role:s,title:!0,text:o.title.text,config:o}]:[]});return[...n.map(s=>({...s,role:e.mode==="legacy"?"all":s.role})),...c]}export{H as createDefaultFacetHeaders,rt as facetHeaderGraphicId,$t as facetHeaderTextEntries,kt as materializeFacetHeaders,O as normalizeFacetHeadersConfig,Dt as prepareFacetHeaders,w as resolveFacetHeaderConfig,bt as resolveFacetHeaderItems,ut as retainedFacetGuideIds};
@@ -1 +1 @@
1
- import{walkGraphicTreeEvents as P}from"../grammar/schemas/graphicTree.js";import{measureFacetContentChildren as E}from"./facetContentBounds.js";import{measureFacetContentChildren as It}from"./facetContentBounds.js";import{resolveFacetContentFlow as O,resolveFacetContentFooters as H}from"../layout/facetContentFlow.js";import{resolveConcreteGraphicBounds as G}from"../grammar/schemas/graphicBounds.js";import{textBoundsFitCanvas as $,textBoundsIntersect as S,resolveTextBounds as I}from"../core/textMetrics.js";import{resolveGraphicBounds as C}from"../layout/canvas.js";import{resolvePlacedPlotBounds as R}from"../layout/composition.js";import{alignedTextAnchor as A,alignedTitleAnchor as _,buildTitleReadingBlock as q,paintedTitleComponent as D,resolveTitleComponentBounds as W,titleTextProperties as N}from"../layout/title.js";import{resolveFacetLayout as k}from"../layout/facets.js";import{assertFacetTopLane as Z}from"./facetTopLane.js";import{namespaceGraphicId as j,namespaceGraphicSnapshot as J}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as T}from"./facetGuides/index.js";import{resolvePromotedFacetLegendLayout as K}from"./facetGuides/layout.js";import{attachSnapshotObject as Q,clearCompositionChildren as U,compositionChildDescriptor as V}from"./composition.js";import{materializeFacetHeaders as X,resolveFacetHeaderItems as Y,prepareFacetHeaders as tt,facetHeaderGraphicId as F,retainedFacetGuideIds as et}from"./facetHeaders.js";const ot={top:0,right:0,bottom:0,left:0},L=Object.freeze(["chartTitle","chartSubtitle"]);function it(t){const c=t.materializationConfigs.facets?.[t.compositionSpec.id];if(c===void 0)throw new Error(`Facet "${t.compositionSpec.id}" requires materialization config.`);return c}function nt(t){if(t.semanticSpec.title.text===void 0)return{height:0};const c=t.titleConfig;if(c===void 0)throw new Error("Facet title requires chart title configuration.");const e=q({text:t.semanticSpec.title.text,subtitle:t.semanticSpec.title.subtitle},c,t.materializationConfigs.textMetrics),i=8+c.offset;if(i<0)throw new Error("Facet parent title offset places text outside the Canvas.");const h=Math.ceil(i+e.height);return{config:c,block:e,top:i,reservation:h,height:c.position==="top"?h:0}}function z(t,c,e,i,h,f,n){if(e.length===0)return t;const{config:s,block:o,top:a}=n,d=t.graphicSpec.objects.canvas.properties,p=["top","bottom"].includes(s.position),u=D(p?{x:A(f.x,f.width,s.align)+(s.alongOffset??0),y:i.map(m=>(s.position==="top"?a:d.height-a-o.height)+m),lines:e,textAlign:s.align,...s.position==="bottom"?{rotation:0,explicitRotation:!0}:{}}:{x:i.map(m=>s.position==="left"?a+m:d.width-a-m),y:_(f.y,f.height,o.width,s.align)+(s.alongOffset??0),lines:e,textAlign:"center",rotation:s.position==="left"?-Math.PI/2:Math.PI/2,explicitRotation:!0}),w=W(u,h,t.materializationConfigs.textMetrics);if(!$(w,t.graphicSpec.objects.canvas.properties))throw new Error("Facet parent title places text outside the Canvas.");const v=u.lines.length;let l=t.createGraphics({id:c,type:"text",...v>1?{length:v}:{},parent:"canvas"});for(const[m,x]of Object.entries({...N(u,h),...u.explicitRotation?{rotation:u.rotation}:{}}))l=l.editGraphics({target:c,property:m,value:x});return l}function ct(t,c,e){let i=z(t,L[0],e.block.titleLines,e.block.titleCenters,e.config.titleStyle,c,e);return e.block.subtitleLines.length>0&&(i=z(i,L[1],e.block.subtitleLines,e.block.subtitleCenters,e.config.subtitleStyle,c,e)),i}function st(t,c,e){const i=t.compositionSpec,h=c.plots,f={spacing:"content",children:c.children.map((r,g)=>({...r,x:0,y:0,row:Math.floor(g/i.columns),column:g%i.columns}))},{records:n,items:s}=Y(f,h,e),o=n.flatMap((r,g)=>{const b=r.descriptor;return b.topology==="role-title"||b.ids?.length>1?[]:[{...s[g],ownerId:b.id??b.ids[0]}]}),a=E(t,{headerItems:o}),d=O({children:a,columns:i.columns,gap:i.gap}),p=new Map(a.map(r=>[r.id,r])),u=new Map(d.children.map(r=>[r.column,r])),w=H({flow:d,footers:[...u.values()].filter(r=>p.get(r.id).footerBounds!==void 0).map(r=>({id:r.id,column:r.column}))}),v=new Map(w.map(r=>[r.ownerId,r])),l={...d.bounds};for(const r of w){const g=p.get(r.ownerId),b=g.footerBounds,y=r.y-g.plot.height;l.left=Math.min(l.left,r.x+b.left),l.right=Math.max(l.right,r.x+b.right),l.top=Math.min(l.top,y+b.top),l.bottom=Math.max(l.bottom,y+b.bottom)}const m=k({...c,spacing:"canvas",topLane:void 0,children:[{id:"packed",width:l.right-l.left,height:l.bottom-l.top}],columns:1,gap:0,headerLayout:{outer:e.headerLayout.outer},plots:void 0}),x=m.children[0].x-l.left,B=m.children[0].y-l.top;return{...m,spacing:"content",columns:d.columns,rows:Math.ceil(d.children.length/d.columns),gap:i.gap,children:d.children.map(r=>{const g=p.get(r.id),y=t.children[r.id].graphicSpec.objects.canvas.properties,M=v.get(r.id);return{id:r.id,row:r.row,column:r.column,x:x+r.x-g.plot.x,y:B+r.y-g.plot.y,width:y.width,height:y.height,...M===void 0?{}:{footerShift:M.y-r.y-g.plot.height}}})}}function rt(t,c){if(t._assertCompositionProgram("resolveFacetProgramLayout"),t.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");c??=T(t);const e=nt(t),i=t.compositionSpec,h=tt(t,it(t).headers),f=new Map((i.facet.grid?.cells??[]).map(p=>[p.id,p])),n={...i.padding};e.config&&e.config.position!=="top"&&(n[e.config.position]+=e.reservation);const s={children:i.children.map((p,u)=>({...V(p,t.children[p]),value:i.facet.values[u],...f.has(p)?{row:f.get(p).row,column:f.get(p).column}:{}})),spacing:i.spacing,topLane:i.topLane,plots:i.children.map(p=>({id:p,...C(t.children[p])})),columns:i.columns,gap:i.gap,align:i.align,padding:n,titleHeight:e.height,headerLayout:h.headerLayout,sharedLegend:i.facet.guides.legend==="shared"&&c?.mode!=="promoted",...c===void 0?{}:{sharedLegendGap:c.reservation.gap,sharedLegendWidth:c.reservation.width,sharedLegendHeight:c.reservation.height,sharedLegendPosition:c.reservation.position}};let o=i.spacing==="content"?st(t,s,h):k(s);const a=i.children.map(p=>({id:p,...C(t.children[p])}));let d=R({placements:o.children,plots:a});return{layout:o,plot:d}=K(t,{layout:o,plot:d,plots:a,title:e,preparedHeaders:h,prepared:c}),{layout:o,title:e,plot:d,plots:a,preparedHeaders:h}}function at(t,c,e){const i=[],h=c.children.map((n,s)=>({id:n.id,left:n.x+e[s].x,right:n.x+e[s].x+e[s].width,top:n.y+e[s].y,bottom:n.y+e[s].y+e[s].height}));for(const n of c.children){const s=et(t,n.id);for(const o of s){const a=j(`${t.compositionSpec.id}-${n.id}`,o);if(t.graphicSpec.objects[a]===void 0)continue;const d=G(t.graphicSpec,a,t.materializationConfigs.textMetrics);d!==void 0&&i.push({owner:n.id,bounds:d})}}const f=t.graphicSpec.objects[F(t.compositionSpec.id)];for(const n of f?.items??[])n.properties.text!==""&&i.push({owner:"headers",bounds:I(n.properties,t.materializationConfigs.textMetrics)});for(let n=0;n<i.length;n+=1){const s=i[n];if(h.some(o=>o.id!==s.owner&&S(s.bounds,o))||i.slice(0,n).some(o=>o.owner!==s.owner&&S(s.bounds,o.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function dt(t,c){if(c.titles.length===0)return;const e=F(t.compositionSpec.id),i=t.materializationConfigs.textMetrics,h=t.graphicSpec.objects[e].items.slice(c.descriptors.length).map(n=>I(n.properties,i)),f=n=>{if((n.ownerId??n.id)===e)return;const s=n.object.type??n.owner?.type,o=n.object.properties;if(s!=="text"||o?.text===""||o?.opacity===0)return;const a=G(t.graphicSpec,n.id,i);if(a!==void 0&&h.some(d=>S(d,a)))throw new Error("Facet field title overlaps retained chart text.")};P(t.graphicSpec,{enter:f,item:f})}function Lt(t){const c=T(t),{layout:e,title:i,plot:h,plots:f,preparedHeaders:n}=rt(t,c),s=t.graphicSpec.objects.canvas?.properties?.background??"white";let o=U(t);o.graphicSpec.objects.canvas===void 0&&(o=o.createGraphics({id:"canvas",type:"canvas"}));for(const[a,d]of Object.entries({width:e.width,height:e.height,background:s}))o=o.editGraphics({target:"canvas",property:a,value:d});for(const a of e.children){const d=t.children[a.id],p=J(d.graphicSpec,{namespace:`${t.compositionSpec.id}-${a.id}`,x:a.x,y:a.y});if(o=Q(o,p,p.order[0],"canvas"),["plot","content"].includes(e.spacing)){const u=p.order[0],w=j(`${t.compositionSpec.id}-${a.id}`,"plot-background"),v=C(d),l=o.graphicSpec.objects[u].children?.[0];o=o.editGraphics({target:u,property:"background",value:"transparent"}).createGraphics({id:w,type:"rect",parent:u,...l===void 0?{}:{before:l}});for(const[m,x]of Object.entries({...v,stroke:"none",strokeWidth:0,fill:d.graphicSpec.objects.canvas.properties.background??"transparent"}))o=o.editGraphics({target:w,property:m,value:x})}}return o=X(o,e,f,n),o=o.composeFacetGuides({layout:e,plot:h}),e.spacing==="plot"&&at(o,e,f),i.config&&(o=ct(o,h,i)),dt(o,n),Z(o,{layout:e,plots:f,headerId:F(t.compositionSpec.id),titleIds:L}),o._withCanvasConfig({margin:ot,size:{width:"auto",height:"auto"}})}export{Lt as materializeFacetGraphics,It as measureFacetContentChildren,rt as resolveFacetProgramLayout};
1
+ import{compositionTitleLayout as B,materializeCompositionTitle as P}from"./compositionTitle.js";import{restoreLegendItemsOffset as T,applyLegendItemsOffset as O}from"./legendItemsOffset.js";import{walkGraphicTreeEvents as E}from"../grammar/schemas/graphicTree.js";import{measureFacetContentChildren as H}from"./facetContentBounds.js";import{measureFacetContentChildren as Fe}from"./facetContentBounds.js";import{resolveFacetContentFlow as $,resolveFacetContentFooters as _}from"../layout/facetContentFlow.js";import{resolveConcreteGraphicBounds as I}from"../grammar/schemas/graphicBounds.js";import{textBoundsIntersect as x,resolveTextBounds as G}from"../core/textMetrics.js";import{resolveGraphicBounds as F}from"../layout/canvas.js";import{resolvePlacedPlotBounds as q}from"../layout/composition.js";import{resolveFacetLayout as M}from"../layout/facets.js";import{assertFacetTopLane as D}from"./facetTopLane.js";import{namespaceGraphicId as j,namespaceGraphicSnapshot as R}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as z}from"./facetGuides/index.js";import{resolvePromotedFacetLegendLayout as W}from"./facetGuides/layout.js";import{attachSnapshotObject as A,clearCompositionChildren as N,compositionChildDescriptor as Z}from"./composition.js";import{materializeFacetHeaders as J,resolveFacetHeaderItems as K,prepareFacetHeaders as Q,facetHeaderGraphicId as C,retainedFacetGuideIds as U}from"./facetHeaders.js";const V={top:0,right:0,bottom:0,left:0},X=Object.freeze(["chartTitle","chartSubtitle"]);function Y(e){const c=e.materializationConfigs.facets?.[e.compositionSpec.id];if(c===void 0)throw new Error(`Facet "${e.compositionSpec.id}" requires materialization config.`);return c}function ee(e,c,s){const n=e.compositionSpec,f=c.plots,l={spacing:"content",children:c.children.map((i,u)=>({...i,x:0,y:0,row:Math.floor(u/n.columns),column:u%n.columns}))},{records:o,items:r}=K(l,f,s),t=o.flatMap((i,u)=>{const m=i.descriptor;return m.topology==="role-title"||m.ids?.length>1?[]:[{...r[u],ownerId:m.id??m.ids[0]}]}),p=H(e,{headerItems:t}),a=$({children:p,columns:n.columns,gap:n.gap}),d=new Map(p.map(i=>[i.id,i])),g=new Map(a.children.map(i=>[i.column,i])),w=_({flow:a,footers:[...g.values()].filter(i=>d.get(i.id).footerBounds!==void 0).map(i=>({id:i.id,column:i.column}))}),v=new Map(w.map(i=>[i.ownerId,i])),h={...a.bounds};for(const i of w){const u=d.get(i.ownerId),m=u.footerBounds,b=i.y-u.plot.height;h.left=Math.min(h.left,i.x+m.left),h.right=Math.max(h.right,i.x+m.right),h.top=Math.min(h.top,b+m.top),h.bottom=Math.max(h.bottom,b+m.bottom)}const y=M({...c,spacing:"canvas",topLane:void 0,children:[{id:"packed",width:h.right-h.left,height:h.bottom-h.top}],columns:1,gap:0,headerLayout:{outer:s.headerLayout.outer},plots:void 0}),S=y.children[0].x-h.left,k=y.children[0].y-h.top;return{...y,spacing:"content",columns:a.columns,rows:Math.ceil(a.children.length/a.columns),gap:n.gap,children:a.children.map(i=>{const u=d.get(i.id),b=e.children[i.id].graphicSpec.objects.canvas.properties,L=v.get(i.id);return{id:i.id,row:i.row,column:i.column,x:S+i.x-u.plot.x,y:k+i.y-u.plot.y,width:b.width,height:b.height,...L===void 0?{}:{footerShift:L.y-i.y-u.plot.height}}})}}function te(e,c){if(e._assertCompositionProgram("resolveFacetProgramLayout"),e.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");c??=z(e);const s=B(e),n=e.compositionSpec,f=Q(e,Y(e).headers),l=new Map((n.facet.grid?.cells??[]).map(d=>[d.id,d])),o={...n.padding};s.config&&s.config.position!=="top"&&(o[s.config.position]+=s.reservation);const r={children:n.children.map((d,g)=>({...Z(d,e.children[d]),value:n.facet.values[g],...l.has(d)?{row:l.get(d).row,column:l.get(d).column}:{}})),spacing:n.spacing,topLane:n.topLane,plots:n.children.map(d=>({id:d,...F(e.children[d])})),columns:n.columns,gap:n.gap,align:n.align,padding:o,titleHeight:s.height,headerLayout:f.headerLayout,sharedLegend:n.facet.guides.legend==="shared"&&c?.mode!=="promoted",...c===void 0?{}:{sharedLegendGap:c.reservation.gap,sharedLegendWidth:c.reservation.width,sharedLegendHeight:c.reservation.height,sharedLegendPosition:c.reservation.position}};let t=n.spacing==="content"?ee(e,r,f):M(r);const p=n.children.map(d=>({id:d,...F(e.children[d])}));let a=q({placements:t.children,plots:p});return{layout:t,plot:a}=W(e,{layout:t,plot:a,plots:p,title:s,preparedHeaders:f,prepared:c}),{layout:t,title:s,plot:a,plots:p,preparedHeaders:f}}function oe(e,c,s){const n=[],f=c.children.map((o,r)=>({id:o.id,left:o.x+s[r].x,right:o.x+s[r].x+s[r].width,top:o.y+s[r].y,bottom:o.y+s[r].y+s[r].height}));for(const o of c.children){const r=U(e,o.id);for(const t of r){const p=j(`${e.compositionSpec.id}-${o.id}`,t);if(e.graphicSpec.objects[p]===void 0)continue;const a=I(e.graphicSpec,p,e.materializationConfigs.textMetrics);a!==void 0&&n.push({owner:o.id,bounds:a})}}const l=e.graphicSpec.objects[C(e.compositionSpec.id)];for(const o of l?.items??[])o.properties.text!==""&&n.push({owner:"headers",bounds:G(o.properties,e.materializationConfigs.textMetrics)});for(let o=0;o<n.length;o+=1){const r=n[o];if(f.some(t=>t.id!==r.owner&&x(r.bounds,t))||n.slice(0,o).some(t=>t.owner!==r.owner&&x(r.bounds,t.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function ie(e,c){if(c.titles.length===0)return;const s=C(e.compositionSpec.id),n=e.materializationConfigs.textMetrics,f=e.graphicSpec.objects[s].items.slice(c.descriptors.length).map(o=>G(o.properties,n)),l=o=>{if((o.ownerId??o.id)===s)return;const r=o.object.type??o.owner?.type,t=o.object.properties;if(r!=="text"||t?.text===""||t?.opacity===0)return;const p=I(e.graphicSpec,o.id,n);if(p!==void 0&&f.some(a=>x(a,p)))throw new Error("Facet field title overlaps retained chart text.")};E(e.graphicSpec,{enter:l,item:l})}function ve(e){e=T(e);const c=z(e),{layout:s,title:n,plot:f,plots:l,preparedHeaders:o}=te(e,c),r=e.graphicSpec.objects.canvas?.properties?.background??"white";let t=N(e);t.graphicSpec.objects.canvas===void 0&&(t=t.createGraphics({id:"canvas",type:"canvas"}));for(const[p,a]of Object.entries({width:s.width,height:s.height,background:r}))t=t.editGraphics({target:"canvas",property:p,value:a});for(const p of s.children){const a=e.children[p.id],d=R(a.graphicSpec,{namespace:`${e.compositionSpec.id}-${p.id}`,x:p.x,y:p.y});if(t=A(t,d,d.order[0],"canvas"),["plot","content"].includes(s.spacing)){const g=d.order[0],w=j(`${e.compositionSpec.id}-${p.id}`,"plot-background"),v=F(a),h=t.graphicSpec.objects[g].children?.[0];t=t.editGraphics({target:g,property:"background",value:"transparent"}).createGraphics({id:w,type:"rect",parent:g,...h===void 0?{}:{before:h}});for(const[y,S]of Object.entries({...v,stroke:"none",strokeWidth:0,fill:a.graphicSpec.objects.canvas.properties.background??"transparent"}))t=t.editGraphics({target:w,property:y,value:S})}}return t=J(t,s,l,o),t=t.composeFacetGuides({layout:s,plot:f}),s.spacing==="plot"&&oe(t,s,l),n.config&&(t=P(t,f,n)),ie(t,o),D(t,{layout:s,plots:l,headerId:C(e.compositionSpec.id),titleIds:X}),O(t)._withCanvasConfig({margin:V,size:{width:"auto",height:"auto"}})}export{ve as materializeFacetGraphics,Fe as measureFacetContentChildren,te as resolveFacetProgramLayout};
@@ -1 +1 @@
1
- import{resetLegendClipGraphics as p,hasLegendClipGraphics as u}from"../legendClip.js";import{localLegendPosition as m}from"../legendGroups.js";import{needsLegendAxisReflow as h,needsSideLegendAxisReflow as x}from"./resources.js";import{getErrorDetails as v}from"../../core/diagnostics.js";import{planCanvasRematerialization as C}from"../layout.js";import{applyMaterializationPlan as L}from"../planner.js";import{assertGuideCollisionBlocks as G}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as w}from"./resources.js";import{hasPlotUnderlays as g}from"../plotDrawOrder.js";import{resolveGuideCollisionBlocks as M}from"./resources.js";function _(i,l,a){const t=a(i._withContext({pendingRelativeTitleAxis:l})),{pendingRelativeTitleAxis:e,...s}=t.context;return Object.hasOwn(i.context,"pendingRelativeTitleAxis")&&(s.pendingRelativeTitleAxis=i.context.pendingRelativeTitleAxis),t._clone({context:s})}function y(i,l){if(i.context.deferGuideLayoutValidation===!0)return l(i);const a=i.materializationConfigs.canvas?.plot;let t=i.materializationConfigs.canvas?.margin,e;for(let f=0;;f+=1)try{let n=p(i)._withContext({deferGuideLayoutValidation:!0});f>0&&(n=n.editGraphics({target:"canvas",property:"width",value:a.width+t.left+t.right}).editGraphics({target:"canvas",property:"height",value:a.height+t.top+t.bottom})._withCanvasConfig({...n.materializationConfigs.canvas,margin:t}),n=L(n,C(n))),e=l(n),u(i)&&!u(e)&&Object.values(e.guideConfigs.legend??{}).some(o=>o.clipHeight!==void 0)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&x(i,e)&&(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&&h(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&(Object.keys(e.guideConfigs.legendGroups??{}).length>0||Object.values(e.guideConfigs.legend??{}).some(o=>m(o)!==void 0))&&(e=e.rematerializeLegendGroups()),a!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle()),(g(i)||g(e))&&(e=e.rematerializePlotDrawOrder());break}catch(n){const o=v(n)?.canvasOverflow;if(a===void 0||o===void 0||f>=16)throw n;t=Object.fromEntries(Object.keys(t).map(d=>[d,o[d]>0?Math.ceil((t[d]+o[d])*4)/4:t[d]]))}const{deferGuideLayoutValidation:s,...c}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(c.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const r=e._clone({context:c});return G(w(r.graphicSpec,r.guideConfigs,r.titleConfig,r.materializationConfigs.textMetrics)),r}function k(i){return function(l){return y(this,a=>i.call(a,l))}}export{M as resolveGuideCollisionBlocks,y as withGuideLayoutTransaction,k as withGuideLayoutValidation,_ as withPendingRelativeAxisTitle};
1
+ import{restoreGuideVisibilityPaint as x,applyGuideVisibilityPaint as C}from"./visibility.js";import{restoreLegendItemsOffset as v,applyLegendItemsOffset as L}from"../legendItemsOffset.js";import{resetLegendClipGraphics as G,hasLegendClipGraphics as p}from"../legendClip.js";import{localLegendPosition as y}from"../legendGroups.js";import{needsLegendAxisReflow as w,needsSideLegendAxisReflow as O}from"./resources.js";import{getErrorDetails as b}from"../../core/diagnostics.js";import{planCanvasRematerialization as z}from"../layout.js";import{applyMaterializationPlan as j}from"../planner.js";import{assertGuideCollisionBlocks as R}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as V}from"./resources.js";import{hasPlotUnderlays as m}from"../plotDrawOrder.js";import{resolveGuideCollisionBlocks as K}from"./resources.js";function q(i,r,o){const t=o(i._withContext({pendingRelativeTitleAxis:r})),{pendingRelativeTitleAxis:e,...f}=t.context;return Object.hasOwn(i.context,"pendingRelativeTitleAxis")&&(f.pendingRelativeTitleAxis=i.context.pendingRelativeTitleAxis),t._clone({context:f})}function A(i,r){if(i.context.deferGuideLayoutValidation===!0)return r(i);const o=i.materializationConfigs.canvas?.plot;let t=i.materializationConfigs.canvas?.margin,e;for(let d=0;;d+=1)try{let n=G(v(x(i)))._withContext({deferGuideLayoutValidation:!0});d>0&&(n=n.editGraphics({target:"canvas",property:"width",value:o.width+t.left+t.right}).editGraphics({target:"canvas",property:"height",value:o.height+t.top+t.bottom})._withCanvasConfig({...n.materializationConfigs.canvas,margin:t}),n=j(n,z(n))),e=r(n),p(i)&&!p(e)&&Object.values(e.guideConfigs.legend??{}).some(l=>l.clipHeight!==void 0)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&O(i,e)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&Object.values(e.guideConfigs.legend??{}).some(l=>["left","right"].includes(l.position))&&(e=e.rematerializeSideLegendLane()),e.compositionSpec===void 0&&w(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&(Object.keys(e.guideConfigs.legendGroups??{}).length>0||Object.values(e.guideConfigs.legend??{}).some(l=>y(l)!==void 0))&&(e=e.rematerializeLegendGroups()),o!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle()),(m(i)||m(e))&&(e=e.rematerializePlotDrawOrder()),e=L(e);break}catch(n){const l=b(n),c=l?.canvasOverflow;if(o===void 0||c===void 0||l.requiresExplicitCanvas||d>=16)throw n;t=Object.fromEntries(Object.keys(t).map(s=>[s,c[s]>0?Math.ceil((t[s]+c[s])*4)/4:t[s]]))}const{deferGuideLayoutValidation:f,...u}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(u.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const a=e._clone({context:u}),g=a.context.compositionCollectedLegends,h=g?{...a.guideConfigs,legend:Object.fromEntries(Object.entries(a.guideConfigs.legend??{}).filter(([d])=>!g.includes(d)))}:a.guideConfigs;return R(V(a.graphicSpec,h,a.titleConfig,a.materializationConfigs.textMetrics)),C(a)}function U(i){return function(r){return A(this,o=>i.call(o,r))}}export{K as resolveGuideCollisionBlocks,A as withGuideLayoutTransaction,U as withGuideLayoutValidation,q as withPendingRelativeAxisTitle};
@@ -1 +1 @@
1
- import{axisGraphicIds as k,selectCartesianAxis as v}from"../../selectors/axes.js";import{localLegendPosition as z,localLegendPlacement as T}from"../legendGroups.js";import{resolveConcreteGraphicBounds as B,unionConcreteGraphicBounds as y}from"../../grammar/schemas/graphicBounds.js";function W(e,i,n){const o=e.context.pendingMaterializationPlan;if(o===void 0)return!1;const t=`edit${i.toUpperCase()}AxisTitle`;return o.steps.slice(o.next).some(r=>r.op===t&&v(e,i,r.args).id===n)}function K(e,i,n,o){const t=[],r=(s,a,u,l)=>{if(u===void 0)return;const b=l.filter(O=>e.objects[O]!==void 0),m=y(e,b,o);m!==void 0&&t.push({id:s,kind:a,position:u,bounds:m})};r("chart title","title",n?.position,["chartTitle","chartSubtitle"]);for(const[s,a]of Object.entries(i.axis??{})){const u=["line","ticks","labels","title"].map(b=>a[b]?.position).find(Boolean),l=["x","y"].includes(s)?s:["top","bottom"].includes(u)?"x":["left","right"].includes(u)?"y":void 0;if(l!==void 0)for(const[b,m]of["line","ticks","labels","title"].entries()){const O=k(l,s)[b],I=t.length;if(r(`${s}-axis ${m}`,"axis",a[m]?.position,[O]),t.length===I)continue;const h=t.at(-1);if(h.axisId=s,h.namedAxis=s!==l,["labels","title"].includes(m)){const G=e.objects[O];h.textBounds=G.items?.map(C=>B(e,C.id,o))??[h.bounds]}}}const c=w(),d=i.legend??{},f=c.find(s=>s.family==="categorical"&&d[s.kind]!==void 0),g=f!==void 0&&d.size!==void 0&&d[f.kind].target===d.size.target&&z(d[f.kind])===void 0&&z(d.size)===void 0&&(d.size.inheritPosition!==!1||d[f.kind].position===d.size.position),p=i.legendGroups??{};for(const[s,a]of Object.entries(p)){const u=c.filter(l=>d[l.kind]?.target===s&&z(d[l.kind])===void 0).flatMap(l=>l.graphicIds);r(`${s} legend group`,"legend",a.placement.position??"free",u)}for(const s of c){const a=d[s.kind];if(p[a?.target]!==void 0&&z(a)===void 0||a===void 0||g&&s.kind==="size")continue;const u=g&&s===f?[...s.graphicIds,...c.find(l=>l.kind==="size").graphicIds]:s.graphicIds;r(`${s.kind}${g&&s===f?"+size":""} legend`,"legend",z(a)??a.position,u)}return t}const L=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(L.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.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 P(e){return $(e).graphicIds}function $(e){const i=j[e];if(i===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return i}function w(){return Object.entries(j).map(([e,i])=>({kind:e,...i}))}function M(e){return[...new Set(e.flatMap(P))]}function x(e){const i=K(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(n=>n.kind==="axis");for(const n of["theta","radius"]){if(e.guideConfigs.axis?.[n]===void 0)continue;const o=k(n).slice(2).filter(r=>e.graphicSpec.objects[r]!==void 0),t=y(e.graphicSpec,o,e.materializationConfigs.textMetrics);if(t!==void 0)for(const r of["left","right","top","bottom"])i.push({id:`${n}-axis text`,kind:"axis",position:r,bounds:t})}return i}function N(e,i,n){if(i.inferredOffset!==!0||!["top","bottom","left","right"].includes(i.position)||i.layout==="legacy-bottom")return i.offset;const o=x(e).filter(r=>r.position===i.position),t=Math.max(0,...o.map(r=>({bottom:r.bounds.bottom-n.y-n.height,top:n.y-r.bounds.top,left:n.x-r.bounds.left,right:r.bounds.right-n.x-n.width})[i.position]));return i.offset+t}function S(e,i,n){if(i!=="size"||n.inheritPosition===!1)return n;const o=["series","color","stroke"].map(t=>e.guideConfigs.legend?.[t]).find(t=>t?.target===n.target);return o===void 0?n:{...n,position:o.position,offset:o.offset,inferredOffset:o.inferredOffset}}function U(e,i){return[e,i].some(o=>Object.entries(o.guideConfigs.legend??{}).some(([t,r])=>{const c=S(o,t,r);return c.inferredOffset===!0&&["top","bottom"].includes(c.position)&&c.layout!=="legacy-bottom"})||Object.values(o.guideConfigs.legendGroups??{}).some(t=>t.inferredOffset===!0&&["top","bottom"].includes(t.placement.position)))&&[e,i].some(o=>x(o).some(t=>["top","bottom"].includes(t.position)))}function _(e,i){const n=new Set;for(const t of[e,i])for(const[r,c]of Object.entries(t.guideConfigs.legend??{})){const d=T(c),f=d===void 0?t.guideConfigs.legendGroups?.[c.target]:void 0,g=S(t,r,c),p=d??f?.placement??g;(d?.inferredOffset??f?.inferredOffset??g.inferredOffset)===!0&&p.at===void 0&&["left","right"].includes(p.position)&&g.layout!=="legacy-bottom"&&n.add(p.position)}const o=t=>JSON.stringify(x(t).filter(r=>n.has(r.position)));return n.size>0&&o(e)!==o(i)}export{M as allLegendGraphicIds,k as axisGraphicIds,S as effectiveLegendOffsetConfig,W as hasPendingAxisTitleRematerialization,P as legendGraphicIds,w as legendResourcePolicies,$ as legendResourcePolicy,U as needsLegendAxisReflow,_ as needsSideLegendAxisReflow,K as resolveGuideCollisionBlocks,N as resolveLegendAxisOffset};
1
+ import{axisGraphicIds as k,selectCartesianAxis as v}from"../../selectors/axes.js";import{localLegendPosition as z,localLegendPlacement as C}from"../legendGroups.js";import{resolveConcreteGraphicBounds as T,unionConcreteGraphicBounds as B}from"../../grammar/schemas/graphicBounds.js";function W(e,t,r){const d=e.context.pendingMaterializationPlan;if(d===void 0)return!1;const i=`edit${t.toUpperCase()}AxisTitle`;return d.steps.slice(d.next).some(n=>n.op===i&&v(e,t,n.args).id===r)}function K(e,t,r,d){const i=[],n=(o,c,u,l)=>{if(u===void 0)return;const b=l.filter(O=>e.objects[O]!==void 0),m=B(e,b,d);m!==void 0&&i.push({id:o,kind:c,position:u,bounds:m})};n("chart title","title",r?.position,["chartTitle","chartSubtitle"]);for(const[o,c]of Object.entries(t.axis??{})){const u=["line","ticks","labels","title"].map(b=>c[b]?.position).find(Boolean),l=["x","y"].includes(o)?o:["top","bottom"].includes(u)?"x":["left","right"].includes(u)?"y":void 0;if(l!==void 0)for(const[b,m]of["line","ticks","labels","title"].entries()){const O=k(l,o)[b],I=i.length;if(n(`${o}-axis ${m}`,"axis",c[m]?.position,[O]),i.length===I)continue;const h=i.at(-1);if(h.axisId=o,h.namedAxis=o!==l,["labels","title"].includes(m)){const S=e.objects[O];h.textBounds=S.items?.map(G=>T(e,G.id,d))??[h.bounds]}}}for(const o of["parallel","theta","radius"])if(t.axis?.[o]!==void 0)for(const c of["left","right","top","bottom"])n(`${o}-axis text`,"axis",c,k(o).slice(2));const a=w(),s=t.legend??{},f=a.find(o=>o.family==="categorical"&&s[o.kind]!==void 0),g=f!==void 0&&s.size!==void 0&&s[f.kind].target===s.size.target&&z(s[f.kind])===void 0&&z(s.size)===void 0&&(s.size.inheritPosition!==!1||s[f.kind].position===s.size.position),p=t.legendGroups??{};for(const[o,c]of Object.entries(p)){const u=a.filter(l=>s[l.kind]?.target===o&&z(s[l.kind])===void 0).flatMap(l=>l.graphicIds);n(`${o} legend group`,"legend",c.placement.position??"free",u)}for(const o of a){const c=s[o.kind];if(p[c?.target]!==void 0&&z(c)===void 0||c===void 0||g&&o.kind==="size")continue;const u=g&&o===f?[...o.graphicIds,...a.find(l=>l.kind==="size").graphicIds]:o.graphicIds;n(`${o.kind}${g&&o===f?"+size":""} legend`,"legend",z(c)??c.position,u)}return i}const L=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(L.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.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 P(e){return $(e).graphicIds}function $(e){const t=j[e];if(t===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return t}function w(){return Object.entries(j).map(([e,t])=>({kind:e,...t}))}function M(e){return[...new Set(e.flatMap(P))]}function y(e){return K(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(t=>t.kind==="axis")}function N(e,t,r){if(t.inferredOffset!==!0||!["top","bottom","left","right"].includes(t.position)||t.layout==="legacy-bottom")return t.offset;const d=y(e).filter(n=>n.position===t.position),i=Math.max(0,...d.map(n=>({bottom:n.bounds.bottom-r.y-r.height,top:r.y-n.bounds.top,left:r.x-n.bounds.left,right:n.bounds.right-r.x-r.width})[t.position]));return t.offset+i}function x(e,t,r){if(t!=="size"||r.inheritPosition===!1)return r;const d=["series","color","stroke"].map(i=>e.guideConfigs.legend?.[i]).find(i=>i?.target===r.target);return d===void 0?r:{...r,position:d.position,offset:d.offset,inferredOffset:d.inferredOffset}}function U(e,t){const r=Object.entries(t.guideConfigs.legend??{}).some(([i,n])=>{const a=x(t,i,n);return a.inferredOffset===!1&&e.guideConfigs.legend?.[i]?.inferredOffset===!0&&["top","bottom"].includes(a.position)&&a.layout!=="legacy-bottom"});return([e,t].some(i=>Object.entries(i.guideConfigs.legend??{}).some(([n,a])=>{const s=x(i,n,a);return s.inferredOffset===!0&&["top","bottom"].includes(s.position)&&s.layout!=="legacy-bottom"})||Object.values(i.guideConfigs.legendGroups??{}).some(n=>n.inferredOffset===!0&&["top","bottom"].includes(n.placement.position)))||r)&&[e,t].some(i=>y(i).some(n=>["top","bottom"].includes(n.position)))}function _(e,t){const r=new Set;for(const i of[e,t])for(const[n,a]of Object.entries(i.guideConfigs.legend??{})){const s=C(a),f=s===void 0?i.guideConfigs.legendGroups?.[a.target]:void 0,g=x(i,n,a),p=s??f?.placement??g;(s?.inferredOffset??f?.inferredOffset??g.inferredOffset)===!0&&p.at===void 0&&["left","right"].includes(p.position)&&g.layout!=="legacy-bottom"&&r.add(p.position)}const d=i=>JSON.stringify(y(i).filter(n=>r.has(n.position)));return r.size>0&&d(e)!==d(t)}export{M as allLegendGraphicIds,k as axisGraphicIds,x as effectiveLegendOffsetConfig,W as hasPendingAxisTitleRematerialization,P as legendGraphicIds,w as legendResourcePolicies,$ as legendResourcePolicy,U as needsLegendAxisReflow,_ as needsSideLegendAxisReflow,K as resolveGuideCollisionBlocks,N as resolveLegendAxisOffset};