ggaction 0.0.11 → 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 +80 -0
- package/README.md +3 -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 +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- 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 +79 -24
- 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 +153 -11
- package/src/actions/errorBars/edit.js +365 -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 +138 -14
- 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 +60 -4
- 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/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- 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/positionCompatibility.js +1 -1
- 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 +9 -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/ordinal.js +23 -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 +86 -23
- 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/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- 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 +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- 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/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- 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 +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { action } from "../../../../core/action.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolvePolarGuideResources } from "../resolve.js";
|
|
3
|
+
import {
|
|
4
|
+
AXIS_COMPONENTS,
|
|
5
|
+
assertRemovableAxisComponent,
|
|
6
|
+
hasAxisComponent,
|
|
7
|
+
removeAxisComponent,
|
|
8
|
+
validateEnabledAxisComponents
|
|
9
|
+
} from "../../axes/components.js";
|
|
3
10
|
import {
|
|
4
11
|
LABEL_EDIT_OPTIONS,
|
|
5
12
|
LINE_EDIT_OPTIONS,
|
|
@@ -22,12 +29,15 @@ const AXIS_EDIT_OPTIONS = Object.freeze([
|
|
|
22
29
|
"angle", "line", "ticks", "labels", "ticksAndLabels", "title"
|
|
23
30
|
]);
|
|
24
31
|
|
|
25
|
-
function
|
|
26
|
-
validateObject(args,
|
|
27
|
-
|
|
32
|
+
export function validatePolarAxisArgs(kind, args, operation) {
|
|
33
|
+
validateObject(args, kind === "theta"
|
|
34
|
+
? AXIS_OPTIONS.filter(option => option !== "angle")
|
|
35
|
+
: AXIS_OPTIONS, operation);
|
|
36
|
+
validateEnabledAxisComponents(args, operation);
|
|
37
|
+
if (Object.hasOwn(args, "line") && args.line !== false) {
|
|
28
38
|
validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
|
|
29
39
|
}
|
|
30
|
-
if (Object.hasOwn(args, "ticksAndLabels")) {
|
|
40
|
+
if (Object.hasOwn(args, "ticksAndLabels") && args.ticksAndLabels !== false) {
|
|
31
41
|
validateObject(
|
|
32
42
|
args.ticksAndLabels,
|
|
33
43
|
TICK_GROUP_OPTIONS,
|
|
@@ -68,7 +78,7 @@ function makeCreateAxis(kind) {
|
|
|
68
78
|
op: operation,
|
|
69
79
|
description: `Create the complete Polar ${kind} axis.`
|
|
70
80
|
}, function (args = {}) {
|
|
71
|
-
|
|
81
|
+
validatePolarAxisArgs(kind, args, operation);
|
|
72
82
|
const resources = resolvePolarGuideResources(this, kind, args, operation);
|
|
73
83
|
const angle = resolveAngle(this, kind, args);
|
|
74
84
|
const shared = {
|
|
@@ -82,21 +92,16 @@ function makeCreateAxis(kind) {
|
|
|
82
92
|
...(Object.hasOwn(group, "values") ? { values: group.values } : {})
|
|
83
93
|
};
|
|
84
94
|
let next = this;
|
|
85
|
-
if (
|
|
86
|
-
next = next
|
|
95
|
+
if (args.line !== false) {
|
|
96
|
+
next = next[`create${prefix(kind)}AxisLine`]({ ...shared, ...(args.line ?? {}) });
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
[`create${prefix(kind)}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
...mode,
|
|
93
|
-
...(group.ticks ?? {})
|
|
94
|
-
})
|
|
95
|
-
[`create${prefix(kind)}AxisLabels`]({
|
|
96
|
-
...shared,
|
|
97
|
-
...mode,
|
|
98
|
-
...(group.labels ?? {})
|
|
98
|
+
if (args.ticksAndLabels !== false) {
|
|
99
|
+
next = next[`create${prefix(kind)}AxisTicks`]({
|
|
100
|
+
...shared, ...mode, ...(group.ticks ?? {})
|
|
101
|
+
})[`create${prefix(kind)}AxisLabels`]({
|
|
102
|
+
...shared, ...mode, ...(group.labels ?? {})
|
|
99
103
|
});
|
|
104
|
+
}
|
|
100
105
|
return args.title === false
|
|
101
106
|
? next
|
|
102
107
|
: next[`create${prefix(kind)}AxisTitle`]({
|
|
@@ -118,29 +123,28 @@ function validateAxisEditArgs(kind, args, operation) {
|
|
|
118
123
|
throw new Error(`${operation} requires at least one axis change.`);
|
|
119
124
|
}
|
|
120
125
|
if (Object.hasOwn(args, "angle")) validateAngle(args.angle);
|
|
121
|
-
if (Object.hasOwn(args, "line")) {
|
|
126
|
+
if (Object.hasOwn(args, "line") && args.line !== false) {
|
|
122
127
|
validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
|
|
123
128
|
}
|
|
124
|
-
if (Object.hasOwn(args, "ticks")) {
|
|
129
|
+
if (Object.hasOwn(args, "ticks") && args.ticks !== false) {
|
|
125
130
|
validateObject(args.ticks, TICK_EDIT_OPTIONS, `${operation}.ticks`);
|
|
126
131
|
validateModeOptions(args.ticks, `${operation}.ticks`);
|
|
127
132
|
}
|
|
128
|
-
if (Object.hasOwn(args, "labels")) {
|
|
133
|
+
if (Object.hasOwn(args, "labels") && args.labels !== false) {
|
|
129
134
|
validateObject(args.labels, LABEL_EDIT_OPTIONS, `${operation}.labels`);
|
|
130
135
|
validateModeOptions(args.labels, `${operation}.labels`);
|
|
131
136
|
}
|
|
132
|
-
if (
|
|
137
|
+
if (args.ticksAndLabels !== undefined &&
|
|
138
|
+
(args.ticks !== undefined || args.labels !== undefined)) {
|
|
139
|
+
throw new Error(`${operation} cannot combine ticksAndLabels with ticks or labels.`);
|
|
140
|
+
}
|
|
141
|
+
if (Object.hasOwn(args, "ticksAndLabels") && args.ticksAndLabels !== false) {
|
|
133
142
|
validateObject(
|
|
134
143
|
args.ticksAndLabels,
|
|
135
144
|
TICK_GROUP_OPTIONS,
|
|
136
145
|
`${operation}.ticksAndLabels`
|
|
137
146
|
);
|
|
138
147
|
validateModeOptions(args.ticksAndLabels, `${operation}.ticksAndLabels`);
|
|
139
|
-
if (args.ticks !== undefined || args.labels !== undefined) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
`${operation} cannot combine ticksAndLabels with ticks or labels.`
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
148
|
if (args.ticksAndLabels.ticks !== undefined) {
|
|
145
149
|
validateObject(
|
|
146
150
|
args.ticksAndLabels.ticks,
|
|
@@ -156,7 +160,7 @@ function validateAxisEditArgs(kind, args, operation) {
|
|
|
156
160
|
);
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
|
-
if (Object.hasOwn(args, "title")) {
|
|
163
|
+
if (Object.hasOwn(args, "title") && args.title !== false) {
|
|
160
164
|
validateObject(
|
|
161
165
|
args.title,
|
|
162
166
|
kind === "theta"
|
|
@@ -167,6 +171,41 @@ function validateAxisEditArgs(kind, args, operation) {
|
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
function applyAxisEdit(program, kind, args) {
|
|
175
|
+
const angleChanged = Object.hasOwn(args, "angle");
|
|
176
|
+
let next = angleChanged
|
|
177
|
+
? program._withGuideConfig("radius", "layout", { angle: args.angle })
|
|
178
|
+
: program;
|
|
179
|
+
const edit = (component, options) => {
|
|
180
|
+
if (options === false) {
|
|
181
|
+
next = removeAxisComponent(next, kind, component);
|
|
182
|
+
} else if (options !== undefined || angleChanged && hasAxisComponent(next, kind, component)) {
|
|
183
|
+
next = next[`edit${prefix(kind)}Axis${component[0].toUpperCase() + component.slice(1)}`](options ?? {});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
edit("line", args.line);
|
|
187
|
+
if (args.ticksAndLabels === false) {
|
|
188
|
+
edit("ticks", false);
|
|
189
|
+
edit("labels", false);
|
|
190
|
+
} else if (args.ticksAndLabels !== undefined) {
|
|
191
|
+
const group = args.ticksAndLabels;
|
|
192
|
+
const mode = {
|
|
193
|
+
...(Object.hasOwn(group, "count") ? { count: group.count } : {}),
|
|
194
|
+
...(Object.hasOwn(group, "values") ? { values: group.values } : {})
|
|
195
|
+
};
|
|
196
|
+
edit("ticks", { ...mode, ...(group.ticks ?? {}) });
|
|
197
|
+
edit("labels", { ...mode, ...(group.labels ?? {}) });
|
|
198
|
+
} else {
|
|
199
|
+
edit("ticks", args.ticks);
|
|
200
|
+
edit("labels", args.labels);
|
|
201
|
+
}
|
|
202
|
+
edit("title", args.title);
|
|
203
|
+
if (!AXIS_COMPONENTS.some(component => hasAxisComponent(next, kind, component))) {
|
|
204
|
+
next = next[`remove${prefix(kind)}Axis`]();
|
|
205
|
+
}
|
|
206
|
+
return next;
|
|
207
|
+
}
|
|
208
|
+
|
|
170
209
|
function makeEditAxis(kind) {
|
|
171
210
|
const operation = `edit${prefix(kind)}Axis`;
|
|
172
211
|
return action({
|
|
@@ -174,42 +213,18 @@ function makeEditAxis(kind) {
|
|
|
174
213
|
description: `Edit selected Polar ${kind}-axis components.`
|
|
175
214
|
}, function (args = {}) {
|
|
176
215
|
validateAxisEditArgs(kind, args, operation);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let next = angleChanged
|
|
180
|
-
? this._withGuideConfig("radius", "layout", { angle: args.angle })
|
|
181
|
-
: this;
|
|
182
|
-
const has = component =>
|
|
183
|
-
next.graphicSpec.objects[names[component]] !== undefined;
|
|
184
|
-
if (args.line !== undefined || (angleChanged && has("line"))) {
|
|
185
|
-
next = next[`edit${prefix(kind)}AxisLine`](args.line ?? {});
|
|
216
|
+
if (!AXIS_COMPONENTS.some(component => hasAxisComponent(this, kind, component))) {
|
|
217
|
+
throw new Error(`${operation} requires an existing ${kind}-axis component.`);
|
|
186
218
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
...(Object.hasOwn(group, "values") ? { values: group.values } : {})
|
|
192
|
-
};
|
|
193
|
-
next = next[`edit${prefix(kind)}AxisTicks`]({
|
|
194
|
-
...mode,
|
|
195
|
-
...(group.ticks ?? {})
|
|
196
|
-
});
|
|
197
|
-
next = next[`edit${prefix(kind)}AxisLabels`]({
|
|
198
|
-
...mode,
|
|
199
|
-
...(group.labels ?? {})
|
|
200
|
-
});
|
|
201
|
-
} else {
|
|
202
|
-
if (args.ticks !== undefined || (angleChanged && has("ticks"))) {
|
|
203
|
-
next = next[`edit${prefix(kind)}AxisTicks`](args.ticks ?? {});
|
|
219
|
+
for (const component of AXIS_COMPONENTS) {
|
|
220
|
+
if (args[component] === false ||
|
|
221
|
+
args.ticksAndLabels === false && ["ticks", "labels"].includes(component)) {
|
|
222
|
+
assertRemovableAxisComponent(this, kind, component, operation);
|
|
204
223
|
}
|
|
205
|
-
if (args.labels !== undefined || (angleChanged && has("labels"))) {
|
|
206
|
-
next = next[`edit${prefix(kind)}AxisLabels`](args.labels ?? {});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (args.title !== undefined || (angleChanged && has("title"))) {
|
|
210
|
-
next = next[`edit${prefix(kind)}AxisTitle`](args.title ?? {});
|
|
211
224
|
}
|
|
212
|
-
|
|
225
|
+
// Validate the whole proposal on an immutable branch before returning changes.
|
|
226
|
+
applyAxisEdit(this, kind, args);
|
|
227
|
+
return applyAxisEdit(this, kind, args);
|
|
213
228
|
});
|
|
214
229
|
}
|
|
215
230
|
|
|
@@ -26,11 +26,12 @@ import {
|
|
|
26
26
|
prefix,
|
|
27
27
|
resolveAngle,
|
|
28
28
|
validateModeOptions,
|
|
29
|
+
validateComponentCreateArgs,
|
|
29
30
|
validateObject,
|
|
30
31
|
withAxisSemantics
|
|
31
32
|
} from "./shared.js";
|
|
32
33
|
|
|
33
|
-
function labelGeometry(program, kind, config) {
|
|
34
|
+
function labelGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
|
|
34
35
|
const frame = resolvePolarFrameForProgram(program);
|
|
35
36
|
const mapped = mapPolarGuideValues(program, config);
|
|
36
37
|
const text = formatPolarGuideValues(program, config, mapped.values);
|
|
@@ -45,7 +46,7 @@ function labelGeometry(program, kind, config) {
|
|
|
45
46
|
})
|
|
46
47
|
: resolveRadialAxisLabels({
|
|
47
48
|
frame,
|
|
48
|
-
angle
|
|
49
|
+
angle,
|
|
49
50
|
radii: mapped.positions,
|
|
50
51
|
offset: config.offset
|
|
51
52
|
}))
|
|
@@ -171,7 +172,7 @@ function makeCreateLabels(kind) {
|
|
|
171
172
|
op: operation.create,
|
|
172
173
|
description: `Create the Polar ${kind}-axis labels.`
|
|
173
174
|
}, function (args = {}) {
|
|
174
|
-
|
|
175
|
+
validateComponentCreateArgs(kind, args, LABEL_CREATE_OPTIONS, operation.create);
|
|
175
176
|
validateModeOptions(args, operation.create);
|
|
176
177
|
const names = polarGuideNames(kind);
|
|
177
178
|
if (this.graphicSpec.objects[names.labels] !== undefined) {
|
|
@@ -180,7 +181,7 @@ function makeCreateLabels(kind) {
|
|
|
180
181
|
const resources = componentResources(this, kind, args, operation.create);
|
|
181
182
|
const angle = resolveAngle(this, kind, args);
|
|
182
183
|
const config = resolveLabelConfig(this, kind, args, resources);
|
|
183
|
-
labelGeometry(this, kind, config);
|
|
184
|
+
labelGeometry(this, kind, config, angle);
|
|
184
185
|
let next = withAxisSemantics(this, kind, resources);
|
|
185
186
|
if (kind === "radius" &&
|
|
186
187
|
next.guideConfigs.axis?.radius?.layout === undefined) {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
operations,
|
|
19
19
|
prefix,
|
|
20
20
|
resolveAngle,
|
|
21
|
+
validateComponentCreateArgs,
|
|
21
22
|
validateObject,
|
|
22
23
|
withAxisSemantics
|
|
23
24
|
} from "./shared.js";
|
|
@@ -76,7 +77,7 @@ function makeCreateLine(kind) {
|
|
|
76
77
|
op: operation.create,
|
|
77
78
|
description: `Create the Polar ${kind}-axis baseline.`
|
|
78
79
|
}, function (args = {}) {
|
|
79
|
-
|
|
80
|
+
validateComponentCreateArgs(kind, args, LINE_CREATE_OPTIONS, operation.create);
|
|
80
81
|
const names = polarGuideNames(kind);
|
|
81
82
|
if (this.graphicSpec.objects[names.line] !== undefined) {
|
|
82
83
|
throw new Error(`${operation.create} requires a missing axis line.`);
|
|
@@ -48,6 +48,12 @@ export function validateObject(args, supported, operation) {
|
|
|
48
48
|
validateOptionObject(args, supported, operation);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export function validateComponentCreateArgs(kind, args, supported, operation) {
|
|
52
|
+
validateObject(args, kind === "theta"
|
|
53
|
+
? supported.filter(option => !["angle", "position"].includes(option))
|
|
54
|
+
: supported, operation);
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
export function validateModeOptions(args, operation) {
|
|
52
58
|
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
53
59
|
throw new Error(`${operation} cannot use count and values together.`);
|
|
@@ -23,11 +23,12 @@ import {
|
|
|
23
23
|
TICK_CREATE_OPTIONS,
|
|
24
24
|
TICK_EDIT_OPTIONS,
|
|
25
25
|
validateModeOptions,
|
|
26
|
+
validateComponentCreateArgs,
|
|
26
27
|
validateObject,
|
|
27
28
|
withAxisSemantics
|
|
28
29
|
} from "./shared.js";
|
|
29
30
|
|
|
30
|
-
function tickGeometry(program, kind, config) {
|
|
31
|
+
function tickGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
|
|
31
32
|
const frame = resolvePolarFrameForProgram(program);
|
|
32
33
|
const mapped = mapPolarGuideValues(program, config);
|
|
33
34
|
return {
|
|
@@ -40,7 +41,7 @@ function tickGeometry(program, kind, config) {
|
|
|
40
41
|
})
|
|
41
42
|
: resolveRadialAxisTicks({
|
|
42
43
|
frame,
|
|
43
|
-
angle
|
|
44
|
+
angle,
|
|
44
45
|
radii: mapped.positions,
|
|
45
46
|
length: config.length
|
|
46
47
|
}))
|
|
@@ -131,7 +132,7 @@ function makeCreateTicks(kind) {
|
|
|
131
132
|
op: operation.create,
|
|
132
133
|
description: `Create the Polar ${kind}-axis ticks.`
|
|
133
134
|
}, function (args = {}) {
|
|
134
|
-
|
|
135
|
+
validateComponentCreateArgs(kind, args, TICK_CREATE_OPTIONS, operation.create);
|
|
135
136
|
validateModeOptions(args, operation.create);
|
|
136
137
|
const names = polarGuideNames(kind);
|
|
137
138
|
if (this.graphicSpec.objects[names.ticks] !== undefined) {
|
|
@@ -140,7 +141,7 @@ function makeCreateTicks(kind) {
|
|
|
140
141
|
const resources = componentResources(this, kind, args, operation.create);
|
|
141
142
|
const angle = resolveAngle(this, kind, args);
|
|
142
143
|
const config = resolveTickConfig(this, kind, args, resources);
|
|
143
|
-
tickGeometry(this, kind, config);
|
|
144
|
+
tickGeometry(this, kind, config, angle);
|
|
144
145
|
let next = withAxisSemantics(this, kind, resources);
|
|
145
146
|
if (kind === "radius" &&
|
|
146
147
|
next.guideConfigs.axis?.radius?.layout === undefined) {
|
|
@@ -24,17 +24,18 @@ import {
|
|
|
24
24
|
resolveAngle,
|
|
25
25
|
TITLE_CREATE_OPTIONS,
|
|
26
26
|
TITLE_EDIT_OPTIONS,
|
|
27
|
+
validateComponentCreateArgs,
|
|
27
28
|
validateObject,
|
|
28
29
|
withAxisSemantics
|
|
29
30
|
} from "./shared.js";
|
|
30
31
|
|
|
31
|
-
function titleGeometry(program, kind, config) {
|
|
32
|
+
function titleGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
|
|
32
33
|
const frame = resolvePolarFrameForProgram(program);
|
|
33
34
|
return kind === "theta"
|
|
34
35
|
? resolveThetaAxisTitle({ frame, offset: config.offset })
|
|
35
36
|
: resolveRadialAxisTitle({
|
|
36
37
|
frame,
|
|
37
|
-
angle
|
|
38
|
+
angle,
|
|
38
39
|
offset: config.offset,
|
|
39
40
|
position: config.position
|
|
40
41
|
});
|
|
@@ -142,7 +143,7 @@ function makeCreateTitle(kind) {
|
|
|
142
143
|
op: operation.create,
|
|
143
144
|
description: `Create the Polar ${kind}-axis title.`
|
|
144
145
|
}, function (args = {}) {
|
|
145
|
-
|
|
146
|
+
validateComponentCreateArgs(kind, args, TITLE_CREATE_OPTIONS, operation.create);
|
|
146
147
|
const names = polarGuideNames(kind);
|
|
147
148
|
if (this.graphicSpec.objects[names.title] !== undefined) {
|
|
148
149
|
throw new Error(`${operation.create} requires a missing axis title.`);
|
|
@@ -154,7 +155,7 @@ function makeCreateTitle(kind) {
|
|
|
154
155
|
args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
|
|
155
156
|
"Polar axis title text"
|
|
156
157
|
);
|
|
157
|
-
const geometry = titleGeometry(this, kind, config);
|
|
158
|
+
const geometry = titleGeometry(this, kind, config, angle);
|
|
158
159
|
validateTitleGeometry(this, kind, config, geometry, text);
|
|
159
160
|
let next = withAxisSemantics(this, kind, resources);
|
|
160
161
|
if (kind === "radius" &&
|
|
@@ -28,7 +28,7 @@ const EDIT_OPTIONS = Object.freeze([
|
|
|
28
28
|
"count", "values", "color", "lineWidth", "strokeDash"
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
export function polarGridOperations(kind) {
|
|
32
32
|
const prefix = kind === "theta" ? "Theta" : "Radial";
|
|
33
33
|
return {
|
|
34
34
|
create: `create${prefix}Grid`,
|
|
@@ -46,7 +46,7 @@ function validateStrokeDash(value) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function
|
|
49
|
+
export function validatePolarGridOptions(args, operation, create) {
|
|
50
50
|
validateOptionObject(
|
|
51
51
|
args,
|
|
52
52
|
create ? CREATE_OPTIONS : EDIT_OPTIONS,
|
|
@@ -154,7 +154,7 @@ function editConcrete(program, kind, config, geometry) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
function makeRematerialize(kind) {
|
|
157
|
-
const operations =
|
|
157
|
+
const operations = polarGridOperations(kind);
|
|
158
158
|
return action({
|
|
159
159
|
op: operations.rematerialize,
|
|
160
160
|
description: `Recompute concrete Polar ${kind} grid geometry.`
|
|
@@ -181,12 +181,12 @@ function makeRematerialize(kind) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
function makeCreate(kind) {
|
|
184
|
-
const operations =
|
|
184
|
+
const operations = polarGridOperations(kind);
|
|
185
185
|
return action({
|
|
186
186
|
op: operations.create,
|
|
187
187
|
description: `Create a semantic and concrete Polar ${kind} grid.`
|
|
188
188
|
}, function (args = {}) {
|
|
189
|
-
|
|
189
|
+
validatePolarGridOptions(args, operations.create, true);
|
|
190
190
|
const graphic = polarGuideNames(kind).grid;
|
|
191
191
|
if (this.semanticSpec.guides.grid?.[kind] !== undefined ||
|
|
192
192
|
this.graphicSpec.objects[graphic] !== undefined) {
|
|
@@ -222,12 +222,12 @@ function makeCreate(kind) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
function makeEdit(kind) {
|
|
225
|
-
const operations =
|
|
225
|
+
const operations = polarGridOperations(kind);
|
|
226
226
|
return action({
|
|
227
227
|
op: operations.edit,
|
|
228
228
|
description: `Edit the existing Polar ${kind} grid.`
|
|
229
229
|
}, function (args = {}) {
|
|
230
|
-
|
|
230
|
+
validatePolarGridOptions(args, operations.edit, false);
|
|
231
231
|
const previous = this.guideConfigs.grid?.[kind];
|
|
232
232
|
if (previous === undefined) {
|
|
233
233
|
throw new Error(`${operations.edit} requires an existing grid.`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
|
|
3
|
+
export function sameGuideValue(left, right) {
|
|
4
|
+
if (left === right) return true;
|
|
5
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
6
|
+
return left.length === right.length && left.every((value, i) => sameGuideValue(value, right[i]));
|
|
7
|
+
}
|
|
8
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
9
|
+
const keys = Object.keys(left);
|
|
10
|
+
return keys.length === Object.keys(right).length && keys.every(key =>
|
|
11
|
+
Object.hasOwn(right, key) && sameGuideValue(left[key], right[key])
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function guideConflict(label) {
|
|
18
|
+
throw new Error(`Facade guide conflict: ${label}. Use the existing guide editor or disable this facade's guide branch.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Only explicit choices constrain existing appearance. Omitted defaults never
|
|
22
|
+
// overwrite an earlier author's title, placement, or style.
|
|
23
|
+
export function assertGuideOptions(requested, stored, label) {
|
|
24
|
+
for (const [key, value] of Object.entries(requested)) {
|
|
25
|
+
if (Array.isArray(value) && Array.isArray(stored?.[key]) && value.length === stored[key].length) {
|
|
26
|
+
assertGuideOptions(Object.fromEntries(value.entries()), Object.fromEntries(stored[key].entries()), `${label}.${key}`);
|
|
27
|
+
} else if (isPlainObject(value) && isPlainObject(stored?.[key])) {
|
|
28
|
+
assertGuideOptions(value, stored[key], `${label}.${key}`);
|
|
29
|
+
} else if (!sameGuideValue(value, stored?.[key])) {
|
|
30
|
+
guideConflict(`${label}.${key} differs from the existing guide`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveStoredGuideCoordinate(program, guide, channel) {
|
|
36
|
+
if (guide.coordinate !== undefined) return guide.coordinate;
|
|
37
|
+
const ids = [...new Set(program.semanticSpec.layers.filter(layer =>
|
|
38
|
+
layer.encoding?.[channel]?.scale === guide.scale
|
|
39
|
+
).map(layer => layer.coordinate).filter(id => id !== undefined))];
|
|
40
|
+
if (ids.length !== 1) {
|
|
41
|
+
guideConflict(`legacy ${channel} guide has no unique compatible coordinate`);
|
|
42
|
+
}
|
|
43
|
+
return ids[0];
|
|
44
|
+
}
|
|
@@ -19,29 +19,27 @@ export function inferHistogramBoundaries(program, channel, scaleId) {
|
|
|
19
19
|
layer.encoding.x.bin !== undefined
|
|
20
20
|
);
|
|
21
21
|
if (consumers.length === 0) return undefined;
|
|
22
|
-
if (consumers.length > 1) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`Guide values cannot infer shared histogram bins for scale "${scaleId}".`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const [layer] = consumers;
|
|
29
|
-
const encoding = layer.encoding.x;
|
|
30
|
-
const dataset = findDataset(program, layer.data);
|
|
31
22
|
const scale = findSemanticScale(program, scaleId);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
23
|
+
const boundaries = consumers.map(layer => {
|
|
24
|
+
const encoding = layer.encoding.x;
|
|
25
|
+
const dataset = findDataset(program, layer.data);
|
|
26
|
+
if (dataset === undefined || scale === undefined) {
|
|
27
|
+
throw new Error(`Guide values require histogram data and scale "${scaleId}".`);
|
|
28
|
+
}
|
|
29
|
+
return resolveHistogramBins({
|
|
30
|
+
values: readQuantitativeField(dataset.values, encoding.field),
|
|
31
|
+
bin: encoding.bin,
|
|
32
|
+
domain: scale.domain,
|
|
33
|
+
nice: scale.nice ?? true,
|
|
34
|
+
zero: scale.zero ?? false
|
|
35
|
+
}).boundaries;
|
|
36
|
+
});
|
|
37
|
+
const [unique] = boundaries;
|
|
38
|
+
if (boundaries.some(values => values.length !== unique.length ||
|
|
39
|
+
values.some((value, index) => value !== unique[index]))) {
|
|
40
|
+
throw new Error(`Guide values cannot infer shared histogram bins for scale "${scaleId}".`);
|
|
36
41
|
}
|
|
37
|
-
|
|
38
|
-
return resolveHistogramBins({
|
|
39
|
-
values: readQuantitativeField(dataset.values, encoding.field),
|
|
40
|
-
bin: encoding.bin,
|
|
41
|
-
domain: scale.domain,
|
|
42
|
-
nice: scale.nice ?? true,
|
|
43
|
-
zero: scale.zero ?? false
|
|
44
|
-
}).boundaries;
|
|
42
|
+
return unique;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
export function valuesFromTickConfig(program, config) {
|
package/src/actions/index.js
CHANGED
|
@@ -18,8 +18,10 @@ import { registerRegressionActions } from "./regression/index.js";
|
|
|
18
18
|
import { registerViolinPlotActions } from "./violinPlots/index.js";
|
|
19
19
|
import { registerErrorBandActions } from "./errorBands/index.js";
|
|
20
20
|
import { registerChartActions } from "./charts/index.js";
|
|
21
|
+
import { registerThemeActions } from "./theme/index.js";
|
|
21
22
|
|
|
22
23
|
export function registerActions(ProgramClass) {
|
|
24
|
+
registerThemeActions(ProgramClass);
|
|
23
25
|
registerPrimitiveActions(ProgramClass);
|
|
24
26
|
registerCompositionActions(ProgramClass);
|
|
25
27
|
registerFacetActions(ProgramClass);
|
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
validateNonEmptyString as requireField,
|
|
8
8
|
validateOptionObject
|
|
9
9
|
} from "../../core/validation.js";
|
|
10
|
-
import { readQuantitativeField } from "../../grammar/scales/index.js";
|
|
10
|
+
import { readQuantitativeField, resolveTemporalUnit } from "../../grammar/scales/index.js";
|
|
11
11
|
import { findDataset } from "../../selectors/datasets.js";
|
|
12
12
|
import { hasLayer, resolveEligibleLayer } from "../../selectors/layers.js";
|
|
13
13
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
14
14
|
|
|
15
15
|
const CHANNEL_OPTIONS = [
|
|
16
|
-
"field", "fieldType", "scale", "center", "extent", "level", "lower", "upper"
|
|
16
|
+
"field", "fieldType", "scale", "center", "extent", "method", "level", "lower", "upper", "temporalUnit"
|
|
17
17
|
];
|
|
18
|
-
const INTERVAL_PARAMETER_KEYS =
|
|
18
|
+
const INTERVAL_PARAMETER_KEYS = ["center", "extent", "method", "level", "lower", "upper"];
|
|
19
19
|
const POSITION_CHANNELS = ["x", "y"];
|
|
20
20
|
const FIELD_TYPES = [
|
|
21
21
|
"quantitative", "temporal", "ordinal", "nominal"
|
|
@@ -71,7 +71,7 @@ function resolveIntervalChannel(channels, sourceLayer, {
|
|
|
71
71
|
}) {
|
|
72
72
|
for (const hints of [
|
|
73
73
|
["lower", "upper"],
|
|
74
|
-
["center", "extent", "level"]
|
|
74
|
+
["center", "extent", "method", "level"]
|
|
75
75
|
]) {
|
|
76
76
|
const hinted = POSITION_CHANNELS.filter(channel =>
|
|
77
77
|
hasAny(channels[channel], hints)
|
|
@@ -123,8 +123,10 @@ function resolvePosition(program, channel, explicit, inferred, {
|
|
|
123
123
|
`${operation} ${channel} position requires ${positionTypes.join(", ")} field type.`
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
|
+
const temporalUnit = resolveTemporalUnit({ ...explicit, field: explicit?.field ?? inferred?.field }, fieldType, inferred);
|
|
126
127
|
return {
|
|
127
128
|
channel,
|
|
129
|
+
...(temporalUnit === undefined ? {} : { temporalUnit }),
|
|
128
130
|
field: requireField(
|
|
129
131
|
explicit?.field ?? inferred?.field,
|
|
130
132
|
`${operation} ${channel} field`
|
|
@@ -143,6 +145,9 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
|
|
|
143
145
|
operation,
|
|
144
146
|
intervalScaleDefaults
|
|
145
147
|
}) {
|
|
148
|
+
if (Object.hasOwn(explicit ?? {}, "temporalUnit")) {
|
|
149
|
+
throw new Error(`${operation} ${channel} interval does not accept temporalUnit.`);
|
|
150
|
+
}
|
|
146
151
|
if (explicit?.fieldType !== undefined) {
|
|
147
152
|
throw new Error(`${operation} ${channel} interval does not accept fieldType.`);
|
|
148
153
|
}
|
|
@@ -167,10 +172,11 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
|
|
|
167
172
|
if (
|
|
168
173
|
explicit.field !== undefined ||
|
|
169
174
|
explicit.extent !== undefined ||
|
|
175
|
+
explicit.method !== undefined ||
|
|
170
176
|
explicit.level !== undefined
|
|
171
177
|
) {
|
|
172
178
|
throw new Error(
|
|
173
|
-
`Explicit ${operation} ${channel} interval cannot combine field, extent, or level.`
|
|
179
|
+
`Explicit ${operation} ${channel} interval cannot combine field, extent, method, or level.`
|
|
174
180
|
);
|
|
175
181
|
}
|
|
176
182
|
const fields = {
|
|
@@ -187,15 +193,22 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
|
|
|
187
193
|
}
|
|
188
194
|
return { channel, mode: "explicit", fields, scale, title: fields.center };
|
|
189
195
|
}
|
|
196
|
+
const field = requireField(
|
|
197
|
+
explicit?.field ?? inferred?.field,
|
|
198
|
+
`${operation} ${channel} field`
|
|
199
|
+
);
|
|
200
|
+
if (!dataset.values.some(row => Number.isFinite(row?.[field]))) {
|
|
201
|
+
throw new TypeError(
|
|
202
|
+
`Field "${field}" must contain at least one finite number.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
190
205
|
return {
|
|
191
206
|
channel,
|
|
192
207
|
mode: "statistical",
|
|
193
|
-
field
|
|
194
|
-
explicit?.field ?? inferred?.field,
|
|
195
|
-
`${operation} ${channel} field`
|
|
196
|
-
),
|
|
208
|
+
field,
|
|
197
209
|
center: explicit?.center,
|
|
198
210
|
extent: explicit?.extent,
|
|
211
|
+
method: explicit?.method,
|
|
199
212
|
level: explicit?.level,
|
|
200
213
|
scale
|
|
201
214
|
};
|
|
@@ -205,11 +218,16 @@ function resolveGrouping(args, sourceLayer, independentField, mode, {
|
|
|
205
218
|
operation,
|
|
206
219
|
allowExplicitGrouping
|
|
207
220
|
}) {
|
|
208
|
-
if (
|
|
221
|
+
if (
|
|
222
|
+
mode === "explicit" &&
|
|
223
|
+
!allowExplicitGrouping &&
|
|
224
|
+
args.groupBy !== undefined &&
|
|
225
|
+
args.groupBy !== false
|
|
226
|
+
) {
|
|
209
227
|
throw new Error(`Explicit ${operation} intervals do not accept groupBy.`);
|
|
210
228
|
}
|
|
211
229
|
const inferred = sourceLayer?.encoding?.group?.field;
|
|
212
|
-
const groupField = args.groupBy ?? inferred;
|
|
230
|
+
const groupField = args.groupBy === false ? undefined : args.groupBy ?? inferred;
|
|
213
231
|
const normalizedGroup = groupField === undefined
|
|
214
232
|
? undefined
|
|
215
233
|
: requireField(groupField, `${operation} groupBy`);
|
|
@@ -234,7 +252,9 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
234
252
|
: requireObject(args.y, `${operation} y`)
|
|
235
253
|
};
|
|
236
254
|
let sourceLayer;
|
|
237
|
-
if (
|
|
255
|
+
if (policy.existingId === undefined && !(
|
|
256
|
+
args.target === undefined && args.x !== undefined && args.y !== undefined
|
|
257
|
+
)) {
|
|
238
258
|
const target = args.target === undefined
|
|
239
259
|
? undefined
|
|
240
260
|
: validateUserId(args.target, `${operation} source layer id`);
|
|
@@ -281,7 +301,7 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
281
301
|
policy
|
|
282
302
|
);
|
|
283
303
|
const defaultId = policy.defaultId;
|
|
284
|
-
const id = resolveOptionalUserId(args.id, {
|
|
304
|
+
const id = policy.existingId ?? resolveOptionalUserId(args.id, {
|
|
285
305
|
defaultId,
|
|
286
306
|
label: policy.ownerLabel,
|
|
287
307
|
operation,
|
|
@@ -289,7 +309,7 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
289
309
|
program.graphicSpec.objects[defaultId] !== undefined ||
|
|
290
310
|
findDataset(program, `${defaultId}IntervalData`) !== undefined
|
|
291
311
|
});
|
|
292
|
-
const generatedFields = {
|
|
312
|
+
const generatedFields = policy.generatedFields ?? {
|
|
293
313
|
center: `__${id}_center`,
|
|
294
314
|
lower: `__${id}_lower`,
|
|
295
315
|
upper: `__${id}_upper`
|
|
@@ -309,7 +329,7 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
309
329
|
source: dataset.id,
|
|
310
330
|
dataId: interval.mode === "statistical" ? `${id}IntervalData` : dataset.id,
|
|
311
331
|
coordinate: validateUserId(
|
|
312
|
-
args.coordinate ?? sourceLayer?.coordinate ?? "main",
|
|
332
|
+
args.coordinate ?? policy.coordinate ?? sourceLayer?.coordinate ?? "main",
|
|
313
333
|
`${resourceLabel} coordinate id`
|
|
314
334
|
),
|
|
315
335
|
orientation: intervalChannel === "y" ? "vertical" : "horizontal",
|