ggaction 0.0.11 → 0.0.13
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 +80 -0
- package/README.md +3 -2
- 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 +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- 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 +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.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/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- 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 +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +79 -24
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- 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 +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +153 -11
- package/src/actions/errorBars/edit.js +365 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- 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 +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- 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 +44 -23
- 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 +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- 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 +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- 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 +138 -14
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +60 -4
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionCompatibility.js +1 -1
- 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 +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +9 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +23 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +86 -23
- package/src/layout/guideCollisions.js +12 -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 +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
1
2
|
import {
|
|
2
3
|
BAR_ORIENTATIONS,
|
|
3
4
|
resolveBarOrientation
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
} from "../../core/vocabulary.js";
|
|
9
10
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
10
11
|
import { findDataset } from "../../selectors/datasets.js";
|
|
12
|
+
import { isOpacityLegendLayer } from "../../materialization/legends.js";
|
|
11
13
|
import { findUpstreamTransform } from
|
|
12
14
|
"../../materialization/dataProvenance.js";
|
|
13
15
|
|
|
@@ -19,8 +21,8 @@ function isHorizonLayer(program, layer) {
|
|
|
19
21
|
) !== undefined;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
function hasEncoding(program, channel, { scaled = false, grid = false } = {}) {
|
|
23
|
-
return
|
|
24
|
+
function hasEncoding(program, layers, channel, { scaled = false, grid = false } = {}) {
|
|
25
|
+
return layers.some(layer => {
|
|
24
26
|
if (isHorizonLayer(program, layer) && channel !== "x") return false;
|
|
25
27
|
const encoding = layer.encoding?.[channel];
|
|
26
28
|
if (!scaled && !grid) return encoding !== undefined;
|
|
@@ -31,23 +33,22 @@ function hasEncoding(program, channel, { scaled = false, grid = false } = {}) {
|
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
function positionalApplicability(program, channels) {
|
|
36
|
+
function positionalApplicability(program, layers, channels) {
|
|
35
37
|
return Object.freeze(Object.fromEntries(
|
|
36
38
|
channels.map(channel => [
|
|
37
39
|
channel,
|
|
38
40
|
Object.freeze({
|
|
39
|
-
axis: hasEncoding(program, channel),
|
|
40
|
-
grid: hasEncoding(program, channel, { grid: true })
|
|
41
|
+
axis: hasEncoding(program, layers, channel),
|
|
42
|
+
grid: hasEncoding(program, layers, channel, { grid: true })
|
|
41
43
|
})
|
|
42
44
|
])
|
|
43
45
|
));
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
export function hasInferableLegend(program) {
|
|
47
|
-
return
|
|
48
|
+
export function hasInferableLegend(program, layers = program.semanticSpec.layers) {
|
|
49
|
+
return layers.some(layer =>
|
|
48
50
|
!isHorizonLayer(program, layer) && (
|
|
49
|
-
(layer
|
|
50
|
-
layer.encoding?.opacity?.scale !== undefined) ||
|
|
51
|
+
isOpacityLegendLayer(layer) ||
|
|
51
52
|
(layer.mark?.type === "point" &&
|
|
52
53
|
layer.encoding?.size?.scale !== undefined) ||
|
|
53
54
|
(layer.mark?.type === "point" &&
|
|
@@ -66,13 +67,14 @@ export function hasInferableLegend(program) {
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
export function resolveGuideApplicability(program) {
|
|
70
|
+
export function resolveGuideApplicability(program, layers = program.semanticSpec.layers) {
|
|
70
71
|
const cartesian = positionalApplicability(
|
|
71
72
|
program,
|
|
73
|
+
layers,
|
|
72
74
|
CARTESIAN_POSITION_CHANNELS
|
|
73
75
|
);
|
|
74
|
-
const polar = positionalApplicability(program, POLAR_POSITION_CHANNELS);
|
|
75
|
-
const parallel =
|
|
76
|
+
const polar = positionalApplicability(program, layers, POLAR_POSITION_CHANNELS);
|
|
77
|
+
const parallel = layers.some(layer =>
|
|
76
78
|
layer.encoding?.parallel?.dimensions?.length >= 2
|
|
77
79
|
);
|
|
78
80
|
return Object.freeze({
|
|
@@ -101,12 +103,13 @@ export function resolveGuideApplicability(program) {
|
|
|
101
103
|
radial: polar.radius.grid
|
|
102
104
|
})
|
|
103
105
|
}),
|
|
104
|
-
legend: hasInferableLegend(program)
|
|
106
|
+
legend: hasInferableLegend(program, layers)
|
|
105
107
|
});
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
export function resolveAutomaticGridOptions(program) {
|
|
109
|
-
|
|
110
|
+
export function resolveAutomaticGridOptions(program, layers = program.semanticSpec.layers) {
|
|
111
|
+
layers = layers.filter(layer => !isSourceOwnedText(layer));
|
|
112
|
+
const applicability = resolveGuideApplicability(program, layers);
|
|
110
113
|
const directions = applicability.grid.directions;
|
|
111
114
|
if (applicability.grid.polar && !applicability.grid.cartesian) {
|
|
112
115
|
return Object.freeze({
|
|
@@ -118,10 +121,10 @@ export function resolveAutomaticGridOptions(program) {
|
|
|
118
121
|
return Object.freeze({ horizontal: false, vertical: {} });
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
const barOrientations =
|
|
124
|
+
const barOrientations = layers
|
|
122
125
|
.map(resolveBarOrientation)
|
|
123
126
|
.filter(Boolean);
|
|
124
|
-
const positionedLayers =
|
|
127
|
+
const positionedLayers = layers.filter(
|
|
125
128
|
layer => layer.encoding?.x !== undefined && layer.encoding?.y !== undefined
|
|
126
129
|
);
|
|
127
130
|
const horizontalQuantitative = positionedLayers.length > 0 &&
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
4
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
@@ -19,7 +20,7 @@ function validateAxisOption(value, channel) {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function
|
|
23
|
+
export function validateAxesArgs(args) {
|
|
23
24
|
validateOptionObject(args, TOP_OPTIONS, "createAxes");
|
|
24
25
|
validateAxisOption(args.x, "x");
|
|
25
26
|
validateAxisOption(args.y, "y");
|
|
@@ -189,9 +190,9 @@ const createAxes = action(
|
|
|
189
190
|
description: "Read a semantic coordinate and create its Cartesian axes."
|
|
190
191
|
},
|
|
191
192
|
function (args = {}) {
|
|
192
|
-
const coordinateDescriptor =
|
|
193
|
+
const coordinateDescriptor = validateAxesArgs(args);
|
|
193
194
|
const { cartesianLayers, polarLayers, parallelLayers } = inspectChannels(
|
|
194
|
-
this.semanticSpec.layers
|
|
195
|
+
this.semanticSpec.layers.filter(layer => !isSourceOwnedText(layer))
|
|
195
196
|
);
|
|
196
197
|
const requestedCoordinateType = coordinateDescriptor.id === undefined
|
|
197
198
|
? undefined
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
2
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
1
3
|
import { action } from "../../../core/action.js";
|
|
2
4
|
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
5
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
4
6
|
import { validateKeys } from "../../../core/validation.js";
|
|
5
7
|
import { hasCoordinate } from "../../../selectors/index.js";
|
|
8
|
+
import { validateEnabledAxisComponents } from "./components.js";
|
|
6
9
|
|
|
7
10
|
const TOP_OPTIONS = Object.freeze([
|
|
8
11
|
"scale",
|
|
@@ -38,14 +41,15 @@ function validateNested(value, supported, label) {
|
|
|
38
41
|
validateKeys(value, supported, label);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function
|
|
44
|
+
export function validateAxisArgs(args, operation) {
|
|
42
45
|
validateKeys(args, TOP_OPTIONS, operation);
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
validateEnabledAxisComponents(args, operation);
|
|
48
|
+
if (Object.hasOwn(args, "line") && args.line !== false) {
|
|
45
49
|
validateNested(args.line, LINE_OPTIONS, `${operation}.line`);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
if (Object.hasOwn(args, "ticksAndLabels")) {
|
|
52
|
+
if (Object.hasOwn(args, "ticksAndLabels") && args.ticksAndLabels !== false) {
|
|
49
53
|
validateNested(
|
|
50
54
|
args.ticksAndLabels,
|
|
51
55
|
TICK_GROUP_OPTIONS,
|
|
@@ -53,7 +57,7 @@ function validateArgs(args, operation) {
|
|
|
53
57
|
);
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
if (Object.hasOwn(args, "title")) {
|
|
60
|
+
if (Object.hasOwn(args, "title") && args.title !== false) {
|
|
57
61
|
validateNested(args.title, TITLE_OPTIONS, `${operation}.title`);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -76,8 +80,8 @@ function makeCreateAxis(channel) {
|
|
|
76
80
|
op: operation.create,
|
|
77
81
|
description: `Create the complete ${channel}-axis.`
|
|
78
82
|
},
|
|
79
|
-
function (args = {}) {
|
|
80
|
-
|
|
83
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
84
|
+
validateAxisArgs(args, operation.create);
|
|
81
85
|
const shared = {};
|
|
82
86
|
if (Object.hasOwn(args, "scale")) shared.scale = args.scale;
|
|
83
87
|
if (Object.hasOwn(args, "position")) shared.position = args.position;
|
|
@@ -89,7 +93,7 @@ function makeCreateAxis(channel) {
|
|
|
89
93
|
const exists = hasCoordinate(next, coordinate);
|
|
90
94
|
const hasConsumer = next.semanticSpec.layers.some(
|
|
91
95
|
layer =>
|
|
92
|
-
layer.coordinate === coordinate &&
|
|
96
|
+
!isSourceOwnedText(layer) && layer.coordinate === coordinate &&
|
|
93
97
|
layer.encoding?.[channel]?.scale === scale
|
|
94
98
|
);
|
|
95
99
|
|
|
@@ -108,17 +112,13 @@ function makeCreateAxis(channel) {
|
|
|
108
112
|
});
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
...shared,
|
|
119
|
-
...(args.title ?? {})
|
|
120
|
-
});
|
|
121
|
-
}
|
|
115
|
+
for (const component of ["line", "ticksAndLabels", "title"]) {
|
|
116
|
+
if (args[component] !== false) {
|
|
117
|
+
next = next[operation[component]]({ ...shared, ...(args[component] ?? {}) });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return next;
|
|
121
|
+
})
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { axisGraphicIds } from "../../../materialization/guides/resources.js";
|
|
2
|
+
|
|
3
|
+
export const AXIS_COMPONENTS = Object.freeze(["line", "ticks", "labels", "title"]);
|
|
4
|
+
|
|
5
|
+
export function axisComponentId(channel, component) {
|
|
6
|
+
return axisGraphicIds(channel)[AXIS_COMPONENTS.indexOf(component)];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function axisComponentDisabled(args, component) {
|
|
10
|
+
return args[component === "ticks" || component === "labels"
|
|
11
|
+
? "ticksAndLabels" : component] === false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validateEnabledAxisComponents(args, operation) {
|
|
15
|
+
if (AXIS_COMPONENTS.every(component => axisComponentDisabled(args, component))) {
|
|
16
|
+
throw new Error(`${operation} requires at least one enabled axis component.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hasAxisComponent(program, channel, component) {
|
|
21
|
+
return program.graphicSpec.objects[axisComponentId(channel, component)] !== undefined ||
|
|
22
|
+
program.guideConfigs.axis?.[channel]?.[component] !== undefined ||
|
|
23
|
+
(component === "title" &&
|
|
24
|
+
program.semanticSpec.guides.axis?.[channel]?.title !== undefined);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function assertRemovableAxisComponent(program, channel, component, operation) {
|
|
28
|
+
if (!hasAxisComponent(program, channel, component)) {
|
|
29
|
+
throw new Error(`${operation}.${component} requires an existing ${channel}-axis ${component}.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function removeAxisComponent(program, channel, component) {
|
|
34
|
+
const id = axisComponentId(channel, component);
|
|
35
|
+
let next = program;
|
|
36
|
+
if (component === "title" && next.semanticSpec.guides.axis?.[channel]?.title !== undefined) {
|
|
37
|
+
next = next.editSemantic({ property: `guide.axis.${channel}.title`, remove: true });
|
|
38
|
+
}
|
|
39
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
40
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
41
|
+
}
|
|
42
|
+
return next._withoutMaterializationConfig(["guides", "axis", channel, component]);
|
|
43
|
+
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
4
|
import { validateKeys, validateOptionObject } from "../../../core/validation.js";
|
|
4
5
|
|
|
6
|
+
import {
|
|
7
|
+
AXIS_COMPONENTS,
|
|
8
|
+
axisComponentId as graphicId,
|
|
9
|
+
hasAxisComponent as hasComponent,
|
|
10
|
+
assertRemovableAxisComponent as assertRemovable,
|
|
11
|
+
removeAxisComponent as removeComponent
|
|
12
|
+
} from "./components.js";
|
|
13
|
+
|
|
5
14
|
const OPTIONS = Object.freeze([
|
|
6
15
|
"position", "line", "ticks", "labels", "ticksAndLabels", "title"
|
|
7
16
|
]);
|
|
@@ -11,19 +20,14 @@ const TICK_OPTIONS = Object.freeze([
|
|
|
11
20
|
]);
|
|
12
21
|
const LABEL_OPTIONS = Object.freeze([
|
|
13
22
|
"count", "values", "offset", "format", "color", "fontSize",
|
|
14
|
-
"fontFamily", "fontWeight"
|
|
23
|
+
"fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
|
|
24
|
+
"lineHeight", "overlap"
|
|
15
25
|
]);
|
|
16
26
|
const GROUP_OPTIONS = Object.freeze(["count", "values", "ticks", "labels"]);
|
|
17
27
|
const TITLE_OPTIONS = Object.freeze([
|
|
18
28
|
"text", "at", "offset", "rotation", "color", "fontSize",
|
|
19
29
|
"fontFamily", "fontWeight"
|
|
20
30
|
]);
|
|
21
|
-
const COMPONENTS = Object.freeze({
|
|
22
|
-
line: Object.freeze({ suffix: "Line", config: "line" }),
|
|
23
|
-
ticks: Object.freeze({ suffix: "Ticks", config: "ticks" }),
|
|
24
|
-
labels: Object.freeze({ suffix: "Labels", config: "labels" }),
|
|
25
|
-
title: Object.freeze({ suffix: "Title", config: "title" })
|
|
26
|
-
});
|
|
27
31
|
|
|
28
32
|
function validateNested(args, key, options, operation, { allowFalse = false } = {}) {
|
|
29
33
|
if (!Object.hasOwn(args, key)) return;
|
|
@@ -59,7 +63,8 @@ function validateArgs(args, operation) {
|
|
|
59
63
|
}
|
|
60
64
|
if (args.ticksAndLabels !== false && args.ticksAndLabels?.labels !== undefined) {
|
|
61
65
|
validateNested(args.ticksAndLabels, "labels", [
|
|
62
|
-
"offset", "format", "color", "fontSize", "fontFamily", "fontWeight"
|
|
66
|
+
"offset", "format", "color", "fontSize", "fontFamily", "fontWeight",
|
|
67
|
+
"rotation", "maxWidth", "wrap", "lineHeight", "overlap"
|
|
63
68
|
], `${operation}.ticksAndLabels`);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
@@ -77,46 +82,6 @@ function names(channel) {
|
|
|
77
82
|
};
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
function graphicId(channel, component) {
|
|
81
|
-
return `${channel}Axis${COMPONENTS[component].suffix}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function hasComponent(program, channel, component) {
|
|
85
|
-
return program.graphicSpec.objects[graphicId(channel, component)] !== undefined ||
|
|
86
|
-
program.guideConfigs.axis?.[channel]?.[COMPONENTS[component].config] !== undefined ||
|
|
87
|
-
(component === "title" &&
|
|
88
|
-
program.semanticSpec.guides.axis?.[channel]?.title !== undefined);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function assertRemovable(program, channel, component, operation) {
|
|
92
|
-
if (!hasComponent(program, channel, component)) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`${operation}.${component} requires an existing ${channel}-axis ${component}.`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function removeComponent(program, channel, component) {
|
|
100
|
-
const id = graphicId(channel, component);
|
|
101
|
-
let next = program;
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
component === "title" &&
|
|
105
|
-
next.semanticSpec.guides.axis?.[channel]?.title !== undefined
|
|
106
|
-
) {
|
|
107
|
-
next = next.editSemantic({
|
|
108
|
-
property: `guide.axis.${channel}.title`,
|
|
109
|
-
remove: true
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (next.graphicSpec.objects[id] !== undefined) {
|
|
113
|
-
next = next.editGraphics({ target: id, remove: true });
|
|
114
|
-
}
|
|
115
|
-
return next._withoutMaterializationConfig([
|
|
116
|
-
"guides", "axis", channel, COMPONENTS[component].config
|
|
117
|
-
]);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
85
|
function buildPlan(program, channel, args, operation) {
|
|
121
86
|
const shared = Object.hasOwn(args, "position")
|
|
122
87
|
? { position: args.position }
|
|
@@ -168,7 +133,7 @@ function applyPlan(program, channel, plan, operation) {
|
|
|
168
133
|
: next[step.operation](step.args);
|
|
169
134
|
}
|
|
170
135
|
|
|
171
|
-
const hasRetainedGraphic =
|
|
136
|
+
const hasRetainedGraphic = AXIS_COMPONENTS.some(
|
|
172
137
|
component => next.graphicSpec.objects[graphicId(channel, component)] !== undefined
|
|
173
138
|
);
|
|
174
139
|
if (!hasRetainedGraphic && (
|
|
@@ -187,7 +152,7 @@ function makeEditAxis(channel) {
|
|
|
187
152
|
op: operation.operation,
|
|
188
153
|
description: `Edit selected existing ${channel}-axis components.`
|
|
189
154
|
},
|
|
190
|
-
function (args = {}) {
|
|
155
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
191
156
|
validateArgs(args, operation.operation);
|
|
192
157
|
const plan = buildPlan(this, channel, args, operation);
|
|
193
158
|
|
|
@@ -195,7 +160,7 @@ function makeEditAxis(channel) {
|
|
|
195
160
|
// later leaf failure cannot begin the returned action trace or state.
|
|
196
161
|
applyPlan(this, channel, plan, operation);
|
|
197
162
|
return applyPlan(this, channel, plan, operation);
|
|
198
|
-
}
|
|
163
|
+
})
|
|
199
164
|
);
|
|
200
165
|
}
|
|
201
166
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
4
|
import {
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
mapOrdinalPositionValues
|
|
17
18
|
} from "../../../grammar/scales/index.js";
|
|
18
19
|
import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
|
|
20
|
+
import { resolveRotation } from "../../../grammar/rotation.js";
|
|
19
21
|
import { valuesFromTickConfig } from "../tickValues.js";
|
|
20
22
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
21
23
|
"../../../theme/defaults.js";
|
|
@@ -35,10 +37,12 @@ import {
|
|
|
35
37
|
} from "../../../core/textMetrics.js";
|
|
36
38
|
import { resolveConcreteGraphicBounds } from
|
|
37
39
|
"../../../grammar/schemas/graphicBounds.js";
|
|
40
|
+
import { wrapText } from "../../../layout/text.js";
|
|
38
41
|
|
|
39
42
|
const OPTIONS = [
|
|
40
43
|
"scale", "position", "count", "values", "offset", "format", "color",
|
|
41
|
-
"fontSize", "fontFamily", "fontWeight"
|
|
44
|
+
"fontSize", "fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
|
|
45
|
+
"lineHeight", "overlap"
|
|
42
46
|
];
|
|
43
47
|
|
|
44
48
|
const DEFAULTS = {
|
|
@@ -46,9 +50,30 @@ const DEFAULTS = {
|
|
|
46
50
|
color: DEFAULT_COLORS.text,
|
|
47
51
|
fontSize: 12,
|
|
48
52
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
49
|
-
fontWeight: "normal"
|
|
53
|
+
fontWeight: "normal",
|
|
54
|
+
rotation: 0,
|
|
55
|
+
overlap: "error"
|
|
50
56
|
};
|
|
51
57
|
|
|
58
|
+
function normalizeLabelLayout(config, args, operation) {
|
|
59
|
+
const next = { ...config };
|
|
60
|
+
next.rotation = Object.hasOwn(args, "rotation")
|
|
61
|
+
? resolveRotation(args.rotation, `${operation} rotation`)
|
|
62
|
+
: next.rotation ?? 0;
|
|
63
|
+
next.overlap ??= "error";
|
|
64
|
+
if (args.maxWidth === false) {
|
|
65
|
+
if (Object.hasOwn(args, "wrap") || Object.hasOwn(args, "lineHeight")) {
|
|
66
|
+
throw new Error(`${operation} cannot combine maxWidth false with wrap or lineHeight.`);
|
|
67
|
+
}
|
|
68
|
+
delete next.maxWidth;
|
|
69
|
+
delete next.wrap;
|
|
70
|
+
delete next.lineHeight;
|
|
71
|
+
} else if (next.maxWidth !== undefined) {
|
|
72
|
+
next.wrap ??= "word";
|
|
73
|
+
}
|
|
74
|
+
return next;
|
|
75
|
+
}
|
|
76
|
+
|
|
52
77
|
export function validateAxisTextStyle(config, label) {
|
|
53
78
|
validateNonEmptyString(config.color, `${label} color`);
|
|
54
79
|
validatePositiveFinite(config.fontSize, `${label} fontSize`);
|
|
@@ -90,6 +115,25 @@ function validateConfig(channel, config) {
|
|
|
90
115
|
validateNonNegativeFinite(config.offset, "Label offset");
|
|
91
116
|
validateAxisTextStyle(config, "Label");
|
|
92
117
|
validateAxisFormat(config.format);
|
|
118
|
+
if (!Number.isFinite(config.rotation)) {
|
|
119
|
+
throw new TypeError("Label rotation must resolve to finite radians.");
|
|
120
|
+
}
|
|
121
|
+
if (config.maxWidth !== undefined) {
|
|
122
|
+
validatePositiveFinite(config.maxWidth, "Label maxWidth");
|
|
123
|
+
if (!["word", "character"].includes(config.wrap)) {
|
|
124
|
+
throw new Error(`Unsupported axis label wrap "${config.wrap}".`);
|
|
125
|
+
}
|
|
126
|
+
} else if (config.wrap !== undefined || config.lineHeight !== undefined) {
|
|
127
|
+
throw new Error("Axis label wrap and lineHeight require maxWidth.");
|
|
128
|
+
}
|
|
129
|
+
if (config.lineHeight !== undefined && (
|
|
130
|
+
!Number.isFinite(config.lineHeight) || config.lineHeight < config.fontSize
|
|
131
|
+
)) {
|
|
132
|
+
throw new RangeError("Axis label lineHeight must cover fontSize.");
|
|
133
|
+
}
|
|
134
|
+
if (!["error", "allow"].includes(config.overlap)) {
|
|
135
|
+
throw new Error(`Unsupported axis label overlap policy "${config.overlap}".`);
|
|
136
|
+
}
|
|
93
137
|
}
|
|
94
138
|
|
|
95
139
|
function assertTickCompatibility(ticks, config, operation) {
|
|
@@ -99,6 +143,59 @@ function assertTickCompatibility(ticks, config, operation) {
|
|
|
99
143
|
if (config.mode === "values" && !sameOrderedValues(ticks.values, config.values)) throw new Error(`${operation} conflicts with axis ticks.`);
|
|
100
144
|
}
|
|
101
145
|
|
|
146
|
+
function expandWrappedLabels(resolved, text, config, channel) {
|
|
147
|
+
if (config.maxWidth === undefined) {
|
|
148
|
+
return {
|
|
149
|
+
...resolved,
|
|
150
|
+
text,
|
|
151
|
+
groups: text.map((_, index) => index)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const style = {
|
|
155
|
+
fontSize: config.fontSize,
|
|
156
|
+
fontFamily: config.fontFamily,
|
|
157
|
+
fontWeight: config.fontWeight
|
|
158
|
+
};
|
|
159
|
+
const lineHeight = config.lineHeight ?? config.fontSize * 1.2;
|
|
160
|
+
const expanded = { text: [], x: [], y: [], groups: [] };
|
|
161
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
162
|
+
const lines = wrapText(text[index], {
|
|
163
|
+
maxWidth: config.maxWidth,
|
|
164
|
+
mode: config.wrap,
|
|
165
|
+
style
|
|
166
|
+
});
|
|
167
|
+
validateGeneratedItemLimit(
|
|
168
|
+
expanded.text.length + lines.length,
|
|
169
|
+
"Axis label line count"
|
|
170
|
+
);
|
|
171
|
+
const baseX = Array.isArray(resolved.x) ? resolved.x[index] : resolved.x;
|
|
172
|
+
const baseY = Array.isArray(resolved.y) ? resolved.y[index] : resolved.y;
|
|
173
|
+
for (let line = 0; line < lines.length; line += 1) {
|
|
174
|
+
expanded.text.push(lines[line]);
|
|
175
|
+
expanded.x.push(baseX);
|
|
176
|
+
expanded.y.push(channel === "x"
|
|
177
|
+
? baseY + (resolved.textBaseline === "top" ? line : -line) * lineHeight
|
|
178
|
+
: baseY + (line - (lines.length - 1) / 2) * lineHeight);
|
|
179
|
+
expanded.groups.push(index);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { ...resolved, ...expanded };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function unionLabelBounds(records) {
|
|
186
|
+
const byGroup = new Map();
|
|
187
|
+
for (const { bounds, group } of records) {
|
|
188
|
+
const previous = byGroup.get(group);
|
|
189
|
+
byGroup.set(group, previous === undefined ? bounds : {
|
|
190
|
+
left: Math.min(previous.left, bounds.left),
|
|
191
|
+
right: Math.max(previous.right, bounds.right),
|
|
192
|
+
top: Math.min(previous.top, bounds.top),
|
|
193
|
+
bottom: Math.max(previous.bottom, bounds.bottom)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return [...byGroup.values()];
|
|
197
|
+
}
|
|
198
|
+
|
|
102
199
|
function resolve(program, channel, config) {
|
|
103
200
|
const scale = program.resolvedScales[config.scale];
|
|
104
201
|
const bounds = resolveGraphicBounds(program);
|
|
@@ -132,9 +229,8 @@ function resolve(program, channel, config) {
|
|
|
132
229
|
? formatTransformedTick(scale.type, item)
|
|
133
230
|
: String(item)
|
|
134
231
|
));
|
|
135
|
-
const
|
|
232
|
+
const geometry = {
|
|
136
233
|
values,
|
|
137
|
-
text,
|
|
138
234
|
...resolveAxisLabelGeometry({
|
|
139
235
|
bounds,
|
|
140
236
|
channel,
|
|
@@ -143,8 +239,9 @@ function resolve(program, channel, config) {
|
|
|
143
239
|
offset: config.offset
|
|
144
240
|
})
|
|
145
241
|
};
|
|
242
|
+
const resolved = expandWrappedLabels(geometry, text, config, channel);
|
|
146
243
|
const canvas = findCanvasGraphic(program)?.properties;
|
|
147
|
-
const labelBounds = text.map((value, index) => resolveTextBounds({
|
|
244
|
+
const labelBounds = resolved.text.map((value, index) => resolveTextBounds({
|
|
148
245
|
x: Array.isArray(resolved.x) ? resolved.x[index] : resolved.x,
|
|
149
246
|
y: Array.isArray(resolved.y) ? resolved.y[index] : resolved.y,
|
|
150
247
|
text: value,
|
|
@@ -152,15 +249,20 @@ function resolve(program, channel, config) {
|
|
|
152
249
|
fontFamily: config.fontFamily,
|
|
153
250
|
fontWeight: config.fontWeight,
|
|
154
251
|
textAlign: resolved.textAlign,
|
|
155
|
-
textBaseline: resolved.textBaseline
|
|
252
|
+
textBaseline: resolved.textBaseline,
|
|
253
|
+
rotation: config.rotation
|
|
156
254
|
}));
|
|
157
|
-
const
|
|
255
|
+
const groupedBounds = unionLabelBounds(labelBounds.map((bounds, index) => ({
|
|
256
|
+
bounds,
|
|
257
|
+
group: resolved.groups[index]
|
|
258
|
+
})));
|
|
259
|
+
const orderedBounds = groupedBounds.sort((left, right) => channel === "x"
|
|
158
260
|
? left.left - right.left
|
|
159
261
|
: left.top - right.top);
|
|
160
262
|
if (!canvas || !labelBounds.every(item => textBoundsFitCanvas(item, canvas))) {
|
|
161
263
|
throw new Error(`The ${channel}-axis labels do not fit the Canvas margin.`);
|
|
162
264
|
}
|
|
163
|
-
if (orderedBounds.some((item, index) => index > 0 &&
|
|
265
|
+
if (config.overlap === "error" && orderedBounds.some((item, index) => index > 0 &&
|
|
164
266
|
textBoundsIntersect(orderedBounds[index - 1], item))) {
|
|
165
267
|
throw new Error(`The ${channel}-axis labels overlap each other.`);
|
|
166
268
|
}
|
|
@@ -177,7 +279,7 @@ function resolve(program, channel, config) {
|
|
|
177
279
|
|
|
178
280
|
function makeEdit(channel) {
|
|
179
281
|
const op = channel === "x" ? "editXAxisLabels" : "editYAxisLabels";
|
|
180
|
-
return action({ op, description: `Edit concrete ${channel}-axis labels.` }, function (args = {}) {
|
|
282
|
+
return action({ op, description: `Edit concrete ${channel}-axis labels.` }, withGuideLayoutValidation(function (args = {}) {
|
|
181
283
|
validateOptions(args, op, false);
|
|
182
284
|
const id = `${channel}AxisLabels`;
|
|
183
285
|
if (this.graphicSpec.objects[id]?.type !== "text") throw new Error(`${op} requires existing axis labels.`);
|
|
@@ -192,20 +294,20 @@ function makeEdit(channel) {
|
|
|
192
294
|
const mode = Object.hasOwn(args, "values") || inferredValues !== undefined
|
|
193
295
|
? "values"
|
|
194
296
|
: Object.hasOwn(args, "count") ? "count" : previous.mode;
|
|
195
|
-
const config = {
|
|
297
|
+
const config = normalizeLabelLayout({
|
|
196
298
|
...previous,
|
|
197
299
|
...args,
|
|
198
300
|
...(inferredValues === undefined ? {} : { values: inferredValues }),
|
|
199
301
|
...(explicitMode ? { inferredValues: false } : {}),
|
|
200
302
|
mode
|
|
201
|
-
};
|
|
303
|
+
}, args, op);
|
|
202
304
|
if (mode === "values") delete config.count; else delete config.values;
|
|
203
305
|
validateConfig(channel, config);
|
|
204
306
|
assertTickCompatibility(this.guideConfigs.axis?.[channel]?.ticks, config, op);
|
|
205
307
|
const resolved = resolve(this, channel, config);
|
|
206
308
|
let next = this._withGuideConfig(channel, "labels", config);
|
|
207
|
-
|
|
208
|
-
length: resolved.
|
|
309
|
+
const properties = {
|
|
310
|
+
length: resolved.text.length,
|
|
209
311
|
x: resolved.x,
|
|
210
312
|
y: resolved.y,
|
|
211
313
|
text: resolved.text,
|
|
@@ -215,14 +317,20 @@ function makeEdit(channel) {
|
|
|
215
317
|
fontWeight: config.fontWeight,
|
|
216
318
|
textAlign: resolved.textAlign,
|
|
217
319
|
textBaseline: resolved.textBaseline
|
|
218
|
-
}
|
|
320
|
+
};
|
|
321
|
+
if (config.rotation !== 0 || this.graphicSpec.objects[id].items.some(
|
|
322
|
+
item => Object.hasOwn(item.properties, "rotation")
|
|
323
|
+
)) properties.rotation = config.rotation;
|
|
324
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
325
|
+
next = next.editGraphics({ target: id, property, value });
|
|
326
|
+
}
|
|
219
327
|
const titleConfig = next.guideConfigs.axis?.[channel]?.title;
|
|
220
328
|
if (titleConfig?.inferredOffset === true) {
|
|
221
329
|
const editTitle = channel === "x" ? "editXAxisTitle" : "editYAxisTitle";
|
|
222
330
|
next = next[editTitle]();
|
|
223
331
|
}
|
|
224
332
|
return next;
|
|
225
|
-
});
|
|
333
|
+
}));
|
|
226
334
|
}
|
|
227
335
|
|
|
228
336
|
const editXAxisLabels = makeEdit("x");
|
|
@@ -231,7 +339,7 @@ const editYAxisLabels = makeEdit("y");
|
|
|
231
339
|
function makeCreate(channel) {
|
|
232
340
|
const op = channel === "x" ? "createXAxisLabels" : "createYAxisLabels";
|
|
233
341
|
const edit = channel === "x" ? "editXAxisLabels" : "editYAxisLabels";
|
|
234
|
-
return action({ op, description: `Create concrete ${channel}-axis labels.` }, function (args = {}) {
|
|
342
|
+
return action({ op, description: `Create concrete ${channel}-axis labels.` }, withGuideLayoutValidation(function (args = {}) {
|
|
235
343
|
validateOptions(args, op, true);
|
|
236
344
|
const scale = validateUserId(args.scale ?? channel, "Scale id");
|
|
237
345
|
const guideScale = this.semanticSpec.guides.axis?.[channel]?.scale;
|
|
@@ -242,7 +350,7 @@ function makeCreate(channel) {
|
|
|
242
350
|
const hasValues = Object.hasOwn(args, "values");
|
|
243
351
|
const hasCount = Object.hasOwn(args, "count");
|
|
244
352
|
const mode = hasValues ? "values" : hasCount ? "count" : ticks?.mode ?? "count";
|
|
245
|
-
const config = {
|
|
353
|
+
const config = normalizeLabelLayout({
|
|
246
354
|
scale,
|
|
247
355
|
position: defaultAxisPosition(channel),
|
|
248
356
|
offset: channel === "x" ? 18 : 12,
|
|
@@ -251,10 +359,12 @@ function makeCreate(channel) {
|
|
|
251
359
|
fontSize: DEFAULTS.fontSize,
|
|
252
360
|
fontFamily: DEFAULTS.fontFamily,
|
|
253
361
|
fontWeight: DEFAULTS.fontWeight,
|
|
362
|
+
rotation: DEFAULTS.rotation,
|
|
363
|
+
overlap: DEFAULTS.overlap,
|
|
254
364
|
...args,
|
|
255
365
|
inferredValues: !hasValues && !hasCount && ticks?.inferredValues === true,
|
|
256
366
|
mode
|
|
257
|
-
};
|
|
367
|
+
}, args, op);
|
|
258
368
|
if (mode === "values") config.values ??= ticks?.values; else config.count ??= ticks?.count ?? DEFAULTS.count;
|
|
259
369
|
validateConfig(channel, config);
|
|
260
370
|
assertTickCompatibility(ticks, config, op);
|
|
@@ -267,7 +377,7 @@ function makeCreate(channel) {
|
|
|
267
377
|
...resolvePlotGraphicPlacement(this)
|
|
268
378
|
})
|
|
269
379
|
._withGuideConfig(channel, "labels", config)[edit]();
|
|
270
|
-
});
|
|
380
|
+
}));
|
|
271
381
|
}
|
|
272
382
|
|
|
273
383
|
const createXAxisLabels = makeCreate("x");
|