ggaction 0.0.16 → 0.0.18
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 +66 -0
- package/README.md +3 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -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 +10 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +16 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +4 -4
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- package/src/actions/marks/bar/materialize.js +9 -6
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -27
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +41 -22
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +382 -0
- package/src/grammar/density.js +53 -5
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/filter.js +133 -39
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +34 -10
- package/src/grammar/itemMissing.js +59 -0
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -190
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +26 -3
- package/src/materialization/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/revisionIdentity.js +19 -0
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +72 -7
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +17 -6
- package/src/materialization/textLines.js +15 -0
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +94 -8
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/svg.js +4 -1
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
|
@@ -39,13 +39,15 @@ import {
|
|
|
39
39
|
requestedStrokeDetails,
|
|
40
40
|
STROKE_STYLE_PROPERTIES
|
|
41
41
|
} from "../../../grammar/strokeStyle.js";
|
|
42
|
+
import { applyItemMissingPolicy, validateItemMissing } from
|
|
43
|
+
"../../../grammar/itemMissing.js";
|
|
42
44
|
|
|
43
45
|
const APPEARANCE_OPTIONS = Object.freeze([
|
|
44
46
|
"stroke", "strokeWidth", "strokeDash", "opacity",
|
|
45
47
|
...STROKE_STYLE_PROPERTIES
|
|
46
48
|
]);
|
|
47
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
|
|
48
|
-
const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
|
|
49
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
|
|
50
|
+
const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
|
|
49
51
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
50
52
|
const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
|
|
51
53
|
const DEFAULT_RULE_CONFIG = Object.freeze({
|
|
@@ -66,10 +68,11 @@ function requireRule(program, id) {
|
|
|
66
68
|
if (layer?.mark?.type !== "rule") {
|
|
67
69
|
throw new Error(`Unknown rule mark "${id}".`);
|
|
68
70
|
}
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
+
const sourceDataset = findDataset(program, layer.data);
|
|
72
|
+
if (sourceDataset === undefined) {
|
|
71
73
|
throw new Error(`Rule mark "${id}" requires an existing dataset.`);
|
|
72
74
|
}
|
|
75
|
+
const dataset = applyItemMissingPolicy(layer, sourceDataset);
|
|
73
76
|
const graphic = program.graphicSpec.objects[id];
|
|
74
77
|
if (graphic?.type !== "line" || graphic.items === undefined) {
|
|
75
78
|
throw new Error(`Rule mark "${id}" requires line collection graphics.`);
|
|
@@ -118,6 +121,12 @@ const createRuleMark = /* @__PURE__ */ action(
|
|
|
118
121
|
let created = this
|
|
119
122
|
.editSemantic({ property: `layer[${id}].mark.type`, value: "rule" })
|
|
120
123
|
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
124
|
+
if (Object.hasOwn(args, "missing")) {
|
|
125
|
+
created = created.editSemantic({
|
|
126
|
+
property: `layer[${id}].mark.missing`,
|
|
127
|
+
value: validateItemMissing(args.missing, "Rule missing")
|
|
128
|
+
});
|
|
129
|
+
}
|
|
121
130
|
created = applyLayeredMarkInheritance(created, id, inherited);
|
|
122
131
|
created = created
|
|
123
132
|
.createGraphics({
|
|
@@ -154,11 +163,18 @@ const editRuleMark = /* @__PURE__ */ action(
|
|
|
154
163
|
label: "rule mark"
|
|
155
164
|
});
|
|
156
165
|
const appearance = planRuleAppearance(args, layer);
|
|
157
|
-
if (appearance.length === 0 && Object.keys(strokeDetails).length === 0
|
|
158
|
-
|
|
166
|
+
if (appearance.length === 0 && Object.keys(strokeDetails).length === 0 &&
|
|
167
|
+
!Object.hasOwn(args, "missing")) {
|
|
168
|
+
throw new Error("editRuleMark requires missing or an appearance change.");
|
|
159
169
|
}
|
|
160
|
-
const
|
|
161
|
-
|
|
170
|
+
const semantic = Object.hasOwn(args, "missing")
|
|
171
|
+
? this.editSemantic({
|
|
172
|
+
property: `layer[${layer.id}].mark.missing`,
|
|
173
|
+
value: validateItemMissing(args.missing, "Rule missing")
|
|
174
|
+
})
|
|
175
|
+
: this;
|
|
176
|
+
const configured = semantic._withMarkConfig(layer.id, {
|
|
177
|
+
...semantic.markConfigs[layer.id],
|
|
162
178
|
...strokeDetails
|
|
163
179
|
});
|
|
164
180
|
const materialized = appearance.length === 0
|
|
@@ -282,13 +298,16 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
|
|
|
282
298
|
properties: item?.properties
|
|
283
299
|
});
|
|
284
300
|
if (box === undefined) throw new Error(`Rule mark "${id}" requires box span owner "${boxSpan}".`);
|
|
301
|
+
const pixels = resolved.markConfigs[boxSpan]?.boxPlot?.median?.width?.pixels;
|
|
285
302
|
if (layer.encoding?.x?.fieldType === "quantitative") {
|
|
286
303
|
startX = endX = mapped.x[index];
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
const height = pixels ?? box.height;
|
|
305
|
+
startY = box.y + (box.height - height) / 2;
|
|
306
|
+
endY = startY + height;
|
|
289
307
|
} else {
|
|
290
|
-
|
|
291
|
-
|
|
308
|
+
const width = pixels ?? box.width;
|
|
309
|
+
startX = box.x + (box.width - width) / 2;
|
|
310
|
+
endX = startX + width;
|
|
292
311
|
startY = endY = mapped.y[index];
|
|
293
312
|
}
|
|
294
313
|
} else if (mode === "fixed-span") {
|
|
@@ -3,6 +3,7 @@ import { action } from "../../../core/action.js";
|
|
|
3
3
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_TEXT_MARK,
|
|
6
|
+
TEXT_MARK_STYLE_OPTIONS as STYLE_OPTIONS,
|
|
6
7
|
isSourceOwnedText,
|
|
7
8
|
normalizeTextMarkConfig
|
|
8
9
|
} from "../../../grammar/text.js";
|
|
@@ -39,13 +40,10 @@ import {
|
|
|
39
40
|
} from "../../charts/shared.js";
|
|
40
41
|
import { resolveMarkGraphicPlacement } from
|
|
41
42
|
"../../../materialization/graphicHierarchy.js";
|
|
43
|
+
import { validateItemMissing } from "../../../grammar/itemMissing.js";
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
"align", "baseline", "rotation", "dx", "dy"
|
|
46
|
-
]);
|
|
47
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
|
|
48
|
-
const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
|
|
45
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", "missing", ...STYLE_OPTIONS]);
|
|
46
|
+
const EDIT_OPTIONS = Object.freeze(["target", "missing", ...STYLE_OPTIONS]);
|
|
49
47
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
|
|
50
48
|
|
|
51
49
|
function sourceMatchesData(program, layer, requestedData) {
|
|
@@ -139,6 +137,9 @@ const createTextMark = /* @__PURE__ */ action(
|
|
|
139
137
|
operation: "createTextMark"
|
|
140
138
|
});
|
|
141
139
|
const inherited = resolveTextInheritance(this, args);
|
|
140
|
+
if (Object.hasOwn(args, "missing") && inherited?.source !== undefined) {
|
|
141
|
+
throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
|
|
142
|
+
}
|
|
142
143
|
const { data } = resolveMarkData(this, {
|
|
143
144
|
...args,
|
|
144
145
|
...(args.data === undefined && inherited?.data !== undefined
|
|
@@ -150,6 +151,12 @@ const createTextMark = /* @__PURE__ */ action(
|
|
|
150
151
|
let next = this
|
|
151
152
|
.editSemantic({ property: `layer[${id}].mark.type`, value: "text" })
|
|
152
153
|
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
154
|
+
if (Object.hasOwn(args, "missing")) {
|
|
155
|
+
next = next.editSemantic({
|
|
156
|
+
property: `layer[${id}].mark.missing`,
|
|
157
|
+
value: validateItemMissing(args.missing, "Text missing")
|
|
158
|
+
});
|
|
159
|
+
}
|
|
153
160
|
if (inherited?.source !== undefined) {
|
|
154
161
|
next = next.editSemantic({
|
|
155
162
|
property: `layer[${id}].source`,
|
|
@@ -185,7 +192,7 @@ const LABEL_OPTIONS = Object.freeze([
|
|
|
185
192
|
]);
|
|
186
193
|
|
|
187
194
|
const ANNOTATION_OPTIONS = Object.freeze([
|
|
188
|
-
"id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
|
|
195
|
+
"id", "text", "format", "anchor", "x", "y", "space", "source", "data", "coordinate", "layout",
|
|
189
196
|
...STYLE_OPTIONS
|
|
190
197
|
]);
|
|
191
198
|
|
|
@@ -261,6 +268,15 @@ const createAnnotation = /* @__PURE__ */ action(
|
|
|
261
268
|
},
|
|
262
269
|
function (args = {}) {
|
|
263
270
|
validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
|
|
271
|
+
if (Object.hasOwn(args, "anchor")) {
|
|
272
|
+
if (args.anchor !== "plot-center") {
|
|
273
|
+
throw new Error('createAnnotation anchor must be "plot-center".');
|
|
274
|
+
}
|
|
275
|
+
if (["x", "y", "space", "source", "align", "baseline"].some(key => Object.hasOwn(args, key))) {
|
|
276
|
+
throw new Error("createAnnotation plot-center owns x, y, space, align, and baseline and does not accept source.");
|
|
277
|
+
}
|
|
278
|
+
args = { ...args, space: "plot", x: 0.5, y: 0.5, align: "center", baseline: "middle" };
|
|
279
|
+
}
|
|
264
280
|
if (!Object.hasOwn(args, "text")) {
|
|
265
281
|
throw new Error("createAnnotation requires text.");
|
|
266
282
|
}
|
|
@@ -465,20 +481,35 @@ const editTextMark = /* @__PURE__ */ action(
|
|
|
465
481
|
},
|
|
466
482
|
function (args = {}) {
|
|
467
483
|
validateMarkOptions(args, EDIT_OPTIONS, "editTextMark");
|
|
468
|
-
if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
|
|
484
|
+
if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
|
|
469
485
|
throw new Error("editTextMark requires at least one editable property.");
|
|
470
486
|
}
|
|
471
487
|
const layer = requireTextLayer(this, args.target, "editTextMark");
|
|
472
|
-
|
|
488
|
+
if (Object.hasOwn(args, "missing") && layer.source !== undefined) {
|
|
489
|
+
throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
|
|
490
|
+
}
|
|
491
|
+
if (args.inheritColor && layer.source === undefined) {
|
|
492
|
+
throw new Error("Text inheritColor requires a source-owned label.");
|
|
493
|
+
}
|
|
494
|
+
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
495
|
+
throw new Error("Text fill conflicts with its color encoding.");
|
|
496
|
+
}
|
|
497
|
+
const semantic = Object.hasOwn(args, "missing")
|
|
498
|
+
? this.editSemantic({
|
|
499
|
+
property: `layer[${layer.id}].mark.missing`,
|
|
500
|
+
value: validateItemMissing(args.missing, "Text missing")
|
|
501
|
+
})
|
|
502
|
+
: this;
|
|
503
|
+
const next = semantic._withMarkConfig(
|
|
473
504
|
layer.id,
|
|
474
505
|
{
|
|
475
506
|
...normalizeTextMarkConfig(
|
|
476
507
|
args,
|
|
477
|
-
|
|
508
|
+
semantic.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
|
|
478
509
|
),
|
|
479
510
|
fillExplicit: Object.hasOwn(args, "fill")
|
|
480
511
|
? true
|
|
481
|
-
:
|
|
512
|
+
: semantic.markConfigs[layer.id]?.fillExplicit ?? false
|
|
482
513
|
}
|
|
483
514
|
);
|
|
484
515
|
return canMaterializeText(next, layer)
|
|
@@ -144,6 +144,9 @@ function layoutInputs(program, layer) {
|
|
|
144
144
|
sourceX: entry.anchor.x,
|
|
145
145
|
sourceY: entry.anchor.y,
|
|
146
146
|
text: entry.graphic.properties.text,
|
|
147
|
+
lines: entry.graphic.properties.lines,
|
|
148
|
+
fontFamily: entry.graphic.properties.fontFamily,
|
|
149
|
+
fontWeight: entry.graphic.properties.fontWeight,
|
|
147
150
|
fontSize: entry.graphic.properties.fontSize,
|
|
148
151
|
textAlign: entry.graphic.properties.textAlign,
|
|
149
152
|
textBaseline: entry.graphic.properties.textBaseline,
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
requestedStrokeDetails,
|
|
22
22
|
STROKE_STYLE_PROPERTIES
|
|
23
23
|
} from "../../../grammar/strokeStyle.js";
|
|
24
|
+
import { applyItemMissingPolicy, validateItemMissing } from
|
|
25
|
+
"../../../grammar/itemMissing.js";
|
|
24
26
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
25
27
|
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
26
28
|
import {
|
|
@@ -37,8 +39,8 @@ import {
|
|
|
37
39
|
const APPEARANCE_OPTIONS = Object.freeze([
|
|
38
40
|
"length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
|
|
39
41
|
]);
|
|
40
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
|
|
41
|
-
const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
|
|
42
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
|
|
43
|
+
const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
|
|
42
44
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
43
45
|
|
|
44
46
|
export const DEFAULT_TICK_CONFIG = Object.freeze({
|
|
@@ -114,6 +116,12 @@ export const createTickMark = /* @__PURE__ */ action(
|
|
|
114
116
|
let created = this
|
|
115
117
|
.editSemantic({ property: `layer[${id}].mark.type`, value: "tick" })
|
|
116
118
|
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
119
|
+
if (Object.hasOwn(args, "missing")) {
|
|
120
|
+
created = created.editSemantic({
|
|
121
|
+
property: `layer[${id}].mark.missing`,
|
|
122
|
+
value: validateItemMissing(args.missing, "Tick missing")
|
|
123
|
+
});
|
|
124
|
+
}
|
|
117
125
|
created = applyLayeredMarkInheritance(created, id, inherited)
|
|
118
126
|
.createGraphics({
|
|
119
127
|
id,
|
|
@@ -141,9 +149,9 @@ export const editTickMark = /* @__PURE__ */ action(
|
|
|
141
149
|
function (args = {}) {
|
|
142
150
|
validateMarkOptions(args, EDIT_OPTIONS, "editTickMark");
|
|
143
151
|
requestedStrokeDetails(args, "editTickMark");
|
|
144
|
-
if (!APPEARANCE_OPTIONS.some(property => Object.hasOwn(args, property))) {
|
|
152
|
+
if (!["missing", ...APPEARANCE_OPTIONS].some(property => Object.hasOwn(args, property))) {
|
|
145
153
|
throw new Error(
|
|
146
|
-
"editTickMark requires length, stroke, strokeWidth, or
|
|
154
|
+
"editTickMark requires length, stroke, strokeWidth, opacity, or missing."
|
|
147
155
|
);
|
|
148
156
|
}
|
|
149
157
|
const layer = resolveTick(this, args.target, "editTickMark");
|
|
@@ -152,11 +160,17 @@ export const editTickMark = /* @__PURE__ */ action(
|
|
|
152
160
|
"editTickMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
|
|
153
161
|
);
|
|
154
162
|
}
|
|
163
|
+
const semantic = Object.hasOwn(args, "missing")
|
|
164
|
+
? this.editSemantic({
|
|
165
|
+
property: `layer[${layer.id}].mark.missing`,
|
|
166
|
+
value: validateItemMissing(args.missing, "Tick missing")
|
|
167
|
+
})
|
|
168
|
+
: this;
|
|
155
169
|
const config = validateTickConfig(args, {
|
|
156
170
|
...DEFAULT_TICK_CONFIG,
|
|
157
|
-
...
|
|
171
|
+
...semantic.markConfigs[layer.id]
|
|
158
172
|
});
|
|
159
|
-
const materialized =
|
|
173
|
+
const materialized = semantic
|
|
160
174
|
._withMarkConfig(layer.id, config)
|
|
161
175
|
.rematerializeTickMark({ id: layer.id });
|
|
162
176
|
return rematerializeExistingLegend(materialized);
|
|
@@ -190,10 +204,11 @@ export const rematerializeTickMark = /* @__PURE__ */ action(
|
|
|
190
204
|
if (graphic?.type !== "line" || !Array.isArray(graphic.items)) {
|
|
191
205
|
throw new Error(`Tick mark "${id}" requires line collection graphics.`);
|
|
192
206
|
}
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
207
|
+
const sourceDataset = findDataset(this, layer.data);
|
|
208
|
+
if (sourceDataset === undefined) {
|
|
195
209
|
throw new Error(`Tick mark "${id}" requires an existing dataset.`);
|
|
196
210
|
}
|
|
211
|
+
const dataset = applyItemMissingPolicy(layer, sourceDataset);
|
|
197
212
|
if (
|
|
198
213
|
layer.encoding?.x?.scale === undefined ||
|
|
199
214
|
layer.encoding?.y?.scale === undefined
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
removeOwnedPath
|
|
8
8
|
} from "../../core/immutable.js";
|
|
9
9
|
import { parseSemanticPath } from "../../grammar/schemas/semanticPath.js";
|
|
10
|
+
import { deriveTransformSchema, inferDatasetSchema, validateRowsAgainstSchema } from "../../grammar/datasetSchema.js";
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
const CONTEXT_KEYS = Object.freeze({
|
|
@@ -44,7 +45,39 @@ function updateEntity(spec, parsed, value) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
const current = index === -1 ? { id: parsed.id } : collection[index];
|
|
47
|
-
|
|
48
|
+
let updated = setNestedProperty(current, parsed.path, value);
|
|
49
|
+
if (
|
|
50
|
+
parsed.kind === "dataset" && parsed.path.length === 1 &&
|
|
51
|
+
parsed.path[0] === "transform" && current.schema === undefined &&
|
|
52
|
+
current.source !== undefined && value.length === 1
|
|
53
|
+
) {
|
|
54
|
+
const source = collection.find(dataset => dataset.id === current.source);
|
|
55
|
+
if (source?.schema !== undefined) {
|
|
56
|
+
updated = setNestedProperty(updated, ["schema"], deriveTransformSchema(
|
|
57
|
+
source.schema,
|
|
58
|
+
value[0],
|
|
59
|
+
undefined,
|
|
60
|
+
{ sourceId: source.id, ownerId: parsed.id }
|
|
61
|
+
));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (
|
|
65
|
+
parsed.kind === "dataset" && parsed.path.length === 1 &&
|
|
66
|
+
parsed.path[0] === "values" &&
|
|
67
|
+
(current.schema === undefined || current.schema.origin === "derived")
|
|
68
|
+
) {
|
|
69
|
+
const source = current.source === undefined
|
|
70
|
+
? undefined
|
|
71
|
+
: collection.find(dataset => dataset.id === current.source);
|
|
72
|
+
const schema = source?.schema !== undefined && current.transform?.length === 1
|
|
73
|
+
? deriveTransformSchema(source.schema, current.transform[0], value, {
|
|
74
|
+
sourceId: source.id,
|
|
75
|
+
ownerId: parsed.id
|
|
76
|
+
})
|
|
77
|
+
: inferDatasetSchema(value);
|
|
78
|
+
validateRowsAgainstSchema(value, schema, `Dataset "${parsed.id}"`);
|
|
79
|
+
updated = setNestedProperty(updated, ["schema"], schema);
|
|
80
|
+
}
|
|
48
81
|
const nextCollection = [...collection];
|
|
49
82
|
|
|
50
83
|
if (index === -1) {
|
|
@@ -155,13 +188,14 @@ export function withRematerializedDerivedDataset(program, {
|
|
|
155
188
|
id,
|
|
156
189
|
source,
|
|
157
190
|
transform,
|
|
158
|
-
values
|
|
191
|
+
values,
|
|
192
|
+
schema
|
|
159
193
|
}) {
|
|
160
194
|
const semanticSpec = {
|
|
161
195
|
...program.semanticSpec,
|
|
162
196
|
datasets: program.semanticSpec.datasets.map(dataset =>
|
|
163
197
|
dataset.id === id
|
|
164
|
-
? { ...dataset, source, transform: [transform], values }
|
|
198
|
+
? { ...dataset, source, transform: [transform], values, ...(schema === undefined ? {} : { schema }) }
|
|
165
199
|
: dataset
|
|
166
200
|
)
|
|
167
201
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
2
|
import { validateDataRows } from "../../../core/validation.js";
|
|
3
|
-
import { hasDataset } from "../../../selectors/datasets.js";
|
|
3
|
+
import { findDataset, hasDataset } from "../../../selectors/datasets.js";
|
|
4
|
+
import {
|
|
5
|
+
validateDatasetSchema,
|
|
6
|
+
validateRowsAgainstSchema
|
|
7
|
+
} from "../../../grammar/datasetSchema.js";
|
|
4
8
|
|
|
5
9
|
export function validateDatasetSemanticValue(
|
|
6
10
|
program,
|
|
@@ -11,6 +15,14 @@ export function validateDatasetSemanticValue(
|
|
|
11
15
|
const property = parsed.path[0];
|
|
12
16
|
if (property === "values") {
|
|
13
17
|
validateDataRows(value, `Dataset "${parsed.id}"`);
|
|
18
|
+
const schema = findDataset(program, parsed.id)?.schema;
|
|
19
|
+
if (schema !== undefined && schema.origin !== "derived") {
|
|
20
|
+
validateRowsAgainstSchema(value, schema, `Dataset "${parsed.id}"`);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (property === "schema") {
|
|
25
|
+
validateDatasetSchema(value);
|
|
14
26
|
return;
|
|
15
27
|
}
|
|
16
28
|
if (property === "source") {
|
|
@@ -19,6 +19,8 @@ import { findLayer } from "../../../selectors/layers.js";
|
|
|
19
19
|
import { validateNonEmptySemanticString } from "./shared.js";
|
|
20
20
|
import { normalizeStatisticalWeight } from
|
|
21
21
|
"../../../grammar/weightedStatistics.js";
|
|
22
|
+
import { ITEM_MISSING_MARK_TYPES, validateItemMissing } from
|
|
23
|
+
"../../../grammar/itemMissing.js";
|
|
22
24
|
|
|
23
25
|
function validateLayerSource(program, parsed, value, sourceMarkTypes) {
|
|
24
26
|
validateUserId(value, "Layer source id");
|
|
@@ -52,8 +54,30 @@ export function validateLayerSemanticValue(
|
|
|
52
54
|
if (property === "mark.type" && !MARK_TYPES.includes(value)) {
|
|
53
55
|
throw new Error(`Unknown mark type "${value}".`);
|
|
54
56
|
}
|
|
55
|
-
if (property === "mark.missing"
|
|
56
|
-
|
|
57
|
+
if (property === "mark.missing") {
|
|
58
|
+
const markType = findLayer(program, parsed.id)?.mark?.type;
|
|
59
|
+
if (markType === "area") {
|
|
60
|
+
if (!["error", "break"].includes(value)) {
|
|
61
|
+
throw new Error(`Unsupported area missing policy "${value}".`);
|
|
62
|
+
}
|
|
63
|
+
} else if (ITEM_MISSING_MARK_TYPES.includes(markType)) {
|
|
64
|
+
validateItemMissing(value, `${markType} missing`);
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error(`Mark type "${markType}" does not support a missing policy.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (property === "mark.orientation" && (findLayer(program, parsed.id)?.mark?.type !== "bar" ||
|
|
70
|
+
!["vertical", "horizontal"].includes(value))) {
|
|
71
|
+
throw new Error("Bar orientation must be vertical or horizontal.");
|
|
72
|
+
}
|
|
73
|
+
if (property === "derivedBindings.regression") {
|
|
74
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
75
|
+
Object.keys(value).length !== 2 || value.mode !== "follow" ||
|
|
76
|
+
!Array.isArray(value.roles) || value.roles.length !== 3 ||
|
|
77
|
+
value.roles.join(",") !== "data,x,y") {
|
|
78
|
+
throw new Error("Regression derived binding must follow data, x, and y roles.");
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
57
81
|
}
|
|
58
82
|
if (property === "layout.mode" && !COLOR_LAYOUTS.includes(value)) {
|
|
59
83
|
throw new Error(`Unsupported series layout "${value}".`);
|
|
@@ -55,6 +55,12 @@ export function validateScaleSemanticValue(program, parsed, value) {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
if (property === "domain") return validateSemanticScaleDomain(value);
|
|
58
|
+
if (property === "emptyDomain") {
|
|
59
|
+
if (!["preserve", "require-explicit"].includes(value)) {
|
|
60
|
+
throw new Error('Scale emptyDomain must be "preserve" or "require-explicit".');
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
58
64
|
if (property === "range") return validateSemanticScaleRange(value);
|
|
59
65
|
if (["nice", "zero", "clamp", "reverse"].includes(property)) {
|
|
60
66
|
if (typeof value !== "boolean") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { closedAction } from "../../core/action.js";
|
|
2
2
|
import { validateKeys } from "../../core/validation.js";
|
|
3
|
-
import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
|
|
3
|
+
import { normalizeRegressionParameters, normalizeRegressionPredict } from "../../grammar/regression/index.js";
|
|
4
4
|
import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
|
|
5
5
|
import {
|
|
6
6
|
findRegressionPoint,
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
} from "./resolve.js";
|
|
11
11
|
|
|
12
12
|
const REGRESSION_OPTIONS = Object.freeze([
|
|
13
|
-
"target", "x", "y", "groupBy", "method", "degree", "span",
|
|
13
|
+
"target", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
|
|
14
14
|
"confidenceMethod", "level", "confidence", "interval", "band", "line"
|
|
15
|
+
, "predict", "sourceBinding", "missing"
|
|
15
16
|
]);
|
|
16
17
|
|
|
17
18
|
export const createRegression = /* @__PURE__ */ closedAction(
|
|
@@ -20,6 +21,10 @@ export const createRegression = /* @__PURE__ */ closedAction(
|
|
|
20
21
|
description: "Fit and layer regression lines with optional interval bands."
|
|
21
22
|
}, REGRESSION_OPTIONS,
|
|
22
23
|
function (args = {}) {
|
|
24
|
+
const sourceBinding = args.sourceBinding ?? "fixed";
|
|
25
|
+
if (!["fixed", "follow"].includes(sourceBinding)) {
|
|
26
|
+
throw new Error('Regression sourceBinding must be "fixed" or "follow".');
|
|
27
|
+
}
|
|
23
28
|
const point = findRegressionPoint(this, args.target);
|
|
24
29
|
const x = args.x === undefined
|
|
25
30
|
? point.encoding.x.field
|
|
@@ -37,11 +42,21 @@ export const createRegression = /* @__PURE__ */ closedAction(
|
|
|
37
42
|
);
|
|
38
43
|
}
|
|
39
44
|
const parameters = normalizeRegressionParameters(args);
|
|
45
|
+
const predict = normalizeRegressionPredict(args.predict);
|
|
46
|
+
const regressionDefinition = {
|
|
47
|
+
...parameters,
|
|
48
|
+
...(predict === undefined ? {} : { predict }),
|
|
49
|
+
...(args.missing === undefined ? {} : { missing: args.missing })
|
|
50
|
+
};
|
|
40
51
|
let band = false;
|
|
41
52
|
if (parameters.method === "loess") {
|
|
42
53
|
if (args.band !== undefined && args.band !== false) {
|
|
43
54
|
throw new Error("LOESS regression does not support a band object.");
|
|
44
55
|
}
|
|
56
|
+
} else if (parameters.interval === false) {
|
|
57
|
+
if (args.band !== undefined && args.band !== false) {
|
|
58
|
+
throw new Error("Regression interval false cannot include a band object.");
|
|
59
|
+
}
|
|
45
60
|
} else if (args.band !== false) {
|
|
46
61
|
band = requireRegressionObject(args.band ?? {}, "Regression band");
|
|
47
62
|
}
|
|
@@ -78,7 +93,7 @@ export const createRegression = /* @__PURE__ */ closedAction(
|
|
|
78
93
|
x,
|
|
79
94
|
y,
|
|
80
95
|
...(groupBy === undefined ? {} : { groupBy }),
|
|
81
|
-
...
|
|
96
|
+
...regressionDefinition
|
|
82
97
|
});
|
|
83
98
|
if (band !== false) {
|
|
84
99
|
next = next.createRegressionBand({
|
|
@@ -105,7 +120,7 @@ export const createRegression = /* @__PURE__ */ closedAction(
|
|
|
105
120
|
yScale,
|
|
106
121
|
...line
|
|
107
122
|
});
|
|
108
|
-
|
|
123
|
+
next = next._withMarkConfig(point.id, {
|
|
109
124
|
...next.markConfigs[point.id],
|
|
110
125
|
regression: {
|
|
111
126
|
source: point.data,
|
|
@@ -119,8 +134,14 @@ export const createRegression = /* @__PURE__ */ closedAction(
|
|
|
119
134
|
dataId,
|
|
120
135
|
bandId: band === false ? undefined : bandId,
|
|
121
136
|
lineId,
|
|
122
|
-
parameters
|
|
137
|
+
parameters: regressionDefinition
|
|
123
138
|
}
|
|
124
139
|
});
|
|
140
|
+
return sourceBinding === "follow"
|
|
141
|
+
? next.editSemantic({
|
|
142
|
+
property: `layer[${point.id}].derivedBindings.regression`,
|
|
143
|
+
value: { mode: "follow", roles: ["data", "x", "y"] }
|
|
144
|
+
})
|
|
145
|
+
: next;
|
|
125
146
|
}
|
|
126
147
|
);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
validateNonNegativeFinite,
|
|
8
8
|
validateUnitInterval
|
|
9
9
|
} from "../../core/validation.js";
|
|
10
|
-
import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
|
|
10
|
+
import { normalizeRegressionParameters, normalizeRegressionPredict } from "../../grammar/regression/index.js";
|
|
11
11
|
import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
|
|
12
12
|
import {
|
|
13
13
|
requestedStrokeDetails,
|
|
@@ -21,8 +21,9 @@ import { removeOwnedColorLegends } from "../guides/legends/remove.js";
|
|
|
21
21
|
import { requireRegressionField } from "./resolve.js";
|
|
22
22
|
|
|
23
23
|
const OPTIONS = Object.freeze([
|
|
24
|
-
"target", "data", "x", "y", "groupBy", "method", "degree", "span",
|
|
24
|
+
"target", "data", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
|
|
25
25
|
"confidenceMethod", "level", "confidence", "interval", "band", "line"
|
|
26
|
+
, "predict", "sourceBinding", "missing"
|
|
26
27
|
]);
|
|
27
28
|
const BAND_OPTIONS = Object.freeze([
|
|
28
29
|
"color", "opacity", "stroke", "strokeWidth", "curve",
|
|
@@ -110,14 +111,14 @@ function resolveParameters(previous, args) {
|
|
|
110
111
|
const method = args.method ?? previous.method;
|
|
111
112
|
const raw = { method };
|
|
112
113
|
for (const key of [
|
|
113
|
-
"degree", "span", "confidenceMethod", "level", "confidence", "interval"
|
|
114
|
+
"degree", "span", "robustIterations", "confidenceMethod", "level", "confidence", "interval"
|
|
114
115
|
]) {
|
|
115
116
|
if (Object.hasOwn(args, key)) raw[key] = args[key];
|
|
116
117
|
}
|
|
117
118
|
const inheritsLevel = !Object.hasOwn(raw, "confidence");
|
|
118
|
-
if (method === previous.method) {
|
|
119
|
+
if (method === previous.method && args.interval !== false) {
|
|
119
120
|
for (const key of [
|
|
120
|
-
"degree", "span", "confidenceMethod", "level", "interval"
|
|
121
|
+
"degree", "span", "robustIterations", "confidenceMethod", "level", "interval"
|
|
121
122
|
]) {
|
|
122
123
|
if (
|
|
123
124
|
!Object.hasOwn(raw, key) &&
|
|
@@ -127,7 +128,7 @@ function resolveParameters(previous, args) {
|
|
|
127
128
|
raw[key] = previous[key];
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
|
-
} else if (method !== "loess" && previous.method !== "loess") {
|
|
131
|
+
} else if (method !== "loess" && previous.method !== "loess" && args.interval !== false) {
|
|
131
132
|
for (const key of ["confidenceMethod", "level", "interval"]) {
|
|
132
133
|
if (
|
|
133
134
|
!Object.hasOwn(raw, key) &&
|
|
@@ -138,7 +139,19 @@ function resolveParameters(previous, args) {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
const parameters = normalizeRegressionParameters(raw);
|
|
143
|
+
const predict = Object.hasOwn(args, "predict")
|
|
144
|
+
? args.predict === false ? undefined : normalizeRegressionPredict(args.predict)
|
|
145
|
+
: previous.predict;
|
|
146
|
+
const missing = Object.hasOwn(args, "missing") ? args.missing : previous.missing;
|
|
147
|
+
if (missing !== undefined && !["error", "drop"].includes(missing)) {
|
|
148
|
+
throw new Error('Regression missing must be "error" or "drop".');
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...parameters,
|
|
152
|
+
...(predict === undefined ? {} : { predict }),
|
|
153
|
+
...(missing === undefined ? {} : { missing })
|
|
154
|
+
};
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
function regressionColorScale(owner, current, groupBy) {
|
|
@@ -245,15 +258,31 @@ export const editRegression = /* @__PURE__ */ closedAction(
|
|
|
245
258
|
}
|
|
246
259
|
const owner = resolveRegressionOwner(this, args.target);
|
|
247
260
|
const current = this.markConfigs[owner.id].regression;
|
|
248
|
-
const
|
|
261
|
+
const currentBinding = owner.derivedBindings?.regression?.mode === "follow" ? "follow" : "fixed";
|
|
262
|
+
const sourceBinding = args.sourceBinding ?? currentBinding;
|
|
263
|
+
if (!["fixed", "follow"].includes(sourceBinding)) throw new Error('Regression sourceBinding must be "fixed" or "follow".');
|
|
264
|
+
const followed = sourceBinding === "follow" ? {
|
|
265
|
+
data: owner.data,
|
|
266
|
+
x: owner.encoding?.x?.fieldType === "quantitative" ? owner.encoding.x.field : undefined,
|
|
267
|
+
y: owner.encoding?.y?.fieldType === "quantitative" ? owner.encoding.y.field : undefined
|
|
268
|
+
} : undefined;
|
|
269
|
+
if (sourceBinding === "follow" && (!followed.data || !followed.x || !followed.y)) {
|
|
270
|
+
throw new Error(`Following regression source "${owner.id}" requires quantitative x/y fields and data.`);
|
|
271
|
+
}
|
|
272
|
+
for (const key of ["data", "x", "y"]) {
|
|
273
|
+
if (followed !== undefined && Object.hasOwn(args, key) && args[key] !== followed[key]) {
|
|
274
|
+
throw new Error(`Following regression ${key} must match source mark "${owner.id}".`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const source = followed?.data ?? (Object.hasOwn(args, "data")
|
|
249
278
|
? validateUserId(args.data, "Regression data id")
|
|
250
|
-
: current.source;
|
|
251
|
-
const x = Object.hasOwn(args, "x")
|
|
279
|
+
: current.source);
|
|
280
|
+
const x = followed?.x ?? (Object.hasOwn(args, "x")
|
|
252
281
|
? requireRegressionField(args.x, "Regression x")
|
|
253
|
-
: current.x;
|
|
254
|
-
const y = Object.hasOwn(args, "y")
|
|
282
|
+
: current.x);
|
|
283
|
+
const y = followed?.y ?? (Object.hasOwn(args, "y")
|
|
255
284
|
? requireRegressionField(args.y, "Regression y")
|
|
256
|
-
: current.y;
|
|
285
|
+
: current.y);
|
|
257
286
|
const groupBy = Object.hasOwn(args, "groupBy")
|
|
258
287
|
? args.groupBy === false
|
|
259
288
|
? undefined
|
|
@@ -272,12 +301,12 @@ export const editRegression = /* @__PURE__ */ closedAction(
|
|
|
272
301
|
const linePatch = Object.hasOwn(args, "line")
|
|
273
302
|
? validateLinePatch(args.line)
|
|
274
303
|
: undefined;
|
|
275
|
-
if (parameters.method === "loess" && bandPatch !== undefined && bandPatch !== false) {
|
|
304
|
+
if ((parameters.method === "loess" || parameters.interval === false) && bandPatch !== undefined && bandPatch !== false) {
|
|
276
305
|
throw new Error("LOESS regression does not support a band object.");
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
const hadBand = current.bandId !== undefined;
|
|
280
|
-
const wantsBand = parameters.method !== "loess" &&
|
|
309
|
+
const wantsBand = parameters.method !== "loess" && parameters.interval !== false &&
|
|
281
310
|
(bandPatch === false
|
|
282
311
|
? false
|
|
283
312
|
: hadBand || bandPatch !== undefined || current.parameters.method === "loess");
|
|
@@ -385,6 +414,12 @@ export const editRegression = /* @__PURE__ */ closedAction(
|
|
|
385
414
|
if (changesStatistics) {
|
|
386
415
|
next = next.releaseDerivedData(revision.release);
|
|
387
416
|
}
|
|
417
|
+
const binding = findLayer(next, owner.id)?.derivedBindings?.regression;
|
|
418
|
+
if (sourceBinding === "follow" && binding?.mode !== "follow") {
|
|
419
|
+
next = next.editSemantic({ property: `layer[${owner.id}].derivedBindings.regression`, value: { mode: "follow", roles: ["data", "x", "y"] } });
|
|
420
|
+
} else if (sourceBinding === "fixed" && binding !== undefined) {
|
|
421
|
+
next = next.editSemantic({ property: `layer[${owner.id}].derivedBindings.regression`, remove: true });
|
|
422
|
+
}
|
|
388
423
|
return next;
|
|
389
424
|
};
|
|
390
425
|
return applyEdit(this);
|