ggaction 0.0.21 → 0.0.22
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 +73 -0
- package/README.md +3 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +6 -2
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +1 -1
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +21 -35
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +21 -4
- package/src/core/datasetReferences.js +17 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +31 -1
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +23 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +5 -0
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +108 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +270 -60
- package/types/scene.d.ts +35 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveColorSizeEdit as R,createColorSizeLegendFromConfig as me}from"./colorSize.js";import{patchLegendClipHeight as Y,validateLegendClip as be}from"../../../grammar/legendClip.js";import{validateLegendCombine as ye,validateMappedSizeShapeSymbol as pe}from"../../../grammar/legendCombinations.js";import{legendBlockDescriptors as j}from"./target.js";import{editFacetParentLegend as he}from"../../../materialization/facetGuides/edit.js";import{localLegendPosition as Z,localLegendPlacement as Le}from"../../../materialization/legendGroups.js";import{mergeLegendTitleStyle as $}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as J}from"./group.js";import{prepareLegendGroup as Se}from"./group.js";import{PLOT_LEGEND_POSITIONS as Q}from"../../../layout/legend.js";import{withGuideLayoutValidation as we}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as ke}from"./creation.js";import{normalizeLegendOrder as U}from"../../../grammar/categoryOrder.js";import{resolveDefinition as ze}from"./categorical/resolve.js";import{action as Ce}from"../../../core/action.js";import{validateOptionObject as B}from"../../../core/validation.js";import{normalizeLegendOverflow as X,normalizeOptions as Oe}from"./categorical/options.js";import{resolveLegendSymbol as Ee}from"./categorical/recipes.js";import{resolveLayout as Ge}from"./categorical/layout.js";import{resolveLegendCreationPlan as ee}from"./categorical/actions.js";import{categoricalSymbolIds as _e,reconcileCategoricalSymbols as Pe,createCategoricalLegendFromConfig as je,resolveCategoricalLegendRevision as Te,removeLegendKinds as te}from"./lifecycle.js";import{normalizeColorSymbols as Ie}from"./continuous/colorSymbols.js";import{readLegendSampling as $e}from"./sampling.js";import{createGradientLegendFromConfig as ne}from"./continuous/gradient.js";import{normalizeLegendGradient as ie}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as xe,normalizeLegendBorder as Ve,normalizeLegendTextOptions as oe,normalizeLegendTitleOptions as le,normalizeContinuousLegend as re}from"./continuous/common.js";import{normalizeOpacitySymbol as De,createOpacityLegendFromConfig as Me}from"./continuous/opacity.js";import{normalizeIntervalLegend as qe,resolveIntervalConfig as Be,resolveIntervalLayout as Ae,createIntervalLegendFromConfig as We}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as Fe,createStrokeIntervalLegendFromConfig as Ke,resolveStrokeIntervalConfig as He,resolveStrokeIntervalLayout as Ne}from"./continuous/stroke.js";import{findLayer as P}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as ve}from"../../../materialization/graphicHierarchy.js";import{resolveLegendBlock as A,resolveLegendTarget as W,validateLegendChannels as Re}from"./target.js";import{isEnumeratedSizeScaleType as de}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as ce,SIZE_LEGEND_TITLE_STYLE as se,resolveSizeLegendLayout as Ye,createSizeLegendFromConfig as Ze}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as Je,STROKE_WIDTH_LEGEND_TITLE_STYLE as Qe,resolveStrokeWidthLegendLayout as Ue,createStrokeWidthLegendFromConfig as Xe}from"./strokeWidth.js";import{normalizeLegendSampling as F}from"./sampling.js";import{patchLegendBlockOverride as et}from"./blocks.js";import{LEGEND_POSITIONS as tt}from"../../../core/vocabulary.js";import{planLegendBlockTransitions as nt}from"./transition.js";const fe=Object.freeze(["target","channel","channels","position","layout","align","direction","columns","offset","titlePosition","title","symbol","labels","titleStyle","itemGap","border","count","values","gradient","representation","order","overflow","blockLayout","at","combine","clipHeight"]);function T(n,t){return t===void 0?n:{...n,...t}}function I(n,t){return t===void 0?n:t===!1||t===!0||n===!1?t:{...n,...t}}function E(n,t,e,o){const i=n.graphicSpec.objects[t]!==void 0;return i&&!e?n.editGraphics({target:t,remove:!0}):!i&&e?n.createGraphics({id:t,...o,...ve(n,{...o.before===void 0?{}:{before:o.before},...o.after===void 0?{}:{after:o.after}})}):n}function it(n,t,e){const o=e.representation??t.representation??"gradient";if(!["gradient","symbols"].includes(o))throw new Error("Legend representation must be gradient or symbols.");const i=["target","representation","position","align","offset","title","labels","titleStyle","titlePosition","border","count",...o==="symbols"?["symbol","values","itemGap","layout","direction","columns"]:["gradient","direction"]];for(const a of Object.keys(e))if(!i.includes(a))throw new Error(`Color ${o} legend does not accept ${a}.`);const s=(t.representation??"gradient")!==o,u=e.title,r=u==="auto"?P(n,t.target)?.encoding?.color?.field:typeof u=="string"?u:t.title,f={target:t.target,position:e.position??t.position,align:e.align??t.align,offset:e.offset??t.offset,title:r,labels:T(t.labels,e.labels),titleStyle:$(t.titleStyle,e.titleStyle,{edit:!0}),border:I(t.border,e.border),titlePosition:e.titlePosition??(s&&(o==="gradient"||t.titlePosition==="bottom")?"top":t.titlePosition)};let m;if(o==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&B(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const a=s?{count:t.count}:{};for(const h of["values","count"])Object.hasOwn(e,h)&&(a[h]=e[h]);Object.hasOwn(e,"values")&&delete a.count,Object.hasOwn(e,"count")&&(a.count=e.count),m=Ie({...f,...a,layout:e.layout??(s?void 0:t.layout),direction:e.direction??(s||e.position!==void 0&&e.position!==t.position?void 0:t.direction),columns:e.columns??(s?void 0:t.columns),itemGap:e.itemGap??(s?void 0:t.itemGap),symbol:e.symbol==="auto"?"auto":e.symbol===void 0?s?void 0:t.symbol:{...s?{}:t.symbol,...e.symbol}},s?{count:t.count}:t,"edit")}else{m=re({...f,...e.direction==="auto"?{}:e.direction!==void 0?{direction:e.direction}:t.gradientDirection===void 0?{}:{direction:t.gradientDirection},count:e.count??(s?$e(t).count:t.count)},"gradient");const a=ie(e.gradient,s?void 0:t.gradient);m.gradient=a}const{representation:c,sampling:S,symbol:l,layout:L,direction:C,columns:z,gradient:k,count:w,...g}=t,p=s&&o==="gradient"&&t.blockOverrides!==void 0?Object.fromEntries(Object.entries(t.blockOverrides).map(([a,h])=>{const{symbol:d,labelMap:b,...y}=h;return[a,y]})):t.blockOverrides;return o==="gradient"&&e.direction==="auto"&&delete g.gradientDirection,{config:{...g,...m,...p===void 0?{}:{blockOverrides:p},inferredTitle:u==="auto"?!0:typeof u=="string"?!1:t.inferredTitle,titleVisible:u===!1?!1:u===void 0?t.titleVisible!==!1:!0},titleMode:u,title:r,titleVisible:u===!1?!1:u===void 0?t.titleVisible!==!1:!0}}function x(n,t,e,o){if(t==="gradient"&&(e.representation==="symbols"||o.representation!==void 0))return it(n,e,o);const i=["gradient","strokeGradient"].includes(t),s=i?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","gradient","representation","direction"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const h of Object.keys(o))if(!s.includes(h))throw new Error(`${t} legend does not accept ${h}.`);const u=o.title,r=u==="auto"?!0:typeof u=="string"?!1:e.inferredTitle,f=u===!1?!1:u===void 0?e.titleVisible!==!1:!0,m=P(n,e.target),c=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity",S=m?.encoding?.[c]?.field,l=u==="auto"?S:typeof u=="string"?u:e.title,L=o.titlePosition??e.titlePosition,C=t==="opacity"&&L==="left"&&e.titlePosition!=="left",z=T(e.labels,o.labels),k=i?void 0:F(o,{previous:e,operation:"edit",label:"Opacity legend"}),w=re({target:e.target,position:o.position??e.position,align:o.align??e.align,offset:o.offset??e.offset,...i?{count:o.count??e.count,...o.direction==="auto"?{}:o.direction!==void 0?{direction:o.direction}:e.gradientDirection===void 0?{}:{direction:e.gradientDirection}}:{},titlePosition:L,title:l,labels:C&&o.labels?.offset===void 0?{...z,offset:8}:z,titleStyle:$(e.titleStyle,o.titleStyle,{edit:!0}),border:I(e.border,o.border),...t==="opacity"?{itemGap:o.itemGap??(C?20:e.itemGap),symbol:o.symbol??e.symbol}:{}},i?"gradient":t),{count:g,...p}=e,a={...i?e:p,...w,...i?{}:{sampling:k},inferredTitle:r,titleVisible:f};if(i){o.direction==="auto"&&delete a.gradientDirection;const h=ie(o.gradient,e.gradient);a.gradient=h}else a.symbol=De(o.symbol??e.symbol);return{config:a,titleMode:u,title:l,titleVisible:f}}function V(n,t,e,o){const{config:i,titleMode:s,title:u,titleVisible:r}=x(n,t,e,o);if(t==="gradient"&&(e.representation??"gradient")!==(i.representation??"gradient"))return ne(te(n,[t]),i).rematerializeLegend();const f=t==="gradient"?"colorGradient":t==="strokeGradient"?"strokeGradient":"opacityLegend",m=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity";let c=n;return(s==="auto"||typeof s=="string")&&(c=c.editSemantic({property:`guide.legend.${m}.title`,value:u})),c=c._withLegendConfig(t,i),c=E(c,`${f}Background`,i.border!==!1,{type:"rect",before:["gradient","strokeGradient"].includes(t)?`${f}${i.representation==="symbols"?"Symbols":"Strips"}`:"opacityLegendSymbols"}),c=E(c,`${f}Title`,r,{type:"text"}),c.rematerializeLegend()}function ae(n,t,e,o="color"){for(const l of Object.keys(e))if(!["target","position","align","direction","offset","title","symbol","labels","titleStyle","itemGap","border","layout","columns","titlePosition"].includes(l))throw new Error(`interval legend does not accept ${l}.`);const i=e.title,s=P(n,t.target),u=i==="auto"?!0:typeof i=="string"?!1:t.inferredTitle,r=i===!1?!1:i===void 0?t.titleVisible!==!1:!0,f=i==="auto"?s?.encoding?.[o]?.field:typeof i=="string"?i:t.title,m=qe({target:t.target,position:e.position??t.position,align:e.align??t.align,direction:e.direction??(e.position!==void 0&&e.position!==t.position?void 0:t.direction),layout:e.layout===void 0?t.layout:e.layout,columns:e.columns??t.columns,titlePosition:e.titlePosition??t.titlePosition,offset:e.offset??t.offset,title:f,symbol:T(t.symbol,e.symbol),labels:T(t.labels,e.labels),titleStyle:$(t.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??t.itemGap,border:I(t.border,e.border)}),c={...t,...m,inferredTitle:u,titleVisible:r,...o==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},S=o==="stroke"?He(n,c):Be(n,c);return(o==="stroke"?Ne:Ae)(n,S.config,S.scale),{normalized:m,config:c,titleMode:i,title:f,titleVisible:r}}function D(n,t,e,o="color"){const i=o==="stroke"?"strokeInterval":"interval",s=o==="stroke"?"strokeInterval":"colorLegend",{normalized:u,config:r,titleMode:f,title:m,titleVisible:c}=ae(n,t,e,o);let S=n._withLegendConfig(i,r);return(f==="auto"||typeof f=="string")&&(S=S.editSemantic({property:`guide.legend.${o}.title`,value:m})),S=E(S,`${s}Title`,c,{type:"text"}),S=E(S,`${s}Background`,u.border!==!1,{type:"rect",before:`${s}Symbols`}),S.rematerializeLegend()}function K(n,t,e,o,i=!1){const s=t==="size",u=s?"size":"stroke-width",r=["target","title","count","values","labels","titleStyle","position","layout","align","direction","columns","titlePosition","offset","itemGap","border"];s&&r.push("overflow");for(const a of Object.keys(o))if(!r.includes(a))throw new Error(`${u} legend does not accept ${a}.`);const f=s&&de(n.resolvedScales[e.scale]?.type),m=s&&n.resolvedScales[e.scale]?.type==="ordinal";if(o.overflow!==void 0&&!m)throw new Error("Size legend overflow requires an ordinal scale.");if(f&&(o.count!==void 0||o.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const c=f?void 0:F(o,{previous:e,operation:"edit",label:`${s?"Size":"Stroke-width"} legend`}),S=P(n,e.target),l=o.title,L=l==="auto"?!0:typeof l=="string"?!1:e.inferredTitle,C=l===!1?!1:l===void 0?e.titleVisible!==!1:!0,z=l==="auto"?S?.encoding?.[t]?.field:typeof l=="string"?l:e.title,{count:k,...w}=e,p={...f?e:w,title:z,inferredTitle:L,titleVisible:C,...f?{}:{sampling:c},...m&&(o.overflow!==void 0||e.overflow!==void 0)?{overflow:X(o.overflow===void 0?e.overflow:o.overflow)}:{},...s?{inheritAppearance:i&&e.inheritAppearance===!0,...o.position===void 0?{}:{inheritPosition:!1}}:{},labels:oe(o.labels,"editLegend.labels",e.labels??(s?ce:Je)),titleStyle:le(o.titleStyle,"editLegend.titleStyle",e.titleStyle??(s?se:Qe),!0)};return Object.assign(p,xe({...e,...o,titleStyle:p.titleStyle,direction:o.direction??(o.position!==void 0&&o.position!==e.position?void 0:e.direction)})),p.border=Ve(I(e.border,o.border)),(s?Ye:Ue)(n,p),{config:p,titleMode:l,title:z,titleVisible:C}}function M(n,t,e,o,i=!1){const{config:s,titleMode:u,title:r,titleVisible:f}=K(n,t,e,o,i),m=t==="size"?"sizeLegend":"strokeWidthLegend";let c=n;return(u==="auto"||typeof u=="string")&&(c=c.editSemantic({property:`guide.legend.${t}.title`,value:r})),c=c._withLegendConfig(t,s),c=E(c,`${m}Background`,s.border!==!1,{type:"rect",before:`${m}Symbols`}),c=E(c,`${m}Title`,f,{type:"text",after:`${m}Labels`}),c.rematerializeLegend()}function ot(n,t,e,o){if(e===void 0)return;const i=de(n.resolvedScales[e.scale]?.type);if(i&&(o.count!==void 0||o.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const s=i?void 0:F(o,{previous:e,operation:"edit",label:"Size legend"}),{count:u,...r}=e,m={...i?e:r,...i?{}:{sampling:s},...o.overflow!==void 0&&n.resolvedScales[e.scale]?.type==="ordinal"?{overflow:X(o.overflow)}:{}};if(o.labels===void 0&&o.titleStyle===void 0)return m;const c=e.inheritAppearance?{...t.labels,offset:e.labels.offset}:e.labels??ce,S=e.inheritAppearance?t.titleStyle:e.titleStyle??se;return{...m,inheritAppearance:!1,labels:oe(o.labels,"editLegend.labels",c),titleStyle:le(o.titleStyle,"editLegend.titleStyle",S,!0)}}function ue(n,t,e,o,i,s=n.semanticSpec.guides.legend?.[t]?.order){if(i.values!==void 0)throw new Error("Combined and categorical legends require a channel block selector for exact values.");if(i.gradient!==void 0)throw new Error("Categorical legends do not accept gradient.");if(i.count!==void 0&&o===void 0)throw new Error("Legend count requires an existing size legend.");const u=i.title,r=u==="auto"?!0:typeof u=="string"?!1:e.inferredTitle,f=u===!1?!1:u===void 0?e.titleVisible!==!1:!0,m=u==="auto"?e.field:typeof u=="string"?u:e.title,c=T(e.labels,i.labels);if(c.format!==void 0&&c.format!=="auto"&&o===void 0)throw new Error("Categorical legend labels do not accept format.");const{format:S,...l}=c,L=i.symbol===void 0?e.inferredSymbol:i.symbol==="auto",C=Oe({target:e.target,channels:e.channels,...i.overflow===void 0&&e.overflow===void 0?{}:{overflow:i.overflow===void 0?e.overflow:i.overflow},layout:i.layout===void 0?e.layout:i.layout,position:i.position??e.position,align:i.align??e.align,direction:i.direction??(i.position!==void 0&&i.position!==e.position?void 0:e.direction),...i.columns===void 0&&e.columns===void 0?{}:{columns:i.columns??e.columns},offset:i.offset??e.offset,titlePosition:i.titlePosition??e.titlePosition,title:m,symbol:i.symbol===void 0?e.symbol:Ee(n,P(n,e.target),e.channels,i.symbol,t),labels:l,titleStyle:$(e.titleStyle,i.titleStyle,{edit:!0}),itemGap:i.itemGap??e.itemGap,border:I(e.border,i.border)},t,{internalSymbol:L}),z={...e,...C,inferredTitle:r,inferredSymbol:L,titleVisible:f},k=i.order===void 0?s:U(i.order);return ze(n,P(n,e.target),e.channels,m,k),{config:z,order:k,titleMode:u,title:m,titleVisible:f,sizeConfig:ot(n,e,o,i)}}function H(n,t,e,o,i){e.combine==="size-shape"&&pe(i.symbol);const{config:s,order:u,titleMode:r,title:f,titleVisible:m,sizeConfig:c}=ue(n,t,e,o,i),S=_e(s);let l=n;(r==="auto"||typeof r=="string")&&(l=l.editSemantic({property:`guide.legend.${t}.title`,value:f})),i.order!==void 0&&(u==="scale"?n.semanticSpec.guides.legend?.[t]?.order!==void 0&&(l=l.editSemantic({property:`guide.legend.${t}.order`,remove:!0})):l=l.editSemantic({property:`guide.legend.${t}.order`,value:u})),l=l._withLegendConfig(t,s),l=Pe(l,e,s),l=E(l,`${t}LegendBackground`,s.border!==!1,{type:"rect",before:[...S][0]});const L=`${t}LegendTitle`;return l=E(l,L,m,{type:"text",...l.graphicSpec.objects.sizeLegendSymbols===void 0?{}:{before:l.graphicSpec.objects.sizeLegendBackground===void 0?"sizeLegendSymbols":"sizeLegendBackground"}}),o!==void 0&&(l=l._withLegendConfig("size",c)),l.rematerializeLegend()}function N(n,t,e){const o=R(n,t,e);let i=n._withLegendConfig("colorSize",o);return i=E(i,"colorSizeLegendBackground",o.border!==!1,{type:"rect",before:"colorSizeLegendSymbols"}),i=E(i,"colorSizeLegendTitle",o.titleVisible!==!1,{type:"text",after:"colorSizeLegendLabels"}),i.rematerializeLegend()}function v(n,t,e,o={position:e.position}){Re(e.channels,"editLegend");const{channels:i,target:s,combine:u,...r}=e,f=n.guideConfigs.legend??{},m=Object.keys(f).filter(d=>f[d].target===t);let c=m.reduce((d,b)=>d._withoutMaterializationConfig(["guides","legend",b]),n);const S=u==="color-size"&&r.representation===void 0?void 0:r.representation??(i.includes("color")&&f.gradient?.target===t?f.gradient.representation:void 0);if(r.representation!==void 0&&!i.includes("color"))throw new Error("Legend representation requires selected continuous color.");const l=Object.values(f).find(d=>d.target===t&&["size-shape","color-size"].includes(d.combine)),L=l?.combine==="color-size"?["color","size"]:["shape","size"],C=L.every(d=>i.includes(d));if(l&&u===void 0&&!C&&i.some(d=>L.includes(d)))throw new Error("An edited combined legend keeps its combination; explicitly choose separate to split it.");const z=u??(l&&C?l.combine:void 0);let k;if(u==="separate"&&l){const d=i.filter(y=>L.includes(y));k={steps:[...l.combine==="color-size"?d.map(y=>[y]):[d],...i.filter(y=>!L.includes(y)).map(y=>[y])].filter(y=>y.length).map(y=>ee(c,{target:t,channels:y})).flatMap(y=>y.steps),...n.guideConfigs.legendGroups?.[t]===void 0?{}:{legendGroup:{target:t,config:n.guideConfigs.legendGroups[t]}}}}else k=ee(c,{target:t,channels:i,...z===void 0?{}:{combine:z},...S===void 0?{}:{representation:S}},n.semanticSpec.layers,i);k.legendGroup!==void 0&&(c=c._withMaterializationConfig(["guides","legendGroups",t],k.legendGroup.config));const w=m.find(d=>["series","color","stroke"].includes(d));let g=k.steps.map(d=>{const b=ke(c,d),{kind:y}=b;if(f[y]!==void 0&&f[y].target!==t)throw new Error(`Legend ${y} content already belongs to another target.`);if(b.config.combine==="size-shape"&&f.series?.combine!=="size-shape"||b.kind==="colorSize"&&f.colorSize?.target!==t)return b;if(l?.combine==="color-size"&&["gradient","size"].includes(y)){const G=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","offset","border"].map(_=>[_,l[_]]));return y==="size"&&Object.assign(G,Object.fromEntries(["sampling","direction","columns","titlePosition","itemGap","layout","clipHeight"].map(_=>[_,l[_]])),{inheritAppearance:!1}),{...b,config:{...b.config,...G}}}if(y==="size"&&l){const O=Object.fromEntries(["title","inferredTitle","titleVisible","labels","titleStyle","position","align","direction","columns","offset","titlePosition","itemGap","border","layout","clipHeight"].map(G=>[G,l[G]]));return{...b,config:{...b.config,...O,inheritAppearance:!1}}}if(["series","color","stroke"].includes(y)&&w!==void 0){const O=f[y]?.target===t?y:w,G=r.order===void 0?n.semanticSpec.guides.legend?.[O]?.order:U(r.order);return{...Te(c,O,f[O],b.config.channels,{order:G,validateLayout:!1}),kind:y}}return{...b,config:f[y]?.target===t?f[y]:b.config}});const p=g.find(d=>["series","color","stroke"].includes(d.kind)),a=g.find(d=>d.kind==="size");if(r.representation!==void 0||g.some(d=>d.kind==="gradient"&&d.config.representation==="symbols")){const d=g.find(b=>b.kind==="gradient");if(d===void 0)throw new Error("Legend representation requires continuous color.");d.config=x(c,"gradient",d.config,r).config}else if(p!==void 0){a!==void 0&&f.size?.target!==t&&(a.config={...a.config,inheritAppearance:p.config.position==="left"||r.position==="left"||r.labels!==void 0||r.titleStyle!==void 0});const d=ue(c,p.kind,p.config,a?.config,r,p.order);if(p.config=d.config,p.order=d.order,a!==void 0&&(a.config=d.sizeConfig,p.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const d=g[0];if(d.kind==="colorSize")d.config=R(c,d.config,r);else if(l?.combine==="color-size"&&u==="separate")for(const b of g)b.config=b.kind==="size"?K(c,"size",b.config,r).config:x(c,b.kind,b.config,Object.fromEntries(Object.entries(r).filter(([y])=>!["values","itemGap","layout","columns"].includes(y)))).config;else d.kind==="size"||d.kind==="strokeWidth"?d.config=K(c,d.kind,d.config,r).config:["interval","strokeInterval"].includes(d.kind)?d.config=ae(c,d.config,r,d.kind==="strokeInterval"?"stroke":"color").config:d.config=x(c,d.kind,d.config,r).config}g=nt(n,t,g,o.position);for(const{kind:d,config:b}of g)c=c._withLegendConfig(d,b);p!==void 0&&Ge(c,p.config);let h=te(n,m);k.legendGroup!==void 0&&(h=h._withMaterializationConfig(["guides","legendGroups",t],k.legendGroup.config));for(const{kind:d,config:b,order:y}of g)["series","color","stroke"].includes(d)?h=je(h,b,y):h={colorSize:me,size:Ze,strokeWidth:Xe,gradient:ne,opacity:Me,interval:We,strokeGradient:Fe,strokeInterval:Ke}[d](h,b);return h.rematerializeLegend()}function q(n={},t=!1,e={position:n.position}){if(B(n,fe,"editLegend"),Object.keys(n).filter(l=>l!=="target"&&l!=="channel").length===0)throw new Error("editLegend requires at least one change.");if(n.title!==void 0&&n.title!==!1&&n.title!=="auto"&&(typeof n.title!="string"||n.title.length===0))throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');const i=W(this,n.target,"editLegend");if(Object.hasOwn(n,"channel")){if(Object.hasOwn(n,"channels"))throw new Error("editLegend cannot combine channel and channels.");if(Object.hasOwn(n,"combine"))throw new Error("Selected legend edits cannot change combine.");if(n.blockLayout!==void 0||n.at!==void 0)throw new Error("Selected legend blocks do not accept whole-group blockLayout or at.");let l=this;const L=A(l,n,"editLegend"),C=Object.hasOwn(n,"position");if(C){if(n.position!=="inherit"&&!tt.includes(n.position))throw new Error("Selected legend position requires an anchor or inherit.");const O=et(L.config,L.key,n.position==="inherit"?{removePosition:!0}:{position:n.position}),{blockOverrides:G,..._}=L.config;l=l._withLegendConfig(L.kind,O===void 0?_:{..._,blockOverrides:O});const{position:lt,...ge}=n;n=ge}const z=l.guideConfigs.legend?.size,k=["series","color","stroke"].map(O=>l.guideConfigs.legend?.[O]).find(O=>O?.target===i);z?.target===i&&z.inheritAppearance===!0&&k&&(L.kind==="size"||["series","color","stroke"].includes(L.kind))&&(l=l._withLegendConfig("size",{...z,inheritAppearance:!1,labels:{...k.labels,offset:z.labels.offset},titleStyle:{...k.titleStyle}}));const{kind:w}=L,g=l.guideConfigs.legend[w],{channel:p,...a}=n,h=Le(g),d=h===void 0?g:{...g,...h},b=C&&!["gradient","strokeGradient","opacity"].includes(w)?{...d,direction:void 0}:d,y=O=>h===void 0?O:O._withLegendConfig(w,{...O.guideConfigs.legend[w],position:g.position,...a.align===void 0?{align:g.align}:{},...a.offset===void 0?{offset:g.offset,inferredOffset:g.inferredOffset}:{}}).rematerializeLegend();return["series","color","stroke"].includes(w)?y(H(l,w,b,void 0,a)):w==="colorSize"?y(N(l,b,a)):["size","strokeWidth"].includes(w)?y(M(l,w,b,a)):["interval","strokeInterval"].includes(w)?y(D(l,b,a,w==="strokeInterval"?"stroke":"color")):y(V(l,w,b,a))}const s=this.guideConfigs.legendGroups?.[i];if(!t&&(s!==void 0||n.blockLayout!==void 0||n.at!==void 0||Q.includes(n.position)&&Object.values(this.guideConfigs.legend??{}).some(l=>l.target===i&&l.kind===void 0))){const l=Se(this,n,i,{corner:Q.includes(n.position)}),{channels:L,...C}=l.options;if(L!==void 0)return v(l.program,i,l.options,e);const z=["position","layout","align","direction","columns","offset","titlePosition","itemGap"];if(Object.keys(C).every(g=>g==="target"||z.includes(g))){let g=l.program;const p=Object.entries(g.guideConfigs.legend??{}).filter(([,a])=>a.target===i);for(const[a]of p){const h=g.guideConfigs.legend[a],d={...C};if(Z(h)!==void 0)for(const b of["position","align","offset"])delete d[b];if(["gradient","strokeGradient","opacity"].includes(a)&&h.representation!=="symbols")for(const b of a==="opacity"?["layout","columns","direction"]:["layout","columns"]){if(Object.hasOwn(n,b))throw new Error(`${a} legend does not accept ${b}.`);delete d[b]}["gradient","strokeGradient"].includes(a)&&h.representation!=="symbols"&&delete d.itemGap,["series","color","stroke"].includes(a)?g=H(g,a,h,void 0,d):a==="colorSize"?g=N(g,h,d):["size","strokeWidth"].includes(a)?g=M(g,a,h,d,!0):["interval","strokeInterval"].includes(a)?g=D(g,h,d,a==="strokeInterval"?"stroke":"color"):g=V(g,a,h,d)}return g.rematerializeLegend()}if(n.combine===void 0&&Object.values(this.guideConfigs.legend??{}).some(g=>g.target===i&&["size-shape","color-size"].includes(g.combine))){if(n.values!==void 0&&j(l.program,i).length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");let g=l.program;const p={...n};for(const a of["position","align","offset","blockLayout","at"])delete p[a];for(const a of j(g,i))g=q.call(g,{...p,target:i,channel:a.channels[0]},!0);return g.rematerializeLegend()}let w=q.call(l.program,l.options,!0,e);for(const[g,p]of Object.entries(this.guideConfigs.legend??{}))p.target!==i||Z(p)===void 0||w.guideConfigs.legend[g]===void 0||(w=w._withLegendConfig(g,{...w.guideConfigs.legend[g],position:p.position,align:p.align,offset:p.offset,inferredOffset:p.inferredOffset,...n.direction===void 0?{direction:p.direction}:{}}));return w.rematerializeLegend()}if(Object.hasOwn(n,"combine")){if(ye(n.combine),n.combine==="size-shape"&&n.channels!==void 0&&!(n.channels.length===2&&n.channels.includes("size")&&n.channels.includes("shape")))throw new Error("size-shape combine requires exactly shape and size channels.");const l=n.channels??[...new Set(j(this,i).flatMap(L=>L.channels))];return v(this,i,{...n,channels:l},e)}if(n.channels!==void 0)return v(this,i,n,e);const r=this.guideConfigs.legend??{},f=Object.keys(r).filter(l=>r[l]?.target===i);if(n.representation!==void 0){if(r.gradient?.target!==i)throw new Error("Legend representation requires continuous color.");return V(this,"gradient",r.gradient,n)}if(r.gradient?.target===i&&r.gradient.representation==="symbols"&&f.length>1)throw new Error("Multiple legend blocks require editLegendBlock with an explicit channel for appearance or samples.");if(n.values!==void 0&&f.length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");const m=["series","color","stroke"].filter(l=>r[l]?.target===i);if(m.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const c=m[0];if(c!==void 0)return H(this,c,r[c],r.size?.target===i?r.size:void 0,n);if(r.colorSize?.target===i)return N(this,r.colorSize,n);if(r.interval?.target===i)return D(this,r.interval,n);if(r.strokeInterval?.target===i)return D(this,r.strokeInterval,n,"stroke");if(r.strokeWidth?.target===i)return M(this,"strokeWidth",r.strokeWidth,n);if(r.size?.target===i)return M(this,"size",r.size,n);const S=["gradient","strokeGradient","opacity"].find(l=>r[l]?.target===i);return V(this,S,r[S],n)}const vt=Ce({op:"editLegend",description:"Edit one stable legend content, layout or appearance.",scope:"any"},we(function(n={}){if(this.compositionSpec!==void 0)return he(this,"editLegend",n);if(B(n,fe,"editLegend"),!Object.hasOwn(n,"clipHeight"))return J(this,q.call(this,n),n);const{clipHeight:t,...e}=n,o=Object.keys(e).some(f=>!["target","channel"].includes(f));let i=this;if(t==="auto"){const f=W(i,n.target,"editLegend"),m=n.channel===void 0?j(i,f):[A(i,n,"editLegend")];for(const c of m)i=i._withLegendConfig(c.kind,Y(c.config,"auto"))}let s=o?q.call(i,e):i;const u=W(s,n.target,"editLegend"),r=n.channel===void 0?j(s,u):[A(s,n,"editLegend")];if(!r.length)throw new Error("Legend clipHeight requires an existing legend block.");for(const f of r){const m=Y(s.guideConfigs.legend[f.kind],t);be(f.kind,m),s=s._withLegendConfig(f.kind,m)}return J(this,s.rematerializeLegend(),n)}));export{vt as editLegend};
|
|
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};
|
|
@@ -21,7 +21,7 @@ const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
|
21
21
|
name: "editLegendLayout",
|
|
22
22
|
options: [
|
|
23
23
|
"position", "layout", "align", "direction", "columns", "offset", "blockLayout", "at",
|
|
24
|
-
"titlePosition", "itemGap"
|
|
24
|
+
"titlePosition", "itemGap", "rowPadding", "titlePadding"
|
|
25
25
|
],
|
|
26
26
|
map: args => args
|
|
27
27
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{translateGraphic as m}from"../../../materialization/graphicTranslation.js";import{editLegendBorderBounds as v}from"../../../materialization/legendBorder.js";import{resolveLegendAxisOffset as I,effectiveLegendOffsetConfig as F}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as A}from"../../../layout/legendGroup.js";import{localLegendPosition as z,localLegendPlacement as $,legendGroup as R}from"../../../materialization/legendGroups.js";import{editLegendBackground as T,resolveLegendBackgroundFromBounds as K}from"./continuous/common.js";import{action as G}from"../../../core/action.js";import{noOptions as S}from"../../../core/validation.js";import{resolveConcreteGraphicBounds as M,unionConcreteGraphicBounds as H}from"../../../grammar/schemas/graphicBounds.js";import{finiteMidpoint as X}from"../../../grammar/numeric.js";import{resolveGraphicBounds as C}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as Y,resolveHorizontalLegendGroup as _,resolveSingleHorizontalLegendPlacement as W,isHorizontalEdgeLegend as D,resolveSideLegendLane as J,resolveSingleSideLegendPlacement as N}from"../../../layout/legendLane.js";import{findCanvasGraphic as w}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as Q}from"../../../materialization/guides/resources.js";const y=Object.freeze({colorSize:0,series:0,color:0,stroke:0,gradient:0,interval:0,strokeGradient:0,strokeInterval:0,size:1,opacity:2,strokeWidth:3});function U(r,n){return["series","color","stroke"].map(t=>r.guideConfigs.legend?.[t]).find(t=>t?.target===n)}function V(r,n,t){return n==="size"&&t.inheritPosition!==!1?U(r,t.target)?.position??t.position:t.position}function Z(r){return r.offset}function h(r,n){return Q(n).graphicIds.filter(t=>r.graphicSpec.objects[t]!==void 0)}function ee(r,n){const t=h(r,n),i=t.find(a=>a.endsWith("Title")),o=t.find(a=>a.endsWith("Labels")),e=t.find(a=>a.endsWith("Background")),s=t.filter(a=>a!==i&&a!==o&&a!==e);return{titleId:i,labelId:o,backgroundId:e,symbolIds:s}}function te(r,n){let t=M(r.graphicSpec,n,r.materializationConfigs.textMetrics);const i=r.graphicSpec.objects[n];if(!n.includes("LegendSymbol")||i?.type!=="line")return t;const o=i.items?.map(e=>e.properties)??[i.properties];for(const e of o){if(Object.hasOwn(e,"lineCap")||typeof e.stroke!="string"||!(e.strokeWidth>0))continue;const s=e.strokeWidth/2;t={left:Math.min(t.left,Math.min(e.x1,e.x2)-s),right:Math.max(t.right,Math.max(e.x1,e.x2)+s),top:Math.min(t.top,Math.min(e.y1,e.y2)-s),bottom:Math.max(t.bottom,Math.max(e.y1,e.y2)+s)}}return t}function b(r,n){return n.map(t=>te(r,t)).filter(t=>t!==void 0).reduce((t,i)=>t===void 0?i:{left:Math.min(t.left,i.left),right:Math.max(t.right,i.right),top:Math.min(t.top,i.top),bottom:Math.max(t.bottom,i.bottom)},void 0)}function ne(r,n){const t=r.graphicSpec.objects[n];return(t.items?.[0]?.properties??t.properties).x}function B(r,n,t){const i=ee(r,n);if(i.labelId===void 0||i.symbolIds.length===0)throw new Error(`Legend lane requires complete ${n} graphics.`);const o=[...i.symbolIds,i.labelId,...i.titleId===void 0?[]:[i.titleId]],e=b(r,o),s=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"?[n==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:i.symbolIds,a=b(r,s),f=b(r,i.symbolIds),u=M(r.graphicSpec,i.labelId,r.materializationConfigs.textMetrics),c=r.graphicSpec.objects[i.labelId].items?.length===0,d=i.titleId===void 0?void 0:(()=>{const g=r.graphicSpec.objects[i.titleId],p=M(r.graphicSpec,i.titleId,r.materializationConfigs.textMetrics);return{x:g.properties.x,y:g.properties.y,fontSize:g.properties.fontSize,width:p.right-p.left,bounds:p}})();if([e,a,f].includes(void 0)||u===void 0&&!c)throw new Error(`Legend lane could not measure ${n} graphics.`);const l=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"&&(t.gradientDirection==="horizontal"||t.titlePosition==="bottom");return{id:n,kind:n,preserveAlignment:l,preserveGrid:l||(["series","color","stroke"].includes(n)||t.representation==="symbols")&&(t.columns??1)>1,target:t.target,border:O(n,t),offset:Z(t),bounds:e,title:d,symbol:{centerX:X(a.left,a.right),left:f.left,right:f.right,bounds:a},labels:{x:c?f.right+t.labels.offset:ne(r,i.labelId),width:c?0:u.right-u.left},occupiedBounds:b(r,[...o,...i.backgroundId===void 0?[]:[i.backgroundId]]),...i,foregroundIds:o}}function O(r,n){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size","colorSize"].includes(r)?n.border:!1}function j(r,n){const t=[];for(let i=0;i<r.length;i+=1){const o=r[i],e=n[o.kind],s=r[i+1];if(["series","color","stroke"].includes(o.kind)&&s?.kind==="size"&&s.target===o.target){t.push({id:`${o.kind}+size`,blocks:[o,s.backgroundId===void 0?s:{...s,bounds:s.occupiedBounds,inset:s.border.padding+s.border.lineWidth/2}],border:O(o.kind,e),backgroundId:o.backgroundId}),i+=1;continue}t.push({id:o.kind,blocks:[o],border:O(o.kind,e),backgroundId:o.backgroundId})}return t}function x(r,n){const t=r.guideConfigs.legend??{},i=new Map(r.semanticSpec.layers.map((o,e)=>[o.id,e]));return Object.entries(t).filter(([o,e])=>z(e)===void 0&&R(r,e.target)===void 0&&V(r,o,e)===n).sort(([o,e],[s,a])=>i.get(e.target)-i.get(a.target)||y[o]-y[s])}function oe(r){return["right","left"].some(n=>x(r,n).length>0)}function ie(r){return["top","bottom"].some(n=>x(r,n).some(([,t])=>D({...t,position:n})))}function Ie(r){return Object.values(r.guideConfigs.legend??{}).some(n=>z(n)!==void 0)||oe(r)||ie(r)}function P(r,n){const t=o=>o.textBaseline==="middle"?{...o,y:o.y-o.fontSize/2,textBaseline:"top"}:o;let i=r;for(const o of n){const e=i.graphicSpec.objects[o];if(e.type==="text")if(e.items!==void 0){if(!e.items.some(s=>s.properties.textBaseline==="middle"))continue;i=i.editGraphics({target:o,property:"items",value:e.items.map(s=>({type:s.type??e.type,properties:t(s.properties)}))})}else e.properties.textBaseline==="middle"&&(i=i.editGraphics({target:o,property:"y",value:t(e.properties).y}).editGraphics({target:o,property:"textBaseline",value:"top"}))}return i}function E(r,n){return n.map(t=>{const i=t.blocks[0],o=r.guideConfigs.legend?.[i.kind],e=t.blocks.length>1,s=e?void 0:i.titleId,a=t.blocks.flatMap(l=>l.foregroundIds).filter(l=>l!==s);e&&a.push(...t.blocks.slice(1).flatMap(l=>l.backgroundId===void 0?[]:[l.backgroundId]));const f=b(r,a),u=b(r,[...a,...s===void 0?[]:[s]]);if(f===void 0||u===void 0)throw new Error(`Legend lane could not measure ${t.id} content.`);const c=t.border,d=c===!1?0:c.padding+c.lineWidth/2;return{id:t.id,atomic:e,titleId:s,contentIds:a,title:e?void 0:i.title,inline:o?.titlePosition==="left",element:e?u:i.symbol.bounds,content:f,horizontal:{left:u.left,right:u.right,top:u.top,bottom:u.bottom},inset:d,padding:c===!1?0:c.padding,backgroundId:t.backgroundId}})}function q(r,n,t){let i=r;const o=new Map(n.map(e=>[e.id,e]));for(const e of t.placements){const s=o.get(e.id);s.titleId!==void 0&&(i=m(i,s.titleId,e.dx,e.titleDy));for(const a of s.contentIds)i=m(i,a,e.dx,e.contentDy);e.background!==void 0&&(i=v(i,e.background.id,e.background))}return t.background!==void 0&&(i=v(i,t.background.id,t.background)),i}const ye=G({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(r={}){if(S(r,"rematerializeSideLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{},o=["right","left"].flatMap(s=>{const a=x(this,s);if(a.length===0)return[];if(a.length===1)return[{side:s,entries:a}];const f=a.map(([c,d])=>B(this,c,d));return[{plan:J({side:s,plot:n,canvas:t,groups:j(f,i)}),blocks:f,side:s,entries:a}]});let e=this;for(const{plan:s,blocks:a,side:f,entries:u}of o){if(s===void 0)continue;const c=new Map(a.map(d=>[d.id,d]));for(const d of s.placements){const l=c.get(d.id);l.titleId!==void 0&&(e=m(e,l.titleId,d.titleDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.titleId,property:"textAlign",value:"left"})));for(const g of l.symbolIds)e=m(e,g,d.symbolDx,d.dy);e=m(e,l.labelId,d.labelDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.labelId,property:"textAlign",value:"left"}))}for(const d of a){if(d.backgroundId===void 0||s.backgrounds.some(p=>p.id===d.backgroundId))continue;const l=b(e,d.foregroundIds),g=K([l],d.border,t,"Legend");e=T(e,d.backgroundId,g,d.border)}for(const d of s.backgrounds)e=v(e,d.id,d)}for(const{side:s,entries:a}of o){const f=a.flatMap(([d])=>h(e,d));e=P(e,f);const u=b(e,f),{dy:c}=N({side:s,plot:n,canvas:t,bounds:u});for(const d of f)e=m(e,d,0,c)}return e}),xe=G({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(r={}){if(S(r,"rematerializeHorizontalLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{};let o=this;for(const e of["top","bottom"]){const s=x(o,e).filter(([,c])=>D({...c,position:e})).map(([c,d])=>[c,F(o,c,d)]);if(s.length===0)continue;if(s.length===1){const[c,d]=s[0],l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k);continue}for(const[c,d]of s){if(d.inferredOffset!==!0||I(o,d,n)===d.offset)continue;const l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k)}let a=j(s.map(([c,d])=>B(o,c,d)),i);for(const c of a.filter(d=>d.blocks.length>1)){const d=i[c.blocks[0].kind],l=E(o,c.blocks.map((p,k)=>({id:p.id,blocks:[p],border:k===0?!1:p.border,backgroundId:k===0?void 0:p.backgroundId}))).map(p=>({...p,element:p.content})),g=_({edge:e,plot:n,canvas:t,groups:l,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0,align:d.align,offset:I(o,d,n),border:c.border,backgroundId:c.backgroundId});o=q(o,l,g)}if(a.length<2)continue;a=j(s.map(([c,d])=>B(o,c,d)),i);const f=E(o,a),u=Y({edge:e,plot:n,canvas:t,groups:f,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0});o=q(o,f,u)}return o}),Le=G({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(r={}){if(S(r,"rematerializeLegendGroups"),this.context.facetLegendIntrinsic===!0)return this;let n=this;const t=C(n),i=w(n)?.properties;for(const[o,e]of Object.entries(n.guideConfigs.legendGroups??{})){const s=Object.entries(n.guideConfigs.legend??{}).filter(([,c])=>c.target===o&&z(c)===void 0).sort(([c],[d])=>y[c]-y[d]);n=P(n,s.flatMap(([c])=>h(n,c)));const a=s.map(([c])=>({id:c,ids:h(n,c),bounds:b(n,h(n,c))})),f=e.placement.at!==void 0?e:{...e,placement:{...e.placement,offset:I(n,{...e.placement,inferredOffset:e.inferredOffset},t)}},u=A({blocks:a,group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of u){const d=a.find(l=>l.id===c.id);for(const l of d.ids)n=m(n,l,c.dx,c.dy)}}for(const[o,e]of Object.entries(n.guideConfigs.legend??{})){const s=$(e);if(s===void 0)continue;const a=h(n,o);n=P(n,a);const f={placement:{...s,offset:I(n,s,t)},blockLayout:"column"},[u]=A({blocks:[{id:o,bounds:b(n,a)}],group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of a)n=m(n,c,u.dx,u.dy)}return n});export{ie as hasHorizontalLegendLane,Ie as hasLegendLane,oe as hasSideLegendLane,xe as rematerializeHorizontalLegendLane,Le as rematerializeLegendGroups,ye as rematerializeSideLegendLane};
|
|
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,140 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
validateGeneratedItemLimit
|
|
4
|
-
} from "../../../core/validation.js";
|
|
5
|
-
|
|
6
|
-
const DEFAULT_COUNT = 5;
|
|
7
|
-
const MAX_EXACT_VALUES = 100;
|
|
8
|
-
|
|
9
|
-
function validateCount(value, label) {
|
|
10
|
-
if (!Number.isInteger(value) || value < 2) {
|
|
11
|
-
throw new RangeError(`${label} count must be an integer of at least 2.`);
|
|
12
|
-
}
|
|
13
|
-
validateGeneratedItemLimit(value, `${label} count`);
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeExactValues(value, label) {
|
|
18
|
-
if (!Array.isArray(value)) {
|
|
19
|
-
throw new TypeError(`${label} values must be an array or "auto".`);
|
|
20
|
-
}
|
|
21
|
-
if (value.length === 0 || value.length > MAX_EXACT_VALUES) {
|
|
22
|
-
throw new RangeError(
|
|
23
|
-
`${label} values must contain between 1 and ${MAX_EXACT_VALUES} values.`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
const values = [...value];
|
|
27
|
-
for (let index = 0; index < values.length; index += 1) {
|
|
28
|
-
if (!Number.isFinite(values[index])) {
|
|
29
|
-
throw new TypeError(`${label} values must contain only finite numbers.`);
|
|
30
|
-
}
|
|
31
|
-
if (index > 0 && !(values[index] > values[index - 1])) {
|
|
32
|
-
throw new RangeError(
|
|
33
|
-
`${label} values must be unique and strictly increasing.`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return Object.freeze(values);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function readLegendSampling(config, defaultCount = DEFAULT_COUNT) {
|
|
41
|
-
if (config?.sampling?.mode === "values") {
|
|
42
|
-
return {
|
|
43
|
-
mode: "values",
|
|
44
|
-
values: config.sampling.values,
|
|
45
|
-
count: config.sampling.count ?? config.count ?? defaultCount
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
mode: "auto",
|
|
50
|
-
count: config?.sampling?.count ?? config?.count ?? defaultCount
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function normalizeLegendSampling(
|
|
55
|
-
patch,
|
|
56
|
-
{ previous, operation = "create", label = "Legend" } = {}
|
|
57
|
-
) {
|
|
58
|
-
const prior = readLegendSampling(previous);
|
|
59
|
-
const hasValues = Object.hasOwn(patch, "values");
|
|
60
|
-
const hasCount = Object.hasOwn(patch, "count");
|
|
61
|
-
const requestedValues = patch.values;
|
|
62
|
-
|
|
63
|
-
if (hasValues && requestedValues !== "auto" && hasCount) {
|
|
64
|
-
throw new Error(`${label} cannot specify both count and exact values.`);
|
|
65
|
-
}
|
|
66
|
-
if (requestedValues === "auto" && operation === "create") {
|
|
67
|
-
throw new Error(`${label} creation uses omission for automatic values.`);
|
|
68
|
-
}
|
|
69
|
-
if (!hasValues && !hasCount) {
|
|
70
|
-
return Object.freeze({
|
|
71
|
-
...prior,
|
|
72
|
-
...(prior.values === undefined
|
|
73
|
-
? {}
|
|
74
|
-
: { values: Object.freeze([...prior.values]) })
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
if (!hasValues) {
|
|
78
|
-
if (prior.mode === "values") {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`${label} count cannot replace exact values; set values to "auto" first.`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
return Object.freeze({ mode: "auto", count: validateCount(patch.count, label) });
|
|
84
|
-
}
|
|
85
|
-
if (requestedValues === "auto") {
|
|
86
|
-
return Object.freeze({
|
|
87
|
-
mode: "auto",
|
|
88
|
-
count: hasCount ? validateCount(patch.count, label) : validateCount(prior.count, label)
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return Object.freeze({
|
|
92
|
-
mode: "values",
|
|
93
|
-
values: normalizeExactValues(requestedValues, label),
|
|
94
|
-
count: validateCount(prior.count, label)
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function validateSamplingAgainstScale(sampling, scale, label = "Legend") {
|
|
99
|
-
if (sampling.mode !== "values") return sampling;
|
|
100
|
-
const domain = scale?.domain;
|
|
101
|
-
if (!Array.isArray(domain) || domain.length < 2 || !domain.every(Number.isFinite)) {
|
|
102
|
-
throw new Error(`${label} exact values require a finite quantitative scale domain.`);
|
|
103
|
-
}
|
|
104
|
-
const lower = Math.min(...domain);
|
|
105
|
-
const upper = Math.max(...domain);
|
|
106
|
-
for (const value of sampling.values) {
|
|
107
|
-
if (scale.type === "log" && value <= 0) {
|
|
108
|
-
throw new RangeError(`${label} exact values must be positive for a log scale.`);
|
|
109
|
-
}
|
|
110
|
-
if (value < lower || value > upper) {
|
|
111
|
-
throw new RangeError(
|
|
112
|
-
`${label} exact value ${value} is outside the scale domain [${lower}, ${upper}].`
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return sampling;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function resolveLegendSampleValues(config, scale, label = "Legend") {
|
|
120
|
-
const sampling = validateSamplingAgainstScale(
|
|
121
|
-
readLegendSampling(config),
|
|
122
|
-
scale,
|
|
123
|
-
label
|
|
124
|
-
);
|
|
125
|
-
return sampling.mode === "values"
|
|
126
|
-
? [...sampling.values]
|
|
127
|
-
: sampleNumericRange(
|
|
128
|
-
scale.domain[0],
|
|
129
|
-
scale.domain[1],
|
|
130
|
-
sampling.count,
|
|
131
|
-
`${label} domain`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function hasExactLegendSamplingForScale(program, scaleId) {
|
|
136
|
-
return ["size", "opacity", "strokeWidth", "gradient"].some(kind => {
|
|
137
|
-
const config = program.guideConfigs.legend?.[kind];
|
|
138
|
-
return config?.scale === scaleId && readLegendSampling(config).mode === "values";
|
|
139
|
-
});
|
|
140
|
-
}
|
|
1
|
+
import{sampleNumericRange as d}from"../../../grammar/numeric.js";import{validateGeneratedItemLimit as f}from"../../../core/validation.js";const v=5,m=100;function s(e,n){if(!Number.isInteger(e)||e<2)throw new RangeError(`${n} count must be an integer of at least 2.`);return f(e,`${n} count`),e}function g(e,n){if(!Array.isArray(e))throw new TypeError(`${n} values must be an array or "auto".`);if(e.length===0||e.length>m)throw new RangeError(`${n} values must contain between 1 and ${m} values.`);const r=[...e];for(let t=0;t<r.length;t+=1){if(!Number.isFinite(r[t]))throw new TypeError(`${n} values must contain only finite numbers.`);if(t>0&&!(r[t]>r[t-1]))throw new RangeError(`${n} values must be unique and strictly increasing.`)}return Object.freeze(r)}function c(e,n=v){return e?.sampling?.mode==="values"?{mode:"values",values:e.sampling.values,count:e.sampling.count??e.count??n}:{mode:"auto",count:e?.sampling?.count??e?.count??n}}function p(e,{previous:n,operation:r="create",label:t="Legend"}={}){const o=c(n),u=Object.hasOwn(e,"values"),a=Object.hasOwn(e,"count"),i=e.values;if(u&&i!=="auto"&&a)throw new Error(`${t} cannot specify both count and exact values.`);if(i==="auto"&&r==="create")throw new Error(`${t} creation uses omission for automatic values.`);if(!u&&!a)return Object.freeze({...o,...o.values===void 0?{}:{values:Object.freeze([...o.values])}});if(!u){if(o.mode==="values")throw new Error(`${t} count cannot replace exact values; set values to "auto" first.`);return Object.freeze({mode:"auto",count:s(e.count,t)})}return Object.freeze(i==="auto"?{mode:"auto",count:s(a?e.count:o.count,t)}:{mode:"values",values:g(i,t),count:s(o.count,t)})}function l(e,n,r="Legend"){if(e.mode!=="values")return e;const t=n?.domain;if(!Array.isArray(t)||t.length<2||!t.every(Number.isFinite))throw new Error(`${r} exact values require a finite quantitative scale domain.`);const o=Math.min(...t),u=Math.max(...t);for(const a of e.values){if(n.type==="log"&&a<=0)throw new RangeError(`${r} exact values must be positive for a log scale.`);if(a<o||a>u)throw new RangeError(`${r} exact value ${a} is outside the scale domain [${o}, ${u}].`)}return e}function $(e,n,r="Legend"){const t=l(c(e),n,r);if(t.mode==="values")return[...t.values];const o=d(n.domain[0],n.domain[1],t.count,`${r} domain`);return o[0]=n.domain[0],o[o.length-1]=n.domain[1],o}function E(e,n){return["size","opacity","strokeWidth","gradient","strokeGradient"].some(r=>{const t=e.guideConfigs.legend?.[r];return t?.scale===n&&c(t).mode==="values"})}export{E as hasExactLegendSamplingForScale,p as normalizeLegendSampling,c as readLegendSampling,$ as resolveLegendSampleValues,l as validateSamplingAgainstScale};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {validateLegendClip} from "../../../grammar/legendClip.js";
|
|
2
|
+
import { APPEARANCE_MARK_TYPES } from "../../../core/vocabulary.js";
|
|
2
3
|
import { intrinsicLegendConfig } from "../../../materialization/legendGroups.js";
|
|
3
4
|
import { action, closedAction } from "../../../core/action.js";
|
|
4
5
|
import {
|
|
@@ -35,7 +36,7 @@ import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
|
|
|
35
36
|
export { STROKE_WIDTH_LEGEND_LABELS, STROKE_WIDTH_LEGEND_TITLE_STYLE };
|
|
36
37
|
|
|
37
38
|
export function isStrokeWidthLegendLayer(layer) {
|
|
38
|
-
return
|
|
39
|
+
return APPEARANCE_MARK_TYPES.encodeStrokeWidth.includes(layer?.mark?.type) &&
|
|
39
40
|
layer.encoding?.strokeWidth?.scale !== undefined;
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -49,7 +50,7 @@ function resolveLayer(program, requested) {
|
|
|
49
50
|
if (layer === undefined) {
|
|
50
51
|
throw new Error(
|
|
51
52
|
requested === undefined
|
|
52
|
-
? "Stroke-width legend requires one eligible line or
|
|
53
|
+
? "Stroke-width legend requires one eligible line, rule or rect mark or an explicit target."
|
|
53
54
|
: `Unknown stroke-width legend target "${requested}".`
|
|
54
55
|
);
|
|
55
56
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./target.js";
|
|
12
12
|
import { validateLegendBlockOverride } from "./blocks.js";
|
|
13
13
|
import { DEFAULT_VERTICAL_LEGEND_ITEM_GAP } from "../../../theme/defaults.js";
|
|
14
|
+
import {LEGEND_SPACING_KEYS,normalizeLegendSpacing,validateLegendSpacing} from "../../../grammar/legendSpacing.js";
|
|
14
15
|
|
|
15
16
|
const family = (channel, type) => isContinuousColorScaleType(type)
|
|
16
17
|
? channel === "stroke" ? "strokeGradient" : "gradient"
|
|
@@ -38,6 +39,9 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
38
39
|
if (from === undefined || to === undefined || from === to) return undefined;
|
|
39
40
|
const stored = program.guideConfigs.legend?.[from];
|
|
40
41
|
if ((stored?.scale ?? stored?.scales?.[0]) !== scale.id) return undefined;
|
|
42
|
+
if (["gradient","strokeGradient"].includes(to) && stored.rowPadding !== undefined) {
|
|
43
|
+
throw new Error(`${channel} legend transition requires resetting rowPadding before creating a gradient block.`);
|
|
44
|
+
}
|
|
41
45
|
const oldDescriptor = describeLegendBlock(program, from, stored);
|
|
42
46
|
const overrideKeys = Object.keys(stored.blockOverrides ?? {});
|
|
43
47
|
if (overrideKeys.some(key => key !== oldDescriptor.key)) {
|
|
@@ -58,7 +62,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
58
62
|
const custom = sampledColor ? false : categorical
|
|
59
63
|
? categoricalFamilyCustom(program, from, stored)
|
|
60
64
|
: gradientFamily
|
|
61
|
-
? stored.gradientDirection !== undefined || stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
|
|
65
|
+
? stored.sampling?.mode === "values" || stored.gradientDirection !== undefined || stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
|
|
62
66
|
: !same(comparableSymbol, defaults.symbol) || stored.itemGap !== defaults.itemGap ||
|
|
63
67
|
stored.direction !== defaults.direction || (stored.columns !== undefined && !(side && stored.columns === 1));
|
|
64
68
|
if (custom) {
|
|
@@ -77,6 +81,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
77
81
|
target: stored.target, channels: [channel], position: stored.position,
|
|
78
82
|
labels: stored.labels, titleStyle: stored.titleStyle, border: stored.border,
|
|
79
83
|
align: stored.align, offset: stored.offset, titlePosition: sampledColor ? "top" : stored.titlePosition,
|
|
84
|
+
...normalizeLegendSpacing(stored),
|
|
80
85
|
...(stored.inferredTitle ? {} : { title: stored.title })
|
|
81
86
|
};
|
|
82
87
|
try {
|
|
@@ -161,7 +166,7 @@ function withoutBlockOverrides(config) {
|
|
|
161
166
|
return rest;
|
|
162
167
|
}
|
|
163
168
|
|
|
164
|
-
export function planLegendBlockTransitions(program, target, plans, position) {
|
|
169
|
+
export function planLegendBlockTransitions(program, target, plans, position, spacingPatch = {}) {
|
|
165
170
|
const oldDescriptors = legendBlockDescriptors(program, target);
|
|
166
171
|
const nextDescriptors = plans.map(plan =>
|
|
167
172
|
describeLegendBlock(program, plan.kind, plan.config));
|
|
@@ -215,6 +220,18 @@ export function planLegendBlockTransitions(program, target, plans, position) {
|
|
|
215
220
|
}
|
|
216
221
|
if (override !== undefined) validateLegendBlockOverride(descriptor, override);
|
|
217
222
|
const config = withoutBlockOverrides(plan.config);
|
|
223
|
+
for (const key of LEGEND_SPACING_KEYS) {
|
|
224
|
+
let value=config[key];
|
|
225
|
+
if (spacingPatch[key] !== undefined) value=spacingPatch[key] === "auto" ? undefined
|
|
226
|
+
: normalizeLegendSpacing({[key]:spacingPatch[key]})[key];
|
|
227
|
+
else if (incoming.length > 0) {
|
|
228
|
+
const values=incoming.map(old=>old.config[key]);
|
|
229
|
+
if (!values.every(value=>value===values[0])) transitionError(target,incoming,descriptor,key);
|
|
230
|
+
value=values[0];
|
|
231
|
+
}
|
|
232
|
+
if (value === undefined) delete config[key];else config[key]=value;
|
|
233
|
+
}
|
|
234
|
+
validateLegendSpacing(plan.kind,config);
|
|
218
235
|
return {
|
|
219
236
|
...plan,
|
|
220
237
|
config: override === undefined
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
1
2
|
import { action } from "../../../../core/action.js";
|
|
2
3
|
import { resolvePolarGuideResources } from "../resolve.js";
|
|
3
4
|
import {
|
|
@@ -77,7 +78,7 @@ function makeCreateAxis(kind) {
|
|
|
77
78
|
return action({
|
|
78
79
|
op: operation,
|
|
79
80
|
description: `Create the complete Polar ${kind} axis.`
|
|
80
|
-
}, function (args = {}) {
|
|
81
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
81
82
|
validatePolarAxisArgs(kind, args, operation);
|
|
82
83
|
const resources = resolvePolarGuideResources(this, kind, args, operation);
|
|
83
84
|
const angle = resolveAngle(this, kind, args);
|
|
@@ -108,7 +109,7 @@ function makeCreateAxis(kind) {
|
|
|
108
109
|
...shared,
|
|
109
110
|
...(args.title ?? {})
|
|
110
111
|
});
|
|
111
|
-
});
|
|
112
|
+
}));
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
function validateAxisEditArgs(kind, args, operation) {
|
|
@@ -211,7 +212,7 @@ function makeEditAxis(kind) {
|
|
|
211
212
|
return action({
|
|
212
213
|
op: operation,
|
|
213
214
|
description: `Edit selected Polar ${kind}-axis components.`
|
|
214
|
-
}, function (args = {}) {
|
|
215
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
215
216
|
validateAxisEditArgs(kind, args, operation);
|
|
216
217
|
if (!AXIS_COMPONENTS.some(component => hasAxisComponent(this, kind, component))) {
|
|
217
218
|
throw new Error(`${operation} requires an existing ${kind}-axis component.`);
|
|
@@ -225,7 +226,7 @@ function makeEditAxis(kind) {
|
|
|
225
226
|
// Validate the whole proposal on an immutable branch before returning changes.
|
|
226
227
|
applyAxisEdit(this, kind, args);
|
|
227
228
|
return applyAxisEdit(this, kind, args);
|
|
228
|
-
});
|
|
229
|
+
}));
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
export const createThetaAxis = /* @__PURE__ */ makeCreateAxis("theta");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
1
2
|
import { axisThemeTypography } from "../../../theme/state.js";
|
|
2
3
|
import { action } from "../../../../core/action.js";
|
|
3
4
|
import { validateNonNegativeFinite } from "../../../../core/validation.js";
|
|
@@ -148,7 +149,7 @@ function makeEditLabels(kind) {
|
|
|
148
149
|
return action({
|
|
149
150
|
op: operation.edit,
|
|
150
151
|
description: `Edit the Polar ${kind}-axis labels.`
|
|
151
|
-
}, function (args = {}) {
|
|
152
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
152
153
|
validateObject(args, labelEditOptions(kind), operation.edit);
|
|
153
154
|
validateModeOptions(args, operation.edit);
|
|
154
155
|
const names = polarGuideNames(kind);
|
|
@@ -184,7 +185,7 @@ function makeEditLabels(kind) {
|
|
|
184
185
|
next = next.editGraphics({ target: names.labels, property, value });
|
|
185
186
|
}
|
|
186
187
|
return next;
|
|
187
|
-
});
|
|
188
|
+
}));
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
function makeCreateLabels(kind) {
|
|
@@ -192,7 +193,7 @@ function makeCreateLabels(kind) {
|
|
|
192
193
|
return action({
|
|
193
194
|
op: operation.create,
|
|
194
195
|
description: `Create the Polar ${kind}-axis labels.`
|
|
195
|
-
}, function (args = {}) {
|
|
196
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
196
197
|
validateComponentCreateArgs(kind, args, labelCreateOptions(kind), operation.create);
|
|
197
198
|
validateModeOptions(args, operation.create);
|
|
198
199
|
const names = polarGuideNames(kind);
|
|
@@ -217,7 +218,7 @@ function makeCreateLabels(kind) {
|
|
|
217
218
|
})
|
|
218
219
|
._withGuideConfig(kind, "labels", config)
|
|
219
220
|
[operation.edit]();
|
|
220
|
-
});
|
|
221
|
+
}));
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
export const createThetaAxisLabels = /* @__PURE__ */ makeCreateLabels("theta");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
1
2
|
import { axisThemeTypography } from "../../../theme/state.js";
|
|
2
3
|
import { action } from "../../../../core/action.js";
|
|
3
4
|
import {
|
|
@@ -96,7 +97,7 @@ function makeEditTitle(kind) {
|
|
|
96
97
|
return action({
|
|
97
98
|
op: operation.edit,
|
|
98
99
|
description: `Edit the Polar ${kind}-axis title.`
|
|
99
|
-
}, function (args = {}) {
|
|
100
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
100
101
|
validateObject(args, TITLE_EDIT_OPTIONS, operation.edit);
|
|
101
102
|
const names = polarGuideNames(kind);
|
|
102
103
|
const previous = this.guideConfigs.axis?.[kind]?.title;
|
|
@@ -136,7 +137,7 @@ function makeEditTitle(kind) {
|
|
|
136
137
|
next = next.editGraphics({ target: names.title, property, value });
|
|
137
138
|
}
|
|
138
139
|
return next;
|
|
139
|
-
});
|
|
140
|
+
}));
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
function makeCreateTitle(kind) {
|
|
@@ -144,7 +145,7 @@ function makeCreateTitle(kind) {
|
|
|
144
145
|
return action({
|
|
145
146
|
op: operation.create,
|
|
146
147
|
description: `Create the Polar ${kind}-axis title.`
|
|
147
|
-
}, function (args = {}) {
|
|
148
|
+
}, withGuideLayoutValidation(function (args = {}) {
|
|
148
149
|
validateComponentCreateArgs(kind, args, TITLE_CREATE_OPTIONS, operation.create);
|
|
149
150
|
const names = polarGuideNames(kind);
|
|
150
151
|
if (this.graphicSpec.objects[names.title] !== undefined) {
|
|
@@ -176,7 +177,7 @@ function makeCreateTitle(kind) {
|
|
|
176
177
|
})
|
|
177
178
|
._withGuideConfig(kind, "title", config)
|
|
178
179
|
[operation.edit]();
|
|
179
|
-
});
|
|
180
|
+
}));
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
export const createThetaAxisTitle = /* @__PURE__ */ makeCreateTitle("theta");
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
walkGraphicDrawOrder
|
|
23
23
|
} from "../../../grammar/schemas/graphicTree.js";
|
|
24
24
|
import { valuesFromTickConfig } from "../tickValues.js";
|
|
25
|
-
import { formatAxisValue, validateAxisFormat } from "../axes/policy.js";
|
|
25
|
+
import { formatAxisValue, formatNumericAxisTicks, validateAxisFormat } from "../axes/policy.js";
|
|
26
26
|
import { resolveDisplayLabel } from "../../../grammar/displayLabels.js";
|
|
27
27
|
|
|
28
28
|
export const POLAR_AXIS_DEFAULTS = Object.freeze({
|
|
@@ -200,6 +200,7 @@ export function mapPolarGuideValues(program, config) {
|
|
|
200
200
|
|
|
201
201
|
export function formatPolarGuideValues(program, config, values) {
|
|
202
202
|
const scale = program.resolvedScales[config.scale];
|
|
203
|
+
if (config.format === "ticks") return formatNumericAxisTicks(values, scale.type);
|
|
203
204
|
if (["ordinal", "band", "point"].includes(scale.type)) {
|
|
204
205
|
return values.map(value => resolveDisplayLabel(
|
|
205
206
|
value,
|