ggaction 0.0.13 → 0.0.16
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 +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- 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 +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- 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 +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- 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 +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -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 +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- 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/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- 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 +341 -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/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- 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 +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- 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 +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- 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/scaleGuideDependencies.js +10 -0
- 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 +20 -5
- 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/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- 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/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import { action } from "../../../core/action.js";
|
|
1
|
+
import { action, closedAction } 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,13 +198,76 @@ function materializeLeaders(program, layer, config, resolved) {
|
|
|
190
198
|
return { program: next, count: leaders.length };
|
|
191
199
|
}
|
|
192
200
|
|
|
193
|
-
|
|
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, program.materializationConfigs.textMetrics)
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const materializeMarkLabelPlacement = /* @__PURE__ */ closedAction(
|
|
230
|
+
{
|
|
231
|
+
op: "materializeMarkLabelPlacement",
|
|
232
|
+
description: "Reconcile semantic attached-label placement leaders."
|
|
233
|
+
}, MATERIALIZE_PLACEMENT_OPTIONS,
|
|
234
|
+
function (args = {}) {
|
|
235
|
+
const id = validateUserId(args.id, "Attached label id");
|
|
236
|
+
const layer = findLayer(this, id);
|
|
237
|
+
if (!isSourceOwnedText(layer)) {
|
|
238
|
+
throw new Error(`Unknown attached label target "${id}".`);
|
|
239
|
+
}
|
|
240
|
+
const leaderId = markLabelPlacementLeaderId(id);
|
|
241
|
+
const placement = this.markConfigs[id]?.labelAuthoring?.placement;
|
|
242
|
+
if (placement?.leader === undefined || placement.leader === false) {
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
const layout = this.materializationConfigs.labelLayouts?.[id];
|
|
246
|
+
if (layout !== undefined && layout.leader !== false) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"Semantic label placement leader conflicts with the active collision-layout leader."
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (!canMaterializeText(this, layer)) {
|
|
252
|
+
return removeLeaderGraphic(this, leaderId);
|
|
253
|
+
}
|
|
254
|
+
return materializeLeaders(this, layer, {
|
|
255
|
+
leaderId,
|
|
256
|
+
leader: {
|
|
257
|
+
...placement.leader,
|
|
258
|
+
strokeDash: [],
|
|
259
|
+
opacity: 1
|
|
260
|
+
}
|
|
261
|
+
}, placementLeaderItems(this, layer)).program;
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const materializeLabelLayout = /* @__PURE__ */ closedAction(
|
|
194
266
|
{
|
|
195
267
|
op: "materializeLabelLayout",
|
|
196
268
|
description: "Resolve concrete label positions and optional leader lines."
|
|
197
|
-
},
|
|
269
|
+
}, MATERIALIZE_OPTIONS,
|
|
198
270
|
function (args = {}) {
|
|
199
|
-
validateOptionObject(args, MATERIALIZE_OPTIONS, "materializeLabelLayout");
|
|
200
271
|
const id = validateUserId(args.id, "Text mark id");
|
|
201
272
|
const rematerializeBase = args.rematerializeBase ?? true;
|
|
202
273
|
if (typeof rematerializeBase !== "boolean") {
|
|
@@ -217,14 +288,14 @@ const materializeLabelLayout = action(
|
|
|
217
288
|
axis: config.axis,
|
|
218
289
|
padding: config.padding,
|
|
219
290
|
maxDisplacement: config.maxDisplacement
|
|
220
|
-
});
|
|
291
|
+
}, next.materializationConfigs.textMetrics);
|
|
221
292
|
next = editMarkGraphic(next, id, {
|
|
222
293
|
x: resolved.items.map(item => item.x),
|
|
223
294
|
y: resolved.items.map(item => item.y)
|
|
224
295
|
});
|
|
225
|
-
const leaders = materializeLeaders(next, layer, config, resolved);
|
|
296
|
+
const leaders = materializeLeaders(next, layer, config, resolved.items);
|
|
226
297
|
next = leaders.program;
|
|
227
|
-
|
|
298
|
+
next = next._withMaterializationConfig(["labelLayouts", id], {
|
|
228
299
|
axis: config.axis,
|
|
229
300
|
padding: config.padding,
|
|
230
301
|
maxDisplacement: config.maxDisplacement,
|
|
@@ -243,18 +314,26 @@ const materializeLabelLayout = action(
|
|
|
243
314
|
warnings: resolved.warnings
|
|
244
315
|
}
|
|
245
316
|
});
|
|
317
|
+
return layer.source === undefined
|
|
318
|
+
? next
|
|
319
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
246
320
|
}
|
|
247
321
|
);
|
|
248
322
|
|
|
249
|
-
const layoutLabels =
|
|
323
|
+
const layoutLabels = /* @__PURE__ */ closedAction(
|
|
250
324
|
{
|
|
251
325
|
op: "layoutLabels",
|
|
252
326
|
description: "Assign deterministic collision-aware layout to a text mark."
|
|
253
|
-
},
|
|
327
|
+
}, TEXT_LABEL_LAYOUT_OPTIONS,
|
|
254
328
|
function (args = {}) {
|
|
255
|
-
validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
|
|
256
329
|
const layer = requireCompleteText(this, args.target, "layoutLabels");
|
|
257
330
|
const policy = normalizePolicy(args);
|
|
331
|
+
const placement = this.markConfigs[layer.id]?.labelAuthoring?.placement;
|
|
332
|
+
if (policy.leader !== false && placement?.leader !== false && placement?.leader !== undefined) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"Collision-layout leader conflicts with the active semantic label placement leader."
|
|
335
|
+
);
|
|
336
|
+
}
|
|
258
337
|
const generatedId = leaderId(layer.id);
|
|
259
338
|
const existingConfig = this.materializationConfigs.labelLayouts?.[layer.id];
|
|
260
339
|
if (
|
|
@@ -272,13 +351,72 @@ const layoutLabels = action(
|
|
|
272
351
|
}
|
|
273
352
|
);
|
|
274
353
|
|
|
275
|
-
const
|
|
354
|
+
const editMarkLabelPlacement = /* @__PURE__ */ action(
|
|
355
|
+
{
|
|
356
|
+
op: "editMarkLabelPlacement",
|
|
357
|
+
description: "Replace or reset semantic placement for one attached label layer."
|
|
358
|
+
},
|
|
359
|
+
function (args = {}) {
|
|
360
|
+
validateOptionObject(args, EDIT_PLACEMENT_OPTIONS, "editMarkLabelPlacement", {
|
|
361
|
+
allowEmpty: false,
|
|
362
|
+
emptyError: Error
|
|
363
|
+
});
|
|
364
|
+
if (!Object.hasOwn(args, "target") || !Object.hasOwn(args, "placement")) {
|
|
365
|
+
throw new Error("editMarkLabelPlacement requires target and placement.");
|
|
366
|
+
}
|
|
367
|
+
const id = validateUserId(args.target, "Attached label id");
|
|
368
|
+
const layer = findLayer(this, id);
|
|
369
|
+
if (!isSourceOwnedText(layer)) {
|
|
370
|
+
throw new Error(`Unknown attached label target "${id}".`);
|
|
371
|
+
}
|
|
372
|
+
let placement;
|
|
373
|
+
if (args.placement !== "auto") {
|
|
374
|
+
placement = validateSourceMarkLabelPlacement(this, layer.source, args.placement);
|
|
375
|
+
if (
|
|
376
|
+
placement.leader !== false &&
|
|
377
|
+
this.materializationConfigs.labelLayouts?.[id]?.leader !== false &&
|
|
378
|
+
this.materializationConfigs.labelLayouts?.[id]?.leader !== undefined
|
|
379
|
+
) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
"Semantic label placement leader conflicts with the active collision-layout leader."
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const generatedId = markLabelPlacementLeaderId(id);
|
|
385
|
+
const previous = this.markConfigs[id]?.labelAuthoring?.placement;
|
|
386
|
+
if (
|
|
387
|
+
placement.leader !== false &&
|
|
388
|
+
this.graphicSpec.objects[generatedId] !== undefined &&
|
|
389
|
+
(previous?.leader === undefined || previous.leader === false)
|
|
390
|
+
) {
|
|
391
|
+
throw new Error(`Mark label placement leader graphic "${generatedId}" already exists.`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const config = this.markConfigs[id] ?? DEFAULT_TEXT_MARK;
|
|
395
|
+
const previousPlacement = config.labelAuthoring?.placement;
|
|
396
|
+
let base = this;
|
|
397
|
+
if (
|
|
398
|
+
previousPlacement?.leader !== undefined &&
|
|
399
|
+
previousPlacement.leader !== false &&
|
|
400
|
+
(placement === undefined || placement.leader === false)
|
|
401
|
+
) {
|
|
402
|
+
base = removeLeaderGraphic(base, markLabelPlacementLeaderId(id));
|
|
403
|
+
}
|
|
404
|
+
const labelAuthoring = { ...config.labelAuthoring };
|
|
405
|
+
if (placement === undefined) delete labelAuthoring.placement;
|
|
406
|
+
else labelAuthoring.placement = placement;
|
|
407
|
+
const next = base._withMarkConfig(id, { ...config, labelAuthoring });
|
|
408
|
+
return canMaterializeText(next, layer)
|
|
409
|
+
? next.rematerializeTextMark({ id })
|
|
410
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const removeLabelLayout = /* @__PURE__ */ closedAction(
|
|
276
415
|
{
|
|
277
416
|
op: "removeLabelLayout",
|
|
278
417
|
description: "Remove label layout and restore semantic base text positions."
|
|
279
|
-
},
|
|
418
|
+
}, REMOVE_OPTIONS,
|
|
280
419
|
function (args = {}) {
|
|
281
|
-
validateOptionObject(args, REMOVE_OPTIONS, "removeLabelLayout");
|
|
282
420
|
const layer = requireConfiguredText(this, args.target);
|
|
283
421
|
const config = this.materializationConfigs.labelLayouts[layer.id];
|
|
284
422
|
return removeLeaderGraphic(
|
|
@@ -292,6 +430,8 @@ export function registerTextLabelLayoutActions(ProgramClass) {
|
|
|
292
430
|
Object.assign(ProgramClass.prototype, {
|
|
293
431
|
layoutLabels,
|
|
294
432
|
removeLabelLayout,
|
|
295
|
-
materializeLabelLayout
|
|
433
|
+
materializeLabelLayout,
|
|
434
|
+
editMarkLabelPlacement,
|
|
435
|
+
materializeMarkLabelPlacement
|
|
296
436
|
});
|
|
297
437
|
}
|
|
@@ -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 = /* @__PURE__ */ 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,72 @@
|
|
|
1
|
+
import { closedAction } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
|
|
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 = /* @__PURE__ */ closedAction(
|
|
44
|
+
{
|
|
45
|
+
op: "editMarkLabelSelection",
|
|
46
|
+
description: "Replace the final-item membership requested by an attached label."
|
|
47
|
+
}, OPTIONS,
|
|
48
|
+
function (args = {}) {
|
|
49
|
+
if (!Object.hasOwn(args, "target")) {
|
|
50
|
+
throw new Error("editMarkLabelSelection requires target.");
|
|
51
|
+
}
|
|
52
|
+
const layer = requireAttachedLabel(this, args.target);
|
|
53
|
+
const selection = normalizeLabelSelectionEdit(args);
|
|
54
|
+
validateSelectionSource(this, layer.source, selection);
|
|
55
|
+
const next = this._withMarkConfig(layer.id, {
|
|
56
|
+
...this.markConfigs[layer.id],
|
|
57
|
+
labelAuthoring: {
|
|
58
|
+
...this.markConfigs[layer.id]?.labelAuthoring,
|
|
59
|
+
selection
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return rematerializeLabelsBeforeHighlights(
|
|
63
|
+
next,
|
|
64
|
+
layer.source,
|
|
65
|
+
[layer.id]
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export function registerTextLabelSelectionActions(ProgramClass) {
|
|
71
|
+
ProgramClass.prototype.editMarkLabelSelection = editMarkLabelSelection;
|
|
72
|
+
}
|
|
@@ -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,17 +83,19 @@ 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
|
|
|
84
|
-
export const createTickMark = action(
|
|
91
|
+
export const createTickMark = /* @__PURE__ */ action(
|
|
85
92
|
{
|
|
86
93
|
op: "createTickMark",
|
|
87
94
|
description: "Create a centered fixed-length Tick mark."
|
|
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",
|
|
@@ -125,30 +133,37 @@ export const createTickMark = action(
|
|
|
125
133
|
}
|
|
126
134
|
);
|
|
127
135
|
|
|
128
|
-
export const editTickMark = action(
|
|
136
|
+
export const editTickMark = /* @__PURE__ */ action(
|
|
129
137
|
{
|
|
130
138
|
op: "editTickMark",
|
|
131
139
|
description: "Edit Tick length and constant line appearance."
|
|
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
|
|
|
151
|
-
export const rematerializeTickMark = action(
|
|
166
|
+
export const rematerializeTickMark = /* @__PURE__ */ action(
|
|
152
167
|
{
|
|
153
168
|
op: "rematerializeTickMark",
|
|
154
169
|
description: "Recompute concrete centered Tick endpoints and appearance."
|
|
@@ -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
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closedAction } from "../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
3
|
import { freezeOwned } from "../../core/immutable.js";
|
|
4
4
|
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
@@ -100,13 +100,14 @@ function insertSibling(siblings, id, { before, after }) {
|
|
|
100
100
|
return freezeOwned(next);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const createGraphics =
|
|
103
|
+
const createGraphics = /* @__PURE__ */ closedAction(
|
|
104
104
|
{
|
|
105
105
|
op: "createGraphics",
|
|
106
106
|
description: "Create and optionally attach a concrete graphic.",
|
|
107
107
|
scope: "any"
|
|
108
|
-
},
|
|
109
|
-
function (
|
|
108
|
+
}, ["id", "type", "length", "parent", "before", "after"],
|
|
109
|
+
function (args = {}) {
|
|
110
|
+
const { id, type, length, parent, before, after } = args;
|
|
110
111
|
if (typeof id !== "string" || !GRAPHIC_ID_PATTERN.test(id)) {
|
|
111
112
|
throw new TypeError(
|
|
112
113
|
"createGraphics requires an id containing only letters, numbers, _ or -."
|