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,8 +1,11 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
2
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
1
3
|
import {
|
|
2
4
|
POSITION_CHANNELS,
|
|
3
5
|
POSITION_ENCODING_CHANNELS
|
|
4
6
|
} from "../../core/vocabulary.js";
|
|
5
7
|
import { getMarkMaterializationPolicy } from "./policies.js";
|
|
8
|
+
import { buildMaterializationPlan } from "../planner.js";
|
|
6
9
|
|
|
7
10
|
export {
|
|
8
11
|
canMaterializeArc,
|
|
@@ -13,10 +16,29 @@ export {
|
|
|
13
16
|
canMaterializeRect,
|
|
14
17
|
canMaterializeRule,
|
|
15
18
|
canMaterializeText,
|
|
16
|
-
canMaterializeTick
|
|
19
|
+
canMaterializeTick,
|
|
20
|
+
isTextSource
|
|
17
21
|
} from "./capabilities.js";
|
|
18
22
|
|
|
19
|
-
export function
|
|
23
|
+
export function getLayerScaleIds(layer) {
|
|
24
|
+
if (isSourceOwnedText(layer)) return [];
|
|
25
|
+
return [
|
|
26
|
+
...Object.values(layer.encoding ?? {}).map(encoding => encoding?.scale),
|
|
27
|
+
...(layer.encoding?.parallel?.dimensions ?? []).map(dimension => dimension.scale)
|
|
28
|
+
].filter(scale => scale !== undefined);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getScaleConsumerMarkSteps(program, scaleIds) {
|
|
32
|
+
return program.semanticSpec.layers
|
|
33
|
+
.filter(layer => getLayerScaleIds(layer).some(id => scaleIds.includes(id)))
|
|
34
|
+
.map(layer => getMarkMaterializationStep(program, layer))
|
|
35
|
+
.filter(step => step !== undefined);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getMarkRematerializationStep(program, layer) {
|
|
39
|
+
if (program.markConfigs?.[layer.id]?.markFilter?.empty === true) {
|
|
40
|
+
return { op: "materializeEmptyMark", args: { id: layer.id } };
|
|
41
|
+
}
|
|
20
42
|
const policy = getMarkMaterializationPolicy(layer);
|
|
21
43
|
return policy === undefined
|
|
22
44
|
? undefined
|
|
@@ -28,14 +50,15 @@ export function getMarkMaterializationStep(program, layer) {
|
|
|
28
50
|
if (policy === undefined || !policy.canMaterialize(program, layer)) {
|
|
29
51
|
return undefined;
|
|
30
52
|
}
|
|
31
|
-
return getMarkRematerializationStep(layer);
|
|
53
|
+
return getMarkRematerializationStep(program, layer);
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
export function getSourceDependentMarkSteps(program, sourceId) {
|
|
35
|
-
return program.semanticSpec.layers.flatMap(layer =>
|
|
57
|
+
return (program.semanticSpec.layers ?? []).flatMap(layer =>
|
|
36
58
|
layer.source === sourceId &&
|
|
37
59
|
getMarkMaterializationPolicy(layer)?.sourceDependent === true
|
|
38
|
-
? [getMarkMaterializationStep(program, layer)
|
|
60
|
+
? [getMarkMaterializationStep(program, layer) ??
|
|
61
|
+
getExistingMarkRematerializationStep(program, layer)].filter(
|
|
39
62
|
step => step !== undefined
|
|
40
63
|
)
|
|
41
64
|
: []
|
|
@@ -46,39 +69,39 @@ export function getPositionEncodingMaterializationSteps(program, layer, scaleId)
|
|
|
46
69
|
const policy = getMarkMaterializationPolicy(layer);
|
|
47
70
|
if (policy === undefined) return [];
|
|
48
71
|
const complete = policy.canMaterialize(program, layer);
|
|
49
|
-
const mark = getMarkRematerializationStep(layer);
|
|
72
|
+
const mark = getMarkRematerializationStep(program, layer);
|
|
50
73
|
const sharedConsumerMarks = (program.semanticSpec.layers ?? [])
|
|
51
74
|
.filter(candidate =>
|
|
52
75
|
candidate.id !== layer.id &&
|
|
76
|
+
!isSourceOwnedText(candidate) &&
|
|
53
77
|
POSITION_CHANNELS.some(channel =>
|
|
54
78
|
candidate.encoding?.[channel]?.scale === scaleId
|
|
55
79
|
)
|
|
56
80
|
)
|
|
57
81
|
.map(candidate => getMarkMaterializationStep(program, candidate))
|
|
58
82
|
.filter(step => step !== undefined);
|
|
83
|
+
const dependentMarks = [mark, ...sharedConsumerMarks]
|
|
84
|
+
.flatMap(step => getSourceDependentMarkSteps(program, step.args.id));
|
|
59
85
|
const scale = {
|
|
60
86
|
op: "rematerializeScale",
|
|
61
87
|
args: sharedConsumerMarks.length === 0
|
|
62
88
|
? { id: scaleId }
|
|
63
89
|
: { id: scaleId, marks: false }
|
|
64
90
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
return policy.positionEncoding.scaleFirst
|
|
91
|
+
const direct = !complete && policy.positionEncoding.incomplete === "scale"
|
|
92
|
+
? [scale, ...sharedConsumerMarks]
|
|
93
|
+
: policy.positionEncoding.scaleFirst
|
|
70
94
|
? [scale, mark, ...sharedConsumerMarks]
|
|
71
95
|
: [mark, ...sharedConsumerMarks];
|
|
72
|
-
}
|
|
73
|
-
return policy.positionEncoding.scaleFirst
|
|
74
|
-
? [scale, mark, ...sharedConsumerMarks]
|
|
75
|
-
: [mark, ...sharedConsumerMarks];
|
|
96
|
+
return buildMaterializationPlan({ marks: [...direct, ...dependentMarks] });
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
export function getScaleConsumerMaterializationMode(layer, channel) {
|
|
79
100
|
const policy = getMarkMaterializationPolicy(layer);
|
|
80
101
|
if (policy === undefined) return "direct";
|
|
81
102
|
if (POSITION_ENCODING_CHANNELS.includes(channel)) {
|
|
103
|
+
// Attached labels require their source's completed geometry, after scales.
|
|
104
|
+
if (layer.source !== undefined && policy.sourceDependent === true) return "defer";
|
|
82
105
|
return policy.scaleApplication.position ?? policy.scaleApplication.default;
|
|
83
106
|
}
|
|
84
107
|
if (policy.scaleApplication.deferredChannels?.includes(channel)) {
|
|
@@ -99,7 +122,7 @@ export function getExistingMarkRematerializationStep(program, layer) {
|
|
|
99
122
|
) {
|
|
100
123
|
return undefined;
|
|
101
124
|
}
|
|
102
|
-
return getMarkRematerializationStep(layer);
|
|
125
|
+
return getMarkRematerializationStep(program, layer);
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
export function getEncodingMaterializationStages(program, layer, channel, scale) {
|
|
@@ -111,22 +134,34 @@ export function getEncodingMaterializationStages(program, layer, channel, scale)
|
|
|
111
134
|
args: { id: scale, guides: false, marks: false }
|
|
112
135
|
}]
|
|
113
136
|
: [];
|
|
114
|
-
const shared =
|
|
115
|
-
|
|
137
|
+
const shared = scale !== undefined && (
|
|
138
|
+
scales.length > 0 || policy.encoding?.sharedChannels?.includes(channel) === true
|
|
139
|
+
);
|
|
116
140
|
const candidates = shared
|
|
117
141
|
? program.semanticSpec.layers.filter(candidate =>
|
|
118
|
-
candidate.
|
|
119
|
-
candidate.encoding?.[channel]?.scale === scale
|
|
142
|
+
candidate.id === layer.id || candidate.encoding?.[channel]?.scale === scale
|
|
120
143
|
)
|
|
121
144
|
: [layer];
|
|
122
145
|
const marks = candidates.flatMap(candidate => {
|
|
123
|
-
|
|
146
|
+
const candidatePolicy = getMarkMaterializationPolicy(candidate);
|
|
147
|
+
if (candidatePolicy?.encoding?.skipRematerialization?.(program, candidate) === true) {
|
|
124
148
|
return [];
|
|
125
149
|
}
|
|
126
|
-
const step =
|
|
150
|
+
const step = candidate.id !== layer.id || candidatePolicy?.encoding?.completeOnly === true
|
|
127
151
|
? getMarkMaterializationStep(program, candidate)
|
|
128
|
-
: getMarkRematerializationStep(candidate);
|
|
152
|
+
: getMarkRematerializationStep(program, candidate);
|
|
129
153
|
return step === undefined ? [] : [step];
|
|
130
154
|
});
|
|
131
155
|
return { scales, marks };
|
|
132
156
|
}
|
|
157
|
+
|
|
158
|
+
// Reuse a plan's scale stage only when it covers every scale consumed by the mark.
|
|
159
|
+
export function reusePlannedMarkScales(program, steps, scaleIds) {
|
|
160
|
+
const resolved = new Set(scaleIds);
|
|
161
|
+
return steps.map(step => {
|
|
162
|
+
const layer = findLayer(program, step.args?.id);
|
|
163
|
+
if (layer === undefined || getMarkMaterializationPolicy(layer)?.acceptsResolvedScales !== true ||
|
|
164
|
+
!getLayerScaleIds(layer).every(id => resolved.has(id))) return step;
|
|
165
|
+
return { ...step, args: { ...step.args, scales: false } };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -40,7 +40,9 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
40
40
|
line: Object.freeze({
|
|
41
41
|
canMaterialize: canMaterializeLine,
|
|
42
42
|
op: "rematerializeLineMark",
|
|
43
|
+
acceptsResolvedScales: true,
|
|
43
44
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
45
|
+
encoding: Object.freeze({ scaleFirst: true, completeOnly: true }),
|
|
44
46
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
45
47
|
}),
|
|
46
48
|
area: Object.freeze({
|
|
@@ -2,28 +2,36 @@ import {
|
|
|
2
2
|
mapOrdinalPositionValues,
|
|
3
3
|
readScaleField
|
|
4
4
|
} from "../grammar/scales/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
6
|
+
import { resolveGraphicBounds } from "../layout/canvas.js";
|
|
7
|
+
import { RECT_MODES, resolveRectMode, rectUsesFields } from "../grammar/rects.js";
|
|
6
8
|
import { DEFAULT_RECT_MARK } from "./rectConfig.js";
|
|
7
9
|
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
8
10
|
|
|
9
|
-
function optionalValues(rows, encoding) {
|
|
11
|
+
function optionalValues(rows, encoding, length, channel) {
|
|
12
|
+
if (Object.hasOwn(encoding, "datum")) {
|
|
13
|
+
const value = normalizePositionDatum(encoding.datum, encoding.fieldType, channel, encoding.temporalUnit, "Rect");
|
|
14
|
+
return Array.from({ length }, () => value);
|
|
15
|
+
}
|
|
10
16
|
return readScaleField(rows, encoding.field, encoding.fieldType, {
|
|
11
|
-
allowUnknown: true
|
|
17
|
+
allowUnknown: true, temporalUnit: encoding.temporalUnit
|
|
12
18
|
});
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
function requiredChannels(layer, mode) {
|
|
16
|
-
|
|
17
|
-
? ["
|
|
18
|
-
: ["x", "y",
|
|
22
|
+
const position = mode === RECT_MODES.xSpan ? ["x", "x2"]
|
|
23
|
+
: mode === RECT_MODES.ySpan ? ["y", "y2"]
|
|
24
|
+
: mode === RECT_MODES.ranged ? ["x", "x2", "y", "y2"] : ["x", "y"];
|
|
25
|
+
return [...position, ...(layer.encoding?.color ? ["color"] : [])];
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export function resolveRectConsumerValues(layer, dataset, channel) {
|
|
22
|
-
const
|
|
29
|
+
const length = rectUsesFields(layer) ? dataset.values.length : 1;
|
|
30
|
+
const requested = optionalValues(dataset.values, layer.encoding[channel], length, channel);
|
|
23
31
|
const mode = resolveRectMode(layer);
|
|
24
32
|
if (mode === undefined) return requested;
|
|
25
33
|
const complete = requiredChannels(layer, mode).map(candidate =>
|
|
26
|
-
optionalValues(dataset.values, layer.encoding[candidate])
|
|
34
|
+
optionalValues(dataset.values, layer.encoding[candidate], length, candidate)
|
|
27
35
|
);
|
|
28
36
|
return requested.map((value, index) =>
|
|
29
37
|
complete.every(values => values[index] !== undefined) ? value : undefined
|
|
@@ -68,24 +76,22 @@ function appearance(config, fill) {
|
|
|
68
76
|
export function resolveRectRows(program, layer, dataset) {
|
|
69
77
|
const mode = resolveRectMode(layer);
|
|
70
78
|
if (mode === undefined) return [];
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const y2 = mode === RECT_MODES.ranged
|
|
77
|
-
? mappedEncoding(program, layer, dataset, "y2")
|
|
78
|
-
: undefined;
|
|
79
|
+
const bounds = resolveGraphicBounds(program);
|
|
80
|
+
const x = layer.encoding.x === undefined ? undefined : mappedEncoding(program, layer, dataset, "x");
|
|
81
|
+
const y = layer.encoding.y === undefined ? undefined : mappedEncoding(program, layer, dataset, "y");
|
|
82
|
+
const x2 = layer.encoding.x2 === undefined ? undefined : mappedEncoding(program, layer, dataset, "x2");
|
|
83
|
+
const y2 = layer.encoding.y2 === undefined ? undefined : mappedEncoding(program, layer, dataset, "y2");
|
|
79
84
|
const color = layer.encoding?.color === undefined
|
|
80
85
|
? undefined
|
|
81
86
|
: mappedEncoding(program, layer, dataset, "color").values;
|
|
82
87
|
const config = program.markConfigs[layer.id] ?? DEFAULT_RECT_MARK;
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
const rows = rectUsesFields(layer) ? dataset.values : [{}];
|
|
90
|
+
return rows.flatMap((row, index) => {
|
|
85
91
|
const fill = color === undefined ? config.fill : color[index];
|
|
86
92
|
if (
|
|
87
|
-
!Number.isFinite(x.values[index]) ||
|
|
88
|
-
!Number.isFinite(y.values[index]) ||
|
|
93
|
+
(x !== undefined && !Number.isFinite(x.values[index])) ||
|
|
94
|
+
(y !== undefined && !Number.isFinite(y.values[index])) ||
|
|
89
95
|
typeof fill !== "string"
|
|
90
96
|
) return [];
|
|
91
97
|
|
|
@@ -103,28 +109,22 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
103
109
|
height
|
|
104
110
|
};
|
|
105
111
|
} else {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
112
|
+
const startX = x?.values[index] ?? bounds.x;
|
|
113
|
+
const startY = y?.values[index] ?? bounds.y;
|
|
114
|
+
const endX = x2?.values[index] ?? bounds.x + bounds.width;
|
|
115
|
+
const endY = y2?.values[index] ?? bounds.y + bounds.height;
|
|
108
116
|
if (!Number.isFinite(endX) || !Number.isFinite(endY)) return [];
|
|
109
117
|
geometry = {
|
|
110
|
-
x: Math.min(
|
|
111
|
-
y: Math.min(
|
|
112
|
-
width: Math.abs(endX -
|
|
113
|
-
height: Math.abs(endY -
|
|
118
|
+
x: Math.min(startX, endX),
|
|
119
|
+
y: Math.min(startY, endY),
|
|
120
|
+
width: Math.abs(endX - startX),
|
|
121
|
+
height: Math.abs(endY - startY)
|
|
114
122
|
};
|
|
115
123
|
if (geometry.width <= 0 || geometry.height <= 0) return [];
|
|
116
124
|
}
|
|
117
125
|
return [{
|
|
118
126
|
row,
|
|
119
127
|
sourceIndex: index,
|
|
120
|
-
channels: Object.fromEntries(
|
|
121
|
-
["x", "y", "x2", "y2", "color"].flatMap(channel => {
|
|
122
|
-
const encoding = layer.encoding?.[channel];
|
|
123
|
-
return encoding?.field === undefined
|
|
124
|
-
? []
|
|
125
|
-
: [[channel, row[encoding.field]]];
|
|
126
|
-
})
|
|
127
|
-
),
|
|
128
128
|
properties: { ...geometry, ...appearance(config, fill) }
|
|
129
129
|
}];
|
|
130
130
|
});
|
|
@@ -5,15 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
mapOrdinalOffsetValues,
|
|
7
7
|
mapOrdinalPositionValues,
|
|
8
|
-
|
|
9
|
-
readQuantitativeField,
|
|
10
|
-
readScaleField,
|
|
11
|
-
readTemporalField
|
|
8
|
+
readScaleField
|
|
12
9
|
} from "../grammar/scales/index.js";
|
|
13
10
|
import {
|
|
14
11
|
finiteMidpoint,
|
|
15
12
|
requireFiniteResult
|
|
16
13
|
} from "../grammar/numeric.js";
|
|
14
|
+
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
17
15
|
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
18
16
|
|
|
19
17
|
export function resolveRowEncodingValues(program, layer, dataset, channel) {
|
|
@@ -26,15 +24,17 @@ export function resolveRowEncodingValues(program, layer, dataset, channel) {
|
|
|
26
24
|
);
|
|
27
25
|
}
|
|
28
26
|
const categorical = ["nominal", "ordinal"].includes(encoding.fieldType);
|
|
29
|
-
const values = Object.hasOwn(
|
|
30
|
-
?
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const values = Object.hasOwn(encoding, "datum")
|
|
28
|
+
? Array(dataset.values.length).fill(normalizePositionDatum(
|
|
29
|
+
encoding.datum,
|
|
30
|
+
encoding.fieldType,
|
|
31
|
+
channel,
|
|
32
|
+
encoding.temporalUnit,
|
|
33
|
+
layer.mark.type[0].toUpperCase() + layer.mark.type.slice(1)
|
|
34
|
+
))
|
|
35
|
+
: readScaleField(dataset.values, encoding.field, encoding.fieldType, {
|
|
36
|
+
allowUnknown: Object.hasOwn(scale, "unknown"), temporalUnit: encoding.temporalUnit
|
|
37
|
+
});
|
|
38
38
|
if (categorical && OFFSET_POSITION_CHANNELS.includes(channel)) {
|
|
39
39
|
return mapOrdinalOffsetValues(values, scale);
|
|
40
40
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../grammar/text.js";
|
|
1
2
|
import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
|
|
2
3
|
import { materializedLegendUsesScale } from "./legends.js";
|
|
3
4
|
import { buildMaterializationPlan } from "./planner.js";
|
|
@@ -19,10 +20,12 @@ const GRIDS = Object.freeze([
|
|
|
19
20
|
|
|
20
21
|
function usesPositionalScale(program, id) {
|
|
21
22
|
return program.semanticSpec.layers.some(layer =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
!isSourceOwnedText(layer) && (
|
|
24
|
+
POSITION_ENCODING_CHANNELS.some(
|
|
25
|
+
channel => layer.encoding?.[channel]?.scale === id
|
|
26
|
+
) || (layer.encoding?.parallel?.dimensions ?? []).some(
|
|
27
|
+
dimension => dimension.scale === id
|
|
28
|
+
)
|
|
26
29
|
)
|
|
27
30
|
);
|
|
28
31
|
}
|
|
@@ -50,7 +53,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
|
|
|
50
53
|
(scale.range === "auto" ||
|
|
51
54
|
usesRadialScale(program, scale.id) ||
|
|
52
55
|
semanticGuideUsesScale(program, scale.id)) &&
|
|
53
|
-
program.resolvedScales[scale.id] !== undefined &&
|
|
56
|
+
(program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
|
|
54
57
|
usesPositionalScale(program, scale.id)
|
|
55
58
|
);
|
|
56
59
|
}
|
|
@@ -19,6 +19,7 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
|
|
|
19
19
|
resolvedScale.range,
|
|
20
20
|
{
|
|
21
21
|
interpolation: resolvedScale.interpolate,
|
|
22
|
+
midpoint: resolvedScale.midpoint,
|
|
22
23
|
clamp: resolvedScale.clamp ?? false,
|
|
23
24
|
...(Object.hasOwn(resolvedScale, "unknown")
|
|
24
25
|
? { unknown: resolvedScale.unknown }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateMeasuredRadiusScale } from "../../../grammar/scales/radial.js";
|
|
1
2
|
export function resolveArcAutoPositionRange({
|
|
2
3
|
consumers,
|
|
3
4
|
scale,
|
|
@@ -35,3 +36,44 @@ export function resolveArcAutoPositionRange({
|
|
|
35
36
|
}
|
|
36
37
|
return range;
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function resolveMeasuredRadiusDomain({ scale, channel, allValues }) {
|
|
42
|
+
if (scale.radialMapping === undefined) return undefined;
|
|
43
|
+
if (channel !== "radius") throw new Error("Measured radius scales support only radius consumers.");
|
|
44
|
+
const maximum = allValues.reduce((maximum, value) => Math.max(maximum, value), -Infinity);
|
|
45
|
+
if (!Number.isFinite(maximum) || maximum <= 0 || allValues.some(value => value < 0)) {
|
|
46
|
+
throw new Error("Measured radius requires finite non-negative aggregates and a positive maximum.");
|
|
47
|
+
}
|
|
48
|
+
const domain = scale.domain === "auto" ? [0, maximum] : scale.domain;
|
|
49
|
+
if (Array.isArray(domain) && domain[1] < maximum) {
|
|
50
|
+
throw new RangeError("Measured radius domain maximum must cover every category aggregate.");
|
|
51
|
+
}
|
|
52
|
+
return domain;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateMeasuredRadiusConsumers({ scale, domain, range, consumers, markConfigs, thetaScales }) {
|
|
56
|
+
if (scale.radialMapping === undefined) return;
|
|
57
|
+
validateMeasuredRadiusScale({ ...scale, domain, range }, { allowAuto: true });
|
|
58
|
+
for (const consumer of consumers) {
|
|
59
|
+
const { layer, encoding } = consumer;
|
|
60
|
+
const theta = layer.encoding?.theta;
|
|
61
|
+
const thetaScale = thetaScales[layer.id];
|
|
62
|
+
if (!isPendingMeasuredRadiusConsumer(consumer) && (layer.mark?.type !== "arc" || !["count", "sum"].includes(encoding.aggregate) ||
|
|
63
|
+
!["nominal", "ordinal"].includes(theta?.fieldType) || theta.aggregate !== undefined ||
|
|
64
|
+
thetaScale?.type !== "band" || (thetaScale.paddingInner ?? 0) !== 0 || (thetaScale.paddingOuter ?? 0) !== 0)) {
|
|
65
|
+
throw new Error("Measured radius requires only equal-angle categorical Arc consumers.");
|
|
66
|
+
}
|
|
67
|
+
const config = markConfigs[layer.id] ?? {};
|
|
68
|
+
if ((config.padAngle ?? 0) !== 0) throw new Error("Measured Arc requires padAngle 0.");
|
|
69
|
+
if (scale.range !== "auto" && config.innerRadiusExplicit === true &&
|
|
70
|
+
Math.abs(config.innerRadius - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
|
+
throw new Error("Measured Arc innerRadius must agree with its explicit radius range.");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isPendingMeasuredRadiusConsumer({ layer, channel, encoding }) {
|
|
77
|
+
return layer.mark?.type === "arc" && channel === "radius" &&
|
|
78
|
+
["count", "sum"].includes(encoding.aggregate) && layer.encoding?.theta === undefined;
|
|
79
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
1
2
|
import { resolveHistogramBins } from "../../../grammar/histogram.js";
|
|
2
3
|
|
|
3
4
|
export function resolveBinnedPositionDomain({
|
|
4
5
|
valuesByConsumer,
|
|
5
6
|
channel,
|
|
6
7
|
scale,
|
|
7
|
-
id
|
|
8
|
-
allValues
|
|
8
|
+
id
|
|
9
9
|
}) {
|
|
10
10
|
const binnedPositions = valuesByConsumer.filter(
|
|
11
11
|
({ consumer }) =>
|
|
@@ -13,7 +13,10 @@ export function resolveBinnedPositionDomain({
|
|
|
13
13
|
consumer.encoding.bin !== undefined
|
|
14
14
|
);
|
|
15
15
|
if (binnedPositions.length === 0) return undefined;
|
|
16
|
-
|
|
16
|
+
const independentConsumers = valuesByConsumer.filter(({ consumer }) =>
|
|
17
|
+
!isSourceOwnedText(consumer.layer)
|
|
18
|
+
);
|
|
19
|
+
if (channel !== "x" || binnedPositions.length !== independentConsumers.length) {
|
|
17
20
|
throw new Error(
|
|
18
21
|
`Binned scale "${id}" cannot be shared with an unbinned consumer.`
|
|
19
22
|
);
|
|
@@ -25,7 +28,7 @@ export function resolveBinnedPositionDomain({
|
|
|
25
28
|
throw new Error(`Binned scale "${id}" requires one shared bin definition.`);
|
|
26
29
|
}
|
|
27
30
|
return resolveHistogramBins({
|
|
28
|
-
values:
|
|
31
|
+
values: binnedPositions.flatMap(item => item.values).filter(value => value !== undefined),
|
|
29
32
|
bin: binnedPositions[0].consumer.encoding.bin,
|
|
30
33
|
domain: scale.domain,
|
|
31
34
|
nice: scale.nice ?? true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
1
2
|
import {
|
|
2
3
|
isTransformedScaleType,
|
|
3
4
|
resolveContinuousDomain,
|
|
@@ -21,7 +22,7 @@ export function resolveSeriesLayoutDomain({
|
|
|
21
22
|
(_, index) => seriesLayouts[index] !== undefined
|
|
22
23
|
);
|
|
23
24
|
const directConsumers = valuesByConsumer.filter(
|
|
24
|
-
(
|
|
25
|
+
({ consumer }, index) => seriesLayouts[index] === undefined && !isSourceOwnedText(consumer.layer)
|
|
25
26
|
);
|
|
26
27
|
const compatibleDirect = directConsumers.every(({ consumer }) =>
|
|
27
28
|
layoutConsumers.every(({ consumer: layoutConsumer }) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
|
|
1
2
|
import {
|
|
2
3
|
isContinuousColorScaleType,
|
|
3
4
|
isDiscretePositionScaleType,
|
|
@@ -21,12 +22,13 @@ import {
|
|
|
21
22
|
resolveStrokeWidthRange,
|
|
22
23
|
resolveTransformedDomain,
|
|
23
24
|
SCALE_ROLES,
|
|
25
|
+
validateSequentialMidpoint,
|
|
24
26
|
validateLinearScaleType,
|
|
25
27
|
validateOrdinalScaleType,
|
|
26
28
|
validateScaleTypeForRole,
|
|
27
29
|
validateTimeScaleType
|
|
28
30
|
} from "../../grammar/scales/index.js";
|
|
29
|
-
import { resolveArcAutoPositionRange } from "./policies/arc.js";
|
|
31
|
+
import { resolveArcAutoPositionRange, resolveMeasuredRadiusDomain, validateMeasuredRadiusConsumers } from "./policies/arc.js";
|
|
30
32
|
import { resolveTemporalBarBand } from "./policies/bar.js";
|
|
31
33
|
import { resolveOffsetScalePolicy } from "./policies/offset.js";
|
|
32
34
|
import { resolveBinnedPositionDomain } from "./policies/binnedPosition.js";
|
|
@@ -139,7 +141,9 @@ function resolveContinuousScale({
|
|
|
139
141
|
: validateLinearScaleType(scale.type),
|
|
140
142
|
domain,
|
|
141
143
|
range,
|
|
144
|
+
...(scale.midpoint === undefined ? {} : { midpoint: scale.midpoint }),
|
|
142
145
|
...(scale.clamp === undefined ? {} : { clamp: scale.clamp }),
|
|
146
|
+
...(scale.radialMapping === undefined ? {} : { radialMapping: scale.radialMapping }),
|
|
143
147
|
...(scale.type === "log"
|
|
144
148
|
? {
|
|
145
149
|
base: normalizeTransformParameters("log", {
|
|
@@ -182,19 +186,38 @@ function reverseResolvedScale(scale) {
|
|
|
182
186
|
};
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
export function resolveScaleMaterialization({
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
export function resolveScaleMaterialization(options) {
|
|
190
|
+
const {
|
|
191
|
+
id,
|
|
192
|
+
scale,
|
|
193
|
+
channel,
|
|
194
|
+
consumers,
|
|
195
|
+
valuesByConsumer,
|
|
196
|
+
bounds,
|
|
197
|
+
resolvedScales,
|
|
198
|
+
markConfigs,
|
|
199
|
+
thetaScales
|
|
200
|
+
} = options;
|
|
195
201
|
const allValues = valuesByConsumer
|
|
196
202
|
.flatMap(item => item.values)
|
|
197
203
|
.filter(value => value !== undefined);
|
|
204
|
+
const preserved = resolvedScales[id]?.domain;
|
|
205
|
+
if (
|
|
206
|
+
scale.domain === "auto" &&
|
|
207
|
+
Array.isArray(preserved) &&
|
|
208
|
+
consumers.some(({ layer }) =>
|
|
209
|
+
markConfigs?.[layer.id]?.markFilter?.empty === true
|
|
210
|
+
)
|
|
211
|
+
) {
|
|
212
|
+
return resolveScaleMaterialization({
|
|
213
|
+
...options,
|
|
214
|
+
scale: { ...scale, domain: preserved },
|
|
215
|
+
valuesByConsumer: valuesByConsumer.map(item => ({
|
|
216
|
+
...item,
|
|
217
|
+
categoryOrder: undefined
|
|
218
|
+
}))
|
|
219
|
+
});
|
|
220
|
+
}
|
|
198
221
|
const isSequentialColor = channel === "color" &&
|
|
199
222
|
isContinuousColorScaleType(scale.type);
|
|
200
223
|
const isDiscretizedColor = channel === "color" &&
|
|
@@ -210,6 +233,9 @@ export function resolveScaleMaterialization({
|
|
|
210
233
|
!isOrdinalOffset &&
|
|
211
234
|
isDiscretePositionScaleType(scale.type);
|
|
212
235
|
|
|
236
|
+
if (isSequentialColor || isDiscretizedColor) {
|
|
237
|
+
for (const consumer of consumers) validateContinuousColorConsumer(consumer.layer, consumer.encoding, scale);
|
|
238
|
+
}
|
|
213
239
|
let discretizedScale;
|
|
214
240
|
if (isDiscretizedColor) {
|
|
215
241
|
discretizedScale = resolveDiscretizedColorScale({
|
|
@@ -225,8 +251,7 @@ export function resolveScaleMaterialization({
|
|
|
225
251
|
valuesByConsumer,
|
|
226
252
|
channel,
|
|
227
253
|
scale,
|
|
228
|
-
id
|
|
229
|
-
allValues
|
|
254
|
+
id
|
|
230
255
|
});
|
|
231
256
|
const seriesLayouts = valuesByConsumer.map(item => item.seriesLayout);
|
|
232
257
|
const seriesDomain = isDiscretizedColor || binnedDomain !== undefined
|
|
@@ -247,7 +272,7 @@ export function resolveScaleMaterialization({
|
|
|
247
272
|
}
|
|
248
273
|
const domain = isDiscretizedColor
|
|
249
274
|
? discretizedScale.domain
|
|
250
|
-
: categoryOrderDomain ?? binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
|
|
275
|
+
: resolveMeasuredRadiusDomain({ scale, channel, allValues }) ?? categoryOrderDomain ?? binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
|
|
251
276
|
scale,
|
|
252
277
|
allValues,
|
|
253
278
|
isOrdinalAppearance,
|
|
@@ -267,6 +292,13 @@ export function resolveScaleMaterialization({
|
|
|
267
292
|
isOrdinalOffset,
|
|
268
293
|
discretizedScale
|
|
269
294
|
});
|
|
295
|
+
validateSequentialMidpoint(scale.midpoint, scale.type, domain);
|
|
296
|
+
if (scale.midpoint !== undefined && (channel !== "color" || consumers.some(
|
|
297
|
+
consumer => consumer.encoding.fieldType !== "quantitative"
|
|
298
|
+
))) {
|
|
299
|
+
throw new Error("Scale midpoint requires quantitative color consumers.");
|
|
300
|
+
}
|
|
301
|
+
validateMeasuredRadiusConsumers({ scale, domain, range, consumers, markConfigs, thetaScales });
|
|
270
302
|
if (channel === "shape" && domain.length > range.length) {
|
|
271
303
|
throw new Error(
|
|
272
304
|
`Shape scale "${id}" requires at least one distinct shape per domain value.`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function assertEncodingSelectionCompatibility(program, target, channels) {
|
|
2
|
+
for (const [id, selection] of Object.entries(
|
|
3
|
+
program.materializationConfigs.selections ?? {}
|
|
4
|
+
)) {
|
|
5
|
+
if (selection.target === target && channels.includes(selection.selector?.channel)) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Cannot remove ${selection.selector.channel} encoding while selection "${id}" references that channel.`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|