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
|
@@ -5,10 +5,15 @@ import { validateKeys } from "../../core/validation.js";
|
|
|
5
5
|
import { findDataset } from "../../selectors/datasets.js";
|
|
6
6
|
import { findLayer } from "../../selectors/layers.js";
|
|
7
7
|
import { applyDetachedScaleRematerialization } from "../../materialization/dependencies.js";
|
|
8
|
+
import { markLabelPlacementLeaderId } from "../../layout/labels.js";
|
|
8
9
|
import {
|
|
9
10
|
getPositionChannelDefinition,
|
|
10
11
|
POSITION_CHANNELS
|
|
11
12
|
} from "../../core/vocabulary.js";
|
|
13
|
+
import {
|
|
14
|
+
canonicalResourcePath,
|
|
15
|
+
collectResourceReferences
|
|
16
|
+
} from "../../core/resourceReferences.js";
|
|
12
17
|
|
|
13
18
|
const OPTIONS = Object.freeze(["target"]);
|
|
14
19
|
const REMOVE_AXIS = Object.freeze({
|
|
@@ -40,6 +45,12 @@ function ownedChildren(program, id) {
|
|
|
40
45
|
program.semanticSpec.layers
|
|
41
46
|
.filter(layer => layer.source === id)
|
|
42
47
|
.map(layer => layer.id)
|
|
48
|
+
).concat(
|
|
49
|
+
program.semanticSpec.layers
|
|
50
|
+
.filter(layer =>
|
|
51
|
+
program.markConfigs[layer.id]?.statisticalReference?.source === id
|
|
52
|
+
)
|
|
53
|
+
.map(layer => layer.id)
|
|
43
54
|
).filter(child => child !== undefined && child !== id && findLayer(program, child) !== undefined);
|
|
44
55
|
}
|
|
45
56
|
|
|
@@ -47,6 +58,9 @@ function ownership(program) {
|
|
|
47
58
|
const ownerByChild = new Map();
|
|
48
59
|
for (const layer of program.semanticSpec.layers) {
|
|
49
60
|
for (const child of ownedChildren(program, layer.id)) {
|
|
61
|
+
if (program.markConfigs[child]?.statisticalReference !== undefined) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
50
64
|
ownerByChild.set(child, layer.id);
|
|
51
65
|
}
|
|
52
66
|
}
|
|
@@ -86,6 +100,37 @@ function collectClosure(program, root) {
|
|
|
86
100
|
return result;
|
|
87
101
|
}
|
|
88
102
|
|
|
103
|
+
function externalMarkReferences(program, ids) {
|
|
104
|
+
const closure = new Set(ids);
|
|
105
|
+
const references = ids.flatMap(id => collectResourceReferences(program, {
|
|
106
|
+
kind: "mark",
|
|
107
|
+
id
|
|
108
|
+
})).filter(reference => {
|
|
109
|
+
if (reference.strength === "context") return false;
|
|
110
|
+
if (["layer", "markConfig"].includes(reference.ownerKind)) {
|
|
111
|
+
return !closure.has(reference.ownerId);
|
|
112
|
+
}
|
|
113
|
+
if (["selection", "highlight", "legend"].includes(reference.ownerKind)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
reference.ownerKind === "axis" &&
|
|
118
|
+
reference.ownerId === "parallel.axes"
|
|
119
|
+
) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
const unique = new Map(references.map(reference => [[
|
|
125
|
+
reference.ownerKind,
|
|
126
|
+
reference.ownerId,
|
|
127
|
+
canonicalResourcePath(reference.path)
|
|
128
|
+
].join("\u0000"), reference]));
|
|
129
|
+
return [...unique.entries()]
|
|
130
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
131
|
+
.map(([, reference]) => reference);
|
|
132
|
+
}
|
|
133
|
+
|
|
89
134
|
function ownedDerivedData(program, ids) {
|
|
90
135
|
const candidates = new Set();
|
|
91
136
|
for (const id of ids) {
|
|
@@ -100,7 +145,8 @@ function ownedDerivedData(program, ids) {
|
|
|
100
145
|
config.gradientPlot?.profileId,
|
|
101
146
|
config.violinPlot?.materialized === true ? layer?.data : undefined,
|
|
102
147
|
config.ecdfPlot?.data,
|
|
103
|
-
config.endpointPlot?.data
|
|
148
|
+
config.endpointPlot?.data,
|
|
149
|
+
config.statisticalReference?.dataId
|
|
104
150
|
]) {
|
|
105
151
|
if (candidate !== undefined) candidates.add(candidate);
|
|
106
152
|
}
|
|
@@ -208,11 +254,26 @@ export const removeMark = action(
|
|
|
208
254
|
validateKeys(args, OPTIONS, "removeMark");
|
|
209
255
|
const owner = resolveOwner(this, args.target);
|
|
210
256
|
const ids = collectClosure(this, owner.id);
|
|
257
|
+
const external = externalMarkReferences(this, ids);
|
|
258
|
+
if (external.length > 0) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`removeMark cannot remove externally referenced marks: ${external.map(
|
|
261
|
+
reference => `${reference.ownerKind} "${reference.ownerId}" at ` +
|
|
262
|
+
canonicalResourcePath(reference.path)
|
|
263
|
+
).join("; ")}.`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
211
266
|
const previousLayers = ids.map(id => findLayer(this, id));
|
|
212
267
|
const derived = ownedDerivedData(this, ids);
|
|
213
268
|
const positionScales = usedPositionScales(this, ids);
|
|
214
269
|
let next = this;
|
|
215
270
|
|
|
271
|
+
if (ids.includes(next.guideConfigs.axis?.parallel?.axes?.target)) {
|
|
272
|
+
next = next.removeParallelAxes({
|
|
273
|
+
target: next.guideConfigs.axis.parallel.axes.target
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
216
277
|
const legendTargets = [...new Set(Object.values(next.guideConfigs.legend ?? {})
|
|
217
278
|
.filter(config => ids.includes(config?.target))
|
|
218
279
|
.map(config => config.target))];
|
|
@@ -230,6 +291,15 @@ export const removeMark = action(
|
|
|
230
291
|
) {
|
|
231
292
|
next = next.editGraphics({ target: labelLayout.leaderId, remove: true });
|
|
232
293
|
}
|
|
294
|
+
const placement = next.markConfigs[id]?.labelAuthoring?.placement;
|
|
295
|
+
const placementLeaderId = markLabelPlacementLeaderId(id);
|
|
296
|
+
if (
|
|
297
|
+
placement?.leader !== undefined &&
|
|
298
|
+
placement.leader !== false &&
|
|
299
|
+
next.graphicSpec.objects[placementLeaderId] !== undefined
|
|
300
|
+
) {
|
|
301
|
+
next = next.editGraphics({ target: placementLeaderId, remove: true });
|
|
302
|
+
}
|
|
233
303
|
if (findLayer(next, id) !== undefined) {
|
|
234
304
|
next = next.editSemantic({ property: `layer[${id}]`, remove: true });
|
|
235
305
|
}
|
|
@@ -9,11 +9,13 @@ import {
|
|
|
9
9
|
normalizeStrokeDashPattern
|
|
10
10
|
} from "../../../grammar/scales/index.js";
|
|
11
11
|
import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveCoordinateBounds } from "../../../materialization/coordinateBounds.js";
|
|
13
|
+
import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
|
|
13
14
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
14
15
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
15
16
|
import { applyRuleAppearance, planRuleAppearance } from "./appearance.js";
|
|
16
17
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
18
|
+
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
17
19
|
import {
|
|
18
20
|
assertMarkAvailable,
|
|
19
21
|
applyLayeredMarkInheritance,
|
|
@@ -31,9 +33,19 @@ import { applyOffsetPositionValues } from
|
|
|
31
33
|
"../../../materialization/rowEncoding.js";
|
|
32
34
|
import { resolveCategorySlotOffset } from
|
|
33
35
|
"../../../materialization/categorySlotOffset.js";
|
|
36
|
+
import { mapScaleConsumerValues } from
|
|
37
|
+
"../../../materialization/scales/map.js";
|
|
38
|
+
import {
|
|
39
|
+
requestedStrokeDetails,
|
|
40
|
+
STROKE_STYLE_PROPERTIES
|
|
41
|
+
} from "../../../grammar/strokeStyle.js";
|
|
34
42
|
|
|
35
|
-
const
|
|
36
|
-
|
|
43
|
+
const APPEARANCE_OPTIONS = Object.freeze([
|
|
44
|
+
"stroke", "strokeWidth", "strokeDash", "opacity",
|
|
45
|
+
...STROKE_STYLE_PROPERTIES
|
|
46
|
+
]);
|
|
47
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
|
|
48
|
+
const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
|
|
37
49
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
38
50
|
const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
|
|
39
51
|
const DEFAULT_RULE_CONFIG = Object.freeze({
|
|
@@ -87,6 +99,7 @@ const createRuleMark = action(
|
|
|
87
99
|
},
|
|
88
100
|
function (args = {}) {
|
|
89
101
|
validateMarkOptions(args, CREATE_OPTIONS, "createRuleMark");
|
|
102
|
+
const strokeDetails = requestedStrokeDetails(args, "createRuleMark");
|
|
90
103
|
const id = resolveMarkId(this, args.id, {
|
|
91
104
|
defaultId: "rule",
|
|
92
105
|
label: "Rule mark id",
|
|
@@ -115,6 +128,7 @@ const createRuleMark = action(
|
|
|
115
128
|
})
|
|
116
129
|
._withMarkConfig(id, {
|
|
117
130
|
...DEFAULT_RULE_CONFIG,
|
|
131
|
+
...strokeDetails,
|
|
118
132
|
...(inherited === undefined
|
|
119
133
|
? {}
|
|
120
134
|
: {
|
|
@@ -132,6 +146,7 @@ const editRuleMark = action(
|
|
|
132
146
|
{ op: "editRuleMark", description: "Edit constant rule appearance through its encoding owners." },
|
|
133
147
|
function (args = {}) {
|
|
134
148
|
validateMarkOptions(args, EDIT_OPTIONS, "editRuleMark");
|
|
149
|
+
const strokeDetails = requestedStrokeDetails(args, "editRuleMark");
|
|
135
150
|
if (args.target !== undefined) validateUserId(args.target, "Rule mark id");
|
|
136
151
|
const layer = resolveEligibleLayer(this, {
|
|
137
152
|
target: args.target,
|
|
@@ -139,8 +154,17 @@ const editRuleMark = action(
|
|
|
139
154
|
label: "rule mark"
|
|
140
155
|
});
|
|
141
156
|
const appearance = planRuleAppearance(args, layer);
|
|
142
|
-
if (appearance.length === 0
|
|
143
|
-
|
|
157
|
+
if (appearance.length === 0 && Object.keys(strokeDetails).length === 0) {
|
|
158
|
+
throw new Error("editRuleMark requires an appearance change.");
|
|
159
|
+
}
|
|
160
|
+
const configured = this._withMarkConfig(layer.id, {
|
|
161
|
+
...this.markConfigs[layer.id],
|
|
162
|
+
...strokeDetails
|
|
163
|
+
});
|
|
164
|
+
const materialized = appearance.length === 0
|
|
165
|
+
? configured.rematerializeRuleMark({ id: layer.id })
|
|
166
|
+
: applyRuleAppearance(configured, layer.id, appearance);
|
|
167
|
+
return rematerializeExistingLegend(materialized);
|
|
144
168
|
}
|
|
145
169
|
);
|
|
146
170
|
|
|
@@ -180,7 +204,7 @@ const rematerializeRuleMark = action(
|
|
|
180
204
|
const scaleIds = [...new Set(
|
|
181
205
|
[
|
|
182
206
|
"x", "y", "x2", "y2", "xOffset", "yOffset",
|
|
183
|
-
"strokeDash", "strokeWidth", "opacity"
|
|
207
|
+
"stroke", "strokeDash", "strokeWidth", "opacity"
|
|
184
208
|
]
|
|
185
209
|
.map(channel => layer.encoding?.[channel]?.scale)
|
|
186
210
|
.filter(scale => scale !== undefined)
|
|
@@ -224,7 +248,7 @@ const rematerializeRuleMark = action(
|
|
|
224
248
|
channel
|
|
225
249
|
});
|
|
226
250
|
}
|
|
227
|
-
const bounds =
|
|
251
|
+
const bounds = resolveCoordinateBounds(resolved, layer.coordinate);
|
|
228
252
|
const x1 = [];
|
|
229
253
|
const y1 = [];
|
|
230
254
|
const x2 = [];
|
|
@@ -252,7 +276,11 @@ const rematerializeRuleMark = action(
|
|
|
252
276
|
startY = endY = mapped.y[index];
|
|
253
277
|
} else if (mode === "box-span") {
|
|
254
278
|
const owner = resolved.graphicSpec.objects[boxSpan];
|
|
255
|
-
const
|
|
279
|
+
const item = owner?.items?.[index];
|
|
280
|
+
const box = readRectItemGeometry({
|
|
281
|
+
type: item?.type ?? owner?.type,
|
|
282
|
+
properties: item?.properties
|
|
283
|
+
});
|
|
256
284
|
if (box === undefined) throw new Error(`Rule mark "${id}" requires box span owner "${boxSpan}".`);
|
|
257
285
|
if (layer.encoding?.x?.fieldType === "quantitative") {
|
|
258
286
|
startX = endX = mapped.x[index];
|
|
@@ -299,6 +327,7 @@ const rematerializeRuleMark = action(
|
|
|
299
327
|
...resolved.markConfigs[id]
|
|
300
328
|
};
|
|
301
329
|
const dashEncoding = layer.encoding?.strokeDash;
|
|
330
|
+
const strokeEncoding = layer.encoding?.stroke;
|
|
302
331
|
const opacityEncoding = layer.encoding?.opacity;
|
|
303
332
|
const strokeWidthEncoding = layer.encoding?.strokeWidth;
|
|
304
333
|
const strokeDash = dashEncoding?.scale === undefined
|
|
@@ -325,6 +354,13 @@ const rematerializeRuleMark = action(
|
|
|
325
354
|
derived.values.strokeWidth,
|
|
326
355
|
resolved.resolvedScales[strokeWidthEncoding.scale]
|
|
327
356
|
);
|
|
357
|
+
const stroke = strokeEncoding?.scale === undefined
|
|
358
|
+
? resolvedConfig.stroke
|
|
359
|
+
: mapScaleConsumerValues(
|
|
360
|
+
derived.values.stroke,
|
|
361
|
+
resolved.resolvedScales[strokeEncoding.scale],
|
|
362
|
+
"stroke"
|
|
363
|
+
);
|
|
328
364
|
|
|
329
365
|
return editMarkGraphic(resolved, id, {
|
|
330
366
|
length: derived.length,
|
|
@@ -332,10 +368,11 @@ const rematerializeRuleMark = action(
|
|
|
332
368
|
y1,
|
|
333
369
|
x2,
|
|
334
370
|
y2,
|
|
335
|
-
stroke
|
|
371
|
+
stroke,
|
|
336
372
|
strokeWidth,
|
|
337
373
|
strokeDash,
|
|
338
|
-
opacity
|
|
374
|
+
opacity,
|
|
375
|
+
...requestedStrokeDetails(resolvedConfig, "Rule mark")
|
|
339
376
|
});
|
|
340
377
|
}
|
|
341
378
|
);
|
|
@@ -13,7 +13,17 @@ import {
|
|
|
13
13
|
canMaterializeText,
|
|
14
14
|
isTextSource
|
|
15
15
|
} from "../../../materialization/marks/index.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
resolveTextGraphicItems,
|
|
18
|
+
validateSourceMarkLabelPlacement
|
|
19
|
+
} from "../../../materialization/text.js";
|
|
20
|
+
import { markLabelPlacementLeaderId } from "../../../layout/labels.js";
|
|
21
|
+
import { resolveMarkSelection } from
|
|
22
|
+
"../../../materialization/selection/state.js";
|
|
23
|
+
import { rematerializeLabelsBeforeHighlights } from
|
|
24
|
+
"../../selection/actions.js";
|
|
25
|
+
import { normalizeLabelSelectionCreate } from
|
|
26
|
+
"../../../grammar/markLabelSelection.js";
|
|
17
27
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
18
28
|
import {
|
|
19
29
|
applyLayeredMarkInheritance,
|
|
@@ -49,6 +59,13 @@ function eligibleSource(program, layer, requestedData) {
|
|
|
49
59
|
!sourceMatchesData(program, layer, requestedData)
|
|
50
60
|
) return false;
|
|
51
61
|
if (layer.mark.type === "arc") return canMaterializeArc(program, layer);
|
|
62
|
+
if (
|
|
63
|
+
layer.mark.type === "point" &&
|
|
64
|
+
findCoordinate(program, layer.coordinate)?.type === "polar"
|
|
65
|
+
) {
|
|
66
|
+
return layer.encoding?.theta?.scale !== undefined &&
|
|
67
|
+
layer.encoding?.radius?.scale !== undefined;
|
|
68
|
+
}
|
|
52
69
|
if (layer.mark.type === "rect" && (canMaterializeRect(program, layer) ||
|
|
53
70
|
program.markConfigs[layer.id]?.gradientPlot?.materialized === true)) return true;
|
|
54
71
|
const encodings = resolveCompatibleEncodings(program, layer, "text");
|
|
@@ -91,7 +108,9 @@ function textInheritance(program, source) {
|
|
|
91
108
|
source: source.id,
|
|
92
109
|
data: source.data,
|
|
93
110
|
coordinate: source.coordinate,
|
|
94
|
-
encoding: source.mark.type === "arc"
|
|
111
|
+
encoding: source.mark.type === "arc" ||
|
|
112
|
+
(source.mark.type === "point" &&
|
|
113
|
+
findCoordinate(program, source.coordinate)?.type === "polar")
|
|
95
114
|
? {}
|
|
96
115
|
: resolveCompatibleEncodings(program, source, "text")
|
|
97
116
|
};
|
|
@@ -161,6 +180,7 @@ const createTextMark = action(
|
|
|
161
180
|
|
|
162
181
|
const LABEL_OPTIONS = Object.freeze([
|
|
163
182
|
"id", "source", "field", "value", "content", "normalizeBy", "format", "layout",
|
|
183
|
+
"select", "selection", "placement",
|
|
164
184
|
...STYLE_OPTIONS
|
|
165
185
|
]);
|
|
166
186
|
|
|
@@ -320,6 +340,32 @@ const createMarkLabels = action(
|
|
|
320
340
|
}
|
|
321
341
|
const id = validateUserId(args.id === undefined ? `${inherited.source}-labels` : args.id, "Text mark id");
|
|
322
342
|
assertMarkAvailable(this, id);
|
|
343
|
+
const selection = normalizeLabelSelectionCreate(args);
|
|
344
|
+
const placement = args.placement === undefined
|
|
345
|
+
? undefined
|
|
346
|
+
: validateSourceMarkLabelPlacement(this, inherited.source, args.placement);
|
|
347
|
+
if (
|
|
348
|
+
placement?.leader !== undefined && placement.leader !== false &&
|
|
349
|
+
this.graphicSpec.objects[markLabelPlacementLeaderId(id)] !== undefined
|
|
350
|
+
) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Mark label placement leader graphic "${markLabelPlacementLeaderId(id)}" already exists.`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (selection.kind === "inline") {
|
|
356
|
+
resolveMarkSelection(this, inherited.source, selection.selector);
|
|
357
|
+
} else if (selection.kind === "named") {
|
|
358
|
+
const definition = this.materializationConfigs.selections?.[selection.id];
|
|
359
|
+
if (definition === undefined) {
|
|
360
|
+
throw new Error(`Unknown label selection "${selection.id}".`);
|
|
361
|
+
}
|
|
362
|
+
if (definition.target !== inherited.source) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Label selection "${selection.id}" must target source mark "${inherited.source}".`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
resolveMarkSelection(this, inherited.source, definition.selector);
|
|
368
|
+
}
|
|
323
369
|
const style = Object.fromEntries(STYLE_OPTIONS
|
|
324
370
|
.filter(option => Object.hasOwn(args, option))
|
|
325
371
|
.map(option => [option, args[option]]));
|
|
@@ -337,9 +383,23 @@ const createMarkLabels = action(
|
|
|
337
383
|
throw new Error("createMarkLabels layout target is owned by the created label layer.");
|
|
338
384
|
}
|
|
339
385
|
const apply = program => {
|
|
340
|
-
|
|
386
|
+
let next = program.createTextMark({
|
|
341
387
|
id, source: inherited.source, align: "center", baseline: "middle", ...style
|
|
342
|
-
})
|
|
388
|
+
});
|
|
389
|
+
next = next._withMarkConfig(id, {
|
|
390
|
+
...next.markConfigs[id],
|
|
391
|
+
labelAuthoring: {
|
|
392
|
+
selection,
|
|
393
|
+
...(placement === undefined ? {} : { placement })
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
next = next.encodeText({ target: id, ...encoding });
|
|
397
|
+
next = rematerializeLabelsBeforeHighlights(
|
|
398
|
+
next,
|
|
399
|
+
inherited.source,
|
|
400
|
+
[id],
|
|
401
|
+
{ skipWithoutHighlights: true }
|
|
402
|
+
);
|
|
343
403
|
return layout === undefined ? next : next.layoutLabels({ ...layout, target: id });
|
|
344
404
|
};
|
|
345
405
|
if (layout !== undefined) {
|
|
@@ -376,9 +436,11 @@ const rematerializeTextMark = action(
|
|
|
376
436
|
: this.editGraphics({ target: id, property: "length", value: 0 });
|
|
377
437
|
return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
|
|
378
438
|
? next.materializeLabelLayout({ id, rematerializeBase: false })
|
|
379
|
-
:
|
|
439
|
+
: layer.source === undefined
|
|
440
|
+
? next
|
|
441
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
380
442
|
}
|
|
381
|
-
|
|
443
|
+
let next = this.editGraphics({
|
|
382
444
|
target: id,
|
|
383
445
|
property: "items",
|
|
384
446
|
value: resolveTextGraphicItems(
|
|
@@ -387,9 +449,12 @@ const rematerializeTextMark = action(
|
|
|
387
449
|
this.markConfigs[id] ?? DEFAULT_TEXT_MARK
|
|
388
450
|
)
|
|
389
451
|
});
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
452
|
+
if (replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined) {
|
|
453
|
+
return next.materializeLabelLayout({ id, rematerializeBase: false });
|
|
454
|
+
}
|
|
455
|
+
return layer.source === undefined
|
|
456
|
+
? next
|
|
457
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
393
458
|
}
|
|
394
459
|
);
|
|
395
460
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { registerTextMarkActions as registerTextCoreActions } from "./actions.js";
|
|
2
2
|
import { registerTextLabelLayoutActions } from "./layout.js";
|
|
3
|
+
import { registerTextLabelRemovalActions } from "./removal.js";
|
|
4
|
+
import { registerTextLabelSelectionActions } from "./selection.js";
|
|
3
5
|
|
|
4
6
|
export function registerTextMarkActions(ProgramClass) {
|
|
5
7
|
registerTextCoreActions(ProgramClass);
|
|
6
8
|
registerTextLabelLayoutActions(ProgramClass);
|
|
9
|
+
registerTextLabelRemovalActions(ProgramClass);
|
|
10
|
+
registerTextLabelSelectionActions(ProgramClass);
|
|
7
11
|
}
|
|
@@ -2,17 +2,23 @@ import { action } from "../../../core/action.js";
|
|
|
2
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
3
|
import { validateOptionObject } from "../../../core/validation.js";
|
|
4
4
|
import { DEFAULT_TEXT_MARK } from "../../../grammar/text.js";
|
|
5
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
5
6
|
import { findGraphicParent } from "../../../grammar/schemas/graphicTree.js";
|
|
6
7
|
import { validateConcreteGraphicValue } from
|
|
7
8
|
"../../../grammar/schemas/concreteGraphic.js";
|
|
8
9
|
import {
|
|
10
|
+
markLabelPlacementLeaderId,
|
|
9
11
|
normalizeLabelLayoutGeometry,
|
|
10
12
|
resolveLabelLayout,
|
|
11
13
|
resolveLabelLeader
|
|
12
14
|
} from "../../../layout/labels.js";
|
|
15
|
+
import { resolveTextBounds } from "../../../core/textMetrics.js";
|
|
13
16
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
14
17
|
import { canMaterializeText } from "../../../materialization/marks/index.js";
|
|
15
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
resolveTextGraphicEntries,
|
|
20
|
+
validateSourceMarkLabelPlacement
|
|
21
|
+
} from "../../../materialization/text.js";
|
|
16
22
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
17
23
|
import { editMarkGraphic } from "../shared.js";
|
|
18
24
|
|
|
@@ -21,6 +27,8 @@ export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
|
|
|
21
27
|
]);
|
|
22
28
|
const REMOVE_OPTIONS = Object.freeze(["target"]);
|
|
23
29
|
const MATERIALIZE_OPTIONS = Object.freeze(["id", "rematerializeBase"]);
|
|
30
|
+
const MATERIALIZE_PLACEMENT_OPTIONS = Object.freeze(["id"]);
|
|
31
|
+
const EDIT_PLACEMENT_OPTIONS = Object.freeze(["target", "placement"]);
|
|
24
32
|
const LEADER_OPTIONS = Object.freeze([
|
|
25
33
|
"stroke", "strokeWidth", "strokeDash", "opacity"
|
|
26
34
|
]);
|
|
@@ -166,10 +174,10 @@ function removeLeaderGraphic(program, id) {
|
|
|
166
174
|
: program.editGraphics({ target: id, remove: true });
|
|
167
175
|
}
|
|
168
176
|
|
|
169
|
-
function materializeLeaders(program, layer, config,
|
|
177
|
+
function materializeLeaders(program, layer, config, items) {
|
|
170
178
|
let next = removeLeaderGraphic(program, config.leaderId);
|
|
171
179
|
if (config.leader === false) return { program: next, count: 0 };
|
|
172
|
-
const leaders =
|
|
180
|
+
const leaders = items.map(resolveLabelLeader).filter(Boolean);
|
|
173
181
|
if (leaders.length === 0) return { program: next, count: 0 };
|
|
174
182
|
next = next.createGraphics({
|
|
175
183
|
id: config.leaderId,
|
|
@@ -190,6 +198,75 @@ function materializeLeaders(program, layer, config, resolved) {
|
|
|
190
198
|
return { program: next, count: leaders.length };
|
|
191
199
|
}
|
|
192
200
|
|
|
201
|
+
function placementLeaderItems(program, layer) {
|
|
202
|
+
const entries = resolveTextGraphicEntries(
|
|
203
|
+
program,
|
|
204
|
+
layer,
|
|
205
|
+
program.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
|
|
206
|
+
);
|
|
207
|
+
const graphic = program.graphicSpec.objects[layer.id];
|
|
208
|
+
if (graphic?.type !== "text" || !Array.isArray(graphic.items)) {
|
|
209
|
+
throw new Error(`Text mark "${layer.id}" requires text collection graphics.`);
|
|
210
|
+
}
|
|
211
|
+
if (entries.length !== graphic.items.length) {
|
|
212
|
+
throw new Error(`Text mark "${layer.id}" placement materialization is inconsistent.`);
|
|
213
|
+
}
|
|
214
|
+
return entries.map((entry, index) => {
|
|
215
|
+
const properties = graphic.items[index].properties;
|
|
216
|
+
return {
|
|
217
|
+
id: graphic.items[index].id,
|
|
218
|
+
x: properties.x,
|
|
219
|
+
y: properties.y,
|
|
220
|
+
sourceX: entry.anchor.x,
|
|
221
|
+
sourceY: entry.anchor.y,
|
|
222
|
+
dx: properties.x - entry.anchor.x,
|
|
223
|
+
dy: properties.y - entry.anchor.y,
|
|
224
|
+
bounds: resolveTextBounds(properties)
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const materializeMarkLabelPlacement = action(
|
|
230
|
+
{
|
|
231
|
+
op: "materializeMarkLabelPlacement",
|
|
232
|
+
description: "Reconcile semantic attached-label placement leaders."
|
|
233
|
+
},
|
|
234
|
+
function (args = {}) {
|
|
235
|
+
validateOptionObject(
|
|
236
|
+
args,
|
|
237
|
+
MATERIALIZE_PLACEMENT_OPTIONS,
|
|
238
|
+
"materializeMarkLabelPlacement"
|
|
239
|
+
);
|
|
240
|
+
const id = validateUserId(args.id, "Attached label id");
|
|
241
|
+
const layer = findLayer(this, id);
|
|
242
|
+
if (!isSourceOwnedText(layer)) {
|
|
243
|
+
throw new Error(`Unknown attached label target "${id}".`);
|
|
244
|
+
}
|
|
245
|
+
const leaderId = markLabelPlacementLeaderId(id);
|
|
246
|
+
const placement = this.markConfigs[id]?.labelAuthoring?.placement;
|
|
247
|
+
if (placement?.leader === undefined || placement.leader === false) {
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
const layout = this.materializationConfigs.labelLayouts?.[id];
|
|
251
|
+
if (layout !== undefined && layout.leader !== false) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"Semantic label placement leader conflicts with the active collision-layout leader."
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (!canMaterializeText(this, layer)) {
|
|
257
|
+
return removeLeaderGraphic(this, leaderId);
|
|
258
|
+
}
|
|
259
|
+
return materializeLeaders(this, layer, {
|
|
260
|
+
leaderId,
|
|
261
|
+
leader: {
|
|
262
|
+
...placement.leader,
|
|
263
|
+
strokeDash: [],
|
|
264
|
+
opacity: 1
|
|
265
|
+
}
|
|
266
|
+
}, placementLeaderItems(this, layer)).program;
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
193
270
|
const materializeLabelLayout = action(
|
|
194
271
|
{
|
|
195
272
|
op: "materializeLabelLayout",
|
|
@@ -222,9 +299,9 @@ const materializeLabelLayout = action(
|
|
|
222
299
|
x: resolved.items.map(item => item.x),
|
|
223
300
|
y: resolved.items.map(item => item.y)
|
|
224
301
|
});
|
|
225
|
-
const leaders = materializeLeaders(next, layer, config, resolved);
|
|
302
|
+
const leaders = materializeLeaders(next, layer, config, resolved.items);
|
|
226
303
|
next = leaders.program;
|
|
227
|
-
|
|
304
|
+
next = next._withMaterializationConfig(["labelLayouts", id], {
|
|
228
305
|
axis: config.axis,
|
|
229
306
|
padding: config.padding,
|
|
230
307
|
maxDisplacement: config.maxDisplacement,
|
|
@@ -243,6 +320,9 @@ const materializeLabelLayout = action(
|
|
|
243
320
|
warnings: resolved.warnings
|
|
244
321
|
}
|
|
245
322
|
});
|
|
323
|
+
return layer.source === undefined
|
|
324
|
+
? next
|
|
325
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
246
326
|
}
|
|
247
327
|
);
|
|
248
328
|
|
|
@@ -255,6 +335,12 @@ const layoutLabels = action(
|
|
|
255
335
|
validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
|
|
256
336
|
const layer = requireCompleteText(this, args.target, "layoutLabels");
|
|
257
337
|
const policy = normalizePolicy(args);
|
|
338
|
+
const placement = this.markConfigs[layer.id]?.labelAuthoring?.placement;
|
|
339
|
+
if (policy.leader !== false && placement?.leader !== false && placement?.leader !== undefined) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
"Collision-layout leader conflicts with the active semantic label placement leader."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
258
344
|
const generatedId = leaderId(layer.id);
|
|
259
345
|
const existingConfig = this.materializationConfigs.labelLayouts?.[layer.id];
|
|
260
346
|
if (
|
|
@@ -272,6 +358,66 @@ const layoutLabels = action(
|
|
|
272
358
|
}
|
|
273
359
|
);
|
|
274
360
|
|
|
361
|
+
const editMarkLabelPlacement = action(
|
|
362
|
+
{
|
|
363
|
+
op: "editMarkLabelPlacement",
|
|
364
|
+
description: "Replace or reset semantic placement for one attached label layer."
|
|
365
|
+
},
|
|
366
|
+
function (args = {}) {
|
|
367
|
+
validateOptionObject(args, EDIT_PLACEMENT_OPTIONS, "editMarkLabelPlacement", {
|
|
368
|
+
allowEmpty: false,
|
|
369
|
+
emptyError: Error
|
|
370
|
+
});
|
|
371
|
+
if (!Object.hasOwn(args, "target") || !Object.hasOwn(args, "placement")) {
|
|
372
|
+
throw new Error("editMarkLabelPlacement requires target and placement.");
|
|
373
|
+
}
|
|
374
|
+
const id = validateUserId(args.target, "Attached label id");
|
|
375
|
+
const layer = findLayer(this, id);
|
|
376
|
+
if (!isSourceOwnedText(layer)) {
|
|
377
|
+
throw new Error(`Unknown attached label target "${id}".`);
|
|
378
|
+
}
|
|
379
|
+
let placement;
|
|
380
|
+
if (args.placement !== "auto") {
|
|
381
|
+
placement = validateSourceMarkLabelPlacement(this, layer.source, args.placement);
|
|
382
|
+
if (
|
|
383
|
+
placement.leader !== false &&
|
|
384
|
+
this.materializationConfigs.labelLayouts?.[id]?.leader !== false &&
|
|
385
|
+
this.materializationConfigs.labelLayouts?.[id]?.leader !== undefined
|
|
386
|
+
) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
"Semantic label placement leader conflicts with the active collision-layout leader."
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const generatedId = markLabelPlacementLeaderId(id);
|
|
392
|
+
const previous = this.markConfigs[id]?.labelAuthoring?.placement;
|
|
393
|
+
if (
|
|
394
|
+
placement.leader !== false &&
|
|
395
|
+
this.graphicSpec.objects[generatedId] !== undefined &&
|
|
396
|
+
(previous?.leader === undefined || previous.leader === false)
|
|
397
|
+
) {
|
|
398
|
+
throw new Error(`Mark label placement leader graphic "${generatedId}" already exists.`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const config = this.markConfigs[id] ?? DEFAULT_TEXT_MARK;
|
|
402
|
+
const previousPlacement = config.labelAuthoring?.placement;
|
|
403
|
+
let base = this;
|
|
404
|
+
if (
|
|
405
|
+
previousPlacement?.leader !== undefined &&
|
|
406
|
+
previousPlacement.leader !== false &&
|
|
407
|
+
(placement === undefined || placement.leader === false)
|
|
408
|
+
) {
|
|
409
|
+
base = removeLeaderGraphic(base, markLabelPlacementLeaderId(id));
|
|
410
|
+
}
|
|
411
|
+
const labelAuthoring = { ...config.labelAuthoring };
|
|
412
|
+
if (placement === undefined) delete labelAuthoring.placement;
|
|
413
|
+
else labelAuthoring.placement = placement;
|
|
414
|
+
const next = base._withMarkConfig(id, { ...config, labelAuthoring });
|
|
415
|
+
return canMaterializeText(next, layer)
|
|
416
|
+
? next.rematerializeTextMark({ id })
|
|
417
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
|
|
275
421
|
const removeLabelLayout = action(
|
|
276
422
|
{
|
|
277
423
|
op: "removeLabelLayout",
|
|
@@ -292,6 +438,8 @@ export function registerTextLabelLayoutActions(ProgramClass) {
|
|
|
292
438
|
Object.assign(ProgramClass.prototype, {
|
|
293
439
|
layoutLabels,
|
|
294
440
|
removeLabelLayout,
|
|
295
|
-
materializeLabelLayout
|
|
441
|
+
materializeLabelLayout,
|
|
442
|
+
editMarkLabelPlacement,
|
|
443
|
+
materializeMarkLabelPlacement
|
|
296
444
|
});
|
|
297
445
|
}
|