ggaction 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +2 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +1 -1
- package/src/accessibility.js +13 -0
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/fitting.js +68 -13
- package/src/actions/charts/beeswarm.js +2 -1
- package/src/actions/charts/ecdf.js +2 -1
- package/src/actions/charts/endpoints.js +25 -16
- package/src/actions/charts/heatmap.js +5 -3
- package/src/actions/charts/missing.js +17 -0
- package/src/actions/charts/radar.js +70 -33
- package/src/actions/charts/raincloud.js +18 -4
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/shared.js +17 -0
- package/src/actions/composition/actions.js +24 -7
- package/src/actions/composition/adjacent.js +90 -0
- package/src/actions/composition/index.js +11 -1
- package/src/actions/composition/layer.js +42 -0
- package/src/actions/composition/layerPlan.js +176 -0
- package/src/actions/composition/targets.js +105 -0
- package/src/actions/data/derived.js +3 -0
- package/src/actions/data/filter.js +1 -1
- package/src/actions/data/raincloudFilter.js +3 -1
- package/src/actions/encodings/appearance.js +26 -7
- package/src/actions/encodings/channels.js +1 -0
- package/src/actions/encodings/color/continuous.js +37 -34
- package/src/actions/encodings/color/index.js +57 -43
- package/src/actions/encodings/color/policy.js +15 -0
- package/src/actions/encodings/density/resolve.js +3 -2
- package/src/actions/encodings/density.js +2 -2
- package/src/actions/encodings/offset.js +2 -0
- package/src/actions/encodings/pathOrder.js +2 -0
- package/src/actions/encodings/position/areaAlignment.js +42 -0
- package/src/actions/encodings/position/index.js +37 -3
- package/src/actions/encodings/position/policies/line.js +1 -1
- package/src/actions/encodings/ranged.js +2 -0
- package/src/actions/encodings/remove.js +13 -10
- package/src/actions/encodings/ruleAppearance.js +14 -2
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/guides/axes/axes.js +2 -1
- package/src/actions/guides/axes/edit.js +1 -1
- package/src/actions/guides/axes/labels.js +2 -1
- package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
- package/src/actions/guides/axes/parallel/policy.js +5 -2
- package/src/actions/guides/axes/parallel/resolve.js +3 -1
- package/src/actions/guides/axes/parallel.js +9 -4
- package/src/actions/guides/axes/titles.js +1 -1
- package/src/actions/guides/facadeLegend.js +3 -2
- package/src/actions/guides/grids/grid.js +3 -2
- package/src/actions/guides/grids/resolve.js +2 -1
- package/src/actions/guides/guides.js +3 -2
- package/src/actions/guides/legends/automatic.js +28 -0
- package/src/actions/guides/legends/blocks.js +15 -21
- package/src/actions/guides/legends/categorical/actions.js +1 -1
- package/src/actions/guides/legends/categorical/components.js +4 -3
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +9 -14
- package/src/actions/guides/legends/categorical/recipes.js +3 -0
- package/src/actions/guides/legends/categorical/resolve.js +7 -5
- package/src/actions/guides/legends/colorSize.js +51 -14
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/lifecycle.js +2 -2
- package/src/actions/guides/legends/remove.js +18 -4
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/transition.js +15 -6
- package/src/actions/guides/polar/axes/facade.js +4 -2
- package/src/actions/guides/polar/axes/labels.js +6 -1
- package/src/actions/guides/polar/axes/shared.js +4 -4
- package/src/actions/guides/polar/axes/titles.js +13 -2
- package/src/actions/guides/polar/grids.js +6 -3
- package/src/actions/guides/tickValues.js +7 -1
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +9 -2
- package/src/actions/marks/area/materialize.js +1 -1
- package/src/actions/marks/bar/materialize.js +4 -4
- package/src/actions/marks/lifecycle.js +14 -0
- package/src/actions/marks/line/actions.js +3 -0
- package/src/actions/marks/line/materialize.js +4 -2
- package/src/actions/marks/point/edit.js +2 -0
- package/src/actions/marks/point/jitter.js +2 -0
- package/src/actions/marks/point/materialize.js +5 -4
- package/src/actions/marks/point/packing.js +2 -0
- package/src/actions/marks/references.js +5 -3
- package/src/actions/marks/remove.js +16 -0
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +5 -2
- package/src/actions/overlays/index.js +168 -0
- package/src/actions/overlays/snapshot.js +45 -0
- package/src/actions/primitives/editGraphics.js +21 -27
- package/src/actions/primitives/semanticAction.js +4 -5
- package/src/actions/primitives/semanticValidation/guide.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +7 -0
- package/src/actions/scales/consumers/common.js +10 -0
- package/src/actions/scales/consumers/families.js +1 -1
- package/src/actions/scales/preview.js +1 -1
- package/src/actions/selection/actions.js +63 -11
- package/src/actions/titles/actions.js +3 -14
- package/src/actions/titles/resolve.js +12 -9
- package/src/core/action.js +41 -25
- package/src/core/compositionScales.js +12 -0
- package/src/core/compositionState.js +16 -2
- package/src/core/immutable.js +11 -6
- package/src/core/programState.js +16 -0
- package/src/core/resourceReferences.js +19 -3
- package/src/core/validation.js +7 -0
- package/src/core/vocabulary.js +8 -2
- package/src/grammar/actionTargets.js +24 -0
- package/src/grammar/bin2d.js +7 -0
- package/src/grammar/colorSizeLegend.js +56 -5
- package/src/grammar/displayLabels.js +10 -0
- package/src/grammar/facetHeaders.js +1 -1
- package/src/grammar/facets/dependencies.js +10 -3
- package/src/grammar/facets/guides.js +2 -1
- package/src/grammar/facets/legendIntent.js +7 -5
- package/src/grammar/legendBlocks.js +1 -1
- package/src/grammar/legendCombinations.js +1 -1
- package/src/grammar/legendGroups.js +1 -1
- package/src/grammar/legendItemsOffset.js +12 -0
- package/src/grammar/legendOverflow.js +25 -0
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/markSelection.js +23 -0
- package/src/grammar/overlay.js +51 -0
- package/src/grammar/parallelCoordinates.js +1 -208
- package/src/grammar/pathCommands.js +8 -2
- package/src/grammar/pathSeries.js +1 -1
- package/src/grammar/plotComposition.js +152 -0
- package/src/grammar/pointShapes.js +18 -0
- package/src/grammar/scales/transformed.js +1 -1
- package/src/grammar/schemas/concreteGraphic.js +8 -1
- package/src/grammar/schemas/graphicBounds.js +8 -1
- package/src/grammar/schemas/graphicTree.js +18 -2
- package/src/grammar/schemas/semanticPath.js +3 -0
- package/src/grammar/strokeStyle.js +14 -8
- package/src/grammar/text.js +72 -0
- package/src/inspection.js +1 -1
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/composition.js +30 -4
- package/src/layout/compositionLegend.js +91 -0
- package/src/layout/facetHeaderTitles.js +1 -1
- package/src/layout/labels.js +55 -27
- package/src/layout/legend.js +3 -2
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +7 -6
- package/src/layout/legendLane.js +1 -1
- package/src/layout/legendText.js +1 -1
- package/src/materialization/adjacentSource.js +27 -0
- package/src/materialization/composition.js +165 -52
- package/src/materialization/compositionLegends.js +120 -0
- package/src/materialization/compositionScales.js +125 -0
- package/src/materialization/compositionTitle.js +96 -0
- package/src/materialization/facetGuides/edit.js +22 -2
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/owner.js +4 -3
- package/src/materialization/facetGuides/ownerLayout.js +12 -6
- package/src/materialization/facetGuides/placement.js +19 -22
- package/src/materialization/facetGuides/preparation.js +17 -5
- package/src/materialization/facetHeaders.js +5 -1
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/guides/visibility.js +105 -0
- package/src/materialization/legendClip.js +1 -1
- package/src/materialization/legendGroups.js +1 -1
- package/src/materialization/legendItemsOffset.js +68 -0
- package/src/materialization/legendSuppression.js +22 -0
- package/src/materialization/legends.js +3 -2
- package/src/materialization/marks/capabilities.js +4 -0
- package/src/materialization/marks/index.js +5 -1
- package/src/materialization/marks/pathOrder.js +1 -1
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/overlayItems.js +154 -0
- package/src/materialization/scales/policies/binnedPosition.js +43 -5
- package/src/materialization/scales/policies/series.js +12 -4
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/bar.js +21 -3
- package/src/materialization/selection/items/common.js +18 -5
- package/src/materialization/selection/items/overlay.js +15 -0
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/items/point.js +2 -3
- package/src/materialization/selection/items/tick.js +3 -12
- package/src/materialization/selection/path.js +2 -2
- package/src/materialization/selection/policies/bar.js +2 -0
- package/src/materialization/selection/policies/index.js +35 -1
- package/src/materialization/selection/policies/point.js +2 -0
- package/src/materialization/selection/policies/text.js +17 -0
- package/src/materialization/selection/rows.js +103 -0
- package/src/materialization/selection/state.js +19 -3
- package/src/materialization/selection/styles.js +8 -0
- package/src/materialization/text.js +6 -489
- package/src/materialization/textGeometry.js +503 -0
- package/src/materialization/textLines.js +20 -0
- package/src/persistence/validation.js +97 -24
- package/src/renderers/canvas/index.js +12 -7
- package/src/renderers/canvas/text.js +5 -5
- package/src/renderers/nativeBackend.js +25 -0
- package/src/renderers/pdf.js +3 -3
- package/src/renderers/png.js +4 -3
- package/src/renderers/svgSerialization.js +1 -643
- package/src/scene.js +5 -3
- package/src/selectors/plotComposition.js +115 -0
- package/src/selectors/selectionTargets.js +19 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +2 -2
- package/types/index.d.ts +11 -0
- package/types/inspection.d.ts +34 -0
- package/types/program.d.ts +553 -365
|
@@ -1 +1 @@
|
|
|
1
|
-
import{validateLegendClip as H}from"../../../../grammar/legendClip.js";import{hasLegendClipGraphics as I}from"../../../../materialization/legendClip.js";import{normalizeColorSymbols as N,resolveColorSymbolsLayout as U,materializeColorSymbols as j}from"./colorSymbols.js";import{alignLegendTitle as Z,resolveLegendTitleText as J}from"../../../../layout/legendText.js";import{resolveLegendContentSpacing as K}from"../../../../layout/legendSpacing.js";import{validateLegendSpacing as Q}from"../../../../grammar/legendSpacing.js";import{intrinsicLegendConfig as X}from"../../../../materialization/legendGroups.js";import{mapContinuousScaleValues as Y,transformedTicks as W}from"../../../../grammar/scales/index.js";import{action as ee,closedAction as te}from"../../../../core/action.js";import{isPlainObject as ie}from"../../../../core/immutable.js";import{normalizeLegendGradient as ne,DEFAULT_LEGEND_GRADIENT as re}from"../../../../grammar/legendGradient.js";import{mapScaleConsumerValues as oe}from"../../../../materialization/scales/map.js";import{inverseLerp as se,interpolateNumber as le}from"../../../../grammar/numeric.js";import{DEFAULT_COLORS as ae}from"../../../../theme/defaults.js";import{assertLegendBoundsInsideCanvas as de,editLegendBackground as pe,editGraphicProperties as M,formatContinuousValues as ue,normalizeContinuousLegend as me,requireResolvedLegendScale as ge,resolveContinuousBounds as ce,resolveContinuousColorLayer as fe,resolveLegendBackgroundFromBounds as ye,resolveLegendTextBounds as F,sampleContinuousValues as he,styleContinuousText as P}from"./common.js";import{resolveLegendGraphicPlacement as C}from"../../../../materialization/graphicHierarchy.js";import{resolveEffectiveLegendBlockConfig as xe}from"../blocks.js";import{readLegendSampling as be,validateSamplingAgainstScale as ve}from"../sampling.js";const He=re;function _(d,e,t,l="Gradient legend",a=0){if(H("gradient",e),Q("gradient",e),e.representation==="symbols")return U(d,e,t);e=X(d,e);const{plot:n,canvas:r}=ce(d),g=e.gradientDirection===void 0?["right","left"].includes(e.position):e.gradientDirection==="vertical",$=g?e.gradient.thickness:e.gradient.length,z=g?e.gradient.length:e.gradient.thickness,m=e.gradient.length,s=e.gradient.thickness,y=e.titleVisible===!1?{height:0,extraHeight:0}:J(e.title,e.titleStyle,d.materializationConfigs.textMetrics),f=e.blockGap??12;let p,i;e.position==="right"?(p=n.x+n.width+e.offset,i=n.y+46+f-12+y.extraHeight/2):e.position==="left"?(p=n.x-e.offset-$,i=n.y+46+f-12+y.extraHeight/2):(p=e.align==="left"?n.x:e.align==="right"?n.x+n.width-$:n.x+(n.width-$)/2,i=e.position==="top"?n.y-e.offset-z-e.labels.offset-e.labels.fontSize:n.y+n.height+e.offset+(e.titleVisible===!1?0:y.height+f));let u=g?{x:p,y:["right","left"].includes(e.position)?n.y+20:i-f-y.height/2,align:"left"}:{x:p+m/2,y:i-f-y.height/2,align:"center"};const h=["log","pow","sqrt","symlog"].includes(t.type),S=ve(be(e),t,l),L=S.mode==="values",x=L?[...S.values]:h?W(t.type,t.domain,e.count,t.type==="log"?{base:t.base}:t.type==="pow"?{exponent:t.exponent}:t.type==="symlog"?{constant:t.constant}:{}):[...he(t.domain,e.count)];!L&&t.midpoint!==void 0&&!x.includes(t.midpoint)&&(x.push(t.midpoint),x.sort((o,c)=>t.domain[1]>t.domain[0]?o-c:c-o));const T=ue(x,t.domain,e.fieldType,e.labels.format),k=h||L?Y(x,{...t,range:[0,1]}):x.map((o,c)=>!L&&t.midpoint===void 0?c/(x.length-1):se(o,...t.domain)),q=e.labels.offset;let G=g?k.map(o=>({x:e.position==="left"?p-q:p+s+q,y:i+m*(1-o),align:e.position==="left"?"right":"left"})):k.map(o=>({x:p+m*o,y:i+s+q+e.labels.fontSize/2,align:"center"})),B=g?G.map(o=>({x1:e.position==="left"?p:p+s,y1:o.y,x2:e.position==="left"?p-6:p+s+6,y2:o.y})):G.map(o=>({x1:o.x,y1:i+s,x2:o.x,y2:i+s+6}));const b={left:p,right:p+(g?s:m),top:i,bottom:i+(g?m:s)},E=B.map(o=>({left:Math.min(o.x1,o.x2)-.5,right:Math.max(o.x1,o.x2)+.5,top:Math.min(o.y1,o.y2)-.5,bottom:Math.max(o.y1,o.y2)+.5})),V=G.map((o,c)=>F(o,T[c],e.labels,d.materializationConfigs.textMetrics));if(e.titlePosition==="bottom"&&e.titleVisible!==!1){const o=Math.max(b.bottom+a/2,...E.map(c=>c.bottom),...V.map(c=>c.bottom));u={x:g?p:p+m/2,y:o+f+y.height/2,align:g?"left":"center"}}e.titleVisible!==!1&&(u=Z(u,e.title,e.titleStyle,[{left:b.left-a/2,right:b.right+a/2,top:b.top-a/2,bottom:b.bottom+a/2},...E,...V],d.materializationConfigs.textMetrics));const O=e.titleVisible===!1?void 0:F(u,e.title,e.titleStyle,d.materializationConfigs.textMetrics,{hardBreaks:!0});let A=[b,...E,...V,...e.titleVisible===!1?[]:[O]];const w=e.titlePadding===void 0?void 0:K(e,[{row:0,x:p,y:i,bounds:[{left:b.left-a/2,right:b.right+a/2,top:b.top-a/2,bottom:b.bottom+a/2},...E,...V.filter((o,c)=>T[c]!=="")]}],O);if(w!==void 0){const{dx:o,dy:c}=w.offsets[0];p+=o,i+=c,G=G.map(v=>({...v,x:v.x+o,y:v.y+c})),B=B.map(v=>({...v,x1:v.x1+o,x2:v.x2+o,y1:v.y1+c,y2:v.y2+c})),u={...u,x:u.x+w.titleOffset.dx,y:u.y+w.titleOffset.dy},A=w.bounds}de(A,r,`${l} layout`,e);const R=ye(A,e.border,r,l,e);return{vertical:g,x:p,y:i,length:m,thickness:s,values:x,texts:T,labels:G,ticks:B,title:u,background:R}}function D(d,e,t="color"){const l=fe(d,e.target,t),a=l.encoding[t];if(!["quantitative","temporal"].includes(a.fieldType))throw new Error(`${t==="color"?"Gradient":"Stroke gradient"} legend requires quantitative or temporal ${t}.`);const n=ge(d,a.scale,"sequential");return{layer:l,encoding:a,scale:n,config:{...e,target:l.id,scale:a.scale,fieldType:a.fieldType,title:e.inferredTitle?a.field:e.title,domain:n.domain}}}function Le(d,e,t){const l=t!==void 0,a=l?"stroke":"color",n=l?"strokeGradient":"gradient",r=`${a}Gradient`,{scale:g,encoding:$,config:z}=e,m=xe(d,n,z);if(!l&&m.representation==="symbols")return j(d,e,m);const s=_(d,m,g,l?"Stroke gradient legend":"Gradient legend",t?.strokeWidth??0),y=60,f=s.length/y,p=Array.from({length:y},(u,h)=>{const S=(h+.5)/y,L=s.vertical?1-S:S,x=g.midpoint===void 0?{...g,type:"sequential",domain:[0,1]}:g,T=g.midpoint===void 0?L:le(...g.domain,L),[k]=oe([T],x,a);return{x:s.x+(s.vertical?0:h*f),y:s.y+(s.vertical?h*f:0),width:s.vertical?s.thickness:f,height:s.vertical?f:s.thickness,fill:l?t.fill:k,stroke:k,strokeWidth:l?t.strokeWidth:0}});let i=d.editSemantic({property:`guide.legend.${a}.scale`,value:$.scale}).editSemantic({property:`guide.legend.${a}.title`,value:m.title})._withLegendConfig(n,z);l||(i=i.editGraphics({target:`${r}Strips`,property:"length",value:p.length})),i=pe(i,`${r}Background`,s.background,m.border),l&&(i=i.editGraphics({target:`${r}Strips`,property:"length",value:p.length}));for(const u of["x","y","width","height","fill","stroke","strokeWidth"])i=i.editGraphics({target:`${r}Strips`,property:u,value:p.map(h=>h[u])});m.gradient.opacity!==void 0&&(i=i.editGraphics({target:`${r}Strips`,property:"opacity",value:m.gradient.opacity})),i=i.editGraphics({target:`${r}Ticks`,property:"length",value:s.ticks.length});for(const u of["x1","y1","x2","y2"])i=i.editGraphics({target:`${r}Ticks`,property:u,value:s.ticks.map(h=>h[u])});return i=M(i,`${r}Ticks`,{stroke:ae.mutedText,strokeWidth:1}),i=M(i,`${r}Labels`,{length:s.labels.length,x:s.labels.map(u=>u.x),y:s.labels.map(u=>u.y),text:s.texts}),i=P(i,`${r}Labels`,m.labels,{align:s.labels[0].align}),m.titleVisible===!1?i:(i=M(i,`${r}Title`,{x:s.title.x,y:s.title.y,text:m.title}),P(i,`${r}Title`,m.titleStyle,{align:s.title.align}))}const Ie=te({op:"rematerializeGradientLegend",description:"Rematerialize a continuous color gradient legend."},[],function(d={}){if(I(this))return this.rematerializeLegend();const e=this.guideConfigs.legend?.gradient;if(e===void 0)throw new Error("Gradient legend requires stored configuration.");return Le(this,D(this,e))});function Ge(d,e={},t="color"){const{representation:l,...a}=e;if(l!==void 0&&!["gradient","symbols"].includes(l))throw new Error("Legend representation must be gradient or symbols.");if(t!=="color"&&l!==void 0)throw new Error("Legend representation requires continuous color.");const n=l==="symbols"?N(a):me(a,"gradient");if(e.channels!==void 0&&(!Array.isArray(e.channels)||e.channels.length!==1||e.channels[0]!==t))throw new Error(`Gradient legend requires channels: ["${t}"].`);if(e.gradient!==void 0&&!ie(e.gradient))throw new TypeError("createLegend.gradient must be a plain object.");return n.representation!=="symbols"&&(n.gradient=ne(e.gradient)),D(d,n,t)}function Ce(d,e,t="color"){const l=`${t}Gradient`,a=t==="color"?"gradient":"strokeGradient",n=D(d,e,t);_(d,n.config,n.scale,t==="color"?"Gradient legend":"Stroke gradient legend");let r=d.editSemantic({property:`guide.legend.${t}.scale`,value:n.encoding.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.config.title})._withLegendConfig(a,n.config);return n.config.border!==!1&&(r=r.createGraphics({id:`${l}Background`,type:"rect",...C(r)})),n.config.representation==="symbols"?r=r.createGraphics({id:`${l}Symbols`,type:"circle",length:0,...C(r)}):r=r.createGraphics({id:`${l}Strips`,type:"rect",length:0,...C(r,n.config.border===!1?{}:{after:`${l}Background`})}).createGraphics({id:`${l}Ticks`,type:"line",length:0,...C(r)}),r=r.createGraphics({id:`${l}Labels`,type:"text",length:0,...C(r)}),n.config.titleVisible!==!1&&(r=r.createGraphics({id:`${l}Title`,type:"text",...C(r)})),r[t==="color"?"rematerializeGradientLegend":"rematerializeStrokeGradientLegend"]()}const Ne=ee({op:"createGradientLegend",description:"Create a continuous color gradient legend."},function(d={}){const e=Ge(this,d);if(_(this,e.config,e.scale),this.guideConfigs.legend?.gradient!==void 0)throw new Error("createGradientLegend requires a missing gradient legend.");return Ce(this,e.config)});export{He as DEFAULT_GRADIENT_SIZE,Ne as createGradientLegend,Ce as createGradientLegendFromConfig,Le as materializeGradientLegend,Ie as rematerializeGradientLegend,D as resolveGradientConfig,_ as resolveGradientLayout,Ge as resolveGradientLegendCreation};
|
|
1
|
+
import{alignLegendLabels as H}from"../../../../layout/legendText.js";import{inferBin2DFieldTitle as N}from"../../../../grammar/bin2d.js";import{findDataset as j}from"../../../../selectors/datasets.js";import{validateLegendClip as U}from"../../../../grammar/legendClip.js";import{hasLegendClipGraphics as Z}from"../../../../materialization/legendClip.js";import{normalizeColorSymbols as J,resolveColorSymbolsLayout as K,materializeColorSymbols as Q}from"./colorSymbols.js";import{alignLegendTitle as X,resolveLegendTitleText as Y}from"../../../../layout/legendText.js";import{resolveLegendContentSpacing as W}from"../../../../layout/legendSpacing.js";import{validateLegendSpacing as ee}from"../../../../grammar/legendSpacing.js";import{intrinsicLegendConfig as te}from"../../../../materialization/legendGroups.js";import{mapContinuousScaleValues as ie,sampleSymlogValues as ne,transformedTicks as re}from"../../../../grammar/scales/index.js";import{action as oe,closedAction as se}from"../../../../core/action.js";import{isPlainObject as le}from"../../../../core/immutable.js";import{normalizeLegendGradient as ae,DEFAULT_LEGEND_GRADIENT as de}from"../../../../grammar/legendGradient.js";import{mapScaleConsumerValues as pe}from"../../../../materialization/scales/map.js";import{inverseLerp as me,interpolateNumber as ue}from"../../../../grammar/numeric.js";import{DEFAULT_COLORS as ce}from"../../../../theme/defaults.js";import{assertLegendBoundsInsideCanvas as fe,editLegendBackground as ge,editGraphicProperties as A,formatContinuousValues as ye,normalizeContinuousLegend as he,requireResolvedLegendScale as xe,resolveContinuousBounds as be,resolveContinuousColorLayer as Le,resolveLegendBackgroundFromBounds as ve,resolveLegendTextBounds as D,sampleContinuousValues as Ge,styleContinuousText as I}from"./common.js";import{resolveLegendGraphicPlacement as $}from"../../../../materialization/graphicHierarchy.js";import{resolveEffectiveLegendBlockConfig as Ce}from"../blocks.js";import{readLegendSampling as Te,validateSamplingAgainstScale as Se}from"../sampling.js";const Xe=de;function $e(l,e,t){const o=t?"top":"left",d=t?"bottom":"right",n=[],r=new Set([0,e.length-1,e.indexOf(0),...e.map((p,y)=>y)]);for(const p of r)p<0||n.every(y=>l[p][d]+4<=l[y][o]||l[y][d]+4<=l[p][o])&&n.push(p);return n.sort((p,y)=>p-y)}function _(l,e,t,o="Gradient legend",d=0){if(U("gradient",e),ee("gradient",e),e.representation==="symbols")return K(l,e,t);e=te(l,e);const{plot:n,canvas:r}=be(l),p=e.gradientDirection===void 0?["right","left"].includes(e.position):e.gradientDirection==="vertical",y=p?e.gradient.thickness:e.gradient.length,T=p?e.gradient.length:e.gradient.thickness,f=e.gradient.length,a=e.gradient.thickness,v=e.titleVisible===!1?{height:0,extraHeight:0}:Y(e.title,e.titleStyle,l.materializationConfigs.textMetrics),x=e.blockGap??12;let m,i;e.position==="right"?(m=n.x+n.width+e.offset,i=n.y+46+x-12+v.extraHeight/2):e.position==="left"?(m=n.x-e.offset-y,i=n.y+46+x-12+v.extraHeight/2):(m=e.align==="left"?n.x:e.align==="right"?n.x+n.width-y:n.x+(n.width-y)/2,i=e.position==="top"?n.y-e.offset-T-e.labels.offset-e.labels.fontSize:n.y+n.height+e.offset+(e.titleVisible===!1?0:v.height+x));let c=p?{x:m,y:["right","left"].includes(e.position)?n.y+20:i-x-v.height/2,align:"left"}:{x:m+f/2,y:i-x-v.height/2,align:"center"};const b=["log","pow","sqrt","symlog"].includes(t.type),w=Se(Te(e),t,o),S=w.mode==="values";let h=S?[...w.values]:t.type==="symlog"?ne(t.domain,e.count,{constant:t.constant}):b?re(t.type,t.domain,e.count,t.type==="log"?{base:t.base}:t.type==="pow"?{exponent:t.exponent}:t.type==="symlog"?{constant:t.constant}:{}):[...Ge(t.domain,e.count)];!S&&t.midpoint!==void 0&&!h.includes(t.midpoint)&&(h.push(t.midpoint),h.sort((s,u)=>t.domain[1]>t.domain[0]?s-u:u-s));let C=ye(h,t.domain,e.fieldType,e.labels.format);const k=b||S?ie(h,{...t,range:[0,1]}):h.map((s,u)=>!S&&t.midpoint===void 0?u/(h.length-1):me(s,...t.domain)),q=e.labels.offset;let L=p?k.map(s=>({x:e.position==="left"?m-q:m+a+q,y:i+f*(1-s),align:e.position==="left"?"right":"left"})):k.map(s=>({x:m+f*s,y:i+a+q+e.labels.fontSize/2,align:"center"}));if(!S&&b){const s=L.map((g,R)=>D(g,C[R],e.labels,l.materializationConfigs.textMetrics)),u=$e(s,h,p);h=u.map(g=>h[g]),C=u.map(g=>C[g]),L=u.map(g=>L[g])}let B=p?L.map(s=>({x1:e.position==="left"?m:m+a,y1:s.y,x2:e.position==="left"?m-6:m+a+6,y2:s.y})):L.map(s=>({x1:s.x,y1:i+a,x2:s.x,y2:i+a+6}));const G={left:m,right:m+(p?a:f),top:i,bottom:i+(p?f:a)},E=B.map(s=>({left:Math.min(s.x1,s.x2)-.5,right:Math.max(s.x1,s.x2)+.5,top:Math.min(s.y1,s.y2)-.5,bottom:Math.max(s.y1,s.y2)+.5}));L=H(L,C,e.labels,l.materializationConfigs.textMetrics);const V=L.map((s,u)=>D(s,C[u],e.labels,l.materializationConfigs.textMetrics));if(e.titlePosition==="bottom"&&e.titleVisible!==!1){const s=Math.max(G.bottom+d/2,...E.map(u=>u.bottom),...V.map(u=>u.bottom));c={x:p?m:m+f/2,y:s+x+v.height/2,align:p?"left":"center"}}e.titleVisible!==!1&&(c=X(c,e.title,e.titleStyle,[{left:G.left-d/2,right:G.right+d/2,top:G.top-d/2,bottom:G.bottom+d/2},...E,...V],l.materializationConfigs.textMetrics));const F=e.titleVisible===!1?void 0:D(c,e.title,e.titleStyle,l.materializationConfigs.textMetrics,{hardBreaks:!0});let M=[G,...E,...V,...e.titleVisible===!1?[]:[F]];const z=e.titlePadding===void 0?void 0:W(e,[{row:0,x:m,y:i,bounds:[{left:G.left-d/2,right:G.right+d/2,top:G.top-d/2,bottom:G.bottom+d/2},...E,...V.filter((s,u)=>C[u]!=="")]}],F);if(z!==void 0){const{dx:s,dy:u}=z.offsets[0];m+=s,i+=u,L=L.map(g=>({...g,x:g.x+s,y:g.y+u})),B=B.map(g=>({...g,x1:g.x1+s,x2:g.x2+s,y1:g.y1+u,y2:g.y2+u})),c={...c,x:c.x+z.titleOffset.dx,y:c.y+z.titleOffset.dy},M=z.bounds}fe(M,r,`${o} layout`,e);const P=ve(M,e.border,r,o,e);return{vertical:p,x:m,y:i,length:f,thickness:a,values:h,texts:C,labels:L,ticks:B,title:c,background:P}}function O(l,e,t="color"){const o=Le(l,e.target,t),d=o.encoding[t];if(!["quantitative","temporal"].includes(d.fieldType))throw new Error(`${t==="color"?"Gradient":"Stroke gradient"} legend requires quantitative or temporal ${t}.`);const n=xe(l,d.scale,"sequential"),r=Object.entries(l.materializationConfigs.data?.bin2d??{}).find(([,T])=>T.current===o.data)?.[0]??o.data,p=j(l,o.data)?.transform?.find(T=>T.type==="bin2d"),y=N(p,r,d.field);return{layer:o,encoding:d,scale:n,config:{...e,target:o.id,scale:d.scale,fieldType:d.fieldType,title:e.inferredTitle?y:e.title,domain:n.domain}}}function we(l,e,t){const o=t!==void 0,d=o?"stroke":"color",n=o?"strokeGradient":"gradient",r=`${d}Gradient`,{scale:p,encoding:y,config:T}=e,f=Ce(l,n,T);if(!o&&f.representation==="symbols")return Q(l,e,f);const a=_(l,f,p,o?"Stroke gradient legend":"Gradient legend",t?.strokeWidth??0),v=60,x=a.length/v,m=Array.from({length:v},(c,b)=>{const w=(b+.5)/v,S=a.vertical?1-w:w,h=p.midpoint===void 0?{...p,type:"sequential",domain:[0,1]}:p,C=p.midpoint===void 0?S:ue(...p.domain,S),[k]=pe([C],h,d);return{x:a.x+(a.vertical?0:b*x),y:a.y+(a.vertical?b*x:0),width:a.vertical?a.thickness:x,height:a.vertical?x:a.thickness,fill:o?t.fill:k,stroke:k,strokeWidth:o?t.strokeWidth:0}});let i=l.editSemantic({property:`guide.legend.${d}.scale`,value:y.scale}).editSemantic({property:`guide.legend.${d}.title`,value:f.title})._withLegendConfig(n,T);o||(i=i.editGraphics({target:`${r}Strips`,property:"length",value:m.length})),i=ge(i,`${r}Background`,a.background,f.border),o&&(i=i.editGraphics({target:`${r}Strips`,property:"length",value:m.length}));for(const c of["x","y","width","height","fill","stroke","strokeWidth"])i=i.editGraphics({target:`${r}Strips`,property:c,value:m.map(b=>b[c])});f.gradient.opacity!==void 0&&(i=i.editGraphics({target:`${r}Strips`,property:"opacity",value:f.gradient.opacity})),i=i.editGraphics({target:`${r}Ticks`,property:"length",value:a.ticks.length});for(const c of["x1","y1","x2","y2"])i=i.editGraphics({target:`${r}Ticks`,property:c,value:a.ticks.map(b=>b[c])});return i=A(i,`${r}Ticks`,{stroke:ce.mutedText,strokeWidth:1}),i=A(i,`${r}Labels`,{length:a.labels.length,x:a.labels.map(c=>c.x),y:a.labels.map(c=>c.y),text:a.texts}),i=I(i,`${r}Labels`,f.labels,{align:a.labels[0].align}),f.titleVisible===!1?i:(i=A(i,`${r}Title`,{x:a.title.x,y:a.title.y,text:f.title}),I(i,`${r}Title`,f.titleStyle,{align:a.title.align}))}const Ye=se({op:"rematerializeGradientLegend",description:"Rematerialize a continuous color gradient legend."},[],function(l={}){if(Z(this))return this.rematerializeLegend();const e=this.guideConfigs.legend?.gradient;if(e===void 0)throw new Error("Gradient legend requires stored configuration.");return we(this,O(this,e))});function ke(l,e={},t="color"){const{representation:o,...d}=e;if(o!==void 0&&!["gradient","symbols"].includes(o))throw new Error("Legend representation must be gradient or symbols.");if(t!=="color"&&o!==void 0)throw new Error("Legend representation requires continuous color.");const n=o==="symbols"?J(d):he(d,"gradient");if(e.channels!==void 0&&(!Array.isArray(e.channels)||e.channels.length!==1||e.channels[0]!==t))throw new Error(`Gradient legend requires channels: ["${t}"].`);if(e.gradient!==void 0&&!le(e.gradient))throw new TypeError("createLegend.gradient must be a plain object.");return n.representation!=="symbols"&&(n.gradient=ae(e.gradient)),O(l,n,t)}function ze(l,e,t="color"){const o=`${t}Gradient`,d=t==="color"?"gradient":"strokeGradient",n=O(l,e,t);_(l,n.config,n.scale,t==="color"?"Gradient legend":"Stroke gradient legend");let r=l.editSemantic({property:`guide.legend.${t}.scale`,value:n.encoding.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.config.title})._withLegendConfig(d,n.config);return n.config.border!==!1&&(r=r.createGraphics({id:`${o}Background`,type:"rect",...$(r)})),n.config.representation==="symbols"?r=r.createGraphics({id:`${o}Symbols`,type:"circle",length:0,...$(r)}):r=r.createGraphics({id:`${o}Strips`,type:"rect",length:0,...$(r,n.config.border===!1?{}:{after:`${o}Background`})}).createGraphics({id:`${o}Ticks`,type:"line",length:0,...$(r)}),r=r.createGraphics({id:`${o}Labels`,type:"text",length:0,...$(r)}),n.config.titleVisible!==!1&&(r=r.createGraphics({id:`${o}Title`,type:"text",...$(r)})),r[t==="color"?"rematerializeGradientLegend":"rematerializeStrokeGradientLegend"]()}const We=oe({op:"createGradientLegend",description:"Create a continuous color gradient legend."},function(l={}){const e=ke(this,l);if(_(this,e.config,e.scale),this.guideConfigs.legend?.gradient!==void 0)throw new Error("createGradientLegend requires a missing gradient legend.");return ze(this,e.config)});export{Xe as DEFAULT_GRADIENT_SIZE,We as createGradientLegend,ze as createGradientLegendFromConfig,we as materializeGradientLegend,Ye as rematerializeGradientLegend,O as resolveGradientConfig,_ as resolveGradientLayout,ke as resolveGradientLegendCreation};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { alignLegendLabels } from "../../../../layout/legendText.js";
|
|
1
2
|
import{validateLegendClip}from"../../../../grammar/legendClip.js";
|
|
2
3
|
import{hasLegendClipGraphics,applyLegendClipGraphics}from"../../../../materialization/legendClip.js";
|
|
3
4
|
import { intrinsicLegendConfig } from "../../../../materialization/legendGroups.js";
|
|
@@ -197,6 +198,7 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
197
198
|
align: "center"
|
|
198
199
|
};
|
|
199
200
|
}
|
|
201
|
+
labels = alignLegendLabels(labels, texts, config.labels, program.materializationConfigs.textMetrics);
|
|
200
202
|
const labelBounds = labels.map((label, index) =>
|
|
201
203
|
resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
|
|
202
204
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveColorSizeEdit as Y,createColorSizeLegendFromConfig as be}from"./colorSize.js";import{prepareLegendSpacingEdit as $,patchLegendSpacing as pe,validateLegendSpacing as ye}from"../../../grammar/legendSpacing.js";import{patchLegendClipHeight as Z,validateLegendClip as he}from"../../../grammar/legendClip.js";import{validateLegendCombine as Le,validateMappedSizeShapeSymbol as Se}from"../../../grammar/legendCombinations.js";import{legendBlockDescriptors as j}from"./target.js";import{editFacetParentLegend as we}from"../../../materialization/facetGuides/edit.js";import{localLegendPosition as J,localLegendPlacement as ke}from"../../../materialization/legendGroups.js";import{mergeLegendTitleStyle as x}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as Q}from"./group.js";import{prepareLegendGroup as ze}from"./group.js";import{PLOT_LEGEND_POSITIONS as U}from"../../../layout/legend.js";import{withGuideLayoutValidation as Ce}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as Oe}from"./creation.js";import{normalizeLegendOrder as X}from"../../../grammar/categoryOrder.js";import{resolveDefinition as Ee}from"./categorical/resolve.js";import{action as Ge}from"../../../core/action.js";import{validateOptionObject as W}from"../../../core/validation.js";import{normalizeLegendOverflow as ee,normalizeOptions as _e}from"./categorical/options.js";import{resolveLegendSymbol as Pe}from"./categorical/recipes.js";import{resolveLayout as je}from"./categorical/layout.js";import{resolveLegendCreationPlan as te}from"./categorical/actions.js";import{categoricalSymbolIds as Te,reconcileCategoricalSymbols as Ie,createCategoricalLegendFromConfig as $e,resolveCategoricalLegendRevision as xe,removeLegendKinds as ne}from"./lifecycle.js";import{normalizeColorSymbols as Ve}from"./continuous/colorSymbols.js";import{readLegendSampling as De}from"./sampling.js";import{createGradientLegendFromConfig as ie}from"./continuous/gradient.js";import{normalizeLegendGradient as oe}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as Me,normalizeLegendBorder as qe,normalizeLegendTextOptions as le,normalizeLegendTitleOptions as de,normalizeContinuousLegend as re}from"./continuous/common.js";import{normalizeOpacitySymbol as Be,createOpacityLegendFromConfig as Ae}from"./continuous/opacity.js";import{normalizeIntervalLegend as We,resolveIntervalConfig as Fe,resolveIntervalLayout as Ke,createIntervalLegendFromConfig as He}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as Ne,createStrokeIntervalLegendFromConfig as ve,resolveStrokeIntervalConfig as Re,resolveStrokeIntervalLayout as Ye}from"./continuous/stroke.js";import{findLayer as P}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as Ze}from"../../../materialization/graphicHierarchy.js";import{resolveLegendBlock as F,resolveLegendTarget as K,validateLegendChannels as Je}from"./target.js";import{isEnumeratedSizeScaleType as ce}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as se,SIZE_LEGEND_TITLE_STYLE as fe,resolveSizeLegendLayout as Qe,createSizeLegendFromConfig as Ue}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as Xe,STROKE_WIDTH_LEGEND_TITLE_STYLE as et,resolveStrokeWidthLegendLayout as tt,createStrokeWidthLegendFromConfig as nt}from"./strokeWidth.js";import{normalizeLegendSampling as V}from"./sampling.js";import{patchLegendBlockOverride as it}from"./blocks.js";import{LEGEND_POSITIONS as ot}from"../../../core/vocabulary.js";import{planLegendBlockTransitions as lt}from"./transition.js";const ae=Object.freeze(["target","channel","channels","position","layout","align","direction","columns","offset","titlePosition","title","symbol","labels","titleStyle","itemGap","rowPadding","titlePadding","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,...Ze(n,{...o.before===void 0?{}:{before:o.before},...o.after===void 0?{}:{after:o.after}})}):n}function dt(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","values",...o==="symbols"?["symbol","itemGap","layout","direction","columns"]:["gradient","direction"]];for(const g of Object.keys(e))if(!i.includes(g))throw new Error(`Color ${o} legend does not accept ${g}.`);const s=(t.representation??"gradient")!==o,a=e.title,r=a==="auto"?P(n,t.target)?.encoding?.color?.field:typeof a=="string"?a: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:x(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 b;if(o==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&W(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const g=De(t),h=s?g.mode==="values"?{values:g.values}:{count:g.count}:{};for(const d of["values","count"])Object.hasOwn(e,d)&&(h[d]=e[d]);Object.hasOwn(e,"values")&&delete h.count,Object.hasOwn(e,"count")&&(h.count=e.count),b=Ve({...f,...h,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{const g=V(e,{previous:t,operation:"edit",label:"Gradient legend"});b=re({...f,...e.direction==="auto"?{}:e.direction!==void 0?{direction:e.direction}:t.gradientDirection===void 0?{}:{direction:t.gradientDirection},count:g.count},"gradient"),g.mode==="values"&&(b.sampling=g);const h=oe(e.gradient,s?void 0:t.gradient);b.gradient=h}const{representation:c,sampling:S,symbol:l,layout:L,direction:C,columns:z,gradient:k,count:w,...u}=t,y=s&&o==="gradient"&&t.blockOverrides!==void 0?Object.fromEntries(Object.entries(t.blockOverrides).map(([g,h])=>{const{symbol:d,labelMap:m,...p}=h;return[g,p]})):t.blockOverrides;return o==="gradient"&&e.direction==="auto"&&delete u.gradientDirection,{config:{...u,...b,...y===void 0?{}:{blockOverrides:y},inferredTitle:a==="auto"?!0:typeof a=="string"?!1:t.inferredTitle,titleVisible:a===!1?!1:a===void 0?t.titleVisible!==!1:!0},titleMode:a,title:r,titleVisible:a===!1?!1:a===void 0?t.titleVisible!==!1:!0}}function D(n,t,e,o){if({previous:e,args:o}=$(e,o),t==="gradient"&&(e.representation==="symbols"||o.representation!==void 0))return dt(n,e,o);const i=["gradient","strokeGradient"].includes(t),s=i?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","values","gradient","representation","direction"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const m of Object.keys(o))if(!s.includes(m))throw new Error(`${t} legend does not accept ${m}.`);const a=o.title,r=a==="auto"?!0:typeof a=="string"?!1:e.inferredTitle,f=a===!1?!1:a===void 0?e.titleVisible!==!1:!0,b=P(n,e.target),c=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity",S=b?.encoding?.[c]?.field,l=a==="auto"?S:typeof a=="string"?a:e.title,L=o.titlePosition??e.titlePosition,C=t==="opacity"&&L==="left"&&e.titlePosition!=="left",z=T(e.labels,o.labels),k=V(o,{previous:e,operation:"edit",label:i?"Gradient legend":"Opacity legend"}),w=re({target:e.target,position:o.position??e.position,align:o.align??e.align,offset:o.offset??e.offset,...i?{count:k.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:x(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:u,...y}=e,{sampling:g,...h}=e,d={...i?h:y,...w,...i&&k.mode!=="values"?{}:{sampling:k},inferredTitle:r,titleVisible:f};if(i){o.direction==="auto"&&delete d.gradientDirection;const m=oe(o.gradient,e.gradient);d.gradient=m}else d.symbol=Be(o.symbol??e.symbol);return{config:d,titleMode:a,title:l,titleVisible:f}}function M(n,t,e,o){const{config:i,titleMode:s,title:a,titleVisible:r}=D(n,t,e,o);if(t==="gradient"&&(e.representation??"gradient")!==(i.representation??"gradient"))return ie(ne(n,[t]),i).rematerializeLegend();const f=t==="gradient"?"colorGradient":t==="strokeGradient"?"strokeGradient":"opacityLegend",b=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity";let c=n;return(s==="auto"||typeof s=="string")&&(c=c.editSemantic({property:`guide.legend.${b}.title`,value:a})),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 ue(n,t,e,o="color"){({previous:t,args:e}=$(t,e));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),a=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,b=We({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:x(t.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??t.itemGap,border:I(t.border,e.border)}),c={...t,...b,inferredTitle:a,titleVisible:r,...o==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},S=o==="stroke"?Re(n,c):Fe(n,c);return(o==="stroke"?Ye:Ke)(n,S.config,S.scale),{normalized:b,config:c,titleMode:i,title:f,titleVisible:r}}function q(n,t,e,o="color"){const i=o==="stroke"?"strokeInterval":"interval",s=o==="stroke"?"strokeInterval":"colorLegend",{normalized:a,config:r,titleMode:f,title:b,titleVisible:c}=ue(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:b})),S=E(S,`${s}Title`,c,{type:"text"}),S=E(S,`${s}Background`,a.border!==!1,{type:"rect",before:`${s}Symbols`}),S.rematerializeLegend()}function H(n,t,e,o,i=!1){({previous:e,args:o}=$(e,o));const s=t==="size",a=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 g of Object.keys(o))if(!r.includes(g))throw new Error(`${a} legend does not accept ${g}.`);const f=s&&ce(n.resolvedScales[e.scale]?.type),b=s&&n.resolvedScales[e.scale]?.type==="ordinal";if(o.overflow!==void 0&&!b)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:V(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,y={...f?e:w,title:z,inferredTitle:L,titleVisible:C,...f?{}:{sampling:c},...b&&(o.overflow!==void 0||e.overflow!==void 0)?{overflow:ee(o.overflow===void 0?e.overflow:o.overflow)}:{},...s?{inheritAppearance:i&&e.inheritAppearance===!0,...o.position===void 0?{}:{inheritPosition:!1}}:{},labels:le(o.labels,"editLegend.labels",e.labels??(s?se:Xe)),titleStyle:de(o.titleStyle,"editLegend.titleStyle",e.titleStyle??(s?fe:et),!0)};return Object.assign(y,Me({...e,...o,titleStyle:y.titleStyle,direction:o.direction??(o.position!==void 0&&o.position!==e.position?void 0:e.direction)})),y.border=qe(I(e.border,o.border)),(s?Qe:tt)(n,y),{config:y,titleMode:l,title:z,titleVisible:C}}function B(n,t,e,o,i=!1){const{config:s,titleMode:a,title:r,titleVisible:f}=H(n,t,e,o,i),b=t==="size"?"sizeLegend":"strokeWidthLegend";let c=n;return(a==="auto"||typeof a=="string")&&(c=c.editSemantic({property:`guide.legend.${t}.title`,value:r})),c=c._withLegendConfig(t,s),c=E(c,`${b}Background`,s.border!==!1,{type:"rect",before:`${b}Symbols`}),c=E(c,`${b}Title`,f,{type:"text",after:`${b}Labels`}),c.rematerializeLegend()}function rt(n,t,e,o){if(e===void 0)return;const i=ce(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:V(o,{previous:e,operation:"edit",label:"Size legend"}),{count:a,...r}=e,b={...i?e:r,...i?{}:{sampling:s},...o.overflow!==void 0&&n.resolvedScales[e.scale]?.type==="ordinal"?{overflow:ee(o.overflow)}:{}};if(o.labels===void 0&&o.titleStyle===void 0)return b;const c=e.inheritAppearance?{...t.labels,offset:e.labels.offset}:e.labels??se,S=e.inheritAppearance?t.titleStyle:e.titleStyle??fe;return{...b,inheritAppearance:!1,labels:le(o.labels,"editLegend.labels",c),titleStyle:de(o.titleStyle,"editLegend.titleStyle",S,!0)}}function ge(n,t,e,o,i,s=n.semanticSpec.guides.legend?.[t]?.order){if(o!==void 0&&(o=pe(o,i)),{previous:e,args:i}=$(e,i),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 a=i.title,r=a==="auto"?!0:typeof a=="string"?!1:e.inferredTitle,f=a===!1?!1:a===void 0?e.titleVisible!==!1:!0,b=a==="auto"?e.field:typeof a=="string"?a: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=_e({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:b,symbol:i.symbol===void 0?e.symbol:Pe(n,P(n,e.target),e.channels,i.symbol,t),labels:l,titleStyle:x(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};ye(t,z);const k=i.order===void 0?s:X(i.order);return Ee(n,P(n,e.target),e.channels,b,k),{config:z,order:k,titleMode:a,title:b,titleVisible:f,sizeConfig:rt(n,e,o,i)}}function N(n,t,e,o,i){e.combine==="size-shape"&&Se(i.symbol);const{config:s,order:a,titleMode:r,title:f,titleVisible:b,sizeConfig:c}=ge(n,t,e,o,i),S=Te(s);let l=n;(r==="auto"||typeof r=="string")&&(l=l.editSemantic({property:`guide.legend.${t}.title`,value:f})),i.order!==void 0&&(a==="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:a})),l=l._withLegendConfig(t,s),l=Ie(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,b,{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 v(n,t,e){const o=Y(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 R(n,t,e,o={position:e.position}){Je(e.channels,"editLegend");const{channels:i,target:s,combine:a,...r}=e,f=n.guideConfigs.legend??{},b=Object.keys(f).filter(d=>f[d].target===t);let c=b.reduce((d,m)=>d._withoutMaterializationConfig(["guides","legend",m]),n);const S=a==="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&&a===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=a??(l&&C?l.combine:void 0);let k;if(a==="separate"&&l){const d=i.filter(p=>L.includes(p));k={steps:[...l.combine==="color-size"?d.map(p=>[p]):[d],...i.filter(p=>!L.includes(p)).map(p=>[p])].filter(p=>p.length).map(p=>te(c,{target:t,channels:p})).flatMap(p=>p.steps),...n.guideConfigs.legendGroups?.[t]===void 0?{}:{legendGroup:{target:t,config:n.guideConfigs.legendGroups[t]}}}}else k=te(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=b.find(d=>["series","color","stroke"].includes(d));let u=k.steps.map(d=>{const m=Oe(c,d),{kind:p}=m;if(f[p]!==void 0&&f[p].target!==t)throw new Error(`Legend ${p} content already belongs to another target.`);if(m.config.combine==="size-shape"&&f.series?.combine!=="size-shape"||m.kind==="colorSize"&&f.colorSize?.target!==t)return m;if(l?.combine==="color-size"&&["gradient","size"].includes(p)){const G=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","offset","border"].map(_=>[_,l[_]]));return p==="size"&&Object.assign(G,Object.fromEntries(["sampling","direction","columns","titlePosition","itemGap","layout","clipHeight"].map(_=>[_,l[_]])),{inheritAppearance:!1}),{...m,config:{...m.config,...G}}}if(p==="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{...m,config:{...m.config,...O,inheritAppearance:!1}}}if(["series","color","stroke"].includes(p)&&w!==void 0){const O=f[p]?.target===t?p:w,G=r.order===void 0?n.semanticSpec.guides.legend?.[O]?.order:X(r.order);return{...xe(c,O,f[O],m.config.channels,{order:G,validateLayout:!1}),kind:p}}return{...m,config:f[p]?.target===t?f[p]:m.config}});const y=u.find(d=>["series","color","stroke"].includes(d.kind)),g=u.find(d=>d.kind==="size");if(r.representation!==void 0||u.some(d=>d.kind==="gradient"&&d.config.representation==="symbols")){const d=u.find(m=>m.kind==="gradient");if(d===void 0)throw new Error("Legend representation requires continuous color.");d.config=D(c,"gradient",d.config,r).config}else if(y!==void 0){g!==void 0&&f.size?.target!==t&&(g.config={...g.config,inheritAppearance:y.config.position==="left"||r.position==="left"||r.labels!==void 0||r.titleStyle!==void 0});const d=ge(c,y.kind,y.config,g?.config,r,y.order);if(y.config=d.config,y.order=d.order,g!==void 0&&(g.config=d.sizeConfig,y.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const d=u[0];if(d.kind==="colorSize")d.config=Y(c,d.config,r);else if(l?.combine==="color-size"&&a==="separate")for(const m of u)m.config=m.kind==="size"?H(c,"size",m.config,r).config:D(c,m.kind,m.config,Object.fromEntries(Object.entries(r).filter(([p])=>!["values","itemGap","layout","columns"].includes(p)))).config;else d.kind==="size"||d.kind==="strokeWidth"?d.config=H(c,d.kind,d.config,r).config:["interval","strokeInterval"].includes(d.kind)?d.config=ue(c,d.config,r,d.kind==="strokeInterval"?"stroke":"color").config:d.config=D(c,d.kind,d.config,r).config}u=lt(n,t,u,o.position,e);for(const{kind:d,config:m}of u)c=c._withLegendConfig(d,m);y!==void 0&&je(c,y.config);let h=ne(n,b);k.legendGroup!==void 0&&(h=h._withMaterializationConfig(["guides","legendGroups",t],k.legendGroup.config));for(const{kind:d,config:m,order:p}of u)["series","color","stroke"].includes(d)?h=$e(h,m,p):h={colorSize:be,size:Ue,strokeWidth:nt,gradient:ie,opacity:Ae,interval:He,strokeGradient:Ne,strokeInterval:ve}[d](h,m);return h.rematerializeLegend()}function A(n={},t=!1,e={position:n.position}){if(W(n,ae,"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=K(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=F(l,n,"editLegend"),C=Object.hasOwn(n,"position");if(C){if(n.position!=="inherit"&&!ot.includes(n.position))throw new Error("Selected legend position requires an anchor or inherit.");const O=it(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:ct,...me}=n;n=me}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,u=l.guideConfigs.legend[w],{channel:y,...g}=n,h=ke(u),d=h===void 0?u:{...u,...h},m=C&&!["gradient","strokeGradient","opacity"].includes(w)?{...d,direction:void 0}:d,p=O=>h===void 0?O:O._withLegendConfig(w,{...O.guideConfigs.legend[w],position:u.position,...g.align===void 0?{align:u.align}:{},...g.offset===void 0?{offset:u.offset,inferredOffset:u.inferredOffset}:{}}).rematerializeLegend();return["series","color","stroke"].includes(w)?p(N(l,w,m,void 0,g)):w==="colorSize"?p(v(l,m,g)):["size","strokeWidth"].includes(w)?p(B(l,w,m,g)):["interval","strokeInterval"].includes(w)?p(q(l,m,g,w==="strokeInterval"?"stroke":"color")):p(M(l,w,m,g))}const s=this.guideConfigs.legendGroups?.[i];if(!t&&(s!==void 0||n.blockLayout!==void 0||n.at!==void 0||U.includes(n.position)&&Object.values(this.guideConfigs.legend??{}).some(l=>l.target===i&&l.kind===void 0))){const l=ze(this,n,i,{corner:U.includes(n.position)}),{channels:L,...C}=l.options;if(L!==void 0)return R(l.program,i,l.options,e);const z=["position","layout","align","direction","columns","offset","titlePosition","itemGap","rowPadding","titlePadding"];if(Object.keys(C).every(u=>u==="target"||z.includes(u))){let u=l.program;const y=Object.entries(u.guideConfigs.legend??{}).filter(([,g])=>g.target===i);for(const[g]of y){const h=u.guideConfigs.legend[g],d={...C};if(J(h)!==void 0)for(const m of["position","align","offset"])delete d[m];if(["gradient","strokeGradient","opacity"].includes(g)&&h.representation!=="symbols")for(const m of g==="opacity"?["layout","columns","direction"]:["layout","columns"]){if(Object.hasOwn(n,m))throw new Error(`${g} legend does not accept ${m}.`);delete d[m]}["gradient","strokeGradient"].includes(g)&&h.representation!=="symbols"&&delete d.itemGap,["series","color","stroke"].includes(g)?u=N(u,g,h,void 0,d):g==="colorSize"?u=v(u,h,d):["size","strokeWidth"].includes(g)?u=B(u,g,h,d,!0):["interval","strokeInterval"].includes(g)?u=q(u,h,d,g==="strokeInterval"?"stroke":"color"):u=M(u,g,h,d)}return u.rematerializeLegend()}if(n.combine===void 0&&Object.values(this.guideConfigs.legend??{}).some(u=>u.target===i&&["size-shape","color-size"].includes(u.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 u=l.program;const y={...n};for(const g of["position","align","offset","blockLayout","at"])delete y[g];for(const g of j(u,i))u=A.call(u,{...y,target:i,channel:g.channels[0]},!0);return u.rematerializeLegend()}let w=A.call(l.program,l.options,!0,e);for(const[u,y]of Object.entries(this.guideConfigs.legend??{}))y.target!==i||J(y)===void 0||w.guideConfigs.legend[u]===void 0||(w=w._withLegendConfig(u,{...w.guideConfigs.legend[u],position:y.position,align:y.align,offset:y.offset,inferredOffset:y.inferredOffset,...n.direction===void 0?{direction:y.direction}:{}}));return w.rematerializeLegend()}if(Object.hasOwn(n,"combine")){if(Le(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 R(this,i,{...n,channels:l},e)}if(n.channels!==void 0)return R(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 M(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 b=["series","color","stroke"].filter(l=>r[l]?.target===i);if(b.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const c=b[0];if(c!==void 0)return N(this,c,r[c],r.size?.target===i?r.size:void 0,n);if(r.colorSize?.target===i)return v(this,r.colorSize,n);if(r.interval?.target===i)return q(this,r.interval,n);if(r.strokeInterval?.target===i)return q(this,r.strokeInterval,n,"stroke");if(r.strokeWidth?.target===i)return B(this,"strokeWidth",r.strokeWidth,n);if(r.size?.target===i)return B(this,"size",r.size,n);const S=["gradient","strokeGradient","opacity"].find(l=>r[l]?.target===i);return M(this,S,r[S],n)}const Jt=Ge({op:"editLegend",description:"Edit one stable legend content, layout or appearance.",scope:"any"},Ce(function(n={}){if(this.compositionSpec!==void 0)return we(this,"editLegend",n);if(W(n,ae,"editLegend"),!Object.hasOwn(n,"clipHeight"))return Q(this,A.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=K(i,n.target,"editLegend"),b=n.channel===void 0?j(i,f):[F(i,n,"editLegend")];for(const c of b)i=i._withLegendConfig(c.kind,Z(c.config,"auto"))}let s=o?A.call(i,e):i;const a=K(s,n.target,"editLegend"),r=n.channel===void 0?j(s,a):[F(s,n,"editLegend")];if(!r.length)throw new Error("Legend clipHeight requires an existing legend block.");for(const f of r){const b=Z(s.guideConfigs.legend[f.kind],t);he(f.kind,b),s=s._withLegendConfig(f.kind,b)}return Q(this,s.rematerializeLegend(),n)}));export{Jt as editLegend};
|
|
1
|
+
import{colorSizeChannels as Oe}from"../../../grammar/colorSizeLegend.js";import{splitGuideVisibility as ze,patchGuideVisibility as Ce}from"../../../materialization/guides/visibility.js";import{patchLegendItemsOffset as ne}from"../../../grammar/legendItemsOffset.js";import{resolveColorSizeEdit as ie,createColorSizeLegendFromConfig as Ee}from"./colorSize.js";import{prepareLegendSpacingEdit as D,patchLegendSpacing as Ge,validateLegendSpacing as _e}from"../../../grammar/legendSpacing.js";import{patchLegendClipHeight as oe,validateLegendClip as je}from"../../../grammar/legendClip.js";import{validateLegendCombine as Pe,validateMappedSizeShapeSymbol as Te}from"../../../grammar/legendCombinations.js";import{legendBlockDescriptors as P}from"./target.js";import{editFacetParentLegend as xe}from"../../../materialization/facetGuides/edit.js";import{localLegendPosition as le,localLegendPlacement as B}from"../../../materialization/legendGroups.js";import{mergeLegendTitleStyle as M}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as de}from"./group.js";import{prepareLegendGroup as Ie}from"./group.js";import{PLOT_LEGEND_POSITIONS as re}from"../../../layout/legend.js";import{withGuideLayoutValidation as $e}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as Ve}from"./creation.js";import{normalizeLegendOrder as ce}from"../../../grammar/categoryOrder.js";import{resolveDefinition as qe}from"./categorical/resolve.js";import{action as ve}from"../../../core/action.js";import{validateOptionObject as W}from"../../../core/validation.js";import{normalizeLegendOverflow as se,normalizeOptions as De}from"./categorical/options.js";import{resolveLegendSymbol as Be}from"./categorical/recipes.js";import{resolveLayout as Me}from"./categorical/layout.js";import{resolveLegendCreationPlan as fe}from"./categorical/actions.js";import{categoricalSymbolIds as We,reconcileCategoricalSymbols as Ae,createCategoricalLegendFromConfig as Fe,resolveCategoricalLegendRevision as Ke,removeLegendKinds as ae}from"./lifecycle.js";import{normalizeColorSymbols as Ne}from"./continuous/colorSymbols.js";import{readLegendSampling as He}from"./sampling.js";import{createGradientLegendFromConfig as ue}from"./continuous/gradient.js";import{normalizeLegendGradient as ge}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as Re,normalizeLegendBorder as Ye,normalizeLegendTextOptions as me,normalizeLegendTitleOptions as be,normalizeContinuousLegend as pe}from"./continuous/common.js";import{normalizeOpacitySymbol as Ze,createOpacityLegendFromConfig as Je}from"./continuous/opacity.js";import{normalizeIntervalLegend as Qe,resolveIntervalConfig as Ue,resolveIntervalLayout as Xe,createIntervalLegendFromConfig as et}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as tt,createStrokeIntervalLegendFromConfig as nt,resolveStrokeIntervalConfig as it,resolveStrokeIntervalLayout as ot}from"./continuous/stroke.js";import{findLayer as x}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as lt}from"../../../materialization/graphicHierarchy.js";import{resolveLegendBlock as V,resolveLegendTarget as I,validateLegendChannels as dt}from"./target.js";import{isEnumeratedSizeScaleType as he}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as ye,SIZE_LEGEND_TITLE_STYLE as Le,resolveSizeLegendLayout as rt,createSizeLegendFromConfig as ct}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as st,STROKE_WIDTH_LEGEND_TITLE_STYLE as ft,resolveStrokeWidthLegendLayout as at,createStrokeWidthLegendFromConfig as ut}from"./strokeWidth.js";import{normalizeLegendSampling as A}from"./sampling.js";import{patchLegendBlockOverride as Z}from"./blocks.js";import{LEGEND_POSITIONS as gt}from"../../../core/vocabulary.js";import{planLegendBlockTransitions as mt}from"./transition.js";const J=Object.freeze(["target","channel","channels","position","layout","align","direction","columns","offset","alongOffset","titlePosition","title","symbol","labels","titleStyle","itemGap","rowPadding","titlePadding","border","count","values","gradient","representation","order","overflow","blockLayout","at","combine","clipHeight","itemsOffset"]);function q(t,n){return n===void 0?t:{...t,...n}}function v(t,n){return n===void 0?t:n===!1||n===!0||t===!1?n:{...t,...n}}function j(t,n,e,l){const i=t.graphicSpec.objects[n]!==void 0;return i&&!e?t.editGraphics({target:n,remove:!0}):!i&&e?t.createGraphics({id:n,...l,...lt(t,{...l.before===void 0?{}:{before:l.before},...l.after===void 0?{}:{after:l.after}})}):t}function bt(t,n,e){const l=e.representation??n.representation??"gradient";if(!["gradient","symbols"].includes(l))throw new Error("Legend representation must be gradient or symbols.");const i=["target","representation","position","align","offset","title","labels","titleStyle","titlePosition","border","count","values",...l==="symbols"?["symbol","itemGap","layout","direction","columns"]:["gradient","direction"]];for(const g of Object.keys(e))if(!i.includes(g))throw new Error(`Color ${l} legend does not accept ${g}.`);const f=(n.representation??"gradient")!==l,s=e.title,c=s==="auto"?x(t,n.target)?.encoding?.color?.field:typeof s=="string"?s:n.title,a={target:n.target,position:e.position??n.position,align:e.align??n.align,offset:e.offset??n.offset,title:c,labels:q(n.labels,e.labels),titleStyle:M(n.titleStyle,e.titleStyle,{edit:!0}),border:v(n.border,e.border),titlePosition:e.titlePosition??(f&&(l==="gradient"||n.titlePosition==="bottom")?"top":n.titlePosition)};let u;if(l==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&W(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const g=He(n),k=f?g.mode==="values"?{values:g.values}:{count:g.count}:{};for(const d of["values","count"])Object.hasOwn(e,d)&&(k[d]=e[d]);Object.hasOwn(e,"values")&&delete k.count,Object.hasOwn(e,"count")&&(k.count=e.count),u=Ne({...a,...k,layout:e.layout??(f?void 0:n.layout),direction:e.direction??(f||e.position!==void 0&&e.position!==n.position?void 0:n.direction),columns:e.columns??(f?void 0:n.columns),itemGap:e.itemGap??(f?void 0:n.itemGap),symbol:e.symbol==="auto"?"auto":e.symbol===void 0?f?void 0:n.symbol:{...f?{}:n.symbol,...e.symbol}},f?{count:n.count}:n,"edit")}else{const g=A(e,{previous:n,operation:"edit",label:"Gradient legend"});u=pe({...a,...e.direction==="auto"?{}:e.direction!==void 0?{direction:e.direction}:n.gradientDirection===void 0?{}:{direction:n.gradientDirection},count:g.count},"gradient"),g.mode==="values"&&(u.sampling=g);const k=ge(e.gradient,f?void 0:n.gradient);u.gradient=k}const{representation:r,sampling:y,symbol:o,layout:h,direction:S,columns:w,gradient:O,count:z,...m}=n,b=f&&l==="gradient"&&n.blockOverrides!==void 0?Object.fromEntries(Object.entries(n.blockOverrides).map(([g,k])=>{const{symbol:d,labelMap:p,...L}=k;return[g,L]})):n.blockOverrides;return l==="gradient"&&e.direction==="auto"&&delete m.gradientDirection,{config:{...m,...u,...b===void 0?{}:{blockOverrides:b},inferredTitle:s==="auto"?!0:typeof s=="string"?!1:n.inferredTitle,titleVisible:s===!1?!1:s===void 0?n.titleVisible!==!1:!0},titleMode:s,title:c,titleVisible:s===!1?!1:s===void 0?n.titleVisible!==!1:!0}}function F(t,n,e,l){if({previous:e,args:l}=D(e,l),n==="gradient"&&(e.representation==="symbols"||l.representation!==void 0))return bt(t,e,l);const i=["gradient","strokeGradient"].includes(n),f=i?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","values","gradient","representation","direction"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const p of Object.keys(l))if(!f.includes(p))throw new Error(`${n} legend does not accept ${p}.`);const s=l.title,c=s==="auto"?!0:typeof s=="string"?!1:e.inferredTitle,a=s===!1?!1:s===void 0?e.titleVisible!==!1:!0,u=x(t,e.target),r=n==="gradient"?"color":n==="strokeGradient"?"stroke":"opacity",y=u?.encoding?.[r]?.field,o=s==="auto"?y:typeof s=="string"?s:e.title,h=l.titlePosition??e.titlePosition,S=n==="opacity"&&h==="left"&&e.titlePosition!=="left",w=q(e.labels,l.labels),O=A(l,{previous:e,operation:"edit",label:i?"Gradient legend":"Opacity legend"}),z=pe({target:e.target,position:l.position??e.position,align:l.align??e.align,offset:l.offset??e.offset,...i?{count:O.count,...l.direction==="auto"?{}:l.direction!==void 0?{direction:l.direction}:e.gradientDirection===void 0?{}:{direction:e.gradientDirection}}:{},titlePosition:h,title:o,labels:S&&l.labels?.offset===void 0?{...w,offset:8}:w,titleStyle:M(e.titleStyle,l.titleStyle,{edit:!0}),border:v(e.border,l.border),...n==="opacity"?{itemGap:l.itemGap??(S?20:e.itemGap),symbol:l.symbol??e.symbol}:{}},i?"gradient":n),{count:m,...b}=e,{sampling:g,...k}=e,d={...i?k:b,...z,...i&&O.mode!=="values"?{}:{sampling:O},inferredTitle:c,titleVisible:a};if(i){l.direction==="auto"&&delete d.gradientDirection;const p=ge(l.gradient,e.gradient);d.gradient=p}else d.symbol=Ze(l.symbol??e.symbol);return{config:d,titleMode:s,title:o,titleVisible:a}}function K(t,n,e,l){const{config:i,titleMode:f,title:s,titleVisible:c}=F(t,n,e,l);if(n==="gradient"&&(e.representation??"gradient")!==(i.representation??"gradient"))return ue(ae(t,[n]),i).rematerializeLegend();const a=n==="gradient"?"colorGradient":n==="strokeGradient"?"strokeGradient":"opacityLegend",u=n==="gradient"?"color":n==="strokeGradient"?"stroke":"opacity";let r=t;return(f==="auto"||typeof f=="string")&&(r=r.editSemantic({property:`guide.legend.${u}.title`,value:s})),r=r._withLegendConfig(n,i),r=j(r,`${a}Background`,i.border!==!1,{type:"rect",before:["gradient","strokeGradient"].includes(n)?`${a}${i.representation==="symbols"?"Symbols":"Strips"}`:"opacityLegendSymbols"}),r=j(r,`${a}Title`,c,{type:"text"}),r.rematerializeLegend()}function Q(t,n,e,l="color"){({previous:n,args:e}=D(n,e));for(const o of Object.keys(e))if(!["target","position","align","direction","offset","title","symbol","labels","titleStyle","itemGap","border","layout","columns","titlePosition"].includes(o))throw new Error(`interval legend does not accept ${o}.`);const i=e.title,f=x(t,n.target),s=i==="auto"?!0:typeof i=="string"?!1:n.inferredTitle,c=i===!1?!1:i===void 0?n.titleVisible!==!1:!0,a=i==="auto"?f?.encoding?.[l]?.field:typeof i=="string"?i:n.title,u=Qe({target:n.target,position:e.position??n.position,align:e.align??n.align,direction:e.direction??(e.position!==void 0&&e.position!==n.position?void 0:n.direction),layout:e.layout===void 0?n.layout:e.layout,columns:e.columns??n.columns,titlePosition:e.titlePosition??n.titlePosition,offset:e.offset??n.offset,title:a,symbol:q(n.symbol,e.symbol),labels:q(n.labels,e.labels),titleStyle:M(n.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??n.itemGap,border:v(n.border,e.border)}),r={...n,...u,inferredTitle:s,titleVisible:c,...l==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},y=l==="stroke"?it(t,r):Ue(t,r);return(l==="stroke"?ot:Xe)(t,y.config,y.scale),{normalized:u,config:r,titleMode:i,title:a,titleVisible:c}}function N(t,n,e,l="color"){const i=l==="stroke"?"strokeInterval":"interval",f=l==="stroke"?"strokeInterval":"colorLegend",{normalized:s,config:c,titleMode:a,title:u,titleVisible:r}=Q(t,n,e,l);let y=t._withLegendConfig(i,c);return(a==="auto"||typeof a=="string")&&(y=y.editSemantic({property:`guide.legend.${l}.title`,value:u})),y=j(y,`${f}Title`,r,{type:"text"}),y=j(y,`${f}Background`,s.border!==!1,{type:"rect",before:`${f}Symbols`}),y.rematerializeLegend()}function H(t,n,e,l,i=!1){({previous:e,args:l}=D(e,l));const f=n==="size",s=f?"size":"stroke-width",c=["target","title","count","values","labels","titleStyle","position","layout","align","direction","columns","titlePosition","offset","itemGap","border"];f&&c.push("overflow");for(const g of Object.keys(l))if(!c.includes(g))throw new Error(`${s} legend does not accept ${g}.`);const a=f&&he(t.resolvedScales[e.scale]?.type),u=f&&t.resolvedScales[e.scale]?.type==="ordinal";if(l.overflow!==void 0&&!u)throw new Error("Size legend overflow requires an ordinal scale.");if(a&&(l.count!==void 0||l.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const r=a?void 0:A(l,{previous:e,operation:"edit",label:`${f?"Size":"Stroke-width"} legend`}),y=x(t,e.target),o=l.title,h=o==="auto"?!0:typeof o=="string"?!1:e.inferredTitle,S=o===!1?!1:o===void 0?e.titleVisible!==!1:!0,w=o==="auto"?y?.encoding?.[n]?.field:typeof o=="string"?o:e.title,{count:O,...z}=e,b={...a?e:z,title:w,inferredTitle:h,titleVisible:S,...a?{}:{sampling:r},...u&&(l.overflow!==void 0||e.overflow!==void 0)?{overflow:se(l.overflow===void 0?e.overflow:l.overflow)}:{},...f?{inheritAppearance:i&&e.inheritAppearance===!0,...l.position===void 0?{}:{inheritPosition:!1}}:{},labels:me(l.labels,"editLegend.labels",e.labels??(f?ye:st)),titleStyle:be(l.titleStyle,"editLegend.titleStyle",e.titleStyle??(f?Le:ft),!0)};return Object.assign(b,Re({...e,...l,titleStyle:b.titleStyle,direction:l.direction??(l.position!==void 0&&l.position!==e.position?void 0:e.direction)})),b.border=Ye(v(e.border,l.border)),(f?rt:at)(t,b),{config:b,titleMode:o,title:w,titleVisible:S}}function R(t,n,e,l,i=!1){const{config:f,titleMode:s,title:c,titleVisible:a}=H(t,n,e,l,i),u=n==="size"?"sizeLegend":"strokeWidthLegend";let r=t;return(s==="auto"||typeof s=="string")&&(r=r.editSemantic({property:`guide.legend.${n}.title`,value:c})),r=r._withLegendConfig(n,f),r=j(r,`${u}Background`,f.border!==!1,{type:"rect",before:`${u}Symbols`}),r=j(r,`${u}Title`,a,{type:"text",after:`${u}Labels`}),r.rematerializeLegend()}function pt(t,n,e,l){if(e===void 0)return;const i=he(t.resolvedScales[e.scale]?.type);if(i&&(l.count!==void 0||l.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const f=i?void 0:A(l,{previous:e,operation:"edit",label:"Size legend"}),{count:s,...c}=e,u={...i?e:c,...i?{}:{sampling:f},...l.overflow!==void 0&&t.resolvedScales[e.scale]?.type==="ordinal"?{overflow:se(l.overflow)}:{}};if(l.labels===void 0&&l.titleStyle===void 0)return u;const r=e.inheritAppearance?{...n.labels,offset:e.labels.offset}:e.labels??ye,y=e.inheritAppearance?n.titleStyle:e.titleStyle??Le;return{...u,inheritAppearance:!1,labels:me(l.labels,"editLegend.labels",r),titleStyle:be(l.titleStyle,"editLegend.titleStyle",y,!0)}}function U(t,n,e,l,i,f=t.semanticSpec.guides.legend?.[n]?.order){if(l!==void 0&&(l=Ge(l,i)),{previous:e,args:i}=D(e,i),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&&l===void 0)throw new Error("Legend count requires an existing size legend.");const s=i.title,c=s==="auto"?!0:typeof s=="string"?!1:e.inferredTitle,a=s===!1?!1:s===void 0?e.titleVisible!==!1:!0,u=s==="auto"?e.field:typeof s=="string"?s:e.title,r=q(e.labels,i.labels);if(r.format!==void 0&&r.format!=="auto"&&l===void 0)throw new Error("Categorical legend labels do not accept format.");const{format:y,...o}=r,h=i.symbol===void 0?e.inferredSymbol:i.symbol==="auto",S=De({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:u,symbol:i.symbol===void 0?e.symbol:Be(t,x(t,e.target),e.channels,i.symbol,n),labels:o,titleStyle:M(e.titleStyle,i.titleStyle,{edit:!0}),itemGap:i.itemGap??e.itemGap,border:v(e.border,i.border)},n,{internalSymbol:h}),w={...e,...S,inferredTitle:c,inferredSymbol:h,titleVisible:a};_e(n,w);const O=i.order===void 0?f:ce(i.order);return qe(t,x(t,e.target),e.channels,u,O),{config:w,order:O,titleMode:s,title:u,titleVisible:a,sizeConfig:pt(t,e,l,i)}}function X(t,n,e,l,i){e.combine==="size-shape"&&Te(i.symbol);const{config:f,order:s,titleMode:c,title:a,titleVisible:u,sizeConfig:r}=U(t,n,e,l,i),y=We(f);let o=t;(c==="auto"||typeof c=="string")&&(o=o.editSemantic({property:`guide.legend.${n}.title`,value:a})),i.order!==void 0&&(s==="scale"?t.semanticSpec.guides.legend?.[n]?.order!==void 0&&(o=o.editSemantic({property:`guide.legend.${n}.order`,remove:!0})):o=o.editSemantic({property:`guide.legend.${n}.order`,value:s})),o=o._withLegendConfig(n,f),o=Ae(o,e,f),o=j(o,`${n}LegendBackground`,f.border!==!1,{type:"rect",before:[...y][0]});const h=`${n}LegendTitle`;return o=j(o,h,u,{type:"text",...o.graphicSpec.objects.sizeLegendSymbols===void 0?{}:{before:o.graphicSpec.objects.sizeLegendBackground===void 0?"sizeLegendSymbols":"sizeLegendBackground"}}),l!==void 0&&(o=o._withLegendConfig("size",r)),o.rematerializeLegend()}function ee(t,n,e){const l=ie(t,n,e);let i=t._withLegendConfig("colorSize",l);return i=j(i,"colorSizeLegendBackground",l.border!==!1,{type:"rect",before:"colorSizeLegendSymbols"}),i=j(i,"colorSizeLegendTitle",l.titleVisible!==!1,{type:"text",after:"colorSizeLegendLabels"}),i.rematerializeLegend()}function te(t,n,e,l={position:e.position}){dt(e.channels,"editLegend");const{channels:i,target:f,combine:s,...c}=e,a=t.guideConfigs.legend??{},u=Object.keys(a).filter(d=>a[d].target===n);let r=u.reduce((d,p)=>d._withoutMaterializationConfig(["guides","legend",p]),t);const y=s==="color-size"&&c.representation===void 0?void 0:c.representation??(i.includes("color")&&a.gradient?.target===n?a.gradient.representation:void 0);if(c.representation!==void 0&&!i.includes("color"))throw new Error("Legend representation requires selected continuous color.");const o=Object.values(a).find(d=>d.target===n&&["size-shape","color-size","color-size-shape"].includes(d.combine)),h=o?.combine?.startsWith("color-size")?Oe(o):["shape","size"],S=h.every(d=>i.includes(d));if(o&&s===void 0&&!S&&i.some(d=>h.includes(d)))throw new Error("An edited combined legend keeps its combination; explicitly choose separate to split it.");const w=s??(o&&S?o.combine:void 0);let O;if(s==="separate"&&o){const d=i.filter(L=>h.includes(L));O={steps:[...o.combine.startsWith("color-size")?d.map(L=>[L]):[d],...i.filter(L=>!h.includes(L)).map(L=>[L])].filter(L=>L.length).map(L=>fe(r,{target:n,channels:L})).flatMap(L=>L.steps),...t.guideConfigs.legendGroups?.[n]===void 0?{}:{legendGroup:{target:n,config:t.guideConfigs.legendGroups[n]}}}}else O=fe(r,{target:n,channels:i,...w===void 0?{}:{combine:w},...w==="color-size-shape"?{values:c.values??o?.sampling?.values}:{},...y===void 0?{}:{representation:y}},t.semanticSpec.layers,i);O.legendGroup!==void 0&&(r=r._withMaterializationConfig(["guides","legendGroups",n],O.legendGroup.config));const z=u.find(d=>["series","color","stroke"].includes(d));let m=O.steps.map(d=>{const p=Ve(r,d),{kind:L}=p;if(a[L]!==void 0&&a[L].target!==n)throw new Error(`Legend ${L} content already belongs to another target.`);if(p.config.combine==="size-shape"&&a.series?.combine!=="size-shape"||p.kind==="colorSize"&&a.colorSize?.target!==n)return p;if(o?.combine==="color-size"&&["gradient","size"].includes(L)){const _=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","offset","border"].map(G=>[G,o[G]]));return L==="size"&&Object.assign(_,Object.fromEntries(["sampling","direction","columns","titlePosition","itemGap","layout","clipHeight"].map(G=>[G,o[G]])),{inheritAppearance:!1}),{...p,config:{...p.config,..._}}}if(L==="size"&&o){const E=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","direction","columns","offset","titlePosition","itemGap","border","layout","clipHeight"].map(_=>[_,o[_]]));return{...p,config:{...p.config,...E,inheritAppearance:!1}}}if(["series","color","stroke"].includes(L)&&z!==void 0){const E=a[L]?.target===n?L:z,_=c.order===void 0?t.semanticSpec.guides.legend?.[E]?.order:ce(c.order);return{...Ke(r,E,a[E],p.config.channels,{order:_,validateLayout:!1}),kind:L}}return{...p,config:a[L]?.target===n?a[L]:p.config}});const b=m.find(d=>["series","color","stroke"].includes(d.kind)),g=m.find(d=>d.kind==="size");if(o?.combine==="color-size-shape"&&s==="separate")for(const d of m){const L={...Object.fromEntries(["title","labels","titleStyle","position","align","direction","offset","border","itemGap","titlePosition"].map(E=>[E,o[E]])),...c};if(["series","color","stroke"].includes(d.kind)){const E=U(r,d.kind,d.config,void 0,L,d.order);d.config=E.config,d.order=E.order}else d.kind==="size"?d.config=H(r,"size",d.config,L).config:d.config=Q(r,d.config,L,d.kind==="strokeInterval"?"stroke":"color").config}else if(c.representation!==void 0||m.some(d=>d.kind==="gradient"&&d.config.representation==="symbols")){const d=m.find(p=>p.kind==="gradient");if(d===void 0)throw new Error("Legend representation requires continuous color.");d.config=F(r,"gradient",d.config,c).config}else if(b!==void 0){g!==void 0&&a.size?.target!==n&&(g.config={...g.config,inheritAppearance:b.config.position==="left"||c.position==="left"||c.labels!==void 0||c.titleStyle!==void 0});const d=U(r,b.kind,b.config,g?.config,c,b.order);if(b.config=d.config,b.order=d.order,g!==void 0&&(g.config=d.sizeConfig,b.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const d=m[0];if(d.kind==="colorSize")d.config=ie(r,d.config,c);else if(o?.combine==="color-size"&&s==="separate")for(const p of m)p.config=p.kind==="size"?H(r,"size",p.config,c).config:F(r,p.kind,p.config,Object.fromEntries(Object.entries(c).filter(([L])=>!["values","itemGap","layout","columns"].includes(L)))).config;else d.kind==="size"||d.kind==="strokeWidth"?d.config=H(r,d.kind,d.config,c).config:["interval","strokeInterval"].includes(d.kind)?d.config=Q(r,d.config,c,d.kind==="strokeInterval"?"stroke":"color").config:d.config=F(r,d.kind,d.config,c).config}m=mt(t,n,m,l.position,e);for(const{kind:d,config:p}of m)r=r._withLegendConfig(d,p);b!==void 0&&Me(r,b.config);let k=ae(t,u);O.legendGroup!==void 0&&(k=k._withMaterializationConfig(["guides","legendGroups",n],O.legendGroup.config));for(const{kind:d,config:p,order:L}of m)["series","color","stroke"].includes(d)?k=Fe(k,p,L):k={colorSize:Ee,size:ct,strokeWidth:ut,gradient:ue,opacity:Je,interval:et,strokeGradient:tt,strokeInterval:nt}[d](k,p);return k.rematerializeLegend()}function Y(t={},n=!1,e={position:t.position}){if(W(t,J,"editLegend"),Object.keys(t).filter(o=>o!=="target"&&o!=="channel").length===0)throw new Error("editLegend requires at least one change.");if(t.title!==void 0&&t.title!==!1&&t.title!=="auto"&&(typeof t.title!="string"||t.title.length===0))throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');const i=I(this,t.target,"editLegend");if(Object.hasOwn(t,"channel")){if(Object.hasOwn(t,"channels"))throw new Error("editLegend cannot combine channel and channels.");if(Object.hasOwn(t,"combine"))throw new Error("Selected legend edits cannot change combine.");if(t.blockLayout!==void 0||t.at!==void 0)throw new Error("Selected legend blocks do not accept whole-group blockLayout or at.");let o=this;const h=V(o,t,"editLegend");if(Object.hasOwn(t,"alongOffset")){if(!Number.isFinite(t.alongOffset))throw new TypeError("Legend alongOffset must be finite.");const C=o.guideConfigs.legendGroups?.[i]?.placement;if(t.position===void 0&&B(h.config)===void 0&&C?.at)throw new Error("Free legend placement requires position before alongOffset.");o=o._withLegendConfig(h.kind,{...h.config,blockOverrides:Z(h.config,h.key,{alongOffset:t.alongOffset})});const{alongOffset:we,...T}=t;t={...T,...T.position===void 0&&C?.position&&!B(h.config)?{position:C.position,align:T.align??C.align,offset:T.offset??C.offset}:{}}}const S=Object.hasOwn(t,"position"),w=B(h.config)?.position??o.guideConfigs.legendGroups?.[i]?.placement.position??h.config.position,O=S&&t.position!==w;if(S){if(t.position!=="inherit"&&!gt.includes(t.position))throw new Error("Selected legend position requires an anchor or inherit.");const C=Z(o.guideConfigs.legend[h.kind],h.key,t.position==="inherit"?{removePosition:!0}:{position:t.position}),{blockOverrides:we,...T}=h.config;o=o._withLegendConfig(h.kind,C===void 0?T:{...T,blockOverrides:C});const{position:yt,...Se}=t;t=Se}const z=o.guideConfigs.legend?.size,m=["series","color","stroke"].map(C=>o.guideConfigs.legend?.[C]).find(C=>C?.target===i);z?.target===i&&z.inheritAppearance===!0&&m&&(h.kind==="size"||["series","color","stroke"].includes(h.kind))&&(o=o._withLegendConfig("size",{...z,inheritAppearance:!1,labels:{...m.labels,offset:z.labels.offset},titleStyle:{...m.titleStyle}}));const{kind:b}=h,g=o.guideConfigs.legend[b],{channel:k,...d}=t,p=B(g),{alongOffset:L,...E}=p??{},_=p===void 0?g:{...g,...E},G=O&&!["gradient","strokeGradient","opacity"].includes(b)?{..._,direction:void 0}:_,$=C=>p===void 0?C:C._withLegendConfig(b,{...C.guideConfigs.legend[b],position:g.position,...d.align===void 0?{align:g.align}:{},...d.offset===void 0?{offset:g.offset,inferredOffset:g.inferredOffset}:{}}).rematerializeLegend();return["series","color","stroke"].includes(b)?$(X(o,b,G,void 0,d)):b==="colorSize"?$(ee(o,G,d)):["size","strokeWidth"].includes(b)?$(R(o,b,G,d)):["interval","strokeInterval"].includes(b)?$(N(o,G,d,b==="strokeInterval"?"stroke":"color")):$(K(o,b,G,d))}const f=this.guideConfigs.legendGroups?.[i];if(!n&&(f!==void 0||t.blockLayout!==void 0||t.at!==void 0||t.alongOffset!==void 0||re.includes(t.position)&&Object.values(this.guideConfigs.legend??{}).some(o=>o.target===i&&o.kind===void 0))){const o=Ie(this,t,i,{corner:re.includes(t.position)}),{channels:h,...S}=o.options;if(h!==void 0)return te(o.program,i,o.options,e);const w=["position","layout","align","direction","columns","offset","titlePosition","itemGap","rowPadding","titlePadding"];if(Object.keys(S).every(m=>m==="target"||w.includes(m))){let m=o.program;const b=Object.entries(m.guideConfigs.legend??{}).filter(([,g])=>g.target===i);for(const[g]of b){const k=m.guideConfigs.legend[g],d={...S};if(le(k)!==void 0)for(const p of["position","align","offset"])delete d[p];if(["gradient","strokeGradient","opacity"].includes(g)&&k.representation!=="symbols")for(const p of g==="opacity"?["layout","columns","direction"]:["layout","columns"]){if(Object.hasOwn(t,p))throw new Error(`${g} legend does not accept ${p}.`);delete d[p]}["gradient","strokeGradient"].includes(g)&&k.representation!=="symbols"&&delete d.itemGap,["series","color","stroke"].includes(g)?m=X(m,g,k,void 0,d):g==="colorSize"?m=ee(m,k,d):["size","strokeWidth"].includes(g)?m=R(m,g,k,d,!0):["interval","strokeInterval"].includes(g)?m=N(m,k,d,g==="strokeInterval"?"stroke":"color"):m=K(m,g,k,d)}return m.rematerializeLegend()}if(t.combine===void 0&&Object.values(this.guideConfigs.legend??{}).some(m=>m.target===i&&["size-shape","color-size","color-size-shape"].includes(m.combine))){if(t.values!==void 0&&P(o.program,i).length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");let m=o.program;const b={...t};for(const g of["position","align","offset","blockLayout","at"])delete b[g];for(const g of P(m,i))m=Y.call(m,{...b,target:i,channel:g.channels[0]},!0);return m.rematerializeLegend()}let z=Y.call(o.program,o.options,!0,e);for(const[m,b]of Object.entries(this.guideConfigs.legend??{}))b.target!==i||le(b)===void 0||z.guideConfigs.legend[m]===void 0||(z=z._withLegendConfig(m,{...z.guideConfigs.legend[m],position:b.position,align:b.align,offset:b.offset,inferredOffset:b.inferredOffset,...t.direction===void 0?{direction:b.direction}:{}}));return z.rematerializeLegend()}if(Object.hasOwn(t,"combine")){if(Pe(t.combine),t.combine==="size-shape"&&t.channels!==void 0&&!(t.channels.length===2&&t.channels.includes("size")&&t.channels.includes("shape")))throw new Error("size-shape combine requires exactly shape and size channels.");const o=t.channels??[...new Set(P(this,i).flatMap(h=>h.channels))];return te(this,i,{...t,channels:o},e)}if(t.channels!==void 0)return te(this,i,t,e);const c=this.guideConfigs.legend??{},a=Object.keys(c).filter(o=>c[o]?.target===i);if(t.representation!==void 0){if(c.gradient?.target!==i)throw new Error("Legend representation requires continuous color.");return K(this,"gradient",c.gradient,t)}if(c.gradient?.target===i&&c.gradient.representation==="symbols"&&a.length>1)throw new Error("Multiple legend blocks require editLegendBlock with an explicit channel for appearance or samples.");if(t.values!==void 0&&a.length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");const u=["series","color","stroke"].filter(o=>c[o]?.target===i);if(u.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const r=u[0];if(r!==void 0)return X(this,r,c[r],c.size?.target===i?c.size:void 0,t);if(c.colorSize?.target===i)return ee(this,c.colorSize,t);if(c.interval?.target===i)return N(this,c.interval,t);if(c.strokeInterval?.target===i)return N(this,c.strokeInterval,t,"stroke");if(c.strokeWidth?.target===i)return R(this,"strokeWidth",c.strokeWidth,t);if(c.size?.target===i)return R(this,"size",c.size,t);const y=["gradient","strokeGradient","opacity"].find(o=>c[o]?.target===i);return K(this,y,c[y],t)}function ht(t,n){if(W(n,[...J,"visible"],"editLegend"),["channels","combine","representation"].some(y=>Object.hasOwn(n,y)))throw new Error('editLegend channel "all" edits existing blocks without replacing their content.');const e=I(t,n.target,"editLegend"),l=P(t,e);if(!l.length)throw new Error('editLegend channel "all" requires an existing legend.');const{channel:i,target:f,...s}=n;if(!Object.keys(s).length)throw new Error("editLegend requires at least one change.");const c=["position","blockLayout","at","layout","align","offset","alongOffset"],a=Object.fromEntries(Object.entries(s).filter(([y])=>c.includes(y))),u=Object.fromEntries(Object.entries(s).filter(([y])=>!c.includes(y)));let r=t;if(Object.keys(a).length){if(a.position!==void 0||a.at!==void 0)for(const o of l){const{blockOverrides:h,...S}=o.config,w=Z(o.config,o.key,{removePosition:!0});r=r._withLegendConfig(o.kind,{...S,...w===void 0?{}:{blockOverrides:w},direction:void 0})}r=r.editLegend({target:e,...a,blockLayout:a.blockLayout??t.guideConfigs.legendGroups?.[e]?.blockLayout??(["top","bottom"].includes(a.position)?"row":"column")})}if(Object.keys(u).length)for(const y of l){const o={...u};y.family==="gradient"&&delete o.itemGap,Object.keys(o).length&&(r=r.editLegend({target:e,channel:y.channels[0],...o}))}return r}const ke=ve({op:"editLegend",description:"Edit one or all existing legend blocks.",scope:"any"},$e(function(t={}){if(this.compositionSpec!==void 0)return xe(this,"editLegend",t);if(t.channel==="all")return ht(this,t);const n=ze(t,["labels","titleStyle"]);if(Object.keys(n.visibility).length){const u=I(this,t.target,"editLegend"),r=t.channel===void 0?P(this,u):[V(this,t,"editLegend")];if(!r.length)throw new Error("Visibility requires an existing legend.");let o=Object.keys(n.args).some(h=>!["target","channel"].includes(h))?ke.call(this,n.args):this;for(const h of r)o=Ce(o,"legend",h.kind,n.visibility);return o}if(W(t,J,"editLegend"),Object.hasOwn(t,"itemsOffset")){const{itemsOffset:u,...r}=t;ne({},u);let o=Object.keys(r).some(w=>!["target","channel"].includes(w))?ke.call(this,r):this;const h=I(o,t.target,"editLegend"),S=t.channel===void 0?P(o,h):[V(o,t,"editLegend")];if(!S.length)throw new Error("Legend itemsOffset requires an existing legend block.");for(const w of S)o=o._withLegendConfig(w.kind,ne(o.guideConfigs.legend[w.kind],u));return o.rematerializeLegend()}if(!Object.hasOwn(t,"clipHeight"))return de(this,Y.call(this,t),t);const{clipHeight:e,...l}=t,i=Object.keys(l).some(u=>!["target","channel"].includes(u));let f=this;if(e==="auto"){const u=I(f,t.target,"editLegend"),r=t.channel===void 0?P(f,u):[V(f,t,"editLegend")];for(const y of r)f=f._withLegendConfig(y.kind,oe(y.config,"auto"))}let s=i?Y.call(f,l):f;const c=I(s,t.target,"editLegend"),a=t.channel===void 0?P(s,c):[V(s,t,"editLegend")];if(!a.length)throw new Error("Legend clipHeight requires an existing legend block.");for(const u of a){const r=oe(s.guideConfigs.legend[u.kind],e);je(u.kind,r),s=s._withLegendConfig(u.kind,r)}return de(this,s.rematerializeLegend(),t)}));export{ke as editLegend};
|
|
@@ -20,7 +20,7 @@ function makeFocusedLegendEdit({ name, options, map }) {
|
|
|
20
20
|
const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
21
21
|
name: "editLegendLayout",
|
|
22
22
|
options: [
|
|
23
|
-
"position", "layout", "align", "direction", "columns", "offset", "blockLayout", "at",
|
|
23
|
+
"position", "layout", "align", "direction", "columns", "offset", "alongOffset", "blockLayout", "at",
|
|
24
24
|
"titlePosition", "itemGap", "rowPadding", "titlePadding"
|
|
25
25
|
],
|
|
26
26
|
map: args => args
|
|
@@ -28,7 +28,7 @@ const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
|
28
28
|
|
|
29
29
|
const editLegendLabels = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
30
30
|
name: "editLegendLabels",
|
|
31
|
-
options: ["offset", "color", "fontSize", "fontFamily", "fontWeight", "format"],
|
|
31
|
+
options: ["offset", "color", "fontSize", "fontFamily", "fontWeight", "format", "align"],
|
|
32
32
|
map: ({ target, channel, ...labels }) => ({ target, ...(channel === undefined ? {} : { channel }), labels })
|
|
33
33
|
});
|
|
34
34
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{legendResourcePolicy as
|
|
1
|
+
import{legendResourcePolicy as y}from"../../../materialization/guides/resources.js";import{LEGEND_POSITIONS as x,LEGEND_BLOCK_LAYOUTS as v}from"../../../core/vocabulary.js";import{validateLegendGroupState as G}from"../../../grammar/legendGroups.js";import{validateOptionObject as _,validateNonNegativeFinite as C}from"../../../core/validation.js";import{PLOT_LEGEND_POSITIONS as O}from"../../../layout/legend.js";import{legendGroup as N}from"../../../materialization/legendGroups.js";const j=x;function S(n){if(Object.hasOwn(n,"at")){if(_(n.at,["x","y"],"Legend at"),!Number.isFinite(n.at.x)||!Number.isFinite(n.at.y))throw new TypeError("Legend at requires finite x and y coordinates.");if(["position","align","offset","alongOffset"].some(e=>Object.hasOwn(n,e)))throw new Error("Legend at conflicts with position, align, offset and alongOffset in the same call.")}if(Object.hasOwn(n,"alongOffset")&&!Number.isFinite(n.alongOffset))throw new TypeError("Legend alongOffset must be finite.");if(Object.hasOwn(n,"blockLayout")&&!v.includes(n.blockLayout))throw new Error("Legend blockLayout must be row, column or wrap.")}function b(n,e){return n.at!==void 0||e.at!==void 0?n.at!==void 0&&e.at!==void 0&&n.at.x===e.at.x&&n.at.y===e.at.y:n.position===e.position&&n.align===e.align&&n.offset===e.offset&&(n.alongOffset??0)===(e.alongOffset??0)}function K(n,e,r,{creation:l=!1,corner:d=!1,nativeOffset:f=30}={}){S(e);const t=N(n,r),s=t!==void 0||e.at!==void 0||e.blockLayout!==void 0||e.alongOffset!==void 0||d,{at:p,blockLayout:c,alongOffset:g,...a}=e;if(!s)return{program:n,options:a,grouped:!1};const m=Object.entries(n.guideConfigs.legend??{}).filter(([,o])=>o.target===r),w=m.map(([,o])=>o),h=m.find(([o])=>["series","color","stroke"].includes(o))?.[1].offset??f;if(w.some(o=>o.layout==="legacy-bottom")||e.layout==="legacy-bottom")throw new Error("Legend groups require edge layout.");let i;if(e.at!==void 0)i={at:{...e.at}};else if(e.position!==void 0){if(!j.includes(e.position))throw new Error(`Unsupported legend position "${e.position}".`);const o=O.includes(e.position),u=t?.placement.position===e.position;i={position:e.position,align:e.align??(u?t.placement.align:"center"),offset:e.offset??(u?t.placement.offset:o?8:h)}}else if(t!==void 0){if(i=t.placement.at===void 0?{...t.placement,...e.align===void 0?{}:{align:e.align},...e.offset===void 0?{}:{offset:e.offset}}:t.placement,t.placement.at!==void 0&&(e.align!==void 0||e.offset!==void 0))throw new Error("Free legend placement requires position before align or offset.")}else{const o=w.map(u=>({position:u.position,align:u.align,offset:u.offset}));if(o.length>1&&!o.every(u=>b(u,o[0])))throw new Error("Legend blockLayout requires an explicit anchor when existing blocks disagree.");i=o[0]??{position:"right",align:"center",offset:h}}if(i.at!==void 0&&e.alongOffset!==void 0)throw new Error("Free legend placement requires position before alongOffset.");if(i.position!==void 0){const o=e.alongOffset??t?.placement.alongOffset;if(o!==void 0&&(i={...i,alongOffset:o}),!["left","center","right"].includes(i.align))throw new Error("Unsupported legend group alignment.");if((O.includes(i.position)||["left","right"].includes(i.position))&&i.align!=="center")throw new Error("Side and in-plot legend groups require center alignment.");C(i.offset,"Legend group offset")}if(l&&t!==void 0&&(e.at!==void 0||e.position!==void 0||e.align!==void 0||e.offset!==void 0||e.alongOffset!==void 0)&&!b(i,t.placement))throw new Error("New legend block anchor conflicts with its target group.");if(l&&t!==void 0&&e.blockLayout!==void 0&&e.blockLayout!==t.blockLayout)throw new Error("New legend blockLayout conflicts with its target group.");const E=e.blockLayout??t?.blockLayout??(i.at!==void 0?"column":O.includes(i.position)?"row":"wrap"),L={placement:i,blockLayout:E};G({[r]:L});const k=n._withMaterializationConfig(["guides","legendGroups",r],L);return i.position!==void 0&&(a.position=i.position,a.offset=i.offset,a.align=i.align),{program:k,options:a,grouped:!0}}function M(n,e){if(e.target!==void 0)return e.target;const r=e.channels,l=n.semanticSpec.layers.filter(d=>r===void 0?Object.keys(d.encoding??{}).some(f=>["color","stroke","shape","strokeDash","size","opacity","strokeWidth"].includes(f)&&d.encoding[f]?.scale!==void 0):r.every(f=>d.encoding?.[f]?.scale!==void 0));if(l.length!==1)throw new Error("Legend group requires one eligible target or an explicit target.");return l[0].id}function U(n,e,r,{creation:l=!1}={}){let d=e;for(const[f,t]of Object.entries(e.guideConfigs.legend??{})){const s=n.guideConfigs.legend?.[f];if(l&&s!==void 0||!l&&t===s)continue;const p=s??Object.entries(n.guideConfigs.legend??{}).find(([g,a])=>a.target===t.target&&y(g).semanticKind===y(f).semanticKind)?.[1],c=r.offset!==void 0?!1:l?!0:p?.inferredOffset===!0;d=d._withLegendConfig(f,{...t,inferredOffset:c})}for(const[f,t]of Object.entries(e.guideConfigs.legendGroups??{})){if(t.placement.at!==void 0){if(t.inferredOffset!==void 0){const{inferredOffset:c,...g}=t;d=d._withMaterializationConfig(["guides","legendGroups",f],g)}continue}const s=n.guideConfigs.legendGroups?.[f];if(t===s)continue;const p=r.offset!==void 0?!1:s?.placement.at!==void 0?!0:s!==void 0?s.inferredOffset===!0:Object.values(d.guideConfigs.legend??{}).filter(c=>c.target===f).every(c=>c.inferredOffset===!0);d=d._withMaterializationConfig(["guides","legendGroups",f],{...t,inferredOffset:p})}return d}export{M as creationLegendTarget,K as prepareLegendGroup,U as recordLegendOffsetIntent,S as validateLegendGroupOptions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{translateGraphic as m}from"../../../materialization/graphicTranslation.js";import{editLegendBorderBounds as z}from"../../../materialization/legendBorder.js";import{resolveLegendAxisOffset as h,effectiveLegendOffsetConfig as y}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as D}from"../../../layout/legendGroup.js";import{localLegendPosition as G,localLegendPlacement as F,legendGroup as R}from"../../../materialization/legendGroups.js";import{editLegendBackground as T,resolveLegendBackgroundFromBounds as K}from"./continuous/common.js";import{action as O}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 w}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as Y,resolveHorizontalLegendGroup as _,resolveSingleHorizontalLegendPlacement as W,isHorizontalEdgeLegend as E,resolveSideLegendLane as J,resolveSingleSideLegendPlacement as N}from"../../../layout/legendLane.js";import{findCanvasGraphic as C}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as Q}from"../../../materialization/guides/resources.js";const L=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(s,t){return["series","color","stroke"].map(n=>s.guideConfigs.legend?.[n]).find(n=>n?.target===t)}function V(s,t,n){return t==="size"&&n.inheritPosition!==!1?U(s,n.target)?.position??n.position:n.position}function Z(s){return s.offset}function x(s,t){return Q(t).graphicIds.filter(n=>s.graphicSpec.objects[n]!==void 0)}function ee(s,t){const n=x(s,t),i=n.find(c=>c.endsWith("Title")),o=n.find(c=>c.endsWith("Labels")),e=n.find(c=>c.endsWith("Background")),r=n.filter(c=>c!==i&&c!==o&&c!==e);return{titleId:i,labelId:o,backgroundId:e,symbolIds:r}}function te(s,t){let n=M(s.graphicSpec,t,s.materializationConfigs.textMetrics);const i=s.graphicSpec.objects[t];if(!t.includes("LegendSymbol")||i?.type!=="line")return n;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 r=e.strokeWidth/2;n={left:Math.min(n.left,Math.min(e.x1,e.x2)-r),right:Math.max(n.right,Math.max(e.x1,e.x2)+r),top:Math.min(n.top,Math.min(e.y1,e.y2)-r),bottom:Math.max(n.bottom,Math.max(e.y1,e.y2)+r)}}return n}function b(s,t){return t.map(n=>te(s,n)).filter(n=>n!==void 0).reduce((n,i)=>n===void 0?i:{left:Math.min(n.left,i.left),right:Math.max(n.right,i.right),top:Math.min(n.top,i.top),bottom:Math.max(n.bottom,i.bottom)},void 0)}function ne(s,t){const n=s.graphicSpec.objects[t];return(n.items?.[0]?.properties??n.properties).x}function B(s,t,n){const i=ee(s,t);if(i.labelId===void 0||i.symbolIds.length===0)throw new Error(`Legend lane requires complete ${t} graphics.`);const o=[...i.symbolIds,i.labelId,...i.titleId===void 0?[]:[i.titleId]],e=b(s,o),r=["gradient","strokeGradient"].includes(t)&&n.representation!=="symbols"?[t==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:i.symbolIds,c=b(s,r),u=b(s,i.symbolIds),f=M(s.graphicSpec,i.labelId,s.materializationConfigs.textMetrics),a=s.graphicSpec.objects[i.labelId].items?.length===0,d=i.titleId===void 0?void 0:(()=>{const g=s.graphicSpec.objects[i.titleId],p=M(s.graphicSpec,i.titleId,s.materializationConfigs.textMetrics);return{x:g.properties.x,y:g.properties.y,fontSize:g.properties.fontSize,width:p.right-p.left,bounds:p}})();if([e,c,u].includes(void 0)||f===void 0&&!a)throw new Error(`Legend lane could not measure ${t} graphics.`);const l=["gradient","strokeGradient"].includes(t)&&n.representation!=="symbols"&&(n.gradientDirection==="horizontal"||n.titlePosition==="bottom");return{id:t,kind:t,preserveAlignment:l,preserveGrid:l||(["series","color","stroke"].includes(t)||n.representation==="symbols")&&(n.columns??1)>1,target:n.target,border:j(t,n),offset:Z(n),bounds:e,title:d,symbol:{centerX:X(c.left,c.right),left:u.left,right:u.right,bounds:c},labels:{x:a?u.right+n.labels.offset:ne(s,i.labelId),width:a?0:f.right-f.left},occupiedBounds:b(s,[...o,...i.backgroundId===void 0?[]:[i.backgroundId]]),...i,foregroundIds:o}}function j(s,t){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size","colorSize"].includes(s)?t.border:!1}function P(s,t){const n=[];for(let i=0;i<s.length;i+=1){const o=s[i],e=t[o.kind],r=s[i+1];if(["series","color","stroke"].includes(o.kind)&&r?.kind==="size"&&r.target===o.target){n.push({id:`${o.kind}+size`,blocks:[o,r.backgroundId===void 0?r:{...r,bounds:r.occupiedBounds,inset:r.border.padding+r.border.lineWidth/2}],border:j(o.kind,e),backgroundId:o.backgroundId}),i+=1;continue}n.push({id:o.kind,blocks:[o],border:j(o.kind,e),backgroundId:o.backgroundId})}return n}function v(s,t){const n=s.guideConfigs.legend??{},i=new Map(s.semanticSpec.layers.map((o,e)=>[o.id,e]));return Object.entries(n).filter(([o,e])=>G(e)===void 0&&R(s,e.target)===void 0&&V(s,o,e)===t).sort(([o,e],[r,c])=>i.get(e.target)-i.get(c.target)||L[o]-L[r])}function oe(s){return["right","left"].some(t=>v(s,t).length>0)}function ie(s){return["top","bottom"].some(t=>v(s,t).some(([,n])=>E({...n,position:t})))}function xe(s){return Object.values(s.guideConfigs.legend??{}).some(t=>G(t)!==void 0)||oe(s)||ie(s)}function A(s,t){const n=o=>o.textBaseline==="middle"?{...o,y:o.y-o.fontSize/2,textBaseline:"top"}:o;let i=s;for(const o of t){const e=i.graphicSpec.objects[o];if(e.type==="text")if(e.items!==void 0){if(!e.items.some(r=>r.properties.textBaseline==="middle"))continue;i=i.editGraphics({target:o,property:"items",value:e.items.map(r=>({type:r.type??e.type,properties:n(r.properties)}))})}else e.properties.textBaseline==="middle"&&(i=i.editGraphics({target:o,property:"y",value:n(e.properties).y}).editGraphics({target:o,property:"textBaseline",value:"top"}))}return i}function $(s,t){return t.map(n=>{const i=n.blocks[0],o=s.guideConfigs.legend?.[i.kind],e=n.blocks.length>1,r=e?void 0:i.titleId,c=n.blocks.flatMap(l=>l.foregroundIds).filter(l=>l!==r);e&&c.push(...n.blocks.slice(1).flatMap(l=>l.backgroundId===void 0?[]:[l.backgroundId]));const u=b(s,c),f=b(s,[...c,...r===void 0?[]:[r]]);if(u===void 0||f===void 0)throw new Error(`Legend lane could not measure ${n.id} content.`);const a=n.border,d=a===!1?0:a.padding+a.lineWidth/2;return{id:n.id,atomic:e,titleId:r,contentIds:c,title:e?void 0:i.title,inline:o?.titlePosition==="left",element:e?f:i.symbol.bounds,content:u,horizontal:{left:f.left,right:f.right,top:f.top,bottom:f.bottom},inset:d,padding:a===!1?0:a.padding,backgroundId:n.backgroundId}})}function q(s,t,n){let i=s;const o=new Map(t.map(e=>[e.id,e]));for(const e of n.placements){const r=o.get(e.id);r.titleId!==void 0&&(i=m(i,r.titleId,e.dx,e.titleDy));for(const c of r.contentIds)i=m(i,c,e.dx,e.contentDy);e.background!==void 0&&(i=z(i,e.background.id,e.background))}return n.background!==void 0&&(i=z(i,n.background.id,n.background)),i}const Ie=O({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(s={}){if(S(s,"rematerializeSideLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const t=w(this),n=C(this)?.properties;if(t===void 0||n===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{},o=["right","left"].flatMap(r=>{const c=v(this,r);if(c.length===0)return[];if(c.length===1)return[{side:r,entries:c}];const u=c.map(([a,d])=>{const l=y(this,a,d),g={...d,position:r,inferredOffset:l.inferredOffset};return B(this,a,{...d,offset:h(this,g,t)})});return[{plan:J({side:r,plot:t,canvas:n,groups:P(u,i)}),blocks:u,side:r,entries:c}]});let e=this;for(const{plan:r,blocks:c,side:u,entries:f}of o){if(r===void 0)continue;const a=new Map(c.map(d=>[d.id,d]));for(const d of r.placements){const l=a.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 c){if(d.backgroundId===void 0||r.backgrounds.some(p=>p.id===d.backgroundId))continue;const l=b(e,d.foregroundIds),g=K([l],d.border,n,"Legend");e=T(e,d.backgroundId,g,d.border)}for(const d of r.backgrounds)e=z(e,d.id,d)}for(const{side:r,entries:c}of o){const u=c.flatMap(([d])=>x(e,d));e=A(e,u);let f=b(e,u);if(c.length===1){const[d,l]=c[0],g=y(e,d,l),p=h(e,g,t);if(g.inferredOffset===!0&&p!==g.offset){const k=r==="left"?t.x-p-f.right:t.x+t.width+p-f.left;for(const I of u)e=m(e,I,k,0);f=b(e,u)}}if(c.length>1){const d=c.map(([l,g])=>y(e,l,g)).filter(l=>l.inferredOffset===!0).map(l=>({base:l.offset,resolved:h(e,l,t)})).filter(l=>l.resolved>l.base);if(d.length>0){const l=Math.max(...d.map(p=>p.resolved)),g=r==="right"?t.x+t.width+l-f.left:t.x-l-f.right;for(const p of u)e=m(e,p,g,0);f=b(e,u)}}const{dy:a}=N({side:r,plot:t,canvas:n,bounds:f});for(const d of u)e=m(e,d,0,a)}for(const[r,c]of Object.entries(e.markConfigs))c.gradientPlot?.materialized&&e.graphicSpec.objects[`${r}DensityLegend`]&&(e=e.rematerializeGradientPlotLegend({owner:r}));return e}),ye=O({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(s={}){if(S(s,"rematerializeHorizontalLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const t=w(this),n=C(this)?.properties;if(t===void 0||n===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 r=v(o,e).filter(([,a])=>E({...a,position:e})).map(([a,d])=>[a,y(o,a,d)]);if(r.length===0)continue;if(r.length===1){const[a,d]=r[0],l=x(o,a),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:t,canvas:n,config:{...d,offset:h(o,d,t)},bounds:g});for(const I of l)o=m(o,I,p,k);continue}for(const[a,d]of r){if(d.inferredOffset!==!0||h(o,d,t)===d.offset)continue;const l=x(o,a),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:t,canvas:n,config:{...d,offset:h(o,d,t)},bounds:g});for(const I of l)o=m(o,I,p,k)}let c=P(r.map(([a,d])=>B(o,a,d)),i);for(const a of c.filter(d=>d.blocks.length>1)){const d=i[a.blocks[0].kind],l=$(o,a.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:t,canvas:n,groups:l,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0,align:d.align,offset:h(o,d,t),border:a.border,backgroundId:a.backgroundId});o=q(o,l,g)}if(c.length<2)continue;c=P(r.map(([a,d])=>B(o,a,d)),i);const u=$(o,c),f=Y({edge:e,plot:t,canvas:n,groups:u,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0});o=q(o,u,f)}return o}),Le=O({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(s={}){if(S(s,"rematerializeLegendGroups"),this.context.facetLegendIntrinsic===!0)return this;let t=this;const n=w(t),i=C(t)?.properties;for(const[o,e]of Object.entries(t.guideConfigs.legendGroups??{})){const r=Object.entries(t.guideConfigs.legend??{}).filter(([,a])=>a.target===o&&G(a)===void 0).sort(([a],[d])=>L[a]-L[d]);t=A(t,r.flatMap(([a])=>x(t,a)));const c=r.map(([a])=>({id:a,ids:x(t,a),bounds:b(t,x(t,a))})),u=e.placement.at!==void 0?e:{...e,placement:{...e.placement,offset:h(t,{...e.placement,inferredOffset:e.inferredOffset},n)}},f=D({blocks:c,group:u,plot:n,canvas:i,allowOverflow:t.materializationConfigs.canvas?.plot!==void 0});for(const a of f){const d=c.find(l=>l.id===a.id);for(const l of d.ids)t=m(t,l,a.dx,a.dy)}}for(const[o,e]of Object.entries(t.guideConfigs.legend??{})){const r=F(e);if(r===void 0)continue;const c=x(t,o);t=A(t,c);const u={placement:{...r,offset:h(t,r,n)},inferredOffset:e.inferredOffset,blockLayout:"column"},[f]=D({blocks:[{id:o,bounds:b(t,c)}],group:u,plot:n,canvas:i,allowOverflow:t.materializationConfigs.canvas?.plot!==void 0});for(const a of c)t=m(t,a,f.dx,f.dy)}return t});export{ie as hasHorizontalLegendLane,xe as hasLegendLane,oe as hasSideLegendLane,ye as rematerializeHorizontalLegendLane,Le as rematerializeLegendGroups,Ie as rematerializeSideLegendLane};
|
|
1
|
+
import{translateGraphic as h}from"../../../materialization/graphicTranslation.js";import{editLegendBorderBounds as w}from"../../../materialization/legendBorder.js";import{resolveLegendAxisOffset as L,effectiveLegendOffsetConfig as y}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as H}from"../../../layout/legendGroup.js";import{localLegendPosition as I,localLegendPlacement as K,retainsLegendSlot as W,legendGroup as E}from"../../../materialization/legendGroups.js";import{editLegendBackground as X,resolveLegendBackgroundFromBounds as Y}from"./continuous/common.js";import{action as S}from"../../../core/action.js";import{noOptions as G}from"../../../core/validation.js";import{resolveConcreteGraphicBounds as M,unionConcreteGraphicBounds as F}from"../../../grammar/schemas/graphicBounds.js";import{finiteMidpoint as _}from"../../../grammar/numeric.js";import{resolveGraphicBounds as B,canvasOverflowError as J}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as N,resolveHorizontalLegendGroup as Q,resolveSingleHorizontalLegendPlacement as q,isHorizontalEdgeLegend as $,resolveSideLegendLane as U,resolveSingleSideLegendPlacement as V}from"../../../layout/legendLane.js";import{findCanvasGraphic as P}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as Z}from"../../../materialization/guides/resources.js";const v=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 ee(s,t){return["series","color","stroke"].map(e=>s.guideConfigs.legend?.[e]).find(e=>e?.target===t)}function te(s,t,e){return t==="size"&&e.inheritPosition!==!1?ee(s,e.target)?.position??e.position:e.position}function ne(s){return s.offset}function k(s,t){return Z(t).graphicIds.filter(e=>s.graphicSpec.objects[e]!==void 0)}function oe(s,t){const e=k(s,t),r=e.find(d=>d.endsWith("Title")),i=e.find(d=>d.endsWith("Labels")),o=e.find(d=>d.endsWith("Background")),n=e.filter(d=>d!==r&&d!==i&&d!==o);return{titleId:r,labelId:i,backgroundId:o,symbolIds:n}}function ie(s,t){let e=M(s.graphicSpec,t,s.materializationConfigs.textMetrics);const r=s.graphicSpec.objects[t];if(!t.includes("LegendSymbol")||r?.type!=="line")return e;const i=r.items?.map(o=>o.properties)??[r.properties];for(const o of i){if(Object.hasOwn(o,"lineCap")||typeof o.stroke!="string"||!(o.strokeWidth>0))continue;const n=o.strokeWidth/2;e={left:Math.min(e.left,Math.min(o.x1,o.x2)-n),right:Math.max(e.right,Math.max(o.x1,o.x2)+n),top:Math.min(e.top,Math.min(o.y1,o.y2)-n),bottom:Math.max(e.bottom,Math.max(o.y1,o.y2)+n)}}return e}function b(s,t){return t.map(e=>ie(s,e)).filter(e=>e!==void 0).reduce((e,r)=>e===void 0?r:{left:Math.min(e.left,r.left),right:Math.max(e.right,r.right),top:Math.min(e.top,r.top),bottom:Math.max(e.bottom,r.bottom)},void 0)}function re(s,t){const e=s.graphicSpec.objects[t];return(e.items?.[0]?.properties??e.properties).x}function j(s,t,e){const r=oe(s,t);if(r.labelId===void 0||r.symbolIds.length===0)throw new Error(`Legend lane requires complete ${t} graphics.`);const i=[...r.symbolIds,r.labelId,...r.titleId===void 0?[]:[r.titleId]],o=b(s,i),n=["gradient","strokeGradient"].includes(t)&&e.representation!=="symbols"?[t==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:r.symbolIds,d=b(s,n),u=b(s,r.symbolIds),p=M(s.graphicSpec,r.labelId,s.materializationConfigs.textMetrics),f=s.graphicSpec.objects[r.labelId].items?.length===0,l=r.titleId===void 0?void 0:(()=>{const a=s.graphicSpec.objects[r.titleId],g=M(s.graphicSpec,r.titleId,s.materializationConfigs.textMetrics);return{x:a.properties.x,y:a.properties.y,fontSize:a.properties.fontSize,width:g.right-g.left,bounds:g}})();if([o,d,u].includes(void 0)||p===void 0&&!f)throw new Error(`Legend lane could not measure ${t} graphics.`);const c=["gradient","strokeGradient"].includes(t)&&e.representation!=="symbols"&&(e.gradientDirection==="horizontal"||e.titlePosition==="bottom");return{id:t,kind:t,preserveAlignment:c,preserveLabelAlignment:e.labels.align!==void 0,preserveGrid:c||(["series","color","stroke"].includes(t)||e.representation==="symbols")&&(e.columns??1)>1,target:e.target,border:A(t,e),offset:ne(e),bounds:o,title:l,symbol:{centerX:_(d.left,d.right),left:u.left,right:u.right,bounds:d},labels:{x:f?u.right+e.labels.offset:e.labels.align===void 0?re(s,r.labelId):p.left,width:f?0:p.right-p.left},occupiedBounds:b(s,[...i,...r.backgroundId===void 0?[]:[r.backgroundId]]),...r,foregroundIds:i}}function A(s,t){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size","colorSize"].includes(s)?t.border:!1}function D(s,t){const e=[];for(let r=0;r<s.length;r+=1){const i=s[r],o=t[i.kind],n=s[r+1];if(["series","color","stroke"].includes(i.kind)&&n?.kind==="size"&&n.target===i.target){e.push({id:`${i.kind}+size`,blocks:[i,n.backgroundId===void 0?n:{...n,bounds:n.occupiedBounds,inset:n.border.padding+n.border.lineWidth/2}],border:A(i.kind,o),backgroundId:i.backgroundId}),r+=1;continue}e.push({id:i.kind,blocks:[i],border:A(i.kind,o),backgroundId:i.backgroundId})}return e}function z(s,t){const e=s.guideConfigs.legend??{},r=new Map(s.semanticSpec.layers.map((i,o)=>[i.id,o]));return Object.entries(e).filter(([i,o])=>(I(o)===void 0||W(o)||I(o)===t)&&E(s,o.target)===void 0&&te(s,i,o)===t).sort(([i,o],[n,d])=>r.get(o.target)-r.get(d.target)||v[i]-v[n])}function se(s){return["right","left"].some(t=>z(s,t).length>0)}function de(s){return["top","bottom"].some(t=>z(s,t).some(([,e])=>$({...e,position:t})))}function Ie(s){return Object.values(s.guideConfigs.legend??{}).some(t=>I(t)!==void 0)||se(s)||de(s)}function C(s,t){const e=i=>i.textBaseline==="middle"?{...i,y:i.y-i.fontSize/2,textBaseline:"top"}:i;let r=s;for(const i of t){const o=r.graphicSpec.objects[i];if(o.type==="text")if(o.items!==void 0){if(!o.items.some(n=>n.properties.textBaseline==="middle"))continue;r=r.editGraphics({target:i,property:"items",value:o.items.map(n=>({type:n.type??o.type,properties:e(n.properties)}))})}else o.properties.textBaseline==="middle"&&(r=r.editGraphics({target:i,property:"y",value:e(o.properties).y}).editGraphics({target:i,property:"textBaseline",value:"top"}))}return r}function R(s,t){return t.map(e=>{const r=e.blocks[0],i=s.guideConfigs.legend?.[r.kind],o=e.blocks.length>1,n=o?void 0:r.titleId,d=e.blocks.flatMap(c=>c.foregroundIds).filter(c=>c!==n);o&&d.push(...e.blocks.slice(1).flatMap(c=>c.backgroundId===void 0?[]:[c.backgroundId]));const u=b(s,d),p=b(s,[...d,...n===void 0?[]:[n]]);if(u===void 0||p===void 0)throw new Error(`Legend lane could not measure ${e.id} content.`);const f=e.border,l=f===!1?0:f.padding+f.lineWidth/2;return{id:e.id,atomic:o,titleId:n,contentIds:d,title:o?void 0:r.title,inline:i?.titlePosition==="left",element:o?p:r.symbol.bounds,content:u,horizontal:{left:p.left,right:p.right,top:p.top,bottom:p.bottom},inset:l,padding:f===!1?0:f.padding,backgroundId:e.backgroundId}})}function T(s,t,e){let r=s;const i=new Map(t.map(o=>[o.id,o]));for(const o of e.placements){const n=i.get(o.id);n.titleId!==void 0&&(r=h(r,n.titleId,o.dx,o.titleDy));for(const d of n.contentIds)r=h(r,d,o.dx,o.contentDy);o.background!==void 0&&(r=w(r,o.background.id,o.background))}return e.background!==void 0&&(r=w(r,e.background.id,e.background)),r}const ve=S({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(s={}){G(s,"rematerializeSideLegendLane");const t=this.context.facetLegendIntrinsic===!0,e=B(this),r=P(this)?.properties;if(e===void 0||r===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{},o=["right","left"].flatMap(d=>{const u=z(this,d);if(u.length===0)return[];if(u.length===1)return[{side:d,entries:u}];const p=u.map(([l,c])=>{const a=y(this,l,c),g={...c,position:d,inferredOffset:a.inferredOffset};return j(this,l,{...c,offset:L(this,g,e)})});return[{plan:U({side:d,plot:e,canvas:r,groups:D(p,i),allowOverflow:t}),blocks:p,side:d,entries:u}]});let n=this;for(const{plan:d,blocks:u,side:p,entries:f}of o){if(d===void 0)continue;const l=new Map(u.map(c=>[c.id,c]));for(const c of d.placements){const a=l.get(c.id);a.titleId!==void 0&&(n=h(n,a.titleId,c.titleDx,c.dy),a.preserveAlignment||(n=n.editGraphics({target:a.titleId,property:"textAlign",value:"left"})));for(const g of a.symbolIds)n=h(n,g,c.symbolDx,c.dy);n=h(n,a.labelId,c.labelDx,c.dy),!a.preserveAlignment&&!a.preserveLabelAlignment&&(n=n.editGraphics({target:a.labelId,property:"textAlign",value:"left"}))}if(!t){for(const c of u){if(c.backgroundId===void 0||d.backgrounds.some(m=>m.id===c.backgroundId))continue;const a=b(n,c.foregroundIds),g=Y([a],c.border,r,"Legend");n=X(n,c.backgroundId,g,c.border)}for(const c of d.backgrounds)n=w(n,c.id,c)}}if(t)return o.filter(({entries:d})=>d.length>1).reduce((d,{entries:u})=>C(d,u.flatMap(([p])=>k(d,p))),n);for(const{side:d,entries:u}of o){const p=u.flatMap(([c])=>k(n,c));n=C(n,p);let f=b(n,p);if(u.length===1){const[c,a]=u[0],g=y(n,c,a),m=L(n,g,e);if(g.inferredOffset===!0&&m!==g.offset){const x=d==="left"?e.x-m-f.right:e.x+e.width+m-f.left;for(const O of p)n=h(n,O,x,0);f=b(n,p)}}if(u.length>1){const c=u.map(([a,g])=>y(n,a,g)).filter(a=>a.inferredOffset===!0).map(a=>({base:a.offset,resolved:L(n,a,e)})).filter(a=>a.resolved>a.base);if(c.length>0){const a=Math.max(...c.map(m=>m.resolved)),g=d==="right"?e.x+e.width+a-f.left:e.x-a-f.right;for(const m of p)n=h(n,m,g,0);f=b(n,p)}}const{dy:l}=V({side:d,plot:e,canvas:r,bounds:f});for(const c of p)n=h(n,c,0,l)}for(const[d,u]of Object.entries(n.markConfigs))u.gradientPlot?.materialized&&n.graphicSpec.objects[`${d}DensityLegend`]&&(n=n.rematerializeGradientPlotLegend({owner:d}));return n}),ze=S({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(s={}){if(G(s,"rematerializeHorizontalLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const t=B(this),e=P(this)?.properties;if(t===void 0||e===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const r=this.guideConfigs.legend??{};let i=this;for(const o of["top","bottom"]){const n=z(i,o).filter(([,f])=>$({...f,position:o})).map(([f,l])=>[f,y(i,f,l)]);if(n.length===0)continue;if(n.length===1){const[f,l]=n[0],c=k(i,f),a=F(i.graphicSpec,c,i.materializationConfigs.textMetrics),{dx:g,dy:m}=q({plot:t,canvas:e,config:{...l,offset:L(i,l,t)},bounds:a});for(const x of c)i=h(i,x,g,m);continue}for(const[f,l]of n){if(l.inferredOffset!==!0||L(i,l,t)===l.offset)continue;const c=k(i,f),a=F(i.graphicSpec,c,i.materializationConfigs.textMetrics),{dx:g,dy:m}=q({plot:t,canvas:e,config:{...l,offset:L(i,l,t)},bounds:a});for(const x of c)i=h(i,x,g,m)}let d=D(n.map(([f,l])=>j(i,f,l)),r);for(const f of d.filter(l=>l.blocks.length>1)){const l=r[f.blocks[0].kind],c=R(i,f.blocks.map((g,m)=>({id:g.id,blocks:[g],border:m===0?!1:g.border,backgroundId:m===0?void 0:g.backgroundId}))).map(g=>({...g,element:g.content})),a=Q({edge:o,plot:t,canvas:e,groups:c,allowOverflow:i.materializationConfigs.canvas?.plot!==void 0,align:l.align,offset:L(i,l,t),border:f.border,backgroundId:f.backgroundId});i=T(i,c,a)}if(d.length<2)continue;d=D(n.map(([f,l])=>j(i,f,l)),r);const u=R(i,d),p=N({edge:o,plot:t,canvas:e,groups:u,allowOverflow:i.materializationConfigs.canvas?.plot!==void 0});i=T(i,u,p)}return i}),Ce=S({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(s={}){if(G(s,"rematerializeLegendGroups"),this.context.facetLegendIntrinsic===!0)return this;let t=this;const e=B(t),r=P(t)?.properties;for(const[i,o]of Object.entries(t.guideConfigs.legendGroups??{})){const n=Object.entries(t.guideConfigs.legend??{}).filter(([,l])=>l.target===i&&(I(l)===void 0||W(l,o.placement.position??null))).sort(([l],[c])=>v[l]-v[c]);t=C(t,n.flatMap(([l])=>k(t,l)));const d=n.map(([l])=>({id:l,ids:k(t,l),bounds:b(t,k(t,l))})),u=o.placement.at!==void 0?o:{...o,placement:{...o.placement,offset:L(t,{...o.placement,inferredOffset:o.inferredOffset},e)}},p=d.length>0&&d.every(l=>t.context.compositionCollectedLegends?.includes(l.id)),f=H({blocks:d,group:u,plot:e,canvas:r,deferCanvasFit:p,deferPlotFit:p,allowOverflow:t.materializationConfigs.canvas?.plot!==void 0});for(const l of f){const c=d.find(a=>a.id===l.id);for(const a of c.ids)t=h(t,a,l.dx,l.dy)}}for(const[i,o]of Object.entries(t.guideConfigs.legend??{})){const n=K(o);if(n===void 0)continue;if(n.position===o.position&&o.inferredOffset!==!1&&["top","bottom","left","right"].includes(o.position)&&!E(t,o.target)){const f=n.alongOffset??0;if(f){const l=["top","bottom"].includes(o.position),c=k(t,i),a=b(t,c),g=l?f:0,m=l?0:f,x={left:a.left+g,right:a.right+g,top:a.top+m,bottom:a.bottom+m};if(!t.context.compositionCollectedLegends?.includes(i)&&(x.left<0||x.top<0||x.right>r.width||x.bottom>r.height))throw J("Legend group requires more Canvas space.",[x],r);for(const O of c)t=h(t,O,g,m)}continue}const d=k(t,i);t=C(t,d);const u={placement:{...n,offset:L(t,n,e)},inferredOffset:o.inferredOffset,blockLayout:"column"},[p]=H({blocks:[{id:i,bounds:b(t,d)}],group:u,plot:e,canvas:r,deferCanvasFit:t.context.compositionCollectedLegends?.includes(i),deferPlotFit:t.context.compositionCollectedLegends?.includes(i),allowOverflow:t.materializationConfigs.canvas?.plot!==void 0});for(const f of d)t=h(t,f,p.dx,p.dy)}return t});export{de as hasHorizontalLegendLane,Ie as hasLegendLane,se as hasSideLegendLane,ze as rematerializeHorizontalLegendLane,Ce as rematerializeLegendGroups,ve as rematerializeSideLegendLane};
|
|
@@ -41,11 +41,11 @@ export function removeLegendKinds(program, kinds) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export function resolveCategoricalLegendRevision(program, kind, previous, channels, {
|
|
44
|
-
order = program.semanticSpec.guides.legend?.[kind]?.order, validateLayout = true
|
|
44
|
+
order = program.semanticSpec.guides.legend?.[kind]?.order, validateLayout = true, preferredKind
|
|
45
45
|
} = {}) {
|
|
46
46
|
const layer = findLayer(program, previous.target);
|
|
47
47
|
const definition = resolveDefinition(program, layer, channels,
|
|
48
|
-
previous.inferredTitle ? undefined : previous.title, order);
|
|
48
|
+
previous.inferredTitle ? undefined : previous.title, order, preferredKind);
|
|
49
49
|
const {combine: _combine, sizeAreas: _areas, ...shared} = previous;
|
|
50
50
|
const config = {
|
|
51
51
|
...shared,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {colorSizeChannels} from "../../../grammar/colorSizeLegend.js";
|
|
1
2
|
import { editFacetParentLegend } from "../../../materialization/facetGuides/edit.js";
|
|
2
3
|
import { closedAction } from "../../../core/action.js";
|
|
3
4
|
|
|
@@ -8,15 +9,17 @@ import {
|
|
|
8
9
|
} from "./lifecycle.js";
|
|
9
10
|
import { resolveLegendTarget, validateLegendChannels } from "./target.js";
|
|
10
11
|
import { planLegendBlockTransitions } from "./transition.js";
|
|
12
|
+
import { legendBlockChannels } from "./target.js";
|
|
13
|
+
import { suppressLegendChannels } from "../../../materialization/legendSuppression.js";
|
|
11
14
|
|
|
12
15
|
export { removeLegendKinds } from "./lifecycle.js";
|
|
13
16
|
|
|
14
|
-
const OPTIONS = Object.freeze(["target", "channels"]);
|
|
17
|
+
const OPTIONS = Object.freeze(["target", "channels", "suppress"]);
|
|
15
18
|
|
|
16
19
|
function legendKindChannels(kind, config) {
|
|
17
20
|
if (["series", "color", "stroke"].includes(kind)) return config.channels;
|
|
18
21
|
return {
|
|
19
|
-
colorSize:
|
|
22
|
+
colorSize: colorSizeChannels(config),
|
|
20
23
|
size: ["size"],
|
|
21
24
|
gradient: ["color"],
|
|
22
25
|
interval: ["color"],
|
|
@@ -40,7 +43,7 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
40
43
|
if (overlap.length === 0) continue;
|
|
41
44
|
kinds.push(kind);
|
|
42
45
|
for (const channel of overlap) matched.add(channel);
|
|
43
|
-
if (overlap.length !== owned.length && !["size-shape","color-size"].includes(config.combine)) {
|
|
46
|
+
if (overlap.length !== owned.length && !["size-shape","color-size","color-size-shape"].includes(config.combine)) {
|
|
44
47
|
partial = { kind, config, channels: owned.filter(channel => !requested.has(channel)) };
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -64,7 +67,7 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
64
67
|
export function removeOwnedColorLegends(program, target) {
|
|
65
68
|
const kinds = Object.entries(program.guideConfigs.legend ?? {})
|
|
66
69
|
.filter(([kind, config]) => config?.target === target && (
|
|
67
|
-
config.
|
|
70
|
+
legendBlockChannels(kind,config).includes("color")
|
|
68
71
|
)).map(([kind]) => kind);
|
|
69
72
|
return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
|
|
70
73
|
}
|
|
@@ -72,6 +75,9 @@ export function removeOwnedColorLegends(program, target) {
|
|
|
72
75
|
export const removeLegend = /* @__PURE__ */ closedAction(
|
|
73
76
|
{ op: "removeLegend", description: "Remove selected legend content or every block owned by one mark.", scope: "any" }, OPTIONS,
|
|
74
77
|
function (args = {}) {
|
|
78
|
+
if (args.suppress !== undefined && typeof args.suppress !== "boolean") {
|
|
79
|
+
throw new TypeError("removeLegend suppress must be a boolean.");
|
|
80
|
+
}
|
|
75
81
|
if (this.compositionSpec !== undefined) return editFacetParentLegend(this, "removeLegend", args);
|
|
76
82
|
const target = resolveLegendTarget(this, args.target, "removeLegend");
|
|
77
83
|
const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
@@ -84,6 +90,14 @@ export const removeLegend = /* @__PURE__ */ closedAction(
|
|
|
84
90
|
if (revision !== undefined) {
|
|
85
91
|
next = createCategoricalLegendFromConfig(next, revision.config, revision.order);
|
|
86
92
|
}
|
|
93
|
+
if (args.suppress === true) {
|
|
94
|
+
const retained = new Set(Object.entries(next.guideConfigs.legend ?? {})
|
|
95
|
+
.filter(([, config]) => config.target === target)
|
|
96
|
+
.flatMap(([kind, config]) => legendBlockChannels(kind, config)));
|
|
97
|
+
const removed = kinds.flatMap(kind => legendBlockChannels(kind, this.guideConfigs.legend[kind]))
|
|
98
|
+
.filter(channel => !retained.has(channel));
|
|
99
|
+
next = suppressLegendChannels(next, target, removed);
|
|
100
|
+
}
|
|
87
101
|
if (!Object.values(next.guideConfigs.legend ?? {}).some(config => config.target === target)) {
|
|
88
102
|
next = next._withoutMaterializationConfig(["guides", "legendGroups", target]);
|
|
89
103
|
}
|
|
@@ -138,7 +138,7 @@ export function resolveSizeLegendLayout(program, config) {
|
|
|
138
138
|
"quantitative",
|
|
139
139
|
config.labels.format
|
|
140
140
|
);
|
|
141
|
-
const mappedText = !discrete &&
|
|
141
|
+
const mappedText = !discrete && effective.labelMap !== undefined
|
|
142
142
|
? itemText.map((label, index) => resolveDisplayLabel(values[index], effective.labelMap, () => label))
|
|
143
143
|
: itemText;
|
|
144
144
|
const text = hiddenCount > 0 ? [...mappedText, `…${hiddenCount} entries`] : mappedText;
|
|
@@ -94,11 +94,13 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
94
94
|
} catch (error) {
|
|
95
95
|
throw new Error(`${channel} legend transition has incompatible layout or style: ${error.message}`);
|
|
96
96
|
}
|
|
97
|
-
return { args, channel, titleVisible: stored.titleVisible !== false, inferredOffset: stored.inferredOffset, blockOverride };
|
|
97
|
+
return { args, channel, visibility: stored.visibility, titleVisible: stored.titleVisible !== false, inferredOffset: stored.inferredOffset, blockOverride };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export function planColorLegendTransitions(program, scale, nextType) {
|
|
100
|
+
export function planColorLegendTransitions(program, scale, nextType, { channel: requestedChannel, target } = {}) {
|
|
101
101
|
return ["color", "stroke"].flatMap(channel => {
|
|
102
|
+
if (requestedChannel !== undefined && channel !== requestedChannel) return [];
|
|
103
|
+
if (target !== undefined && program.guideConfigs.legend?.[family(channel, scale.type)]?.target !== target) return [];
|
|
102
104
|
const plan = planChannelLegendTransition(program, scale, nextType, channel);
|
|
103
105
|
return plan === undefined ? [] : [plan];
|
|
104
106
|
});
|
|
@@ -114,12 +116,19 @@ export function applyColorLegendTransition(program, plan) {
|
|
|
114
116
|
if (!plan.titleVisible) {
|
|
115
117
|
next = next.editLegend({ target: plan.args.target, title: false });
|
|
116
118
|
}
|
|
119
|
+
if (plan.visibility !== undefined) {
|
|
120
|
+
const { all, labels, titleStyle } = plan.visibility;
|
|
121
|
+
next = next.editLegend({ target: plan.args.target, channel: plan.channel,
|
|
122
|
+
...(all === undefined ? {} : { visible: all }),
|
|
123
|
+
...(labels === undefined ? {} : { labels: { visible: labels } }),
|
|
124
|
+
...(titleStyle === undefined ? {} : { titleStyle: { visible: titleStyle } }) });
|
|
125
|
+
}
|
|
117
126
|
if (plan.blockOverride === undefined) return next;
|
|
118
127
|
const descriptor = resolveLegendBlock(next, {
|
|
119
128
|
target: plan.args.target,
|
|
120
129
|
channel: plan.channel
|
|
121
130
|
}, `${plan.channel} legend transition`);
|
|
122
|
-
validateLegendBlockOverride(descriptor, plan.blockOverride);
|
|
131
|
+
validateLegendBlockOverride(descriptor, plan.blockOverride, next.resolvedScales[descriptor.scaleIds[0]]?.type);
|
|
123
132
|
next = next._withLegendConfig(descriptor.kind, {
|
|
124
133
|
...descriptor.config,
|
|
125
134
|
blockOverrides: { [descriptor.key]: plan.blockOverride }
|
|
@@ -174,7 +183,7 @@ export function planLegendBlockTransitions(program, target, plans, position, spa
|
|
|
174
183
|
const descriptor = nextDescriptors[index];
|
|
175
184
|
const incoming = oldDescriptors.filter(old => intersects(old, descriptor));
|
|
176
185
|
let override;
|
|
177
|
-
if (["size-shape","color-size"].includes(descriptor.config.combine) && incoming.length > 1) {
|
|
186
|
+
if (["size-shape","color-size","color-size-shape"].includes(descriptor.config.combine) && incoming.length > 1) {
|
|
178
187
|
const positions = incoming.map(old=>old.config.blockOverrides?.[old.key]?.position);
|
|
179
188
|
if (position === undefined && !positions.every(value=>value===positions[0])) transitionError(target, incoming, descriptor, "position");
|
|
180
189
|
override = position === undefined && positions[0] !== undefined ? {position: positions[0]} : undefined;
|
|
@@ -186,7 +195,7 @@ export function planLegendBlockTransitions(program, target, plans, position, spa
|
|
|
186
195
|
} else if (incoming.length === 1) {
|
|
187
196
|
const old = incoming[0];
|
|
188
197
|
override = old.config.blockOverrides?.[old.key];
|
|
189
|
-
if (["size-shape","color-size"].includes(incoming[0].config.combine) && override !== undefined) {
|
|
198
|
+
if (["size-shape","color-size","color-size-shape"].includes(incoming[0].config.combine) && override !== undefined) {
|
|
190
199
|
const {title: _title, ...rest} = override; override = Object.keys(rest).length ? rest : undefined;
|
|
191
200
|
}
|
|
192
201
|
if(old.config.combine==='color-size'&&descriptor.kind==='gradient'&&override!==undefined){
|
|
@@ -218,7 +227,7 @@ export function planLegendBlockTransitions(program, target, plans, position, spa
|
|
|
218
227
|
}
|
|
219
228
|
override = overrides[0];
|
|
220
229
|
}
|
|
221
|
-
if (override !== undefined) validateLegendBlockOverride(descriptor, override);
|
|
230
|
+
if (override !== undefined) validateLegendBlockOverride(descriptor, override, program.resolvedScales[descriptor.scaleIds[0]]?.type);
|
|
222
231
|
const config = withoutBlockOverrides(plan.config);
|
|
223
232
|
for (const key of LEGEND_SPACING_KEYS) {
|
|
224
233
|
let value=config[key];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { splitGuideVisibility, patchGuideVisibility } from "../../../../materialization/guides/visibility.js";
|
|
1
2
|
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
2
3
|
import { action } from "../../../../core/action.js";
|
|
3
4
|
import { resolvePolarGuideResources } from "../resolve.js";
|
|
@@ -213,7 +214,8 @@ function makeEditAxis(kind) {
|
|
|
213
214
|
op: operation,
|
|
214
215
|
description: `Edit selected Polar ${kind}-axis components.`
|
|
215
216
|
}, withGuideLayoutValidation(function (args = {}) {
|
|
216
|
-
|
|
217
|
+
const split = splitGuideVisibility(args); args = split.args;
|
|
218
|
+
if (Object.keys(args).length || !Object.keys(split.visibility).length) validateAxisEditArgs(kind, args, operation);
|
|
217
219
|
if (!AXIS_COMPONENTS.some(component => hasAxisComponent(this, kind, component))) {
|
|
218
220
|
throw new Error(`${operation} requires an existing ${kind}-axis component.`);
|
|
219
221
|
}
|
|
@@ -225,7 +227,7 @@ function makeEditAxis(kind) {
|
|
|
225
227
|
}
|
|
226
228
|
// Validate the whole proposal on an immutable branch before returning changes.
|
|
227
229
|
applyAxisEdit(this, kind, args);
|
|
228
|
-
return applyAxisEdit(this, kind, args);
|
|
230
|
+
return patchGuideVisibility(applyAxisEdit(this, kind, args), "axis", kind, split.visibility);
|
|
229
231
|
}));
|
|
230
232
|
}
|
|
231
233
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { materializeHardTextLines } from "../../../../materialization/textLines.js";
|
|
2
|
+
import { resolveTextLines } from "../../../../core/textMetrics.js";
|
|
1
3
|
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
2
4
|
import { axisThemeTypography } from "../../../theme/state.js";
|
|
3
5
|
import { action } from "../../../../core/action.js";
|
|
@@ -55,6 +57,7 @@ function labelGeometry(program, kind, config, angle = resolveAngle(program, kind
|
|
|
55
57
|
offset: config.offset
|
|
56
58
|
}))
|
|
57
59
|
};
|
|
60
|
+
if (config.align !== undefined) geometry.textAlign = geometry.textAlign.map(() => config.align);
|
|
58
61
|
assertPolarTextLayout({
|
|
59
62
|
canvas: program.graphicSpec.objects.canvas,
|
|
60
63
|
label: `${prefix(kind)} axis labels`,
|
|
@@ -62,6 +65,7 @@ function labelGeometry(program, kind, config, angle = resolveAngle(program, kind
|
|
|
62
65
|
x: geometry.x[index],
|
|
63
66
|
y: geometry.y[index],
|
|
64
67
|
text: value,
|
|
68
|
+
lines: resolveTextLines(value, {fontSize: config.fontSize, blockAlign: "middle", baseline: geometry.textBaseline[index]}),
|
|
65
69
|
fontSize: config.fontSize,
|
|
66
70
|
fontFamily: config.fontFamily,
|
|
67
71
|
fontWeight: config.fontWeight,
|
|
@@ -104,6 +108,7 @@ function resolveLabelConfig(program, kind, args, resources, previous) {
|
|
|
104
108
|
const typography = axisThemeTypography(program.materializationConfigs.theme, "labels");
|
|
105
109
|
const config = {
|
|
106
110
|
...(previous ?? {}),
|
|
111
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
107
112
|
scale: resources.scale,
|
|
108
113
|
coordinate: resources.coordinate,
|
|
109
114
|
...mode,
|
|
@@ -184,7 +189,7 @@ function makeEditLabels(kind) {
|
|
|
184
189
|
for (const [property, value] of Object.entries(properties)) {
|
|
185
190
|
next = next.editGraphics({ target: names.labels, property, value });
|
|
186
191
|
}
|
|
187
|
-
return next;
|
|
192
|
+
return materializeHardTextLines(next, names.labels);
|
|
188
193
|
}));
|
|
189
194
|
}
|
|
190
195
|
|