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
|
@@ -15,13 +15,22 @@ import {
|
|
|
15
15
|
getSourceDependentMarkSteps
|
|
16
16
|
} from "../../materialization/marks/index.js";
|
|
17
17
|
import { findLayer } from "../../selectors/layers.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
removeLegendKinds,
|
|
20
|
+
resolveCategoricalLegendRevision,
|
|
21
|
+
createCategoricalLegendFromConfig
|
|
22
|
+
} from "../guides/legends/lifecycle.js";
|
|
19
23
|
import { clearMarkGraphic } from "./shared.js";
|
|
24
|
+
import { applyDetachedScaleRematerialization } from "../../materialization/dependencies.js";
|
|
25
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
26
|
+
import { assertPathGroupCompatible, validatePathGroupAppearance } from "../../materialization/marks/grouping.js";
|
|
27
|
+
import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
|
|
28
|
+
import { planLegendBlockTransitions } from "../guides/legends/transition.js";
|
|
20
29
|
|
|
21
30
|
const OPTIONS = Object.freeze(["target", "channel"]);
|
|
22
31
|
const REMOVABLE_CHANNELS = Object.freeze([
|
|
23
32
|
"x", "y", "x2", "y2", "xOffset", "yOffset", "theta", "radius",
|
|
24
|
-
"color", "strokeDash", "strokeWidth", "size", "shape", "group",
|
|
33
|
+
"color", "stroke", "strokeDash", "strokeWidth", "size", "shape", "group",
|
|
25
34
|
"angle", "opacity", "text"
|
|
26
35
|
]);
|
|
27
36
|
const REMOVE_AXIS = Object.freeze({
|
|
@@ -32,14 +41,11 @@ const REMOVE_AXIS = Object.freeze({
|
|
|
32
41
|
});
|
|
33
42
|
const SPECIALIZED_LEGEND_KIND = Object.freeze({
|
|
34
43
|
color: Object.freeze(["gradient", "interval"]),
|
|
44
|
+
stroke: Object.freeze(["strokeGradient", "strokeInterval"]),
|
|
35
45
|
size: Object.freeze(["size"]),
|
|
36
46
|
opacity: Object.freeze(["opacity"]),
|
|
37
47
|
strokeWidth: Object.freeze(["strokeWidth"])
|
|
38
48
|
});
|
|
39
|
-
const LEGEND_OPTION_KEYS = Object.freeze([
|
|
40
|
-
"position", "align", "direction", "columns", "offset", "titlePosition",
|
|
41
|
-
"title", "symbol", "labels", "titleStyle", "itemGap", "border"
|
|
42
|
-
]);
|
|
43
49
|
|
|
44
50
|
function resolveTarget(program, requested, channel) {
|
|
45
51
|
const candidates = program.semanticSpec.layers.filter(
|
|
@@ -77,7 +83,7 @@ function activeCascade(layer, channel) {
|
|
|
77
83
|
channels.add("y2");
|
|
78
84
|
channels.add("yOffset");
|
|
79
85
|
}
|
|
80
|
-
if (channel === "color" && layer.mark?.type === "bar") {
|
|
86
|
+
if (channel === "color" && layer.mark?.type === "bar" && layer.layout === undefined) {
|
|
81
87
|
if (resolveBarColorLayout(layer) === "group") {
|
|
82
88
|
channels.add(resolveBarOffsetChannel(layer));
|
|
83
89
|
}
|
|
@@ -85,6 +91,7 @@ function activeCascade(layer, channel) {
|
|
|
85
91
|
if (
|
|
86
92
|
channel === "group" &&
|
|
87
93
|
layer.mark?.type === "area" &&
|
|
94
|
+
layer.layout === undefined &&
|
|
88
95
|
layer.encoding?.color?.field !== undefined &&
|
|
89
96
|
layer.encoding.color.field === layer.encoding.group?.field
|
|
90
97
|
) {
|
|
@@ -93,21 +100,6 @@ function activeCascade(layer, channel) {
|
|
|
93
100
|
return [...channels].filter(active => layer.encoding?.[active] !== undefined);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
function assertSelectionCompatibility(program, target, channels) {
|
|
97
|
-
for (const [id, selection] of Object.entries(
|
|
98
|
-
program.materializationConfigs.selections ?? {}
|
|
99
|
-
)) {
|
|
100
|
-
if (
|
|
101
|
-
selection.target === target &&
|
|
102
|
-
channels.includes(selection.selector?.channel)
|
|
103
|
-
) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Cannot remove ${selection.selector.channel} encoding while selection "${id}" references that channel.`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
103
|
function removeChannelConfigs(program, target, channels) {
|
|
112
104
|
let next = program;
|
|
113
105
|
for (const channel of channels) {
|
|
@@ -167,7 +159,7 @@ function cleanupPositionGuides(program, removed) {
|
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
function reconcileCategoricalLegend(program, target, channels) {
|
|
170
|
-
const entry = ["series", "color"]
|
|
162
|
+
const entry = ["series", "color", "stroke"]
|
|
171
163
|
.map(kind => [kind, program.guideConfigs.legend?.[kind]])
|
|
172
164
|
.find(([, config]) =>
|
|
173
165
|
config?.target === target &&
|
|
@@ -182,16 +174,12 @@ function reconcileCategoricalLegend(program, target, channels) {
|
|
|
182
174
|
if (remaining.length === 0) {
|
|
183
175
|
return removeLegendKinds(program, [kind]);
|
|
184
176
|
}
|
|
177
|
+
const resolved = resolveCategoricalLegendRevision(program, kind, config, remaining);
|
|
178
|
+
const revision = planLegendBlockTransitions(program, target, [
|
|
179
|
+
{ ...resolved, kind: resolved.config.kind }
|
|
180
|
+
])[0];
|
|
185
181
|
const next = removeLegendKinds(program, [kind]);
|
|
186
|
-
|
|
187
|
-
for (const property of LEGEND_OPTION_KEYS) {
|
|
188
|
-
if (
|
|
189
|
-
(property === "columns" && config[property] === undefined) ||
|
|
190
|
-
(property === "title" && config.inferredTitle)
|
|
191
|
-
) continue;
|
|
192
|
-
options[property] = config[property];
|
|
193
|
-
}
|
|
194
|
-
return next.createLegend(options);
|
|
182
|
+
return createCategoricalLegendFromConfig(next, revision.config, revision.order);
|
|
195
183
|
}
|
|
196
184
|
|
|
197
185
|
function cleanupLegends(program, target, channels) {
|
|
@@ -228,8 +216,30 @@ export const removeEncoding = action(
|
|
|
228
216
|
);
|
|
229
217
|
}
|
|
230
218
|
const layer = resolveTarget(this, args.target, args.channel);
|
|
219
|
+
if (args.channel === "group" && layer.layout?.mode !== undefined && layer.layout.mode !== "overlay") throw new Error("Remove active series layout before removing its group.");
|
|
220
|
+
if (args.channel === "theta" && layer.mark.type === "arc" &&
|
|
221
|
+
["count", "sum"].includes(layer.encoding?.radius?.aggregate)) {
|
|
222
|
+
throw new Error("Remove measured radius before removing its category theta encoding.");
|
|
223
|
+
}
|
|
231
224
|
const channels = activeCascade(layer, args.channel);
|
|
232
|
-
|
|
225
|
+
assertEncodingSelectionCompatibility(this, layer.id, channels);
|
|
226
|
+
for (const kind of ["series", "color", "stroke"]) {
|
|
227
|
+
const config = this.guideConfigs.legend?.[kind];
|
|
228
|
+
const order = this.semanticSpec.guides.legend?.[kind]?.order;
|
|
229
|
+
if (config?.target === layer.id && channels.includes(order?.channel) &&
|
|
230
|
+
config.channels.some(channel => !channels.includes(channel))) {
|
|
231
|
+
throw new Error('Reset linked legend order to "scale" before removing its position encoding.');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (["line", "area"].includes(layer.mark?.type)) {
|
|
235
|
+
const dataset = findDataset(this, layer.data);
|
|
236
|
+
if (args.channel === "group") {
|
|
237
|
+
assertPathGroupCompatible(this, layer, dataset, undefined);
|
|
238
|
+
}
|
|
239
|
+
const encoding = { ...layer.encoding };
|
|
240
|
+
for (const channel of channels) delete encoding[channel];
|
|
241
|
+
validatePathGroupAppearance(this, { ...layer, encoding }, dataset);
|
|
242
|
+
}
|
|
233
243
|
const removedPositions = channels
|
|
234
244
|
.filter(channel => POSITION_ENCODING_CHANNELS.includes(channel))
|
|
235
245
|
.map(channel => ({
|
|
@@ -249,7 +259,7 @@ export const removeEncoding = action(
|
|
|
249
259
|
next = restoreBarBaseline(next, layer, channels);
|
|
250
260
|
next = cleanupLegends(next, layer.id, channels);
|
|
251
261
|
next = cleanupPositionGuides(next, removedPositions);
|
|
252
|
-
return rematerializeTarget(next, layer.id);
|
|
262
|
+
return applyDetachedScaleRematerialization(rematerializeTarget(next, layer.id), [layer]);
|
|
253
263
|
}
|
|
254
264
|
);
|
|
255
265
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
|
-
import {
|
|
3
|
-
validateRuleStroke,
|
|
4
|
-
validateRuleStrokeWidth
|
|
5
|
-
} from "../../grammar/ruleAppearance.js";
|
|
2
|
+
import { validateRuleStrokeWidth } from "../../grammar/ruleAppearance.js";
|
|
6
3
|
import { readQuantitativeField } from "../../grammar/scales/index.js";
|
|
7
4
|
import { resolveStrokeWidthScaleDefinition } from "../scales/definitions.js";
|
|
8
5
|
import {
|
|
9
6
|
applyEncodingScale,
|
|
7
|
+
applyDetachedScaleRematerialization,
|
|
10
8
|
rematerializeEncoding,
|
|
11
9
|
resolveReassignmentScaleOptions,
|
|
12
10
|
resolveTarget,
|
|
@@ -14,33 +12,17 @@ import {
|
|
|
14
12
|
validateOptions
|
|
15
13
|
} from "./shared.js";
|
|
16
14
|
import { findLayer } from "../../selectors/layers.js";
|
|
15
|
+
import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
|
|
16
|
+
import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
|
|
17
17
|
|
|
18
|
-
const STROKE_OPTIONS = Object.freeze(["target", "value"]);
|
|
19
18
|
const WIDTH_OPTIONS = Object.freeze([
|
|
20
19
|
"target", "value", "field", "fieldType", "scale"
|
|
21
20
|
]);
|
|
22
21
|
|
|
23
|
-
const encodeStroke = action(
|
|
24
|
-
{
|
|
25
|
-
op: "encodeStroke",
|
|
26
|
-
description: "Set a constant graphical stroke on a rule mark."
|
|
27
|
-
},
|
|
28
|
-
function (args = {}) {
|
|
29
|
-
validateOptions(args, STROKE_OPTIONS, "encodeStroke");
|
|
30
|
-
const { id } = resolveTarget(this, args.target, ["rule"], "rule mark");
|
|
31
|
-
return this
|
|
32
|
-
._withMarkConfig(id, {
|
|
33
|
-
...this.markConfigs[id],
|
|
34
|
-
stroke: validateRuleStroke(args.value)
|
|
35
|
-
})
|
|
36
|
-
.rematerializeRuleMark({ id });
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
|
|
40
22
|
const encodeStrokeWidth = action(
|
|
41
23
|
{
|
|
42
24
|
op: "encodeStrokeWidth",
|
|
43
|
-
description: "
|
|
25
|
+
description: "Assign constant or field-driven line or rule stroke width."
|
|
44
26
|
},
|
|
45
27
|
function (args = {}) {
|
|
46
28
|
validateOptions(args, WIDTH_OPTIONS, "encodeStrokeWidth");
|
|
@@ -50,9 +32,14 @@ const encodeStrokeWidth = action(
|
|
|
50
32
|
throw new Error("encodeStrokeWidth requires exactly one of value or field.");
|
|
51
33
|
}
|
|
52
34
|
if (hasValue) {
|
|
53
|
-
|
|
35
|
+
if (args.fieldType !== undefined || args.scale !== undefined) {
|
|
36
|
+
throw new Error("Constant stroke width does not accept fieldType or scale.");
|
|
37
|
+
}
|
|
38
|
+
validateRuleStrokeWidth(args.value);
|
|
39
|
+
const { id, layer } = resolveTarget(this, args.target, ["rule", "line"], "rule or line mark");
|
|
40
|
+
assertEncodingSelectionCompatibility(this, id, ["strokeWidth"]);
|
|
54
41
|
let next = this.guideConfigs.legend?.strokeWidth?.target === id
|
|
55
|
-
? this.removeLegend({ target: id })
|
|
42
|
+
? this.removeLegend({ target: id, channels: ["strokeWidth"] })
|
|
56
43
|
: this;
|
|
57
44
|
if (findLayer(next, id)?.encoding?.strokeWidth !== undefined) {
|
|
58
45
|
next = next.editSemantic({
|
|
@@ -60,12 +47,15 @@ const encodeStrokeWidth = action(
|
|
|
60
47
|
remove: true
|
|
61
48
|
});
|
|
62
49
|
}
|
|
63
|
-
|
|
64
|
-
._withMarkConfig(id, {
|
|
50
|
+
next = next._withMarkConfig(id, {
|
|
65
51
|
...next.markConfigs[id],
|
|
66
|
-
strokeWidth:
|
|
67
|
-
})
|
|
68
|
-
|
|
52
|
+
strokeWidth: args.value
|
|
53
|
+
});
|
|
54
|
+
if (layer.mark.type === "line") {
|
|
55
|
+
return rematerializeEncoding(next, id, "strokeWidth", undefined, layer);
|
|
56
|
+
}
|
|
57
|
+
next = next.rematerializeRuleMark({ id });
|
|
58
|
+
return applyDetachedScaleRematerialization(next, [layer]);
|
|
69
59
|
}
|
|
70
60
|
const { id, dataset, layer } = resolveTarget(
|
|
71
61
|
this,
|
|
@@ -83,6 +73,9 @@ const encodeStrokeWidth = action(
|
|
|
83
73
|
`encodeStrokeWidth field "${args.field}" cannot contain negative values.`
|
|
84
74
|
);
|
|
85
75
|
}
|
|
76
|
+
validatePathSeriesAppearance(dataset.values, {
|
|
77
|
+
...layer, encoding: { ...layer.encoding, strokeWidth: { field: args.field, fieldType } }
|
|
78
|
+
});
|
|
86
79
|
const previous = layer.encoding?.strokeWidth;
|
|
87
80
|
const requestedScale = resolveReassignmentScaleOptions(
|
|
88
81
|
previous,
|
|
@@ -100,11 +93,10 @@ const encodeStrokeWidth = action(
|
|
|
100
93
|
next = applyEncodingScale(next, scale, requestedScale, {
|
|
101
94
|
reassignment: previous?.scale === scale.id
|
|
102
95
|
});
|
|
103
|
-
return rematerializeEncoding(next, id, "strokeWidth", scale.id);
|
|
96
|
+
return rematerializeEncoding(next, id, "strokeWidth", scale.id, layer);
|
|
104
97
|
}
|
|
105
98
|
);
|
|
106
99
|
|
|
107
100
|
export function registerRuleAppearanceEncodingActions(ProgramClass) {
|
|
108
|
-
ProgramClass.prototype.encodeStroke = encodeStroke;
|
|
109
101
|
ProgramClass.prototype.encodeStrokeWidth = encodeStrokeWidth;
|
|
110
102
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateColorLayout } from "../../grammar/seriesLayout.js";
|
|
3
|
+
import { deriveAreaSeries, deriveDensityAreaSeries, layoutDensityAreaSeries } from "../../grammar/areaSeries.js";
|
|
4
|
+
import { readSeriesIdentity } from "../../grammar/pathSeries.js";
|
|
5
|
+
import { BAR_GRAINS, resolveBarChannels, resolveBarGrain } from "../../grammar/bars/policy.js";
|
|
6
|
+
import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
|
|
7
|
+
import { findSemanticScale, hasSemanticScaleReferences } from "../../selectors/scales.js";
|
|
8
|
+
import { findUpstreamTransform } from "../../materialization/dataProvenance.js";
|
|
9
|
+
import { resolveOffsetScaleDefinition } from "../scales/definitions.js";
|
|
10
|
+
import { resolveScalePreview } from "../scales/preview.js";
|
|
11
|
+
import { getPositionEncodingMaterializationSteps } from "../../materialization/marks/index.js";
|
|
12
|
+
import { resolveTarget, setEncodingProperties, validateOptions } from "./shared.js";
|
|
13
|
+
|
|
14
|
+
export function stackLayoutMode(stack) {
|
|
15
|
+
return stack === "normalize" ? "fill" : stack === "center" ? "center" : stack === null ? "overlay" : "stack";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const layoutSeries = action({ op: "layoutSeries", description: "Assign series placement independently of color." }, function (args = {}) {
|
|
19
|
+
validateOptions(args, ["target", "mode"], "layoutSeries");
|
|
20
|
+
const mode = validateColorLayout(args.mode);
|
|
21
|
+
const { id: target, layer, dataset } = resolveTarget(this, args.target, ["bar", "area"], "series mark");
|
|
22
|
+
const encoding = Object.fromEntries(Object.entries(layer.encoding ?? {}).map(([key, value]) => [key, { ...value }]));
|
|
23
|
+
const group = encoding.group;
|
|
24
|
+
const color = encoding.color;
|
|
25
|
+
if (group === undefined && color?.field !== undefined && ["nominal", "ordinal"].includes(color.fieldType)) {
|
|
26
|
+
encoding.group = { field: color.field, fieldType: "nominal", inferredFrom: "color" };
|
|
27
|
+
}
|
|
28
|
+
for (const channel of ["x", "y"]) if (encoding[channel]) delete encoding[channel].stack;
|
|
29
|
+
if (color) delete color.layout;
|
|
30
|
+
const updated = { ...layer, layout: { mode }, encoding };
|
|
31
|
+
const density = findUpstreamTransform(this, dataset, "density");
|
|
32
|
+
const horizon = findUpstreamTransform(this, dataset, "horizon");
|
|
33
|
+
let endpoint, offset, offsetScale, offsetChannel;
|
|
34
|
+
if (layer.mark.type === "area") {
|
|
35
|
+
if (horizon !== undefined) throw new Error("Horizon owns its layout; layoutSeries is not supported.");
|
|
36
|
+
if (mode === "group") throw new Error("Area does not support group layout.");
|
|
37
|
+
if (density !== undefined) layoutDensityAreaSeries(deriveDensityAreaSeries(dataset.values, updated, density), mode);
|
|
38
|
+
else {
|
|
39
|
+
if (mode !== "overlay" && encoding.x2 === undefined && encoding.y2 === undefined && encoding.y?.fieldType === "quantitative") {
|
|
40
|
+
endpoint = { channel: "y2", datum: 0, fieldType: "quantitative", scale: encoding.y.scale };
|
|
41
|
+
encoding.y2 = { datum: 0, fieldType: "quantitative", scale: encoding.y.scale };
|
|
42
|
+
}
|
|
43
|
+
if (encoding.x?.scale === undefined || encoding.y?.scale === undefined) throw new Error("Area layout requires complete positions.");
|
|
44
|
+
deriveAreaSeries(dataset.values, updated);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
const channels = resolveBarChannels(updated), grain = resolveBarGrain(updated);
|
|
48
|
+
if (grain === undefined || encoding.x?.scale === undefined || encoding.y?.scale === undefined) throw new Error("Series layout requires a complete bar.");
|
|
49
|
+
if (mode === "center") throw new Error("Centered bars are not supported.");
|
|
50
|
+
if (grain === BAR_GRAINS.ranged && mode !== "overlay") throw new Error("Ranged bars support only overlay layout.");
|
|
51
|
+
if (grain === BAR_GRAINS.aggregate) deriveBarAggregates(dataset.values, updated);
|
|
52
|
+
if (mode === "group" && grain === BAR_GRAINS.aggregate) {
|
|
53
|
+
if (encoding.group === undefined) throw new Error("Grouped bars require a group encoding.");
|
|
54
|
+
readSeriesIdentity(dataset.values, updated);
|
|
55
|
+
offsetChannel = `${channels.category}Offset`;
|
|
56
|
+
offsetScale = resolveOffsetScaleDefinition(this, { id: encoding[offsetChannel]?.scale ?? offsetChannel }, offsetChannel);
|
|
57
|
+
offset = { ...(encoding.group.field === undefined ? {} : { field: encoding.group.field }), fieldType: encoding[offsetChannel]?.fieldType ?? "nominal", scale: offsetScale.id };
|
|
58
|
+
encoding[offsetChannel] = offset;
|
|
59
|
+
} else for (const channel of ["xOffset", "yOffset"]) delete encoding[channel];
|
|
60
|
+
}
|
|
61
|
+
const scales = offsetScale === undefined ? this.semanticSpec.scales
|
|
62
|
+
: [...this.semanticSpec.scales.filter(scale => scale.id !== offsetScale.id), offsetScale];
|
|
63
|
+
const preview = { ...this, markConfigs: this.markConfigs, canvasConfig: this.canvasConfig,
|
|
64
|
+
resolvedScales: { ...this.resolvedScales },
|
|
65
|
+
semanticSpec: { ...this.semanticSpec, scales,
|
|
66
|
+
layers: this.semanticSpec.layers.map(item => item.id === target ? updated : item) } };
|
|
67
|
+
for (const id of new Set([encoding.x?.scale, encoding.y?.scale, offset?.scale].filter(Boolean))) {
|
|
68
|
+
preview.resolvedScales[id] = resolveScalePreview(preview, id).resolvedScale;
|
|
69
|
+
}
|
|
70
|
+
let next = this.editSemantic({ property: `layer[${target}].layout.mode`, value: mode });
|
|
71
|
+
if (group === undefined && encoding.group !== undefined) next = setEncodingProperties(next, target, "group", encoding.group);
|
|
72
|
+
for (const channel of ["x", "y"]) if (Object.hasOwn(layer.encoding?.[channel] ?? {}, "stack")) {
|
|
73
|
+
next = next.editSemantic({ property: `layer[${target}].encoding.${channel}.stack`, remove: true });
|
|
74
|
+
}
|
|
75
|
+
if (color && Object.hasOwn(layer.encoding.color, "layout")) next = next.editSemantic({ property: `layer[${target}].encoding.color.layout`, remove: true });
|
|
76
|
+
if (endpoint !== undefined) {
|
|
77
|
+
next = next[endpoint.channel === "y2" ? "encodeY2" : "encodeX2"]({ target, datum: 0 });
|
|
78
|
+
}
|
|
79
|
+
if (layer.mark.type === "bar") {
|
|
80
|
+
for (const channel of ["xOffset", "yOffset"]) {
|
|
81
|
+
if (layer.encoding?.[channel] !== undefined) next = next.editSemantic({ property: `layer[${target}].encoding.${channel}`, remove: true });
|
|
82
|
+
if (channel !== offsetChannel) next = next._withoutMaterializationConfig(["marks", target, channel]);
|
|
83
|
+
}
|
|
84
|
+
if (offset !== undefined) {
|
|
85
|
+
next = offset.field === undefined
|
|
86
|
+
? setEncodingProperties(next, target, offsetChannel, offset).createScale(offsetScale)
|
|
87
|
+
: next[offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset"]({ target, field: offset.field, fieldType: offset.fieldType, scale: offsetScale });
|
|
88
|
+
next = next._withoutMaterializationConfig(["marks", target, offsetChannel]);
|
|
89
|
+
if (findSemanticScale(this, offsetScale.id) === undefined) next = next._withMarkConfig(target, { ...next.markConfigs[target], seriesOffsetScale: offsetScale.id });
|
|
90
|
+
} else {
|
|
91
|
+
const owned = next.markConfigs[target]?.seriesOffsetScale;
|
|
92
|
+
if (owned !== undefined) {
|
|
93
|
+
const referenced = hasSemanticScaleReferences(next.semanticSpec, owned);
|
|
94
|
+
if (!referenced) next = next.editSemantic({ property: `scale[${owned}]`, remove: true });
|
|
95
|
+
next = next._withoutMaterializationConfig(["marks", target, "seriesOffsetScale"]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const step of getPositionEncodingMaterializationSteps(next, updated, encoding.y.scale)) next = next[step.op](step.args);
|
|
100
|
+
return next;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export function registerSeriesLayoutAction(ProgramClass) {
|
|
104
|
+
ProgramClass.prototype.layoutSeries = layoutSeries;
|
|
105
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
1
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
2
3
|
import { findDataset } from "../../selectors/datasets.js";
|
|
3
4
|
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
4
5
|
import { hasMaterializedLegend } from "../../materialization/legends.js";
|
|
5
|
-
import { applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
6
|
+
import { applyDetachedScaleRematerialization, applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
6
7
|
import { planEncodingRematerialization } from "../../materialization/encodings.js";
|
|
7
8
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
8
9
|
import { validateOptionObject } from "../../core/validation.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
getPositionChannelDefinition
|
|
12
|
-
} from "../../core/vocabulary.js";
|
|
10
|
+
import { getMarkGraphicTypes, getPositionChannelDefinition } from "../../core/vocabulary.js";
|
|
11
|
+
import { DEFAULT_TICK_COUNT } from "../guides/tickValues.js";
|
|
13
12
|
|
|
14
13
|
export function validateOptions(args, supported, operation) {
|
|
15
14
|
validateOptionObject(args, supported, operation);
|
|
@@ -31,11 +30,16 @@ export function setEncodingProperties(
|
|
|
31
30
|
return next;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
export
|
|
35
|
-
|
|
33
|
+
export { applyDetachedScaleRematerialization };
|
|
34
|
+
|
|
35
|
+
export function rematerializeEncoding(program, target, channel, scale, previousLayer) {
|
|
36
|
+
const next = applyMaterializationPlan(
|
|
36
37
|
program,
|
|
37
38
|
planEncodingRematerialization(program, { target, channel, scale })
|
|
38
39
|
);
|
|
40
|
+
return previousLayer === undefined
|
|
41
|
+
? next
|
|
42
|
+
: applyDetachedScaleRematerialization(next, [previousLayer]);
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export function clearMarkGraphic(program, target) {
|
|
@@ -50,19 +54,6 @@ export function clearMarkGraphic(program, target) {
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
|
|
53
|
-
export function validateLineSeriesCompatibility(layer, channel, field) {
|
|
54
|
-
if (layer.mark?.type !== "line") return;
|
|
55
|
-
for (const companion of ["group", "color", "strokeDash"]) {
|
|
56
|
-
if (companion === channel) continue;
|
|
57
|
-
const companionField = layer.encoding?.[companion]?.field;
|
|
58
|
-
if (companionField !== undefined && companionField !== field) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`Line ${channel} field "${field}" must match ${companion} field "${companionField}".`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
57
|
export function resolveReassignmentScaleOptions(encoding, options) {
|
|
67
58
|
if (encoding?.scale === undefined || options?.id !== undefined) return options;
|
|
68
59
|
return { ...options, id: encoding.scale };
|
|
@@ -72,13 +63,14 @@ export function applyEncodingScale(
|
|
|
72
63
|
program,
|
|
73
64
|
definition,
|
|
74
65
|
options = {},
|
|
75
|
-
{ reassignment = false } = {}
|
|
66
|
+
{ reassignment = false, allowTypeChange = false } = {}
|
|
76
67
|
) {
|
|
77
68
|
const existing = findSemanticScale(program, definition.id);
|
|
78
69
|
if (existing === undefined) {
|
|
79
70
|
return program.createScale(definition);
|
|
80
71
|
}
|
|
81
72
|
if (existing.type !== definition.type) {
|
|
73
|
+
if (reassignment && allowTypeChange) return program.editScale({ ...definition });
|
|
82
74
|
throw new Error(
|
|
83
75
|
`Scale "${definition.id}" cannot change type from "${existing.type}" to "${definition.type}".`
|
|
84
76
|
);
|
|
@@ -88,7 +80,7 @@ export function applyEncodingScale(
|
|
|
88
80
|
for (const property of [
|
|
89
81
|
"domain", "range", "nice", "zero", "clamp", "reverse",
|
|
90
82
|
"base", "exponent", "constant", "paddingInner", "paddingOuter",
|
|
91
|
-
"padding", "align", "unknown", "interpolate"
|
|
83
|
+
"padding", "align", "unknown", "interpolate", "radialMapping"
|
|
92
84
|
]) {
|
|
93
85
|
if (Object.hasOwn(options, property)) patch[property] = definition[property];
|
|
94
86
|
}
|
|
@@ -114,33 +106,100 @@ export function rebindPositionGuides(
|
|
|
114
106
|
const direction = definition.gridDirection;
|
|
115
107
|
const grid = program.semanticSpec.guides.grid?.[direction];
|
|
116
108
|
const ownsAxis = axis?.scale === previousScale;
|
|
109
|
+
const refreshesReboundAxis = axis?.scale === nextScale;
|
|
117
110
|
const ownsGrid = grid?.scale === previousScale;
|
|
118
|
-
if (!ownsAxis && !ownsGrid) return program;
|
|
111
|
+
if (!ownsAxis && !ownsGrid && !refreshesReboundAxis) return program;
|
|
119
112
|
|
|
120
113
|
const remaining = program.semanticSpec.layers.some(layer =>
|
|
121
|
-
layer.id !== target && layer.encoding?.[channel]?.scale === previousScale
|
|
114
|
+
layer.id !== target && !isSourceOwnedText(layer) && layer.encoding?.[channel]?.scale === previousScale
|
|
122
115
|
);
|
|
123
|
-
if (remaining) {
|
|
116
|
+
if ((ownsAxis || ownsGrid) && remaining) {
|
|
124
117
|
throw new Error(
|
|
125
118
|
`Cannot rebind ${channel} guides from shared scale "${previousScale}" while it has other consumers.`
|
|
126
119
|
);
|
|
127
120
|
}
|
|
128
121
|
|
|
129
122
|
let next = program;
|
|
130
|
-
if (ownsAxis) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
123
|
+
if (ownsAxis || refreshesReboundAxis) {
|
|
124
|
+
const previousAxisConfig = next.guideConfigs.axis?.[definition.guideChannel];
|
|
125
|
+
const previousTicks = previousAxisConfig?.ticks;
|
|
126
|
+
const previousLabels = previousAxisConfig?.labels;
|
|
127
|
+
const labelsFollowTicks = previousTicks !== undefined &&
|
|
128
|
+
previousLabels !== undefined &&
|
|
129
|
+
previousTicks.mode === previousLabels.mode &&
|
|
130
|
+
(previousTicks.mode === "count"
|
|
131
|
+
? previousTicks.count === previousLabels.count
|
|
132
|
+
: previousTicks.values?.length === previousLabels.values?.length &&
|
|
133
|
+
previousTicks.values.every(
|
|
134
|
+
(value, index) => Object.is(value, previousLabels.values[index])
|
|
135
|
+
));
|
|
136
|
+
if (ownsAxis) {
|
|
137
|
+
next = next.editSemantic({
|
|
138
|
+
property: `guide.axis.${definition.guideChannel}.scale`,
|
|
139
|
+
value: nextScale
|
|
140
|
+
});
|
|
141
|
+
for (const component of ["line", "ticks", "labels", "title"]) {
|
|
142
|
+
const config = next.guideConfigs.axis?.[definition.guideChannel]?.[component];
|
|
143
|
+
if (config?.scale === previousScale) {
|
|
144
|
+
next = next._withGuideConfig(definition.guideChannel, component, {
|
|
145
|
+
...config,
|
|
146
|
+
scale: nextScale
|
|
147
|
+
});
|
|
148
|
+
}
|
|
142
149
|
}
|
|
143
150
|
}
|
|
151
|
+
const resolved = next.resolvedScales[nextScale] ??
|
|
152
|
+
findSemanticScale(next, nextScale);
|
|
153
|
+
const discrete = ["ordinal", "band", "point"].includes(resolved?.type);
|
|
154
|
+
let ticks = next.guideConfigs.axis?.[definition.guideChannel]?.ticks;
|
|
155
|
+
if (ticks !== undefined && discrete && ticks.mode !== "values") {
|
|
156
|
+
ticks = {
|
|
157
|
+
...ticks,
|
|
158
|
+
mode: "values",
|
|
159
|
+
values: resolved.domain,
|
|
160
|
+
inferredValues: true
|
|
161
|
+
};
|
|
162
|
+
delete ticks.count;
|
|
163
|
+
next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
|
|
164
|
+
} else if (
|
|
165
|
+
ticks !== undefined &&
|
|
166
|
+
!discrete &&
|
|
167
|
+
ticks.mode === "values" &&
|
|
168
|
+
ticks.inferredValues === true
|
|
169
|
+
) {
|
|
170
|
+
ticks = {
|
|
171
|
+
...ticks,
|
|
172
|
+
mode: "count",
|
|
173
|
+
count: DEFAULT_TICK_COUNT,
|
|
174
|
+
inferredValues: true
|
|
175
|
+
};
|
|
176
|
+
delete ticks.values;
|
|
177
|
+
next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
|
|
178
|
+
} else if (
|
|
179
|
+
ticks !== undefined &&
|
|
180
|
+
discrete &&
|
|
181
|
+
ticks.inferredValues === true
|
|
182
|
+
) {
|
|
183
|
+
ticks = { ...ticks, values: resolved.domain };
|
|
184
|
+
next = next._withGuideConfig(definition.guideChannel, "ticks", ticks);
|
|
185
|
+
}
|
|
186
|
+
if (previousLabels !== undefined && labelsFollowTicks && ticks !== undefined) {
|
|
187
|
+
const labels = {
|
|
188
|
+
...next.guideConfigs.axis[definition.guideChannel].labels,
|
|
189
|
+
mode: ticks.mode,
|
|
190
|
+
inferredValues: ticks.inferredValues === true,
|
|
191
|
+
...(ticks.mode === "values"
|
|
192
|
+
? { values: ticks.values }
|
|
193
|
+
: { count: ticks.count })
|
|
194
|
+
};
|
|
195
|
+
if (ticks.mode === "values") delete labels.count;
|
|
196
|
+
else delete labels.values;
|
|
197
|
+
next = next._withGuideConfig(
|
|
198
|
+
definition.guideChannel,
|
|
199
|
+
"labels",
|
|
200
|
+
labels
|
|
201
|
+
);
|
|
202
|
+
}
|
|
144
203
|
}
|
|
145
204
|
if (ownsGrid) {
|
|
146
205
|
next = next
|
|
@@ -195,3 +254,14 @@ export function resolveTarget(
|
|
|
195
254
|
|
|
196
255
|
return { id, dataset, layer };
|
|
197
256
|
}
|
|
257
|
+
|
|
258
|
+
export function inferSeriesGroup(program, layer, field, origin) {
|
|
259
|
+
const group = layer.encoding?.group;
|
|
260
|
+
if (group !== undefined && group.inferredFrom !== origin) {
|
|
261
|
+
if (origin === "offset" && (group.fields !== undefined || group.field !== field)) {
|
|
262
|
+
throw new Error("Offset field conflicts with the stored series group.");
|
|
263
|
+
}
|
|
264
|
+
return program;
|
|
265
|
+
}
|
|
266
|
+
return setEncodingProperties(program, layer.id, "group", { field, fieldType: "nominal", inferredFrom: origin });
|
|
267
|
+
}
|