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
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
2
|
import { freezeOwned, isPlainObject } from "../../core/immutable.js";
|
|
3
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
4
|
import {
|
|
4
5
|
validateNonEmptyString,
|
|
5
6
|
validateNonNegativeFinite,
|
|
6
7
|
validateOptionObject,
|
|
7
8
|
validatePositiveFinite
|
|
8
9
|
} from "../../core/validation.js";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
resolveFacetDefinition,
|
|
12
|
+
resolveFacetGridDefinition
|
|
13
|
+
} from "../../grammar/facets/index.js";
|
|
14
|
+
import { collectFacetScaleBindings, resolveFacetFamily } from
|
|
15
|
+
"../../grammar/facets/dependencies.js";
|
|
10
16
|
import {
|
|
11
17
|
FACET_SCALE_CHANNELS,
|
|
12
18
|
normalizeFacetScalePolicies
|
|
@@ -14,27 +20,43 @@ import {
|
|
|
14
20
|
import { resolveFacetLayout } from "../../layout/facets.js";
|
|
15
21
|
import { compositionChildDescriptor } from
|
|
16
22
|
"../../materialization/composition.js";
|
|
17
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
|
|
18
23
|
import { deriveFacetChildren } from "./derive.js";
|
|
24
|
+
import { resolveFacetChildrenScales } from "./derive.js";
|
|
19
25
|
import { replayDerivedData } from "./replay.js";
|
|
20
26
|
import { composeFacetGuides } from "./guides.js";
|
|
21
27
|
import { applyCompositionState } from "../composition/actions.js";
|
|
28
|
+
import { replayCompositionThemeState } from "../theme/composition.js";
|
|
29
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
30
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
31
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
32
|
+
import { normalizeDisplayLabelMap } from "../../grammar/displayLabels.js";
|
|
33
|
+
import {
|
|
34
|
+
createDefaultFacetHeaders,
|
|
35
|
+
normalizeFacetHeadersConfig,
|
|
36
|
+
resolveFacetHeaderConfig
|
|
37
|
+
} from "../../materialization/facetHeaders.js";
|
|
22
38
|
|
|
23
39
|
const FACET_OPTIONS = Object.freeze([
|
|
24
|
-
"id", "field", "data", "columns", "gap", "align", "padding", "scales",
|
|
40
|
+
"id", "field", "data", "values", "columns", "gap", "align", "padding", "scales",
|
|
25
41
|
"guides"
|
|
26
42
|
]);
|
|
43
|
+
const FACET_GRID_OPTIONS = Object.freeze([
|
|
44
|
+
"id", "data", "rows", "columns", "combinations", "gap", "align",
|
|
45
|
+
"padding", "scales", "guides"
|
|
46
|
+
]);
|
|
47
|
+
const REPEAT_OPTIONS = Object.freeze([
|
|
48
|
+
"id", "target", "channel", "fields", "columns", "gap", "align",
|
|
49
|
+
"padding", "scales", "guides"
|
|
50
|
+
]);
|
|
51
|
+
const SOURCE_EDIT_OPTIONS = Object.freeze(["program"]);
|
|
27
52
|
const GUIDE_OPTIONS = Object.freeze(["axes", "legend"]);
|
|
28
53
|
const HEADER_OPTIONS = Object.freeze([
|
|
29
|
-
"fontSize", "fontFamily", "fontWeight", "color", "offset"
|
|
54
|
+
"fontSize", "fontFamily", "fontWeight", "color", "offset", "role",
|
|
55
|
+
"labelMap", "side", "align"
|
|
56
|
+
]);
|
|
57
|
+
const HEADER_STYLE_OPTIONS = Object.freeze([
|
|
58
|
+
"fontSize", "fontFamily", "fontWeight", "color", "offset", "align"
|
|
30
59
|
]);
|
|
31
|
-
const DEFAULT_HEADERS = Object.freeze({
|
|
32
|
-
fontSize: 12,
|
|
33
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
34
|
-
fontWeight: 600,
|
|
35
|
-
color: DEFAULT_COLORS.strongText,
|
|
36
|
-
offset: 10
|
|
37
|
-
});
|
|
38
60
|
|
|
39
61
|
function normalizeGuides(guides) {
|
|
40
62
|
if (guides === undefined) return { axes: "each", legend: false };
|
|
@@ -57,18 +79,38 @@ function requireFacetProgram(program, operation) {
|
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
|
|
82
|
+
function usedFacetScalePolicies(program, policies) {
|
|
83
|
+
const used = new Set(
|
|
84
|
+
collectFacetScaleBindings(program.semanticSpec).map(binding => binding.policyKey)
|
|
85
|
+
);
|
|
86
|
+
return Object.fromEntries(
|
|
87
|
+
FACET_SCALE_CHANNELS.flatMap(channel =>
|
|
88
|
+
used.has(channel) ? [[channel, policies[channel]]] : []
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
60
93
|
function facetUnitTemplate(program) {
|
|
61
|
-
const
|
|
94
|
+
const seedId = program.compositionSpec.children.find(id =>
|
|
95
|
+
program.children[id]?.semanticSpec.layers.length > 0
|
|
96
|
+
) ?? program.compositionSpec.children[0];
|
|
97
|
+
const seed = program.children[seedId];
|
|
62
98
|
if (seed === undefined) {
|
|
63
99
|
throw new Error(`Facet "${program.compositionSpec.id}" requires a retained child.`);
|
|
64
100
|
}
|
|
65
|
-
const { facets: _facets, ...unitConfigs } = program.materializationConfigs;
|
|
101
|
+
const { facets: _facets, guides: _guides, ...unitConfigs } = program.materializationConfigs;
|
|
66
102
|
return new program.constructor({
|
|
67
103
|
semanticSpec: program.semanticSpec,
|
|
68
104
|
graphicSpec: seed.graphicSpec,
|
|
69
105
|
resolvedScales: program.resolvedScales,
|
|
70
106
|
materializationConfigs: freezeOwned({
|
|
71
107
|
...unitConfigs,
|
|
108
|
+
...(seed.materializationConfigs.theme === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { theme: seed.materializationConfigs.theme }),
|
|
111
|
+
...(seed.materializationConfigs.guides === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { guides: seed.materializationConfigs.guides }),
|
|
72
114
|
canvas: seed.materializationConfigs.canvas
|
|
73
115
|
}),
|
|
74
116
|
children: {},
|
|
@@ -81,19 +123,44 @@ function facetUnitTemplate(program) {
|
|
|
81
123
|
|
|
82
124
|
function rederiveFacet(program, { scales, guides }) {
|
|
83
125
|
const current = program.compositionSpec;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
if (current.facet.repeat !== undefined) {
|
|
127
|
+
const template = facetUnitTemplate(program);
|
|
128
|
+
const definition = resolveRepeatDefinition(template, {
|
|
129
|
+
id: current.id,
|
|
130
|
+
...current.facet.repeat
|
|
131
|
+
});
|
|
132
|
+
const derived = deriveRepeatChildren(
|
|
133
|
+
template,
|
|
134
|
+
definition,
|
|
135
|
+
usedFacetScalePolicies(template, scales),
|
|
136
|
+
true
|
|
137
|
+
);
|
|
138
|
+
return applyCompositionState(program, {
|
|
139
|
+
children: derived.children,
|
|
140
|
+
compositionSpec: {
|
|
141
|
+
...current,
|
|
142
|
+
facet: { ...current.facet, scales, guides }
|
|
143
|
+
}
|
|
144
|
+
}, current.children);
|
|
145
|
+
}
|
|
146
|
+
const definition = current.facet.grid === undefined
|
|
147
|
+
? resolveFacetDefinition(program.semanticSpec, {
|
|
148
|
+
id: current.id,
|
|
149
|
+
data: current.facet.data,
|
|
150
|
+
field: current.facet.field,
|
|
151
|
+
values: current.facet.values
|
|
152
|
+
})
|
|
153
|
+
: resolveFacetGridDefinition(program.semanticSpec, {
|
|
154
|
+
id: current.id,
|
|
155
|
+
data: current.facet.data,
|
|
156
|
+
rows: current.facet.grid.rows,
|
|
157
|
+
columns: current.facet.grid.columns,
|
|
158
|
+
combinations: current.facet.grid.combinations
|
|
159
|
+
});
|
|
160
|
+
const request = usedFacetScalePolicies(program, scales);
|
|
95
161
|
const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
|
|
96
|
-
const
|
|
162
|
+
const template = facetUnitTemplate(program);
|
|
163
|
+
const derived = deriveFacetChildren(template, definition, {
|
|
97
164
|
closeInheritedAction: true,
|
|
98
165
|
stripTitle: true,
|
|
99
166
|
scales: request
|
|
@@ -106,7 +173,11 @@ function rederiveFacet(program, { scales, guides }) {
|
|
|
106
173
|
guides
|
|
107
174
|
}
|
|
108
175
|
};
|
|
109
|
-
|
|
176
|
+
let parent = program._withoutMaterializationConfig(["guides", "legend"]);
|
|
177
|
+
for (const [kind, config] of Object.entries(template.guideConfigs.legend ?? {})) {
|
|
178
|
+
parent = parent._withLegendConfig(kind, config);
|
|
179
|
+
}
|
|
180
|
+
return applyCompositionState(parent, {
|
|
110
181
|
children: derived.children,
|
|
111
182
|
compositionSpec
|
|
112
183
|
}, compositionSpec.children);
|
|
@@ -121,6 +192,9 @@ export const facet = action(
|
|
|
121
192
|
validateOptionObject(args, FACET_OPTIONS, "facet");
|
|
122
193
|
const guides = normalizeGuides(args.guides);
|
|
123
194
|
const definition = resolveFacetDefinition(this.semanticSpec, args);
|
|
195
|
+
if (definition.family !== "cartesian" && guides.axes === "outer") {
|
|
196
|
+
throw new Error("Polar and Parallel facets do not support outer axes.");
|
|
197
|
+
}
|
|
124
198
|
const scalePolicies = normalizeFacetScalePolicies(
|
|
125
199
|
this.semanticSpec,
|
|
126
200
|
args.scales ?? {}
|
|
@@ -159,7 +233,7 @@ export const facet = action(
|
|
|
159
233
|
};
|
|
160
234
|
return applyCompositionState(
|
|
161
235
|
this._withMaterializationConfig(["facets", definition.id], {
|
|
162
|
-
headers:
|
|
236
|
+
headers: createDefaultFacetHeaders()
|
|
163
237
|
}),
|
|
164
238
|
{
|
|
165
239
|
children: derived.children,
|
|
@@ -170,6 +244,327 @@ export const facet = action(
|
|
|
170
244
|
}
|
|
171
245
|
);
|
|
172
246
|
|
|
247
|
+
function resolveRepeatDefinition(program, args) {
|
|
248
|
+
if (!isPlainObject(args)) {
|
|
249
|
+
throw new TypeError("repeatCharts options must be a plain object.");
|
|
250
|
+
}
|
|
251
|
+
const id = validateUserId(args.id ?? "repeat", "Repeat id");
|
|
252
|
+
const parallelChannel = isPlainObject(args.channel);
|
|
253
|
+
if (parallelChannel) {
|
|
254
|
+
const keys = Object.keys(args.channel);
|
|
255
|
+
if (keys.length !== 1 || keys[0] !== "parallelDimension" ||
|
|
256
|
+
typeof args.channel.parallelDimension !== "string" ||
|
|
257
|
+
args.channel.parallelDimension.length === 0) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
"repeatCharts Parallel channel must contain exactly one non-empty parallelDimension."
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
} else if (!["x", "y", "theta", "r"].includes(args.channel)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
'repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (!Array.isArray(args.fields) || args.fields.length === 0 ||
|
|
268
|
+
args.fields.some(field => typeof field !== "string" || field.length === 0)) {
|
|
269
|
+
throw new TypeError("repeatCharts fields must be a non-empty array of field names.");
|
|
270
|
+
}
|
|
271
|
+
if (new Set(args.fields).size !== args.fields.length) {
|
|
272
|
+
throw new Error("repeatCharts fields must be unique.");
|
|
273
|
+
}
|
|
274
|
+
const family = resolveFacetFamily(program.semanticSpec).family;
|
|
275
|
+
const semanticChannel = args.channel === "r" ? "radius" : args.channel;
|
|
276
|
+
const eligible = program.semanticSpec.layers.filter(layer => {
|
|
277
|
+
if (parallelChannel) {
|
|
278
|
+
const matches = (layer.encoding?.parallel?.dimensions ?? []).filter(
|
|
279
|
+
dimension => dimension.field === args.channel.parallelDimension
|
|
280
|
+
);
|
|
281
|
+
return family === "parallel" && layer.mark?.type === "line" && matches.length === 1;
|
|
282
|
+
}
|
|
283
|
+
if (["x", "y"].includes(args.channel)) {
|
|
284
|
+
return family === "cartesian" &&
|
|
285
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
286
|
+
layer.encoding?.y?.scale !== undefined &&
|
|
287
|
+
layer.encoding?.[args.channel]?.field !== undefined &&
|
|
288
|
+
["point", "line", "area", "bar", "rule", "tick", "rect"].includes(layer.mark?.type);
|
|
289
|
+
}
|
|
290
|
+
const role = program.markConfigs[layer.id]?.compositionRole;
|
|
291
|
+
if (family !== "polar" || ["pie", "radar"].includes(role)) return false;
|
|
292
|
+
return ["point", "line", "arc"].includes(layer.mark?.type) &&
|
|
293
|
+
layer.encoding?.[semanticChannel]?.field !== undefined;
|
|
294
|
+
});
|
|
295
|
+
let target;
|
|
296
|
+
if (args.target !== undefined) {
|
|
297
|
+
target = validateUserId(args.target, "Repeat target");
|
|
298
|
+
if (!eligible.some(layer => layer.id === target)) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`repeatCharts target "${target}" is not eligible for the requested field role.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
} else if (eligible.length === 1) {
|
|
304
|
+
target = eligible[0].id;
|
|
305
|
+
} else {
|
|
306
|
+
throw new Error(
|
|
307
|
+
eligible.length === 0
|
|
308
|
+
? "repeatCharts requires one eligible complete mark for the requested field role."
|
|
309
|
+
: "repeatCharts target is ambiguous; provide target."
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const unrelatedLayers = program.semanticSpec.layers.filter(layer => {
|
|
313
|
+
if (layer.id === target) return false;
|
|
314
|
+
const attachedLabel = layer.mark?.type === "text" && layer.source === target;
|
|
315
|
+
const statisticalReference = program.markConfigs[layer.id]
|
|
316
|
+
?.statisticalReference?.source === target;
|
|
317
|
+
return !attachedLabel && !statisticalReference;
|
|
318
|
+
});
|
|
319
|
+
if (unrelatedLayers.length > 0) {
|
|
320
|
+
throw new Error("repeatCharts currently supports one direct mark only.");
|
|
321
|
+
}
|
|
322
|
+
const config = program.markConfigs[target] ?? {};
|
|
323
|
+
const composite = [
|
|
324
|
+
"boxPlot", "gradientPlot", "violinPlot", "regressionPlot", "endpointPlot",
|
|
325
|
+
"ecdfPlot", "raincloudPlot", "intervalPlot"
|
|
326
|
+
].find(key => config[key] !== undefined);
|
|
327
|
+
if (composite !== undefined) {
|
|
328
|
+
throw new Error(`repeatCharts does not replace the ${composite} composite role.`);
|
|
329
|
+
}
|
|
330
|
+
const layer = findLayer(program, target);
|
|
331
|
+
const dataset = findDataset(program, layer.data);
|
|
332
|
+
if (dataset?.transform?.length > 0) {
|
|
333
|
+
throw new Error("repeatCharts does not rewrite a derived dataset dependency.");
|
|
334
|
+
}
|
|
335
|
+
const encoding = parallelChannel
|
|
336
|
+
? layer.encoding.parallel
|
|
337
|
+
: layer.encoding[semanticChannel];
|
|
338
|
+
const dimensionIndex = parallelChannel
|
|
339
|
+
? encoding.dimensions.findIndex(
|
|
340
|
+
dimension => dimension.field === args.channel.parallelDimension
|
|
341
|
+
)
|
|
342
|
+
: undefined;
|
|
343
|
+
return {
|
|
344
|
+
id,
|
|
345
|
+
target,
|
|
346
|
+
channel: parallelChannel ? { ...args.channel } : args.channel,
|
|
347
|
+
policyKey: parallelChannel ? "parallelDimensions" : args.channel,
|
|
348
|
+
fields: [...args.fields],
|
|
349
|
+
data: layer.data,
|
|
350
|
+
encoding,
|
|
351
|
+
...(dimensionIndex === undefined ? {} : { dimensionIndex }),
|
|
352
|
+
cells: args.fields.map((field, index) => ({
|
|
353
|
+
id: `${id}-field-${index + 1}`,
|
|
354
|
+
field,
|
|
355
|
+
value: field
|
|
356
|
+
}))
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function repeatEncodingArgs(definition, field) {
|
|
361
|
+
const encoding = definition.encoding;
|
|
362
|
+
return {
|
|
363
|
+
target: definition.target,
|
|
364
|
+
field,
|
|
365
|
+
fieldType: encoding.fieldType,
|
|
366
|
+
...(encoding.temporalUnit === undefined ? {} : { temporalUnit: encoding.temporalUnit }),
|
|
367
|
+
...(encoding.aggregate === undefined ? {} : { aggregate: encoding.aggregate }),
|
|
368
|
+
...(encoding.bin === undefined ? {} : { bin: encoding.bin }),
|
|
369
|
+
...(encoding.stack === undefined ? {} : { stack: encoding.stack }),
|
|
370
|
+
...(encoding.weight === undefined ? {} : { weight: encoding.weight }),
|
|
371
|
+
scale: { id: encoding.scale }
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function parallelRepeatDimensions(program, definition, field) {
|
|
376
|
+
return definition.encoding.dimensions.map((dimension, index) => {
|
|
377
|
+
const semanticScale = findSemanticScale(program, dimension.scale);
|
|
378
|
+
if (semanticScale === undefined) {
|
|
379
|
+
throw new Error(`repeatCharts Parallel dimension scale "${dimension.scale}" is missing.`);
|
|
380
|
+
}
|
|
381
|
+
const { id: _id, ...scale } = semanticScale;
|
|
382
|
+
void _id;
|
|
383
|
+
const replacement = index === definition.dimensionIndex;
|
|
384
|
+
return {
|
|
385
|
+
field: replacement ? field : dimension.field,
|
|
386
|
+
fieldType: dimension.fieldType,
|
|
387
|
+
title: replacement && dimension.title === dimension.field
|
|
388
|
+
? field
|
|
389
|
+
: dimension.title,
|
|
390
|
+
scale
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function deriveRepeatedProgram(template, definition, field) {
|
|
396
|
+
if (isPlainObject(definition.channel)) {
|
|
397
|
+
return template.encodeParallelCoordinates({
|
|
398
|
+
target: definition.target,
|
|
399
|
+
coordinate: findLayer(template, definition.target).coordinate,
|
|
400
|
+
dimensions: parallelRepeatDimensions(template, definition, field),
|
|
401
|
+
...(definition.encoding.key === undefined ? {} : { key: definition.encoding.key }),
|
|
402
|
+
missing: definition.encoding.missing
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const operation = {
|
|
406
|
+
x: "encodeX",
|
|
407
|
+
y: "encodeY",
|
|
408
|
+
theta: "encodeTheta",
|
|
409
|
+
r: "encodeR"
|
|
410
|
+
}[definition.channel];
|
|
411
|
+
return template[operation](repeatEncodingArgs(definition, field));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
|
|
415
|
+
const template = base.semanticSpec.title.text === undefined
|
|
416
|
+
? base
|
|
417
|
+
: base.removeTitle();
|
|
418
|
+
const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
|
|
419
|
+
cell.id,
|
|
420
|
+
deriveRepeatedProgram(template, definition, cell.field)
|
|
421
|
+
]));
|
|
422
|
+
return resolveFacetChildrenScales(
|
|
423
|
+
template,
|
|
424
|
+
definition.cells.map(cell => cell.id),
|
|
425
|
+
independentlyResolved,
|
|
426
|
+
scales,
|
|
427
|
+
closeInheritedAction
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export const repeatCharts = action(
|
|
432
|
+
{
|
|
433
|
+
op: "repeatCharts",
|
|
434
|
+
description: "Repeat one direct chart across an ordered field-role list."
|
|
435
|
+
},
|
|
436
|
+
function (args = {}) {
|
|
437
|
+
validateOptionObject(args, REPEAT_OPTIONS, "repeatCharts");
|
|
438
|
+
const guides = normalizeGuides(args.guides);
|
|
439
|
+
if (guides.axes === "outer") {
|
|
440
|
+
throw new Error("repeatCharts does not promote axes across different repeated fields.");
|
|
441
|
+
}
|
|
442
|
+
const definition = resolveRepeatDefinition(this, args);
|
|
443
|
+
const requestedScales = {
|
|
444
|
+
...(args.scales ?? {}),
|
|
445
|
+
[definition.policyKey]: args.scales?.[definition.policyKey] ?? "independent"
|
|
446
|
+
};
|
|
447
|
+
const scalePolicies = normalizeFacetScalePolicies(
|
|
448
|
+
this.semanticSpec,
|
|
449
|
+
requestedScales
|
|
450
|
+
);
|
|
451
|
+
const derived = deriveRepeatChildren(
|
|
452
|
+
this,
|
|
453
|
+
definition,
|
|
454
|
+
requestedScales,
|
|
455
|
+
true
|
|
456
|
+
);
|
|
457
|
+
const preflight = resolveFacetLayout({
|
|
458
|
+
children: definition.cells.map(cell => ({
|
|
459
|
+
...compositionChildDescriptor(cell.id, derived.children[cell.id]),
|
|
460
|
+
value: cell.value
|
|
461
|
+
})),
|
|
462
|
+
...(Object.hasOwn(args, "columns") ? { columns: args.columns } : {}),
|
|
463
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
464
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
465
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
|
|
466
|
+
sharedLegend: guides.legend === "shared"
|
|
467
|
+
});
|
|
468
|
+
const compositionSpec = {
|
|
469
|
+
id: definition.id,
|
|
470
|
+
type: "facet",
|
|
471
|
+
children: definition.cells.map(cell => cell.id),
|
|
472
|
+
columns: preflight.columns,
|
|
473
|
+
gap: preflight.gap,
|
|
474
|
+
align: preflight.align,
|
|
475
|
+
padding: preflight.padding,
|
|
476
|
+
facet: {
|
|
477
|
+
data: definition.data,
|
|
478
|
+
values: definition.fields,
|
|
479
|
+
repeat: {
|
|
480
|
+
target: definition.target,
|
|
481
|
+
channel: definition.channel,
|
|
482
|
+
fields: definition.fields
|
|
483
|
+
},
|
|
484
|
+
scales: scalePolicies.channels,
|
|
485
|
+
guides
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
return applyCompositionState(
|
|
489
|
+
this._withMaterializationConfig(["facets", definition.id], {
|
|
490
|
+
headers: createDefaultFacetHeaders()
|
|
491
|
+
}),
|
|
492
|
+
{ children: derived.children, compositionSpec },
|
|
493
|
+
compositionSpec.children
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
export const facetGrid = action(
|
|
499
|
+
{
|
|
500
|
+
op: "facetGrid",
|
|
501
|
+
description: "Repeat one direct-source Cartesian chart across a row and column field grid."
|
|
502
|
+
},
|
|
503
|
+
function (args = {}) {
|
|
504
|
+
validateOptionObject(args, FACET_GRID_OPTIONS, "facetGrid");
|
|
505
|
+
const guides = normalizeGuides(args.guides);
|
|
506
|
+
const definition = resolveFacetGridDefinition(this.semanticSpec, args);
|
|
507
|
+
if (definition.family !== "cartesian" && guides.axes === "outer") {
|
|
508
|
+
throw new Error("Polar and Parallel facets do not support outer axes.");
|
|
509
|
+
}
|
|
510
|
+
const scalePolicies = normalizeFacetScalePolicies(
|
|
511
|
+
this.semanticSpec,
|
|
512
|
+
args.scales ?? {}
|
|
513
|
+
);
|
|
514
|
+
const derived = deriveFacetChildren(this, definition, {
|
|
515
|
+
closeInheritedAction: true,
|
|
516
|
+
stripTitle: true,
|
|
517
|
+
scales: args.scales ?? {}
|
|
518
|
+
});
|
|
519
|
+
const preflight = resolveFacetLayout({
|
|
520
|
+
children: definition.cells.map(cell => ({
|
|
521
|
+
...compositionChildDescriptor(cell.id, derived.children[cell.id]),
|
|
522
|
+
value: cell.value,
|
|
523
|
+
row: cell.row,
|
|
524
|
+
column: cell.column
|
|
525
|
+
})),
|
|
526
|
+
columns: definition.grid.columns.values.length,
|
|
527
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
528
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
529
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
|
|
530
|
+
sharedLegend: guides.legend === "shared"
|
|
531
|
+
});
|
|
532
|
+
const compositionSpec = {
|
|
533
|
+
id: definition.id,
|
|
534
|
+
type: "facet",
|
|
535
|
+
children: definition.cells.map(cell => cell.id),
|
|
536
|
+
columns: preflight.columns,
|
|
537
|
+
gap: preflight.gap,
|
|
538
|
+
align: preflight.align,
|
|
539
|
+
padding: preflight.padding,
|
|
540
|
+
facet: {
|
|
541
|
+
data: definition.data,
|
|
542
|
+
values: definition.cells.map(cell => cell.value),
|
|
543
|
+
grid: {
|
|
544
|
+
...definition.grid,
|
|
545
|
+
cells: definition.cells.map(cell => ({
|
|
546
|
+
id: cell.id,
|
|
547
|
+
row: cell.row,
|
|
548
|
+
column: cell.column,
|
|
549
|
+
rowValue: cell.rowValue,
|
|
550
|
+
columnValue: cell.columnValue,
|
|
551
|
+
empty: cell.empty
|
|
552
|
+
}))
|
|
553
|
+
},
|
|
554
|
+
scales: scalePolicies.channels,
|
|
555
|
+
guides
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
return applyCompositionState(
|
|
559
|
+
this._withMaterializationConfig(["facets", definition.id], {
|
|
560
|
+
headers: createDefaultFacetHeaders()
|
|
561
|
+
}),
|
|
562
|
+
{ children: derived.children, compositionSpec },
|
|
563
|
+
compositionSpec.children
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
|
|
173
568
|
export const editFacetHeaders = action(
|
|
174
569
|
{
|
|
175
570
|
op: "editFacetHeaders",
|
|
@@ -187,18 +582,73 @@ export const editFacetHeaders = action(
|
|
|
187
582
|
allowEmpty: false,
|
|
188
583
|
emptyMessage: "editFacetHeaders requires at least one change."
|
|
189
584
|
});
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
585
|
+
const role = args.role ?? "all";
|
|
586
|
+
if (!["all", "row", "column"].includes(role)) {
|
|
587
|
+
throw new Error(`Unknown facet header role "${role}".`);
|
|
588
|
+
}
|
|
589
|
+
if (role === "all" && Object.hasOwn(args, "side")) {
|
|
590
|
+
throw new Error("editFacetHeaders side requires an explicit row or column role.");
|
|
591
|
+
}
|
|
592
|
+
if (role === "row" && this.compositionSpec.facet.grid === undefined) {
|
|
593
|
+
throw new Error("editFacetHeaders row role requires a row-column facet grid.");
|
|
594
|
+
}
|
|
595
|
+
if (Object.hasOwn(args, "side")) {
|
|
596
|
+
const sides = role === "row" ? ["left", "right"] : ["top", "bottom"];
|
|
597
|
+
if (!sides.includes(args.side)) {
|
|
598
|
+
throw new Error(`editFacetHeaders ${role} side must be ${sides.join(" or ")}.`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (Object.hasOwn(args, "align") &&
|
|
602
|
+
!["start", "center", "end"].includes(args.align)) {
|
|
603
|
+
throw new Error("editFacetHeaders align must be start, center, or end.");
|
|
604
|
+
}
|
|
605
|
+
if (role === "all" && Object.keys(args).every(key => key === "role")) {
|
|
606
|
+
throw new Error("editFacetHeaders requires at least one change.");
|
|
607
|
+
}
|
|
608
|
+
const current = normalizeFacetHeadersConfig(config.headers);
|
|
609
|
+
const owner = role === "all" ? "common" : role;
|
|
610
|
+
const patch = { ...current[owner] };
|
|
611
|
+
for (const key of HEADER_STYLE_OPTIONS) {
|
|
612
|
+
if (Object.hasOwn(args, key)) patch[key] = args[key];
|
|
613
|
+
}
|
|
614
|
+
if (Object.hasOwn(args, "side")) patch.side = args.side;
|
|
615
|
+
if (Object.hasOwn(args, "labelMap")) {
|
|
616
|
+
if (args.labelMap === "auto") delete patch.labelMap;
|
|
617
|
+
else {
|
|
618
|
+
patch.labelMap = normalizeDisplayLabelMap(
|
|
619
|
+
args.labelMap,
|
|
620
|
+
`editFacetHeaders ${role} labelMap`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const headers = {
|
|
625
|
+
...current,
|
|
626
|
+
...(role === "all" ? {} : { mode: "roles" }),
|
|
627
|
+
[owner]: patch
|
|
628
|
+
};
|
|
629
|
+
for (const headerRole of ["all", "row", "column"]) {
|
|
630
|
+
const resolved = resolveFacetHeaderConfig(headers, headerRole);
|
|
631
|
+
validatePositiveFinite(resolved.fontSize, `Facet ${headerRole} header fontSize`);
|
|
632
|
+
validateNonEmptyString(resolved.fontFamily, `Facet ${headerRole} header fontFamily`);
|
|
633
|
+
validateNonEmptyString(resolved.color, `Facet ${headerRole} header color`);
|
|
634
|
+
validateNonNegativeFinite(resolved.offset, `Facet ${headerRole} header offset`);
|
|
635
|
+
if (!(
|
|
636
|
+
(typeof resolved.fontWeight === "string" && resolved.fontWeight.length > 0) ||
|
|
637
|
+
Number.isFinite(resolved.fontWeight)
|
|
638
|
+
)) {
|
|
639
|
+
throw new TypeError(
|
|
640
|
+
`Facet ${headerRole} header fontWeight must be a non-empty string or number.`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (!["start", "center", "end"].includes(resolved.align)) {
|
|
644
|
+
throw new Error(`Facet ${headerRole} header align must be start, center, or end.`);
|
|
645
|
+
}
|
|
646
|
+
if (resolved.labelMap !== undefined) {
|
|
647
|
+
normalizeDisplayLabelMap(
|
|
648
|
+
resolved.labelMap,
|
|
649
|
+
`Facet ${headerRole} header labelMap`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
202
652
|
}
|
|
203
653
|
return this
|
|
204
654
|
._withMaterializationConfig(["facets", id], { ...config, headers })
|
|
@@ -219,8 +669,8 @@ export const editFacetScales = action(
|
|
|
219
669
|
emptyMessage: "editFacetScales requires at least one channel policy change."
|
|
220
670
|
});
|
|
221
671
|
for (const channel of Object.keys(args)) {
|
|
222
|
-
if (!this.semanticSpec.
|
|
223
|
-
|
|
672
|
+
if (!collectFacetScaleBindings(this.semanticSpec).some(
|
|
673
|
+
binding => binding.policyKey === channel
|
|
224
674
|
)) {
|
|
225
675
|
throw new Error(
|
|
226
676
|
`Facet scale channel "${channel}" is not used by an affected layer.`
|
|
@@ -255,6 +705,13 @@ export const editFacetGuides = action(
|
|
|
255
705
|
});
|
|
256
706
|
const current = this.compositionSpec.facet;
|
|
257
707
|
const guides = normalizeGuides({ ...current.guides, ...args });
|
|
708
|
+
const family = resolveFacetFamily(this.semanticSpec).family;
|
|
709
|
+
if (family !== "cartesian" && guides.axes === "outer") {
|
|
710
|
+
throw new Error("Polar and Parallel facets do not support outer axes.");
|
|
711
|
+
}
|
|
712
|
+
if (current.repeat !== undefined && guides.axes === "outer") {
|
|
713
|
+
throw new Error("repeatCharts does not promote axes across different repeated fields.");
|
|
714
|
+
}
|
|
258
715
|
return rederiveFacet(this, {
|
|
259
716
|
scales: current.scales,
|
|
260
717
|
guides
|
|
@@ -262,11 +719,132 @@ export const editFacetGuides = action(
|
|
|
262
719
|
}
|
|
263
720
|
);
|
|
264
721
|
|
|
722
|
+
function adoptUnitState(program, actionOwner) {
|
|
723
|
+
if (!(program instanceof actionOwner.constructor)) {
|
|
724
|
+
throw new TypeError("editFacetSource program must be a ChartProgram.");
|
|
725
|
+
}
|
|
726
|
+
if (program.compositionSpec !== undefined) {
|
|
727
|
+
throw new Error("editFacetSource program must be a complete unit ChartProgram.");
|
|
728
|
+
}
|
|
729
|
+
if (program.actionStack.length !== 0) {
|
|
730
|
+
throw new Error("editFacetSource program has an unfinished action stack.");
|
|
731
|
+
}
|
|
732
|
+
const seedId = actionOwner.compositionSpec.children.find(id =>
|
|
733
|
+
actionOwner.children[id]?.semanticSpec.layers.length > 0
|
|
734
|
+
) ?? actionOwner.compositionSpec.children[0];
|
|
735
|
+
const retained = actionOwner.children[seedId]?.materializationConfigs ?? {};
|
|
736
|
+
const {
|
|
737
|
+
canvas: _retainedCanvas,
|
|
738
|
+
theme: _retainedTheme,
|
|
739
|
+
facets: _retainedFacets,
|
|
740
|
+
...retainedRecipe
|
|
741
|
+
} = retained;
|
|
742
|
+
void _retainedCanvas;
|
|
743
|
+
void _retainedTheme;
|
|
744
|
+
void _retainedFacets;
|
|
745
|
+
return new actionOwner.constructor({
|
|
746
|
+
semanticSpec: program.semanticSpec,
|
|
747
|
+
graphicSpec: program.graphicSpec,
|
|
748
|
+
resolvedScales: program.resolvedScales,
|
|
749
|
+
materializationConfigs: freezeOwned({
|
|
750
|
+
...program.materializationConfigs,
|
|
751
|
+
...retainedRecipe,
|
|
752
|
+
...(program.materializationConfigs.canvas === undefined
|
|
753
|
+
? {}
|
|
754
|
+
: { canvas: program.materializationConfigs.canvas }),
|
|
755
|
+
...(program.materializationConfigs.theme === undefined
|
|
756
|
+
? {}
|
|
757
|
+
: { theme: program.materializationConfigs.theme })
|
|
758
|
+
}),
|
|
759
|
+
children: {},
|
|
760
|
+
context: program.context,
|
|
761
|
+
trace: actionOwner.trace,
|
|
762
|
+
actionStack: actionOwner.actionStack,
|
|
763
|
+
actionSequence: actionOwner._actionSequence
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export const editFacetSource = action(
|
|
768
|
+
{
|
|
769
|
+
op: "editFacetSource",
|
|
770
|
+
description: "Reapply one facet, grid, or repeat recipe to a revised complete unit program.",
|
|
771
|
+
scope: "composition"
|
|
772
|
+
},
|
|
773
|
+
function (args = {}) {
|
|
774
|
+
validateOptionObject(args, SOURCE_EDIT_OPTIONS, "editFacetSource");
|
|
775
|
+
requireFacetProgram(this, "editFacetSource");
|
|
776
|
+
const current = this.compositionSpec;
|
|
777
|
+
const facetConfig = this.materializationConfigs.facets?.[current.id];
|
|
778
|
+
const base = adoptUnitState(args.program, this);
|
|
779
|
+
let revised;
|
|
780
|
+
const scales = usedFacetScalePolicies(base, current.facet.scales);
|
|
781
|
+
if (current.facet.grid !== undefined) {
|
|
782
|
+
revised = base.facetGrid({
|
|
783
|
+
id: current.id,
|
|
784
|
+
data: current.facet.data,
|
|
785
|
+
rows: current.facet.grid.rows,
|
|
786
|
+
columns: current.facet.grid.columns,
|
|
787
|
+
combinations: current.facet.grid.combinations,
|
|
788
|
+
gap: current.gap,
|
|
789
|
+
align: current.align,
|
|
790
|
+
padding: current.padding,
|
|
791
|
+
scales,
|
|
792
|
+
guides: current.facet.guides
|
|
793
|
+
});
|
|
794
|
+
} else if (current.facet.repeat !== undefined) {
|
|
795
|
+
revised = base.repeatCharts({
|
|
796
|
+
id: current.id,
|
|
797
|
+
...current.facet.repeat,
|
|
798
|
+
columns: current.columns,
|
|
799
|
+
gap: current.gap,
|
|
800
|
+
align: current.align,
|
|
801
|
+
padding: current.padding,
|
|
802
|
+
scales,
|
|
803
|
+
guides: current.facet.guides
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
revised = base.facet({
|
|
807
|
+
id: current.id,
|
|
808
|
+
data: current.facet.data,
|
|
809
|
+
field: current.facet.field,
|
|
810
|
+
values: current.facet.values,
|
|
811
|
+
columns: current.columns,
|
|
812
|
+
gap: current.gap,
|
|
813
|
+
align: current.align,
|
|
814
|
+
padding: current.padding,
|
|
815
|
+
scales,
|
|
816
|
+
guides: current.facet.guides
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
if (facetConfig !== undefined) {
|
|
820
|
+
revised = revised
|
|
821
|
+
._withMaterializationConfig(["facets", current.id], facetConfig)
|
|
822
|
+
.materializeComposition();
|
|
823
|
+
}
|
|
824
|
+
if (revised.semanticSpec.title.text !== undefined) {
|
|
825
|
+
revised = revised.removeTitle();
|
|
826
|
+
}
|
|
827
|
+
if (this.semanticSpec.title.text !== undefined) {
|
|
828
|
+
revised = revised.createTitle({
|
|
829
|
+
...this.titleConfig,
|
|
830
|
+
...this.semanticSpec.title
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
return replayCompositionThemeState(
|
|
834
|
+
revised,
|
|
835
|
+
this.materializationConfigs.theme
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
);
|
|
839
|
+
|
|
265
840
|
export function registerFacetActions(ProgramClass) {
|
|
266
841
|
ProgramClass.prototype.replayDerivedData = replayDerivedData;
|
|
267
842
|
ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
|
|
268
843
|
ProgramClass.prototype.facet = facet;
|
|
844
|
+
ProgramClass.prototype.facetGrid = facetGrid;
|
|
845
|
+
ProgramClass.prototype.repeatCharts = repeatCharts;
|
|
269
846
|
ProgramClass.prototype.editFacetHeaders = editFacetHeaders;
|
|
270
847
|
ProgramClass.prototype.editFacetScales = editFacetScales;
|
|
271
848
|
ProgramClass.prototype.editFacetGuides = editFacetGuides;
|
|
849
|
+
ProgramClass.prototype.editFacetSource = editFacetSource;
|
|
272
850
|
}
|