ggaction 0.0.13 → 0.0.15
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 +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateOptionObject } from "../../../core/validation.js";
|
|
4
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
5
|
+
import { isTextSource } from "../../../materialization/marks/index.js";
|
|
6
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
7
|
+
import { markLabelPlacementLeaderId } from "../../../layout/labels.js";
|
|
8
|
+
|
|
9
|
+
const REMOVE_MARK_LABELS_OPTIONS = Object.freeze(["target", "source"]);
|
|
10
|
+
|
|
11
|
+
function compareIds(left, right) {
|
|
12
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function attachedLabels(program, source) {
|
|
16
|
+
return program.semanticSpec.layers
|
|
17
|
+
.filter(layer => isSourceOwnedText(layer) && layer.source === source)
|
|
18
|
+
.sort((left, right) => compareIds(left.id, right.id));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveAttachedLabelTargets(program, args) {
|
|
22
|
+
const hasTarget = Object.hasOwn(args, "target");
|
|
23
|
+
const hasSource = Object.hasOwn(args, "source");
|
|
24
|
+
if (hasTarget === hasSource) {
|
|
25
|
+
throw new Error("removeMarkLabels requires exactly one of target or source.");
|
|
26
|
+
}
|
|
27
|
+
if (hasTarget) {
|
|
28
|
+
const target = validateUserId(args.target, "Attached label id");
|
|
29
|
+
const layer = findLayer(program, target);
|
|
30
|
+
if (!isSourceOwnedText(layer) || !isTextSource(findLayer(program, layer.source))) {
|
|
31
|
+
throw new Error(`Unknown attached label target "${target}".`);
|
|
32
|
+
}
|
|
33
|
+
return [layer];
|
|
34
|
+
}
|
|
35
|
+
const source = validateUserId(args.source, "Label source id");
|
|
36
|
+
const layer = findLayer(program, source);
|
|
37
|
+
if (!isTextSource(layer)) {
|
|
38
|
+
throw new Error(`Unknown label source "${source}".`);
|
|
39
|
+
}
|
|
40
|
+
return attachedLabels(program, source);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectAttachedLabelRemoval(program, labels) {
|
|
44
|
+
const labelIds = labels.map(layer => layer.id);
|
|
45
|
+
const targets = new Set(labelIds);
|
|
46
|
+
const sourceByLabel = new Map(labels.map(layer => [layer.id, layer.source]));
|
|
47
|
+
const leaderIds = labelIds.flatMap(id => {
|
|
48
|
+
const ids = [];
|
|
49
|
+
const layoutLeader = program.materializationConfigs.labelLayouts?.[id]?.leaderId;
|
|
50
|
+
if (layoutLeader !== undefined) ids.push(layoutLeader);
|
|
51
|
+
const placement = program.markConfigs[id]?.labelAuthoring?.placement;
|
|
52
|
+
if (placement?.leader !== undefined && placement.leader !== false) {
|
|
53
|
+
ids.push(markLabelPlacementLeaderId(id));
|
|
54
|
+
}
|
|
55
|
+
return ids;
|
|
56
|
+
});
|
|
57
|
+
const selectionIds = Object.entries(
|
|
58
|
+
program.materializationConfigs.selections ?? {}
|
|
59
|
+
).filter(([, config]) => targets.has(config.target)).map(([id]) => id).sort();
|
|
60
|
+
const selected = new Set(selectionIds);
|
|
61
|
+
const highlightIds = Object.entries(
|
|
62
|
+
program.materializationConfigs.highlights ?? {}
|
|
63
|
+
).filter(([, config]) =>
|
|
64
|
+
targets.has(config.target) || selected.has(config.selection)
|
|
65
|
+
).map(([id]) => id).sort();
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
sourceByLabel,
|
|
68
|
+
labelIds: Object.freeze(labelIds),
|
|
69
|
+
leaderIds: Object.freeze([...new Set(leaderIds)].sort()),
|
|
70
|
+
selectionIds: Object.freeze(selectionIds),
|
|
71
|
+
highlightIds: Object.freeze(highlightIds)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function externalAttachedLabelReferences(program, plan) {
|
|
76
|
+
const labels = new Set(plan.labelIds);
|
|
77
|
+
const leaders = new Set(plan.leaderIds);
|
|
78
|
+
const selections = new Set(plan.selectionIds);
|
|
79
|
+
const references = [];
|
|
80
|
+
for (const layer of program.semanticSpec.layers) {
|
|
81
|
+
if (!labels.has(layer.id) && labels.has(layer.source)) {
|
|
82
|
+
references.push(`layer[${layer.id}].source`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
|
|
86
|
+
if (labels.has(config?.target)) references.push(`guide.legend.${kind}.target`);
|
|
87
|
+
}
|
|
88
|
+
for (const [id, config] of Object.entries(
|
|
89
|
+
program.materializationConfigs.labelLayouts ?? {}
|
|
90
|
+
)) {
|
|
91
|
+
if (!labels.has(id) && leaders.has(config?.leaderId)) {
|
|
92
|
+
references.push(`materialization.labelLayouts[${id}].leaderId`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const [id, config] of Object.entries(program.markConfigs)) {
|
|
96
|
+
if (labels.has(id)) continue;
|
|
97
|
+
const selection = config?.labelAuthoring?.selection;
|
|
98
|
+
if (selection?.kind === "named" && selections.has(selection.id)) {
|
|
99
|
+
references.push(`markConfig[${id}].labelAuthoring.selection`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return references.sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function removeLabelState(program, plan) {
|
|
106
|
+
let next = program;
|
|
107
|
+
for (const id of plan.highlightIds) {
|
|
108
|
+
next = next._withoutMaterializationConfig(["highlights", id]);
|
|
109
|
+
}
|
|
110
|
+
for (const id of plan.selectionIds) {
|
|
111
|
+
next = next._withoutMaterializationConfig(["selections", id]);
|
|
112
|
+
}
|
|
113
|
+
for (const id of plan.labelIds) {
|
|
114
|
+
next = next
|
|
115
|
+
._withoutMaterializationConfig(["labelLayouts", id])
|
|
116
|
+
._withoutMaterializationConfig(["marks", id])
|
|
117
|
+
._withoutMaterializationConfig(["jitters", id])
|
|
118
|
+
._withoutMaterializationConfig(["pointPacking", id]);
|
|
119
|
+
}
|
|
120
|
+
for (const id of plan.leaderIds) {
|
|
121
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
122
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const id of plan.labelIds) {
|
|
126
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
127
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
128
|
+
}
|
|
129
|
+
if (findLayer(next, id) !== undefined) {
|
|
130
|
+
next = next.editSemantic({ property: `layer[${id}]`, remove: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const currentLabel = plan.sourceByLabel.get(program.context.currentMark);
|
|
134
|
+
const context = {
|
|
135
|
+
...(currentLabel === undefined ? {} : { currentMark: currentLabel }),
|
|
136
|
+
...(plan.selectionIds.includes(program.context.currentSelection)
|
|
137
|
+
? { currentSelection: undefined }
|
|
138
|
+
: {})
|
|
139
|
+
};
|
|
140
|
+
return Object.keys(context).length === 0 ? next : next._withContext(context);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const removeMarkLabels = action(
|
|
144
|
+
{
|
|
145
|
+
op: "removeMarkLabels",
|
|
146
|
+
description: "Remove attached label layers while preserving their source mark."
|
|
147
|
+
},
|
|
148
|
+
function (args = {}) {
|
|
149
|
+
validateOptionObject(
|
|
150
|
+
args,
|
|
151
|
+
REMOVE_MARK_LABELS_OPTIONS,
|
|
152
|
+
"removeMarkLabels",
|
|
153
|
+
{
|
|
154
|
+
allowEmpty: false,
|
|
155
|
+
emptyError: Error,
|
|
156
|
+
emptyMessage: "removeMarkLabels requires exactly one of target or source."
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
const labels = resolveAttachedLabelTargets(this, args);
|
|
160
|
+
const plan = collectAttachedLabelRemoval(this, labels);
|
|
161
|
+
const external = externalAttachedLabelReferences(this, plan);
|
|
162
|
+
if (external.length > 0) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`removeMarkLabels cannot remove externally referenced labels: ${external.join(", ")}.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return removeLabelState(this, plan);
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
export function registerTextLabelRemovalActions(ProgramClass) {
|
|
172
|
+
ProgramClass.prototype.removeMarkLabels = removeMarkLabels;
|
|
173
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateOptionObject } from "../../../core/validation.js";
|
|
4
|
+
import { normalizeLabelSelectionEdit } from
|
|
5
|
+
"../../../grammar/markLabelSelection.js";
|
|
6
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
7
|
+
import { isTextSource } from "../../../materialization/marks/index.js";
|
|
8
|
+
import { resolveMarkSelection } from
|
|
9
|
+
"../../../materialization/selection/state.js";
|
|
10
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
11
|
+
import { rematerializeLabelsBeforeHighlights } from
|
|
12
|
+
"../../selection/actions.js";
|
|
13
|
+
|
|
14
|
+
const OPTIONS = Object.freeze(["target", "select", "selection", "all"]);
|
|
15
|
+
|
|
16
|
+
function requireAttachedLabel(program, target) {
|
|
17
|
+
const id = validateUserId(target, "Attached label id");
|
|
18
|
+
const layer = findLayer(program, id);
|
|
19
|
+
if (!isSourceOwnedText(layer) || !isTextSource(findLayer(program, layer.source))) {
|
|
20
|
+
throw new Error(`Unknown attached label target "${id}".`);
|
|
21
|
+
}
|
|
22
|
+
return layer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function validateSelectionSource(program, source, requested) {
|
|
26
|
+
if (requested.kind === "all") return;
|
|
27
|
+
if (requested.kind === "inline") {
|
|
28
|
+
resolveMarkSelection(program, source, requested.selector);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const definition = program.materializationConfigs.selections?.[requested.id];
|
|
32
|
+
if (definition === undefined) {
|
|
33
|
+
throw new Error(`Unknown label selection "${requested.id}".`);
|
|
34
|
+
}
|
|
35
|
+
if (definition.target !== source) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Label selection "${requested.id}" must target source mark "${source}".`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
resolveMarkSelection(program, source, definition.selector);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const editMarkLabelSelection = action(
|
|
44
|
+
{
|
|
45
|
+
op: "editMarkLabelSelection",
|
|
46
|
+
description: "Replace the final-item membership requested by an attached label."
|
|
47
|
+
},
|
|
48
|
+
function (args = {}) {
|
|
49
|
+
validateOptionObject(args, OPTIONS, "editMarkLabelSelection");
|
|
50
|
+
if (!Object.hasOwn(args, "target")) {
|
|
51
|
+
throw new Error("editMarkLabelSelection requires target.");
|
|
52
|
+
}
|
|
53
|
+
const layer = requireAttachedLabel(this, args.target);
|
|
54
|
+
const selection = normalizeLabelSelectionEdit(args);
|
|
55
|
+
validateSelectionSource(this, layer.source, selection);
|
|
56
|
+
const next = this._withMarkConfig(layer.id, {
|
|
57
|
+
...this.markConfigs[layer.id],
|
|
58
|
+
labelAuthoring: {
|
|
59
|
+
...this.markConfigs[layer.id]?.labelAuthoring,
|
|
60
|
+
selection
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return rematerializeLabelsBeforeHighlights(
|
|
64
|
+
next,
|
|
65
|
+
layer.source,
|
|
66
|
+
[layer.id]
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export function registerTextLabelSelectionActions(ProgramClass) {
|
|
72
|
+
ProgramClass.prototype.editMarkLabelSelection = editMarkLabelSelection;
|
|
73
|
+
}
|
|
@@ -17,7 +17,12 @@ import { resolveRowEncodingValues } from
|
|
|
17
17
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
18
18
|
import { findLayer } from "../../../selectors/layers.js";
|
|
19
19
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
20
|
+
import {
|
|
21
|
+
requestedStrokeDetails,
|
|
22
|
+
STROKE_STYLE_PROPERTIES
|
|
23
|
+
} from "../../../grammar/strokeStyle.js";
|
|
20
24
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
25
|
+
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
21
26
|
import {
|
|
22
27
|
applyLayeredMarkInheritance,
|
|
23
28
|
assertMarkAvailable,
|
|
@@ -30,7 +35,7 @@ import {
|
|
|
30
35
|
} from "../shared.js";
|
|
31
36
|
|
|
32
37
|
const APPEARANCE_OPTIONS = Object.freeze([
|
|
33
|
-
"length", "stroke", "strokeWidth", "opacity"
|
|
38
|
+
"length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
|
|
34
39
|
]);
|
|
35
40
|
const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
|
|
36
41
|
const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
|
|
@@ -66,6 +71,7 @@ function resolveTick(program, requested, operation) {
|
|
|
66
71
|
|
|
67
72
|
function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
|
|
68
73
|
return {
|
|
74
|
+
...current,
|
|
69
75
|
length: Object.hasOwn(args, "length")
|
|
70
76
|
? validatePositiveFinite(args.length, "Tick length")
|
|
71
77
|
: current.length,
|
|
@@ -77,7 +83,8 @@ function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
|
|
|
77
83
|
: current.strokeWidth,
|
|
78
84
|
opacity: Object.hasOwn(args, "opacity")
|
|
79
85
|
? validateUnitInterval(args.opacity, "Tick opacity")
|
|
80
|
-
: current.opacity
|
|
86
|
+
: current.opacity,
|
|
87
|
+
...requestedStrokeDetails(args, "Tick")
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -88,6 +95,7 @@ export const createTickMark = action(
|
|
|
88
95
|
},
|
|
89
96
|
function (args = {}) {
|
|
90
97
|
validateMarkOptions(args, CREATE_OPTIONS, "createTickMark");
|
|
98
|
+
requestedStrokeDetails(args, "createTickMark");
|
|
91
99
|
const id = resolveMarkId(this, args.id, {
|
|
92
100
|
defaultId: "tick",
|
|
93
101
|
label: "Tick mark id",
|
|
@@ -132,19 +140,26 @@ export const editTickMark = action(
|
|
|
132
140
|
},
|
|
133
141
|
function (args = {}) {
|
|
134
142
|
validateMarkOptions(args, EDIT_OPTIONS, "editTickMark");
|
|
143
|
+
requestedStrokeDetails(args, "editTickMark");
|
|
135
144
|
if (!APPEARANCE_OPTIONS.some(property => Object.hasOwn(args, property))) {
|
|
136
145
|
throw new Error(
|
|
137
146
|
"editTickMark requires length, stroke, strokeWidth, or opacity."
|
|
138
147
|
);
|
|
139
148
|
}
|
|
140
149
|
const layer = resolveTick(this, args.target, "editTickMark");
|
|
150
|
+
if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"editTickMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
|
|
153
|
+
);
|
|
154
|
+
}
|
|
141
155
|
const config = validateTickConfig(args, {
|
|
142
156
|
...DEFAULT_TICK_CONFIG,
|
|
143
157
|
...this.markConfigs[layer.id]
|
|
144
158
|
});
|
|
145
|
-
|
|
159
|
+
const materialized = this
|
|
146
160
|
._withMarkConfig(layer.id, config)
|
|
147
161
|
.rematerializeTickMark({ id: layer.id });
|
|
162
|
+
return rematerializeExistingLegend(materialized);
|
|
148
163
|
}
|
|
149
164
|
);
|
|
150
165
|
|
|
@@ -190,6 +205,7 @@ export const rematerializeTickMark = action(
|
|
|
190
205
|
|
|
191
206
|
const x = resolveRowEncodingValues(this, layer, dataset, "x");
|
|
192
207
|
const y = resolveRowEncodingValues(this, layer, dataset, "y");
|
|
208
|
+
const stroke = resolveRowEncodingValues(this, layer, dataset, "stroke");
|
|
193
209
|
const angles = resolveDirectionValues(dataset.values, layer.encoding?.angle);
|
|
194
210
|
const config = validateTickConfig({}, {
|
|
195
211
|
...DEFAULT_TICK_CONFIG,
|
|
@@ -210,9 +226,10 @@ export const rematerializeTickMark = action(
|
|
|
210
226
|
y1: segments.map(item => item.y1),
|
|
211
227
|
x2: segments.map(item => item.x2),
|
|
212
228
|
y2: segments.map(item => item.y2),
|
|
213
|
-
stroke: config.stroke,
|
|
229
|
+
stroke: stroke ?? config.stroke,
|
|
214
230
|
strokeWidth: config.strokeWidth,
|
|
215
|
-
opacity: config.opacity
|
|
231
|
+
opacity: config.opacity,
|
|
232
|
+
...requestedStrokeDetails(config, "Tick mark")
|
|
216
233
|
});
|
|
217
234
|
}
|
|
218
235
|
);
|
|
@@ -63,6 +63,32 @@ function replaceDrawableItems(value, id, type) {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export function replaceGraphicItems(program, target, baseType, items) {
|
|
67
|
+
let next = program.editGraphics({ target, property: "items", value: items });
|
|
68
|
+
const graphic = next.graphicSpec.objects[target];
|
|
69
|
+
if (
|
|
70
|
+
graphic.type !== "collection" ||
|
|
71
|
+
!graphic.items.every(item => item.type === baseType)
|
|
72
|
+
) return next;
|
|
73
|
+
const normalized = freezeOwned({
|
|
74
|
+
...graphic,
|
|
75
|
+
type: baseType,
|
|
76
|
+
items: freezeOwned(graphic.items.map(item => freezeOwned({
|
|
77
|
+
id: item.id,
|
|
78
|
+
properties: item.properties
|
|
79
|
+
})))
|
|
80
|
+
});
|
|
81
|
+
return next._clone({
|
|
82
|
+
graphicSpec: freezeOwned({
|
|
83
|
+
...next.graphicSpec,
|
|
84
|
+
objects: freezeOwned({
|
|
85
|
+
...next.graphicSpec.objects,
|
|
86
|
+
[target]: normalized
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
66
92
|
function editGeneratedItem(owner, itemIndex, property, value) {
|
|
67
93
|
const items = [...owner.items];
|
|
68
94
|
const item = items[itemIndex];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSemanticPrimitiveAction } from "./semanticAction.js";
|
|
2
|
+
import { planResourceRemoval } from "../resources/remove.js";
|
|
2
3
|
import { validateDatasetTransforms } from "../../grammar/transforms.js";
|
|
3
4
|
import {
|
|
4
5
|
validateParallelDimensions,
|
|
@@ -24,5 +25,5 @@ export function registerSemanticPrimitiveAction(ProgramClass) {
|
|
|
24
25
|
sourceMarkTypes: ["point", "bar", "line", "rule", "rect", "arc"]
|
|
25
26
|
});
|
|
26
27
|
ProgramClass.prototype.editSemantic =
|
|
27
|
-
createSemanticPrimitiveAction(validateSemanticValue);
|
|
28
|
+
createSemanticPrimitiveAction(validateSemanticValue, planResourceRemoval);
|
|
28
29
|
}
|
|
@@ -79,7 +79,7 @@ function removeEntity(spec, parsed) {
|
|
|
79
79
|
if (parsed.kind === "scale" && parsed.path.length === 0) {
|
|
80
80
|
if (hasSemanticScaleReferences(spec, parsed.id)) throw new Error(`Scale "${parsed.id}" is still referenced.`);
|
|
81
81
|
}
|
|
82
|
-
if (["layer", "scale"].includes(parsed.kind) && parsed.path.length === 0) {
|
|
82
|
+
if (["layer", "scale", "coordinate"].includes(parsed.kind) && parsed.path.length === 0) {
|
|
83
83
|
const nextCollection = collection.filter((_, itemIndex) => itemIndex !== index);
|
|
84
84
|
return freezeOwned({
|
|
85
85
|
...spec,
|
|
@@ -87,10 +87,6 @@ function removeEntity(spec, parsed) {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
if (parsed.kind === "dataset" && parsed.path.length === 0) {
|
|
90
|
-
const dataset = collection[index];
|
|
91
|
-
if (dataset.source === undefined) {
|
|
92
|
-
throw new Error(`Source dataset "${parsed.id}" is immutable after creation.`);
|
|
93
|
-
}
|
|
94
90
|
const referenced = spec.layers.some(layer => layer.data === parsed.id) ||
|
|
95
91
|
spec.datasets.some(candidate => candidate.source === parsed.id);
|
|
96
92
|
if (referenced) {
|
|
@@ -151,7 +147,50 @@ export function withPreviewDatasetValues(program, {
|
|
|
151
147
|
return program._clone({ semanticSpec });
|
|
152
148
|
}
|
|
153
149
|
|
|
154
|
-
|
|
150
|
+
// Owned materializers may refresh a generated derived dataset at a stable ID.
|
|
151
|
+
// Keep that privileged immutable transition inside the semantic primitive
|
|
152
|
+
// boundary; public editSemantic still rejects every post-creation dataset edit.
|
|
153
|
+
export function withRematerializedDerivedDataset(program, {
|
|
154
|
+
id,
|
|
155
|
+
source,
|
|
156
|
+
transform,
|
|
157
|
+
values
|
|
158
|
+
}) {
|
|
159
|
+
const semanticSpec = {
|
|
160
|
+
...program.semanticSpec,
|
|
161
|
+
datasets: program.semanticSpec.datasets.map(dataset =>
|
|
162
|
+
dataset.id === id
|
|
163
|
+
? { ...dataset, source, transform: [transform], values }
|
|
164
|
+
: dataset
|
|
165
|
+
)
|
|
166
|
+
};
|
|
167
|
+
return program._clone({ semanticSpec });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Final-state planners may need to remove mutually constrained roles before
|
|
171
|
+
// replaying their canonical primitive owners. Keep that speculative clone in
|
|
172
|
+
// the primitive boundary and preserve the caller's layer for later dependency
|
|
173
|
+
// and guide reconciliation.
|
|
174
|
+
export function withoutPreviewLayerEncodings(program, { id, channels }) {
|
|
175
|
+
const removed = new Set(channels);
|
|
176
|
+
const semanticSpec = {
|
|
177
|
+
...program.semanticSpec,
|
|
178
|
+
layers: program.semanticSpec.layers.map(layer => {
|
|
179
|
+
if (layer.id !== id) return layer;
|
|
180
|
+
return {
|
|
181
|
+
...layer,
|
|
182
|
+
encoding: Object.fromEntries(
|
|
183
|
+
Object.entries(layer.encoding ?? {}).filter(
|
|
184
|
+
([channel]) => !removed.has(channel)
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
};
|
|
188
|
+
})
|
|
189
|
+
};
|
|
190
|
+
return program._clone({ semanticSpec });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createSemanticPrimitiveAction(validateSemanticValue, planNamedRemoval) {
|
|
155
194
|
return action(
|
|
156
195
|
{
|
|
157
196
|
op: "editSemantic",
|
|
@@ -170,14 +209,37 @@ export function createSemanticPrimitiveAction(validateSemanticValue) {
|
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
const parsed = parseSemanticPath(property, { allowContainer: remove });
|
|
212
|
+
const removesNamedResource = remove && parsed.path.length === 0 &&
|
|
213
|
+
["dataset", "scale", "coordinate"].includes(parsed.kind);
|
|
173
214
|
if (this.compositionSpec !== undefined && !(
|
|
174
|
-
this.compositionSpec.type === "facet" &&
|
|
215
|
+
this.compositionSpec.type === "facet" &&
|
|
216
|
+
(parsed.kind === "title" || removesNamedResource)
|
|
175
217
|
)) {
|
|
176
218
|
throw new Error(
|
|
177
|
-
"editSemantic on a composition parent
|
|
219
|
+
"editSemantic on a composition parent supports only facet title state and unreferenced named resource removal."
|
|
178
220
|
);
|
|
179
221
|
}
|
|
180
222
|
if (remove) {
|
|
223
|
+
// Source/coordinate removal and facet-parent removal use the same full
|
|
224
|
+
// dependency policy as the domain lifecycle. Existing unit derived-data
|
|
225
|
+
// teardown retains its semantic-only primitive contract; its owning
|
|
226
|
+
// action coordinates configuration and graphics cleanup separately.
|
|
227
|
+
const resource = removesNamedResource
|
|
228
|
+
? this.semanticSpec[parsed.collection].find(item => item.id === parsed.id)
|
|
229
|
+
: undefined;
|
|
230
|
+
if (resource !== undefined && (
|
|
231
|
+
parsed.kind === "coordinate" || this.compositionSpec?.type === "facet" ||
|
|
232
|
+
(parsed.kind === "dataset" && resource.source === undefined)
|
|
233
|
+
)) {
|
|
234
|
+
if (planNamedRemoval === undefined) {
|
|
235
|
+
throw new Error("Named resource removal requires the full ChartProgram.");
|
|
236
|
+
}
|
|
237
|
+
planNamedRemoval(this, {
|
|
238
|
+
kind: parsed.kind === "dataset" ? "data" : parsed.kind,
|
|
239
|
+
id: parsed.id,
|
|
240
|
+
operation: "editSemantic"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
181
243
|
const semanticSpec = parsed.kind === "guide"
|
|
182
244
|
? removeRootProperty(this.semanticSpec, "guides", parsed.path)
|
|
183
245
|
: parsed.kind === "title"
|
|
@@ -192,17 +254,28 @@ export function createSemanticPrimitiveAction(validateSemanticValue) {
|
|
|
192
254
|
parsed.kind === "layer" &&
|
|
193
255
|
parsed.path.length === 0 &&
|
|
194
256
|
this.context.currentMark === parsed.id;
|
|
257
|
+
const clearsCurrentCoordinate =
|
|
258
|
+
parsed.kind === "coordinate" &&
|
|
259
|
+
parsed.path.length === 0 &&
|
|
260
|
+
this.context.currentCoordinate === parsed.id;
|
|
261
|
+
const clearsCurrentGuide =
|
|
262
|
+
parsed.kind === "guide" &&
|
|
263
|
+
parsed.path.length === 2 &&
|
|
264
|
+
this.context.currentGuide === parsed.id;
|
|
195
265
|
const removesScale = parsed.kind === "scale" && parsed.path.length === 0;
|
|
196
266
|
const resolvedScales = removesScale ? Object.fromEntries(Object.entries(this.resolvedScales).filter(([id]) => id !== parsed.id)) : this.resolvedScales;
|
|
197
267
|
return this._clone({
|
|
198
268
|
semanticSpec,
|
|
199
269
|
...(removesScale ? { resolvedScales: freezeOwned(resolvedScales) } : {}),
|
|
200
|
-
...(clearsCurrentData || clearsCurrentMark ||
|
|
270
|
+
...(clearsCurrentData || clearsCurrentMark || clearsCurrentGuide || clearsCurrentCoordinate ||
|
|
271
|
+
(removesScale && this.context.currentScale === parsed.id)
|
|
201
272
|
? { context: freezeOwned({
|
|
202
273
|
...this.context,
|
|
203
274
|
...(removesScale && this.context.currentScale === parsed.id ? { currentScale: undefined } : {}),
|
|
204
275
|
...(clearsCurrentData ? { currentData: undefined } : {}),
|
|
205
|
-
...(clearsCurrentMark ? { currentMark: undefined } : {})
|
|
276
|
+
...(clearsCurrentMark ? { currentMark: undefined } : {}),
|
|
277
|
+
...(clearsCurrentCoordinate ? { currentCoordinate: undefined } : {}),
|
|
278
|
+
...(clearsCurrentGuide ? { currentGuide: undefined } : {})
|
|
206
279
|
}) }
|
|
207
280
|
: {})
|
|
208
281
|
});
|
|
@@ -30,7 +30,7 @@ export function validateGuideSemanticValue(_program, parsed, value) {
|
|
|
30
30
|
validateParallelAxisTitles(value);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
if (["legend.series", "legend.color"].includes(parsed.id) && property === "order") {
|
|
33
|
+
if (["legend.series", "legend.color", "legend.stroke"].includes(parsed.id) && property === "order") {
|
|
34
34
|
normalizeLegendOrder(value);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
normalizeCoordinateAspect,
|
|
3
|
+
validateCoordinateType
|
|
4
|
+
} from "../../../grammar/coordinates.js";
|
|
5
|
+
import { normalizePolarFrameOptions } from "../../../grammar/polar.js";
|
|
2
6
|
import { validateScaleSemanticValue } from "./scale.js";
|
|
3
7
|
import { validateDatasetSemanticValue } from "./dataset.js";
|
|
4
8
|
import { validateLayerSemanticValue } from "./layer.js";
|
|
@@ -27,6 +31,10 @@ export function createSemanticValueValidator({
|
|
|
27
31
|
validateScaleSemanticValue(program, parsed, value);
|
|
28
32
|
} else if (parsed.kind === "coordinate" && parsed.path[0] === "type") {
|
|
29
33
|
validateCoordinateType(value);
|
|
34
|
+
} else if (parsed.kind === "coordinate" && parsed.path[0] === "aspect") {
|
|
35
|
+
normalizeCoordinateAspect(value);
|
|
36
|
+
} else if (parsed.kind === "coordinate" && parsed.path[0] === "polarFrame") {
|
|
37
|
+
normalizePolarFrameOptions(value);
|
|
30
38
|
} else if (parsed.kind === "guide") {
|
|
31
39
|
validateGuideSemanticValue(program, parsed, value);
|
|
32
40
|
} else if (parsed.kind === "title") {
|
|
@@ -17,6 +17,8 @@ import { normalizeCategoryOrder } from "../../../grammar/categoryOrder.js";
|
|
|
17
17
|
import { validateSemanticFieldType } from "../../../grammar/scales/index.js";
|
|
18
18
|
import { findLayer } from "../../../selectors/layers.js";
|
|
19
19
|
import { validateNonEmptySemanticString } from "./shared.js";
|
|
20
|
+
import { normalizeStatisticalWeight } from
|
|
21
|
+
"../../../grammar/weightedStatistics.js";
|
|
20
22
|
|
|
21
23
|
function validateLayerSource(program, parsed, value, sourceMarkTypes) {
|
|
22
24
|
validateUserId(value, "Layer source id");
|
|
@@ -81,6 +83,9 @@ export function validateLayerSemanticValue(
|
|
|
81
83
|
validateParallel?.(property, value);
|
|
82
84
|
}
|
|
83
85
|
if (property.endsWith(".aggregate")) validateAggregate(value);
|
|
86
|
+
if (property === "encoding.x.weight") {
|
|
87
|
+
normalizeStatisticalWeight(value, "Histogram weight");
|
|
88
|
+
}
|
|
84
89
|
if (property.endsWith(".bin.maxBins")) normalizeHistogramBin({ maxBins: value });
|
|
85
90
|
if (property.endsWith(".bin.step")) validateHistogramBinStep(value);
|
|
86
91
|
if (property.endsWith(".bin.boundaries")) validateHistogramBinBoundaries(value);
|
|
@@ -8,9 +8,22 @@ import {
|
|
|
8
8
|
validateSemanticScaleType
|
|
9
9
|
} from "../../../grammar/scales/index.js";
|
|
10
10
|
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
11
|
+
import { findScaleConsumers } from "../../scales/consumers/index.js";
|
|
11
12
|
|
|
12
|
-
function
|
|
13
|
+
function isOffsetScale(program, id, existing) {
|
|
14
|
+
if (existing?.type !== "ordinal") return false;
|
|
15
|
+
const consumers = findScaleConsumers(program, id);
|
|
16
|
+
return consumers.length > 0 && consumers.every(
|
|
17
|
+
consumer => ["xOffset", "yOffset"].includes(consumer.channel)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateOwnedProperty(program, id, existing, property) {
|
|
13
22
|
if (existing?.type !== undefined) {
|
|
23
|
+
if (
|
|
24
|
+
isOffsetScale(program, id, existing) &&
|
|
25
|
+
["paddingInner", "paddingOuter", "align"].includes(property)
|
|
26
|
+
) return;
|
|
14
27
|
validateScalePropertyForType(existing.type, property);
|
|
15
28
|
}
|
|
16
29
|
}
|
|
@@ -31,7 +44,13 @@ export function validateScaleSemanticValue(program, parsed, value) {
|
|
|
31
44
|
"nice", "zero", "clamp", "base", "exponent", "constant", "midpoint",
|
|
32
45
|
"paddingInner", "paddingOuter", "padding", "align"
|
|
33
46
|
]) {
|
|
34
|
-
if (existing?.[owned]
|
|
47
|
+
if (existing?.[owned] === undefined) continue;
|
|
48
|
+
if (
|
|
49
|
+
value === "ordinal" &&
|
|
50
|
+
isOffsetScale(program, parsed.id, existing) &&
|
|
51
|
+
["paddingInner", "paddingOuter", "align"].includes(owned)
|
|
52
|
+
) continue;
|
|
53
|
+
validateScalePropertyForType(value, owned);
|
|
35
54
|
}
|
|
36
55
|
return;
|
|
37
56
|
}
|
|
@@ -41,7 +60,9 @@ export function validateScaleSemanticValue(program, parsed, value) {
|
|
|
41
60
|
if (typeof value !== "boolean") {
|
|
42
61
|
throw new TypeError(`Scale ${property} must be a boolean.`);
|
|
43
62
|
}
|
|
44
|
-
if (property !== "reverse")
|
|
63
|
+
if (property !== "reverse") {
|
|
64
|
+
validateOwnedProperty(program, parsed.id, existing, property);
|
|
65
|
+
}
|
|
45
66
|
return;
|
|
46
67
|
}
|
|
47
68
|
if (["base", "exponent", "constant"].includes(property)) {
|
|
@@ -51,7 +72,7 @@ export function validateScaleSemanticValue(program, parsed, value) {
|
|
|
51
72
|
if (property === "base" && value === 1) {
|
|
52
73
|
throw new RangeError("Scale base must not equal 1.");
|
|
53
74
|
}
|
|
54
|
-
validateOwnedProperty(existing, property);
|
|
75
|
+
validateOwnedProperty(program, parsed.id, existing, property);
|
|
55
76
|
return;
|
|
56
77
|
}
|
|
57
78
|
if (property === "interpolate") {
|
|
@@ -64,20 +85,20 @@ export function validateScaleSemanticValue(program, parsed, value) {
|
|
|
64
85
|
"Scale paddingInner must be from 0 (inclusive) to 1 (exclusive)."
|
|
65
86
|
);
|
|
66
87
|
}
|
|
67
|
-
validateOwnedProperty(existing, property);
|
|
88
|
+
validateOwnedProperty(program, parsed.id, existing, property);
|
|
68
89
|
return;
|
|
69
90
|
}
|
|
70
91
|
if (property === "paddingOuter" || property === "padding") {
|
|
71
92
|
if (!Number.isFinite(value) || value < 0) {
|
|
72
93
|
throw new RangeError(`Scale ${property} must be a non-negative finite number.`);
|
|
73
94
|
}
|
|
74
|
-
validateOwnedProperty(existing, property);
|
|
95
|
+
validateOwnedProperty(program, parsed.id, existing, property);
|
|
75
96
|
return;
|
|
76
97
|
}
|
|
77
98
|
if (property === "align") {
|
|
78
99
|
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
79
100
|
throw new RangeError("Scale align must be between 0 and 1.");
|
|
80
101
|
}
|
|
81
|
-
validateOwnedProperty(existing, property);
|
|
102
|
+
validateOwnedProperty(program, parsed.id, existing, property);
|
|
82
103
|
}
|
|
83
104
|
}
|