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,17 @@
|
|
|
1
|
+
import { TEXT_LABEL_LAYOUT_OPTIONS } from "./layout.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_TEXT_MARK,
|
|
6
|
+
isSourceOwnedText,
|
|
5
7
|
normalizeTextMarkConfig
|
|
6
8
|
} from "../../../grammar/text.js";
|
|
9
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
7
10
|
import {
|
|
8
11
|
canMaterializeArc,
|
|
9
|
-
|
|
12
|
+
canMaterializeRect,
|
|
13
|
+
canMaterializeText,
|
|
14
|
+
isTextSource
|
|
10
15
|
} from "../../../materialization/marks/index.js";
|
|
11
16
|
import { resolveTextGraphicItems } from "../../../materialization/text.js";
|
|
12
17
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
@@ -18,6 +23,10 @@ import {
|
|
|
18
23
|
resolveMarkId,
|
|
19
24
|
validateMarkOptions
|
|
20
25
|
} from "../shared.js";
|
|
26
|
+
import {
|
|
27
|
+
resolveFacadeData,
|
|
28
|
+
resolveFacadeId
|
|
29
|
+
} from "../../charts/shared.js";
|
|
21
30
|
import { resolveMarkGraphicPlacement } from
|
|
22
31
|
"../../../materialization/graphicHierarchy.js";
|
|
23
32
|
|
|
@@ -25,10 +34,9 @@ const STYLE_OPTIONS = Object.freeze([
|
|
|
25
34
|
"fill", "opacity", "fontSize", "fontFamily", "fontWeight",
|
|
26
35
|
"align", "baseline", "rotation", "dx", "dy"
|
|
27
36
|
]);
|
|
28
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
|
|
37
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
|
|
29
38
|
const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
|
|
30
39
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
|
|
31
|
-
const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect", "arc"]);
|
|
32
40
|
|
|
33
41
|
function sourceMatchesData(program, layer, requestedData) {
|
|
34
42
|
if (requestedData === undefined || layer?.data === requestedData) return true;
|
|
@@ -37,16 +45,27 @@ function sourceMatchesData(program, layer, requestedData) {
|
|
|
37
45
|
|
|
38
46
|
function eligibleSource(program, layer, requestedData) {
|
|
39
47
|
if (
|
|
40
|
-
!
|
|
41
|
-
layer.data === undefined ||
|
|
48
|
+
!isTextSource(layer) ||
|
|
42
49
|
!sourceMatchesData(program, layer, requestedData)
|
|
43
50
|
) return false;
|
|
44
51
|
if (layer.mark.type === "arc") return canMaterializeArc(program, layer);
|
|
52
|
+
if (layer.mark.type === "rect" && (canMaterializeRect(program, layer) ||
|
|
53
|
+
program.markConfigs[layer.id]?.gradientPlot?.materialized === true)) return true;
|
|
45
54
|
const encodings = resolveCompatibleEncodings(program, layer, "text");
|
|
46
55
|
return encodings.x !== undefined && encodings.y !== undefined;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
function resolveTextInheritance(program, args) {
|
|
59
|
+
if (Object.hasOwn(args, "source")) {
|
|
60
|
+
if (Object.hasOwn(args, "data")) {
|
|
61
|
+
throw new Error("createTextMark source and data are mutually exclusive.");
|
|
62
|
+
}
|
|
63
|
+
return textInheritance(program, resolveEligibleLayer(program, {
|
|
64
|
+
target: validateUserId(args.source, "Text source id"),
|
|
65
|
+
predicate: isTextSource,
|
|
66
|
+
label: "text source"
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
50
69
|
if (Object.hasOwn(args, "data")) return undefined;
|
|
51
70
|
const requestedData = program.context.currentData;
|
|
52
71
|
const candidates = program.semanticSpec.layers.filter(layer =>
|
|
@@ -60,10 +79,14 @@ function resolveTextInheritance(program, args) {
|
|
|
60
79
|
: undefined;
|
|
61
80
|
if (source === undefined && candidates.length > 1) {
|
|
62
81
|
throw new Error(
|
|
63
|
-
"Text source inference is ambiguous; provide data and encode its position explicitly."
|
|
82
|
+
"Text source inference is ambiguous; provide source, or data and encode its position explicitly."
|
|
64
83
|
);
|
|
65
84
|
}
|
|
66
85
|
if (source === undefined) return undefined;
|
|
86
|
+
return textInheritance(program, source);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function textInheritance(program, source) {
|
|
67
90
|
return {
|
|
68
91
|
source: source.id,
|
|
69
92
|
data: source.data,
|
|
@@ -136,6 +159,199 @@ const createTextMark = action(
|
|
|
136
159
|
}
|
|
137
160
|
);
|
|
138
161
|
|
|
162
|
+
const LABEL_OPTIONS = Object.freeze([
|
|
163
|
+
"id", "source", "field", "value", "content", "normalizeBy", "format", "layout",
|
|
164
|
+
...STYLE_OPTIONS
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const ANNOTATION_OPTIONS = Object.freeze([
|
|
168
|
+
"id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
|
|
169
|
+
...STYLE_OPTIONS
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
function annotationLayout(value) {
|
|
173
|
+
const layout = value === undefined || value === false ? undefined : value;
|
|
174
|
+
if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
|
|
175
|
+
throw new Error("createAnnotation layout target is owned by the created text layer.");
|
|
176
|
+
}
|
|
177
|
+
if (layout !== undefined) {
|
|
178
|
+
validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createAnnotation layout");
|
|
179
|
+
}
|
|
180
|
+
return layout;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveAnnotationBinding(program, args) {
|
|
184
|
+
const hasX = Object.hasOwn(args, "x");
|
|
185
|
+
const hasY = Object.hasOwn(args, "y");
|
|
186
|
+
if (!hasX && !hasY) {
|
|
187
|
+
if (["space", "data", "coordinate"].some(option => Object.hasOwn(args, option))) {
|
|
188
|
+
throw new Error("createAnnotation mark anchor accepts source but not space, data, or coordinate.");
|
|
189
|
+
}
|
|
190
|
+
return { mode: "mark" };
|
|
191
|
+
}
|
|
192
|
+
if (!hasX || !hasY) {
|
|
193
|
+
throw new Error("createAnnotation coordinate anchor requires both x and y.");
|
|
194
|
+
}
|
|
195
|
+
const space = args.space ?? "data";
|
|
196
|
+
if (space !== "data" && space !== "plot") {
|
|
197
|
+
throw new Error("createAnnotation space must be data or plot.");
|
|
198
|
+
}
|
|
199
|
+
if (space === "plot") {
|
|
200
|
+
if (Object.hasOwn(args, "source")) {
|
|
201
|
+
throw new Error("createAnnotation plot anchor does not accept source.");
|
|
202
|
+
}
|
|
203
|
+
if (![args.x, args.y].every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
|
|
204
|
+
throw new Error("createAnnotation plot coordinates must be finite numbers in [0, 1].");
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
mode: "plot",
|
|
208
|
+
data: resolveFacadeData(program, args.data, "createAnnotation"),
|
|
209
|
+
coordinate: args.coordinate
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
|
|
213
|
+
throw new Error("createAnnotation data anchor uses source data and coordinate.");
|
|
214
|
+
}
|
|
215
|
+
const source = resolveEligibleLayer(program, {
|
|
216
|
+
target: args.source === undefined
|
|
217
|
+
? undefined
|
|
218
|
+
: validateUserId(args.source, "createAnnotation source"),
|
|
219
|
+
predicate: layer =>
|
|
220
|
+
!isSourceOwnedText(layer) &&
|
|
221
|
+
layer.data !== undefined &&
|
|
222
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
223
|
+
layer.encoding?.y?.scale !== undefined &&
|
|
224
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian",
|
|
225
|
+
label: "createAnnotation Cartesian layer",
|
|
226
|
+
targetOption: "source"
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
mode: "data",
|
|
230
|
+
data: source.data,
|
|
231
|
+
coordinate: source.coordinate,
|
|
232
|
+
x: source.encoding.x,
|
|
233
|
+
y: source.encoding.y
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const createAnnotation = action(
|
|
238
|
+
{
|
|
239
|
+
op: "createAnnotation",
|
|
240
|
+
description: "Create text at a final mark, data, or plot anchor."
|
|
241
|
+
},
|
|
242
|
+
function (args = {}) {
|
|
243
|
+
validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
|
|
244
|
+
if (!Object.hasOwn(args, "text")) {
|
|
245
|
+
throw new Error("createAnnotation requires text.");
|
|
246
|
+
}
|
|
247
|
+
const id = resolveFacadeId(this, args.id, {
|
|
248
|
+
defaultId: "annotation",
|
|
249
|
+
operation: "createAnnotation"
|
|
250
|
+
});
|
|
251
|
+
const binding = resolveAnnotationBinding(this, args);
|
|
252
|
+
const layout = annotationLayout(args.layout);
|
|
253
|
+
const style = Object.fromEntries(STYLE_OPTIONS
|
|
254
|
+
.filter(option => Object.hasOwn(args, option))
|
|
255
|
+
.map(option => [option, args[option]]));
|
|
256
|
+
const text = {
|
|
257
|
+
value: args.text,
|
|
258
|
+
...(args.format === undefined ? {} : { format: args.format })
|
|
259
|
+
};
|
|
260
|
+
const apply = program => {
|
|
261
|
+
if (binding.mode === "mark") {
|
|
262
|
+
return program.createMarkLabels({
|
|
263
|
+
id,
|
|
264
|
+
...(args.source === undefined ? {} : { source: args.source }),
|
|
265
|
+
...style,
|
|
266
|
+
...text,
|
|
267
|
+
...(layout === undefined ? {} : { layout })
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const xScale = binding.mode === "plot" ? `${id}-x` : binding.x.scale;
|
|
271
|
+
const yScale = binding.mode === "plot" ? `${id}-y` : binding.y.scale;
|
|
272
|
+
let next = binding.mode === "plot"
|
|
273
|
+
? program
|
|
274
|
+
.createScale({ id: xScale, type: "linear", domain: [0, 1] })
|
|
275
|
+
.createScale({ id: yScale, type: "linear", domain: [0, 1] })
|
|
276
|
+
: program;
|
|
277
|
+
next = next
|
|
278
|
+
.createTextMark({ id, data: binding.data, ...style })
|
|
279
|
+
.encodeText({ target: id, ...text })
|
|
280
|
+
.encodeX({
|
|
281
|
+
target: id,
|
|
282
|
+
datum: args.x,
|
|
283
|
+
fieldType: binding.mode === "plot" ? "quantitative" : binding.x.fieldType,
|
|
284
|
+
scale: { id: xScale },
|
|
285
|
+
...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
|
|
286
|
+
...(binding.mode === "data" && binding.x.temporalUnit !== undefined
|
|
287
|
+
? { temporalUnit: binding.x.temporalUnit }
|
|
288
|
+
: {})
|
|
289
|
+
})
|
|
290
|
+
.encodeY({
|
|
291
|
+
target: id,
|
|
292
|
+
datum: args.y,
|
|
293
|
+
fieldType: binding.mode === "plot" ? "quantitative" : binding.y.fieldType,
|
|
294
|
+
scale: { id: yScale },
|
|
295
|
+
...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
|
|
296
|
+
...(binding.mode === "data" && binding.y.temporalUnit !== undefined
|
|
297
|
+
? { temporalUnit: binding.y.temporalUnit }
|
|
298
|
+
: {})
|
|
299
|
+
});
|
|
300
|
+
return layout === undefined
|
|
301
|
+
? next
|
|
302
|
+
: next.layoutLabels({ ...layout, target: id });
|
|
303
|
+
};
|
|
304
|
+
// Validate the full lower action chain before committing any child effect.
|
|
305
|
+
apply(this);
|
|
306
|
+
return apply(this);
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const createMarkLabels = action(
|
|
311
|
+
{
|
|
312
|
+
op: "createMarkLabels",
|
|
313
|
+
description: "Create final-item text labels attached to an existing mark."
|
|
314
|
+
},
|
|
315
|
+
function (args = {}) {
|
|
316
|
+
validateMarkOptions(args, LABEL_OPTIONS, "createMarkLabels");
|
|
317
|
+
const inherited = resolveTextInheritance(this, args);
|
|
318
|
+
if (inherited === undefined) {
|
|
319
|
+
throw new Error("createMarkLabels requires an eligible source mark; provide source explicitly.");
|
|
320
|
+
}
|
|
321
|
+
const id = validateUserId(args.id === undefined ? `${inherited.source}-labels` : args.id, "Text mark id");
|
|
322
|
+
assertMarkAvailable(this, id);
|
|
323
|
+
const style = Object.fromEntries(STYLE_OPTIONS
|
|
324
|
+
.filter(option => Object.hasOwn(args, option))
|
|
325
|
+
.map(option => [option, args[option]]));
|
|
326
|
+
normalizeTextMarkConfig(style);
|
|
327
|
+
const encoding = Object.fromEntries(
|
|
328
|
+
["field", "value", "content", "normalizeBy", "format"]
|
|
329
|
+
.filter(option => Object.hasOwn(args, option))
|
|
330
|
+
.map(option => [option, args[option]])
|
|
331
|
+
);
|
|
332
|
+
if (!["field", "value", "content"].some(option => Object.hasOwn(args, option))) {
|
|
333
|
+
encoding.content = "value";
|
|
334
|
+
}
|
|
335
|
+
const layout = args.layout === undefined || args.layout === false ? undefined : args.layout;
|
|
336
|
+
if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
|
|
337
|
+
throw new Error("createMarkLabels layout target is owned by the created label layer.");
|
|
338
|
+
}
|
|
339
|
+
const apply = program => {
|
|
340
|
+
const next = program.createTextMark({
|
|
341
|
+
id, source: inherited.source, align: "center", baseline: "middle", ...style
|
|
342
|
+
}).encodeText({ target: id, ...encoding });
|
|
343
|
+
return layout === undefined ? next : next.layoutLabels({ ...layout, target: id });
|
|
344
|
+
};
|
|
345
|
+
if (layout !== undefined) {
|
|
346
|
+
// Validate object shape before spreading it into the child action's options.
|
|
347
|
+
validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createMarkLabels layout");
|
|
348
|
+
}
|
|
349
|
+
// Validate all child effects on a discarded immutable branch, as scale edits do.
|
|
350
|
+
apply(this);
|
|
351
|
+
return apply(this);
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
|
|
139
355
|
const rematerializeTextMark = action(
|
|
140
356
|
{
|
|
141
357
|
op: "rematerializeTextMark",
|
|
@@ -209,6 +425,8 @@ const editTextMark = action(
|
|
|
209
425
|
export function registerTextMarkActions(ProgramClass) {
|
|
210
426
|
Object.assign(ProgramClass.prototype, {
|
|
211
427
|
createTextMark,
|
|
428
|
+
createMarkLabels,
|
|
429
|
+
createAnnotation,
|
|
212
430
|
editTextMark,
|
|
213
431
|
rematerializeTextMark
|
|
214
432
|
});
|
|
@@ -16,7 +16,7 @@ import { resolveTextGraphicEntries } from "../../../materialization/text.js";
|
|
|
16
16
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
17
17
|
import { editMarkGraphic } from "../shared.js";
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
|
|
20
20
|
"target", "axis", "padding", "maxDisplacement", "bounds", "leader"
|
|
21
21
|
]);
|
|
22
22
|
const REMOVE_OPTIONS = Object.freeze(["target"]);
|
|
@@ -252,7 +252,7 @@ const layoutLabels = action(
|
|
|
252
252
|
description: "Assign deterministic collision-aware layout to a text mark."
|
|
253
253
|
},
|
|
254
254
|
function (args = {}) {
|
|
255
|
-
validateOptionObject(args,
|
|
255
|
+
validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
|
|
256
256
|
const layer = requireCompleteText(this, args.target, "layoutLabels");
|
|
257
257
|
const policy = normalizePolicy(args);
|
|
258
258
|
const generatedId = leaderId(layer.id);
|
|
@@ -129,7 +129,9 @@ const createGraphics = action(
|
|
|
129
129
|
id, type: validatedType, parent, before, after
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
const existing = this.graphicSpec.objects
|
|
132
|
+
const existing = Object.hasOwn(this.graphicSpec.objects, id)
|
|
133
|
+
? this.graphicSpec.objects[id]
|
|
134
|
+
: undefined;
|
|
133
135
|
if (existing !== undefined) {
|
|
134
136
|
if (
|
|
135
137
|
sameDefinition(existing, { type: validatedType, length }) &&
|
|
@@ -21,7 +21,7 @@ export function registerSemanticPrimitiveAction(ProgramClass) {
|
|
|
21
21
|
const validateSemanticValue = createSemanticValueValidator({
|
|
22
22
|
validateDatasetTransforms,
|
|
23
23
|
validateParallel,
|
|
24
|
-
sourceMarkTypes: ["point", "bar", "rule", "rect", "arc"]
|
|
24
|
+
sourceMarkTypes: ["point", "bar", "line", "rule", "rect", "arc"]
|
|
25
25
|
});
|
|
26
26
|
ProgramClass.prototype.editSemantic =
|
|
27
27
|
createSemanticPrimitiveAction(validateSemanticValue);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasSemanticScaleReferences } from "../../selectors/scales.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import {
|
|
3
4
|
cloneAndFreeze,
|
|
@@ -75,7 +76,10 @@ function removeEntity(spec, parsed) {
|
|
|
75
76
|
const collection = spec[parsed.collection];
|
|
76
77
|
const index = collection.findIndex(item => item.id === parsed.id);
|
|
77
78
|
if (index === -1) return spec;
|
|
78
|
-
if (parsed.kind === "
|
|
79
|
+
if (parsed.kind === "scale" && parsed.path.length === 0) {
|
|
80
|
+
if (hasSemanticScaleReferences(spec, parsed.id)) throw new Error(`Scale "${parsed.id}" is still referenced.`);
|
|
81
|
+
}
|
|
82
|
+
if (["layer", "scale"].includes(parsed.kind) && parsed.path.length === 0) {
|
|
79
83
|
const nextCollection = collection.filter((_, itemIndex) => itemIndex !== index);
|
|
80
84
|
return freezeOwned({
|
|
81
85
|
...spec,
|
|
@@ -118,6 +122,35 @@ function removeRootProperty(spec, root, path) {
|
|
|
118
122
|
: spec;
|
|
119
123
|
}
|
|
120
124
|
|
|
125
|
+
// Materializers use this only for an immutable speculative branch while they
|
|
126
|
+
// replay a multi-step recipe. The public semantic primitive deliberately keeps
|
|
127
|
+
// materialized dataset values immutable after creation.
|
|
128
|
+
export function withPreviewDatasetValues(program, {
|
|
129
|
+
id,
|
|
130
|
+
values,
|
|
131
|
+
target,
|
|
132
|
+
omitValues = false
|
|
133
|
+
}) {
|
|
134
|
+
const semanticSpec = {
|
|
135
|
+
...program.semanticSpec,
|
|
136
|
+
datasets: program.semanticSpec.datasets.map(dataset => {
|
|
137
|
+
if (dataset.id !== id) return dataset;
|
|
138
|
+
if (omitValues) {
|
|
139
|
+
const { values: unused, ...definition } = dataset;
|
|
140
|
+
void unused;
|
|
141
|
+
return definition;
|
|
142
|
+
}
|
|
143
|
+
return { ...dataset, values };
|
|
144
|
+
}),
|
|
145
|
+
layers: target === undefined
|
|
146
|
+
? program.semanticSpec.layers
|
|
147
|
+
: program.semanticSpec.layers.map(layer =>
|
|
148
|
+
layer.id === target ? { ...layer, data: id } : layer
|
|
149
|
+
)
|
|
150
|
+
};
|
|
151
|
+
return program._clone({ semanticSpec });
|
|
152
|
+
}
|
|
153
|
+
|
|
121
154
|
export function createSemanticPrimitiveAction(validateSemanticValue) {
|
|
122
155
|
return action(
|
|
123
156
|
{
|
|
@@ -159,11 +192,15 @@ export function createSemanticPrimitiveAction(validateSemanticValue) {
|
|
|
159
192
|
parsed.kind === "layer" &&
|
|
160
193
|
parsed.path.length === 0 &&
|
|
161
194
|
this.context.currentMark === parsed.id;
|
|
195
|
+
const removesScale = parsed.kind === "scale" && parsed.path.length === 0;
|
|
196
|
+
const resolvedScales = removesScale ? Object.fromEntries(Object.entries(this.resolvedScales).filter(([id]) => id !== parsed.id)) : this.resolvedScales;
|
|
162
197
|
return this._clone({
|
|
163
198
|
semanticSpec,
|
|
164
|
-
...(
|
|
199
|
+
...(removesScale ? { resolvedScales: freezeOwned(resolvedScales) } : {}),
|
|
200
|
+
...(clearsCurrentData || clearsCurrentMark || (removesScale && this.context.currentScale === parsed.id)
|
|
165
201
|
? { context: freezeOwned({
|
|
166
202
|
...this.context,
|
|
203
|
+
...(removesScale && this.context.currentScale === parsed.id ? { currentScale: undefined } : {}),
|
|
167
204
|
...(clearsCurrentData ? { currentData: undefined } : {}),
|
|
168
205
|
...(clearsCurrentMark ? { currentMark: undefined } : {})
|
|
169
206
|
}) }
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
|
|
1
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
3
|
import { CATEGORICAL_LEGEND_CHANNELS } from "../../../core/vocabulary.js";
|
|
3
4
|
import { validateNonEmptySemanticString } from "./shared.js";
|
|
5
|
+
import { validateParallelAxisTitles } from "../../../grammar/parallelAxisTitles.js";
|
|
4
6
|
|
|
5
7
|
function validateLegend(property, value) {
|
|
6
8
|
if (property === "title") {
|
|
@@ -24,6 +26,14 @@ function validateLegend(property, value) {
|
|
|
24
26
|
|
|
25
27
|
export function validateGuideSemanticValue(_program, parsed, value) {
|
|
26
28
|
const property = parsed.path.at(-1);
|
|
29
|
+
if (parsed.id === "axis.parallel" && property === "titles") {
|
|
30
|
+
validateParallelAxisTitles(value);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (["legend.series", "legend.color"].includes(parsed.id) && property === "order") {
|
|
34
|
+
normalizeLegendOrder(value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
27
37
|
if (parsed.id === "legend.series") {
|
|
28
38
|
validateLegend(property, value);
|
|
29
39
|
return;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
MARK_TYPES,
|
|
5
5
|
STACK_MODES
|
|
6
6
|
} from "../../../core/vocabulary.js";
|
|
7
|
+
import { validateMarkLabelContent, validateMarkLabelNormalization } from "../../../grammar/markLabels.js";
|
|
7
8
|
import { validateAggregate } from "../../../grammar/aggregate.js";
|
|
8
9
|
import {
|
|
9
10
|
normalizeHistogramBin,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
validateHistogramBinStep
|
|
12
13
|
} from "../../../grammar/histogram.js";
|
|
13
14
|
import { validatePathOrderDirection } from "../../../grammar/pathOrder.js";
|
|
15
|
+
import { normalizeGroupFields } from "../../../grammar/pathSeries.js";
|
|
14
16
|
import { normalizeCategoryOrder } from "../../../grammar/categoryOrder.js";
|
|
15
17
|
import { validateSemanticFieldType } from "../../../grammar/scales/index.js";
|
|
16
18
|
import { findLayer } from "../../../selectors/layers.js";
|
|
@@ -40,7 +42,7 @@ export function validateLayerSemanticValue(
|
|
|
40
42
|
parsed,
|
|
41
43
|
value,
|
|
42
44
|
{
|
|
43
|
-
sourceMarkTypes = ["point", "bar", "rule", "rect", "arc"],
|
|
45
|
+
sourceMarkTypes = ["point", "bar", "line", "rule", "rect", "arc"],
|
|
44
46
|
validateParallel
|
|
45
47
|
} = {}
|
|
46
48
|
) {
|
|
@@ -48,6 +50,17 @@ export function validateLayerSemanticValue(
|
|
|
48
50
|
if (property === "mark.type" && !MARK_TYPES.includes(value)) {
|
|
49
51
|
throw new Error(`Unknown mark type "${value}".`);
|
|
50
52
|
}
|
|
53
|
+
if (property === "mark.missing" && !["error", "break"].includes(value)) {
|
|
54
|
+
throw new Error(`Unsupported area missing policy "${value}".`);
|
|
55
|
+
}
|
|
56
|
+
if (property === "layout.mode" && !COLOR_LAYOUTS.includes(value)) {
|
|
57
|
+
throw new Error(`Unsupported series layout "${value}".`);
|
|
58
|
+
}
|
|
59
|
+
if (property === "encoding.group.inferredFrom" && !["color", "offset"].includes(value)) {
|
|
60
|
+
throw new Error(`Unsupported group inference origin "${value}".`);
|
|
61
|
+
}
|
|
62
|
+
if (property === "encoding.text.content") validateMarkLabelContent(value);
|
|
63
|
+
if (property === "encoding.text.normalizeBy") validateMarkLabelNormalization(value);
|
|
51
64
|
if (property === "source") {
|
|
52
65
|
validateLayerSource(program, parsed, value, sourceMarkTypes);
|
|
53
66
|
}
|
|
@@ -58,6 +71,10 @@ export function validateLayerSemanticValue(
|
|
|
58
71
|
throw new Error("Path order field type must be quantitative.");
|
|
59
72
|
}
|
|
60
73
|
if (property.endsWith(".fieldType")) validateSemanticFieldType(value);
|
|
74
|
+
if (property === "encoding.group.fields") {
|
|
75
|
+
if (!Array.isArray(value)) throw new TypeError("Group fields must be an array.");
|
|
76
|
+
normalizeGroupFields(value);
|
|
77
|
+
}
|
|
61
78
|
if (property === "encoding.pathOrder.order") validatePathOrderDirection(value);
|
|
62
79
|
if (property.endsWith(".categoryOrder")) normalizeCategoryOrder(value);
|
|
63
80
|
if (property.startsWith("encoding.parallel.")) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { validateRadialMapping } from "../../../grammar/scales/radial.js";
|
|
1
2
|
import {
|
|
2
3
|
validateContinuousColorInterpolation,
|
|
4
|
+
validateSequentialMidpoint,
|
|
3
5
|
validateScalePropertyForType,
|
|
4
6
|
validateSemanticScaleDomain,
|
|
5
7
|
validateSemanticScaleRange,
|
|
@@ -16,10 +18,17 @@ function validateOwnedProperty(existing, property) {
|
|
|
16
18
|
export function validateScaleSemanticValue(program, parsed, value) {
|
|
17
19
|
const property = parsed.path.join(".");
|
|
18
20
|
const existing = findSemanticScale(program, parsed.id);
|
|
21
|
+
if (property === "midpoint") {
|
|
22
|
+
if (value === "auto" || value === undefined) {
|
|
23
|
+
throw new TypeError("Semantic midpoint must be a finite number; remove the property to reset it.");
|
|
24
|
+
}
|
|
25
|
+
return validateSequentialMidpoint(value, existing?.type ?? "sequential");
|
|
26
|
+
}
|
|
27
|
+
if (property === "radialMapping") return validateRadialMapping(value);
|
|
19
28
|
if (property === "type") {
|
|
20
29
|
validateSemanticScaleType(value);
|
|
21
30
|
for (const owned of [
|
|
22
|
-
"nice", "zero", "clamp", "base", "exponent", "constant",
|
|
31
|
+
"nice", "zero", "clamp", "base", "exponent", "constant", "midpoint",
|
|
23
32
|
"paddingInner", "paddingOuter", "padding", "align"
|
|
24
33
|
]) {
|
|
25
34
|
if (existing?.[owned] !== undefined) validateScalePropertyForType(value, owned);
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
const REGRESSION_OPTIONS = Object.freeze([
|
|
12
12
|
"target", "x", "y", "groupBy", "method", "degree", "span",
|
|
13
|
-
"confidence", "interval", "band", "line"
|
|
13
|
+
"confidenceMethod", "level", "confidence", "interval", "band", "line"
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
16
|
export const createRegression = action(
|
|
@@ -18,7 +18,7 @@ import { requireRegressionField } from "./resolve.js";
|
|
|
18
18
|
|
|
19
19
|
const OPTIONS = Object.freeze([
|
|
20
20
|
"target", "data", "x", "y", "groupBy", "method", "degree", "span",
|
|
21
|
-
"confidence", "interval", "band", "line"
|
|
21
|
+
"confidenceMethod", "level", "confidence", "interval", "band", "line"
|
|
22
22
|
]);
|
|
23
23
|
const BAND_OPTIONS = Object.freeze([
|
|
24
24
|
"color", "opacity", "stroke", "strokeWidth", "curve"
|
|
@@ -100,18 +100,31 @@ function validateLinePatch(value) {
|
|
|
100
100
|
function resolveParameters(previous, args) {
|
|
101
101
|
const method = args.method ?? previous.method;
|
|
102
102
|
const raw = { method };
|
|
103
|
-
for (const key of [
|
|
103
|
+
for (const key of [
|
|
104
|
+
"degree", "span", "confidenceMethod", "level", "confidence", "interval"
|
|
105
|
+
]) {
|
|
104
106
|
if (Object.hasOwn(args, key)) raw[key] = args[key];
|
|
105
107
|
}
|
|
108
|
+
const inheritsLevel = !Object.hasOwn(raw, "confidence");
|
|
106
109
|
if (method === previous.method) {
|
|
107
|
-
for (const key of [
|
|
108
|
-
|
|
110
|
+
for (const key of [
|
|
111
|
+
"degree", "span", "confidenceMethod", "level", "interval"
|
|
112
|
+
]) {
|
|
113
|
+
if (
|
|
114
|
+
!Object.hasOwn(raw, key) &&
|
|
115
|
+
(key !== "level" || inheritsLevel) &&
|
|
116
|
+
Object.hasOwn(previous, key)
|
|
117
|
+
) {
|
|
109
118
|
raw[key] = previous[key];
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
} else if (method !== "loess" && previous.method !== "loess") {
|
|
113
|
-
for (const key of ["
|
|
114
|
-
if (
|
|
122
|
+
for (const key of ["confidenceMethod", "level", "interval"]) {
|
|
123
|
+
if (
|
|
124
|
+
!Object.hasOwn(raw, key) &&
|
|
125
|
+
(key !== "level" || inheritsLevel) &&
|
|
126
|
+
Object.hasOwn(previous, key)
|
|
127
|
+
) {
|
|
115
128
|
raw[key] = previous[key];
|
|
116
129
|
}
|
|
117
130
|
}
|
|
@@ -41,7 +41,7 @@ export function findRegressionPoint(program, requested) {
|
|
|
41
41
|
|
|
42
42
|
export function inferRegressionGroup(layer, args) {
|
|
43
43
|
if (Object.hasOwn(args, "groupBy")) {
|
|
44
|
-
return args.groupBy === undefined
|
|
44
|
+
return args.groupBy === undefined || args.groupBy === false
|
|
45
45
|
? undefined
|
|
46
46
|
: requireRegressionField(args.groupBy, "Regression groupBy");
|
|
47
47
|
}
|