ggaction 0.0.18 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +1 -1
  7. package/src/actions/canvas/fitting.js +6 -2
  8. package/src/actions/charts/heatmap.js +4 -3
  9. package/src/actions/data/derived.js +11 -1
  10. package/src/actions/data/edit.js +2 -0
  11. package/src/actions/data/index.js +4 -0
  12. package/src/actions/data/regression.js +24 -16
  13. package/src/actions/data/regressionStatistics.js +73 -0
  14. package/src/actions/data/shared.js +5 -2
  15. package/src/actions/distributions/revision.js +24 -18
  16. package/src/actions/encodings/remove.js +11 -6
  17. package/src/actions/encodings/shared.js +26 -24
  18. package/src/actions/facets/actions.js +18 -7
  19. package/src/actions/facets/guides.js +2 -1
  20. package/src/actions/guides/axes/axes.js +30 -11
  21. package/src/actions/guides/axes/axis.js +58 -8
  22. package/src/actions/guides/axes/components.js +13 -13
  23. package/src/actions/guides/axes/edit.js +36 -24
  24. package/src/actions/guides/axes/labels.js +37 -27
  25. package/src/actions/guides/axes/lines.js +22 -20
  26. package/src/actions/guides/axes/remove.js +10 -6
  27. package/src/actions/guides/axes/tickGroups.js +8 -7
  28. package/src/actions/guides/axes/ticks.js +31 -22
  29. package/src/actions/guides/axes/titles.js +53 -60
  30. package/src/actions/guides/facadeAxes.js +33 -24
  31. package/src/actions/guides/facadeLegend.js +3 -3
  32. package/src/actions/guides/grids/grid.js +1 -1
  33. package/src/actions/guides/grids/resolve.js +48 -51
  34. package/src/actions/guides/legends/blocks.js +43 -19
  35. package/src/actions/guides/legends/categorical/actions.js +30 -7
  36. package/src/actions/guides/legends/categorical/components.js +3 -10
  37. package/src/actions/guides/legends/categorical/index.js +2 -0
  38. package/src/actions/guides/legends/categorical/layout.js +9 -13
  39. package/src/actions/guides/legends/categorical/options.js +8 -2
  40. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  41. package/src/actions/guides/legends/categorical/resolve.js +9 -2
  42. package/src/actions/guides/legends/categorical/symbols.js +22 -8
  43. package/src/actions/guides/legends/colorSize.js +110 -0
  44. package/src/actions/guides/legends/combine.js +37 -0
  45. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -1
  46. package/src/actions/guides/legends/continuous/common.js +1 -1
  47. package/src/actions/guides/legends/continuous/gradient.js +27 -13
  48. package/src/actions/guides/legends/continuous/opacity.js +16 -11
  49. package/src/actions/guides/legends/creation.js +2 -0
  50. package/src/actions/guides/legends/edit.js +1 -1
  51. package/src/actions/guides/legends/focused.js +6 -5
  52. package/src/actions/guides/legends/lane.js +1 -1
  53. package/src/actions/guides/legends/lifecycle.js +2 -1
  54. package/src/actions/guides/legends/remove.js +6 -3
  55. package/src/actions/guides/legends/size.js +16 -6
  56. package/src/actions/guides/legends/strokeWidth.js +2 -0
  57. package/src/actions/guides/legends/target.js +2 -79
  58. package/src/actions/guides/legends/transition.js +24 -5
  59. package/src/actions/guides/tickValues.js +8 -5
  60. package/src/actions/marks/bar/edit.js +23 -12
  61. package/src/actions/marks/bar/materialize.js +6 -1
  62. package/src/actions/marks/point/materialize.js +5 -1
  63. package/src/actions/marks/remove.js +10 -10
  64. package/src/actions/primitives/createGraphics.js +3 -2
  65. package/src/actions/primitives/editGraphics.js +15 -11
  66. package/src/actions/primitives/graphicProperties.js +1 -6
  67. package/src/actions/primitives/semanticAction.js +27 -1
  68. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  69. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  70. package/src/actions/regression/edit.js +15 -3
  71. package/src/actions/scales/channels.js +2 -2
  72. package/src/actions/scales/create.js +3 -3
  73. package/src/actions/scales/definitions.js +1 -1
  74. package/src/actions/scales/edit.js +1 -1
  75. package/src/actions/scales/editPolicy.js +3 -0
  76. package/src/actions/scales/patch.js +1 -1
  77. package/src/actions/scales/quantitativeColor.js +8 -3
  78. package/src/actions/theme/reconcile.js +50 -23
  79. package/src/core/compositionState.js +2 -2
  80. package/src/core/graphicProperties.js +1 -0
  81. package/src/core/programState.js +4 -0
  82. package/src/core/resourceReferences.js +2 -2
  83. package/src/core/validation.js +9 -0
  84. package/src/core/vocabulary.js +3 -0
  85. package/src/grammar/axisTextPlacement.js +1 -0
  86. package/src/grammar/colorSizeLegend.js +39 -0
  87. package/src/grammar/computed.js +23 -1
  88. package/src/grammar/datasetSchema.js +10 -2
  89. package/src/grammar/displayLabels.js +8 -0
  90. package/src/grammar/facets/guides.js +54 -31
  91. package/src/grammar/facets/legendIntent.js +65 -0
  92. package/src/grammar/guideDash.js +1 -0
  93. package/src/grammar/legendBlocks.js +1 -0
  94. package/src/grammar/legendBorder.js +9 -0
  95. package/src/grammar/legendClip.js +19 -0
  96. package/src/grammar/legendCombinations.js +45 -0
  97. package/src/grammar/legendGradient.js +9 -0
  98. package/src/grammar/legendGroups.js +1 -1
  99. package/src/grammar/legendOpacity.js +6 -0
  100. package/src/grammar/pointShapes.js +16 -2
  101. package/src/grammar/regression/derive.js +9 -1
  102. package/src/grammar/regression/models.js +60 -0
  103. package/src/grammar/regression/parameters.js +9 -1
  104. package/src/grammar/regression/statistics.js +64 -0
  105. package/src/grammar/scales/color.js +1 -1
  106. package/src/grammar/scales/colorConsumers.js +1 -1
  107. package/src/grammar/scales/definition.js +1 -1
  108. package/src/grammar/scales/types.js +1 -1
  109. package/src/grammar/schemas/concreteGraphic.js +3 -0
  110. package/src/grammar/schemas/graphic.js +3 -0
  111. package/src/grammar/schemas/graphicBounds.js +31 -7
  112. package/src/grammar/schemas/semanticPath.js +17 -2
  113. package/src/grammar/summary.js +1 -1
  114. package/src/grammar/timeUnit.js +6 -4
  115. package/src/grammar/transformTopology.js +1 -0
  116. package/src/grammar/transforms.js +11 -1
  117. package/src/inspectionDescriptors.js +1 -1
  118. package/src/layout/guideCollisions.js +9 -1
  119. package/src/layout/legendGroup.js +1 -1
  120. package/src/materialization/compositionSnapshot.js +2 -7
  121. package/src/materialization/coordinateBounds.js +17 -4
  122. package/src/materialization/facetGuides/edit.js +30 -0
  123. package/src/materialization/facetGuides/layout.js +1 -1
  124. package/src/materialization/facetGuides/owner.js +125 -0
  125. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  126. package/src/materialization/facetGuides/placement.js +52 -6
  127. package/src/materialization/facetGuides/preparation.js +7 -0
  128. package/src/materialization/facets.js +1 -1
  129. package/src/materialization/graphicHierarchy.js +5 -1
  130. package/src/materialization/graphicTranslation.js +73 -0
  131. package/src/materialization/guides/layout.js +1 -1
  132. package/src/materialization/guides/resources.js +1 -1
  133. package/src/materialization/legendBorder.js +28 -0
  134. package/src/materialization/legendClip.js +76 -0
  135. package/src/materialization/legendGroups.js +1 -1
  136. package/src/materialization/legends.js +1 -1
  137. package/src/materialization/scaleGuideDependencies.js +13 -14
  138. package/src/materialization/scales/map.js +3 -0
  139. package/src/materialization/scales/resolve.js +2 -0
  140. package/src/materialization/typography.js +10 -2
  141. package/src/persistence/validation.js +174 -1
  142. package/src/persistence.js +1 -1
  143. package/src/renderers/canvas/index.js +20 -10
  144. package/src/renderers/canvas/isolated.js +166 -0
  145. package/src/renderers/canvas/limits.js +3 -0
  146. package/src/renderers/canvas/rect.js +1 -1
  147. package/src/renderers/pdf.js +2 -2
  148. package/src/renderers/png.js +2 -2
  149. package/src/renderers/svg.js +10 -3
  150. package/src/selectors/axes.js +1 -0
  151. package/src/selectors/graphicCapacity.js +38 -0
  152. package/src/version.js +1 -1
  153. package/types/basic.d.ts +2 -0
  154. package/types/index.d.ts +5 -0
  155. package/types/program.d.ts +206 -61
@@ -1,3 +1,4 @@
1
+ import {resolveColorSizeConfig} from "./colorSize.js";
1
2
  import { resolveCategoricalLegendConfig } from "./categorical/actions.js";
2
3
  import { resolveGradientLegendCreation } from "./continuous/gradient.js";
3
4
  import { resolveOpacityLegendCreation } from "./continuous/opacity.js";
