ggaction 0.0.12 → 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 +127 -0
- package/README.md +20 -11
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +50 -25
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- 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 +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- 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/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- 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 +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -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 +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- 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 +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- 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 +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import { TEXT_LABEL_LAYOUT_OPTIONS } from "./layout.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_TEXT_MARK,
|
|
6
|
+
isSourceOwnedText,
|
|
5
7
|
normalizeTextMarkConfig
|
|
6
8
|
} from "../../../grammar/text.js";
|
|
9
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
7
10
|
import {
|
|
8
11
|
canMaterializeArc,
|
|
9
|
-
|
|
12
|
+
canMaterializeRect,
|
|
13
|
+
canMaterializeText,
|
|
14
|
+
isTextSource
|
|
10
15
|
} from "../../../materialization/marks/index.js";
|
|
11
|
-
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";
|
|
12
27
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
13
28
|
import {
|
|
14
29
|
applyLayeredMarkInheritance,
|
|
@@ -18,6 +33,10 @@ import {
|
|
|
18
33
|
resolveMarkId,
|
|
19
34
|
validateMarkOptions
|
|
20
35
|
} from "../shared.js";
|
|
36
|
+
import {
|
|
37
|
+
resolveFacadeData,
|
|
38
|
+
resolveFacadeId
|
|
39
|
+
} from "../../charts/shared.js";
|
|
21
40
|
import { resolveMarkGraphicPlacement } from
|
|
22
41
|
"../../../materialization/graphicHierarchy.js";
|
|
23
42
|
|
|
@@ -25,10 +44,9 @@ const STYLE_OPTIONS = Object.freeze([
|
|
|
25
44
|
"fill", "opacity", "fontSize", "fontFamily", "fontWeight",
|
|
26
45
|
"align", "baseline", "rotation", "dx", "dy"
|
|
27
46
|
]);
|
|
28
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
|
|
47
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
|
|
29
48
|
const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
|
|
30
49
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
|
|
31
|
-
const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect", "arc"]);
|
|
32
50
|
|
|
33
51
|
function sourceMatchesData(program, layer, requestedData) {
|
|
34
52
|
if (requestedData === undefined || layer?.data === requestedData) return true;
|
|
@@ -37,16 +55,34 @@ function sourceMatchesData(program, layer, requestedData) {
|
|
|
37
55
|
|
|
38
56
|
function eligibleSource(program, layer, requestedData) {
|
|
39
57
|
if (
|
|
40
|
-
!
|
|
41
|
-
layer.data === undefined ||
|
|
58
|
+
!isTextSource(layer) ||
|
|
42
59
|
!sourceMatchesData(program, layer, requestedData)
|
|
43
60
|
) return false;
|
|
44
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
|
+
}
|
|
69
|
+
if (layer.mark.type === "rect" && (canMaterializeRect(program, layer) ||
|
|
70
|
+
program.markConfigs[layer.id]?.gradientPlot?.materialized === true)) return true;
|
|
45
71
|
const encodings = resolveCompatibleEncodings(program, layer, "text");
|
|
46
72
|
return encodings.x !== undefined && encodings.y !== undefined;
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
function resolveTextInheritance(program, args) {
|
|
76
|
+
if (Object.hasOwn(args, "source")) {
|
|
77
|
+
if (Object.hasOwn(args, "data")) {
|
|
78
|
+
throw new Error("createTextMark source and data are mutually exclusive.");
|
|
79
|
+
}
|
|
80
|
+
return textInheritance(program, resolveEligibleLayer(program, {
|
|
81
|
+
target: validateUserId(args.source, "Text source id"),
|
|
82
|
+
predicate: isTextSource,
|
|
83
|
+
label: "text source"
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
50
86
|
if (Object.hasOwn(args, "data")) return undefined;
|
|
51
87
|
const requestedData = program.context.currentData;
|
|
52
88
|
const candidates = program.semanticSpec.layers.filter(layer =>
|
|
@@ -60,15 +96,21 @@ function resolveTextInheritance(program, args) {
|
|
|
60
96
|
: undefined;
|
|
61
97
|
if (source === undefined && candidates.length > 1) {
|
|
62
98
|
throw new Error(
|
|
63
|
-
"Text source inference is ambiguous; provide data and encode its position explicitly."
|
|
99
|
+
"Text source inference is ambiguous; provide source, or data and encode its position explicitly."
|
|
64
100
|
);
|
|
65
101
|
}
|
|
66
102
|
if (source === undefined) return undefined;
|
|
103
|
+
return textInheritance(program, source);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function textInheritance(program, source) {
|
|
67
107
|
return {
|
|
68
108
|
source: source.id,
|
|
69
109
|
data: source.data,
|
|
70
110
|
coordinate: source.coordinate,
|
|
71
|
-
encoding: source.mark.type === "arc"
|
|
111
|
+
encoding: source.mark.type === "arc" ||
|
|
112
|
+
(source.mark.type === "point" &&
|
|
113
|
+
findCoordinate(program, source.coordinate)?.type === "polar")
|
|
72
114
|
? {}
|
|
73
115
|
: resolveCompatibleEncodings(program, source, "text")
|
|
74
116
|
};
|
|
@@ -136,6 +178,240 @@ const createTextMark = action(
|
|
|
136
178
|
}
|
|
137
179
|
);
|
|
138
180
|
|
|
181
|
+
const LABEL_OPTIONS = Object.freeze([
|
|
182
|
+
"id", "source", "field", "value", "content", "normalizeBy", "format", "layout",
|
|
183
|
+
"select", "selection", "placement",
|
|
184
|
+
...STYLE_OPTIONS
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const ANNOTATION_OPTIONS = Object.freeze([
|
|
188
|
+
"id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
|
|
189
|
+
...STYLE_OPTIONS
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
function annotationLayout(value) {
|
|
193
|
+
const layout = value === undefined || value === false ? undefined : value;
|
|
194
|
+
if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
|
|
195
|
+
throw new Error("createAnnotation layout target is owned by the created text layer.");
|
|
196
|
+
}
|
|
197
|
+
if (layout !== undefined) {
|
|
198
|
+
validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createAnnotation layout");
|
|
199
|
+
}
|
|
200
|
+
return layout;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveAnnotationBinding(program, args) {
|
|
204
|
+
const hasX = Object.hasOwn(args, "x");
|
|
205
|
+
const hasY = Object.hasOwn(args, "y");
|
|
206
|
+
if (!hasX && !hasY) {
|
|
207
|
+
if (["space", "data", "coordinate"].some(option => Object.hasOwn(args, option))) {
|
|
208
|
+
throw new Error("createAnnotation mark anchor accepts source but not space, data, or coordinate.");
|
|
209
|
+
}
|
|
210
|
+
return { mode: "mark" };
|
|
211
|
+
}
|
|
212
|
+
if (!hasX || !hasY) {
|
|
213
|
+
throw new Error("createAnnotation coordinate anchor requires both x and y.");
|
|
214
|
+
}
|
|
215
|
+
const space = args.space ?? "data";
|
|
216
|
+
if (space !== "data" && space !== "plot") {
|
|
217
|
+
throw new Error("createAnnotation space must be data or plot.");
|
|
218
|
+
}
|
|
219
|
+
if (space === "plot") {
|
|
220
|
+
if (Object.hasOwn(args, "source")) {
|
|
221
|
+
throw new Error("createAnnotation plot anchor does not accept source.");
|
|
222
|
+
}
|
|
223
|
+
if (![args.x, args.y].every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
|
|
224
|
+
throw new Error("createAnnotation plot coordinates must be finite numbers in [0, 1].");
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
mode: "plot",
|
|
228
|
+
data: resolveFacadeData(program, args.data, "createAnnotation"),
|
|
229
|
+
coordinate: args.coordinate
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
|
|
233
|
+
throw new Error("createAnnotation data anchor uses source data and coordinate.");
|
|
234
|
+
}
|
|
235
|
+
const source = resolveEligibleLayer(program, {
|
|
236
|
+
target: args.source === undefined
|
|
237
|
+
? undefined
|
|
238
|
+
: validateUserId(args.source, "createAnnotation source"),
|
|
239
|
+
predicate: layer =>
|
|
240
|
+
!isSourceOwnedText(layer) &&
|
|
241
|
+
layer.data !== undefined &&
|
|
242
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
243
|
+
layer.encoding?.y?.scale !== undefined &&
|
|
244
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian",
|
|
245
|
+
label: "createAnnotation Cartesian layer",
|
|
246
|
+
targetOption: "source"
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
mode: "data",
|
|
250
|
+
data: source.data,
|
|
251
|
+
coordinate: source.coordinate,
|
|
252
|
+
x: source.encoding.x,
|
|
253
|
+
y: source.encoding.y
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const createAnnotation = action(
|
|
258
|
+
{
|
|
259
|
+
op: "createAnnotation",
|
|
260
|
+
description: "Create text at a final mark, data, or plot anchor."
|
|
261
|
+
},
|
|
262
|
+
function (args = {}) {
|
|
263
|
+
validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
|
|
264
|
+
if (!Object.hasOwn(args, "text")) {
|
|
265
|
+
throw new Error("createAnnotation requires text.");
|
|
266
|
+
}
|
|
267
|
+
const id = resolveFacadeId(this, args.id, {
|
|
268
|
+
defaultId: "annotation",
|
|
269
|
+
operation: "createAnnotation"
|
|
270
|
+
});
|
|
271
|
+
const binding = resolveAnnotationBinding(this, args);
|
|
272
|
+
const layout = annotationLayout(args.layout);
|
|
273
|
+
const style = Object.fromEntries(STYLE_OPTIONS
|
|
274
|
+
.filter(option => Object.hasOwn(args, option))
|
|
275
|
+
.map(option => [option, args[option]]));
|
|
276
|
+
const text = {
|
|
277
|
+
value: args.text,
|
|
278
|
+
...(args.format === undefined ? {} : { format: args.format })
|
|
279
|
+
};
|
|
280
|
+
const apply = program => {
|
|
281
|
+
if (binding.mode === "mark") {
|
|
282
|
+
return program.createMarkLabels({
|
|
283
|
+
id,
|
|
284
|
+
...(args.source === undefined ? {} : { source: args.source }),
|
|
285
|
+
...style,
|
|
286
|
+
...text,
|
|
287
|
+
...(layout === undefined ? {} : { layout })
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const xScale = binding.mode === "plot" ? `${id}-x` : binding.x.scale;
|
|
291
|
+
const yScale = binding.mode === "plot" ? `${id}-y` : binding.y.scale;
|
|
292
|
+
let next = binding.mode === "plot"
|
|
293
|
+
? program
|
|
294
|
+
.createScale({ id: xScale, type: "linear", domain: [0, 1] })
|
|
295
|
+
.createScale({ id: yScale, type: "linear", domain: [0, 1] })
|
|
296
|
+
: program;
|
|
297
|
+
next = next
|
|
298
|
+
.createTextMark({ id, data: binding.data, ...style })
|
|
299
|
+
.encodeText({ target: id, ...text })
|
|
300
|
+
.encodeX({
|
|
301
|
+
target: id,
|
|
302
|
+
datum: args.x,
|
|
303
|
+
fieldType: binding.mode === "plot" ? "quantitative" : binding.x.fieldType,
|
|
304
|
+
scale: { id: xScale },
|
|
305
|
+
...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
|
|
306
|
+
...(binding.mode === "data" && binding.x.temporalUnit !== undefined
|
|
307
|
+
? { temporalUnit: binding.x.temporalUnit }
|
|
308
|
+
: {})
|
|
309
|
+
})
|
|
310
|
+
.encodeY({
|
|
311
|
+
target: id,
|
|
312
|
+
datum: args.y,
|
|
313
|
+
fieldType: binding.mode === "plot" ? "quantitative" : binding.y.fieldType,
|
|
314
|
+
scale: { id: yScale },
|
|
315
|
+
...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
|
|
316
|
+
...(binding.mode === "data" && binding.y.temporalUnit !== undefined
|
|
317
|
+
? { temporalUnit: binding.y.temporalUnit }
|
|
318
|
+
: {})
|
|
319
|
+
});
|
|
320
|
+
return layout === undefined
|
|
321
|
+
? next
|
|
322
|
+
: next.layoutLabels({ ...layout, target: id });
|
|
323
|
+
};
|
|
324
|
+
// Validate the full lower action chain before committing any child effect.
|
|
325
|
+
apply(this);
|
|
326
|
+
return apply(this);
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const createMarkLabels = action(
|
|
331
|
+
{
|
|
332
|
+
op: "createMarkLabels",
|
|
333
|
+
description: "Create final-item text labels attached to an existing mark."
|
|
334
|
+
},
|
|
335
|
+
function (args = {}) {
|
|
336
|
+
validateMarkOptions(args, LABEL_OPTIONS, "createMarkLabels");
|
|
337
|
+
const inherited = resolveTextInheritance(this, args);
|
|
338
|
+
if (inherited === undefined) {
|
|
339
|
+
throw new Error("createMarkLabels requires an eligible source mark; provide source explicitly.");
|
|
340
|
+
}
|
|
341
|
+
const id = validateUserId(args.id === undefined ? `${inherited.source}-labels` : args.id, "Text mark id");
|
|
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
|
+
}
|
|
369
|
+
const style = Object.fromEntries(STYLE_OPTIONS
|
|
370
|
+
.filter(option => Object.hasOwn(args, option))
|
|
371
|
+
.map(option => [option, args[option]]));
|
|
372
|
+
normalizeTextMarkConfig(style);
|
|
373
|
+
const encoding = Object.fromEntries(
|
|
374
|
+
["field", "value", "content", "normalizeBy", "format"]
|
|
375
|
+
.filter(option => Object.hasOwn(args, option))
|
|
376
|
+
.map(option => [option, args[option]])
|
|
377
|
+
);
|
|
378
|
+
if (!["field", "value", "content"].some(option => Object.hasOwn(args, option))) {
|
|
379
|
+
encoding.content = "value";
|
|
380
|
+
}
|
|
381
|
+
const layout = args.layout === undefined || args.layout === false ? undefined : args.layout;
|
|
382
|
+
if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
|
|
383
|
+
throw new Error("createMarkLabels layout target is owned by the created label layer.");
|
|
384
|
+
}
|
|
385
|
+
const apply = program => {
|
|
386
|
+
let next = program.createTextMark({
|
|
387
|
+
id, source: inherited.source, align: "center", baseline: "middle", ...style
|
|
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
|
+
);
|
|
403
|
+
return layout === undefined ? next : next.layoutLabels({ ...layout, target: id });
|
|
404
|
+
};
|
|
405
|
+
if (layout !== undefined) {
|
|
406
|
+
// Validate object shape before spreading it into the child action's options.
|
|
407
|
+
validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createMarkLabels layout");
|
|
408
|
+
}
|
|
409
|
+
// Validate all child effects on a discarded immutable branch, as scale edits do.
|
|
410
|
+
apply(this);
|
|
411
|
+
return apply(this);
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
|
|
139
415
|
const rematerializeTextMark = action(
|
|
140
416
|
{
|
|
141
417
|
op: "rematerializeTextMark",
|
|
@@ -160,9 +436,11 @@ const rematerializeTextMark = action(
|
|
|
160
436
|
: this.editGraphics({ target: id, property: "length", value: 0 });
|
|
161
437
|
return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
|
|
162
438
|
? next.materializeLabelLayout({ id, rematerializeBase: false })
|
|
163
|
-
:
|
|
439
|
+
: layer.source === undefined
|
|
440
|
+
? next
|
|
441
|
+
: next.materializeMarkLabelPlacement({ id });
|
|
164
442
|
}
|
|
165
|
-
|
|
443
|
+
let next = this.editGraphics({
|
|
166
444
|
target: id,
|
|
167
445
|
property: "items",
|
|
168
446
|
value: resolveTextGraphicItems(
|
|
@@ -171,9 +449,12 @@ const rematerializeTextMark = action(
|
|
|
171
449
|
this.markConfigs[id] ?? DEFAULT_TEXT_MARK
|
|
172
450
|
)
|
|
173
451
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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 });
|
|
177
458
|
}
|
|
178
459
|
);
|
|
179
460
|
|
|
@@ -209,6 +490,8 @@ const editTextMark = action(
|
|
|
209
490
|
export function registerTextMarkActions(ProgramClass) {
|
|
210
491
|
Object.assign(ProgramClass.prototype, {
|
|
211
492
|
createTextMark,
|
|
493
|
+
createMarkLabels,
|
|
494
|
+
createAnnotation,
|
|
212
495
|
editTextMark,
|
|
213
496
|
rematerializeTextMark
|
|
214
497
|
});
|
|
@@ -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,25 +2,33 @@ 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
|
|
|
19
|
-
const
|
|
25
|
+
export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
|
|
20
26
|
"target", "axis", "padding", "maxDisplacement", "bounds", "leader"
|
|
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
|
|
|
@@ -252,9 +332,15 @@ const layoutLabels = action(
|
|
|
252
332
|
description: "Assign deterministic collision-aware layout to a text mark."
|
|
253
333
|
},
|
|
254
334
|
function (args = {}) {
|
|
255
|
-
validateOptionObject(args,
|
|
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
|
}
|