ggaction 0.0.12 → 0.0.13
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 +49 -0
- package/README.md +2 -2
- 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 +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- 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 +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.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/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- 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 +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- 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 +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- 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 +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +21 -10
- package/src/actions/errorBars/edit.js +355 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- 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 +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- 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 +44 -23
- 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 +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- 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 +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- 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 +106 -2
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +34 -3
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- 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 +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- 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 +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +7 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +41 -7
- package/src/layout/guideCollisions.js +12 -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 +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facets.js +8 -1
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readTemporalField
|
|
6
|
-
} from "../../../grammar/scales/index.js";
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
2
|
+
import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
|
|
3
|
+
import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
|
|
4
|
+
import { readScaleField } from "../../../grammar/scales/index.js";
|
|
7
5
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
8
6
|
import { requireSemanticScale } from "../../../selectors/scales.js";
|
|
9
7
|
import { SCALED_ENCODING_CHANNELS } from "../../../core/vocabulary.js";
|
|
@@ -15,6 +13,7 @@ export function findScale(program, id) {
|
|
|
15
13
|
export function findScaleConsumers(program, id) {
|
|
16
14
|
const consumers = [];
|
|
17
15
|
for (const layer of program.semanticSpec.layers) {
|
|
16
|
+
if (isSourceOwnedText(layer)) continue;
|
|
18
17
|
for (const channel of SCALED_ENCODING_CHANNELS) {
|
|
19
18
|
const encoding = layer.encoding?.[channel];
|
|
20
19
|
if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
|
|
@@ -46,7 +45,7 @@ export function requireConsumerDataset(program, consumer) {
|
|
|
46
45
|
export function isDirectCategoricalConsumer(consumer) {
|
|
47
46
|
return ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(
|
|
48
47
|
consumer.channel
|
|
49
|
-
) && consumer.encoding.fieldType
|
|
48
|
+
) && ["nominal", "ordinal"].includes(consumer.encoding.fieldType);
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
export function readConsumerFieldValues(
|
|
@@ -55,55 +54,23 @@ export function readConsumerFieldValues(
|
|
|
55
54
|
dataset,
|
|
56
55
|
scale = findScale(program, consumer.encoding.scale)
|
|
57
56
|
) {
|
|
58
|
-
const
|
|
59
|
-
if (consumer.
|
|
60
|
-
return
|
|
61
|
-
dataset.values,
|
|
62
|
-
consumer.encoding.field,
|
|
63
|
-
consumer.encoding.fieldType,
|
|
64
|
-
{ allowUnknown: true }
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
if (isDirectCategoricalConsumer(consumer)) {
|
|
68
|
-
return allowUnknown
|
|
69
|
-
? readScaleField(
|
|
70
|
-
dataset.values,
|
|
71
|
-
consumer.encoding.field,
|
|
72
|
-
consumer.encoding.fieldType,
|
|
73
|
-
{ allowUnknown: true }
|
|
74
|
-
)
|
|
75
|
-
: readNominalField(dataset.values, consumer.encoding.field);
|
|
76
|
-
}
|
|
77
|
-
if (consumer.encoding.fieldType === "temporal") {
|
|
78
|
-
return allowUnknown
|
|
79
|
-
? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
|
|
80
|
-
allowUnknown: true
|
|
81
|
-
})
|
|
82
|
-
: readTemporalField(dataset.values, consumer.encoding.field);
|
|
57
|
+
const { field, fieldType, temporalUnit } = consumer.encoding;
|
|
58
|
+
if (consumer.layer.mark.type === "bar" && consumer.layer.encoding?.group !== undefined && ["xOffset", "yOffset"].includes(consumer.channel)) {
|
|
59
|
+
return readSeriesIdentity(dataset.values, consumer.layer).values;
|
|
83
60
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
return allowUnknown
|
|
91
|
-
? readScaleField(
|
|
92
|
-
dataset.values,
|
|
93
|
-
consumer.encoding.field,
|
|
94
|
-
consumer.encoding.fieldType,
|
|
95
|
-
{ allowUnknown: true }
|
|
96
|
-
)
|
|
97
|
-
: readNominalField(dataset.values, consumer.encoding.field);
|
|
98
|
-
}
|
|
99
|
-
if (consumer.encoding.fieldType !== "quantitative") {
|
|
61
|
+
const parallel = consumer.role === "parallelDimension";
|
|
62
|
+
if (!parallel && !isDirectCategoricalConsumer(consumer) &&
|
|
63
|
+
fieldType !== "quantitative" && fieldType !== "temporal" &&
|
|
64
|
+
(!["nominal", "ordinal"].includes(fieldType) ||
|
|
65
|
+
!["ordinal", "band", "point"].includes(scale.type))) {
|
|
100
66
|
throw new Error(
|
|
101
67
|
`Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
|
|
102
68
|
);
|
|
103
69
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
:
|
|
70
|
+
if (consumer.layer.mark.type === "area" && ["x", "y", "x2", "y2"].includes(consumer.channel) && fieldType === "quantitative") {
|
|
71
|
+
return readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing).filter(value => value != null);
|
|
72
|
+
}
|
|
73
|
+
return readScaleField(dataset.values, field, fieldType, {
|
|
74
|
+
allowUnknown: parallel || Object.hasOwn(scale, "unknown"), temporalUnit
|
|
75
|
+
});
|
|
109
76
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isPendingMeasuredRadiusConsumer } from "../../../materialization/scales/policies/arc.js";
|
|
2
|
+
import { deriveMeasuredArcValues } from "../../../grammar/arcs.js";
|
|
1
3
|
import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
|
|
2
4
|
import {
|
|
3
5
|
BAR_GRAINS,
|
|
@@ -5,9 +7,9 @@ import {
|
|
|
5
7
|
} from "../../../grammar/bars/policy.js";
|
|
6
8
|
import {
|
|
7
9
|
deriveLineSeries,
|
|
8
|
-
deriveLineSeriesFieldValues,
|
|
9
10
|
resolveLineBins
|
|
10
11
|
} from "../../../grammar/lineSeries.js";
|
|
12
|
+
import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
|
|
11
13
|
import { isAggregate } from "../../../grammar/aggregate.js";
|
|
12
14
|
import {
|
|
13
15
|
resolveRectConsumerValues
|
|
@@ -26,6 +28,11 @@ function lineDerivationOptions(program, consumer, dataset) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
|
|
31
|
+
if (consumer.layer.mark?.type === "arc" && consumer.channel === "radius" &&
|
|
32
|
+
findScale(program, consumer.encoding.scale).radialMapping !== undefined) {
|
|
33
|
+
return { matched: true, values: isPendingMeasuredRadiusConsumer(consumer) ? [] : deriveMeasuredArcValues(dataset.values, consumer.layer).map(item => item.radius) };
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
if (consumer.layer.mark?.type === "rect") {
|
|
30
37
|
return {
|
|
31
38
|
matched: true,
|
|
@@ -36,29 +43,35 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
|
|
|
36
43
|
)
|
|
37
44
|
};
|
|
38
45
|
}
|
|
46
|
+
const { layer, channel } = consumer;
|
|
47
|
+
const encoding = layer.encoding ?? {};
|
|
48
|
+
const appearance = ["strokeWidth", "opacity"].includes(channel);
|
|
39
49
|
if (
|
|
40
|
-
|
|
41
|
-
((
|
|
42
|
-
isAggregate(
|
|
43
|
-
!(
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
layer.mark?.type === "line" &&
|
|
51
|
+
((["x", "y"].includes(channel) && encoding.x !== undefined &&
|
|
52
|
+
isAggregate(encoding.y?.aggregate) &&
|
|
53
|
+
!(channel === "x" &&
|
|
54
|
+
encoding.x?.bin !== undefined)) ||
|
|
55
|
+
appearance) &&
|
|
56
|
+
encoding.parallel === undefined &&
|
|
57
|
+
((encoding.x !== undefined && encoding.y !== undefined) ||
|
|
58
|
+
(encoding.theta !== undefined && encoding.radius !== undefined))
|
|
46
59
|
) {
|
|
47
60
|
const derived = deriveLineSeries(
|
|
48
61
|
dataset.values,
|
|
49
|
-
|
|
62
|
+
layer,
|
|
50
63
|
lineDerivationOptions(program, consumer, dataset)
|
|
51
64
|
);
|
|
52
65
|
return {
|
|
53
66
|
matched: true,
|
|
54
|
-
values:
|
|
55
|
-
?
|
|
67
|
+
values: appearance
|
|
68
|
+
? derivePathSeriesFieldValues(
|
|
56
69
|
dataset.values,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
derived.series,
|
|
71
|
+
consumer.encoding.field,
|
|
72
|
+
channel
|
|
60
73
|
)
|
|
61
|
-
:
|
|
74
|
+
: channel === "x" ? derived.xValues : derived.yValues
|
|
62
75
|
};
|
|
63
76
|
}
|
|
64
77
|
if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
|
|
1
2
|
import { normalizeRuleDatum } from "../../../grammar/rules.js";
|
|
3
|
+
import { normalizePositionDatum } from "../../../grammar/positionDatum.js";
|
|
2
4
|
import {
|
|
3
5
|
findScale,
|
|
4
6
|
isDirectCategoricalConsumer,
|
|
@@ -6,7 +8,7 @@ import {
|
|
|
6
8
|
requireConsumerDataset
|
|
7
9
|
} from "./common.js";
|
|
8
10
|
import { resolveMarkFamilyConsumerValues } from "./families.js";
|
|
9
|
-
import { resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
|
|
11
|
+
import { CATEGORY_ORDER_CHANNELS, resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
|
|
10
12
|
|
|
11
13
|
export { findScale, findScaleConsumers } from "./common.js";
|
|
12
14
|
export {
|
|
@@ -16,11 +18,26 @@ export {
|
|
|
16
18
|
|
|
17
19
|
export function resolveConsumerValues(program, consumer) {
|
|
18
20
|
const dataset = requireConsumerDataset(program, consumer);
|
|
21
|
+
if (consumer.layer.mark.type === "rect") return resolveMarkFamilyConsumerValues(program, consumer, dataset).values;
|
|
19
22
|
if (Object.hasOwn(consumer.encoding, "datum")) {
|
|
23
|
+
if (consumer.layer.mark.type === "area") {
|
|
24
|
+
readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing);
|
|
25
|
+
return [consumer.encoding.datum];
|
|
26
|
+
}
|
|
27
|
+
if (consumer.layer.mark.type === "text") {
|
|
28
|
+
return [normalizePositionDatum(
|
|
29
|
+
consumer.encoding.datum,
|
|
30
|
+
consumer.encoding.fieldType,
|
|
31
|
+
consumer.channel,
|
|
32
|
+
consumer.encoding.temporalUnit,
|
|
33
|
+
"Text"
|
|
34
|
+
)];
|
|
35
|
+
}
|
|
20
36
|
return [normalizeRuleDatum(
|
|
21
37
|
consumer.encoding.datum,
|
|
22
38
|
consumer.encoding.fieldType,
|
|
23
|
-
consumer.channel
|
|
39
|
+
consumer.channel,
|
|
40
|
+
consumer.encoding.temporalUnit
|
|
24
41
|
)];
|
|
25
42
|
}
|
|
26
43
|
if (
|
|
@@ -44,6 +61,10 @@ export function resolveConsumerValues(program, consumer) {
|
|
|
44
61
|
export function resolveConsumerCategoryOrder(program, consumer) {
|
|
45
62
|
const order = consumer.encoding.categoryOrder;
|
|
46
63
|
if (order === undefined) return undefined;
|
|
64
|
+
if (!CATEGORY_ORDER_CHANNELS.includes(consumer.channel) ||
|
|
65
|
+
!["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
|
|
66
|
+
throw new Error("Remove category order before assigning a non-categorical position.");
|
|
67
|
+
}
|
|
47
68
|
const dataset = requireConsumerDataset(program, consumer);
|
|
48
69
|
return resolveCategoryOrder(dataset.values, consumer.encoding.field, order);
|
|
49
70
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
|
|
2
|
+
import { deriveAreaSeries } from "../../../grammar/areaSeries.js";
|
|
1
3
|
import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
|
|
2
4
|
import { validateGeneratedItemLimit } from "../../../core/validation.js";
|
|
3
5
|
import {
|
|
@@ -67,6 +69,15 @@ function resolveHistogramPartitions(program, consumer) {
|
|
|
67
69
|
zero: xScale.zero ?? false
|
|
68
70
|
});
|
|
69
71
|
const colorEncoding = layer.encoding?.color;
|
|
72
|
+
if (layer.encoding?.group !== undefined) {
|
|
73
|
+
const identity = readSeriesIdentity(dataset.values, layer);
|
|
74
|
+
validateGeneratedItemLimit((bins.boundaries.length - 1) * identity.domain.length, "Histogram layout cell count");
|
|
75
|
+
const counts = bins.boundaries.slice(0, -1).map(() => identity.domain.map(() => 0));
|
|
76
|
+
const groups = new Map(identity.domain.map((value, index) => [value, index]));
|
|
77
|
+
xValues.forEach((value, index) => { const bin = findHistogramBinIndex(value, bins.boundaries);
|
|
78
|
+
if (bin !== -1) counts[bin][groups.get(identity.values[index])] += 1; });
|
|
79
|
+
return counts;
|
|
80
|
+
}
|
|
70
81
|
if (colorEncoding?.scale === undefined) {
|
|
71
82
|
return countHistogramBins(xValues, bins.boundaries).map(value => [value]);
|
|
72
83
|
}
|
|
@@ -101,6 +112,12 @@ function resolveAggregatePartitions(program, consumer) {
|
|
|
101
112
|
: derived.yValues;
|
|
102
113
|
const categoryDomain = resolveOrdinalDomain(categoryScale.domain, categoryValues);
|
|
103
114
|
const colorEncoding = layer.encoding?.color;
|
|
115
|
+
if (layer.encoding?.group !== undefined) {
|
|
116
|
+
const series = readSeriesIdentity(dataset.values, layer).domain;
|
|
117
|
+
validateGeneratedItemLimit(categoryDomain.length * series.length, "Aggregate layout cell count");
|
|
118
|
+
const cells = new Map(derived.values.map(value => [JSON.stringify([value[channels.category], value.series]), value[channels.measure]]));
|
|
119
|
+
return categoryDomain.map(category => series.map(key => cells.get(JSON.stringify([category, key])) ?? 0));
|
|
120
|
+
}
|
|
104
121
|
if (
|
|
105
122
|
colorEncoding?.scale === undefined ||
|
|
106
123
|
colorEncoding.fieldType === "quantitative"
|
|
@@ -181,11 +198,25 @@ export function resolveSeriesLayoutScaleValues(program, consumer) {
|
|
|
181
198
|
values: resolveSeriesLayoutDomainValues(partitions, layout)
|
|
182
199
|
};
|
|
183
200
|
}
|
|
201
|
+
if (consumer.layer.mark?.type === "area" && consumer.layer.layout?.mode !== undefined) {
|
|
202
|
+
const layer = consumer.layer;
|
|
203
|
+
const dataset = findDataset(program, layer.data);
|
|
204
|
+
const density = dataset.transform?.some(transform => transform.type === "density");
|
|
205
|
+
if (!density && (layer.encoding?.x === undefined || layer.encoding?.y === undefined || (layer.encoding?.x2 === undefined && layer.encoding?.y2 === undefined))) return undefined;
|
|
206
|
+
if (!density && layer.encoding?.x !== undefined && layer.encoding?.y !== undefined && (layer.encoding?.x2 !== undefined || layer.encoding?.y2 !== undefined)) {
|
|
207
|
+
const derived = deriveAreaSeries(dataset.values, layer);
|
|
208
|
+
const measure = derived.orientation === "vertical" ? "y" : "x";
|
|
209
|
+
if (![measure, `${measure}2`].includes(consumer.channel)) return undefined;
|
|
210
|
+
const layout = layer.layout.mode;
|
|
211
|
+
if (layout === "overlay") return undefined;
|
|
212
|
+
return { layout, values: layout === "fill" ? [0, 1] : derived[`${measure}Values`] };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
184
215
|
if (consumer.channel !== "y") return undefined;
|
|
185
216
|
if (consumer.layer.mark?.type === "area") {
|
|
186
|
-
const layout = consumer.layer.encoding?.y?.stack === "center"
|
|
217
|
+
const layout = consumer.layer.layout?.mode ?? (consumer.layer.encoding?.y?.stack === "center"
|
|
187
218
|
? "center"
|
|
188
|
-
: consumer.layer.encoding?.color?.layout;
|
|
219
|
+
: consumer.layer.encoding?.color?.layout);
|
|
189
220
|
if (layout === undefined || layout === "overlay") return undefined;
|
|
190
221
|
const partitions = resolveAreaPartitions(program, consumer);
|
|
191
222
|
if (partitions === undefined) {
|
|
@@ -35,8 +35,8 @@ const CREATE_SCALE_OPTIONS = Object.freeze([
|
|
|
35
35
|
"padding",
|
|
36
36
|
"align",
|
|
37
37
|
"palette",
|
|
38
|
-
"interpolate",
|
|
39
|
-
"unknown"
|
|
38
|
+
"interpolate", "midpoint",
|
|
39
|
+
"radialMapping", "unknown"
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
function validateOptions(args) {
|
|
@@ -133,8 +133,8 @@ export const createScale = action(
|
|
|
133
133
|
|
|
134
134
|
for (const property of [
|
|
135
135
|
"nice", "zero", "clamp", "reverse", "base", "exponent", "constant",
|
|
136
|
-
"paddingInner", "paddingOuter", "padding", "align", "interpolate",
|
|
137
|
-
"unknown"
|
|
136
|
+
"paddingInner", "paddingOuter", "padding", "align", "interpolate", "midpoint",
|
|
137
|
+
"radialMapping", "unknown"
|
|
138
138
|
]) {
|
|
139
139
|
if (definition[property] === undefined) continue;
|
|
140
140
|
next = next.editSemantic({
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { prepareScaleEdit } from "./editPolicy.js";
|
|
1
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
2
3
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
4
|
import { validateKeys } from "../../core/validation.js";
|
|
4
5
|
import {
|
|
5
6
|
validateColorRange,
|
|
6
7
|
validateContinuousColorInterpolation,
|
|
8
|
+
validateSequentialMidpoint,
|
|
7
9
|
validateLinearScaleType,
|
|
8
10
|
validateOpacityRange,
|
|
9
11
|
validateOrdinalDomain,
|
|
@@ -49,7 +51,7 @@ const POSITION_OPTIONS = [
|
|
|
49
51
|
const COLOR_OPTIONS = [...BASE_OPTIONS, "palette", "unknown"];
|
|
50
52
|
const SEQUENTIAL_COLOR_OPTIONS = [
|
|
51
53
|
...COLOR_OPTIONS,
|
|
52
|
-
"interpolate",
|
|
54
|
+
"interpolate", "midpoint",
|
|
53
55
|
...CLAMP_REVERSE
|
|
54
56
|
];
|
|
55
57
|
const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
|
|
@@ -121,7 +123,7 @@ export function resolvePositionScaleDefinition(
|
|
|
121
123
|
...normalizeScaleDefinition({
|
|
122
124
|
type,
|
|
123
125
|
previous: existing,
|
|
124
|
-
patch: options,
|
|
126
|
+
patch: { ...options, ...(defaults.radialMapping === undefined ? {} : { radialMapping: defaults.radialMapping }) },
|
|
125
127
|
defaults: {
|
|
126
128
|
...(existing === undefined && defaults.nice !== undefined
|
|
127
129
|
? { nice: defaults.nice }
|
|
@@ -210,6 +212,15 @@ export function resolveSequentialColorScaleDefinition(
|
|
|
210
212
|
options.interpolate ?? existing?.interpolate ?? "rgb"
|
|
211
213
|
)
|
|
212
214
|
};
|
|
215
|
+
const midpoint = validateSequentialMidpoint(
|
|
216
|
+
Object.hasOwn(options, "midpoint") ? options.midpoint : existing?.midpoint, type, scale.domain
|
|
217
|
+
);
|
|
218
|
+
if (midpoint !== undefined) {
|
|
219
|
+
if (fieldType !== "quantitative") {
|
|
220
|
+
throw new Error("Scale midpoint requires quantitative color consumers.");
|
|
221
|
+
}
|
|
222
|
+
scale.midpoint = midpoint;
|
|
223
|
+
}
|
|
213
224
|
return withScaleUnknown(
|
|
214
225
|
assignOptions(scale, options, existing, CLAMP_REVERSE),
|
|
215
226
|
{ ...existing, ...options },
|
|
@@ -227,6 +238,12 @@ export function resolveQuantitativeColorScaleDefinition(
|
|
|
227
238
|
program,
|
|
228
239
|
options.id ?? "color"
|
|
229
240
|
)?.type ?? "sequential";
|
|
241
|
+
const existing = findSemanticScale(program, options.id ?? "color");
|
|
242
|
+
if (existing !== undefined && existing.type !== type) {
|
|
243
|
+
validateKeys(options, type === "sequential" ? SEQUENTIAL_COLOR_OPTIONS : [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
|
|
244
|
+
if (fieldType !== "quantitative") throw new Error("Color scale type transition requires quantitative color.");
|
|
245
|
+
return { id: existing.id, ...prepareScaleEdit(program, existing, "color", [], options) };
|
|
246
|
+
}
|
|
230
247
|
if (type === "sequential") {
|
|
231
248
|
return resolveSequentialColorScaleDefinition(program, fieldType, options);
|
|
232
249
|
}
|
|
@@ -238,7 +255,6 @@ export function resolveQuantitativeColorScaleDefinition(
|
|
|
238
255
|
validatePaletteRange(options);
|
|
239
256
|
validateBooleanOptions(options, CLAMP_REVERSE, type);
|
|
240
257
|
const id = validateUserId(options.id ?? "color", "Scale id");
|
|
241
|
-
const existing = findSemanticScale(program, id);
|
|
242
258
|
const previous = existing?.type === type ? existing : undefined;
|
|
243
259
|
const requestedRange = options.palette === undefined
|
|
244
260
|
? options.range
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
|
|
2
|
+
import { planColorLegendTransition, applyColorLegendTransition } from "../guides/legends/transition.js";
|
|
1
3
|
import { action } from "../../core/action.js";
|
|
2
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
5
|
import { validateOptionObject } from "../../core/validation.js";
|
|
4
|
-
import { getMarkMaterializationStep } from "../../materialization/marks/index.js";
|
|
6
|
+
import { getMarkMaterializationStep, getSourceDependentMarkSteps } from "../../materialization/marks/index.js";
|
|
5
7
|
import {
|
|
6
8
|
applyMaterializationPlan
|
|
7
9
|
} from "../../materialization/dependencies.js";
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
const OPTIONS = Object.freeze([
|
|
19
21
|
"id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
|
|
20
22
|
"base", "exponent", "constant", "paddingInner", "paddingOuter", "padding",
|
|
21
|
-
"align", "interpolate", "unknown", "palette"
|
|
23
|
+
"align", "interpolate", "midpoint", "unknown", "palette", "radialMapping"
|
|
22
24
|
]);
|
|
23
25
|
const EDITABLE = Object.freeze(
|
|
24
26
|
OPTIONS.filter(option => !["id", "palette"].includes(option))
|
|
@@ -63,7 +65,41 @@ function planMarkRematerialization(program, consumers) {
|
|
|
63
65
|
seen.add(consumer.layer.id);
|
|
64
66
|
plan.push(step);
|
|
65
67
|
}
|
|
66
|
-
return
|
|
68
|
+
return [
|
|
69
|
+
...plan,
|
|
70
|
+
...consumers.flatMap(consumer => getSourceDependentMarkSteps(program, consumer.layer.id))
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function applyScaleEdit(program, { id, scale, consumers, definition, legendTransition }) {
|
|
75
|
+
let next = legendTransition === undefined ? program
|
|
76
|
+
: program.removeLegend({ target: legendTransition.args.target, channels: ["color"] });
|
|
77
|
+
for (const property of EDITABLE) {
|
|
78
|
+
if (
|
|
79
|
+
Object.hasOwn(scale, property) &&
|
|
80
|
+
!Object.hasOwn(definition, property)
|
|
81
|
+
) {
|
|
82
|
+
next = next.editSemantic({
|
|
83
|
+
property: `scale[${id}].${property}`,
|
|
84
|
+
remove: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const property of EDITABLE) {
|
|
89
|
+
if (
|
|
90
|
+
!Object.hasOwn(definition, property) ||
|
|
91
|
+
sameValue(scale[property], definition[property])
|
|
92
|
+
) continue;
|
|
93
|
+
next = next.editSemantic({
|
|
94
|
+
property: `scale[${id}].${property}`,
|
|
95
|
+
value: definition[property]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (consumers.length === 0) return next;
|
|
99
|
+
|
|
100
|
+
next = next.rematerializeScale({ id });
|
|
101
|
+
next = applyMaterializationPlan(next, planMarkRematerialization(next, consumers));
|
|
102
|
+
return legendTransition === undefined ? next : applyColorLegendTransition(next, legendTransition);
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
export const editScale = action(
|
|
@@ -71,7 +107,7 @@ export const editScale = action(
|
|
|
71
107
|
op: "editScale",
|
|
72
108
|
description: "Edit an existing scale and rematerialize its consumers."
|
|
73
109
|
},
|
|
74
|
-
function (args = {}) {
|
|
110
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
75
111
|
validateOptionObject(args, OPTIONS, "editScale");
|
|
76
112
|
if (!REQUESTED_CHANGES.some(property => Object.hasOwn(args, property))) {
|
|
77
113
|
throw new Error("editScale requires at least one editable property.");
|
|
@@ -82,34 +118,10 @@ export const editScale = action(
|
|
|
82
118
|
const channel = resolveScaleConsumerChannel(consumers, id);
|
|
83
119
|
const definition = prepareScaleEdit(this, scale, channel, consumers, args);
|
|
84
120
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
next = next.editSemantic({
|
|
92
|
-
property: `scale[${id}].${property}`,
|
|
93
|
-
remove: true
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
for (const property of EDITABLE) {
|
|
98
|
-
if (
|
|
99
|
-
!Object.hasOwn(definition, property) ||
|
|
100
|
-
sameValue(scale[property], definition[property])
|
|
101
|
-
) continue;
|
|
102
|
-
next = next.editSemantic({
|
|
103
|
-
property: `scale[${id}].${property}`,
|
|
104
|
-
value: definition[property]
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (consumers.length === 0) return next;
|
|
108
|
-
|
|
109
|
-
next = next.rematerializeScale({ id });
|
|
110
|
-
return applyMaterializationPlan(
|
|
111
|
-
next,
|
|
112
|
-
planMarkRematerialization(next, consumers)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
121
|
+
const legendTransition = planColorLegendTransition(this, scale, definition.type);
|
|
122
|
+
const proposal = { id, scale, consumers, definition, legendTransition };
|
|
123
|
+
// Preflight every dependent mark and guide on a discarded immutable branch.
|
|
124
|
+
if (scale.type !== definition.type) applyScaleEdit(this, proposal);
|
|
125
|
+
return applyScaleEdit(this, proposal);
|
|
126
|
+
})
|
|
115
127
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
|
|
1
2
|
import {
|
|
2
3
|
hasOrdinalDomain,
|
|
3
4
|
isDiscretizedColorScaleType,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
validateScaleType,
|
|
15
16
|
validateScaleTypeForRole,
|
|
16
17
|
validateScaleUnknown,
|
|
18
|
+
validateSequentialMidpoint,
|
|
17
19
|
validateSequentialColorRange,
|
|
18
20
|
validateShapeRange,
|
|
19
21
|
validateSizeRange,
|
|
@@ -60,18 +62,11 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
60
62
|
validateScaleType(nextType);
|
|
61
63
|
if (consumers.length === 0) return;
|
|
62
64
|
if (nextType === "sequential" || isDiscretizedColorScaleType(nextType)) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
consumer.layer.mark?.type !== "point"
|
|
69
|
-
)) ||
|
|
70
|
-
(!discretized && !["point", "bar"].includes(consumer.layer.mark?.type))
|
|
71
|
-
)) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
|
|
74
|
-
);
|
|
65
|
+
if (channel !== "color") {
|
|
66
|
+
throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
|
|
67
|
+
}
|
|
68
|
+
for (const consumer of consumers) {
|
|
69
|
+
validateContinuousColorConsumer(consumer.layer, consumer.encoding, { type: nextType });
|
|
75
70
|
}
|
|
76
71
|
return;
|
|
77
72
|
}
|
|
@@ -132,27 +127,17 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
132
127
|
}
|
|
133
128
|
}
|
|
134
129
|
|
|
135
|
-
function validateLegendTypeTransition(program, scale, nextType) {
|
|
136
|
-
if (nextType === scale.type) return;
|
|
137
|
-
const legends = program.guideConfigs.legend ?? {};
|
|
138
|
-
if (legends.gradient?.scale === scale.id && nextType !== "sequential") {
|
|
139
|
-
throw new Error(
|
|
140
|
-
`Scale "${scale.id}" cannot change type while its gradient legend is active.`
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
if (
|
|
144
|
-
legends.interval?.scale === scale.id &&
|
|
145
|
-
!isDiscretizedColorScaleType(nextType)
|
|
146
|
-
) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
`Scale "${scale.id}" cannot change type while its interval legend is active.`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
130
|
function normalizeDefinition(scale, channel, consumers, patch) {
|
|
131
|
+
if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
|
|
132
|
+
patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
|
|
133
|
+
throw new Error("Remove measured radius encodings before clearing their scale mapping.");
|
|
134
|
+
}
|
|
154
135
|
const type = patch.type ?? scale.type;
|
|
155
136
|
validateTypeTransition(scale, type, channel, consumers);
|
|
137
|
+
if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
|
|
138
|
+
(["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
|
|
139
|
+
throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
|
|
140
|
+
}
|
|
156
141
|
const definition = normalizeScaleDefinition({
|
|
157
142
|
type,
|
|
158
143
|
previous: scale,
|
|
@@ -175,6 +160,12 @@ function normalizeDefinition(scale, channel, consumers, patch) {
|
|
|
175
160
|
) {
|
|
176
161
|
throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
|
|
177
162
|
}
|
|
163
|
+
validateSequentialMidpoint(definition.midpoint, type, definition.domain);
|
|
164
|
+
if (definition.midpoint !== undefined && consumers.some(
|
|
165
|
+
consumer => consumer.channel !== "color" || consumer.encoding.fieldType !== "quantitative"
|
|
166
|
+
)) {
|
|
167
|
+
throw new Error("Scale midpoint requires quantitative color consumers.");
|
|
168
|
+
}
|
|
178
169
|
const typeChanged = type !== scale.type;
|
|
179
170
|
const unknown = Object.hasOwn(patch, "unknown")
|
|
180
171
|
? patch.unknown
|
|
@@ -209,6 +200,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
|
|
|
209
200
|
...Object.entries(args).filter(([key]) => key !== "palette"),
|
|
210
201
|
["range", { palette: args.palette }]
|
|
211
202
|
]);
|
|
212
|
-
validateLegendTypeTransition(program, scale, args.type ?? scale.type);
|
|
213
203
|
return normalizeDefinition(scale, channel, consumers, patch);
|
|
214
204
|
}
|