ggaction 0.0.13 → 0.0.16
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 +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +341 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +20 -5
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- 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/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
|
@@ -22,15 +22,18 @@ import { legendResourcePolicy } from
|
|
|
22
22
|
const FAMILY_ORDER = Object.freeze({
|
|
23
23
|
series: 0,
|
|
24
24
|
color: 0,
|
|
25
|
+
stroke: 0,
|
|
25
26
|
gradient: 0,
|
|
26
27
|
interval: 0,
|
|
28
|
+
strokeGradient: 0,
|
|
29
|
+
strokeInterval: 0,
|
|
27
30
|
size: 1,
|
|
28
31
|
opacity: 2,
|
|
29
32
|
strokeWidth: 3
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
function categoricalFor(program, target) {
|
|
33
|
-
return ["series", "color"]
|
|
36
|
+
return ["series", "color", "stroke"]
|
|
34
37
|
.map(kind => program.guideConfigs.legend?.[kind])
|
|
35
38
|
.find(config => config?.target === target);
|
|
36
39
|
}
|
|
@@ -63,6 +66,41 @@ function componentIds(program, kind) {
|
|
|
63
66
|
return { titleId, labelId, backgroundId, symbolIds };
|
|
64
67
|
}
|
|
65
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
|
+
|
|
66
104
|
function textAnchor(program, id) {
|
|
67
105
|
const graphic = program.graphicSpec.objects[id];
|
|
68
106
|
const properties = graphic.items?.[0]?.properties ?? graphic.properties;
|
|
@@ -79,22 +117,16 @@ function blockDescriptor(program, kind, config) {
|
|
|
79
117
|
components.labelId,
|
|
80
118
|
...(components.titleId === undefined ? [] : [components.titleId])
|
|
81
119
|
];
|
|
82
|
-
const bounds =
|
|
83
|
-
const symbolAnchorIds =
|
|
84
|
-
? ["colorGradientStrips"]
|
|
120
|
+
const bounds = unionLegendBounds(program, foregroundIds);
|
|
121
|
+
const symbolAnchorIds = ["gradient", "strokeGradient"].includes(kind)
|
|
122
|
+
? [kind === "gradient" ? "colorGradientStrips" : "strokeGradientStrips"]
|
|
85
123
|
: components.symbolIds;
|
|
86
|
-
const symbolAnchor =
|
|
87
|
-
|
|
88
|
-
symbolAnchorIds
|
|
89
|
-
);
|
|
90
|
-
const symbolBounds = unionConcreteGraphicBounds(
|
|
91
|
-
program.graphicSpec,
|
|
92
|
-
components.symbolIds
|
|
93
|
-
);
|
|
124
|
+
const symbolAnchor = unionLegendBounds(program, symbolAnchorIds);
|
|
125
|
+
const symbolBounds = unionLegendBounds(program, components.symbolIds);
|
|
94
126
|
const labels = resolveConcreteGraphicBounds(
|
|
95
127
|
program.graphicSpec,
|
|
96
128
|
components.labelId
|
|
97
|
-
);
|
|
129
|
+
, program.materializationConfigs.textMetrics);
|
|
98
130
|
const title = components.titleId === undefined
|
|
99
131
|
? undefined
|
|
100
132
|
: (() => {
|
|
@@ -102,7 +134,7 @@ function blockDescriptor(program, kind, config) {
|
|
|
102
134
|
const titleBounds = resolveConcreteGraphicBounds(
|
|
103
135
|
program.graphicSpec,
|
|
104
136
|
components.titleId
|
|
105
|
-
);
|
|
137
|
+
, program.materializationConfigs.textMetrics);
|
|
106
138
|
return {
|
|
107
139
|
x: graphic.properties.x,
|
|
108
140
|
y: graphic.properties.y,
|
|
@@ -132,7 +164,7 @@ function blockDescriptor(program, kind, config) {
|
|
|
132
164
|
x: textAnchor(program, components.labelId),
|
|
133
165
|
width: labels.right - labels.left
|
|
134
166
|
},
|
|
135
|
-
occupiedBounds:
|
|
167
|
+
occupiedBounds: unionLegendBounds(program, [
|
|
136
168
|
...foregroundIds,
|
|
137
169
|
...(components.backgroundId === undefined ? [] : [components.backgroundId])
|
|
138
170
|
]),
|
|
@@ -142,7 +174,10 @@ function blockDescriptor(program, kind, config) {
|
|
|
142
174
|
}
|
|
143
175
|
|
|
144
176
|
function borderFor(kind, config) {
|
|
145
|
-
return [
|
|
177
|
+
return [
|
|
178
|
+
"series", "color", "stroke", "gradient", "strokeGradient", "opacity",
|
|
179
|
+
"interval", "strokeInterval", "strokeWidth", "size"
|
|
180
|
+
].includes(kind)
|
|
146
181
|
? config.border
|
|
147
182
|
: false;
|
|
148
183
|
}
|
|
@@ -154,7 +189,7 @@ function groupBlocks(blocks, configs) {
|
|
|
154
189
|
const config = configs[block.kind];
|
|
155
190
|
const next = blocks[index + 1];
|
|
156
191
|
if (
|
|
157
|
-
["series", "color"].includes(block.kind) &&
|
|
192
|
+
["series", "color", "stroke"].includes(block.kind) &&
|
|
158
193
|
next?.kind === "size" && next.target === block.target
|
|
159
194
|
) {
|
|
160
195
|
groups.push({
|
|
@@ -282,9 +317,9 @@ function horizontalGroups(program, groups) {
|
|
|
282
317
|
.filter(id => id !== titleId);
|
|
283
318
|
if (atomic) contentIds.push(...group.blocks.slice(1).flatMap(block =>
|
|
284
319
|
block.backgroundId === undefined ? [] : [block.backgroundId]));
|
|
285
|
-
const content =
|
|
286
|
-
const foreground =
|
|
287
|
-
program
|
|
320
|
+
const content = unionLegendBounds(program, contentIds);
|
|
321
|
+
const foreground = unionLegendBounds(
|
|
322
|
+
program,
|
|
288
323
|
[...contentIds, ...(titleId === undefined ? [] : [titleId])]
|
|
289
324
|
);
|
|
290
325
|
if (content === undefined || foreground === undefined) {
|
|
@@ -339,7 +374,7 @@ function applyHorizontalPlan(program, groups, plan) {
|
|
|
339
374
|
return next;
|
|
340
375
|
}
|
|
341
376
|
|
|
342
|
-
export const rematerializeSideLegendLane = action(
|
|
377
|
+
export const rematerializeSideLegendLane = /* @__PURE__ */ action(
|
|
343
378
|
{
|
|
344
379
|
op: "rematerializeSideLegendLane",
|
|
345
380
|
description: "Align and stack every right or left legend block."
|
|
@@ -404,7 +439,7 @@ export const rematerializeSideLegendLane = action(
|
|
|
404
439
|
}
|
|
405
440
|
for (const block of blocks) {
|
|
406
441
|
if (block.backgroundId === undefined || plan.backgrounds.some(item => item.id === block.backgroundId)) continue;
|
|
407
|
-
const bounds =
|
|
442
|
+
const bounds = unionLegendBounds(next, block.foregroundIds);
|
|
408
443
|
const background = resolveLegendBackgroundFromBounds([bounds], block.border, canvas, "Legend");
|
|
409
444
|
next = editLegendBackground(next, block.backgroundId, background, block.border);
|
|
410
445
|
}
|
|
@@ -422,7 +457,7 @@ export const rematerializeSideLegendLane = action(
|
|
|
422
457
|
}
|
|
423
458
|
);
|
|
424
459
|
|
|
425
|
-
export const rematerializeHorizontalLegendLane = action(
|
|
460
|
+
export const rematerializeHorizontalLegendLane = /* @__PURE__ */ action(
|
|
426
461
|
{
|
|
427
462
|
op: "rematerializeHorizontalLegendLane",
|
|
428
463
|
description: "Pack and align every top or bottom legend block."
|
|
@@ -444,7 +479,7 @@ export const rematerializeHorizontalLegendLane = action(
|
|
|
444
479
|
if (entries.length === 1) {
|
|
445
480
|
const [kind, config] = entries[0];
|
|
446
481
|
const ids = existingIds(next, kind);
|
|
447
|
-
const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids);
|
|
482
|
+
const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids, next.materializationConfigs.textMetrics);
|
|
448
483
|
const { dx, dy } = resolveSingleHorizontalLegendPlacement({
|
|
449
484
|
plot, canvas, config, bounds
|
|
450
485
|
});
|
|
@@ -49,8 +49,13 @@ export function resolveCategoricalLegendRevision(program, kind, previous, channe
|
|
|
49
49
|
const config = {
|
|
50
50
|
...previous,
|
|
51
51
|
...definition,
|
|
52
|
-
symbol: normalizeRecipe(resolveLegendSymbol(
|
|
53
|
-
|
|
52
|
+
symbol: normalizeRecipe(resolveLegendSymbol(
|
|
53
|
+
program,
|
|
54
|
+
layer,
|
|
55
|
+
channels,
|
|
56
|
+
previous.inferredSymbol ? undefined : previous.symbol,
|
|
57
|
+
definition.kind
|
|
58
|
+
), definition.kind, { internal: previous.inferredSymbol })
|
|
54
59
|
};
|
|
55
60
|
if (validateLayout) resolveLayout(program, config);
|
|
56
61
|
return { kind, config, order };
|
|
@@ -76,11 +81,11 @@ export function createCategoricalLegendFromConfig(program, config, order) {
|
|
|
76
81
|
} else {
|
|
77
82
|
next = next
|
|
78
83
|
.editSemantic({
|
|
79
|
-
property:
|
|
84
|
+
property: `guide.legend.${kind}.scale`,
|
|
80
85
|
value: config.scales[0]
|
|
81
86
|
})
|
|
82
87
|
.editSemantic({
|
|
83
|
-
property:
|
|
88
|
+
property: `guide.legend.${kind}.title`,
|
|
84
89
|
value: config.title
|
|
85
90
|
});
|
|
86
91
|
}
|
|
@@ -91,9 +96,15 @@ export function createCategoricalLegendFromConfig(program, config, order) {
|
|
|
91
96
|
});
|
|
92
97
|
}
|
|
93
98
|
next = next._withLegendConfig(kind, config);
|
|
94
|
-
if (config.border !== false)
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
if (config.border !== false) {
|
|
100
|
+
next = next.createLegendBackground({ kind });
|
|
101
|
+
}
|
|
102
|
+
next = next
|
|
103
|
+
.createLegendSymbols({ kind })
|
|
104
|
+
.createLegendLabels({ kind });
|
|
105
|
+
return config.titleVisible === false
|
|
106
|
+
? next
|
|
107
|
+
: next.createLegendTitle({ kind });
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
// Categorical content precedes its sampled companions in both creation and revision.
|
|
@@ -125,7 +136,7 @@ export function reconcileCategoricalSymbols(program, previous, config) {
|
|
|
125
136
|
for (const layer of config.symbol.layers) {
|
|
126
137
|
if (next.graphicSpec.objects[symbolGraphic(config, layer.type)] === undefined) {
|
|
127
138
|
const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[layer.type];
|
|
128
|
-
next = next[`createLegendSymbol${suffix}`]();
|
|
139
|
+
next = next[`createLegendSymbol${suffix}`]({ kind: config.kind });
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
return next;
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { closedAction } from "../../../core/action.js";
|
|
2
|
+
|
|
3
3
|
import {
|
|
4
4
|
removeLegendKinds,
|
|
5
5
|
resolveCategoricalLegendRevision,
|
|
6
6
|
createCategoricalLegendFromConfig
|
|
7
7
|
} from "./lifecycle.js";
|
|
8
8
|
import { resolveLegendTarget, validateLegendChannels } from "./target.js";
|
|
9
|
+
import { planLegendBlockTransitions } from "./transition.js";
|
|
9
10
|
|
|
10
11
|
export { removeLegendKinds } from "./lifecycle.js";
|
|
11
12
|
|
|
12
13
|
const OPTIONS = Object.freeze(["target", "channels"]);
|
|
13
14
|
|
|
14
15
|
function legendKindChannels(kind, config) {
|
|
15
|
-
if (["series", "color"].includes(kind)) return config.channels;
|
|
16
|
+
if (["series", "color", "stroke"].includes(kind)) return config.channels;
|
|
16
17
|
return {
|
|
17
18
|
size: ["size"],
|
|
18
19
|
gradient: ["color"],
|
|
19
20
|
interval: ["color"],
|
|
21
|
+
strokeGradient: ["stroke"],
|
|
22
|
+
strokeInterval: ["stroke"],
|
|
20
23
|
opacity: ["opacity"],
|
|
21
24
|
strokeWidth: ["strokeWidth"]
|
|
22
25
|
}[kind];
|
|
@@ -46,8 +49,13 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
46
49
|
// Remove departing sibling configs from the immutable layout preflight view.
|
|
47
50
|
const view = kinds.reduce((next, kind) =>
|
|
48
51
|
next._withoutMaterializationConfig(["guides", "legend", kind]), program);
|
|
49
|
-
const
|
|
52
|
+
const resolved = partial === undefined ? undefined
|
|
50
53
|
: resolveCategoricalLegendRevision(view, partial.kind, partial.config, partial.channels);
|
|
54
|
+
const revision = resolved === undefined ? undefined : planLegendBlockTransitions(
|
|
55
|
+
program,
|
|
56
|
+
target,
|
|
57
|
+
[{ ...resolved, kind: resolved.config.kind }]
|
|
58
|
+
)[0];
|
|
51
59
|
return { kinds, revision };
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -59,10 +67,9 @@ export function removeOwnedColorLegends(program, target) {
|
|
|
59
67
|
return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
export const removeLegend =
|
|
63
|
-
{ op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." },
|
|
70
|
+
export const removeLegend = /* @__PURE__ */ closedAction(
|
|
71
|
+
{ op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." }, OPTIONS,
|
|
64
72
|
function (args = {}) {
|
|
65
|
-
validateKeys(args, OPTIONS, "removeLegend");
|
|
66
73
|
const target = resolveLegendTarget(this, args.target, "removeLegend");
|
|
67
74
|
const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
68
75
|
.filter(([, config]) => config?.target === target)
|
|
@@ -81,7 +88,9 @@ export const removeLegend = action(
|
|
|
81
88
|
: next.rematerializeLegend();
|
|
82
89
|
}
|
|
83
90
|
const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
|
|
84
|
-
const removedCategorical = kinds.some(kind =>
|
|
91
|
+
const removedCategorical = kinds.some(kind =>
|
|
92
|
+
["series", "color", "stroke"].includes(kind)
|
|
93
|
+
);
|
|
85
94
|
if (
|
|
86
95
|
removedCategorical && revision === undefined &&
|
|
87
96
|
remainingTargetKinds.includes("size") &&
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { sampleNumericRange } from "../../../grammar/numeric.js";
|
|
2
|
+
import {
|
|
3
|
+
validateGeneratedItemLimit
|
|
4
|
+
} from "../../../core/validation.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COUNT = 5;
|
|
7
|
+
const MAX_EXACT_VALUES = 100;
|
|
8
|
+
|
|
9
|
+
function validateCount(value, label) {
|
|
10
|
+
if (!Number.isInteger(value) || value < 2) {
|
|
11
|
+
throw new RangeError(`${label} count must be an integer of at least 2.`);
|
|
12
|
+
}
|
|
13
|
+
validateGeneratedItemLimit(value, `${label} count`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeExactValues(value, label) {
|
|
18
|
+
if (!Array.isArray(value)) {
|
|
19
|
+
throw new TypeError(`${label} values must be an array or "auto".`);
|
|
20
|
+
}
|
|
21
|
+
if (value.length === 0 || value.length > MAX_EXACT_VALUES) {
|
|
22
|
+
throw new RangeError(
|
|
23
|
+
`${label} values must contain between 1 and ${MAX_EXACT_VALUES} values.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const values = [...value];
|
|
27
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
28
|
+
if (!Number.isFinite(values[index])) {
|
|
29
|
+
throw new TypeError(`${label} values must contain only finite numbers.`);
|
|
30
|
+
}
|
|
31
|
+
if (index > 0 && !(values[index] > values[index - 1])) {
|
|
32
|
+
throw new RangeError(
|
|
33
|
+
`${label} values must be unique and strictly increasing.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(values);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readLegendSampling(config, defaultCount = DEFAULT_COUNT) {
|
|
41
|
+
if (config?.sampling?.mode === "values") {
|
|
42
|
+
return {
|
|
43
|
+
mode: "values",
|
|
44
|
+
values: config.sampling.values,
|
|
45
|
+
count: config.sampling.count ?? config.count ?? defaultCount
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
mode: "auto",
|
|
50
|
+
count: config?.sampling?.count ?? config?.count ?? defaultCount
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeLegendSampling(
|
|
55
|
+
patch,
|
|
56
|
+
{ previous, operation = "create", label = "Legend" } = {}
|
|
57
|
+
) {
|
|
58
|
+
const prior = readLegendSampling(previous);
|
|
59
|
+
const hasValues = Object.hasOwn(patch, "values");
|
|
60
|
+
const hasCount = Object.hasOwn(patch, "count");
|
|
61
|
+
const requestedValues = patch.values;
|
|
62
|
+
|
|
63
|
+
if (hasValues && requestedValues !== "auto" && hasCount) {
|
|
64
|
+
throw new Error(`${label} cannot specify both count and exact values.`);
|
|
65
|
+
}
|
|
66
|
+
if (requestedValues === "auto" && operation === "create") {
|
|
67
|
+
throw new Error(`${label} creation uses omission for automatic values.`);
|
|
68
|
+
}
|
|
69
|
+
if (!hasValues && !hasCount) {
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
...prior,
|
|
72
|
+
...(prior.values === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: { values: Object.freeze([...prior.values]) })
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (!hasValues) {
|
|
78
|
+
if (prior.mode === "values") {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${label} count cannot replace exact values; set values to "auto" first.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze({ mode: "auto", count: validateCount(patch.count, label) });
|
|
84
|
+
}
|
|
85
|
+
if (requestedValues === "auto") {
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
mode: "auto",
|
|
88
|
+
count: hasCount ? validateCount(patch.count, label) : validateCount(prior.count, label)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
mode: "values",
|
|
93
|
+
values: normalizeExactValues(requestedValues, label),
|
|
94
|
+
count: validateCount(prior.count, label)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateSamplingAgainstScale(sampling, scale, label = "Legend") {
|
|
99
|
+
if (sampling.mode !== "values") return sampling;
|
|
100
|
+
const domain = scale?.domain;
|
|
101
|
+
if (!Array.isArray(domain) || domain.length < 2 || !domain.every(Number.isFinite)) {
|
|
102
|
+
throw new Error(`${label} exact values require a finite quantitative scale domain.`);
|
|
103
|
+
}
|
|
104
|
+
const lower = Math.min(...domain);
|
|
105
|
+
const upper = Math.max(...domain);
|
|
106
|
+
for (const value of sampling.values) {
|
|
107
|
+
if (scale.type === "log" && value <= 0) {
|
|
108
|
+
throw new RangeError(`${label} exact values must be positive for a log scale.`);
|
|
109
|
+
}
|
|
110
|
+
if (value < lower || value > upper) {
|
|
111
|
+
throw new RangeError(
|
|
112
|
+
`${label} exact value ${value} is outside the scale domain [${lower}, ${upper}].`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return sampling;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function resolveLegendSampleValues(config, scale, label = "Legend") {
|
|
120
|
+
const sampling = validateSamplingAgainstScale(
|
|
121
|
+
readLegendSampling(config),
|
|
122
|
+
scale,
|
|
123
|
+
label
|
|
124
|
+
);
|
|
125
|
+
return sampling.mode === "values"
|
|
126
|
+
? [...sampling.values]
|
|
127
|
+
: sampleNumericRange(
|
|
128
|
+
scale.domain[0],
|
|
129
|
+
scale.domain[1],
|
|
130
|
+
sampling.count,
|
|
131
|
+
`${label} domain`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function hasExactLegendSamplingForScale(program, scaleId) {
|
|
136
|
+
return ["size", "opacity", "strokeWidth"].some(kind => {
|
|
137
|
+
const config = program.guideConfigs.legend?.[kind];
|
|
138
|
+
return config?.scale === scaleId && readLegendSampling(config).mode === "values";
|
|
139
|
+
});
|
|
140
|
+
}
|