@@ -11,6 +12,7 @@ import {
11
12
 
12
13
  export function resolveLegendStepConfig(program, step) {
13
14
  switch (step.op) {
15
+ case "createColorSizeLegend": return {kind:"colorSize",config:resolveColorSizeConfig(program,step.args)};
14
16
  case "createCategoricalLegend": {
15
17
  const config = resolveCategoricalLegendConfig(program, step.args);
16
18
  return { kind: config.kind, config };
@@ -1 +1 @@
1
- import{mergeLegendTitleStyle as G}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as X}from"./group.js";import{prepareLegendGroup as v}from"./group.js";import{PLOT_LEGEND_POSITIONS as $}from"../../../layout/legend.js";import{withGuideLayoutValidation as ee}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as te}from"./creation.js";import{normalizeLegendOrder as j}from"../../../grammar/categoryOrder.js";import{resolveDefinition as ne}from"./categorical/resolve.js";import{action as oe}from"../../../core/action.js";import{validateOptionObject as x}from"../../../core/validation.js";import{normalizeLegendOverflow as V,normalizeOptions as ie}from"./categorical/options.js";import{resolveLegendSymbol as le}from"./categorical/recipes.js";import{resolveLayout as re}from"./categorical/layout.js";import{resolveLegendCreationPlan as de}from"./categorical/actions.js";import{categoricalSymbolIds as ce,reconcileCategoricalSymbols as se,createCategoricalLegendFromConfig as fe,resolveCategoricalLegendRevision as ae,removeLegendKinds as W}from"./lifecycle.js";import{normalizeColorSymbols as ue}from"./continuous/colorSymbols.js";import{readLegendSampling as me}from"./sampling.js";import{createGradientLegendFromConfig as M}from"./continuous/gradient.js";import{normalizeLegendGradient as B}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as ge,normalizeLegendBorder as ye,normalizeLegendTextOptions as q,normalizeLegendTitleOptions as D,normalizeContinuousLegend as A}from"./continuous/common.js";import{normalizeOpacitySymbol as be,createOpacityLegendFromConfig as pe}from"./continuous/opacity.js";import{normalizeIntervalLegend as Le,resolveIntervalConfig as Se,resolveIntervalLayout as he,createIntervalLegendFromConfig as we}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as ke,createStrokeIntervalLegendFromConfig as ze,resolveStrokeIntervalConfig as Ee,resolveStrokeIntervalLayout as Ce}from"./continuous/stroke.js";import{findLayer as z}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as Ge}from"../../../materialization/graphicHierarchy.js";import{resolveLegendTarget as Oe,validateLegendChannels as _e}from"./target.js";import{isEnumeratedSizeScaleType as F}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as K,SIZE_LEGEND_TITLE_STYLE as N,resolveSizeLegendLayout as Te,createSizeLegendFromConfig as Ie}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as Pe,STROKE_WIDTH_LEGEND_TITLE_STYLE as $e,resolveStrokeWidthLegendLayout as je,createStrokeWidthLegendFromConfig as xe}from"./strokeWidth.js";import{normalizeLegendSampling as O}from"./sampling.js";import{planLegendBlockTransitions as Ve}from"./transition.js";const We=Object.freeze(["target","channels","position","layout","align","direction","columns","offset","titlePosition","title","symbol","labels","titleStyle","itemGap","border","count","values","gradient","representation","order","overflow","blockLayout","at"]);function E(o,t){return t===void 0?o:{...o,...t}}function C(o,t){return t===void 0?o:t===!1||t===!0||o===!1?t:{...o,...t}}function k(o,t,e,n){const l=o.graphicSpec.objects[t]!==void 0;return l&&!e?o.editGraphics({target:t,remove:!0}):!l&&e?o.createGraphics({id:t,...n,...Ge(o,{...n.before===void 0?{}:{before:n.before},...n.after===void 0?{}:{after:n.after}})}):o}function Me(o,t,e){const n=e.representation??t.representation??"gradient";if(!["gradient","symbols"].includes(n))throw new Error("Legend representation must be gradient or symbols.");const l=["target","representation","position","align","offset","title","labels","titleStyle","titlePosition","border","count",...n==="symbols"?["symbol","values","itemGap","layout","direction","columns"]:["gradient"]];for(const y of Object.keys(e))if(!l.includes(y))throw new Error(`Color ${n} legend does not accept ${y}.`);const d=(t.representation??"gradient")!==n,i=e.title,m=i==="auto"?z(o,t.target)?.encoding?.color?.field:typeof i=="string"?i:t.title,s={target:t.target,position:e.position??t.position,align:e.align??t.align,offset:e.offset??t.offset,title:m,labels:E(t.labels,e.labels),titleStyle:G(t.titleStyle,e.titleStyle,{edit:!0}),border:C(t.border,e.border),titlePosition:e.titlePosition??(d&&n==="gradient"?"top":t.titlePosition)};let u;if(n==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&x(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const y=d?{count:t.count}:{};for(const w of["values","count"])Object.hasOwn(e,w)&&(y[w]=e[w]);Object.hasOwn(e,"values")&&delete y.count,Object.hasOwn(e,"count")&&(y.count=e.count),u=ue({...s,...y,layout:e.layout??(d?void 0:t.layout),direction:e.direction??(d||e.position!==void 0&&e.position!==t.position?void 0:t.direction),columns:e.columns??(d?void 0:t.columns),itemGap:e.itemGap??(d?void 0:t.itemGap),symbol:e.symbol==="auto"?"auto":e.symbol===void 0?d?void 0:t.symbol:{...d?{}:t.symbol,...e.symbol}},d?{count:t.count}:t,"edit")}else{u=A({...s,count:e.count??(d?me(t).count:t.count)},"gradient");const y=B(e.gradient,d?void 0:t.gradient);u.gradient=y}const{representation:f,sampling:a,symbol:c,layout:b,direction:S,columns:h,gradient:r,count:p,...g}=t,L=d&&n==="gradient"&&t.blockOverrides!==void 0?Object.fromEntries(Object.entries(t.blockOverrides).map(([y,w])=>{const{symbol:qe,labelMap:De,...U}=w;return[y,U]})):t.blockOverrides;return{config:{...g,...u,...L===void 0?{}:{blockOverrides:L},inferredTitle:i==="auto"?!0:typeof i=="string"?!1:t.inferredTitle,titleVisible:i===!1?!1:i===void 0?t.titleVisible!==!1:!0},titleMode:i,title:m,titleVisible:i===!1?!1:i===void 0?t.titleVisible!==!1:!0}}function _(o,t,e,n){if(t==="gradient"&&(e.representation==="symbols"||n.representation!==void 0))return Me(o,e,n);const l=["gradient","strokeGradient"].includes(t),d=l?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","gradient","representation"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const w of Object.keys(n))if(!d.includes(w))throw new Error(`${t} legend does not accept ${w}.`);const i=n.title,m=i==="auto"?!0:typeof i=="string"?!1:e.inferredTitle,s=i===!1?!1:i===void 0?e.titleVisible!==!1:!0,u=z(o,e.target),f=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity",a=u?.encoding?.[f]?.field,c=i==="auto"?a:typeof i=="string"?i:e.title,b=n.titlePosition??e.titlePosition,S=t==="opacity"&&b==="left"&&e.titlePosition!=="left",h=E(e.labels,n.labels),r=l?void 0:O(n,{previous:e,operation:"edit",label:"Opacity legend"}),p=A({target:e.target,position:n.position??e.position,align:n.align??e.align,offset:n.offset??e.offset,...l?{count:n.count??e.count}:{},titlePosition:b,title:c,labels:S&&n.labels?.offset===void 0?{...h,offset:8}:h,titleStyle:G(e.titleStyle,n.titleStyle,{edit:!0}),border:C(e.border,n.border),...t==="opacity"?{itemGap:n.itemGap??(S?20:e.itemGap),symbol:n.symbol??e.symbol}:{}},l?"gradient":t),{count:g,...L}=e,y={...l?e:L,...p,...l?{}:{sampling:r},inferredTitle:m,titleVisible:s};if(l){const w=B(n.gradient,e.gradient);y.gradient=w}else y.symbol=be(n.symbol??e.symbol);return{config:y,titleMode:i,title:c,titleVisible:s}}function T(o,t,e,n){const{config:l,titleMode:d,title:i,titleVisible:m}=_(o,t,e,n);if(t==="gradient"&&(e.representation??"gradient")!==(l.representation??"gradient"))return M(W(o,[t]),l).rematerializeLegend();const s=t==="gradient"?"colorGradient":t==="strokeGradient"?"strokeGradient":"opacityLegend",u=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity";let f=o;return(d==="auto"||typeof d=="string")&&(f=f.editSemantic({property:`guide.legend.${u}.title`,value:i})),f=f._withLegendConfig(t,l),f=k(f,`${s}Background`,l.border!==!1,{type:"rect",before:["gradient","strokeGradient"].includes(t)?`${s}${l.representation==="symbols"?"Symbols":"Strips"}`:"opacityLegendSymbols"}),f=k(f,`${s}Title`,m,{type:"text"}),f.rematerializeLegend()}function R(o,t,e,n="color"){for(const c of Object.keys(e))if(!["target","position","align","direction","offset","title","symbol","labels","titleStyle","itemGap","border","layout","columns","titlePosition"].includes(c))throw new Error(`interval legend does not accept ${c}.`);const l=e.title,d=z(o,t.target),i=l==="auto"?!0:typeof l=="string"?!1:t.inferredTitle,m=l===!1?!1:l===void 0?t.titleVisible!==!1:!0,s=l==="auto"?d?.encoding?.[n]?.field:typeof l=="string"?l:t.title,u=Le({target:t.target,position:e.position??t.position,align:e.align??t.align,direction:e.direction??(e.position!==void 0&&e.position!==t.position?void 0:t.direction),layout:e.layout===void 0?t.layout:e.layout,columns:e.columns??t.columns,titlePosition:e.titlePosition??t.titlePosition,offset:e.offset??t.offset,title:s,symbol:E(t.symbol,e.symbol),labels:E(t.labels,e.labels),titleStyle:G(t.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??t.itemGap,border:C(t.border,e.border)}),f={...t,...u,inferredTitle:i,titleVisible:m,...n==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},a=n==="stroke"?Ee(o,f):Se(o,f);return(n==="stroke"?Ce:he)(o,a.config,a.scale),{normalized:u,config:f,titleMode:l,title:s,titleVisible:m}}function I(o,t,e,n="color"){const l=n==="stroke"?"strokeInterval":"interval",d=n==="stroke"?"strokeInterval":"colorLegend",{normalized:i,config:m,titleMode:s,title:u,titleVisible:f}=R(o,t,e,n);let a=o._withLegendConfig(l,m);return(s==="auto"||typeof s=="string")&&(a=a.editSemantic({property:`guide.legend.${n}.title`,value:u})),a=k(a,`${d}Title`,f,{type:"text"}),a=k(a,`${d}Background`,i.border!==!1,{type:"rect",before:`${d}Symbols`}),a.rematerializeLegend()}function H(o,t,e,n,l=!1){const d=t==="size",i=d?"size":"stroke-width",m=["target","title","count","values","labels","titleStyle","position","layout","align","direction","columns","titlePosition","offset","itemGap","border"];d&&m.push("overflow");for(const y of Object.keys(n))if(!m.includes(y))throw new Error(`${i} legend does not accept ${y}.`);const s=d&&F(o.resolvedScales[e.scale]?.type),u=d&&o.resolvedScales[e.scale]?.type==="ordinal";if(n.overflow!==void 0&&!u)throw new Error("Size legend overflow requires an ordinal scale.");if(s&&(n.count!==void 0||n.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const f=s?void 0:O(n,{previous:e,operation:"edit",label:`${d?"Size":"Stroke-width"} legend`}),a=z(o,e.target),c=n.title,b=c==="auto"?!0:typeof c=="string"?!1:e.inferredTitle,S=c===!1?!1:c===void 0?e.titleVisible!==!1:!0,h=c==="auto"?a?.encoding?.[t]?.field:typeof c=="string"?c:e.title,{count:r,...p}=e,L={...s?e:p,title:h,inferredTitle:b,titleVisible:S,...s?{}:{sampling:f},...u&&(n.overflow!==void 0||e.overflow!==void 0)?{overflow:V(n.overflow===void 0?e.overflow:n.overflow)}:{},...d?{inheritAppearance:l&&e.inheritAppearance===!0,...n.position===void 0?{}:{inheritPosition:!1}}:{},labels:q(n.labels,"editLegend.labels",e.labels??(d?K:Pe)),titleStyle:D(n.titleStyle,"editLegend.titleStyle",e.titleStyle??(d?N:$e),!0)};return Object.assign(L,ge({...e,...n,titleStyle:L.titleStyle,direction:n.direction??(n.position!==void 0&&n.position!==e.position?void 0:e.direction)})),L.border=ye(C(e.border,n.border)),(d?Te:je)(o,L),{config:L,titleMode:c,title:h,titleVisible:S}}function P(o,t,e,n,l=!1){const{config:d,titleMode:i,title:m,titleVisible:s}=H(o,t,e,n,l),u=t==="size"?"sizeLegend":"strokeWidthLegend";let f=o;return(i==="auto"||typeof i=="string")&&(f=f.editSemantic({property:`guide.legend.${t}.title`,value:m})),f=f._withLegendConfig(t,d),f=k(f,`${u}Background`,d.border!==!1,{type:"rect",before:`${u}Symbols`}),f=k(f,`${u}Title`,s,{type:"text",after:`${u}Labels`}),f.rematerializeLegend()}function Be(o,t,e,n){if(e===void 0)return;const l=F(o.resolvedScales[e.scale]?.type);if(l&&(n.count!==void 0||n.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const d=l?void 0:O(n,{previous:e,operation:"edit",label:"Size legend"}),{count:i,...m}=e,u={...l?e:m,...l?{}:{sampling:d},...n.overflow!==void 0&&o.resolvedScales[e.scale]?.type==="ordinal"?{overflow:V(n.overflow)}:{}};if(n.labels===void 0&&n.titleStyle===void 0)return u;const f=e.inheritAppearance?{...t.labels,offset:e.labels.offset}:e.labels??K,a=e.inheritAppearance?t.titleStyle:e.titleStyle??N;return{...u,inheritAppearance:!1,labels:q(n.labels,"editLegend.labels",f),titleStyle:D(n.titleStyle,"editLegend.titleStyle",a,!0)}}function Y(o,t,e,n,l,d=o.semanticSpec.guides.legend?.[t]?.order){if(l.values!==void 0)throw new Error("Combined and categorical legends require a channel block selector for exact values.");if(l.gradient!==void 0)throw new Error("Categorical legends do not accept gradient.");if(l.count!==void 0&&n===void 0)throw new Error("Legend count requires an existing size legend.");const i=l.title,m=i==="auto"?!0:typeof i=="string"?!1:e.inferredTitle,s=i===!1?!1:i===void 0?e.titleVisible!==!1:!0,u=i==="auto"?e.field:typeof i=="string"?i:e.title,f=E(e.labels,l.labels);if(f.format!==void 0&&f.format!=="auto"&&n===void 0)throw new Error("Categorical legend labels do not accept format.");const{format:a,...c}=f,b=l.symbol===void 0?e.inferredSymbol:l.symbol==="auto",S=ie({target:e.target,channels:e.channels,...l.overflow===void 0&&e.overflow===void 0?{}:{overflow:l.overflow===void 0?e.overflow:l.overflow},layout:l.layout===void 0?e.layout:l.layout,position:l.position??e.position,align:l.align??e.align,direction:l.direction??(l.position!==void 0&&l.position!==e.position?void 0:e.direction),...l.columns===void 0&&e.columns===void 0?{}:{columns:l.columns??e.columns},offset:l.offset??e.offset,titlePosition:l.titlePosition??e.titlePosition,title:u,symbol:l.symbol===void 0?e.symbol:le(o,z(o,e.target),e.channels,l.symbol,t),labels:c,titleStyle:G(e.titleStyle,l.titleStyle,{edit:!0}),itemGap:l.itemGap??e.itemGap,border:C(e.border,l.border)},t,{internalSymbol:b}),h={...e,...S,inferredTitle:m,inferredSymbol:b,titleVisible:s},r=l.order===void 0?d:j(l.order);return ne(o,z(o,e.target),e.channels,u,r),{config:h,order:r,titleMode:i,title:u,titleVisible:s,sizeConfig:Be(o,e,n,l)}}function Z(o,t,e,n,l){const{config:d,order:i,titleMode:m,title:s,titleVisible:u,sizeConfig:f}=Y(o,t,e,n,l),a=ce(d);let c=o;(m==="auto"||typeof m=="string")&&(c=c.editSemantic({property:`guide.legend.${t}.title`,value:s})),l.order!==void 0&&(i==="scale"?o.semanticSpec.guides.legend?.[t]?.order!==void 0&&(c=c.editSemantic({property:`guide.legend.${t}.order`,remove:!0})):c=c.editSemantic({property:`guide.legend.${t}.order`,value:i})),c=c._withLegendConfig(t,d),c=se(c,e,d),c=k(c,`${t}LegendBackground`,d.border!==!1,{type:"rect",before:[...a][0]});const b=`${t}LegendTitle`;return c=k(c,b,u,{type:"text",...c.graphicSpec.objects.sizeLegendSymbols===void 0?{}:{before:c.graphicSpec.objects.sizeLegendBackground===void 0?"sizeLegendSymbols":"sizeLegendBackground"}}),n!==void 0&&(c=c._withLegendConfig("size",f)),c.rematerializeLegend()}function J(o,t,e){_e(e.channels,"editLegend");const{channels:n,target:l,...d}=e,i=o.guideConfigs.legend??{},m=Object.keys(i).filter(r=>i[r].target===t);let s=m.reduce((r,p)=>r._withoutMaterializationConfig(["guides","legend",p]),o);const u=d.representation??(n.includes("color")&&i.gradient?.target===t?i.gradient.representation:void 0);if(d.representation!==void 0&&!n.includes("color"))throw new Error("Legend representation requires selected continuous color.");const f=de(s,{target:t,channels:n,...u===void 0?{}:{representation:u}}),a=m.find(r=>["series","color","stroke"].includes(r));let c=f.steps.map(r=>{const p=te(s,r),{kind:g}=p;if(i[g]!==void 0&&i[g].target!==t)throw new Error(`Legend ${g} content already belongs to another target.`);if(["series","color","stroke"].includes(g)&&a!==void 0){const L=d.order===void 0?o.semanticSpec.guides.legend?.[a]?.order:j(d.order);return{...ae(s,a,i[a],p.config.channels,{order:L,validateLayout:!1}),kind:g}}return{...p,config:i[g]?.target===t?i[g]:p.config}});const b=c.find(r=>["series","color","stroke"].includes(r.kind)),S=c.find(r=>r.kind==="size");if(d.representation!==void 0||c.some(r=>r.kind==="gradient"&&r.config.representation==="symbols")){const r=c.find(p=>p.kind==="gradient");if(r===void 0)throw new Error("Legend representation requires continuous color.");r.config=_(s,"gradient",r.config,d).config}else if(b!==void 0){S!==void 0&&i.size?.target!==t&&(S.config={...S.config,inheritAppearance:b.config.position==="left"||d.position==="left"||d.labels!==void 0||d.titleStyle!==void 0});const r=Y(s,b.kind,b.config,S?.config,d,b.order);if(b.config=r.config,b.order=r.order,S!==void 0&&(S.config=r.sizeConfig,b.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const r=c[0];r.kind==="size"||r.kind==="strokeWidth"?r.config=H(s,r.kind,r.config,d).config:["interval","strokeInterval"].includes(r.kind)?r.config=R(s,r.config,d,r.kind==="strokeInterval"?"stroke":"color").config:r.config=_(s,r.kind,r.config,d).config}c=Ve(o,t,c);for(const{kind:r,config:p}of c)s=s._withLegendConfig(r,p);b!==void 0&&re(s,b.config);let h=W(o,m);for(const{kind:r,config:p,order:g}of c)["series","color","stroke"].includes(r)?h=fe(h,p,g):h={size:Ie,strokeWidth:xe,gradient:M,opacity:pe,interval:we,strokeGradient:ke,strokeInterval:ze}[r](h,p);return h.rematerializeLegend()}function Q(o={},t=!1){if(x(o,We,"editLegend"),Object.keys(o).filter(a=>a!=="target").length===0)throw new Error("editLegend requires at least one change.");if(o.title!==void 0&&o.title!==!1&&o.title!=="auto"&&(typeof o.title!="string"||o.title.length===0))throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');const n=Oe(this,o.target,"editLegend"),l=this.guideConfigs.legendGroups?.[n];if(!t&&(l!==void 0||o.blockLayout!==void 0||o.at!==void 0||$.includes(o.position)&&Object.values(this.guideConfigs.legend??{}).some(a=>a.target===n&&a.kind===void 0))){const a=v(this,o,n,{corner:$.includes(o.position)}),{channels:c,...b}=a.options;if(c!==void 0)return J(a.program,n,a.options);const S=["position","layout","align","direction","columns","offset","titlePosition","itemGap"];if(Object.keys(b).every(r=>r==="target"||S.includes(r))){let r=a.program;const p=Object.entries(r.guideConfigs.legend??{}).filter(([,g])=>g.target===n);for(const[g]of p){const L=r.guideConfigs.legend[g],y={...b};if(["gradient","strokeGradient","opacity"].includes(g)&&L.representation!=="symbols")for(const w of["layout","columns","direction"]){if(Object.hasOwn(o,w))throw new Error(`${g} legend does not accept ${w}.`);delete y[w]}["gradient","strokeGradient"].includes(g)&&L.representation!=="symbols"&&delete y.itemGap,["series","color","stroke"].includes(g)?r=Z(r,g,L,void 0,y):["size","strokeWidth"].includes(g)?r=P(r,g,L,y,!0):["interval","strokeInterval"].includes(g)?r=I(r,L,y,g==="strokeInterval"?"stroke":"color"):r=T(r,g,L,y)}return r.rematerializeLegend()}return Q.call(a.program,a.options,!0)}if(o.channels!==void 0)return J(this,n,o);const i=this.guideConfigs.legend??{},m=Object.keys(i).filter(a=>i[a]?.target===n);if(o.representation!==void 0){if(i.gradient?.target!==n)throw new Error("Legend representation requires continuous color.");return T(this,"gradient",i.gradient,o)}if(i.gradient?.target===n&&i.gradient.representation==="symbols"&&m.length>1)throw new Error("Multiple legend blocks require editLegendBlock with an explicit channel for appearance or samples.");if(o.values!==void 0&&m.length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");const s=["series","color","stroke"].filter(a=>i[a]?.target===n);if(s.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const u=s[0];if(u!==void 0)return Z(this,u,i[u],i.size?.target===n?i.size:void 0,o);if(i.interval?.target===n)return I(this,i.interval,o);if(i.strokeInterval?.target===n)return I(this,i.strokeInterval,o,"stroke");if(i.strokeWidth?.target===n)return P(this,"strokeWidth",i.strokeWidth,o);if(i.size?.target===n)return P(this,"size",i.size,o);const f=["gradient","strokeGradient","opacity"].find(a=>i[a]?.target===n);return T(this,f,i[f],o)}const St=oe({op:"editLegend",description:"Edit one stable legend content, layout or appearance."},ee(function(o={}){return X(this,Q.call(this,o),o)}));export{St as editLegend};
1
+ import{resolveColorSizeEdit as R,createColorSizeLegendFromConfig as me}from"./colorSize.js";import{patchLegendClipHeight as Y,validateLegendClip as be}from"../../../grammar/legendClip.js";import{validateLegendCombine as ye,validateMappedSizeShapeSymbol as pe}from"../../../grammar/legendCombinations.js";import{legendBlockDescriptors as j}from"./target.js";import{editFacetParentLegend as he}from"../../../materialization/facetGuides/edit.js";import{localLegendPosition as Z,localLegendPlacement as Le}from"../../../materialization/legendGroups.js";import{mergeLegendTitleStyle as $}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as J}from"./group.js";import{prepareLegendGroup as Se}from"./group.js";import{PLOT_LEGEND_POSITIONS as Q}from"../../../layout/legend.js";import{withGuideLayoutValidation as we}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as ke}from"./creation.js";import{normalizeLegendOrder as U}from"../../../grammar/categoryOrder.js";import{resolveDefinition as ze}from"./categorical/resolve.js";import{action as Ce}from"../../../core/action.js";import{validateOptionObject as B}from"../../../core/validation.js";import{normalizeLegendOverflow as X,normalizeOptions as Oe}from"./categorical/options.js";import{resolveLegendSymbol as Ee}from"./categorical/recipes.js";import{resolveLayout as Ge}from"./categorical/layout.js";import{resolveLegendCreationPlan as ee}from"./categorical/actions.js";import{categoricalSymbolIds as _e,reconcileCategoricalSymbols as Pe,createCategoricalLegendFromConfig as je,resolveCategoricalLegendRevision as Te,removeLegendKinds as te}from"./lifecycle.js";import{normalizeColorSymbols as Ie}from"./continuous/colorSymbols.js";import{readLegendSampling as $e}from"./sampling.js";import{createGradientLegendFromConfig as ne}from"./continuous/gradient.js";import{normalizeLegendGradient as ie}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as xe,normalizeLegendBorder as Ve,normalizeLegendTextOptions as oe,normalizeLegendTitleOptions as le,normalizeContinuousLegend as re}from"./continuous/common.js";import{normalizeOpacitySymbol as De,createOpacityLegendFromConfig as Me}from"./continuous/opacity.js";import{normalizeIntervalLegend as qe,resolveIntervalConfig as Be,resolveIntervalLayout as Ae,createIntervalLegendFromConfig as We}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as Fe,createStrokeIntervalLegendFromConfig as Ke,resolveStrokeIntervalConfig as He,resolveStrokeIntervalLayout as Ne}from"./continuous/stroke.js";import{findLayer as P}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as ve}from"../../../materialization/graphicHierarchy.js";import{resolveLegendBlock as A,resolveLegendTarget as W,validateLegendChannels as Re}from"./target.js";import{isEnumeratedSizeScaleType as de}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as ce,SIZE_LEGEND_TITLE_STYLE as se,resolveSizeLegendLayout as Ye,createSizeLegendFromConfig as Ze}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as Je,STROKE_WIDTH_LEGEND_TITLE_STYLE as Qe,resolveStrokeWidthLegendLayout as Ue,createStrokeWidthLegendFromConfig as Xe}from"./strokeWidth.js";import{normalizeLegendSampling as F}from"./sampling.js";import{patchLegendBlockOverride as et}from"./blocks.js";import{LEGEND_POSITIONS as tt}from"../../../core/vocabulary.js";import{planLegendBlockTransitions as nt}from"./transition.js";const fe=Object.freeze(["target","channel","channels","position","layout","align","direction","columns","offset","titlePosition","title","symbol","labels","titleStyle","itemGap","border","count","values","gradient","representation","order","overflow","blockLayout","at","combine","clipHeight"]);function T(n,t){return t===void 0?n:{...n,...t}}function I(n,t){return t===void 0?n:t===!1||t===!0||n===!1?t:{...n,...t}}function E(n,t,e,o){const i=n.graphicSpec.objects[t]!==void 0;return i&&!e?n.editGraphics({target:t,remove:!0}):!i&&e?n.createGraphics({id:t,...o,...ve(n,{...o.before===void 0?{}:{before:o.before},...o.after===void 0?{}:{after:o.after}})}):n}function it(n,t,e){const o=e.representation??t.representation??"gradient";if(!["gradient","symbols"].includes(o))throw new Error("Legend representation must be gradient or symbols.");const i=["target","representation","position","align","offset","title","labels","titleStyle","titlePosition","border","count",...o==="symbols"?["symbol","values","itemGap","layout","direction","columns"]:["gradient","direction"]];for(const a of Object.keys(e))if(!i.includes(a))throw new Error(`Color ${o} legend does not accept ${a}.`);const s=(t.representation??"gradient")!==o,u=e.title,r=u==="auto"?P(n,t.target)?.encoding?.color?.field:typeof u=="string"?u:t.title,f={target:t.target,position:e.position??t.position,align:e.align??t.align,offset:e.offset??t.offset,title:r,labels:T(t.labels,e.labels),titleStyle:$(t.titleStyle,e.titleStyle,{edit:!0}),border:I(t.border,e.border),titlePosition:e.titlePosition??(s&&(o==="gradient"||t.titlePosition==="bottom")?"top":t.titlePosition)};let m;if(o==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&B(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const a=s?{count:t.count}:{};for(const h of["values","count"])Object.hasOwn(e,h)&&(a[h]=e[h]);Object.hasOwn(e,"values")&&delete a.count,Object.hasOwn(e,"count")&&(a.count=e.count),m=Ie({...f,...a,layout:e.layout??(s?void 0:t.layout),direction:e.direction??(s||e.position!==void 0&&e.position!==t.position?void 0:t.direction),columns:e.columns??(s?void 0:t.columns),itemGap:e.itemGap??(s?void 0:t.itemGap),symbol:e.symbol==="auto"?"auto":e.symbol===void 0?s?void 0:t.symbol:{...s?{}:t.symbol,...e.symbol}},s?{count:t.count}:t,"edit")}else{m=re({...f,...e.direction==="auto"?{}:e.direction!==void 0?{direction:e.direction}:t.gradientDirection===void 0?{}:{direction:t.gradientDirection},count:e.count??(s?$e(t).count:t.count)},"gradient");const a=ie(e.gradient,s?void 0:t.gradient);m.gradient=a}const{representation:c,sampling:S,symbol:l,layout:L,direction:C,columns:z,gradient:k,count:w,...g}=t,p=s&&o==="gradient"&&t.blockOverrides!==void 0?Object.fromEntries(Object.entries(t.blockOverrides).map(([a,h])=>{const{symbol:d,labelMap:b,...y}=h;return[a,y]})):t.blockOverrides;return o==="gradient"&&e.direction==="auto"&&delete g.gradientDirection,{config:{...g,...m,...p===void 0?{}:{blockOverrides:p},inferredTitle:u==="auto"?!0:typeof u=="string"?!1:t.inferredTitle,titleVisible:u===!1?!1:u===void 0?t.titleVisible!==!1:!0},titleMode:u,title:r,titleVisible:u===!1?!1:u===void 0?t.titleVisible!==!1:!0}}function x(n,t,e,o){if(t==="gradient"&&(e.representation==="symbols"||o.representation!==void 0))return it(n,e,o);const i=["gradient","strokeGradient"].includes(t),s=i?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","gradient","representation","direction"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const h of Object.keys(o))if(!s.includes(h))throw new Error(`${t} legend does not accept ${h}.`);const u=o.title,r=u==="auto"?!0:typeof u=="string"?!1:e.inferredTitle,f=u===!1?!1:u===void 0?e.titleVisible!==!1:!0,m=P(n,e.target),c=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity",S=m?.encoding?.[c]?.field,l=u==="auto"?S:typeof u=="string"?u:e.title,L=o.titlePosition??e.titlePosition,C=t==="opacity"&&L==="left"&&e.titlePosition!=="left",z=T(e.labels,o.labels),k=i?void 0:F(o,{previous:e,operation:"edit",label:"Opacity legend"}),w=re({target:e.target,position:o.position??e.position,align:o.align??e.align,offset:o.offset??e.offset,...i?{count:o.count??e.count,...o.direction==="auto"?{}:o.direction!==void 0?{direction:o.direction}:e.gradientDirection===void 0?{}:{direction:e.gradientDirection}}:{},titlePosition:L,title:l,labels:C&&o.labels?.offset===void 0?{...z,offset:8}:z,titleStyle:$(e.titleStyle,o.titleStyle,{edit:!0}),border:I(e.border,o.border),...t==="opacity"?{itemGap:o.itemGap??(C?20:e.itemGap),symbol:o.symbol??e.symbol}:{}},i?"gradient":t),{count:g,...p}=e,a={...i?e:p,...w,...i?{}:{sampling:k},inferredTitle:r,titleVisible:f};if(i){o.direction==="auto"&&delete a.gradientDirection;const h=ie(o.gradient,e.gradient);a.gradient=h}else a.symbol=De(o.symbol??e.symbol);return{config:a,titleMode:u,title:l,titleVisible:f}}function V(n,t,e,o){const{config:i,titleMode:s,title:u,titleVisible:r}=x(n,t,e,o);if(t==="gradient"&&(e.representation??"gradient")!==(i.representation??"gradient"))return ne(te(n,[t]),i).rematerializeLegend();const f=t==="gradient"?"colorGradient":t==="strokeGradient"?"strokeGradient":"opacityLegend",m=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity";let c=n;return(s==="auto"||typeof s=="string")&&(c=c.editSemantic({property:`guide.legend.${m}.title`,value:u})),c=c._withLegendConfig(t,i),c=E(c,`${f}Background`,i.border!==!1,{type:"rect",before:["gradient","strokeGradient"].includes(t)?`${f}${i.representation==="symbols"?"Symbols":"Strips"}`:"opacityLegendSymbols"}),c=E(c,`${f}Title`,r,{type:"text"}),c.rematerializeLegend()}function ae(n,t,e,o="color"){for(const l of Object.keys(e))if(!["target","position","align","direction","offset","title","symbol","labels","titleStyle","itemGap","border","layout","columns","titlePosition"].includes(l))throw new Error(`interval legend does not accept ${l}.`);const i=e.title,s=P(n,t.target),u=i==="auto"?!0:typeof i=="string"?!1:t.inferredTitle,r=i===!1?!1:i===void 0?t.titleVisible!==!1:!0,f=i==="auto"?s?.encoding?.[o]?.field:typeof i=="string"?i:t.title,m=qe({target:t.target,position:e.position??t.position,align:e.align??t.align,direction:e.direction??(e.position!==void 0&&e.position!==t.position?void 0:t.direction),layout:e.layout===void 0?t.layout:e.layout,columns:e.columns??t.columns,titlePosition:e.titlePosition??t.titlePosition,offset:e.offset??t.offset,title:f,symbol:T(t.symbol,e.symbol),labels:T(t.labels,e.labels),titleStyle:$(t.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??t.itemGap,border:I(t.border,e.border)}),c={...t,...m,inferredTitle:u,titleVisible:r,...o==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},S=o==="stroke"?He(n,c):Be(n,c);return(o==="stroke"?Ne:Ae)(n,S.config,S.scale),{normalized:m,config:c,titleMode:i,title:f,titleVisible:r}}function D(n,t,e,o="color"){const i=o==="stroke"?"strokeInterval":"interval",s=o==="stroke"?"strokeInterval":"colorLegend",{normalized:u,config:r,titleMode:f,title:m,titleVisible:c}=ae(n,t,e,o);let S=n._withLegendConfig(i,r);return(f==="auto"||typeof f=="string")&&(S=S.editSemantic({property:`guide.legend.${o}.title`,value:m})),S=E(S,`${s}Title`,c,{type:"text"}),S=E(S,`${s}Background`,u.border!==!1,{type:"rect",before:`${s}Symbols`}),S.rematerializeLegend()}function K(n,t,e,o,i=!1){const s=t==="size",u=s?"size":"stroke-width",r=["target","title","count","values","labels","titleStyle","position","layout","align","direction","columns","titlePosition","offset","itemGap","border"];s&&r.push("overflow");for(const a of Object.keys(o))if(!r.includes(a))throw new Error(`${u} legend does not accept ${a}.`);const f=s&&de(n.resolvedScales[e.scale]?.type),m=s&&n.resolvedScales[e.scale]?.type==="ordinal";if(o.overflow!==void 0&&!m)throw new Error("Size legend overflow requires an ordinal scale.");if(f&&(o.count!==void 0||o.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const c=f?void 0:F(o,{previous:e,operation:"edit",label:`${s?"Size":"Stroke-width"} legend`}),S=P(n,e.target),l=o.title,L=l==="auto"?!0:typeof l=="string"?!1:e.inferredTitle,C=l===!1?!1:l===void 0?e.titleVisible!==!1:!0,z=l==="auto"?S?.encoding?.[t]?.field:typeof l=="string"?l:e.title,{count:k,...w}=e,p={...f?e:w,title:z,inferredTitle:L,titleVisible:C,...f?{}:{sampling:c},...m&&(o.overflow!==void 0||e.overflow!==void 0)?{overflow:X(o.overflow===void 0?e.overflow:o.overflow)}:{},...s?{inheritAppearance:i&&e.inheritAppearance===!0,...o.position===void 0?{}:{inheritPosition:!1}}:{},labels:oe(o.labels,"editLegend.labels",e.labels??(s?ce:Je)),titleStyle:le(o.titleStyle,"editLegend.titleStyle",e.titleStyle??(s?se:Qe),!0)};return Object.assign(p,xe({...e,...o,titleStyle:p.titleStyle,direction:o.direction??(o.position!==void 0&&o.position!==e.position?void 0:e.direction)})),p.border=Ve(I(e.border,o.border)),(s?Ye:Ue)(n,p),{config:p,titleMode:l,title:z,titleVisible:C}}function M(n,t,e,o,i=!1){const{config:s,titleMode:u,title:r,titleVisible:f}=K(n,t,e,o,i),m=t==="size"?"sizeLegend":"strokeWidthLegend";let c=n;return(u==="auto"||typeof u=="string")&&(c=c.editSemantic({property:`guide.legend.${t}.title`,value:r})),c=c._withLegendConfig(t,s),c=E(c,`${m}Background`,s.border!==!1,{type:"rect",before:`${m}Symbols`}),c=E(c,`${m}Title`,f,{type:"text",after:`${m}Labels`}),c.rematerializeLegend()}function ot(n,t,e,o){if(e===void 0)return;const i=de(n.resolvedScales[e.scale]?.type);if(i&&(o.count!==void 0||o.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const s=i?void 0:F(o,{previous:e,operation:"edit",label:"Size legend"}),{count:u,...r}=e,m={...i?e:r,...i?{}:{sampling:s},...o.overflow!==void 0&&n.resolvedScales[e.scale]?.type==="ordinal"?{overflow:X(o.overflow)}:{}};if(o.labels===void 0&&o.titleStyle===void 0)return m;const c=e.inheritAppearance?{...t.labels,offset:e.labels.offset}:e.labels??ce,S=e.inheritAppearance?t.titleStyle:e.titleStyle??se;return{...m,inheritAppearance:!1,labels:oe(o.labels,"editLegend.labels",c),titleStyle:le(o.titleStyle,"editLegend.titleStyle",S,!0)}}function ue(n,t,e,o,i,s=n.semanticSpec.guides.legend?.[t]?.order){if(i.values!==void 0)throw new Error("Combined and categorical legends require a channel block selector for exact values.");if(i.gradient!==void 0)throw new Error("Categorical legends do not accept gradient.");if(i.count!==void 0&&o===void 0)throw new Error("Legend count requires an existing size legend.");const u=i.title,r=u==="auto"?!0:typeof u=="string"?!1:e.inferredTitle,f=u===!1?!1:u===void 0?e.titleVisible!==!1:!0,m=u==="auto"?e.field:typeof u=="string"?u:e.title,c=T(e.labels,i.labels);if(c.format!==void 0&&c.format!=="auto"&&o===void 0)throw new Error("Categorical legend labels do not accept format.");const{format:S,...l}=c,L=i.symbol===void 0?e.inferredSymbol:i.symbol==="auto",C=Oe({target:e.target,channels:e.channels,...i.overflow===void 0&&e.overflow===void 0?{}:{overflow:i.overflow===void 0?e.overflow:i.overflow},layout:i.layout===void 0?e.layout:i.layout,position:i.position??e.position,align:i.align??e.align,direction:i.direction??(i.position!==void 0&&i.position!==e.position?void 0:e.direction),...i.columns===void 0&&e.columns===void 0?{}:{columns:i.columns??e.columns},offset:i.offset??e.offset,titlePosition:i.titlePosition??e.titlePosition,title:m,symbol:i.symbol===void 0?e.symbol:Ee(n,P(n,e.target),e.channels,i.symbol,t),labels:l,titleStyle:$(e.titleStyle,i.titleStyle,{edit:!0}),itemGap:i.itemGap??e.itemGap,border:I(e.border,i.border)},t,{internalSymbol:L}),z={...e,...C,inferredTitle:r,inferredSymbol:L,titleVisible:f},k=i.order===void 0?s:U(i.order);return ze(n,P(n,e.target),e.channels,m,k),{config:z,order:k,titleMode:u,title:m,titleVisible:f,sizeConfig:ot(n,e,o,i)}}function H(n,t,e,o,i){e.combine==="size-shape"&&pe(i.symbol);const{config:s,order:u,titleMode:r,title:f,titleVisible:m,sizeConfig:c}=ue(n,t,e,o,i),S=_e(s);let l=n;(r==="auto"||typeof r=="string")&&(l=l.editSemantic({property:`guide.legend.${t}.title`,value:f})),i.order!==void 0&&(u==="scale"?n.semanticSpec.guides.legend?.[t]?.order!==void 0&&(l=l.editSemantic({property:`guide.legend.${t}.order`,remove:!0})):l=l.editSemantic({property:`guide.legend.${t}.order`,value:u})),l=l._withLegendConfig(t,s),l=Pe(l,e,s),l=E(l,`${t}LegendBackground`,s.border!==!1,{type:"rect",before:[...S][0]});const L=`${t}LegendTitle`;return l=E(l,L,m,{type:"text",...l.graphicSpec.objects.sizeLegendSymbols===void 0?{}:{before:l.graphicSpec.objects.sizeLegendBackground===void 0?"sizeLegendSymbols":"sizeLegendBackground"}}),o!==void 0&&(l=l._withLegendConfig("size",c)),l.rematerializeLegend()}function N(n,t,e){const o=R(n,t,e);let i=n._withLegendConfig("colorSize",o);return i=E(i,"colorSizeLegendBackground",o.border!==!1,{type:"rect",before:"colorSizeLegendSymbols"}),i=E(i,"colorSizeLegendTitle",o.titleVisible!==!1,{type:"text",after:"colorSizeLegendLabels"}),i.rematerializeLegend()}function v(n,t,e,o={position:e.position}){Re(e.channels,"editLegend");const{channels:i,target:s,combine:u,...r}=e,f=n.guideConfigs.legend??{},m=Object.keys(f).filter(d=>f[d].target===t);let c=m.reduce((d,b)=>d._withoutMaterializationConfig(["guides","legend",b]),n);const S=u==="color-size"&&r.representation===void 0?void 0:r.representation??(i.includes("color")&&f.gradient?.target===t?f.gradient.representation:void 0);if(r.representation!==void 0&&!i.includes("color"))throw new Error("Legend representation requires selected continuous color.");const l=Object.values(f).find(d=>d.target===t&&["size-shape","color-size"].includes(d.combine)),L=l?.combine==="color-size"?["color","size"]:["shape","size"],C=L.every(d=>i.includes(d));if(l&&u===void 0&&!C&&i.some(d=>L.includes(d)))throw new Error("An edited combined legend keeps its combination; explicitly choose separate to split it.");const z=u??(l&&C?l.combine:void 0);let k;if(u==="separate"&&l){const d=i.filter(y=>L.includes(y));k={steps:[...l.combine==="color-size"?d.map(y=>[y]):[d],...i.filter(y=>!L.includes(y)).map(y=>[y])].filter(y=>y.length).map(y=>ee(c,{target:t,channels:y})).flatMap(y=>y.steps),...n.guideConfigs.legendGroups?.[t]===void 0?{}:{legendGroup:{target:t,config:n.guideConfigs.legendGroups[t]}}}}else k=ee(c,{target:t,channels:i,...z===void 0?{}:{combine:z},...S===void 0?{}:{representation:S}},n.semanticSpec.layers,i);k.legendGroup!==void 0&&(c=c._withMaterializationConfig(["guides","legendGroups",t],k.legendGroup.config));const w=m.find(d=>["series","color","stroke"].includes(d));let g=k.steps.map(d=>{const b=ke(c,d),{kind:y}=b;if(f[y]!==void 0&&f[y].target!==t)throw new Error(`Legend ${y} content already belongs to another target.`);if(b.config.combine==="size-shape"&&f.series?.combine!=="size-shape"||b.kind==="colorSize"&&f.colorSize?.target!==t)return b;if(l?.combine==="color-size"&&["gradient","size"].includes(y)){const G=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","offset","border"].map(_=>[_,l[_]]));return y==="size"&&Object.assign(G,Object.fromEntries(["sampling","direction","columns","titlePosition","itemGap","layout","clipHeight"].map(_=>[_,l[_]])),{inheritAppearance:!1}),{...b,config:{...b.config,...G}}}if(y==="size"&&l){const O=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","direction","columns","offset","titlePosition","itemGap","border","layout","clipHeight"].map(G=>[G,l[G]]));return{...b,config:{...b.config,...O,inheritAppearance:!1}}}if(["series","color","stroke"].includes(y)&&w!==void 0){const O=f[y]?.target===t?y:w,G=r.order===void 0?n.semanticSpec.guides.legend?.[O]?.order:U(r.order);return{...Te(c,O,f[O],b.config.channels,{order:G,validateLayout:!1}),kind:y}}return{...b,config:f[y]?.target===t?f[y]:b.config}});const p=g.find(d=>["series","color","stroke"].includes(d.kind)),a=g.find(d=>d.kind==="size");if(r.representation!==void 0||g.some(d=>d.kind==="gradient"&&d.config.representation==="symbols")){const d=g.find(b=>b.kind==="gradient");if(d===void 0)throw new Error("Legend representation requires continuous color.");d.config=x(c,"gradient",d.config,r).config}else if(p!==void 0){a!==void 0&&f.size?.target!==t&&(a.config={...a.config,inheritAppearance:p.config.position==="left"||r.position==="left"||r.labels!==void 0||r.titleStyle!==void 0});const d=ue(c,p.kind,p.config,a?.config,r,p.order);if(p.config=d.config,p.order=d.order,a!==void 0&&(a.config=d.sizeConfig,p.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const d=g[0];if(d.kind==="colorSize")d.config=R(c,d.config,r);else if(l?.combine==="color-size"&&u==="separate")for(const b of g)b.config=b.kind==="size"?K(c,"size",b.config,r).config:x(c,b.kind,b.config,Object.fromEntries(Object.entries(r).filter(([y])=>!["values","itemGap","layout","columns"].includes(y)))).config;else d.kind==="size"||d.kind==="strokeWidth"?d.config=K(c,d.kind,d.config,r).config:["interval","strokeInterval"].includes(d.kind)?d.config=ae(c,d.config,r,d.kind==="strokeInterval"?"stroke":"color").config:d.config=x(c,d.kind,d.config,r).config}g=nt(n,t,g,o.position);for(const{kind:d,config:b}of g)c=c._withLegendConfig(d,b);p!==void 0&&Ge(c,p.config);let h=te(n,m);k.legendGroup!==void 0&&(h=h._withMaterializationConfig(["guides","legendGroups",t],k.legendGroup.config));for(const{kind:d,config:b,order:y}of g)["series","color","stroke"].includes(d)?h=je(h,b,y):h={colorSize:me,size:Ze,strokeWidth:Xe,gradient:ne,opacity:Me,interval:We,strokeGradient:Fe,strokeInterval:Ke}[d](h,b);return h.rematerializeLegend()}function q(n={},t=!1,e={position:n.position}){if(B(n,fe,"editLegend"),Object.keys(n).filter(l=>l!=="target"&&l!=="channel").length===0)throw new Error("editLegend requires at least one change.");if(n.title!==void 0&&n.title!==!1&&n.title!=="auto"&&(typeof n.title!="string"||n.title.length===0))throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');const i=W(this,n.target,"editLegend");if(Object.hasOwn(n,"channel")){if(Object.hasOwn(n,"channels"))throw new Error("editLegend cannot combine channel and channels.");if(Object.hasOwn(n,"combine"))throw new Error("Selected legend edits cannot change combine.");if(n.blockLayout!==void 0||n.at!==void 0)throw new Error("Selected legend blocks do not accept whole-group blockLayout or at.");let l=this;const L=A(l,n,"editLegend"),C=Object.hasOwn(n,"position");if(C){if(n.position!=="inherit"&&!tt.includes(n.position))throw new Error("Selected legend position requires an anchor or inherit.");const O=et(L.config,L.key,n.position==="inherit"?{removePosition:!0}:{position:n.position}),{blockOverrides:G,..._}=L.config;l=l._withLegendConfig(L.kind,O===void 0?_:{..._,blockOverrides:O});const{position:lt,...ge}=n;n=ge}const z=l.guideConfigs.legend?.size,k=["series","color","stroke"].map(O=>l.guideConfigs.legend?.[O]).find(O=>O?.target===i);z?.target===i&&z.inheritAppearance===!0&&k&&(L.kind==="size"||["series","color","stroke"].includes(L.kind))&&(l=l._withLegendConfig("size",{...z,inheritAppearance:!1,labels:{...k.labels,offset:z.labels.offset},titleStyle:{...k.titleStyle}}));const{kind:w}=L,g=l.guideConfigs.legend[w],{channel:p,...a}=n,h=Le(g),d=h===void 0?g:{...g,...h},b=C&&!["gradient","strokeGradient","opacity"].includes(w)?{...d,direction:void 0}:d,y=O=>h===void 0?O:O._withLegendConfig(w,{...O.guideConfigs.legend[w],position:g.position,...a.align===void 0?{align:g.align}:{},...a.offset===void 0?{offset:g.offset,inferredOffset:g.inferredOffset}:{}}).rematerializeLegend();return["series","color","stroke"].includes(w)?y(H(l,w,b,void 0,a)):w==="colorSize"?y(N(l,b,a)):["size","strokeWidth"].includes(w)?y(M(l,w,b,a)):["interval","strokeInterval"].includes(w)?y(D(l,b,a,w==="strokeInterval"?"stroke":"color")):y(V(l,w,b,a))}const s=this.guideConfigs.legendGroups?.[i];if(!t&&(s!==void 0||n.blockLayout!==void 0||n.at!==void 0||Q.includes(n.position)&&Object.values(this.guideConfigs.legend??{}).some(l=>l.target===i&&l.kind===void 0))){const l=Se(this,n,i,{corner:Q.includes(n.position)}),{channels:L,...C}=l.options;if(L!==void 0)return v(l.program,i,l.options,e);const z=["position","layout","align","direction","columns","offset","titlePosition","itemGap"];if(Object.keys(C).every(g=>g==="target"||z.includes(g))){let g=l.program;const p=Object.entries(g.guideConfigs.legend??{}).filter(([,a])=>a.target===i);for(const[a]of p){const h=g.guideConfigs.legend[a],d={...C};if(Z(h)!==void 0)for(const b of["position","align","offset"])delete d[b];if(["gradient","strokeGradient","opacity"].includes(a)&&h.representation!=="symbols")for(const b of a==="opacity"?["layout","columns","direction"]:["layout","columns"]){if(Object.hasOwn(n,b))throw new Error(`${a} legend does not accept ${b}.`);delete d[b]}["gradient","strokeGradient"].includes(a)&&h.representation!=="symbols"&&delete d.itemGap,["series","color","stroke"].includes(a)?g=H(g,a,h,void 0,d):a==="colorSize"?g=N(g,h,d):["size","strokeWidth"].includes(a)?g=M(g,a,h,d,!0):["interval","strokeInterval"].includes(a)?g=D(g,h,d,a==="strokeInterval"?"stroke":"color"):g=V(g,a,h,d)}return g.rematerializeLegend()}if(n.combine===void 0&&Object.values(this.guideConfigs.legend??{}).some(g=>g.target===i&&["size-shape","color-size"].includes(g.combine))){if(n.values!==void 0&&j(l.program,i).length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");let g=l.program;const p={...n};for(const a of["position","align","offset","blockLayout","at"])delete p[a];for(const a of j(g,i))g=q.call(g,{...p,target:i,channel:a.channels[0]},!0);return g.rematerializeLegend()}let w=q.call(l.program,l.options,!0,e);for(const[g,p]of Object.entries(this.guideConfigs.legend??{}))p.target!==i||Z(p)===void 0||w.guideConfigs.legend[g]===void 0||(w=w._withLegendConfig(g,{...w.guideConfigs.legend[g],position:p.position,align:p.align,offset:p.offset,inferredOffset:p.inferredOffset,...n.direction===void 0?{direction:p.direction}:{}}));return w.rematerializeLegend()}if(Object.hasOwn(n,"combine")){if(ye(n.combine),n.combine==="size-shape"&&n.channels!==void 0&&!(n.channels.length===2&&n.channels.includes("size")&&n.channels.includes("shape")))throw new Error("size-shape combine requires exactly shape and size channels.");const l=n.channels??[...new Set(j(this,i).flatMap(L=>L.channels))];return v(this,i,{...n,channels:l},e)}if(n.channels!==void 0)return v(this,i,n,e);const r=this.guideConfigs.legend??{},f=Object.keys(r).filter(l=>r[l]?.target===i);if(n.representation!==void 0){if(r.gradient?.target!==i)throw new Error("Legend representation requires continuous color.");return V(this,"gradient",r.gradient,n)}if(r.gradient?.target===i&&r.gradient.representation==="symbols"&&f.length>1)throw new Error("Multiple legend blocks require editLegendBlock with an explicit channel for appearance or samples.");if(n.values!==void 0&&f.length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");const m=["series","color","stroke"].filter(l=>r[l]?.target===i);if(m.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const c=m[0];if(c!==void 0)return H(this,c,r[c],r.size?.target===i?r.size:void 0,n);if(r.colorSize?.target===i)return N(this,r.colorSize,n);if(r.interval?.target===i)return D(this,r.interval,n);if(r.strokeInterval?.target===i)return D(this,r.strokeInterval,n,"stroke");if(r.strokeWidth?.target===i)return M(this,"strokeWidth",r.strokeWidth,n);if(r.size?.target===i)return M(this,"size",r.size,n);const S=["gradient","strokeGradient","opacity"].find(l=>r[l]?.target===i);return V(this,S,r[S],n)}const vt=Ce({op:"editLegend",description:"Edit one stable legend content, layout or appearance.",scope:"any"},we(function(n={}){if(this.compositionSpec!==void 0)return he(this,"editLegend",n);if(B(n,fe,"editLegend"),!Object.hasOwn(n,"clipHeight"))return J(this,q.call(this,n),n);const{clipHeight:t,...e}=n,o=Object.keys(e).some(f=>!["target","channel"].includes(f));let i=this;if(t==="auto"){const f=W(i,n.target,"editLegend"),m=n.channel===void 0?j(i,f):[A(i,n,"editLegend")];for(const c of m)i=i._withLegendConfig(c.kind,Y(c.config,"auto"))}let s=o?q.call(i,e):i;const u=W(s,n.target,"editLegend"),r=n.channel===void 0?j(s,u):[A(s,n,"editLegend")];if(!r.length)throw new Error("Legend clipHeight requires an existing legend block.");for(const f of r){const m=Y(s.guideConfigs.legend[f.kind],t);be(f.kind,m),s=s._withLegendConfig(f.kind,m)}return J(this,s.rematerializeLegend(),n)}));export{vt as editLegend};
@@ -4,11 +4,11 @@ import { validateOptionObject } from "../../../core/validation.js";
4
4
  function makeFocusedLegendEdit({ name, options, map }) {
5
5
  return action(
6
6
  {
7
- op: name,
7
+ op: name, scope: "any",
8
8
  description: `Edit one focused legend component through editLegend.`
9
9
  },
10
10
  function (args = {}) {
11
- validateOptionObject(args, ["target", ...options], name);
11
+ validateOptionObject(args, ["target", "channel", ...options], name);
12
12
  if (!options.some(option => Object.hasOwn(args, option))) {
13
13
  throw new Error(`${name} requires at least one component change.`);
14
14
  }
@@ -29,14 +29,15 @@ const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
29
29
  const editLegendLabels = /* @__PURE__ */ makeFocusedLegendEdit({
30
30
  name: "editLegendLabels",
31
31
  options: ["offset", "color", "fontSize", "fontFamily", "fontWeight", "format"],
32
- map: ({ target, ...labels }) => ({ target, labels })
32
+ map: ({ target, channel, ...labels }) => ({ target, ...(channel === undefined ? {} : { channel }), labels })
33
33
  });
34
34
 
35
35
  const editLegendTitle = /* @__PURE__ */ makeFocusedLegendEdit({
36
36
  name: "editLegendTitle",
37
37
  options: ["title", "color", "fontSize", "fontFamily", "fontWeight", "align"],
38
- map: ({ target, title, ...titleStyle }) => ({
38
+ map: ({ target, channel, title, ...titleStyle }) => ({
39
39
  target,
40
+ ...(channel === undefined ? {} : { channel }),
40
41
  ...(title === undefined ? {} : { title }),
41
42
  ...(Object.keys(titleStyle).length === 0 ? {} : { titleStyle })
42
43
  })
@@ -44,7 +45,7 @@ const editLegendTitle = /* @__PURE__ */ makeFocusedLegendEdit({
44
45
 
45
46
  const editLegendSymbols = /* @__PURE__ */ makeFocusedLegendEdit({
46
47
  name: "editLegendSymbols",
47
- options: ["symbol", "count", "gradient"],
48
+ options: ["symbol", "count", "gradient", "clipHeight"],
48
49
  map: args => args
49
50
  });
50
51
 
@@ -1 +1 @@
1
- import{resolveLegendAxisOffset as k,effectiveLegendOffsetConfig as E}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as q}from"../../../layout/legendGroup.js";import{legendGroup as F}from"../../../materialization/legendGroups.js";import{editLegendBackground as $,resolveLegendBackgroundFromBounds as R}from"./continuous/common.js";import{action as v}from"../../../core/action.js";import{noOptions as z}from"../../../core/validation.js";import{resolveConcreteGraphicBounds as G,unionConcreteGraphicBounds as O}from"../../../grammar/schemas/graphicBounds.js";import{finiteMidpoint as T}from"../../../grammar/numeric.js";import{resolveGraphicBounds as M}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as K,resolveHorizontalLegendGroup as X,resolveSingleHorizontalLegendPlacement as j,isHorizontalEdgeLegend as P,resolveSideLegendLane as Y,resolveSingleSideLegendPlacement as Z}from"../../../layout/legendLane.js";import{findCanvasGraphic as S}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as _}from"../../../materialization/guides/resources.js";const x=Object.freeze({series:0,color:0,stroke:0,gradient:0,interval:0,strokeGradient:0,strokeInterval:0,size:1,opacity:2,strokeWidth:3});function J(r,i){return["series","color","stroke"].map(t=>r.guideConfigs.legend?.[t]).find(t=>t?.target===i)}function N(r,i,t){return i==="size"&&t.inheritPosition!==!1?J(r,t.target)?.position??t.position:t.position}function Q(r){return r.offset}function m(r,i){return _(i).graphicIds.filter(t=>r.graphicSpec.objects[t]!==void 0)}function U(r,i){const t=m(r,i),o=t.find(c=>c.endsWith("Title")),e=t.find(c=>c.endsWith("Labels")),n=t.find(c=>c.endsWith("Background")),s=t.filter(c=>c!==o&&c!==e&&c!==n);return{titleId:o,labelId:e,backgroundId:n,symbolIds:s}}function V(r,i){let t=G(r.graphicSpec,i,r.materializationConfigs.textMetrics);const o=r.graphicSpec.objects[i];if(!i.includes("LegendSymbol")||o?.type!=="line")return t;const e=o.items?.map(n=>n.properties)??[o.properties];for(const n of e){if(Object.hasOwn(n,"lineCap")||typeof n.stroke!="string"||!(n.strokeWidth>0))continue;const s=n.strokeWidth/2;t={left:Math.min(t.left,Math.min(n.x1,n.x2)-s),right:Math.max(t.right,Math.max(n.x1,n.x2)+s),top:Math.min(t.top,Math.min(n.y1,n.y2)-s),bottom:Math.max(t.bottom,Math.max(n.y1,n.y2)+s)}}return t}function h(r,i){return i.map(t=>V(r,t)).filter(t=>t!==void 0).reduce((t,o)=>t===void 0?o:{left:Math.min(t.left,o.left),right:Math.max(t.right,o.right),top:Math.min(t.top,o.top),bottom:Math.max(t.bottom,o.bottom)},void 0)}function ee(r,i){const t=r.graphicSpec.objects[i];return(t.items?.[0]?.properties??t.properties).x}function w(r,i,t){const o=U(r,i);if(o.labelId===void 0||o.symbolIds.length===0)throw new Error(`Legend lane requires complete ${i} graphics.`);const e=[...o.symbolIds,o.labelId,...o.titleId===void 0?[]:[o.titleId]],n=h(r,e),s=["gradient","strokeGradient"].includes(i)&&t.representation!=="symbols"?[i==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:o.symbolIds,c=h(r,s),f=h(r,o.symbolIds),p=G(r.graphicSpec,o.labelId,r.materializationConfigs.textMetrics),a=r.graphicSpec.objects[o.labelId].items?.length===0,d=o.titleId===void 0?void 0:(()=>{const l=r.graphicSpec.objects[o.titleId],u=G(r.graphicSpec,o.titleId,r.materializationConfigs.textMetrics);return{x:l.properties.x,y:l.properties.y,fontSize:l.properties.fontSize,width:u.right-u.left,bounds:u}})();if([n,c,f].includes(void 0)||p===void 0&&!a)throw new Error(`Legend lane could not measure ${i} graphics.`);return{id:i,kind:i,preserveGrid:(["series","color","stroke"].includes(i)||t.representation==="symbols")&&(t.columns??1)>1,target:t.target,border:C(i,t),offset:Q(t),bounds:n,title:d,symbol:{centerX:T(c.left,c.right),left:f.left,right:f.right,bounds:c},labels:{x:a?f.right+t.labels.offset:ee(r,o.labelId),width:a?0:p.right-p.left},occupiedBounds:h(r,[...e,...o.backgroundId===void 0?[]:[o.backgroundId]]),...o,foregroundIds:e}}function C(r,i){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size"].includes(r)?i.border:!1}function B(r,i){const t=[];for(let o=0;o<r.length;o+=1){const e=r[o],n=i[e.kind],s=r[o+1];if(["series","color","stroke"].includes(e.kind)&&s?.kind==="size"&&s.target===e.target){t.push({id:`${e.kind}+size`,blocks:[e,s.backgroundId===void 0?s:{...s,bounds:s.occupiedBounds,inset:s.border.padding+s.border.lineWidth/2}],border:C(e.kind,n),backgroundId:e.backgroundId}),o+=1;continue}t.push({id:e.kind,blocks:[e],border:C(e.kind,n),backgroundId:e.backgroundId})}return t}function I(r,i){const t=r.guideConfigs.legend??{},o=new Map(r.semanticSpec.layers.map((e,n)=>[e.id,n]));return Object.entries(t).filter(([e,n])=>F(r,n.target)===void 0&&N(r,e,n)===i).sort(([e,n],[s,c])=>o.get(n.target)-o.get(c.target)||x[e]-x[s])}function te(r){return["right","left"].some(i=>I(r,i).length>0)}function ne(r){return["top","bottom"].some(i=>I(r,i).some(([,t])=>P({...t,position:i})))}function be(r){return te(r)||ne(r)}function oe(r,i,t){return r.map(o=>{if(o.op==="Z")return{...o};const e={...o,x:o.x+i,y:o.y+t};return o.op==="C"&&(e.x1=o.x1+i,e.y1=o.y1+t,e.x2=o.x2+i,e.y2=o.y2+t),e})}function H(r,i,t,o){const e={...i};return["circle","rect","text"].includes(r)?(e.x+=t,e.y+=o):r==="line"?(e.x1+=t,e.x2+=t,e.y1+=o,e.y2+=o):r==="path"&&(e.commands=oe(e.commands,t,o)),e}function b(r,i,t,o){const e=r.graphicSpec.objects[i];if(t===0&&o===0)return r;if(e.items!==void 0)return r.editGraphics({target:i,property:"items",value:e.items.map(c=>({type:c.type??e.type,properties:H(c.type??e.type,c.properties,t,o)}))});let n=r;const s=H(e.type,e.properties,t,o);for(const[c,f]of Object.entries(s))f!==e.properties[c]&&(n=n.editGraphics({target:i,property:c,value:f}));return n}function W(r,i){const t=e=>e.textBaseline==="middle"?{...e,y:e.y-e.fontSize/2,textBaseline:"top"}:e;let o=r;for(const e of i){const n=o.graphicSpec.objects[e];if(n.type==="text")if(n.items!==void 0){if(!n.items.some(s=>s.properties.textBaseline==="middle"))continue;o=o.editGraphics({target:e,property:"items",value:n.items.map(s=>({type:s.type??n.type,properties:t(s.properties)}))})}else n.properties.textBaseline==="middle"&&(o=o.editGraphics({target:e,property:"y",value:t(n.properties).y}).editGraphics({target:e,property:"textBaseline",value:"top"}))}return o}function A(r,i){return i.map(t=>{const o=t.blocks[0],e=r.guideConfigs.legend?.[o.kind],n=t.blocks.length>1,s=n?void 0:o.titleId,c=t.blocks.flatMap(l=>l.foregroundIds).filter(l=>l!==s);n&&c.push(...t.blocks.slice(1).flatMap(l=>l.backgroundId===void 0?[]:[l.backgroundId]));const f=h(r,c),p=h(r,[...c,...s===void 0?[]:[s]]);if(f===void 0||p===void 0)throw new Error(`Legend lane could not measure ${t.id} content.`);const a=t.border,d=a===!1?0:a.padding+a.lineWidth/2;return{id:t.id,atomic:n,titleId:s,contentIds:c,title:n?void 0:o.title,inline:e?.titlePosition==="left",element:n?p:o.symbol.bounds,content:f,horizontal:{left:p.left,right:p.right,top:p.top,bottom:p.bottom},inset:d,padding:a===!1?0:a.padding,backgroundId:t.backgroundId}})}function D(r,i,t){let o=r;const e=new Map(i.map(n=>[n.id,n]));for(const n of t.placements){const s=e.get(n.id);s.titleId!==void 0&&(o=b(o,s.titleId,n.dx,n.titleDy));for(const c of s.contentIds)o=b(o,c,n.dx,n.contentDy);if(n.background!==void 0)for(const c of["x","y","width","height"])o=o.editGraphics({target:n.background.id,property:c,value:n.background[c]})}if(t.background!==void 0)for(const n of["x","y","width","height"])o=o.editGraphics({target:t.background.id,property:n,value:t.background[n]});return o}const me=v({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(r={}){z(r,"rematerializeSideLegendLane");const i=M(this),t=S(this)?.properties;if(i===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const o=this.guideConfigs.legend??{},e=["right","left"].flatMap(s=>{const c=I(this,s);if(c.length===0)return[];if(c.length===1)return[{side:s,entries:c}];const f=c.map(([a,d])=>w(this,a,d));return[{plan:Y({side:s,plot:i,canvas:t,groups:B(f,o)}),blocks:f,side:s,entries:c}]});let n=this;for(const{plan:s,blocks:c,side:f,entries:p}of e){if(s===void 0)continue;const a=new Map(c.map(d=>[d.id,d]));for(const d of s.placements){const l=a.get(d.id);l.titleId!==void 0&&(n=b(n,l.titleId,d.titleDx,d.dy).editGraphics({target:l.titleId,property:"textAlign",value:"left"}));for(const u of l.symbolIds)n=b(n,u,d.symbolDx,d.dy);n=b(n,l.labelId,d.labelDx,d.dy).editGraphics({target:l.labelId,property:"textAlign",value:"left"})}for(const d of c){if(d.backgroundId===void 0||s.backgrounds.some(g=>g.id===d.backgroundId))continue;const l=h(n,d.foregroundIds),u=R([l],d.border,t,"Legend");n=$(n,d.backgroundId,u,d.border)}for(const d of s.backgrounds)for(const l of["x","y","width","height"])n=n.editGraphics({target:d.id,property:l,value:d[l]})}for(const{side:s,entries:c}of e){const f=c.flatMap(([d])=>m(n,d));n=W(n,f);const p=h(n,f),{dy:a}=Z({side:s,plot:i,canvas:t,bounds:p});for(const d of f)n=b(n,d,0,a)}return n}),ye=v({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(r={}){z(r,"rematerializeHorizontalLegendLane");const i=M(this),t=S(this)?.properties;if(i===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const o=this.guideConfigs.legend??{};let e=this;for(const n of["top","bottom"]){const s=I(e,n).filter(([,a])=>P({...a,position:n})).map(([a,d])=>[a,E(e,a,d)]);if(s.length===0)continue;if(s.length===1){const[a,d]=s[0],l=m(e,a),u=O(e.graphicSpec,l,e.materializationConfigs.textMetrics),{dx:g,dy:y}=j({plot:i,canvas:t,config:{...d,offset:k(e,d,i)},bounds:u});for(const L of l)e=b(e,L,g,y);continue}for(const[a,d]of s){if(d.inferredOffset!==!0||k(e,d,i)===d.offset)continue;const l=m(e,a),u=O(e.graphicSpec,l,e.materializationConfigs.textMetrics),{dx:g,dy:y}=j({plot:i,canvas:t,config:{...d,offset:k(e,d,i)},bounds:u});for(const L of l)e=b(e,L,g,y)}let c=B(s.map(([a,d])=>w(e,a,d)),o);for(const a of c.filter(d=>d.blocks.length>1)){const d=o[a.blocks[0].kind],l=A(e,a.blocks.map((g,y)=>({id:g.id,blocks:[g],border:y===0?!1:g.border,backgroundId:y===0?void 0:g.backgroundId}))).map(g=>({...g,element:g.content})),u=X({edge:n,plot:i,canvas:t,groups:l,allowOverflow:e.materializationConfigs.canvas?.plot!==void 0,align:d.align,offset:k(e,d,i),border:a.border,backgroundId:a.backgroundId});e=D(e,l,u)}if(c.length<2)continue;c=B(s.map(([a,d])=>w(e,a,d)),o);const f=A(e,c),p=K({edge:n,plot:i,canvas:t,groups:f,allowOverflow:e.materializationConfigs.canvas?.plot!==void 0});e=D(e,f,p)}return e}),ke=v({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(r={}){z(r,"rematerializeLegendGroups");let i=this;const t=M(i),o=S(i)?.properties;for(const[e,n]of Object.entries(i.guideConfigs.legendGroups??{})){const s=Object.entries(i.guideConfigs.legend??{}).filter(([,a])=>a.target===e).sort(([a],[d])=>x[a]-x[d]);i=W(i,s.flatMap(([a])=>m(i,a)));const c=s.map(([a])=>({id:a,ids:m(i,a),bounds:h(i,m(i,a))})),f=n.placement.at!==void 0?n:{...n,placement:{...n.placement,offset:k(i,{...n.placement,inferredOffset:n.inferredOffset},t)}},p=q({blocks:c,group:f,plot:t,canvas:o,allowOverflow:i.materializationConfigs.canvas?.plot!==void 0});for(const a of p){const d=c.find(l=>l.id===a.id);for(const l of d.ids)i=b(i,l,a.dx,a.dy)}}return i});export{ne as hasHorizontalLegendLane,be as hasLegendLane,te as hasSideLegendLane,ye as rematerializeHorizontalLegendLane,ke as rematerializeLegendGroups,me as rematerializeSideLegendLane};
1
+ import{translateGraphic as m}from"../../../materialization/graphicTranslation.js";import{editLegendBorderBounds as v}from"../../../materialization/legendBorder.js";import{resolveLegendAxisOffset as I,effectiveLegendOffsetConfig as F}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as A}from"../../../layout/legendGroup.js";import{localLegendPosition as z,localLegendPlacement as $,legendGroup as R}from"../../../materialization/legendGroups.js";import{editLegendBackground as T,resolveLegendBackgroundFromBounds as K}from"./continuous/common.js";import{action as G}from"../../../core/action.js";import{noOptions as S}from"../../../core/validation.js";import{resolveConcreteGraphicBounds as M,unionConcreteGraphicBounds as H}from"../../../grammar/schemas/graphicBounds.js";import{finiteMidpoint as X}from"../../../grammar/numeric.js";import{resolveGraphicBounds as C}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as Y,resolveHorizontalLegendGroup as _,resolveSingleHorizontalLegendPlacement as W,isHorizontalEdgeLegend as D,resolveSideLegendLane as J,resolveSingleSideLegendPlacement as N}from"../../../layout/legendLane.js";import{findCanvasGraphic as w}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as Q}from"../../../materialization/guides/resources.js";const y=Object.freeze({colorSize:0,series:0,color:0,stroke:0,gradient:0,interval:0,strokeGradient:0,strokeInterval:0,size:1,opacity:2,strokeWidth:3});function U(r,n){return["series","color","stroke"].map(t=>r.guideConfigs.legend?.[t]).find(t=>t?.target===n)}function V(r,n,t){return n==="size"&&t.inheritPosition!==!1?U(r,t.target)?.position??t.position:t.position}function Z(r){return r.offset}function h(r,n){return Q(n).graphicIds.filter(t=>r.graphicSpec.objects[t]!==void 0)}function ee(r,n){const t=h(r,n),i=t.find(a=>a.endsWith("Title")),o=t.find(a=>a.endsWith("Labels")),e=t.find(a=>a.endsWith("Background")),s=t.filter(a=>a!==i&&a!==o&&a!==e);return{titleId:i,labelId:o,backgroundId:e,symbolIds:s}}function te(r,n){let t=M(r.graphicSpec,n,r.materializationConfigs.textMetrics);const i=r.graphicSpec.objects[n];if(!n.includes("LegendSymbol")||i?.type!=="line")return t;const o=i.items?.map(e=>e.properties)??[i.properties];for(const e of o){if(Object.hasOwn(e,"lineCap")||typeof e.stroke!="string"||!(e.strokeWidth>0))continue;const s=e.strokeWidth/2;t={left:Math.min(t.left,Math.min(e.x1,e.x2)-s),right:Math.max(t.right,Math.max(e.x1,e.x2)+s),top:Math.min(t.top,Math.min(e.y1,e.y2)-s),bottom:Math.max(t.bottom,Math.max(e.y1,e.y2)+s)}}return t}function b(r,n){return n.map(t=>te(r,t)).filter(t=>t!==void 0).reduce((t,i)=>t===void 0?i:{left:Math.min(t.left,i.left),right:Math.max(t.right,i.right),top:Math.min(t.top,i.top),bottom:Math.max(t.bottom,i.bottom)},void 0)}function ne(r,n){const t=r.graphicSpec.objects[n];return(t.items?.[0]?.properties??t.properties).x}function B(r,n,t){const i=ee(r,n);if(i.labelId===void 0||i.symbolIds.length===0)throw new Error(`Legend lane requires complete ${n} graphics.`);const o=[...i.symbolIds,i.labelId,...i.titleId===void 0?[]:[i.titleId]],e=b(r,o),s=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"?[n==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:i.symbolIds,a=b(r,s),f=b(r,i.symbolIds),u=M(r.graphicSpec,i.labelId,r.materializationConfigs.textMetrics),c=r.graphicSpec.objects[i.labelId].items?.length===0,d=i.titleId===void 0?void 0:(()=>{const g=r.graphicSpec.objects[i.titleId],p=M(r.graphicSpec,i.titleId,r.materializationConfigs.textMetrics);return{x:g.properties.x,y:g.properties.y,fontSize:g.properties.fontSize,width:p.right-p.left,bounds:p}})();if([e,a,f].includes(void 0)||u===void 0&&!c)throw new Error(`Legend lane could not measure ${n} graphics.`);const l=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"&&(t.gradientDirection==="horizontal"||t.titlePosition==="bottom");return{id:n,kind:n,preserveAlignment:l,preserveGrid:l||(["series","color","stroke"].includes(n)||t.representation==="symbols")&&(t.columns??1)>1,target:t.target,border:O(n,t),offset:Z(t),bounds:e,title:d,symbol:{centerX:X(a.left,a.right),left:f.left,right:f.right,bounds:a},labels:{x:c?f.right+t.labels.offset:ne(r,i.labelId),width:c?0:u.right-u.left},occupiedBounds:b(r,[...o,...i.backgroundId===void 0?[]:[i.backgroundId]]),...i,foregroundIds:o}}function O(r,n){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size","colorSize"].includes(r)?n.border:!1}function j(r,n){const t=[];for(let i=0;i<r.length;i+=1){const o=r[i],e=n[o.kind],s=r[i+1];if(["series","color","stroke"].includes(o.kind)&&s?.kind==="size"&&s.target===o.target){t.push({id:`${o.kind}+size`,blocks:[o,s.backgroundId===void 0?s:{...s,bounds:s.occupiedBounds,inset:s.border.padding+s.border.lineWidth/2}],border:O(o.kind,e),backgroundId:o.backgroundId}),i+=1;continue}t.push({id:o.kind,blocks:[o],border:O(o.kind,e),backgroundId:o.backgroundId})}return t}function x(r,n){const t=r.guideConfigs.legend??{},i=new Map(r.semanticSpec.layers.map((o,e)=>[o.id,e]));return Object.entries(t).filter(([o,e])=>z(e)===void 0&&R(r,e.target)===void 0&&V(r,o,e)===n).sort(([o,e],[s,a])=>i.get(e.target)-i.get(a.target)||y[o]-y[s])}function oe(r){return["right","left"].some(n=>x(r,n).length>0)}function ie(r){return["top","bottom"].some(n=>x(r,n).some(([,t])=>D({...t,position:n})))}function Ie(r){return Object.values(r.guideConfigs.legend??{}).some(n=>z(n)!==void 0)||oe(r)||ie(r)}function P(r,n){const t=o=>o.textBaseline==="middle"?{...o,y:o.y-o.fontSize/2,textBaseline:"top"}:o;let i=r;for(const o of n){const e=i.graphicSpec.objects[o];if(e.type==="text")if(e.items!==void 0){if(!e.items.some(s=>s.properties.textBaseline==="middle"))continue;i=i.editGraphics({target:o,property:"items",value:e.items.map(s=>({type:s.type??e.type,properties:t(s.properties)}))})}else e.properties.textBaseline==="middle"&&(i=i.editGraphics({target:o,property:"y",value:t(e.properties).y}).editGraphics({target:o,property:"textBaseline",value:"top"}))}return i}function E(r,n){return n.map(t=>{const i=t.blocks[0],o=r.guideConfigs.legend?.[i.kind],e=t.blocks.length>1,s=e?void 0:i.titleId,a=t.blocks.flatMap(l=>l.foregroundIds).filter(l=>l!==s);e&&a.push(...t.blocks.slice(1).flatMap(l=>l.backgroundId===void 0?[]:[l.backgroundId]));const f=b(r,a),u=b(r,[...a,...s===void 0?[]:[s]]);if(f===void 0||u===void 0)throw new Error(`Legend lane could not measure ${t.id} content.`);const c=t.border,d=c===!1?0:c.padding+c.lineWidth/2;return{id:t.id,atomic:e,titleId:s,contentIds:a,title:e?void 0:i.title,inline:o?.titlePosition==="left",element:e?u:i.symbol.bounds,content:f,horizontal:{left:u.left,right:u.right,top:u.top,bottom:u.bottom},inset:d,padding:c===!1?0:c.padding,backgroundId:t.backgroundId}})}function q(r,n,t){let i=r;const o=new Map(n.map(e=>[e.id,e]));for(const e of t.placements){const s=o.get(e.id);s.titleId!==void 0&&(i=m(i,s.titleId,e.dx,e.titleDy));for(const a of s.contentIds)i=m(i,a,e.dx,e.contentDy);e.background!==void 0&&(i=v(i,e.background.id,e.background))}return t.background!==void 0&&(i=v(i,t.background.id,t.background)),i}const ye=G({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(r={}){if(S(r,"rematerializeSideLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{},o=["right","left"].flatMap(s=>{const a=x(this,s);if(a.length===0)return[];if(a.length===1)return[{side:s,entries:a}];const f=a.map(([c,d])=>B(this,c,d));return[{plan:J({side:s,plot:n,canvas:t,groups:j(f,i)}),blocks:f,side:s,entries:a}]});let e=this;for(const{plan:s,blocks:a,side:f,entries:u}of o){if(s===void 0)continue;const c=new Map(a.map(d=>[d.id,d]));for(const d of s.placements){const l=c.get(d.id);l.titleId!==void 0&&(e=m(e,l.titleId,d.titleDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.titleId,property:"textAlign",value:"left"})));for(const g of l.symbolIds)e=m(e,g,d.symbolDx,d.dy);e=m(e,l.labelId,d.labelDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.labelId,property:"textAlign",value:"left"}))}for(const d of a){if(d.backgroundId===void 0||s.backgrounds.some(p=>p.id===d.backgroundId))continue;const l=b(e,d.foregroundIds),g=K([l],d.border,t,"Legend");e=T(e,d.backgroundId,g,d.border)}for(const d of s.backgrounds)e=v(e,d.id,d)}for(const{side:s,entries:a}of o){const f=a.flatMap(([d])=>h(e,d));e=P(e,f);const u=b(e,f),{dy:c}=N({side:s,plot:n,canvas:t,bounds:u});for(const d of f)e=m(e,d,0,c)}return e}),xe=G({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(r={}){if(S(r,"rematerializeHorizontalLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{};let o=this;for(const e of["top","bottom"]){const s=x(o,e).filter(([,c])=>D({...c,position:e})).map(([c,d])=>[c,F(o,c,d)]);if(s.length===0)continue;if(s.length===1){const[c,d]=s[0],l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k);continue}for(const[c,d]of s){if(d.inferredOffset!==!0||I(o,d,n)===d.offset)continue;const l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k)}let a=j(s.map(([c,d])=>B(o,c,d)),i);for(const c of a.filter(d=>d.blocks.length>1)){const d=i[c.blocks[0].kind],l=E(o,c.blocks.map((p,k)=>({id:p.id,blocks:[p],border:k===0?!1:p.border,backgroundId:k===0?void 0:p.backgroundId}))).map(p=>({...p,element:p.content})),g=_({edge:e,plot:n,canvas:t,groups:l,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0,align:d.align,offset:I(o,d,n),border:c.border,backgroundId:c.backgroundId});o=q(o,l,g)}if(a.length<2)continue;a=j(s.map(([c,d])=>B(o,c,d)),i);const f=E(o,a),u=Y({edge:e,plot:n,canvas:t,groups:f,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0});o=q(o,f,u)}return o}),Le=G({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(r={}){if(S(r,"rematerializeLegendGroups"),this.context.facetLegendIntrinsic===!0)return this;let n=this;const t=C(n),i=w(n)?.properties;for(const[o,e]of Object.entries(n.guideConfigs.legendGroups??{})){const s=Object.entries(n.guideConfigs.legend??{}).filter(([,c])=>c.target===o&&z(c)===void 0).sort(([c],[d])=>y[c]-y[d]);n=P(n,s.flatMap(([c])=>h(n,c)));const a=s.map(([c])=>({id:c,ids:h(n,c),bounds:b(n,h(n,c))})),f=e.placement.at!==void 0?e:{...e,placement:{...e.placement,offset:I(n,{...e.placement,inferredOffset:e.inferredOffset},t)}},u=A({blocks:a,group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of u){const d=a.find(l=>l.id===c.id);for(const l of d.ids)n=m(n,l,c.dx,c.dy)}}for(const[o,e]of Object.entries(n.guideConfigs.legend??{})){const s=$(e);if(s===void 0)continue;const a=h(n,o);n=P(n,a);const f={placement:{...s,offset:I(n,s,t)},blockLayout:"column"},[u]=A({blocks:[{id:o,bounds:b(n,a)}],group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of a)n=m(n,c,u.dx,u.dy)}return n});export{ie as hasHorizontalLegendLane,Ie as hasLegendLane,oe as hasSideLegendLane,xe as rematerializeHorizontalLegendLane,Le as rematerializeLegendGroups,ye as rematerializeSideLegendLane};
@@ -46,8 +46,9 @@ export function resolveCategoricalLegendRevision(program, kind, previous, channe
46
46
  const layer = findLayer(program, previous.target);
47
47
  const definition = resolveDefinition(program, layer, channels,
48
48
  previous.inferredTitle ? undefined : previous.title, order);
49
+ const {combine: _combine, sizeAreas: _areas, ...shared} = previous;
49
50
  const config = {
50
- ...previous,
51
+ ...shared,
51
52
  ...definition,
52
53
  symbol: normalizeRecipe(resolveLegendSymbol(
53
54
  program,
@@ -1,3 +1,4 @@
1
+ import { editFacetParentLegend } from "../../../materialization/facetGuides/edit.js";
1
2
  import { closedAction } from "../../../core/action.js";
2
3
 
3
4
  import {
@@ -15,6 +16,7 @@ const OPTIONS = Object.freeze(["target", "channels"]);
15
16
  function legendKindChannels(kind, config) {
16
17
  if (["series", "color", "stroke"].includes(kind)) return config.channels;
17
18
  return {
19
+ colorSize: ["color","size"],
18
20
  size: ["size"],
19
21
  gradient: ["color"],
20
22
  interval: ["color"],
@@ -38,7 +40,7 @@ function resolveRequestedRemoval(program, target, channels) {
38
40
  if (overlap.length === 0) continue;
39
41
  kinds.push(kind);
40
42
  for (const channel of overlap) matched.add(channel);
41
- if (overlap.length !== owned.length) {
43
+ if (overlap.length !== owned.length && !["size-shape","color-size"].includes(config.combine)) {
42
44
  partial = { kind, config, channels: owned.filter(channel => !requested.has(channel)) };
43
45
  }
44
46
  }
@@ -62,14 +64,15 @@ function resolveRequestedRemoval(program, target, channels) {
62
64
  export function removeOwnedColorLegends(program, target) {
63
65
  const kinds = Object.entries(program.guideConfigs.legend ?? {})
64
66
  .filter(([kind, config]) => config?.target === target && (
65
- config.channels?.includes("color") || ["gradient", "interval"].includes(kind)
67
+ config.channels?.includes("color") || ["gradient", "interval", "colorSize"].includes(kind)
66
68
  )).map(([kind]) => kind);
67
69
  return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
68
70
  }
69
71
 
70
72
  export const removeLegend = /* @__PURE__ */ closedAction(
71
- { op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." }, OPTIONS,
73
+ { op: "removeLegend", description: "Remove selected legend content or every block owned by one mark.", scope: "any" }, OPTIONS,
72
74
  function (args = {}) {
75
+ if (this.compositionSpec !== undefined) return editFacetParentLegend(this, "removeLegend", args);
73
76
  const target = resolveLegendTarget(this, args.target, "removeLegend");
74
77
  const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
75
78
  .filter(([, config]) => config?.target === target)
@@ -1,3 +1,6 @@
1
+ import{validateLegendClip,clippedSampleBounds}from"../../../grammar/legendClip.js";
2
+ import{hasLegendClipGraphics}from"../../../materialization/legendClip.js";
3
+ import { resolveDisplayLabel } from "../../../grammar/displayLabels.js";
1
4
  import { intrinsicLegendConfig, legendGroup } from "../../../materialization/legendGroups.js";
2
5
  import { formatVisibleText } from "../../../core/textMetrics.js";
3
6
  import { action, closedAction } from "../../../core/action.js";
@@ -69,6 +72,7 @@ function requireScale(program, id) {
69
72
  }
70
73
 
71
74
  export function resolveSizeLegendLayout(program, config) {
75
+ validateLegendClip("size",config);
72
76
  config = intrinsicLegendConfig(program, config);
73
77
  const scale = requireScale(program, config.scale);
74
78
  const categorical = [
@@ -88,7 +92,7 @@ export function resolveSizeLegendLayout(program, config) {
88
92
  const labels = effective.labels;
89
93
  const titleStyle = effective.titleStyle;
90
94
  // Group ownership replaces placement inheritance, never shared typography.
91
- const placementCategorical = legendGroup(program, config.target) === undefined &&
95
+ const placementCategorical = config.blockLocalPosition === undefined && legendGroup(program, config.target) === undefined &&
92
96
  (config.inheritPosition !== false || categorical?.position === config.position)
93
97
  ? categorical : undefined;
94
98
  const position = placementCategorical?.position ?? config.position;
@@ -134,24 +138,29 @@ export function resolveSizeLegendLayout(program, config) {
134
138
  "quantitative",
135
139
  config.labels.format
136
140
  );
137
- const text = hiddenCount > 0 ? [...itemText, `…${hiddenCount} entries`] : itemText;
141
+ const mappedText = !discrete && !ordinalSize && effective.labelMap !== undefined
142
+ ? itemText.map((label, index) => resolveDisplayLabel(values[index], effective.labelMap, () => label))
143
+ : itemText;
144
+ const text = hiddenCount > 0 ? [...mappedText, `…${hiddenCount} entries`] : mappedText;
138
145
  const symbolStroke = effective.blockSymbol?.strokeWidth ?? 0;
139
146
  const symbolExtent = symbolStroke / 2;
140
147
  const sampleWidth = width + symbolStroke;
141
148
  const layout = resolveLegendItemLayout(plot, { ...effective, ...geometry, labels, titleStyle }, text, {
142
- width: sampleWidth, height: radius * 2 + symbolStroke,
143
- itemBounds: radii.map(r => ({
149
+ width: sampleWidth, height: Math.min(radius * 2 + symbolStroke, effective.clipHeight??Infinity),
150
+ itemBounds: radii.map(r => clippedSampleBounds({
144
151
  left: sampleWidth / 2 - r - symbolExtent,
145
152
  right: sampleWidth / 2 + r + symbolExtent,
146
153
  top: -r - symbolExtent,
147
154
  bottom: r + symbolExtent
148
- }))
155
+ },effective.clipHeight))
149
156
  }, undefined, program.materializationConfigs.textMetrics);
150
157
  assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...effective, ...geometry });
151
158
  const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Size legend", { ...effective, ...geometry });
152
159
  return { ...layout, symbolX: layout.symbolX.slice(0, radii.length).map(x => x + sampleWidth / 2),
153
160
  symbolY: layout.itemY.slice(0, radii.length), radii, text,
154
- labels, titleStyle, background, config: effective };
161
+ ...(effective.labelMap === undefined ? {} : {
162
+ labelItemIndices: text.flatMap((label, index) => label === "" ? [] : [index])
163
+ }), labels, titleStyle, background, config: effective };
155
164
  }
156
165
 
157
166
  export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
@@ -160,6 +169,7 @@ export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
160
169
  description: "Rematerialize a point-size legend."
161
170
  }, [],
162
171
  function (args = {}) {
172
+ if(hasLegendClipGraphics(this))return this.rematerializeLegend();
163
173
  const config = this.guideConfigs.legend?.size;
164
174
  if (config === undefined) throw new Error("Size legend requires stored configuration.");
165
175
  const layer = findLayer(this, config.target);
@@ -1,3 +1,4 @@
1
+ import {validateLegendClip} from "../../../grammar/legendClip.js";
1
2
  import { intrinsicLegendConfig } from "../../../materialization/legendGroups.js";
2
3
  import { action, closedAction } from "../../../core/action.js";
3
4
  import {
@@ -64,6 +65,7 @@ function requireScale(program, id) {
64
65
  }
65
66
 
66
67
  export function resolveStrokeWidthLegendLayout(program, config) {
68
+ validateLegendClip("strokeWidth", config);
67
69
  config = intrinsicLegendConfig(program, config);
68
70
  const effective = resolveEffectiveLegendBlockConfig(program, "strokeWidth", config);
69
71
  const scale = requireScale(program, config.scale);
@@ -3,18 +3,8 @@ import {
3
3
  LEGEND_CHANNELS,
4
4
  LEGEND_CONFIG_KINDS
5
5
  } from "../../../core/vocabulary.js";
6
- import { isEnumeratedSizeScaleType } from "../../../grammar/scales/index.js";
7
-
8
- export function validateLegendChannels(channels, operation) {
9
- if (!Array.isArray(channels)) throw new TypeError(`${operation} channels must be an array.`);
10
- if (channels.length === 0) throw new Error(`${operation} channels must select at least one channel.`);
11
- const seen = new Set();
12
- for (const channel of channels) {
13
- if (!LEGEND_CHANNELS.includes(channel)) throw new Error(`Unsupported legend channel "${channel}".`);
14
- if (seen.has(channel)) throw new Error(`${operation} channels contains duplicate "${channel}".`);
15
- seen.add(channel);
16
- }
17
- }
6
+ import { validateLegendChannels, describeLegendBlock } from "../../../grammar/legendBlocks.js";
7
+ export { validateLegendChannels, describeLegendBlock, legendBlockKey, legendBlockChannels } from "../../../grammar/legendBlocks.js";
18
8
 
19
9
  export function legendTargets(program) {
20
10
  return [...new Set(Object.values(program.guideConfigs.legend ?? {})
@@ -40,73 +30,6 @@ export function resolveLegendTarget(program, requested, operation) {
40
30
  return targets[0];
41
31
  }
42
32
 
43
- const CATEGORICAL_KINDS = Object.freeze(["series", "color", "stroke"]);
44
- const SINGLE_CHANNELS = Object.freeze({
45
- size: "size",
46
- opacity: "opacity",
47
- strokeWidth: "strokeWidth",
48
- gradient: "color",
49
- interval: "color",
50
- strokeGradient: "stroke",
51
- strokeInterval: "stroke"
52
- });
53
-
54
- const asciiCompare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
55
-
56
- export function legendBlockChannels(kind, config) {
57
- const channels = CATEGORICAL_KINDS.includes(kind)
58
- ? config?.channels
59
- : SINGLE_CHANNELS[kind] === undefined
60
- ? undefined
61
- : [SINGLE_CHANNELS[kind]];
62
- if (!Array.isArray(channels) || channels.length === 0) {
63
- throw new Error(`Legend block ${kind} requires at least one channel.`);
64
- }
65
- validateLegendChannels(channels, `Legend block ${kind}`);
66
- return Object.freeze([...channels].sort(asciiCompare));
67
- }
68
-
69
- export function legendBlockKey(channels) {
70
- if (!Array.isArray(channels) || channels.length === 0) {
71
- throw new TypeError("Legend block key requires a non-empty channel array.");
72
- }
73
- return JSON.stringify([...channels].sort(asciiCompare));
74
- }
75
-
76
- export function describeLegendBlock(program, kind, config) {
77
- if (!LEGEND_CONFIG_KINDS.includes(kind)) {
78
- throw new Error(`Unknown legend block kind "${kind}".`);
79
- }
80
- const channels = legendBlockChannels(kind, config);
81
- const scaleIds = CATEGORICAL_KINDS.includes(kind)
82
- ? config?.scales
83
- : [config?.scale];
84
- if (!Array.isArray(scaleIds) || scaleIds.length === 0 ||
85
- scaleIds.some(id => typeof id !== "string" || program.resolvedScales[id] === undefined)) {
86
- throw new Error(`Legend block ${kind} requires resolved scales.`);
87
- }
88
- const family = CATEGORICAL_KINDS.includes(kind)
89
- ? "categorical"
90
- : kind === "size"
91
- ? isEnumeratedSizeScaleType(program.resolvedScales[scaleIds[0]].type)
92
- ? "discrete-size"
93
- : "sampled"
94
- : ["opacity", "strokeWidth"].includes(kind) || kind === "gradient" && config.representation === "symbols"
95
- ? "sampled"
96
- : ["gradient", "strokeGradient"].includes(kind)
97
- ? "gradient"
98
- : "interval";
99
- return Object.freeze({
100
- target: config.target,
101
- kind,
102
- family,
103
- key: legendBlockKey(channels),
104
- channels,
105
- scaleIds: Object.freeze([...scaleIds]),
106
- config
107
- });
108
- }
109
-
110
33
  export function legendBlockDescriptors(program, target) {
111
34
  const requested = validateUserId(target, "Legend target id");
112
35
  const descriptors = LEGEND_CONFIG_KINDS.flatMap(kind => {
@@ -58,7 +58,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
58
58
  const custom = sampledColor ? false : categorical
59
59
  ? categoricalFamilyCustom(program, from, stored)
60
60
  : gradientFamily
61
- ? stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
61
+ ? stored.gradientDirection !== undefined || stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
62
62
  : !same(comparableSymbol, defaults.symbol) || stored.itemGap !== defaults.itemGap ||
63
63
  stored.direction !== defaults.direction || (stored.columns !== undefined && !(side && stored.columns === 1));
64
64
  if (custom) {
@@ -161,7 +161,7 @@ function withoutBlockOverrides(config) {
161
161
  return rest;
162
162
  }
163
163
 
164
- export function planLegendBlockTransitions(program, target, plans) {
164
+ export function planLegendBlockTransitions(program, target, plans, position) {
165
165
  const oldDescriptors = legendBlockDescriptors(program, target);
166
166
  const nextDescriptors = plans.map(plan =>
167
167
  describeLegendBlock(program, plan.kind, plan.config));
@@ -169,7 +169,11 @@ export function planLegendBlockTransitions(program, target, plans) {
169
169
  const descriptor = nextDescriptors[index];
170
170
  const incoming = oldDescriptors.filter(old => intersects(old, descriptor));
171
171
  let override;
172
- if (incoming.length === 1 && incoming[0].key === descriptor.key) {
172
+ if (["size-shape","color-size"].includes(descriptor.config.combine) && incoming.length > 1) {
173
+ const positions = incoming.map(old=>old.config.blockOverrides?.[old.key]?.position);
174
+ if (position === undefined && !positions.every(value=>value===positions[0])) transitionError(target, incoming, descriptor, "position");
175
+ override = position === undefined && positions[0] !== undefined ? {position: positions[0]} : undefined;
176
+ } else if (incoming.length === 1 && incoming[0].key === descriptor.key) {
173
177
  override = incoming[0].config.blockOverrides?.[incoming[0].key];
174
178
  if (incoming[0].config.representation === "symbols" && descriptor.kind === "gradient" && descriptor.config.representation !== "symbols" && override !== undefined) {
175
179
  const { symbol: _symbol, labelMap: _labelMap, ...common } = override; override = common;
@@ -177,14 +181,29 @@ export function planLegendBlockTransitions(program, target, plans) {
177
181
  } else if (incoming.length === 1) {
178
182
  const old = incoming[0];
179
183
  override = old.config.blockOverrides?.[old.key];
184
+ if (["size-shape","color-size"].includes(incoming[0].config.combine) && override !== undefined) {
185
+ const {title: _title, ...rest} = override; override = Object.keys(rest).length ? rest : undefined;
186
+ }
187
+ if(old.config.combine==='color-size'&&descriptor.kind==='gradient'&&override!==undefined){
188
+ const {symbol:_symbol,labelMap:_map,gap:_gap,...common}=override;override=Object.keys(common).length?common:undefined;
189
+ }
190
+ if(old.config.combine==='color-size'&&descriptor.kind==='size'){
191
+ const {type:_type,...style}=old.config.symbol;
192
+ override={...override,symbol:{...style,...override?.symbol}};
193
+ }
180
194
  if (override?.title !== undefined) {
181
195
  transitionError(target, incoming, descriptor, "title");
182
196
  }
183
- if (override?.labelMap !== undefined) {
197
+ if (override?.labelMap !== undefined && !(old.config.combine==='color-size'&&descriptor.kind==='size')) {
184
198
  transitionError(target, incoming, descriptor, "labelMap");
185
199
  }
186
200
  } else if (incoming.length > 1) {
187
- const overrides = incoming.map(old => old.config.blockOverrides?.[old.key]);
201
+ const overrides = incoming.map(old => {
202
+ const override = old.config.blockOverrides?.[old.key];
203
+ if (position === undefined || override === undefined) return override;
204
+ const {position: _local, ...rest} = override;
205
+ return Object.keys(rest).length ? rest : undefined;
206
+ });
188
207
  if (!overrides.every(value => sameBlockValue(value, overrides[0]))) {
189
208
  transitionError(target, incoming, descriptor, "block override");
190
209
  }
@@ -1,3 +1,4 @@
1
+ import { cartesianAxisOwners } from "../../selectors/axes.js";
1
2
  import { resolveHistogramBins } from "../../grammar/histogram.js";
2
3
  import {
3
4
  isTransformedScaleType,
@@ -61,12 +62,14 @@ export function valuesFromTickConfig(program, config) {
61
62
  }
62
63
 
63
64
  export function inferGridTickConfig(program, channel, scaleId) {
64
- const axis = program.guideConfigs.axis?.[channel]?.ticks;
65
- if (axis?.scale === scaleId) {
66
- return axis.mode === "values"
67
- ? { mode: "values", values: axis.values }
68
- : { mode: "count", count: axis.count };
65
+ const axes = cartesianAxisOwners(program).filter(owner => owner.channel === channel &&
66
+ owner.config?.ticks?.scale === scaleId).map(owner => owner.config.ticks);
67
+ const policies = axes.map(axis => axis.mode === "values"
68
+ ? { mode: "values", values: axis.values } : { mode: "count", count: axis.count });
69
+ if (new Set(policies.map(policy => JSON.stringify(policy))).size > 1) {
70
+ throw new Error("Grid values require explicit count or values when bound axis tick policies differ.");
69
71
  }
72
+ if (policies.length > 0) return policies[0];
70
73
 
71
74
  const boundaries = inferHistogramBoundaries(program, channel, scaleId);
72
75
  const scale = program.resolvedScales[scaleId];