ggaction 0.0.13 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +341 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +20 -5
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
isColorScaleType,
|
|
15
15
|
isContinuousColorScaleType,
|
|
16
16
|
isDiscretizedColorScaleType,
|
|
17
|
+
normalizeSizeScaleDefinition,
|
|
17
18
|
normalizeScaleDefinition
|
|
18
19
|
} from "../../grammar/scales/index.js";
|
|
19
20
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
21
|
+
import { findScaleConsumers } from "./consumers/index.js";
|
|
20
22
|
|
|
21
23
|
const CREATE_SCALE_OPTIONS = Object.freeze([
|
|
22
24
|
"id",
|
|
@@ -75,7 +77,7 @@ function assertEquivalentScale(existing, expected) {
|
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
export const createScale = action(
|
|
80
|
+
export const createScale = /* @__PURE__ */ action(
|
|
79
81
|
{
|
|
80
82
|
op: "createScale",
|
|
81
83
|
description: "Create a named semantic scale."
|
|
@@ -97,28 +99,58 @@ export const createScale = action(
|
|
|
97
99
|
const requestedRange = args.palette === undefined
|
|
98
100
|
? args.range
|
|
99
101
|
: { palette: args.palette };
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
const consumers = findScaleConsumers(this, id);
|
|
103
|
+
const offsetScale = consumers.length > 0 && consumers.every(
|
|
104
|
+
consumer => ["xOffset", "yOffset"].includes(consumer.channel)
|
|
105
|
+
);
|
|
106
|
+
const sizeScale = consumers.length > 0 && consumers.every(
|
|
107
|
+
consumer => consumer.channel === "size"
|
|
108
|
+
);
|
|
109
|
+
if (offsetScale && type !== "ordinal") {
|
|
110
|
+
throw new Error(`Scale type "${type}" is not valid for offset positions.`);
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
offsetScale &&
|
|
114
|
+
Object.hasOwn(args, "range") &&
|
|
115
|
+
args.range !== "auto"
|
|
116
|
+
) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Offset scale range is derived from its parent categorical slot."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const definition = sizeScale
|
|
122
|
+
? normalizeSizeScaleDefinition({
|
|
123
|
+
patch: {
|
|
124
|
+
...args,
|
|
125
|
+
type,
|
|
126
|
+
...(requestedRange === undefined ? {} : { range: requestedRange })
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
: normalizeScaleDefinition({
|
|
130
|
+
type,
|
|
131
|
+
patch: {
|
|
132
|
+
...args,
|
|
133
|
+
...(requestedRange === undefined ? {} : { range: requestedRange })
|
|
134
|
+
},
|
|
135
|
+
allowOrdinalBandParameters: offsetScale,
|
|
136
|
+
validateDomain: (scaleType, value) =>
|
|
137
|
+
isDiscretizedColorScaleType(scaleType)
|
|
138
|
+
? validateDiscretizedColorDomain(scaleType, value)
|
|
139
|
+
: hasOrdinalDomain(scaleType)
|
|
140
|
+
? validateOrdinalDomain(value)
|
|
141
|
+
: validateScaleDomain(value),
|
|
142
|
+
validateRange: (scaleType, value) =>
|
|
143
|
+
isDiscretizedColorScaleType(scaleType)
|
|
144
|
+
? validateDiscretizedColorRange(value)
|
|
145
|
+
: isContinuousColorScaleType(scaleType)
|
|
146
|
+
? validateSequentialColorRange(value)
|
|
147
|
+
: scaleType === "ordinal"
|
|
148
|
+
? validateOrdinalRange(value)
|
|
149
|
+
: validateScaleRange(value)
|
|
150
|
+
});
|
|
151
|
+
if (Object.hasOwn(args, "unknown") && !sizeScale) {
|
|
152
|
+
definition.unknown = args.unknown;
|
|
153
|
+
}
|
|
122
154
|
const existing = findSemanticScale(this, id);
|
|
123
155
|
|
|
124
156
|
if (existing !== undefined) {
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
validateScaleDomain,
|
|
14
14
|
validateScaleRange,
|
|
15
15
|
validateShapeRange,
|
|
16
|
-
validateSizeRange,
|
|
17
16
|
validateStrokeDashRange,
|
|
18
17
|
validateStrokeWidthRange,
|
|
19
18
|
validateSequentialColorRange,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
validateScalePropertyForType,
|
|
26
25
|
validateScaleTypeForRole,
|
|
27
26
|
isDiscretePositionScaleType,
|
|
27
|
+
normalizeSizeScaleDefinition,
|
|
28
28
|
withScaleUnknown
|
|
29
29
|
} from "../../grammar/scales/index.js";
|
|
30
30
|
import {
|
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
validateThetaRange
|
|
33
33
|
} from "../../grammar/polar.js";
|
|
34
34
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
35
|
+
import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
|
|
36
|
+
import { resolveRequestedOffsetPolicy } from
|
|
37
|
+
"../../materialization/scales/policies/offset.js";
|
|
38
|
+
import { findScaleConsumers } from "./consumers/index.js";
|
|
35
39
|
|
|
36
40
|
const BASE_OPTIONS = ["id", "type", "domain", "range"];
|
|
37
41
|
const UNKNOWN_OPTIONS = [...BASE_OPTIONS, "unknown"];
|
|
@@ -58,6 +62,9 @@ const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
|
|
|
58
62
|
const STROKE_WIDTH_OPTIONS = [
|
|
59
63
|
...BASE_OPTIONS, ...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS
|
|
60
64
|
];
|
|
65
|
+
const SIZE_OPTIONS = [
|
|
66
|
+
...UNKNOWN_OPTIONS, "clamp", "reverse", "base", "exponent"
|
|
67
|
+
];
|
|
61
68
|
|
|
62
69
|
function optionsObject(options) {
|
|
63
70
|
if (!isPlainObject(options)) {
|
|
@@ -151,21 +158,22 @@ export function resolvePositionScaleDefinition(
|
|
|
151
158
|
return withScaleUnknown(scale, { ...existing, ...options }, channel);
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
export function resolveColorScaleDefinition(program, options) {
|
|
161
|
+
export function resolveColorScaleDefinition(program, options, channel = "color") {
|
|
155
162
|
optionsObject(options);
|
|
156
163
|
validateKeys(options, COLOR_OPTIONS, "scale");
|
|
157
164
|
validatePaletteRange(options);
|
|
158
|
-
const id = validateUserId(options.id ??
|
|
165
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
159
166
|
const existing = findSemanticScale(program, id);
|
|
167
|
+
const previous = existing?.type === "ordinal" ? existing : undefined;
|
|
160
168
|
const range = options.palette === undefined
|
|
161
169
|
? options.range
|
|
162
170
|
: { palette: options.palette };
|
|
163
171
|
return withScaleUnknown({
|
|
164
172
|
id,
|
|
165
|
-
type: validateOrdinalScaleType(options.type ??
|
|
166
|
-
domain: validateOrdinalDomain(options.domain ??
|
|
167
|
-
range: validateColorRange(range ??
|
|
168
|
-
}, { ...
|
|
173
|
+
type: validateOrdinalScaleType(options.type ?? previous?.type ?? "ordinal"),
|
|
174
|
+
domain: validateOrdinalDomain(options.domain ?? previous?.domain ?? "auto"),
|
|
175
|
+
range: validateColorRange(range ?? previous?.range ?? "auto")
|
|
176
|
+
}, { ...previous, ...options }, channel);
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
function continuousDomain(value, fieldType) {
|
|
@@ -186,14 +194,16 @@ function continuousDomain(value, fieldType) {
|
|
|
186
194
|
export function resolveSequentialColorScaleDefinition(
|
|
187
195
|
program,
|
|
188
196
|
fieldType,
|
|
189
|
-
options
|
|
197
|
+
options,
|
|
198
|
+
channel = "color"
|
|
190
199
|
) {
|
|
191
200
|
optionsObject(options);
|
|
192
201
|
validateKeys(options, SEQUENTIAL_COLOR_OPTIONS, "scale");
|
|
193
202
|
validatePaletteRange(options);
|
|
194
|
-
const id = validateUserId(options.id ??
|
|
203
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
195
204
|
const existing = findSemanticScale(program, id);
|
|
196
|
-
const
|
|
205
|
+
const previous = existing?.type === "sequential" ? existing : undefined;
|
|
206
|
+
const type = options.type ?? previous?.type ?? "sequential";
|
|
197
207
|
if (type !== "sequential") {
|
|
198
208
|
throw new Error(`Unsupported continuous color scale type "${type}".`);
|
|
199
209
|
}
|
|
@@ -204,16 +214,16 @@ export function resolveSequentialColorScaleDefinition(
|
|
|
204
214
|
const scale = {
|
|
205
215
|
id,
|
|
206
216
|
type,
|
|
207
|
-
domain: continuousDomain(options.domain ??
|
|
217
|
+
domain: continuousDomain(options.domain ?? previous?.domain ?? "auto", fieldType),
|
|
208
218
|
range: validateSequentialColorRange(
|
|
209
|
-
requestedRange ??
|
|
219
|
+
requestedRange ?? previous?.range ?? { palette: "viridis" }
|
|
210
220
|
),
|
|
211
221
|
interpolate: validateContinuousColorInterpolation(
|
|
212
|
-
options.interpolate ??
|
|
222
|
+
options.interpolate ?? previous?.interpolate ?? "rgb"
|
|
213
223
|
)
|
|
214
224
|
};
|
|
215
225
|
const midpoint = validateSequentialMidpoint(
|
|
216
|
-
Object.hasOwn(options, "midpoint") ? options.midpoint :
|
|
226
|
+
Object.hasOwn(options, "midpoint") ? options.midpoint : previous?.midpoint, type, scale.domain
|
|
217
227
|
);
|
|
218
228
|
if (midpoint !== undefined) {
|
|
219
229
|
if (fieldType !== "quantitative") {
|
|
@@ -222,30 +232,34 @@ export function resolveSequentialColorScaleDefinition(
|
|
|
222
232
|
scale.midpoint = midpoint;
|
|
223
233
|
}
|
|
224
234
|
return withScaleUnknown(
|
|
225
|
-
assignOptions(scale, options,
|
|
226
|
-
{ ...
|
|
227
|
-
|
|
235
|
+
assignOptions(scale, options, previous, CLAMP_REVERSE),
|
|
236
|
+
{ ...previous, ...options },
|
|
237
|
+
channel
|
|
228
238
|
);
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
export function resolveQuantitativeColorScaleDefinition(
|
|
232
242
|
program,
|
|
233
243
|
fieldType,
|
|
234
|
-
options
|
|
244
|
+
options,
|
|
245
|
+
channel = "color"
|
|
235
246
|
) {
|
|
236
247
|
optionsObject(options);
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
const existing = findSemanticScale(program, options.id ?? channel);
|
|
249
|
+
const existingContinuous = existing !== undefined && (
|
|
250
|
+
existing.type === "sequential" ||
|
|
251
|
+
["quantize", "quantile", "threshold"].includes(existing.type)
|
|
252
|
+
);
|
|
253
|
+
const type = options.type ?? (
|
|
254
|
+
existingContinuous ? existing.type : "sequential"
|
|
255
|
+
);
|
|
256
|
+
if (existingContinuous && existing.type !== type) {
|
|
243
257
|
validateKeys(options, type === "sequential" ? SEQUENTIAL_COLOR_OPTIONS : [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
|
|
244
258
|
if (fieldType !== "quantitative") throw new Error("Color scale type transition requires quantitative color.");
|
|
245
|
-
return { id: existing.id, ...prepareScaleEdit(program, existing,
|
|
259
|
+
return { id: existing.id, ...prepareScaleEdit(program, existing, channel, [], options) };
|
|
246
260
|
}
|
|
247
261
|
if (type === "sequential") {
|
|
248
|
-
return resolveSequentialColorScaleDefinition(program, fieldType, options);
|
|
262
|
+
return resolveSequentialColorScaleDefinition(program, fieldType, options, channel);
|
|
249
263
|
}
|
|
250
264
|
validateKeys(options, [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
|
|
251
265
|
if (fieldType !== "quantitative") {
|
|
@@ -254,7 +268,7 @@ export function resolveQuantitativeColorScaleDefinition(
|
|
|
254
268
|
validateScaleTypeForRole(type, SCALE_ROLES.discretizedColor);
|
|
255
269
|
validatePaletteRange(options);
|
|
256
270
|
validateBooleanOptions(options, CLAMP_REVERSE, type);
|
|
257
|
-
const id = validateUserId(options.id ??
|
|
271
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
258
272
|
const previous = existing?.type === type ? existing : undefined;
|
|
259
273
|
const requestedRange = options.palette === undefined
|
|
260
274
|
? options.range
|
|
@@ -273,7 +287,7 @@ export function resolveQuantitativeColorScaleDefinition(
|
|
|
273
287
|
return withScaleUnknown(
|
|
274
288
|
assignOptions(scale, options, previous, CLAMP_REVERSE),
|
|
275
289
|
{ ...existing, ...options },
|
|
276
|
-
|
|
290
|
+
channel
|
|
277
291
|
);
|
|
278
292
|
}
|
|
279
293
|
|
|
@@ -298,21 +312,23 @@ export function resolveStrokeDashScaleDefinition(program, options) {
|
|
|
298
312
|
|
|
299
313
|
export function resolveAppearanceScaleDefinition(program, channel, options) {
|
|
300
314
|
optionsObject(options);
|
|
315
|
+
if (channel === "size") {
|
|
316
|
+
validateKeys(options, SIZE_OPTIONS, "scale");
|
|
317
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
318
|
+
const existing = findSemanticScale(program, id);
|
|
319
|
+
return {
|
|
320
|
+
id,
|
|
321
|
+
...normalizeSizeScaleDefinition({ previous: existing, patch: options })
|
|
322
|
+
};
|
|
323
|
+
}
|
|
301
324
|
validateKeys(options, UNKNOWN_OPTIONS, "scale");
|
|
302
325
|
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
303
326
|
const existing = findSemanticScale(program, id);
|
|
304
|
-
const shape = channel === "shape";
|
|
305
327
|
return withScaleUnknown({
|
|
306
328
|
id,
|
|
307
|
-
type:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
domain: shape
|
|
311
|
-
? validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto")
|
|
312
|
-
: validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
313
|
-
range: shape
|
|
314
|
-
? validateShapeRange(options.range ?? existing?.range ?? "auto")
|
|
315
|
-
: validateSizeRange(options.range ?? existing?.range ?? "auto")
|
|
329
|
+
type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
|
|
330
|
+
domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
331
|
+
range: validateShapeRange(options.range ?? existing?.range ?? "auto")
|
|
316
332
|
}, { ...existing, ...options }, channel);
|
|
317
333
|
}
|
|
318
334
|
|
|
@@ -365,7 +381,45 @@ export function resolveStrokeWidthScaleDefinition(program, options) {
|
|
|
365
381
|
}
|
|
366
382
|
|
|
367
383
|
export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
|
|
368
|
-
|
|
369
|
-
|
|
384
|
+
optionsObject(options);
|
|
385
|
+
validateKeys(options, [
|
|
386
|
+
"id", "type", "domain", "range", "reverse",
|
|
387
|
+
"padding", "paddingInner", "paddingOuter", "align"
|
|
388
|
+
], "scale");
|
|
389
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
390
|
+
const existing = findSemanticScale(program, id);
|
|
391
|
+
const type = options.type ?? existing?.type ?? "ordinal";
|
|
392
|
+
if (type !== "ordinal") {
|
|
393
|
+
throw new Error(`Scale type "${type}" is not valid for ${channel}.`);
|
|
394
|
+
}
|
|
395
|
+
if (Object.hasOwn(options, "range") && options.range !== "auto") {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`${channel} scale range is derived from its parent categorical slot.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const consumers = findScaleConsumers(program, id).filter(
|
|
401
|
+
consumer => consumer.channel === channel
|
|
370
402
|
);
|
|
403
|
+
const currentPolicy = resolveRequestedOffsetPolicy({
|
|
404
|
+
scale: existing,
|
|
405
|
+
consumers,
|
|
406
|
+
markConfigs: program.markConfigs,
|
|
407
|
+
id,
|
|
408
|
+
channel
|
|
409
|
+
});
|
|
410
|
+
const policy = normalizeOffsetScalePolicy(options, currentPolicy, channel);
|
|
411
|
+
const patch = Object.fromEntries(Object.entries(options).filter(
|
|
412
|
+
([property]) => !["id", "padding"].includes(property)
|
|
413
|
+
));
|
|
414
|
+
return {
|
|
415
|
+
id,
|
|
416
|
+
...normalizeScaleDefinition({
|
|
417
|
+
type,
|
|
418
|
+
previous: existing,
|
|
419
|
+
patch: { ...patch, ...policy },
|
|
420
|
+
allowOrdinalBandParameters: true,
|
|
421
|
+
validateDomain: (_scaleType, value) => validateOrdinalDomain(value),
|
|
422
|
+
validateRange: (_scaleType, value) => validateScaleRange(value)
|
|
423
|
+
})
|
|
424
|
+
};
|
|
371
425
|
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
applyColorLegendTransition,
|
|
4
|
+
planColorLegendTransitions
|
|
5
|
+
} from "../guides/legends/transition.js";
|
|
3
6
|
import { action } from "../../core/action.js";
|
|
4
7
|
import { validateUserId } from "../../core/identifiers.js";
|
|
5
8
|
import { validateOptionObject } from "../../core/validation.js";
|
|
6
9
|
import { getMarkMaterializationStep, getSourceDependentMarkSteps } from "../../materialization/marks/index.js";
|
|
7
10
|
import {
|
|
8
|
-
applyMaterializationPlan
|
|
11
|
+
applyMaterializationPlan,
|
|
12
|
+
planCoordinateRematerialization
|
|
9
13
|
} from "../../materialization/dependencies.js";
|
|
14
|
+
import { findDataAspectCoordinatesForScale } from
|
|
15
|
+
"../../materialization/coordinateBounds.js";
|
|
10
16
|
import {
|
|
11
17
|
findSemanticScale,
|
|
12
18
|
requireSemanticScale
|
|
@@ -16,6 +22,8 @@ import {
|
|
|
16
22
|
prepareScaleEdit,
|
|
17
23
|
resolveScaleConsumerChannel
|
|
18
24
|
} from "./editPolicy.js";
|
|
25
|
+
import { hasExactLegendSamplingForScale } from
|
|
26
|
+
"../guides/legends/sampling.js";
|
|
19
27
|
|
|
20
28
|
const OPTIONS = Object.freeze([
|
|
21
29
|
"id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
|
|
@@ -71,9 +79,21 @@ function planMarkRematerialization(program, consumers) {
|
|
|
71
79
|
];
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
function applyScaleEdit(program, {
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
function applyScaleEdit(program, {
|
|
83
|
+
id,
|
|
84
|
+
scale,
|
|
85
|
+
channel,
|
|
86
|
+
consumers,
|
|
87
|
+
definition,
|
|
88
|
+
legendTransitions
|
|
89
|
+
}) {
|
|
90
|
+
let next = program;
|
|
91
|
+
for (const transition of legendTransitions) {
|
|
92
|
+
next = next.removeLegend({
|
|
93
|
+
target: transition.args.target,
|
|
94
|
+
channels: [transition.channel]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
77
97
|
for (const property of EDITABLE) {
|
|
78
98
|
if (
|
|
79
99
|
Object.hasOwn(scale, property) &&
|
|
@@ -97,12 +117,35 @@ function applyScaleEdit(program, { id, scale, consumers, definition, legendTrans
|
|
|
97
117
|
}
|
|
98
118
|
if (consumers.length === 0) return next;
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
const aspectCoordinates = findDataAspectCoordinatesForScale(next, id);
|
|
121
|
+
if (aspectCoordinates.length > 0) {
|
|
122
|
+
for (const coordinate of aspectCoordinates) {
|
|
123
|
+
next = applyMaterializationPlan(
|
|
124
|
+
next,
|
|
125
|
+
planCoordinateRematerialization(next, coordinate.id)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
next = next.rematerializeScale({ id });
|
|
130
|
+
next = applyMaterializationPlan(
|
|
131
|
+
next,
|
|
132
|
+
planMarkRematerialization(next, consumers)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (["xOffset", "yOffset"].includes(channel)) {
|
|
136
|
+
for (const consumer of consumers) {
|
|
137
|
+
next = next._withoutMaterializationConfig([
|
|
138
|
+
"marks", consumer.layer.id, channel
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const transition of legendTransitions) {
|
|
143
|
+
next = applyColorLegendTransition(next, transition);
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
103
146
|
}
|
|
104
147
|
|
|
105
|
-
export const editScale = action(
|
|
148
|
+
export const editScale = /* @__PURE__ */ action(
|
|
106
149
|
{
|
|
107
150
|
op: "editScale",
|
|
108
151
|
description: "Edit an existing scale and rematerialize its consumers."
|
|
@@ -118,10 +161,22 @@ export const editScale = action(
|
|
|
118
161
|
const channel = resolveScaleConsumerChannel(consumers, id);
|
|
119
162
|
const definition = prepareScaleEdit(this, scale, channel, consumers, args);
|
|
120
163
|
|
|
121
|
-
const
|
|
122
|
-
const proposal = {
|
|
164
|
+
const legendTransitions = planColorLegendTransitions(this, scale, definition.type);
|
|
165
|
+
const proposal = {
|
|
166
|
+
id,
|
|
167
|
+
scale,
|
|
168
|
+
channel,
|
|
169
|
+
consumers,
|
|
170
|
+
definition,
|
|
171
|
+
legendTransitions
|
|
172
|
+
};
|
|
123
173
|
// Preflight every dependent mark and guide on a discarded immutable branch.
|
|
124
|
-
if (
|
|
174
|
+
if (
|
|
175
|
+
scale.type !== definition.type ||
|
|
176
|
+
hasExactLegendSamplingForScale(this, id)
|
|
177
|
+
) {
|
|
178
|
+
applyScaleEdit(this, proposal);
|
|
179
|
+
}
|
|
125
180
|
return applyScaleEdit(this, proposal);
|
|
126
181
|
})
|
|
127
182
|
);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isDiscretizedColorScaleType,
|
|
5
5
|
isDiscretePositionScaleType,
|
|
6
6
|
isTransformedScaleType,
|
|
7
|
+
normalizeSizeScaleDefinition,
|
|
7
8
|
normalizeScaleDefinition,
|
|
8
9
|
SCALE_ROLES,
|
|
9
10
|
validateColorRange,
|
|
@@ -26,11 +27,20 @@ import {
|
|
|
26
27
|
validateRadialRange,
|
|
27
28
|
validateThetaRange
|
|
28
29
|
} from "../../grammar/polar.js";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
normalizePositionScaleChannel,
|
|
32
|
+
OFFSET_POSITION_CHANNELS
|
|
33
|
+
} from "../../core/vocabulary.js";
|
|
34
|
+
import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
|
|
35
|
+
import { resolveRequestedOffsetPolicy } from
|
|
36
|
+
"../../materialization/scales/policies/offset.js";
|
|
30
37
|
|
|
31
38
|
export function resolveScaleConsumerChannel(consumers, id) {
|
|
32
39
|
const channels = new Set(
|
|
33
|
-
consumers.map(consumer =>
|
|
40
|
+
consumers.map(consumer => {
|
|
41
|
+
const channel = normalizePositionScaleChannel(consumer.channel);
|
|
42
|
+
return channel === "stroke" ? "color" : channel;
|
|
43
|
+
})
|
|
34
44
|
);
|
|
35
45
|
if (channels.size > 1) {
|
|
36
46
|
throw new Error(`Scale "${id}" cannot be shared across channels.`);
|
|
@@ -40,6 +50,7 @@ export function resolveScaleConsumerChannel(consumers, id) {
|
|
|
40
50
|
|
|
41
51
|
function validateRangeForChannel(scale, channel, value) {
|
|
42
52
|
if (value === "auto") return value;
|
|
53
|
+
if (channel === "size") return validateSizeRange(value);
|
|
43
54
|
if (channel === "theta") return validateThetaRange(value);
|
|
44
55
|
if (channel === "radius") return validateRadialRange(value);
|
|
45
56
|
if (scale.type === "sequential") {
|
|
@@ -49,7 +60,7 @@ function validateRangeForChannel(scale, channel, value) {
|
|
|
49
60
|
return validateDiscretizedColorRange(value);
|
|
50
61
|
}
|
|
51
62
|
if (scale.type !== "ordinal") return validateScaleRange(value);
|
|
52
|
-
if (
|
|
63
|
+
if (["color", "stroke"].includes(channel)) return validateColorRange(value);
|
|
53
64
|
if (channel === "shape") return validateShapeRange(value);
|
|
54
65
|
if (channel === "strokeDash") return validateStrokeDashRange(value);
|
|
55
66
|
if (channel === "size") return validateSizeRange(value);
|
|
@@ -66,7 +77,23 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
66
77
|
throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
|
|
67
78
|
}
|
|
68
79
|
for (const consumer of consumers) {
|
|
69
|
-
validateContinuousColorConsumer(
|
|
80
|
+
validateContinuousColorConsumer(
|
|
81
|
+
consumer.layer,
|
|
82
|
+
consumer.encoding,
|
|
83
|
+
{ type: nextType },
|
|
84
|
+
{ channel: consumer.channel }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (nextType === "ordinal" && channel === "color") {
|
|
90
|
+
if (consumers.some(consumer =>
|
|
91
|
+
!["color", "stroke"].includes(consumer.channel) ||
|
|
92
|
+
!["nominal", "ordinal"].includes(consumer.encoding.fieldType)
|
|
93
|
+
)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Scale "${scale.id}" has a consumer incompatible with type "ordinal".`
|
|
96
|
+
);
|
|
70
97
|
}
|
|
71
98
|
return;
|
|
72
99
|
}
|
|
@@ -127,23 +154,65 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
|
|
130
|
-
function normalizeDefinition(scale, channel, consumers, patch) {
|
|
157
|
+
function normalizeDefinition(program, scale, channel, consumers, patch) {
|
|
131
158
|
if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
|
|
132
159
|
patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
|
|
133
160
|
throw new Error("Remove measured radius encodings before clearing their scale mapping.");
|
|
134
161
|
}
|
|
162
|
+
if (channel === "size") {
|
|
163
|
+
if (consumers.some(consumer =>
|
|
164
|
+
consumer.layer.mark?.type !== "point" ||
|
|
165
|
+
consumer.encoding.fieldType !== "quantitative"
|
|
166
|
+
)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Scale "${scale.id}" has a consumer incompatible with size mapping.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return normalizeSizeScaleDefinition({ previous: scale, patch });
|
|
172
|
+
}
|
|
135
173
|
const type = patch.type ?? scale.type;
|
|
136
174
|
validateTypeTransition(scale, type, channel, consumers);
|
|
175
|
+
const offset = OFFSET_POSITION_CHANNELS.includes(channel);
|
|
176
|
+
if (offset && Object.hasOwn(patch, "range")) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`${channel} scale range is derived from its parent categorical slot.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (offset && Object.hasOwn(patch, "unknown")) {
|
|
182
|
+
throw new Error(`${channel} scale unknown is not supported for offset positions.`);
|
|
183
|
+
}
|
|
137
184
|
if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
|
|
138
185
|
(["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
|
|
139
186
|
throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
|
|
140
187
|
}
|
|
188
|
+
const offsetPolicy = offset
|
|
189
|
+
? normalizeOffsetScalePolicy(
|
|
190
|
+
patch,
|
|
191
|
+
resolveRequestedOffsetPolicy({
|
|
192
|
+
scale,
|
|
193
|
+
consumers,
|
|
194
|
+
markConfigs: program.markConfigs,
|
|
195
|
+
id: scale.id,
|
|
196
|
+
channel
|
|
197
|
+
}),
|
|
198
|
+
channel
|
|
199
|
+
)
|
|
200
|
+
: undefined;
|
|
201
|
+
const normalizedPatch = offset
|
|
202
|
+
? {
|
|
203
|
+
...Object.fromEntries(Object.entries(patch).filter(
|
|
204
|
+
([property]) => property !== "padding"
|
|
205
|
+
)),
|
|
206
|
+
...offsetPolicy
|
|
207
|
+
}
|
|
208
|
+
: patch;
|
|
141
209
|
const definition = normalizeScaleDefinition({
|
|
142
210
|
type,
|
|
143
211
|
previous: scale,
|
|
144
|
-
patch,
|
|
212
|
+
patch: normalizedPatch,
|
|
145
213
|
retainCoreOnTypeChange: true,
|
|
146
214
|
retainCompatibleOnTypeChange: true,
|
|
215
|
+
allowOrdinalBandParameters: offset,
|
|
147
216
|
validateDomain: (scaleType, value) =>
|
|
148
217
|
isDiscretizedColorScaleType(scaleType)
|
|
149
218
|
? validateDiscretizedColorDomain(scaleType, value)
|
|
@@ -162,13 +231,14 @@ function normalizeDefinition(scale, channel, consumers, patch) {
|
|
|
162
231
|
}
|
|
163
232
|
validateSequentialMidpoint(definition.midpoint, type, definition.domain);
|
|
164
233
|
if (definition.midpoint !== undefined && consumers.some(
|
|
165
|
-
consumer =>
|
|
234
|
+
consumer => !["color", "stroke"].includes(consumer.channel) ||
|
|
235
|
+
consumer.encoding.fieldType !== "quantitative"
|
|
166
236
|
)) {
|
|
167
237
|
throw new Error("Scale midpoint requires quantitative color consumers.");
|
|
168
238
|
}
|
|
169
239
|
const typeChanged = type !== scale.type;
|
|
170
|
-
const unknown = Object.hasOwn(
|
|
171
|
-
?
|
|
240
|
+
const unknown = Object.hasOwn(normalizedPatch, "unknown")
|
|
241
|
+
? normalizedPatch.unknown
|
|
172
242
|
: typeChanged ? undefined : scale.unknown;
|
|
173
243
|
if (unknown !== undefined) {
|
|
174
244
|
if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
|
|
@@ -188,7 +258,7 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
|
|
|
188
258
|
}
|
|
189
259
|
if (
|
|
190
260
|
hasPalette &&
|
|
191
|
-
|
|
261
|
+
!["color", "stroke"].includes(channel) &&
|
|
192
262
|
scale.type !== "sequential" &&
|
|
193
263
|
!isDiscretizedColorScaleType(scale.type)
|
|
194
264
|
) {
|
|
@@ -200,5 +270,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
|
|
|
200
270
|
...Object.entries(args).filter(([key]) => key !== "palette"),
|
|
201
271
|
["range", { palette: args.palette }]
|
|
202
272
|
]);
|
|
203
|
-
return normalizeDefinition(scale, channel, consumers, patch);
|
|
273
|
+
return normalizeDefinition(program, scale, channel, consumers, patch);
|
|
204
274
|
}
|
|
@@ -2,9 +2,15 @@ import { createScale } from "./create.js";
|
|
|
2
2
|
import { editScale } from "./edit.js";
|
|
3
3
|
import { rematerializeScale } from "./materialize.js";
|
|
4
4
|
import { setQuantitativeColorScale } from "./quantitativeColor.js";
|
|
5
|
+
import { registerChannelScaleActions } from "./channels.js";
|
|
6
|
+
import { editParallelScale } from "./parallel.js";
|
|
7
|
+
import { registerOffsetScaleActions } from "./offset.js";
|
|
5
8
|
|
|
6
9
|
export function registerScaleActions(ProgramClass) {
|
|
7
10
|
ProgramClass.prototype.editScale = editScale;
|
|
11
|
+
ProgramClass.prototype.editParallelScale = editParallelScale;
|
|
12
|
+
registerOffsetScaleActions(ProgramClass);
|
|
13
|
+
registerChannelScaleActions(ProgramClass);
|
|
8
14
|
registerBasicScaleActions(ProgramClass);
|
|
9
15
|
}
|
|
10
16
|
|