ggaction 0.0.12 → 0.0.15
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 +127 -0
- package/README.md +20 -11
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -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 +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +50 -25
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -25,8 +25,9 @@ function validateOptions(args) {
|
|
|
25
25
|
validateGuideOption(args.legend, "createGuides legend");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function inferAxesOptions(program, applicability) {
|
|
29
|
-
const horizontalInterval =
|
|
28
|
+
function inferAxesOptions(program, applicability, layers) {
|
|
29
|
+
const horizontalInterval = layers.some(layer =>
|
|
30
|
+
program.markConfigs[layer.id]?.boxPlot?.orientation === "horizontal" ||
|
|
30
31
|
layer.mark?.type === "rule" &&
|
|
31
32
|
layer.encoding?.x?.fieldType === "quantitative" &&
|
|
32
33
|
layer.encoding?.x2?.fieldType === "quantitative" &&
|
|
@@ -36,11 +37,11 @@ function inferAxesOptions(program, applicability) {
|
|
|
36
37
|
? { x: { ticksAndLabels: { count: 7 } } }
|
|
37
38
|
: {};
|
|
38
39
|
const directions = applicability.axes.directions;
|
|
39
|
-
const hasStoredY =
|
|
40
|
+
const hasStoredY = layers.some(
|
|
40
41
|
layer => layer.encoding?.y !== undefined
|
|
41
42
|
);
|
|
42
43
|
if (!directions.y && hasStoredY) inferred.y = false;
|
|
43
|
-
const horizonLayers =
|
|
44
|
+
const horizonLayers = layers.filter(layer =>
|
|
44
45
|
findUpstreamTransform(
|
|
45
46
|
program,
|
|
46
47
|
findDataset(program, layer.data),
|
|
@@ -83,47 +84,69 @@ function mergeInferredOptions(inferred, explicit) {
|
|
|
83
84
|
? mergeInferredOptions(inferred[key], value)
|
|
84
85
|
: value;
|
|
85
86
|
}
|
|
87
|
+
if (Object.hasOwn(explicit, "count")) delete merged.values;
|
|
88
|
+
if (Object.hasOwn(explicit, "values")) delete merged.count;
|
|
86
89
|
return merged;
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
function hasStoredGuideCollection(program, collection) {
|
|
93
|
+
return [
|
|
94
|
+
program.semanticSpec.guides?.[collection],
|
|
95
|
+
program.guideConfigs?.[collection]
|
|
96
|
+
].some(value => value !== undefined && Object.keys(value).length > 0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveGuideOptions(program, args = {}, layers = program.semanticSpec.layers) {
|
|
100
|
+
validateOptions(args);
|
|
101
|
+
const applicability = resolveGuideApplicability(program, layers);
|
|
102
|
+
const hasAxes = applicability.axes.cartesian || applicability.axes.polar ||
|
|
103
|
+
applicability.axes.parallel;
|
|
104
|
+
const inferredAxes = applicability.axes.cartesian
|
|
105
|
+
? inferAxesOptions(program, applicability, layers)
|
|
106
|
+
: {};
|
|
107
|
+
const axes = args.axes === undefined && applicability.axes.cartesian
|
|
108
|
+
? inferredAxes
|
|
109
|
+
: args.axes !== undefined && args.axes !== false && hasAxes
|
|
110
|
+
? mergeInferredOptions(inferredAxes, args.axes)
|
|
111
|
+
: selectOption(args.axes, hasAxes);
|
|
112
|
+
const grid = args.grid === undefined &&
|
|
113
|
+
(applicability.grid.cartesian || applicability.grid.polar)
|
|
114
|
+
? resolveAutomaticGridOptions(program, layers)
|
|
115
|
+
: selectOption(
|
|
116
|
+
args.grid,
|
|
117
|
+
applicability.grid.cartesian || applicability.grid.polar
|
|
118
|
+
);
|
|
119
|
+
const legend = selectOption(
|
|
120
|
+
args.legend,
|
|
121
|
+
applicability.legend
|
|
122
|
+
);
|
|
123
|
+
return { axes, grid, legend };
|
|
124
|
+
}
|
|
125
|
+
|
|
89
126
|
const createGuides = action(
|
|
90
127
|
{
|
|
91
128
|
op: "createGuides",
|
|
92
129
|
description: "Create applicable axes, grid, and legend."
|
|
93
130
|
},
|
|
94
131
|
function (args = {}) {
|
|
95
|
-
|
|
96
|
-
const applicability = resolveGuideApplicability(this);
|
|
97
|
-
const hasAxes = applicability.axes.cartesian || applicability.axes.polar ||
|
|
98
|
-
applicability.axes.parallel;
|
|
99
|
-
const inferredAxes = applicability.axes.cartesian
|
|
100
|
-
? inferAxesOptions(this, applicability)
|
|
101
|
-
: {};
|
|
102
|
-
const axes = args.axes === undefined && applicability.axes.cartesian
|
|
103
|
-
? inferredAxes
|
|
104
|
-
: args.axes !== undefined && args.axes !== false && hasAxes
|
|
105
|
-
? mergeInferredOptions(inferredAxes, args.axes)
|
|
106
|
-
: selectOption(args.axes, hasAxes);
|
|
107
|
-
const grid = args.grid === undefined &&
|
|
108
|
-
(applicability.grid.cartesian || applicability.grid.polar)
|
|
109
|
-
? resolveAutomaticGridOptions(this)
|
|
110
|
-
: selectOption(
|
|
111
|
-
args.grid,
|
|
112
|
-
applicability.grid.cartesian || applicability.grid.polar
|
|
113
|
-
);
|
|
114
|
-
const legend = selectOption(
|
|
115
|
-
args.legend,
|
|
116
|
-
applicability.legend
|
|
117
|
-
);
|
|
118
|
-
|
|
132
|
+
const { axes, grid, legend } = resolveGuideOptions(this, args);
|
|
119
133
|
if (axes === undefined && grid === undefined && legend === undefined) {
|
|
120
134
|
throw new Error("createGuides requires at least one selected guide.");
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
let next = this;
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
if (
|
|
139
|
+
axes !== undefined &&
|
|
140
|
+
!(args.axes === undefined && hasStoredGuideCollection(this, "axis"))
|
|
141
|
+
) next = next.createAxes(axes);
|
|
142
|
+
if (
|
|
143
|
+
grid !== undefined &&
|
|
144
|
+
!(args.grid === undefined && hasStoredGuideCollection(this, "grid"))
|
|
145
|
+
) next = next.createGrid(grid);
|
|
146
|
+
if (
|
|
147
|
+
legend !== undefined &&
|
|
148
|
+
!(args.legend === undefined && hasStoredGuideCollection(this, "legend"))
|
|
149
|
+
) next = next.createLegend(legend);
|
|
127
150
|
return next;
|
|
128
151
|
}
|
|
129
152
|
);
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
|
+
import {
|
|
4
|
+
validateNonEmptyString,
|
|
5
|
+
validateNonNegativeFinite,
|
|
6
|
+
validateOptionObject,
|
|
7
|
+
validatePositiveFinite,
|
|
8
|
+
validateUnitInterval
|
|
9
|
+
} from "../../../core/validation.js";
|
|
10
|
+
import { isNominalValue } from "../../../grammar/scales/fields.js";
|
|
11
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
12
|
+
import { legendResourcePolicy } from "../../../materialization/guides/resources.js";
|
|
13
|
+
import {
|
|
14
|
+
preserveGraphicPlacement,
|
|
15
|
+
resolveLegendGraphicPlacement
|
|
16
|
+
} from "../../../materialization/graphicHierarchy.js";
|
|
17
|
+
import {
|
|
18
|
+
normalizeLegendSampling,
|
|
19
|
+
validateSamplingAgainstScale
|
|
20
|
+
} from "./sampling.js";
|
|
21
|
+
import {
|
|
22
|
+
legendBlockKey,
|
|
23
|
+
resolveLegendBlock
|
|
24
|
+
} from "./target.js";
|
|
25
|
+
import { validateFontWeight } from "./categorical/validation.js";
|
|
26
|
+
import { normalizeDisplayLabelMap } from "../../../grammar/displayLabels.js";
|
|
27
|
+
|
|
28
|
+
const OPTIONS = Object.freeze([
|
|
29
|
+
"target", "channel", "title", "values", "count", "order", "gap", "text", "symbol",
|
|
30
|
+
"labelMap"
|
|
31
|
+
]);
|
|
32
|
+
const TEXT_OPTIONS = Object.freeze(["fontSize", "fontFamily", "fontWeight", "color"]);
|
|
33
|
+
const SYMBOL_OPTIONS = Object.freeze(["size", "fill", "stroke", "strokeWidth", "opacity"]);
|
|
34
|
+
|
|
35
|
+
function validatePatchObject(value, keys, label) {
|
|
36
|
+
if (!isPlainObject(value)) throw new TypeError(`${label} must be a plain object.`);
|
|
37
|
+
validateOptionObject(value, keys, label);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeTextPatch(value) {
|
|
41
|
+
validatePatchObject(value, TEXT_OPTIONS, "editLegendBlock text");
|
|
42
|
+
if (value.fontSize !== undefined) {
|
|
43
|
+
validatePositiveFinite(value.fontSize, "editLegendBlock text.fontSize");
|
|
44
|
+
}
|
|
45
|
+
if (value.fontFamily !== undefined) {
|
|
46
|
+
validateNonEmptyString(value.fontFamily, "editLegendBlock text.fontFamily");
|
|
47
|
+
}
|
|
48
|
+
if (value.fontWeight !== undefined) {
|
|
49
|
+
validateFontWeight(value.fontWeight, "editLegendBlock text.fontWeight");
|
|
50
|
+
}
|
|
51
|
+
if (value.color !== undefined) {
|
|
52
|
+
validateNonEmptyString(value.color, "editLegendBlock text.color");
|
|
53
|
+
}
|
|
54
|
+
return { ...value };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function categoricalSupports(config, property) {
|
|
58
|
+
const types = new Set(config.symbol.layers.map(layer => layer.type));
|
|
59
|
+
if (property === "size") return types.has("point");
|
|
60
|
+
if (property === "fill") return types.has("point") || types.has("swatch");
|
|
61
|
+
return types.size > 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validateSymbolSupport(descriptor, property) {
|
|
65
|
+
const { channels, family, kind, config } = descriptor;
|
|
66
|
+
if (family === "gradient") {
|
|
67
|
+
throw new Error(`editLegendBlock symbol.${property} is unsupported for gradient blocks.`);
|
|
68
|
+
}
|
|
69
|
+
if (family === "categorical") {
|
|
70
|
+
if (property === "fill" && channels.includes("color")) {
|
|
71
|
+
throw new Error("editLegendBlock symbol.fill conflicts with the color mapping.");
|
|
72
|
+
}
|
|
73
|
+
if (property === "stroke" && channels.includes("stroke")) {
|
|
74
|
+
throw new Error("editLegendBlock symbol.stroke conflicts with the stroke mapping.");
|
|
75
|
+
}
|
|
76
|
+
if (!categoricalSupports(config, property)) {
|
|
77
|
+
throw new Error(`editLegendBlock symbol.${property} is unsupported by this categorical recipe.`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (kind === "size" && property === "size") {
|
|
82
|
+
throw new Error("editLegendBlock symbol.size conflicts with the size mapping.");
|
|
83
|
+
}
|
|
84
|
+
if (kind === "opacity" && property === "opacity") {
|
|
85
|
+
throw new Error("editLegendBlock symbol.opacity conflicts with the opacity mapping.");
|
|
86
|
+
}
|
|
87
|
+
if (kind === "strokeWidth" && property === "strokeWidth") {
|
|
88
|
+
throw new Error("editLegendBlock symbol.strokeWidth conflicts with the strokeWidth mapping.");
|
|
89
|
+
}
|
|
90
|
+
if (kind === "strokeWidth" && ["size", "fill"].includes(property)) {
|
|
91
|
+
throw new Error(`editLegendBlock symbol.${property} is unsupported for strokeWidth blocks.`);
|
|
92
|
+
}
|
|
93
|
+
if (kind === "interval" && property === "fill") {
|
|
94
|
+
throw new Error("editLegendBlock symbol.fill conflicts with the color mapping.");
|
|
95
|
+
}
|
|
96
|
+
if (kind === "strokeInterval" && property === "stroke") {
|
|
97
|
+
throw new Error("editLegendBlock symbol.stroke conflicts with the stroke mapping.");
|
|
98
|
+
}
|
|
99
|
+
if (["interval", "strokeInterval"].includes(kind) && property === "size") {
|
|
100
|
+
throw new Error(`editLegendBlock symbol.size is unsupported for ${kind} blocks.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeSymbolPatch(value, descriptor) {
|
|
105
|
+
validatePatchObject(value, SYMBOL_OPTIONS, "editLegendBlock symbol");
|
|
106
|
+
if (value.size !== undefined) {
|
|
107
|
+
validateNonNegativeFinite(value.size, "editLegendBlock symbol.size");
|
|
108
|
+
}
|
|
109
|
+
if (value.fill !== undefined) {
|
|
110
|
+
validateNonEmptyString(value.fill, "editLegendBlock symbol.fill");
|
|
111
|
+
}
|
|
112
|
+
if (value.stroke !== undefined) {
|
|
113
|
+
validateNonEmptyString(value.stroke, "editLegendBlock symbol.stroke");
|
|
114
|
+
}
|
|
115
|
+
if (value.strokeWidth !== undefined) {
|
|
116
|
+
validateNonNegativeFinite(value.strokeWidth, "editLegendBlock symbol.strokeWidth");
|
|
117
|
+
}
|
|
118
|
+
if (value.opacity !== undefined) {
|
|
119
|
+
validateUnitInterval(value.opacity, "editLegendBlock symbol.opacity");
|
|
120
|
+
}
|
|
121
|
+
for (const property of Object.keys(value)) {
|
|
122
|
+
validateSymbolSupport(descriptor, property);
|
|
123
|
+
}
|
|
124
|
+
return { ...value };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function validateLegendBlockOverride(descriptor, override) {
|
|
128
|
+
if (override.symbol !== undefined) normalizeSymbolPatch(override.symbol, descriptor);
|
|
129
|
+
if (override.labelMap !== undefined) {
|
|
130
|
+
if (descriptor.family !== "categorical") {
|
|
131
|
+
throw new Error("editLegendBlock labelMap requires a categorical block.");
|
|
132
|
+
}
|
|
133
|
+
normalizeDisplayLabelMap(override.labelMap, "editLegendBlock labelMap");
|
|
134
|
+
}
|
|
135
|
+
return override;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function exactCategoricalOrder(program, descriptor, value) {
|
|
139
|
+
if (descriptor.family !== "categorical") {
|
|
140
|
+
throw new Error("editLegendBlock order requires a categorical block.");
|
|
141
|
+
}
|
|
142
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
143
|
+
throw new TypeError("editLegendBlock order must be a non-empty array.");
|
|
144
|
+
}
|
|
145
|
+
if (!value.every(isNominalValue)) {
|
|
146
|
+
throw new TypeError("editLegendBlock order must contain categorical values.");
|
|
147
|
+
}
|
|
148
|
+
if (value.some((item, index) =>
|
|
149
|
+
value.slice(0, index).some(previous => Object.is(previous, item)))) {
|
|
150
|
+
throw new Error("editLegendBlock order values must be unique.");
|
|
151
|
+
}
|
|
152
|
+
const domain = program.resolvedScales[descriptor.scaleIds[0]].domain;
|
|
153
|
+
if (domain.length !== value.length ||
|
|
154
|
+
domain.some(item => !value.some(candidate => Object.is(candidate, item)))) {
|
|
155
|
+
throw new Error("editLegendBlock order must be an exact permutation of the current domain.");
|
|
156
|
+
}
|
|
157
|
+
return Object.freeze({ values: Object.freeze([...value]) });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function withoutEmptyNested(entry, key, value) {
|
|
161
|
+
if (Object.keys(value).length > 0) return { ...entry, [key]: value };
|
|
162
|
+
const { [key]: _removed, ...rest } = entry;
|
|
163
|
+
return rest;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function patchLegendBlockOverride(config, key, patch) {
|
|
167
|
+
const previous = config.blockOverrides?.[key] ?? {};
|
|
168
|
+
let entry = { ...previous };
|
|
169
|
+
if (Object.hasOwn(patch, "title")) entry.title = patch.title;
|
|
170
|
+
if (Object.hasOwn(patch, "gap")) entry.gap = patch.gap;
|
|
171
|
+
if (Object.hasOwn(patch, "text")) {
|
|
172
|
+
entry = withoutEmptyNested(entry, "text", patch.text);
|
|
173
|
+
}
|
|
174
|
+
if (Object.hasOwn(patch, "symbol")) {
|
|
175
|
+
entry = withoutEmptyNested(entry, "symbol", patch.symbol);
|
|
176
|
+
}
|
|
177
|
+
if (Object.hasOwn(patch, "labelMap")) entry.labelMap = patch.labelMap;
|
|
178
|
+
if (patch.removeLabelMap === true) delete entry.labelMap;
|
|
179
|
+
const blockOverrides = { ...(config.blockOverrides ?? {}) };
|
|
180
|
+
if (Object.keys(entry).length === 0) delete blockOverrides[key];
|
|
181
|
+
else blockOverrides[key] = entry;
|
|
182
|
+
return Object.keys(blockOverrides).length === 0
|
|
183
|
+
? undefined
|
|
184
|
+
: blockOverrides;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function applyCategoricalSymbol(symbol, patch) {
|
|
188
|
+
return {
|
|
189
|
+
...symbol,
|
|
190
|
+
layers: symbol.layers.map(layer => ({
|
|
191
|
+
...layer,
|
|
192
|
+
...(patch.size !== undefined && layer.type === "point"
|
|
193
|
+
? { size: Math.sqrt(patch.size / Math.PI) }
|
|
194
|
+
: {}),
|
|
195
|
+
...(patch.fill !== undefined && ["point", "swatch"].includes(layer.type)
|
|
196
|
+
? { fill: patch.fill }
|
|
197
|
+
: {}),
|
|
198
|
+
...(patch.stroke !== undefined ? { stroke: patch.stroke } : {}),
|
|
199
|
+
...(patch.strokeWidth !== undefined
|
|
200
|
+
? layer.type === "line"
|
|
201
|
+
? { lineWidth: patch.strokeWidth }
|
|
202
|
+
: { strokeWidth: patch.strokeWidth }
|
|
203
|
+
: {}),
|
|
204
|
+
...(patch.opacity !== undefined ? { opacity: patch.opacity } : {})
|
|
205
|
+
}))
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function resolveEffectiveLegendBlockConfig(program, kind, config) {
|
|
210
|
+
const channels = kind === "series" || kind === "color" || kind === "stroke"
|
|
211
|
+
? config.channels
|
|
212
|
+
: [{ size: "size", opacity: "opacity", strokeWidth: "strokeWidth",
|
|
213
|
+
gradient: "color", interval: "color", strokeGradient: "stroke",
|
|
214
|
+
strokeInterval: "stroke" }[kind]];
|
|
215
|
+
const override = config.blockOverrides?.[legendBlockKey(channels)] ?? {};
|
|
216
|
+
const effective = {
|
|
217
|
+
...config,
|
|
218
|
+
...(override.title === undefined || override.title === ""
|
|
219
|
+
? {}
|
|
220
|
+
: { title: override.title }),
|
|
221
|
+
...(override.title === ""
|
|
222
|
+
? { titleVisible: false }
|
|
223
|
+
: override.title === undefined
|
|
224
|
+
? {}
|
|
225
|
+
: { titleVisible: true }),
|
|
226
|
+
...(override.gap === undefined ? {} : { itemGap: override.gap, blockGap: override.gap }),
|
|
227
|
+
...(override.text === undefined
|
|
228
|
+
? {}
|
|
229
|
+
: { labels: { ...config.labels, ...override.text } }),
|
|
230
|
+
...(Object.hasOwn(override, "labelMap")
|
|
231
|
+
? { labelMap: override.labelMap }
|
|
232
|
+
: {})
|
|
233
|
+
};
|
|
234
|
+
if (override.symbol === undefined) return effective;
|
|
235
|
+
if (["series", "color", "stroke"].includes(kind)) {
|
|
236
|
+
return {
|
|
237
|
+
...effective,
|
|
238
|
+
symbol: applyCategoricalSymbol(config.symbol, override.symbol),
|
|
239
|
+
blockSymbol: override.symbol
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (kind === "opacity") {
|
|
243
|
+
return {
|
|
244
|
+
...effective,
|
|
245
|
+
symbol: {
|
|
246
|
+
...config.symbol,
|
|
247
|
+
...(override.symbol.size === undefined
|
|
248
|
+
? {}
|
|
249
|
+
: { radius: Math.sqrt(override.symbol.size / Math.PI) }),
|
|
250
|
+
...Object.fromEntries(Object.entries(override.symbol)
|
|
251
|
+
.filter(([key]) => ["fill", "stroke", "strokeWidth"].includes(key)))
|
|
252
|
+
},
|
|
253
|
+
blockSymbol: override.symbol
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (["interval", "strokeInterval"].includes(kind)) {
|
|
257
|
+
return {
|
|
258
|
+
...effective,
|
|
259
|
+
symbol: { ...config.symbol, ...override.symbol },
|
|
260
|
+
blockSymbol: override.symbol
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return { ...effective, blockSymbol: override.symbol };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function titleGraphicId(kind) {
|
|
267
|
+
return legendResourcePolicy(kind).graphicIds.find(id => id.endsWith("Title"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function reconcileLegendBlockTitleGraphic(program, kind, visible) {
|
|
271
|
+
const id = titleGraphicId(kind);
|
|
272
|
+
const exists = program.graphicSpec.objects[id] !== undefined;
|
|
273
|
+
if (!visible && exists) return program.editGraphics({ target: id, remove: true });
|
|
274
|
+
if (visible && !exists) {
|
|
275
|
+
const owned = new Set(legendResourcePolicy(kind).graphicIds);
|
|
276
|
+
const anchor = program.graphicSpec.objects.canvas.children
|
|
277
|
+
.filter(childId => childId !== id && owned.has(childId))
|
|
278
|
+
.at(-1);
|
|
279
|
+
if (anchor === undefined) {
|
|
280
|
+
throw new Error(`editLegendBlock cannot place the ${kind} legend title.`);
|
|
281
|
+
}
|
|
282
|
+
return program.createGraphics({
|
|
283
|
+
id,
|
|
284
|
+
type: "text",
|
|
285
|
+
...resolveLegendGraphicPlacement(program, { after: anchor })
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return program;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const SPECIALIZED_SYMBOLS = Object.freeze({
|
|
292
|
+
size: Object.freeze({ id: "sizeLegendSymbols", type: "circle" }),
|
|
293
|
+
opacity: Object.freeze({ id: "opacityLegendSymbols", type: "circle" }),
|
|
294
|
+
strokeWidth: Object.freeze({ id: "strokeWidthLegendSymbols", type: "line" }),
|
|
295
|
+
interval: Object.freeze({ id: "colorLegendSymbols", type: "rect" }),
|
|
296
|
+
strokeInterval: Object.freeze({ id: "strokeIntervalSymbols", type: "rect" })
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Symbol patches replace the complete block-local patch. Recreating the concrete
|
|
300
|
+
// symbol owner removes properties that existed only in the previous patch before
|
|
301
|
+
// the ordinary rematerializer writes the new effective appearance.
|
|
302
|
+
function recreateSymbolGraphics(program, descriptor) {
|
|
303
|
+
if (descriptor.family === "gradient") return program;
|
|
304
|
+
if (descriptor.family === "categorical") {
|
|
305
|
+
const symbolIds = legendResourcePolicy(descriptor.kind).graphicIds
|
|
306
|
+
.filter(id => id.includes("Symbol"));
|
|
307
|
+
let next = program;
|
|
308
|
+
for (const id of symbolIds) {
|
|
309
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
310
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return next.createLegendSymbols({ kind: descriptor.kind });
|
|
314
|
+
}
|
|
315
|
+
const symbol = SPECIALIZED_SYMBOLS[descriptor.kind];
|
|
316
|
+
if (symbol === undefined || program.graphicSpec.objects[symbol.id] === undefined) {
|
|
317
|
+
throw new Error(`editLegendBlock cannot recreate ${descriptor.kind} legend symbols.`);
|
|
318
|
+
}
|
|
319
|
+
const placement = preserveGraphicPlacement(program, symbol.id);
|
|
320
|
+
return program
|
|
321
|
+
.editGraphics({ target: symbol.id, remove: true })
|
|
322
|
+
.createGraphics({ id: symbol.id, type: symbol.type, length: 0, ...placement });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function normalizeBlockPatch(args, descriptor) {
|
|
326
|
+
const patch = {};
|
|
327
|
+
if (Object.hasOwn(args, "title")) {
|
|
328
|
+
if (typeof args.title !== "string") {
|
|
329
|
+
throw new TypeError("editLegendBlock title must be a string.");
|
|
330
|
+
}
|
|
331
|
+
patch.title = args.title;
|
|
332
|
+
}
|
|
333
|
+
if (Object.hasOwn(args, "gap")) {
|
|
334
|
+
validateNonNegativeFinite(args.gap, "editLegendBlock gap");
|
|
335
|
+
patch.gap = args.gap;
|
|
336
|
+
}
|
|
337
|
+
if (Object.hasOwn(args, "text")) patch.text = normalizeTextPatch(args.text);
|
|
338
|
+
if (Object.hasOwn(args, "symbol")) {
|
|
339
|
+
patch.symbol = normalizeSymbolPatch(args.symbol, descriptor);
|
|
340
|
+
}
|
|
341
|
+
if (Object.hasOwn(args, "labelMap")) {
|
|
342
|
+
if (descriptor.family !== "categorical") {
|
|
343
|
+
throw new Error("editLegendBlock labelMap requires a categorical block.");
|
|
344
|
+
}
|
|
345
|
+
if (args.labelMap === "auto") patch.removeLabelMap = true;
|
|
346
|
+
else {
|
|
347
|
+
patch.labelMap = normalizeDisplayLabelMap(
|
|
348
|
+
args.labelMap,
|
|
349
|
+
"editLegendBlock labelMap"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return patch;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export const editLegendBlock = action(
|
|
357
|
+
{
|
|
358
|
+
op: "editLegendBlock",
|
|
359
|
+
description: "Edit one logical legend block selected by channel."
|
|
360
|
+
},
|
|
361
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
362
|
+
validateOptionObject(args, OPTIONS, "editLegendBlock");
|
|
363
|
+
if (!Object.hasOwn(args, "target") || typeof args.target !== "string") {
|
|
364
|
+
throw new TypeError("editLegendBlock target must be an explicit legend target id.");
|
|
365
|
+
}
|
|
366
|
+
if (!Object.hasOwn(args, "channel") || typeof args.channel !== "string") {
|
|
367
|
+
throw new TypeError("editLegendBlock channel must be explicit.");
|
|
368
|
+
}
|
|
369
|
+
if (!Object.keys(args).some(key => !["target", "channel"].includes(key))) {
|
|
370
|
+
throw new Error("editLegendBlock requires at least one change.");
|
|
371
|
+
}
|
|
372
|
+
const descriptor = resolveLegendBlock(this, args, "editLegendBlock");
|
|
373
|
+
if ((Object.hasOwn(args, "values") || Object.hasOwn(args, "count")) &&
|
|
374
|
+
descriptor.family !== "sampled") {
|
|
375
|
+
throw new Error("editLegendBlock values and count require a sampled legend block.");
|
|
376
|
+
}
|
|
377
|
+
const patch = normalizeBlockPatch(args, descriptor);
|
|
378
|
+
let config = descriptor.config;
|
|
379
|
+
const blockOverrides = patchLegendBlockOverride(config, descriptor.key, patch);
|
|
380
|
+
if (blockOverrides === undefined) {
|
|
381
|
+
const { blockOverrides: _removed, ...rest } = config;
|
|
382
|
+
config = rest;
|
|
383
|
+
} else {
|
|
384
|
+
config = { ...config, blockOverrides };
|
|
385
|
+
}
|
|
386
|
+
if (Object.hasOwn(args, "values") || Object.hasOwn(args, "count")) {
|
|
387
|
+
const sampling = normalizeLegendSampling(args, {
|
|
388
|
+
previous: config,
|
|
389
|
+
operation: "edit",
|
|
390
|
+
label: `editLegendBlock ${descriptor.channel ?? args.channel} legend`
|
|
391
|
+
});
|
|
392
|
+
validateSamplingAgainstScale(sampling, this.resolvedScales[descriptor.scaleIds[0]],
|
|
393
|
+
"editLegendBlock values");
|
|
394
|
+
const { count: _legacyCount, ...withoutCount } = config;
|
|
395
|
+
config = { ...withoutCount, sampling };
|
|
396
|
+
}
|
|
397
|
+
const order = Object.hasOwn(args, "order")
|
|
398
|
+
? exactCategoricalOrder(this, descriptor, args.order)
|
|
399
|
+
: undefined;
|
|
400
|
+
if (args.title !== undefined && args.title !== "") {
|
|
401
|
+
config = { ...config, title: args.title, inferredTitle: false };
|
|
402
|
+
}
|
|
403
|
+
let next = this._withLegendConfig(descriptor.kind, config);
|
|
404
|
+
const semanticKind = legendResourcePolicy(descriptor.kind).semanticKind;
|
|
405
|
+
if (order !== undefined) {
|
|
406
|
+
next = next.editSemantic({
|
|
407
|
+
property: `guide.legend.${semanticKind}.order`,
|
|
408
|
+
value: order
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (args.title !== undefined && args.title !== "") {
|
|
412
|
+
next = next.editSemantic({
|
|
413
|
+
property: `guide.legend.${semanticKind}.title`,
|
|
414
|
+
value: args.title
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const effective = resolveEffectiveLegendBlockConfig(next, descriptor.kind,
|
|
418
|
+
next.guideConfigs.legend[descriptor.kind]);
|
|
419
|
+
if (Object.hasOwn(args, "symbol")) {
|
|
420
|
+
next = recreateSymbolGraphics(next, descriptor);
|
|
421
|
+
}
|
|
422
|
+
next = reconcileLegendBlockTitleGraphic(next, descriptor.kind,
|
|
423
|
+
effective.titleVisible !== false);
|
|
424
|
+
return next.rematerializeLegend();
|
|
425
|
+
})
|
|
426
|
+
);
|