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,12 +1,16 @@
|
|
|
1
|
+
import { stackLayoutMode } from "../seriesLayout.js";
|
|
2
|
+
import { resolveBarGrain } from "../../../grammar/bars/policy.js";
|
|
1
3
|
import { action } from "../../../core/action.js";
|
|
2
4
|
import {
|
|
3
5
|
getPositionEncodingMaterializationSteps
|
|
4
6
|
} from "../../../materialization/marks/index.js";
|
|
5
7
|
import { resolvePositionEncoding } from "./resolve.js";
|
|
6
8
|
import { findLayer } from "../../../selectors/layers.js";
|
|
9
|
+
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
7
10
|
import { applyPositionSemantics } from "./apply.js";
|
|
8
11
|
import {
|
|
9
12
|
applyEncodingScale,
|
|
13
|
+
applyDetachedScaleRematerialization,
|
|
10
14
|
rebindPositionGuides
|
|
11
15
|
} from "../shared.js";
|
|
12
16
|
|
|
@@ -20,12 +24,14 @@ function encodePosition(program, channel, args, operation) {
|
|
|
20
24
|
datum,
|
|
21
25
|
hasField,
|
|
22
26
|
fieldType,
|
|
27
|
+
temporalUnit,
|
|
23
28
|
scale,
|
|
24
29
|
coordinate,
|
|
25
30
|
bin,
|
|
26
31
|
aggregate,
|
|
27
32
|
stack,
|
|
28
|
-
weight
|
|
33
|
+
weight,
|
|
34
|
+
companion
|
|
29
35
|
} = resolvePositionEncoding(program, channel, args, operation);
|
|
30
36
|
|
|
31
37
|
let next = program
|
|
@@ -43,6 +49,7 @@ function encodePosition(program, channel, args, operation) {
|
|
|
43
49
|
datum,
|
|
44
50
|
hasField,
|
|
45
51
|
fieldType,
|
|
52
|
+
temporalUnit,
|
|
46
53
|
bin,
|
|
47
54
|
aggregate,
|
|
48
55
|
stack,
|
|
@@ -54,8 +61,12 @@ function encodePosition(program, channel, args, operation) {
|
|
|
54
61
|
value: scale.id
|
|
55
62
|
});
|
|
56
63
|
next = applyEncodingScale(next, scale, requestedScale, {
|
|
57
|
-
reassignment: previous?.scale === scale.id
|
|
64
|
+
reassignment: previous?.scale === scale.id,
|
|
65
|
+
allowTypeChange: layer.mark.type === "area"
|
|
58
66
|
});
|
|
67
|
+
if (["bar", "area"].includes(layer.mark.type) && Object.hasOwn(args, "stack") && args.stack !== undefined) {
|
|
68
|
+
next = next.editSemantic({ property: `layer[${target}].layout.mode`, value: stackLayoutMode(args.stack) });
|
|
69
|
+
}
|
|
59
70
|
next = rebindPositionGuides(
|
|
60
71
|
next,
|
|
61
72
|
channel,
|
|
@@ -64,13 +75,30 @@ function encodePosition(program, channel, args, operation) {
|
|
|
64
75
|
target
|
|
65
76
|
);
|
|
66
77
|
|
|
78
|
+
if (companion !== undefined) {
|
|
79
|
+
const encoding = companion.encoding;
|
|
80
|
+
const storedScale = findSemanticScale(next, encoding.scale);
|
|
81
|
+
next = next[companion.channel === "x" ? "encodeX" : "encodeY"]({
|
|
82
|
+
target,
|
|
83
|
+
field: encoding.field,
|
|
84
|
+
fieldType: encoding.fieldType,
|
|
85
|
+
aggregate: encoding.aggregate,
|
|
86
|
+
...(layer.layout?.mode === undefined ? {} : { stack: encoding.stack }),
|
|
87
|
+
scale: {
|
|
88
|
+
id: encoding.scale,
|
|
89
|
+
...(storedScale?.zero === undefined ? { zero: encoding.stack !== null } : {})
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
67
94
|
if (layer.mark.type === "bar") {
|
|
68
95
|
const updated = findLayer(next, target);
|
|
69
96
|
const pendingBox = next.markConfigs[target]?.boxPlot;
|
|
70
97
|
if (pendingBox !== undefined && !pendingBox.materialized) {
|
|
71
|
-
|
|
98
|
+
const materialized = updated.encoding?.x !== undefined && updated.encoding?.y !== undefined
|
|
72
99
|
? next.materializeBoxPlot({ id: target })
|
|
73
100
|
: next;
|
|
101
|
+
return applyDetachedScaleRematerialization(materialized, [layer]);
|
|
74
102
|
}
|
|
75
103
|
}
|
|
76
104
|
|
|
@@ -78,13 +106,23 @@ function encodePosition(program, channel, args, operation) {
|
|
|
78
106
|
const updated = findLayer(next, target);
|
|
79
107
|
const pendingGradient = next.markConfigs[target]?.gradientPlot;
|
|
80
108
|
if (pendingGradient !== undefined && !pendingGradient.materialized) {
|
|
81
|
-
|
|
109
|
+
const materialized = updated.encoding?.x !== undefined && updated.encoding?.y !== undefined
|
|
82
110
|
? next.materializeGradientPlot({ id: target })
|
|
83
111
|
: next;
|
|
112
|
+
return applyDetachedScaleRematerialization(materialized, [layer]);
|
|
84
113
|
}
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
const updated = findLayer(next, target);
|
|
117
|
+
if (["bar", "area"].includes(layer.mark.type) && next.markConfigs[target]?.boxPlot === undefined &&
|
|
118
|
+
updated.encoding?.x?.scale !== undefined && updated.encoding?.y?.scale !== undefined) {
|
|
119
|
+
const requested = Object.hasOwn(args, "stack") ? args.stack : undefined;
|
|
120
|
+
const mode = requested !== undefined ? stackLayoutMode(requested) : updated.layout?.mode ??
|
|
121
|
+
(layer.mark.type === "bar" && resolveBarGrain(updated) === "histogram" ? stackLayoutMode(updated.encoding.y.stack) : undefined);
|
|
122
|
+
if (mode !== undefined && (layer.mark.type === "area" || resolveBarGrain(updated) !== undefined)) {
|
|
123
|
+
return applyDetachedScaleRematerialization(next.layoutSeries({ target, mode }), [layer]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
88
126
|
for (const step of getPositionEncodingMaterializationSteps(
|
|
89
127
|
next,
|
|
90
128
|
updated,
|
|
@@ -92,7 +130,7 @@ function encodePosition(program, channel, args, operation) {
|
|
|
92
130
|
)) {
|
|
93
131
|
next = next[step.op](step.args);
|
|
94
132
|
}
|
|
95
|
-
return next;
|
|
133
|
+
return applyDetachedScaleRematerialization(next, [layer]);
|
|
96
134
|
}
|
|
97
135
|
|
|
98
136
|
const encodeX = action(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateRadialMapping } from "../../../../grammar/scales/radial.js";
|
|
1
2
|
import { validateAggregate } from "../../../../grammar/aggregate.js";
|
|
2
3
|
import { validateNonEmptyString } from "../../../../core/validation.js";
|
|
3
4
|
import { emptyPositionPolicy } from "./common.js";
|
|
@@ -15,6 +16,19 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
|
|
|
15
16
|
"Arc radius encoding cannot be combined with quantitative theta."
|
|
16
17
|
);
|
|
17
18
|
}
|
|
19
|
+
if (args.mapping !== undefined) {
|
|
20
|
+
validateRadialMapping(args.mapping);
|
|
21
|
+
if (fieldType !== "quantitative" || !["count", "sum"].includes(args.aggregate)) {
|
|
22
|
+
throw new Error("Measured radius requires quantitative count or sum aggregation.");
|
|
23
|
+
}
|
|
24
|
+
if (args.aggregate === "count" && Object.hasOwn(args, "field")) {
|
|
25
|
+
throw new Error("Measured radius count does not accept a field.");
|
|
26
|
+
}
|
|
27
|
+
if (layer.encoding?.theta?.aggregate !== undefined) {
|
|
28
|
+
throw new Error("Measured radius requires equal-angle theta without aggregation.");
|
|
29
|
+
}
|
|
30
|
+
return { ...emptyPositionPolicy(), aggregate: args.aggregate };
|
|
31
|
+
}
|
|
18
32
|
if (args.aggregate !== undefined) {
|
|
19
33
|
throw new Error("Arc radius encoding does not support aggregate.");
|
|
20
34
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { validateStack, emptyPositionPolicy } from "./common.js";
|
|
2
|
-
import { deriveCenteredAreaSeries } from "../../../../grammar/areaSeries.js";
|
|
3
|
-
import { readQuantitativeField } from "../../../../grammar/scales/index.js";
|
|
4
2
|
|
|
5
3
|
export function isCategoricalDensityPosition({
|
|
6
4
|
layer,
|
|
@@ -50,44 +48,8 @@ export function resolveAreaPositionPolicy({
|
|
|
50
48
|
throw new Error("Area position encoding does not support aggregate or bin.");
|
|
51
49
|
}
|
|
52
50
|
if (args.stack === undefined) return emptyPositionPolicy();
|
|
53
|
-
const stack = validateStack(args.stack, "Area
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
if (layer.encoding?.group?.fieldType !== "nominal") {
|
|
59
|
-
throw new Error("Area center stack requires a nominal group encoding.");
|
|
60
|
-
}
|
|
61
|
-
if (
|
|
62
|
-
layer.encoding?.color?.layout !== undefined &&
|
|
63
|
-
layer.encoding.color.layout !== "center"
|
|
64
|
-
) {
|
|
65
|
-
throw new Error("Area center stack requires a matching center color layout.");
|
|
66
|
-
}
|
|
67
|
-
const values = readQuantitativeField(dataset.values, field);
|
|
68
|
-
if (values.some(value => value < 0)) {
|
|
69
|
-
throw new RangeError("Area center stack requires non-negative values.");
|
|
70
|
-
}
|
|
71
|
-
if (layer.encoding?.x !== undefined) {
|
|
72
|
-
deriveCenteredAreaSeries(dataset.values, {
|
|
73
|
-
...layer,
|
|
74
|
-
encoding: {
|
|
75
|
-
...layer.encoding,
|
|
76
|
-
y: { field, fieldType, stack: "center" }
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
return { bin: undefined, aggregate: undefined, stack };
|
|
81
|
-
}
|
|
82
|
-
if (
|
|
83
|
-
channel !== "y" ||
|
|
84
|
-
density === undefined
|
|
85
|
-
) {
|
|
86
|
-
throw new Error("Area stack currently requires a density y encoding.");
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
bin: undefined,
|
|
90
|
-
aggregate: undefined,
|
|
91
|
-
stack
|
|
92
|
-
};
|
|
51
|
+
const stack = validateStack(args.stack, "Area position encoding");
|
|
52
|
+
if (fieldType !== "quantitative") throw new Error("Area stack requires a quantitative measure.");
|
|
53
|
+
if (stack === "center" && channel !== "y") throw new Error("Area center stack requires a y encoding.");
|
|
54
|
+
return { bin: undefined, aggregate: undefined, stack };
|
|
93
55
|
}
|
|
@@ -4,12 +4,14 @@ import {
|
|
|
4
4
|
} from "../../../../grammar/aggregate.js";
|
|
5
5
|
import {
|
|
6
6
|
BAR_ORIENTATIONS,
|
|
7
|
+
BAR_GRAINS,
|
|
8
|
+
resolveBarGrain,
|
|
7
9
|
resolveBarOffsetChannel,
|
|
8
10
|
resolveBarOrientation
|
|
9
11
|
} from "../../../../grammar/bars/policy.js";
|
|
10
12
|
import { resolveBin, validateStack } from "./common.js";
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
function resolveBarChannelPolicy({
|
|
13
15
|
program,
|
|
14
16
|
layer,
|
|
15
17
|
channel,
|
|
@@ -72,17 +74,12 @@ export function resolveBarPositionPolicy({
|
|
|
72
74
|
if (pendingBoxRange && args.aggregate === undefined) {
|
|
73
75
|
return { bin, aggregate, stack };
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
: "Bar y encoding requires a binned quantitative or ordinal x category, temporal x category, or aggregate."
|
|
80
|
-
);
|
|
77
|
+
stack = Object.hasOwn(args, "stack") ? args.stack : opposite === undefined ? undefined : null;
|
|
78
|
+
if (aggregate !== undefined) {
|
|
79
|
+
aggregate = validateAggregate(aggregate);
|
|
80
|
+
validateAggregateFieldType(aggregate, fieldType);
|
|
81
81
|
}
|
|
82
|
-
stack
|
|
83
|
-
aggregate = validateAggregate(aggregate);
|
|
84
|
-
validateAggregateFieldType(aggregate, fieldType);
|
|
85
|
-
stack = validateStack(stack, `Bar ${channel} encoding`);
|
|
82
|
+
if (stack !== undefined) stack = validateStack(stack, `Bar ${channel} encoding`);
|
|
86
83
|
if (stack === "center") {
|
|
87
84
|
throw new Error("Centered bars are not supported.");
|
|
88
85
|
}
|
|
@@ -92,13 +89,45 @@ export function resolveBarPositionPolicy({
|
|
|
92
89
|
);
|
|
93
90
|
}
|
|
94
91
|
|
|
92
|
+
return { bin, aggregate, stack };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function resolveBarPositionPolicy(context) {
|
|
96
|
+
const { program, layer, channel, field, fieldType } = context;
|
|
97
|
+
const policy = resolveBarChannelPolicy(context);
|
|
98
|
+
const oppositeChannel = channel === "x" ? "y" : "x";
|
|
99
|
+
const opposite = layer.encoding?.[oppositeChannel];
|
|
100
|
+
const pendingBoxRange = program.markConfigs[layer.id]?.boxPlot !== undefined;
|
|
95
101
|
const candidate = {
|
|
96
102
|
...layer,
|
|
97
103
|
encoding: {
|
|
98
104
|
...layer.encoding,
|
|
99
|
-
[channel]: { field, fieldType,
|
|
105
|
+
[channel]: { field, fieldType, ...policy }
|
|
100
106
|
}
|
|
101
107
|
};
|
|
108
|
+
// The same channel policy owns a measure whether it is authored first or last.
|
|
109
|
+
// Absence of an aggregate/stack is sufficient to represent an unresolved role.
|
|
110
|
+
let companion;
|
|
111
|
+
if (
|
|
112
|
+
!pendingBoxRange && resolveBarGrain(layer) === undefined &&
|
|
113
|
+
opposite?.fieldType === "quantitative" && opposite.bin === undefined
|
|
114
|
+
) {
|
|
115
|
+
const resolved = resolveBarChannelPolicy({
|
|
116
|
+
...context,
|
|
117
|
+
layer: candidate,
|
|
118
|
+
channel: oppositeChannel,
|
|
119
|
+
field: opposite.field,
|
|
120
|
+
fieldType: opposite.fieldType,
|
|
121
|
+
args: opposite
|
|
122
|
+
});
|
|
123
|
+
candidate.encoding[oppositeChannel] = { ...opposite, ...resolved };
|
|
124
|
+
if (
|
|
125
|
+
(opposite.aggregate === undefined && resolved.aggregate !== undefined) ||
|
|
126
|
+
opposite.stack !== resolved.stack
|
|
127
|
+
) {
|
|
128
|
+
companion = { channel: oppositeChannel, encoding: candidate.encoding[oppositeChannel] };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
102
131
|
const orientation = resolveBarOrientation(candidate);
|
|
103
132
|
if (opposite !== undefined && orientation === undefined && !pendingBoxRange) {
|
|
104
133
|
throw new Error(
|
|
@@ -116,5 +145,11 @@ export function resolveBarPositionPolicy({
|
|
|
116
145
|
`${orientation} bars require ${expectedOffset}; remove the incompatible ${incompatibleOffset} encoding.`
|
|
117
146
|
);
|
|
118
147
|
}
|
|
119
|
-
|
|
148
|
+
if (
|
|
149
|
+
program.markConfigs[layer.id]?.barWidth !== undefined &&
|
|
150
|
+
resolveBarGrain(candidate) === BAR_GRAINS.histogram
|
|
151
|
+
) {
|
|
152
|
+
throw new Error("A saved Bar width requires an aggregate or ranged category slot and cannot be applied to histogram bins.");
|
|
153
|
+
}
|
|
154
|
+
return companion === undefined ? policy : { ...policy, companion };
|
|
120
155
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
2
|
+
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
3
|
+
import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
|
|
1
4
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
5
|
import { getPositionCoordinateDefaults } from "../../../grammar/coordinates.js";
|
|
3
6
|
import {
|
|
@@ -6,6 +9,7 @@ import {
|
|
|
6
9
|
readQuantitativeField,
|
|
7
10
|
readScaleField,
|
|
8
11
|
readTemporalField,
|
|
12
|
+
resolveTemporalUnit,
|
|
9
13
|
validateFieldType,
|
|
10
14
|
validatePositionChannel
|
|
11
15
|
} from "../../../grammar/scales/index.js";
|
|
@@ -21,7 +25,7 @@ import {
|
|
|
21
25
|
validateAggregateFieldType,
|
|
22
26
|
validateAggregateFieldValues,
|
|
23
27
|
} from "../../../grammar/aggregate.js";
|
|
24
|
-
import {
|
|
28
|
+
import { normalizePositionDatum } from "../../../grammar/positionDatum.js";
|
|
25
29
|
import {
|
|
26
30
|
readArcThetaValues,
|
|
27
31
|
readArcThetaWeights
|
|
@@ -34,10 +38,10 @@ import {
|
|
|
34
38
|
|
|
35
39
|
const POSITION_ENCODING_OPTIONS = Object.freeze([
|
|
36
40
|
"field", "datum", "target", "fieldType", "scale", "coordinate",
|
|
37
|
-
"aggregate", "bin", "stack", "weight"
|
|
41
|
+
"aggregate", "bin", "stack", "weight", "temporalUnit", "mapping"
|
|
38
42
|
]);
|
|
39
43
|
|
|
40
|
-
function
|
|
44
|
+
function inferDatumFieldType(datum, operation) {
|
|
41
45
|
if (Number.isFinite(datum)) return "quantitative";
|
|
42
46
|
if (isNominalValue(datum)) return "nominal";
|
|
43
47
|
throw new Error(`${operation} datum requires an explicit fieldType.`);
|
|
@@ -93,29 +97,45 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
93
97
|
args.target,
|
|
94
98
|
getPositionChannelDefinition(channel).markTypes
|
|
95
99
|
);
|
|
100
|
+
if (isSourceOwnedText(layer)) {
|
|
101
|
+
throw new Error(`${operation} cannot replace source-owned Text positions; edit the source, use dx/dy, or create independent Text with data.`);
|
|
102
|
+
}
|
|
96
103
|
if (
|
|
97
104
|
Object.hasOwn(args, "weight") &&
|
|
98
105
|
!(layer.mark.type === "arc" && channel === "theta")
|
|
99
106
|
) {
|
|
100
107
|
throw new Error(`${operation} weight is supported only for arc theta encoding.`);
|
|
101
108
|
}
|
|
109
|
+
if (Object.hasOwn(args, "mapping") && (layer.mark.type !== "arc" || channel !== "radius")) {
|
|
110
|
+
throw new Error(`${operation} mapping requires Arc radius.`);
|
|
111
|
+
}
|
|
102
112
|
validateCoordinateFamily(layer, channel, operation);
|
|
103
113
|
const hasField = Object.hasOwn(args, "field");
|
|
104
114
|
const hasDatum = Object.hasOwn(args, "datum");
|
|
105
|
-
if (layer.mark.type
|
|
115
|
+
if (["rule", "rect", "text", "point", "tick"].includes(layer.mark.type)) {
|
|
106
116
|
if (hasField === hasDatum) {
|
|
107
|
-
throw new Error(`${operation} requires exactly one of field or datum for a
|
|
117
|
+
throw new Error(`${operation} requires exactly one of field or datum for a ${layer.mark.type} mark.`);
|
|
108
118
|
}
|
|
109
|
-
if (hasField && args.fieldType === undefined) {
|
|
119
|
+
if (layer.mark.type === "rule" && hasField && args.fieldType === undefined) {
|
|
110
120
|
throw new Error(`${operation} requires fieldType for a rule mark.`);
|
|
111
121
|
}
|
|
122
|
+
} else if (layer.mark.type === "area") {
|
|
123
|
+
if (hasField === hasDatum) throw new Error(`${operation} requires exactly one of field or datum for an area mark.`);
|
|
124
|
+
if (hasDatum && (!Number.isFinite(args.datum) || (args.fieldType ?? "quantitative") !== "quantitative" ||
|
|
125
|
+
["aggregate", "bin", "stack", "temporalUnit"].some(key => Object.hasOwn(args, key)))) {
|
|
126
|
+
throw new Error("Area datum requires a finite quantitative constant without aggregate, bin, stack, or temporalUnit.");
|
|
127
|
+
}
|
|
112
128
|
} else if (hasDatum) {
|
|
113
129
|
throw new Error(`${operation} does not support datum for a ${layer.mark.type} mark.`);
|
|
114
130
|
}
|
|
115
131
|
const previous = layer.encoding?.[channel];
|
|
132
|
+
const mapping = channel === "radius" && layer.mark.type === "arc"
|
|
133
|
+
? args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
|
|
134
|
+
: undefined;
|
|
135
|
+
const countRadius = mapping !== undefined && (args.aggregate ?? previous?.aggregate) === "count";
|
|
116
136
|
const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
|
|
117
|
-
layer.mark.type
|
|
118
|
-
?
|
|
137
|
+
["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum
|
|
138
|
+
? inferDatumFieldType(args.datum, operation)
|
|
119
139
|
: layer.mark.type === "arc" && channel === "theta" &&
|
|
120
140
|
["count", "sum"].includes(args.aggregate)
|
|
121
141
|
? "nominal"
|
|
@@ -130,7 +150,22 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
130
150
|
? xEncoding.field
|
|
131
151
|
: args.field;
|
|
132
152
|
const datum = args.datum;
|
|
133
|
-
const
|
|
153
|
+
const temporalUnit = resolveTemporalUnit({ ...args, ...(hasDatum ? {} : { field }) }, fieldType, previous);
|
|
154
|
+
const usesField = !countRadius && (!["rule", "area", "rect", "text", "point", "tick"].includes(layer.mark.type) || hasField);
|
|
155
|
+
if (usesField && (typeof field !== "string" || field.length === 0)) {
|
|
156
|
+
throw new TypeError(`${operation} field must be a non-empty string.`);
|
|
157
|
+
}
|
|
158
|
+
// A missing field is invalid even before the Bar's measure/category role is known.
|
|
159
|
+
if (layer.mark.type === "bar" && program.markConfigs[target]?.boxPlot === undefined) {
|
|
160
|
+
if (fieldType === "quantitative" && dataset.values.length > 0 &&
|
|
161
|
+
!dataset.values.some(row => Object.hasOwn(row, field))) {
|
|
162
|
+
throw new Error(`${operation} field "${field}" does not exist in the dataset.`);
|
|
163
|
+
}
|
|
164
|
+
if (fieldType === "quantitative") {
|
|
165
|
+
validateAggregateFieldValues(dataset.values, field, fieldType);
|
|
166
|
+
} else readScaleField(dataset.values, field, fieldType, { temporalUnit });
|
|
167
|
+
}
|
|
168
|
+
const effectiveArgs = { ...args, ...(mapping === undefined ? {} : { mapping }) };
|
|
134
169
|
const directQuantitativeArcTheta =
|
|
135
170
|
layer.mark.type === "arc" &&
|
|
136
171
|
channel === "theta" &&
|
|
@@ -159,11 +194,6 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
159
194
|
fieldType,
|
|
160
195
|
field
|
|
161
196
|
});
|
|
162
|
-
const usesField = layer.mark.type !== "rule" || hasField;
|
|
163
|
-
if (usesField && (typeof field !== "string" || field.length === 0)) {
|
|
164
|
-
throw new TypeError(`${operation} field must be a non-empty string.`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
197
|
const aggregateOutput = isAggregate(policy.aggregate) && !(
|
|
168
198
|
layer.mark.type === "arc" && channel === "theta"
|
|
169
199
|
);
|
|
@@ -176,11 +206,14 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
176
206
|
channel,
|
|
177
207
|
aggregateOutput ? "quantitative" : fieldType,
|
|
178
208
|
requestedScale,
|
|
179
|
-
|
|
209
|
+
mapping !== undefined ? { radialMapping: mapping, zero: true, nice: false }
|
|
210
|
+
: layer.mark.type === "bar"
|
|
180
211
|
? program.markConfigs[target]?.boxPlot !== undefined && fieldType === "quantitative"
|
|
181
212
|
? { nice: true, zero: false }
|
|
182
213
|
: fieldType === "quantitative"
|
|
183
|
-
? policy.bin
|
|
214
|
+
? policy.bin === undefined && policy.stack === undefined
|
|
215
|
+
? { nice: true }
|
|
216
|
+
: policy.bin !== undefined || policy.stack === null
|
|
184
217
|
? { nice: true, zero: false }
|
|
185
218
|
: { nice: true, zero: true }
|
|
186
219
|
: fieldType === "temporal"
|
|
@@ -217,8 +250,13 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
217
250
|
"Position scale unknown currently requires a row-owned point mark."
|
|
218
251
|
);
|
|
219
252
|
}
|
|
220
|
-
if (layer.mark.type === "
|
|
221
|
-
|
|
253
|
+
if (layer.mark.type === "area" && fieldType === "quantitative") {
|
|
254
|
+
readAreaEndpoint(dataset.values, { ...(hasDatum ? { datum } : { field }), fieldType }, layer.mark.missing);
|
|
255
|
+
} else if (["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum) {
|
|
256
|
+
const mark = layer.mark.type[0].toUpperCase() + layer.mark.type.slice(1);
|
|
257
|
+
normalizePositionDatum(datum, fieldType, channel, temporalUnit, mark);
|
|
258
|
+
} else if (countRadius) {
|
|
259
|
+
// Count has no source measure field. Category-grain validation runs once theta exists.
|
|
222
260
|
} else if (aggregateOutput) {
|
|
223
261
|
validateAggregateFieldType(policy.aggregate, fieldType);
|
|
224
262
|
validateAggregateFieldValues(dataset.values, field, fieldType);
|
|
@@ -239,14 +277,20 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
239
277
|
}
|
|
240
278
|
}
|
|
241
279
|
} else if (layer.mark.type === "rect") {
|
|
242
|
-
readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
|
|
280
|
+
readScaleField(dataset.values, field, fieldType, { allowUnknown: true, temporalUnit });
|
|
243
281
|
} else if (Object.hasOwn(scale, "unknown")) {
|
|
244
|
-
readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
|
|
245
|
-
} else if (fieldType === "temporal") readTemporalField(dataset.values, field);
|
|
282
|
+
readScaleField(dataset.values, field, fieldType, { allowUnknown: true, temporalUnit });
|
|
283
|
+
} else if (fieldType === "temporal") readTemporalField(dataset.values, field, temporalUnit);
|
|
246
284
|
else if (["ordinal", "nominal"].includes(fieldType)) {
|
|
247
285
|
readNominalField(dataset.values, field);
|
|
248
286
|
} else readQuantitativeField(dataset.values, field);
|
|
249
287
|
|
|
288
|
+
if (mapping !== undefined) {
|
|
289
|
+
const values = countRadius ? dataset.values.map(() => 1) : readQuantitativeField(dataset.values, field);
|
|
290
|
+
if (values.length === 0 || values.some(value => value < 0) || !values.some(value => value > 0)) {
|
|
291
|
+
throw new Error("Measured radius requires non-negative values and a positive aggregate.");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
250
294
|
if (
|
|
251
295
|
["bar", "rect"].includes(layer.mark.type) &&
|
|
252
296
|
["ordinal", "nominal"].includes(fieldType) &&
|
|
@@ -258,11 +302,12 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
258
302
|
target,
|
|
259
303
|
layer,
|
|
260
304
|
previous,
|
|
261
|
-
requestedScale,
|
|
305
|
+
requestedScale: mapping === undefined ? requestedScale : { ...requestedScale, radialMapping: mapping },
|
|
262
306
|
field,
|
|
263
307
|
datum,
|
|
264
308
|
hasField: usesField,
|
|
265
309
|
fieldType,
|
|
310
|
+
temporalUnit,
|
|
266
311
|
scale,
|
|
267
312
|
coordinate: resolveCoordinate(program, channel, layer, args.coordinate),
|
|
268
313
|
...policy
|