ggaction 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -4,11 +4,11 @@ import { validateOptionObject } from "../../../core/validation.js";
|
|
|
4
4
|
function makeFocusedLegendEdit({ name, options, map }) {
|
|
5
5
|
return action(
|
|
6
6
|
{
|
|
7
|
-
op: name,
|
|
7
|
+
op: name, scope: "any",
|
|
8
8
|
description: `Edit one focused legend component through editLegend.`
|
|
9
9
|
},
|
|
10
10
|
function (args = {}) {
|
|
11
|
-
validateOptionObject(args, ["target", ...options], name);
|
|
11
|
+
validateOptionObject(args, ["target", "channel", ...options], name);
|
|
12
12
|
if (!options.some(option => Object.hasOwn(args, option))) {
|
|
13
13
|
throw new Error(`${name} requires at least one component change.`);
|
|
14
14
|
}
|
|
@@ -20,7 +20,7 @@ function makeFocusedLegendEdit({ name, options, map }) {
|
|
|
20
20
|
const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
21
21
|
name: "editLegendLayout",
|
|
22
22
|
options: [
|
|
23
|
-
"position", "layout", "align", "direction", "columns", "offset",
|
|
23
|
+
"position", "layout", "align", "direction", "columns", "offset", "blockLayout", "at",
|
|
24
24
|
"titlePosition", "itemGap"
|
|
25
25
|
],
|
|
26
26
|
map: args => args
|
|
@@ -29,14 +29,15 @@ const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
|
29
29
|
const editLegendLabels = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
30
30
|
name: "editLegendLabels",
|
|
31
31
|
options: ["offset", "color", "fontSize", "fontFamily", "fontWeight", "format"],
|
|
32
|
-
map: ({ target, ...labels }) => ({ target, labels })
|
|
32
|
+
map: ({ target, channel, ...labels }) => ({ target, ...(channel === undefined ? {} : { channel }), labels })
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
const editLegendTitle = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
36
36
|
name: "editLegendTitle",
|
|
37
|
-
options: ["title", "color", "fontSize", "fontFamily", "fontWeight"],
|
|
38
|
-
map: ({ target, title, ...titleStyle }) => ({
|
|
37
|
+
options: ["title", "color", "fontSize", "fontFamily", "fontWeight", "align"],
|
|
38
|
+
map: ({ target, channel, title, ...titleStyle }) => ({
|
|
39
39
|
target,
|
|
40
|
+
...(channel === undefined ? {} : { channel }),
|
|
40
41
|
...(title === undefined ? {} : { title }),
|
|
41
42
|
...(Object.keys(titleStyle).length === 0 ? {} : { titleStyle })
|
|
42
43
|
})
|
|
@@ -44,7 +45,7 @@ const editLegendTitle = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
|
44
45
|
|
|
45
46
|
const editLegendSymbols = /* @__PURE__ */ makeFocusedLegendEdit({
|
|
46
47
|
name: "editLegendSymbols",
|
|
47
|
-
options: ["symbol", "count", "gradient"],
|
|
48
|
+
options: ["symbol", "count", "gradient", "clipHeight"],
|
|
48
49
|
map: args => args
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{legendResourcePolicy as O}from"../../../materialization/guides/resources.js";import{LEGEND_POSITIONS as E,LEGEND_BLOCK_LAYOUTS as x}from"../../../core/vocabulary.js";import{validateLegendGroupState as v}from"../../../grammar/legendGroups.js";import{validateOptionObject as G,validateNonNegativeFinite as C}from"../../../core/validation.js";import{PLOT_LEGEND_POSITIONS as m}from"../../../layout/legend.js";import{legendGroup as _}from"../../../materialization/legendGroups.js";const N=E;function j(n){if(Object.hasOwn(n,"at")){if(G(n.at,["x","y"],"Legend at"),!Number.isFinite(n.at.x)||!Number.isFinite(n.at.y))throw new TypeError("Legend at requires finite x and y coordinates.");if(["position","align","offset"].some(e=>Object.hasOwn(n,e)))throw new Error("Legend at conflicts with position, align and offset in the same call.")}if(Object.hasOwn(n,"blockLayout")&&!x.includes(n.blockLayout))throw new Error("Legend blockLayout must be row, column or wrap.")}function y(n,e){return n.at!==void 0||e.at!==void 0?n.at!==void 0&&e.at!==void 0&&n.at.x===e.at.x&&n.at.y===e.at.y:n.position===e.position&&n.align===e.align&&n.offset===e.offset}function P(n,e,r,{creation:u=!1,corner:f=!1,nativeOffset:o=30}={}){j(e);const t=_(n,r),s=t!==void 0||e.at!==void 0||e.blockLayout!==void 0||f,{at:p,blockLayout:a,...l}=e;if(!s)return{program:n,options:l,grouped:!1};const g=Object.entries(n.guideConfigs.legend??{}).filter(([,d])=>d.target===r),w=g.map(([,d])=>d),h=g.find(([d])=>["series","color","stroke"].includes(d))?.[1].offset??o;if(w.some(d=>d.layout==="legacy-bottom")||e.layout==="legacy-bottom")throw new Error("Legend groups require edge layout.");let i;if(e.at!==void 0)i={at:{...e.at}};else if(e.position!==void 0){if(!N.includes(e.position))throw new Error(`Unsupported legend position "${e.position}".`);const d=m.includes(e.position),c=t?.placement.position===e.position;i={position:e.position,align:e.align??(c?t.placement.align:"center"),offset:e.offset??(c?t.placement.offset:d?8:h)}}else if(t!==void 0){if(i=t.placement.at===void 0?{...t.placement,...e.align===void 0?{}:{align:e.align},...e.offset===void 0?{}:{offset:e.offset}}:t.placement,t.placement.at!==void 0&&(e.align!==void 0||e.offset!==void 0))throw new Error("Free legend placement requires position before align or offset.")}else{const d=w.map(c=>({position:c.position,align:c.align,offset:c.offset}));if(d.length>1&&!d.every(c=>y(c,d[0])))throw new Error("Legend blockLayout requires an explicit anchor when existing blocks disagree.");i=d[0]??{position:"right",align:"center",offset:h}}if(i.position!==void 0){if(!["left","center","right"].includes(i.align))throw new Error("Unsupported legend group alignment.");if((m.includes(i.position)||["left","right"].includes(i.position))&&i.align!=="center")throw new Error("Side and in-plot legend groups require center alignment.");C(i.offset,"Legend group offset")}if(u&&t!==void 0&&(e.at!==void 0||e.position!==void 0||e.align!==void 0||e.offset!==void 0)&&!y(i,t.placement))throw new Error("New legend block anchor conflicts with its target group.");if(u&&t!==void 0&&e.blockLayout!==void 0&&e.blockLayout!==t.blockLayout)throw new Error("New legend blockLayout conflicts with its target group.");const b=e.blockLayout??t?.blockLayout??(i.at!==void 0?"column":m.includes(i.position)?"row":"wrap"),L={placement:i,blockLayout:b};v({[r]:L});const k=n._withMaterializationConfig(["guides","legendGroups",r],L);return i.position!==void 0&&(l.position=i.position,l.offset=i.offset,l.align=i.align),{program:k,options:l,grouped:!0}}function K(n,e){if(e.target!==void 0)return e.target;const r=e.channels,u=n.semanticSpec.layers.filter(f=>r===void 0?Object.keys(f.encoding??{}).some(o=>["color","stroke","shape","strokeDash","size","opacity","strokeWidth"].includes(o)&&f.encoding[o]?.scale!==void 0):r.every(o=>f.encoding?.[o]?.scale!==void 0));if(u.length!==1)throw new Error("Legend group requires one eligible target or an explicit target.");return u[0].id}function M(n,e,r,{creation:u=!1}={}){let f=e;for(const[o,t]of Object.entries(e.guideConfigs.legend??{})){const s=n.guideConfigs.legend?.[o];if(u&&s!==void 0||!u&&t===s)continue;const p=s??Object.entries(n.guideConfigs.legend??{}).find(([l,g])=>g.target===t.target&&O(l).semanticKind===O(o).semanticKind)?.[1],a=r.offset!==void 0?!1:u?!0:p?.inferredOffset===!0;f=f._withLegendConfig(o,{...t,inferredOffset:a})}for(const[o,t]of Object.entries(e.guideConfigs.legendGroups??{})){if(t.placement.at!==void 0){if(t.inferredOffset!==void 0){const{inferredOffset:a,...l}=t;f=f._withMaterializationConfig(["guides","legendGroups",o],l)}continue}const s=n.guideConfigs.legendGroups?.[o];if(t===s)continue;const p=r.offset!==void 0?!1:s?.placement.at!==void 0?!0:s!==void 0?s.inferredOffset===!0:Object.values(f.guideConfigs.legend??{}).filter(a=>a.target===o).every(a=>a.inferredOffset===!0);f=f._withMaterializationConfig(["guides","legendGroups",o],{...t,inferredOffset:p})}return f}export{K as creationLegendTarget,P as prepareLegendGroup,M as recordLegendOffsetIntent,j as validateLegendGroupOptions};
|
|
@@ -1,516 +1 @@
|
|
|
1
|
-
import { editLegendBackground, resolveLegendBackgroundFromBounds } from "./continuous/common.js";
|
|
2
|
-
import { action } from "../../../core/action.js";
|
|
3
|
-
import { noOptions } from "../../../core/validation.js";
|
|
4
|
-
import {
|
|
5
|
-
resolveConcreteGraphicBounds,
|
|
6
|
-
unionConcreteGraphicBounds
|
|
7
|
-
} from "../../../grammar/schemas/graphicBounds.js";
|
|
8
|
-
import { finiteMidpoint } from "../../../grammar/numeric.js";
|
|
9
|
-
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
10
|
-
import {
|
|
11
|
-
resolveHorizontalLegendLane,
|
|
12
|
-
resolveHorizontalLegendGroup,
|
|
13
|
-
resolveSingleHorizontalLegendPlacement,
|
|
14
|
-
isHorizontalEdgeLegend,
|
|
15
|
-
resolveSideLegendLane
|
|
16
|
-
} from "../../../layout/legendLane.js";
|
|
17
|
-
import { findCanvasGraphic } from
|
|
18
|
-
"../../../materialization/graphicHierarchy.js";
|
|
19
|
-
import { legendResourcePolicy } from
|
|
20
|
-
"../../../materialization/guides/resources.js";
|
|
21
|
-
|
|
22
|
-
const FAMILY_ORDER = Object.freeze({
|
|
23
|
-
series: 0,
|
|
24
|
-
color: 0,
|
|
25
|
-
stroke: 0,
|
|
26
|
-
gradient: 0,
|
|
27
|
-
interval: 0,
|
|
28
|
-
strokeGradient: 0,
|
|
29
|
-
strokeInterval: 0,
|
|
30
|
-
size: 1,
|
|
31
|
-
opacity: 2,
|
|
32
|
-
strokeWidth: 3
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
function categoricalFor(program, target) {
|
|
36
|
-
return ["series", "color", "stroke"]
|
|
37
|
-
.map(kind => program.guideConfigs.legend?.[kind])
|
|
38
|
-
.find(config => config?.target === target);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function legendPosition(program, kind, config) {
|
|
42
|
-
if (kind === "size") {
|
|
43
|
-
return categoricalFor(program, config.target)?.position ?? config.position;
|
|
44
|
-
}
|
|
45
|
-
return config.position;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function requestedOffset(config) {
|
|
49
|
-
return config.offset;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function existingIds(program, kind) {
|
|
53
|
-
return legendResourcePolicy(kind).graphicIds.filter(
|
|
54
|
-
id => program.graphicSpec.objects[id] !== undefined
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function componentIds(program, kind) {
|
|
59
|
-
const ids = existingIds(program, kind);
|
|
60
|
-
const titleId = ids.find(id => id.endsWith("Title"));
|
|
61
|
-
const labelId = ids.find(id => id.endsWith("Labels"));
|
|
62
|
-
const backgroundId = ids.find(id => id.endsWith("Background"));
|
|
63
|
-
const symbolIds = ids.filter(id =>
|
|
64
|
-
id !== titleId && id !== labelId && id !== backgroundId
|
|
65
|
-
);
|
|
66
|
-
return { titleId, labelId, backgroundId, symbolIds };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function legendGraphicBounds(program, id) {
|
|
70
|
-
let bounds = resolveConcreteGraphicBounds(program.graphicSpec, id, program.materializationConfigs.textMetrics);
|
|
71
|
-
const graphic = program.graphicSpec.objects[id];
|
|
72
|
-
if (!id.includes("LegendSymbol") || graphic?.type !== "line") return bounds;
|
|
73
|
-
const properties = graphic.items?.map(item => item.properties) ??
|
|
74
|
-
[graphic.properties];
|
|
75
|
-
for (const item of properties) {
|
|
76
|
-
if (
|
|
77
|
-
Object.hasOwn(item, "lineCap") ||
|
|
78
|
-
typeof item.stroke !== "string" ||
|
|
79
|
-
!(item.strokeWidth > 0)
|
|
80
|
-
) continue;
|
|
81
|
-
const extent = item.strokeWidth / 2;
|
|
82
|
-
bounds = {
|
|
83
|
-
left: Math.min(bounds.left, Math.min(item.x1, item.x2) - extent),
|
|
84
|
-
right: Math.max(bounds.right, Math.max(item.x1, item.x2) + extent),
|
|
85
|
-
top: Math.min(bounds.top, Math.min(item.y1, item.y2) - extent),
|
|
86
|
-
bottom: Math.max(bounds.bottom, Math.max(item.y1, item.y2) + extent)
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return bounds;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function unionLegendBounds(program, ids) {
|
|
93
|
-
return ids.map(id => legendGraphicBounds(program, id))
|
|
94
|
-
.reduce((result, bounds) => result === undefined
|
|
95
|
-
? bounds
|
|
96
|
-
: {
|
|
97
|
-
left: Math.min(result.left, bounds.left),
|
|
98
|
-
right: Math.max(result.right, bounds.right),
|
|
99
|
-
top: Math.min(result.top, bounds.top),
|
|
100
|
-
bottom: Math.max(result.bottom, bounds.bottom)
|
|
101
|
-
}, undefined);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function textAnchor(program, id) {
|
|
105
|
-
const graphic = program.graphicSpec.objects[id];
|
|
106
|
-
const properties = graphic.items?.[0]?.properties ?? graphic.properties;
|
|
107
|
-
return properties.x;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function blockDescriptor(program, kind, config) {
|
|
111
|
-
const components = componentIds(program, kind);
|
|
112
|
-
if (components.labelId === undefined || components.symbolIds.length === 0) {
|
|
113
|
-
throw new Error(`Legend lane requires complete ${kind} graphics.`);
|
|
114
|
-
}
|
|
115
|
-
const foregroundIds = [
|
|
116
|
-
...components.symbolIds,
|
|
117
|
-
components.labelId,
|
|
118
|
-
...(components.titleId === undefined ? [] : [components.titleId])
|
|
119
|
-
];
|
|
120
|
-
const bounds = unionLegendBounds(program, foregroundIds);
|
|
121
|
-
const symbolAnchorIds = ["gradient", "strokeGradient"].includes(kind)
|
|
122
|
-
? [kind === "gradient" ? "colorGradientStrips" : "strokeGradientStrips"]
|
|
123
|
-
: components.symbolIds;
|
|
124
|
-
const symbolAnchor = unionLegendBounds(program, symbolAnchorIds);
|
|
125
|
-
const symbolBounds = unionLegendBounds(program, components.symbolIds);
|
|
126
|
-
const labels = resolveConcreteGraphicBounds(
|
|
127
|
-
program.graphicSpec,
|
|
128
|
-
components.labelId
|
|
129
|
-
, program.materializationConfigs.textMetrics);
|
|
130
|
-
const title = components.titleId === undefined
|
|
131
|
-
? undefined
|
|
132
|
-
: (() => {
|
|
133
|
-
const graphic = program.graphicSpec.objects[components.titleId];
|
|
134
|
-
const titleBounds = resolveConcreteGraphicBounds(
|
|
135
|
-
program.graphicSpec,
|
|
136
|
-
components.titleId
|
|
137
|
-
, program.materializationConfigs.textMetrics);
|
|
138
|
-
return {
|
|
139
|
-
x: graphic.properties.x,
|
|
140
|
-
y: graphic.properties.y,
|
|
141
|
-
fontSize: graphic.properties.fontSize,
|
|
142
|
-
width: titleBounds.right - titleBounds.left,
|
|
143
|
-
bounds: titleBounds
|
|
144
|
-
};
|
|
145
|
-
})();
|
|
146
|
-
if ([bounds, symbolAnchor, symbolBounds, labels].includes(undefined)) {
|
|
147
|
-
throw new Error(`Legend lane could not measure ${kind} graphics.`);
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
id: kind,
|
|
151
|
-
kind,
|
|
152
|
-
target: config.target,
|
|
153
|
-
border: borderFor(kind, config),
|
|
154
|
-
offset: requestedOffset(config),
|
|
155
|
-
bounds,
|
|
156
|
-
title,
|
|
157
|
-
symbol: {
|
|
158
|
-
centerX: finiteMidpoint(symbolAnchor.left, symbolAnchor.right),
|
|
159
|
-
left: symbolBounds.left,
|
|
160
|
-
right: symbolBounds.right,
|
|
161
|
-
bounds: symbolAnchor
|
|
162
|
-
},
|
|
163
|
-
labels: {
|
|
164
|
-
x: textAnchor(program, components.labelId),
|
|
165
|
-
width: labels.right - labels.left
|
|
166
|
-
},
|
|
167
|
-
occupiedBounds: unionLegendBounds(program, [
|
|
168
|
-
...foregroundIds,
|
|
169
|
-
...(components.backgroundId === undefined ? [] : [components.backgroundId])
|
|
170
|
-
]),
|
|
171
|
-
...components,
|
|
172
|
-
foregroundIds
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function borderFor(kind, config) {
|
|
177
|
-
return [
|
|
178
|
-
"series", "color", "stroke", "gradient", "strokeGradient", "opacity",
|
|
179
|
-
"interval", "strokeInterval", "strokeWidth", "size"
|
|
180
|
-
].includes(kind)
|
|
181
|
-
? config.border
|
|
182
|
-
: false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function groupBlocks(blocks, configs) {
|
|
186
|
-
const groups = [];
|
|
187
|
-
for (let index = 0; index < blocks.length; index += 1) {
|
|
188
|
-
const block = blocks[index];
|
|
189
|
-
const config = configs[block.kind];
|
|
190
|
-
const next = blocks[index + 1];
|
|
191
|
-
if (
|
|
192
|
-
["series", "color", "stroke"].includes(block.kind) &&
|
|
193
|
-
next?.kind === "size" && next.target === block.target
|
|
194
|
-
) {
|
|
195
|
-
groups.push({
|
|
196
|
-
id: `${block.kind}+size`,
|
|
197
|
-
blocks: [block, next.backgroundId === undefined ? next : { ...next,
|
|
198
|
-
bounds: next.occupiedBounds, inset: next.border.padding + next.border.lineWidth / 2 }],
|
|
199
|
-
border: borderFor(block.kind, config),
|
|
200
|
-
backgroundId: block.backgroundId
|
|
201
|
-
});
|
|
202
|
-
index += 1;
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
groups.push({
|
|
206
|
-
id: block.kind,
|
|
207
|
-
blocks: [block],
|
|
208
|
-
border: borderFor(block.kind, config),
|
|
209
|
-
backgroundId: block.backgroundId
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
return groups;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function edgeKinds(program, edge) {
|
|
216
|
-
const configs = program.guideConfigs.legend ?? {};
|
|
217
|
-
const layerOrder = new Map(program.semanticSpec.layers.map(
|
|
218
|
-
(layer, index) => [layer.id, index]
|
|
219
|
-
));
|
|
220
|
-
return Object.entries(configs)
|
|
221
|
-
.filter(([kind, config]) => legendPosition(program, kind, config) === edge)
|
|
222
|
-
.sort(([kindA, configA], [kindB, configB]) =>
|
|
223
|
-
(layerOrder.get(configA.target) - layerOrder.get(configB.target)) ||
|
|
224
|
-
(FAMILY_ORDER[kindA] - FAMILY_ORDER[kindB])
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export function hasMultiSideLegendLane(program) {
|
|
229
|
-
return ["right", "left"].some(side => edgeKinds(program, side).length > 1);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function hasHorizontalLegendLane(program) {
|
|
233
|
-
return ["top", "bottom"].some(edge => edgeKinds(program, edge).some(
|
|
234
|
-
([, config]) => isHorizontalEdgeLegend({ ...config, position: edge })
|
|
235
|
-
));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function hasLegendLane(program) {
|
|
239
|
-
return hasMultiSideLegendLane(program) ||
|
|
240
|
-
hasHorizontalLegendLane(program);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function translateCommands(commands, dx, dy) {
|
|
244
|
-
return commands.map(command => {
|
|
245
|
-
if (command.op === "Z") return { ...command };
|
|
246
|
-
const next = {
|
|
247
|
-
...command,
|
|
248
|
-
x: command.x + dx,
|
|
249
|
-
y: command.y + dy
|
|
250
|
-
};
|
|
251
|
-
if (command.op === "C") {
|
|
252
|
-
next.x1 = command.x1 + dx;
|
|
253
|
-
next.y1 = command.y1 + dy;
|
|
254
|
-
next.x2 = command.x2 + dx;
|
|
255
|
-
next.y2 = command.y2 + dy;
|
|
256
|
-
}
|
|
257
|
-
return next;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function translatedProperties(type, properties, dx, dy) {
|
|
262
|
-
const next = { ...properties };
|
|
263
|
-
if (["circle", "rect", "text"].includes(type)) {
|
|
264
|
-
next.x += dx;
|
|
265
|
-
next.y += dy;
|
|
266
|
-
} else if (type === "line") {
|
|
267
|
-
next.x1 += dx;
|
|
268
|
-
next.x2 += dx;
|
|
269
|
-
next.y1 += dy;
|
|
270
|
-
next.y2 += dy;
|
|
271
|
-
} else if (type === "path") {
|
|
272
|
-
next.commands = translateCommands(next.commands, dx, dy);
|
|
273
|
-
}
|
|
274
|
-
return next;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function translateGraphic(program, id, dx, dy) {
|
|
278
|
-
const graphic = program.graphicSpec.objects[id];
|
|
279
|
-
if (dx === 0 && dy === 0) return program;
|
|
280
|
-
if (graphic.items !== undefined) {
|
|
281
|
-
return program.editGraphics({
|
|
282
|
-
target: id,
|
|
283
|
-
property: "items",
|
|
284
|
-
value: graphic.items.map(item => ({
|
|
285
|
-
type: item.type ?? graphic.type,
|
|
286
|
-
properties: translatedProperties(
|
|
287
|
-
item.type ?? graphic.type,
|
|
288
|
-
item.properties,
|
|
289
|
-
dx,
|
|
290
|
-
dy
|
|
291
|
-
)
|
|
292
|
-
}))
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
let next = program;
|
|
296
|
-
const properties = translatedProperties(
|
|
297
|
-
graphic.type,
|
|
298
|
-
graphic.properties,
|
|
299
|
-
dx,
|
|
300
|
-
dy
|
|
301
|
-
);
|
|
302
|
-
for (const [property, value] of Object.entries(properties)) {
|
|
303
|
-
if (value !== graphic.properties[property]) {
|
|
304
|
-
next = next.editGraphics({ target: id, property, value });
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return next;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function horizontalGroups(program, groups) {
|
|
311
|
-
return groups.map(group => {
|
|
312
|
-
const representative = group.blocks[0];
|
|
313
|
-
const config = program.guideConfigs.legend?.[representative.kind];
|
|
314
|
-
const atomic = group.blocks.length > 1;
|
|
315
|
-
const titleId = atomic ? undefined : representative.titleId;
|
|
316
|
-
const contentIds = group.blocks.flatMap(block => block.foregroundIds)
|
|
317
|
-
.filter(id => id !== titleId);
|
|
318
|
-
if (atomic) contentIds.push(...group.blocks.slice(1).flatMap(block =>
|
|
319
|
-
block.backgroundId === undefined ? [] : [block.backgroundId]));
|
|
320
|
-
const content = unionLegendBounds(program, contentIds);
|
|
321
|
-
const foreground = unionLegendBounds(
|
|
322
|
-
program,
|
|
323
|
-
[...contentIds, ...(titleId === undefined ? [] : [titleId])]
|
|
324
|
-
);
|
|
325
|
-
if (content === undefined || foreground === undefined) {
|
|
326
|
-
throw new Error(`Legend lane could not measure ${group.id} content.`);
|
|
327
|
-
}
|
|
328
|
-
const border = group.border;
|
|
329
|
-
const inset = border === false
|
|
330
|
-
? 0
|
|
331
|
-
: border.padding + border.lineWidth / 2;
|
|
332
|
-
return {
|
|
333
|
-
id: group.id,
|
|
334
|
-
atomic,
|
|
335
|
-
titleId,
|
|
336
|
-
contentIds,
|
|
337
|
-
title: atomic ? undefined : representative.title,
|
|
338
|
-
inline: config?.titlePosition === "left",
|
|
339
|
-
element: atomic ? foreground : representative.symbol.bounds,
|
|
340
|
-
content,
|
|
341
|
-
horizontal: { left: foreground.left, right: foreground.right,
|
|
342
|
-
top: foreground.top, bottom: foreground.bottom },
|
|
343
|
-
inset,
|
|
344
|
-
padding: border === false ? 0 : border.padding,
|
|
345
|
-
backgroundId: group.backgroundId
|
|
346
|
-
};
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function applyHorizontalPlan(program, groups, plan) {
|
|
351
|
-
let next = program;
|
|
352
|
-
const byId = new Map(groups.map(group => [group.id, group]));
|
|
353
|
-
for (const placement of plan.placements) {
|
|
354
|
-
const group = byId.get(placement.id);
|
|
355
|
-
if (group.titleId !== undefined) {
|
|
356
|
-
next = translateGraphic(next, group.titleId, placement.dx, placement.titleDy);
|
|
357
|
-
}
|
|
358
|
-
for (const id of group.contentIds) {
|
|
359
|
-
next = translateGraphic(next, id, placement.dx, placement.contentDy);
|
|
360
|
-
}
|
|
361
|
-
if (placement.background !== undefined) {
|
|
362
|
-
for (const property of ["x", "y", "width", "height"]) {
|
|
363
|
-
next = next.editGraphics({ target: placement.background.id, property,
|
|
364
|
-
value: placement.background[property] });
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (plan.background !== undefined) {
|
|
369
|
-
for (const property of ["x", "y", "width", "height"]) {
|
|
370
|
-
next = next.editGraphics({ target: plan.background.id, property,
|
|
371
|
-
value: plan.background[property] });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return next;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export const rematerializeSideLegendLane = /* @__PURE__ */ action(
|
|
378
|
-
{
|
|
379
|
-
op: "rematerializeSideLegendLane",
|
|
380
|
-
description: "Align and stack every right or left legend block."
|
|
381
|
-
},
|
|
382
|
-
function (args = {}) {
|
|
383
|
-
noOptions(args, "rematerializeSideLegendLane");
|
|
384
|
-
const plot = resolveGraphicBounds(this);
|
|
385
|
-
const canvas = findCanvasGraphic(this)?.properties;
|
|
386
|
-
if (plot === undefined || canvas === undefined) {
|
|
387
|
-
throw new Error("Legend lane requires resolved Canvas and plot bounds.");
|
|
388
|
-
}
|
|
389
|
-
const configs = this.guideConfigs.legend ?? {};
|
|
390
|
-
const plans = ["right", "left"].flatMap(side => {
|
|
391
|
-
const entries = edgeKinds(this, side);
|
|
392
|
-
if (entries.length < 2) return [];
|
|
393
|
-
const blocks = entries.map(([kind, config]) =>
|
|
394
|
-
blockDescriptor(this, kind, config)
|
|
395
|
-
);
|
|
396
|
-
const plan = resolveSideLegendLane({
|
|
397
|
-
side,
|
|
398
|
-
plot,
|
|
399
|
-
canvas,
|
|
400
|
-
groups: groupBlocks(blocks, configs)
|
|
401
|
-
});
|
|
402
|
-
return [{ plan, blocks }];
|
|
403
|
-
});
|
|
404
|
-
let next = this;
|
|
405
|
-
for (const { plan, blocks } of plans) {
|
|
406
|
-
const byId = new Map(blocks.map(block => [block.id, block]));
|
|
407
|
-
for (const placement of plan.placements) {
|
|
408
|
-
const block = byId.get(placement.id);
|
|
409
|
-
if (block.titleId !== undefined) {
|
|
410
|
-
next = translateGraphic(
|
|
411
|
-
next,
|
|
412
|
-
block.titleId,
|
|
413
|
-
placement.titleDx,
|
|
414
|
-
placement.dy
|
|
415
|
-
).editGraphics({
|
|
416
|
-
target: block.titleId,
|
|
417
|
-
property: "textAlign",
|
|
418
|
-
value: "left"
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
for (const id of block.symbolIds) {
|
|
422
|
-
next = translateGraphic(
|
|
423
|
-
next,
|
|
424
|
-
id,
|
|
425
|
-
placement.symbolDx,
|
|
426
|
-
placement.dy
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
next = translateGraphic(
|
|
430
|
-
next,
|
|
431
|
-
block.labelId,
|
|
432
|
-
placement.labelDx,
|
|
433
|
-
placement.dy
|
|
434
|
-
).editGraphics({
|
|
435
|
-
target: block.labelId,
|
|
436
|
-
property: "textAlign",
|
|
437
|
-
value: "left"
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
for (const block of blocks) {
|
|
441
|
-
if (block.backgroundId === undefined || plan.backgrounds.some(item => item.id === block.backgroundId)) continue;
|
|
442
|
-
const bounds = unionLegendBounds(next, block.foregroundIds);
|
|
443
|
-
const background = resolveLegendBackgroundFromBounds([bounds], block.border, canvas, "Legend");
|
|
444
|
-
next = editLegendBackground(next, block.backgroundId, background, block.border);
|
|
445
|
-
}
|
|
446
|
-
for (const background of plan.backgrounds) {
|
|
447
|
-
for (const property of ["x", "y", "width", "height"]) {
|
|
448
|
-
next = next.editGraphics({
|
|
449
|
-
target: background.id,
|
|
450
|
-
property,
|
|
451
|
-
value: background[property]
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return next;
|
|
457
|
-
}
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
export const rematerializeHorizontalLegendLane = /* @__PURE__ */ action(
|
|
461
|
-
{
|
|
462
|
-
op: "rematerializeHorizontalLegendLane",
|
|
463
|
-
description: "Pack and align every top or bottom legend block."
|
|
464
|
-
},
|
|
465
|
-
function (args = {}) {
|
|
466
|
-
noOptions(args, "rematerializeHorizontalLegendLane");
|
|
467
|
-
const plot = resolveGraphicBounds(this);
|
|
468
|
-
const canvas = findCanvasGraphic(this)?.properties;
|
|
469
|
-
if (plot === undefined || canvas === undefined) {
|
|
470
|
-
throw new Error("Legend lane requires resolved Canvas and plot bounds.");
|
|
471
|
-
}
|
|
472
|
-
const configs = this.guideConfigs.legend ?? {};
|
|
473
|
-
let next = this;
|
|
474
|
-
for (const edge of ["top", "bottom"]) {
|
|
475
|
-
const entries = edgeKinds(next, edge).filter(
|
|
476
|
-
([, config]) => isHorizontalEdgeLegend({ ...config, position: edge })
|
|
477
|
-
);
|
|
478
|
-
if (entries.length === 0) continue;
|
|
479
|
-
if (entries.length === 1) {
|
|
480
|
-
const [kind, config] = entries[0];
|
|
481
|
-
const ids = existingIds(next, kind);
|
|
482
|
-
const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids, next.materializationConfigs.textMetrics);
|
|
483
|
-
const { dx, dy } = resolveSingleHorizontalLegendPlacement({
|
|
484
|
-
plot, canvas, config, bounds
|
|
485
|
-
});
|
|
486
|
-
for (const id of ids) next = translateGraphic(next, id, dx, dy);
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
let groups = groupBlocks(entries.map(([kind, config]) =>
|
|
490
|
-
blockDescriptor(next, kind, config)), configs);
|
|
491
|
-
for (const group of groups.filter(group => group.blocks.length > 1)) {
|
|
492
|
-
const config = configs[group.blocks[0].kind];
|
|
493
|
-
const children = horizontalGroups(next, group.blocks.map((block, index) => ({
|
|
494
|
-
id: block.id, blocks: [block], border: index === 0 ? false : block.border,
|
|
495
|
-
backgroundId: index === 0 ? undefined : block.backgroundId
|
|
496
|
-
}))).map(child => ({ ...child, element: child.content }));
|
|
497
|
-
const plan = resolveHorizontalLegendGroup({ edge, plot, canvas, groups: children,
|
|
498
|
-
align: config.align, offset: config.offset, border: group.border,
|
|
499
|
-
backgroundId: group.backgroundId });
|
|
500
|
-
next = applyHorizontalPlan(next, children, plan);
|
|
501
|
-
}
|
|
502
|
-
if (groups.length < 2) continue;
|
|
503
|
-
groups = groupBlocks(entries.map(([kind, config]) =>
|
|
504
|
-
blockDescriptor(next, kind, config)), configs);
|
|
505
|
-
const horizontal = horizontalGroups(next, groups);
|
|
506
|
-
const plan = resolveHorizontalLegendLane({
|
|
507
|
-
edge,
|
|
508
|
-
plot,
|
|
509
|
-
canvas,
|
|
510
|
-
groups: horizontal
|
|
511
|
-
});
|
|
512
|
-
next = applyHorizontalPlan(next, horizontal, plan);
|
|
513
|
-
}
|
|
514
|
-
return next;
|
|
515
|
-
}
|
|
516
|
-
);
|
|
1
|
+
import{translateGraphic as m}from"../../../materialization/graphicTranslation.js";import{editLegendBorderBounds as v}from"../../../materialization/legendBorder.js";import{resolveLegendAxisOffset as I,effectiveLegendOffsetConfig as F}from"../../../materialization/guides/resources.js";import{resolveLegendGroupPlacement as A}from"../../../layout/legendGroup.js";import{localLegendPosition as z,localLegendPlacement as $,legendGroup as R}from"../../../materialization/legendGroups.js";import{editLegendBackground as T,resolveLegendBackgroundFromBounds as K}from"./continuous/common.js";import{action as G}from"../../../core/action.js";import{noOptions as S}from"../../../core/validation.js";import{resolveConcreteGraphicBounds as M,unionConcreteGraphicBounds as H}from"../../../grammar/schemas/graphicBounds.js";import{finiteMidpoint as X}from"../../../grammar/numeric.js";import{resolveGraphicBounds as C}from"../../../layout/canvas.js";import{resolveHorizontalLegendLane as Y,resolveHorizontalLegendGroup as _,resolveSingleHorizontalLegendPlacement as W,isHorizontalEdgeLegend as D,resolveSideLegendLane as J,resolveSingleSideLegendPlacement as N}from"../../../layout/legendLane.js";import{findCanvasGraphic as w}from"../../../materialization/graphicHierarchy.js";import{legendResourcePolicy as Q}from"../../../materialization/guides/resources.js";const y=Object.freeze({colorSize:0,series:0,color:0,stroke:0,gradient:0,interval:0,strokeGradient:0,strokeInterval:0,size:1,opacity:2,strokeWidth:3});function U(r,n){return["series","color","stroke"].map(t=>r.guideConfigs.legend?.[t]).find(t=>t?.target===n)}function V(r,n,t){return n==="size"&&t.inheritPosition!==!1?U(r,t.target)?.position??t.position:t.position}function Z(r){return r.offset}function h(r,n){return Q(n).graphicIds.filter(t=>r.graphicSpec.objects[t]!==void 0)}function ee(r,n){const t=h(r,n),i=t.find(a=>a.endsWith("Title")),o=t.find(a=>a.endsWith("Labels")),e=t.find(a=>a.endsWith("Background")),s=t.filter(a=>a!==i&&a!==o&&a!==e);return{titleId:i,labelId:o,backgroundId:e,symbolIds:s}}function te(r,n){let t=M(r.graphicSpec,n,r.materializationConfigs.textMetrics);const i=r.graphicSpec.objects[n];if(!n.includes("LegendSymbol")||i?.type!=="line")return t;const o=i.items?.map(e=>e.properties)??[i.properties];for(const e of o){if(Object.hasOwn(e,"lineCap")||typeof e.stroke!="string"||!(e.strokeWidth>0))continue;const s=e.strokeWidth/2;t={left:Math.min(t.left,Math.min(e.x1,e.x2)-s),right:Math.max(t.right,Math.max(e.x1,e.x2)+s),top:Math.min(t.top,Math.min(e.y1,e.y2)-s),bottom:Math.max(t.bottom,Math.max(e.y1,e.y2)+s)}}return t}function b(r,n){return n.map(t=>te(r,t)).filter(t=>t!==void 0).reduce((t,i)=>t===void 0?i:{left:Math.min(t.left,i.left),right:Math.max(t.right,i.right),top:Math.min(t.top,i.top),bottom:Math.max(t.bottom,i.bottom)},void 0)}function ne(r,n){const t=r.graphicSpec.objects[n];return(t.items?.[0]?.properties??t.properties).x}function B(r,n,t){const i=ee(r,n);if(i.labelId===void 0||i.symbolIds.length===0)throw new Error(`Legend lane requires complete ${n} graphics.`);const o=[...i.symbolIds,i.labelId,...i.titleId===void 0?[]:[i.titleId]],e=b(r,o),s=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"?[n==="gradient"?"colorGradientStrips":"strokeGradientStrips"]:i.symbolIds,a=b(r,s),f=b(r,i.symbolIds),u=M(r.graphicSpec,i.labelId,r.materializationConfigs.textMetrics),c=r.graphicSpec.objects[i.labelId].items?.length===0,d=i.titleId===void 0?void 0:(()=>{const g=r.graphicSpec.objects[i.titleId],p=M(r.graphicSpec,i.titleId,r.materializationConfigs.textMetrics);return{x:g.properties.x,y:g.properties.y,fontSize:g.properties.fontSize,width:p.right-p.left,bounds:p}})();if([e,a,f].includes(void 0)||u===void 0&&!c)throw new Error(`Legend lane could not measure ${n} graphics.`);const l=["gradient","strokeGradient"].includes(n)&&t.representation!=="symbols"&&(t.gradientDirection==="horizontal"||t.titlePosition==="bottom");return{id:n,kind:n,preserveAlignment:l,preserveGrid:l||(["series","color","stroke"].includes(n)||t.representation==="symbols")&&(t.columns??1)>1,target:t.target,border:O(n,t),offset:Z(t),bounds:e,title:d,symbol:{centerX:X(a.left,a.right),left:f.left,right:f.right,bounds:a},labels:{x:c?f.right+t.labels.offset:ne(r,i.labelId),width:c?0:u.right-u.left},occupiedBounds:b(r,[...o,...i.backgroundId===void 0?[]:[i.backgroundId]]),...i,foregroundIds:o}}function O(r,n){return["series","color","stroke","gradient","strokeGradient","opacity","interval","strokeInterval","strokeWidth","size","colorSize"].includes(r)?n.border:!1}function j(r,n){const t=[];for(let i=0;i<r.length;i+=1){const o=r[i],e=n[o.kind],s=r[i+1];if(["series","color","stroke"].includes(o.kind)&&s?.kind==="size"&&s.target===o.target){t.push({id:`${o.kind}+size`,blocks:[o,s.backgroundId===void 0?s:{...s,bounds:s.occupiedBounds,inset:s.border.padding+s.border.lineWidth/2}],border:O(o.kind,e),backgroundId:o.backgroundId}),i+=1;continue}t.push({id:o.kind,blocks:[o],border:O(o.kind,e),backgroundId:o.backgroundId})}return t}function x(r,n){const t=r.guideConfigs.legend??{},i=new Map(r.semanticSpec.layers.map((o,e)=>[o.id,e]));return Object.entries(t).filter(([o,e])=>z(e)===void 0&&R(r,e.target)===void 0&&V(r,o,e)===n).sort(([o,e],[s,a])=>i.get(e.target)-i.get(a.target)||y[o]-y[s])}function oe(r){return["right","left"].some(n=>x(r,n).length>0)}function ie(r){return["top","bottom"].some(n=>x(r,n).some(([,t])=>D({...t,position:n})))}function Ie(r){return Object.values(r.guideConfigs.legend??{}).some(n=>z(n)!==void 0)||oe(r)||ie(r)}function P(r,n){const t=o=>o.textBaseline==="middle"?{...o,y:o.y-o.fontSize/2,textBaseline:"top"}:o;let i=r;for(const o of n){const e=i.graphicSpec.objects[o];if(e.type==="text")if(e.items!==void 0){if(!e.items.some(s=>s.properties.textBaseline==="middle"))continue;i=i.editGraphics({target:o,property:"items",value:e.items.map(s=>({type:s.type??e.type,properties:t(s.properties)}))})}else e.properties.textBaseline==="middle"&&(i=i.editGraphics({target:o,property:"y",value:t(e.properties).y}).editGraphics({target:o,property:"textBaseline",value:"top"}))}return i}function E(r,n){return n.map(t=>{const i=t.blocks[0],o=r.guideConfigs.legend?.[i.kind],e=t.blocks.length>1,s=e?void 0:i.titleId,a=t.blocks.flatMap(l=>l.foregroundIds).filter(l=>l!==s);e&&a.push(...t.blocks.slice(1).flatMap(l=>l.backgroundId===void 0?[]:[l.backgroundId]));const f=b(r,a),u=b(r,[...a,...s===void 0?[]:[s]]);if(f===void 0||u===void 0)throw new Error(`Legend lane could not measure ${t.id} content.`);const c=t.border,d=c===!1?0:c.padding+c.lineWidth/2;return{id:t.id,atomic:e,titleId:s,contentIds:a,title:e?void 0:i.title,inline:o?.titlePosition==="left",element:e?u:i.symbol.bounds,content:f,horizontal:{left:u.left,right:u.right,top:u.top,bottom:u.bottom},inset:d,padding:c===!1?0:c.padding,backgroundId:t.backgroundId}})}function q(r,n,t){let i=r;const o=new Map(n.map(e=>[e.id,e]));for(const e of t.placements){const s=o.get(e.id);s.titleId!==void 0&&(i=m(i,s.titleId,e.dx,e.titleDy));for(const a of s.contentIds)i=m(i,a,e.dx,e.contentDy);e.background!==void 0&&(i=v(i,e.background.id,e.background))}return t.background!==void 0&&(i=v(i,t.background.id,t.background)),i}const ye=G({op:"rematerializeSideLegendLane",description:"Align and stack every right or left legend block."},function(r={}){if(S(r,"rematerializeSideLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{},o=["right","left"].flatMap(s=>{const a=x(this,s);if(a.length===0)return[];if(a.length===1)return[{side:s,entries:a}];const f=a.map(([c,d])=>B(this,c,d));return[{plan:J({side:s,plot:n,canvas:t,groups:j(f,i)}),blocks:f,side:s,entries:a}]});let e=this;for(const{plan:s,blocks:a,side:f,entries:u}of o){if(s===void 0)continue;const c=new Map(a.map(d=>[d.id,d]));for(const d of s.placements){const l=c.get(d.id);l.titleId!==void 0&&(e=m(e,l.titleId,d.titleDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.titleId,property:"textAlign",value:"left"})));for(const g of l.symbolIds)e=m(e,g,d.symbolDx,d.dy);e=m(e,l.labelId,d.labelDx,d.dy),l.preserveAlignment||(e=e.editGraphics({target:l.labelId,property:"textAlign",value:"left"}))}for(const d of a){if(d.backgroundId===void 0||s.backgrounds.some(p=>p.id===d.backgroundId))continue;const l=b(e,d.foregroundIds),g=K([l],d.border,t,"Legend");e=T(e,d.backgroundId,g,d.border)}for(const d of s.backgrounds)e=v(e,d.id,d)}for(const{side:s,entries:a}of o){const f=a.flatMap(([d])=>h(e,d));e=P(e,f);const u=b(e,f),{dy:c}=N({side:s,plot:n,canvas:t,bounds:u});for(const d of f)e=m(e,d,0,c)}return e}),xe=G({op:"rematerializeHorizontalLegendLane",description:"Pack and align every top or bottom legend block."},function(r={}){if(S(r,"rematerializeHorizontalLegendLane"),this.context.facetLegendIntrinsic===!0)return this;const n=C(this),t=w(this)?.properties;if(n===void 0||t===void 0)throw new Error("Legend lane requires resolved Canvas and plot bounds.");const i=this.guideConfigs.legend??{};let o=this;for(const e of["top","bottom"]){const s=x(o,e).filter(([,c])=>D({...c,position:e})).map(([c,d])=>[c,F(o,c,d)]);if(s.length===0)continue;if(s.length===1){const[c,d]=s[0],l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k);continue}for(const[c,d]of s){if(d.inferredOffset!==!0||I(o,d,n)===d.offset)continue;const l=h(o,c),g=H(o.graphicSpec,l,o.materializationConfigs.textMetrics),{dx:p,dy:k}=W({plot:n,canvas:t,config:{...d,offset:I(o,d,n)},bounds:g});for(const L of l)o=m(o,L,p,k)}let a=j(s.map(([c,d])=>B(o,c,d)),i);for(const c of a.filter(d=>d.blocks.length>1)){const d=i[c.blocks[0].kind],l=E(o,c.blocks.map((p,k)=>({id:p.id,blocks:[p],border:k===0?!1:p.border,backgroundId:k===0?void 0:p.backgroundId}))).map(p=>({...p,element:p.content})),g=_({edge:e,plot:n,canvas:t,groups:l,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0,align:d.align,offset:I(o,d,n),border:c.border,backgroundId:c.backgroundId});o=q(o,l,g)}if(a.length<2)continue;a=j(s.map(([c,d])=>B(o,c,d)),i);const f=E(o,a),u=Y({edge:e,plot:n,canvas:t,groups:f,allowOverflow:o.materializationConfigs.canvas?.plot!==void 0});o=q(o,f,u)}return o}),Le=G({op:"rematerializeLegendGroups",description:"Place each opted-in target legend group from occupied bounds."},function(r={}){if(S(r,"rematerializeLegendGroups"),this.context.facetLegendIntrinsic===!0)return this;let n=this;const t=C(n),i=w(n)?.properties;for(const[o,e]of Object.entries(n.guideConfigs.legendGroups??{})){const s=Object.entries(n.guideConfigs.legend??{}).filter(([,c])=>c.target===o&&z(c)===void 0).sort(([c],[d])=>y[c]-y[d]);n=P(n,s.flatMap(([c])=>h(n,c)));const a=s.map(([c])=>({id:c,ids:h(n,c),bounds:b(n,h(n,c))})),f=e.placement.at!==void 0?e:{...e,placement:{...e.placement,offset:I(n,{...e.placement,inferredOffset:e.inferredOffset},t)}},u=A({blocks:a,group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of u){const d=a.find(l=>l.id===c.id);for(const l of d.ids)n=m(n,l,c.dx,c.dy)}}for(const[o,e]of Object.entries(n.guideConfigs.legend??{})){const s=$(e);if(s===void 0)continue;const a=h(n,o);n=P(n,a);const f={placement:{...s,offset:I(n,s,t)},blockLayout:"column"},[u]=A({blocks:[{id:o,bounds:b(n,a)}],group:f,plot:t,canvas:i,allowOverflow:n.materializationConfigs.canvas?.plot!==void 0});for(const c of a)n=m(n,c,u.dx,u.dy)}return n});export{ie as hasHorizontalLegendLane,Ie as hasLegendLane,oe as hasSideLegendLane,xe as rematerializeHorizontalLegendLane,Le as rematerializeLegendGroups,ye as rematerializeSideLegendLane};
|
|
@@ -46,8 +46,9 @@ export function resolveCategoricalLegendRevision(program, kind, previous, channe
|
|
|
46
46
|
const layer = findLayer(program, previous.target);
|
|
47
47
|
const definition = resolveDefinition(program, layer, channels,
|
|
48
48
|
previous.inferredTitle ? undefined : previous.title, order);
|
|
49
|
+
const {combine: _combine, sizeAreas: _areas, ...shared} = previous;
|
|
49
50
|
const config = {
|
|
50
|
-
...
|
|
51
|
+
...shared,
|
|
51
52
|
...definition,
|
|
52
53
|
symbol: normalizeRecipe(resolveLegendSymbol(
|
|
53
54
|
program,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { editFacetParentLegend } from "../../../materialization/facetGuides/edit.js";
|
|
1
2
|
import { closedAction } from "../../../core/action.js";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -15,6 +16,7 @@ const OPTIONS = Object.freeze(["target", "channels"]);
|
|
|
15
16
|
function legendKindChannels(kind, config) {
|
|
16
17
|
if (["series", "color", "stroke"].includes(kind)) return config.channels;
|
|
17
18
|
return {
|
|
19
|
+
colorSize: ["color","size"],
|
|
18
20
|
size: ["size"],
|
|
19
21
|
gradient: ["color"],
|
|
20
22
|
interval: ["color"],
|
|
@@ -38,7 +40,7 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
38
40
|
if (overlap.length === 0) continue;
|
|
39
41
|
kinds.push(kind);
|
|
40
42
|
for (const channel of overlap) matched.add(channel);
|
|
41
|
-
if (overlap.length !== owned.length) {
|
|
43
|
+
if (overlap.length !== owned.length && !["size-shape","color-size"].includes(config.combine)) {
|
|
42
44
|
partial = { kind, config, channels: owned.filter(channel => !requested.has(channel)) };
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -62,14 +64,15 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
62
64
|
export function removeOwnedColorLegends(program, target) {
|
|
63
65
|
const kinds = Object.entries(program.guideConfigs.legend ?? {})
|
|
64
66
|
.filter(([kind, config]) => config?.target === target && (
|
|
65
|
-
config.channels?.includes("color") || ["gradient", "interval"].includes(kind)
|
|
67
|
+
config.channels?.includes("color") || ["gradient", "interval", "colorSize"].includes(kind)
|
|
66
68
|
)).map(([kind]) => kind);
|
|
67
69
|
return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export const removeLegend = /* @__PURE__ */ closedAction(
|
|
71
|
-
{ op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." }, OPTIONS,
|
|
73
|
+
{ op: "removeLegend", description: "Remove selected legend content or every block owned by one mark.", scope: "any" }, OPTIONS,
|
|
72
74
|
function (args = {}) {
|
|
75
|
+
if (this.compositionSpec !== undefined) return editFacetParentLegend(this, "removeLegend", args);
|
|
73
76
|
const target = resolveLegendTarget(this, args.target, "removeLegend");
|
|
74
77
|
const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
75
78
|
.filter(([, config]) => config?.target === target)
|
|
@@ -81,6 +84,9 @@ export const removeLegend = /* @__PURE__ */ closedAction(
|
|
|
81
84
|
if (revision !== undefined) {
|
|
82
85
|
next = createCategoricalLegendFromConfig(next, revision.config, revision.order);
|
|
83
86
|
}
|
|
87
|
+
if (!Object.values(next.guideConfigs.legend ?? {}).some(config => config.target === target)) {
|
|
88
|
+
next = next._withoutMaterializationConfig(["guides", "legendGroups", target]);
|
|
89
|
+
}
|
|
84
90
|
const retainedKinds = Object.keys(next.guideConfigs.legend ?? {});
|
|
85
91
|
if (args.channels === undefined) {
|
|
86
92
|
return retainedKinds.length === 0
|
|
@@ -133,7 +133,7 @@ export function resolveLegendSampleValues(config, scale, label = "Legend") {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
export function hasExactLegendSamplingForScale(program, scaleId) {
|
|
136
|
-
return ["size", "opacity", "strokeWidth"].some(kind => {
|
|
136
|
+
return ["size", "opacity", "strokeWidth", "gradient"].some(kind => {
|
|
137
137
|
const config = program.guideConfigs.legend?.[kind];
|
|
138
138
|
return config?.scale === scaleId && readLegendSampling(config).mode === "values";
|
|
139
139
|
});
|