ggaction 0.0.23 → 0.0.26
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 +65 -0
- package/README.md +2 -2
- package/knowledge/action-cards.json +1 -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 +1 -1
- package/src/accessibility.js +13 -0
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/fitting.js +68 -13
- package/src/actions/charts/beeswarm.js +2 -1
- package/src/actions/charts/ecdf.js +2 -1
- package/src/actions/charts/endpoints.js +25 -16
- package/src/actions/charts/heatmap.js +6 -4
- package/src/actions/charts/missing.js +17 -0
- package/src/actions/charts/radar.js +70 -33
- package/src/actions/charts/raincloud.js +18 -4
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/shared.js +17 -0
- package/src/actions/composition/actions.js +24 -7
- package/src/actions/composition/adjacent.js +90 -0
- package/src/actions/composition/index.js +11 -1
- package/src/actions/composition/layer.js +42 -0
- package/src/actions/composition/layerPlan.js +176 -0
- package/src/actions/composition/targets.js +105 -0
- package/src/actions/data/basicBin2d.js +1 -1
- package/src/actions/data/bin2d.js +2 -2
- package/src/actions/data/derived.js +3 -0
- package/src/actions/data/filter.js +1 -1
- package/src/actions/data/raincloudFilter.js +3 -1
- package/src/actions/encodings/appearance.js +26 -7
- package/src/actions/encodings/channels.js +1 -0
- package/src/actions/encodings/color/continuous.js +37 -34
- package/src/actions/encodings/color/index.js +57 -43
- package/src/actions/encodings/color/policy.js +15 -0
- package/src/actions/encodings/density/resolve.js +3 -2
- package/src/actions/encodings/density.js +2 -2
- package/src/actions/encodings/offset.js +2 -0
- package/src/actions/encodings/pathOrder.js +2 -0
- package/src/actions/encodings/position/areaAlignment.js +42 -0
- package/src/actions/encodings/position/index.js +37 -3
- package/src/actions/encodings/position/policies/line.js +1 -1
- package/src/actions/encodings/ranged.js +4 -2
- package/src/actions/encodings/remove.js +13 -10
- package/src/actions/encodings/ruleAppearance.js +14 -2
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/guides/axes/axes.js +2 -1
- package/src/actions/guides/axes/components.js +5 -0
- package/src/actions/guides/axes/edit.js +1 -188
- package/src/actions/guides/axes/labels.js +2 -537
- package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
- package/src/actions/guides/axes/parallel/policy.js +5 -2
- package/src/actions/guides/axes/parallel/resolve.js +3 -1
- package/src/actions/guides/axes/parallel.js +9 -4
- package/src/actions/guides/axes/tickGroups.js +16 -1
- package/src/actions/guides/axes/ticks.js +33 -24
- package/src/actions/guides/axes/titles.js +1 -389
- package/src/actions/guides/facadeLegend.js +3 -2
- package/src/actions/guides/grids/grid.js +3 -2
- package/src/actions/guides/grids/resolve.js +2 -1
- package/src/actions/guides/guides.js +3 -2
- package/src/actions/guides/legends/automatic.js +28 -0
- package/src/actions/guides/legends/blocks.js +15 -21
- package/src/actions/guides/legends/categorical/actions.js +1 -1
- package/src/actions/guides/legends/categorical/components.js +4 -3
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +9 -14
- package/src/actions/guides/legends/categorical/recipes.js +3 -0
- package/src/actions/guides/legends/categorical/resolve.js +7 -5
- package/src/actions/guides/legends/colorSize.js +51 -14
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/lifecycle.js +2 -2
- package/src/actions/guides/legends/remove.js +18 -4
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/transition.js +15 -6
- package/src/actions/guides/polar/axes/facade.js +4 -2
- package/src/actions/guides/polar/axes/labels.js +6 -1
- package/src/actions/guides/polar/axes/shared.js +4 -4
- package/src/actions/guides/polar/axes/titles.js +13 -2
- package/src/actions/guides/polar/grids.js +6 -3
- package/src/actions/guides/tickValues.js +7 -1
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +9 -2
- package/src/actions/marks/area/materialize.js +1 -1
- package/src/actions/marks/bar/materialize.js +4 -4
- package/src/actions/marks/lifecycle.js +14 -0
- package/src/actions/marks/line/actions.js +3 -0
- package/src/actions/marks/line/materialize.js +4 -2
- package/src/actions/marks/point/edit.js +2 -0
- package/src/actions/marks/point/jitter.js +2 -0
- package/src/actions/marks/point/materialize.js +5 -4
- package/src/actions/marks/point/packing.js +2 -0
- package/src/actions/marks/references.js +5 -3
- package/src/actions/marks/remove.js +16 -0
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +5 -2
- package/src/actions/overlays/index.js +168 -0
- package/src/actions/overlays/snapshot.js +45 -0
- package/src/actions/primitives/editGraphics.js +21 -27
- package/src/actions/primitives/semanticAction.js +4 -5
- package/src/actions/primitives/semanticValidation/guide.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +7 -0
- package/src/actions/scales/consumers/common.js +10 -0
- package/src/actions/scales/consumers/families.js +1 -1
- package/src/actions/scales/preview.js +1 -1
- package/src/actions/selection/actions.js +63 -11
- package/src/actions/titles/actions.js +3 -14
- package/src/actions/titles/resolve.js +23 -10
- package/src/core/action.js +41 -25
- package/src/core/compositionScales.js +12 -0
- package/src/core/compositionState.js +20 -3
- package/src/core/facetGap.js +44 -0
- package/src/core/immutable.js +11 -6
- package/src/core/programState.js +16 -0
- package/src/core/resourceReferences.js +19 -3
- package/src/core/validation.js +7 -0
- package/src/core/vocabulary.js +8 -2
- package/src/grammar/actionTargets.js +24 -0
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bin2d.js +41 -4
- package/src/grammar/colorSizeLegend.js +56 -5
- package/src/grammar/displayLabels.js +10 -0
- package/src/grammar/facetHeaders.js +1 -1
- package/src/grammar/facets/dependencies.js +10 -3
- package/src/grammar/facets/guides.js +2 -1
- package/src/grammar/facets/legendIntent.js +7 -5
- package/src/grammar/legendBlocks.js +1 -1
- package/src/grammar/legendCombinations.js +1 -1
- package/src/grammar/legendGroups.js +1 -1
- package/src/grammar/legendItemsOffset.js +12 -0
- package/src/grammar/legendOverflow.js +25 -0
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/markSelection.js +23 -0
- package/src/grammar/overlay.js +51 -0
- package/src/grammar/parallelCoordinates.js +1 -208
- package/src/grammar/pathCommands.js +8 -2
- package/src/grammar/pathSeries.js +1 -1
- package/src/grammar/plotComposition.js +152 -0
- package/src/grammar/pointShapes.js +18 -0
- package/src/grammar/scales/transformed.js +1 -1
- package/src/grammar/schemas/concreteGraphic.js +8 -1
- package/src/grammar/schemas/graphicBounds.js +8 -1
- package/src/grammar/schemas/graphicTree.js +18 -2
- package/src/grammar/schemas/semanticPath.js +3 -0
- package/src/grammar/strokeStyle.js +14 -8
- package/src/grammar/text.js +72 -0
- package/src/inspection.js +1 -1
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/composition.js +32 -10
- package/src/layout/compositionLegend.js +91 -0
- package/src/layout/facetContentFlow.js +15 -7
- package/src/layout/facetHeaderTitles.js +1 -1
- package/src/layout/facets.js +1 -1
- package/src/layout/labels.js +55 -27
- package/src/layout/legend.js +3 -2
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +7 -6
- package/src/layout/legendLane.js +1 -1
- package/src/layout/legendText.js +1 -1
- package/src/layout/title.js +1 -1
- package/src/materialization/adjacentSource.js +27 -0
- package/src/materialization/composition.js +165 -52
- package/src/materialization/compositionLegends.js +120 -0
- package/src/materialization/compositionScales.js +125 -0
- package/src/materialization/compositionTitle.js +96 -0
- package/src/materialization/facetGuides/edit.js +22 -2
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/owner.js +4 -3
- package/src/materialization/facetGuides/ownerLayout.js +12 -6
- package/src/materialization/facetGuides/placement.js +19 -22
- package/src/materialization/facetGuides/preparation.js +17 -5
- package/src/materialization/facetHeaders.js +5 -1
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/guides/visibility.js +105 -0
- package/src/materialization/legendClip.js +1 -1
- package/src/materialization/legendGroups.js +1 -1
- package/src/materialization/legendItemsOffset.js +68 -0
- package/src/materialization/legendSuppression.js +22 -0
- package/src/materialization/legends.js +3 -2
- package/src/materialization/marks/capabilities.js +4 -0
- package/src/materialization/marks/index.js +5 -1
- package/src/materialization/marks/pathOrder.js +1 -1
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/overlayItems.js +154 -0
- package/src/materialization/planner.js +19 -4
- package/src/materialization/scales/policies/binnedPosition.js +43 -5
- package/src/materialization/scales/policies/series.js +12 -4
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/bar.js +21 -3
- package/src/materialization/selection/items/common.js +18 -5
- package/src/materialization/selection/items/overlay.js +15 -0
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/items/point.js +2 -3
- package/src/materialization/selection/items/tick.js +3 -12
- package/src/materialization/selection/path.js +2 -2
- package/src/materialization/selection/policies/bar.js +2 -0
- package/src/materialization/selection/policies/index.js +35 -1
- package/src/materialization/selection/policies/point.js +2 -0
- package/src/materialization/selection/policies/text.js +17 -0
- package/src/materialization/selection/rows.js +103 -0
- package/src/materialization/selection/state.js +19 -3
- package/src/materialization/selection/styles.js +8 -0
- package/src/materialization/text.js +6 -489
- package/src/materialization/textGeometry.js +503 -0
- package/src/materialization/textLines.js +20 -0
- package/src/persistence/validation.js +116 -25
- package/src/renderers/canvas/index.js +12 -7
- package/src/renderers/canvas/text.js +5 -5
- package/src/renderers/nativeBackend.js +25 -0
- package/src/renderers/pdf.js +3 -3
- package/src/renderers/png.js +4 -3
- package/src/renderers/svgSerialization.js +1 -643
- package/src/scene.js +5 -3
- package/src/selectors/plotComposition.js +115 -0
- package/src/selectors/selectionTargets.js +19 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +4 -2
- package/types/index.d.ts +14 -0
- package/types/inspection.d.ts +34 -0
- package/types/program.d.ts +572 -372
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
assertNoConstantColor,
|
|
27
27
|
withoutConstantColor,
|
|
28
28
|
resolveColorScaleOptions,
|
|
29
|
-
resolveColorReassignmentScaleOptions
|
|
29
|
+
resolveColorReassignmentScaleOptions,
|
|
30
|
+
withColorLegendReassignment
|
|
30
31
|
} from "./policy.js";
|
|
31
32
|
|
|
32
33
|
import { applyTemporalUnit } from "../temporal.js";
|
|
@@ -85,38 +86,40 @@ export function encodeContinuousColor(program, args) {
|
|
|
85
86
|
} else {
|
|
86
87
|
readQuantitativeField(dataset.values, args.field);
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (layer.mark.type === "tick") {
|
|
103
|
-
const config = { ...next.markConfigs[target] };
|
|
104
|
-
delete config.stroke;
|
|
105
|
-
next = next._withMarkConfig(target, config);
|
|
106
|
-
}
|
|
107
|
-
const encoded = aggregate === undefined
|
|
108
|
-
? next
|
|
109
|
-
: next.editSemantic({
|
|
110
|
-
property: `layer[${target}].encoding.color.aggregate`,
|
|
111
|
-
value: aggregate
|
|
89
|
+
return withColorLegendReassignment(baseline, layer, scale, candidate => {
|
|
90
|
+
const prepared = baseline === program ? candidate : releaseDetachedEncodingScale(candidate, scale.id);
|
|
91
|
+
let next = applyTemporalUnit(prepared, target, "color", temporalUnit, layer.encoding?.color)
|
|
92
|
+
.editSemantic({
|
|
93
|
+
property: `layer[${target}].encoding.color.field`,
|
|
94
|
+
value: args.field
|
|
95
|
+
})
|
|
96
|
+
.editSemantic({
|
|
97
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
98
|
+
value: args.fieldType
|
|
99
|
+
})
|
|
100
|
+
.editSemantic({
|
|
101
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
102
|
+
value: scale.id
|
|
112
103
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
if (layer.mark.type === "tick") {
|
|
105
|
+
const config = { ...next.markConfigs[target] };
|
|
106
|
+
delete config.stroke;
|
|
107
|
+
next = next._withMarkConfig(target, config);
|
|
108
|
+
}
|
|
109
|
+
const encoded = aggregate === undefined
|
|
110
|
+
? next
|
|
111
|
+
: next.editSemantic({
|
|
112
|
+
property: `layer[${target}].encoding.color.aggregate`,
|
|
113
|
+
value: aggregate
|
|
114
|
+
});
|
|
115
|
+
const scaled = encoded.setQuantitativeColorScale(scale);
|
|
116
|
+
return applyDetachedScaleRematerialization(applyMaterializationPlan(
|
|
117
|
+
scaled,
|
|
118
|
+
planEncodingRematerialization(scaled, {
|
|
119
|
+
target,
|
|
120
|
+
channel: "color",
|
|
121
|
+
scale: scale.id
|
|
122
|
+
})
|
|
123
|
+
), [layer]);
|
|
124
|
+
});
|
|
122
125
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
2
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
1
3
|
import { findRaincloudParent } from "../../../selectors/raincloud.js";
|
|
2
4
|
import { APPEARANCE_MARK_TYPES } from "../../../core/vocabulary.js";
|
|
3
5
|
import { removeEncoding } from "../remove.js";
|
|
4
6
|
import { validateNonEmptyString } from "../../../core/validation.js";
|
|
5
7
|
import { inferSeriesGroup } from "../shared.js";
|
|
6
|
-
import { action } from "../../../core/action.js";
|
|
8
|
+
import { action, invokeWrappedActionImplementation } from "../../../core/action.js";
|
|
7
9
|
import {
|
|
8
10
|
readNominalField,
|
|
9
11
|
readScaleField,
|
|
@@ -41,7 +43,8 @@ import {
|
|
|
41
43
|
COLOR_ENCODING_OPTIONS,
|
|
42
44
|
resolveColorLayout,
|
|
43
45
|
resolveColorScaleOptions,
|
|
44
|
-
resolveColorReassignmentScaleOptions
|
|
46
|
+
resolveColorReassignmentScaleOptions,
|
|
47
|
+
withColorLegendReassignment
|
|
45
48
|
} from "./policy.js";
|
|
46
49
|
|
|
47
50
|
import { applyTemporalUnit } from "../temporal.js";
|
|
@@ -53,7 +56,7 @@ const encodeColor = /* @__PURE__ */ action(
|
|
|
53
56
|
op: "encodeColor",
|
|
54
57
|
description: "Assign constant or field-driven graphical color."
|
|
55
58
|
},
|
|
56
|
-
function (args = {}) {
|
|
59
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
57
60
|
validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
|
|
58
61
|
const hasValue = Object.hasOwn(args, "value");
|
|
59
62
|
if (hasValue === Object.hasOwn(args, "field")) {
|
|
@@ -64,6 +67,12 @@ const encodeColor = /* @__PURE__ */ action(
|
|
|
64
67
|
const { target, ...color } = args;
|
|
65
68
|
return this.editRaincloudPlot({ target, color });
|
|
66
69
|
}
|
|
70
|
+
const overlayTarget = findLayer(this, args.target ?? this.context.currentMark);
|
|
71
|
+
if (overlayTarget?.derivedBindings?.overlay?.inheritColor) {
|
|
72
|
+
const next = this.editSemantic({ property: `layer[${overlayTarget.id}].derivedBindings.overlay`,
|
|
73
|
+
value: { ...overlayTarget.derivedBindings.overlay, inheritColor: false } });
|
|
74
|
+
return invokeWrappedActionImplementation(encodeColor, next, args);
|
|
75
|
+
}
|
|
67
76
|
if (hasValue) {
|
|
68
77
|
if (COLOR_ENCODING_OPTIONS.some(key => !["target", "value"].includes(key) && args[key] !== undefined)) {
|
|
69
78
|
throw new Error("Constant color accepts only value and target.");
|
|
@@ -160,50 +169,55 @@ const encodeColor = /* @__PURE__ */ action(
|
|
|
160
169
|
readNominalField(dataset.values, args.field);
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.editSemantic({
|
|
172
|
-
property: `layer[${target}].encoding.color.fieldType`,
|
|
173
|
-
value: fieldType
|
|
172
|
+
return withColorLegendReassignment(baseline, layer, scale, candidate => {
|
|
173
|
+
const prepared = baseline === this ? candidate : releaseDetachedEncodingScale(candidate, scale.id);
|
|
174
|
+
let next = layer.mark.type === "bar" || createsCenterGroup
|
|
175
|
+
? inferSeriesGroup(prepared, layer, args.field, "color")
|
|
176
|
+
: prepared;
|
|
177
|
+
next = applyTemporalUnit(next, target, "color", undefined, layer.encoding?.color).editSemantic({
|
|
178
|
+
property: `layer[${target}].encoding.color.field`,
|
|
179
|
+
value: args.field
|
|
174
180
|
})
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
.editSemantic({
|
|
182
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
183
|
+
value: fieldType
|
|
184
|
+
})
|
|
185
|
+
.editSemantic({
|
|
186
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
187
|
+
value: scale.id
|
|
188
|
+
});
|
|
189
|
+
if (layout !== undefined && (layer.mark.type === "arc" || horizonTransform !== undefined)) {
|
|
190
|
+
next = next.editSemantic({
|
|
191
|
+
property: `layer[${target}].encoding.color.layout`,
|
|
192
|
+
value: layout
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (layer.encoding?.color?.aggregate !== undefined) {
|
|
196
|
+
next = next.editSemantic({ property: `layer[${target}].encoding.color.aggregate`, remove: true });
|
|
197
|
+
}
|
|
198
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
199
|
+
reassignment: layer.encoding?.color?.scale === scale.id,
|
|
200
|
+
allowTypeChange: true
|
|
178
201
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
next = applyColorLayoutCompanion(next, {
|
|
203
|
+
target,
|
|
204
|
+
layer,
|
|
205
|
+
layout,
|
|
206
|
+
scale,
|
|
207
|
+
field: args.field,
|
|
208
|
+
horizon: horizonTransform !== undefined
|
|
183
209
|
});
|
|
184
|
-
}
|
|
185
|
-
next = applyEncodingScale(next, scale, requestedScale, {
|
|
186
|
-
reassignment: layer.encoding?.color?.scale === scale.id,
|
|
187
|
-
allowTypeChange: true
|
|
188
|
-
});
|
|
189
|
-
next = applyColorLayoutCompanion(next, {
|
|
190
|
-
target,
|
|
191
|
-
layer,
|
|
192
|
-
layout,
|
|
193
|
-
scale,
|
|
194
|
-
field: args.field,
|
|
195
|
-
horizon: horizonTransform !== undefined
|
|
196
|
-
});
|
|
197
210
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
return applyDetachedScaleRematerialization(applyMaterializationPlan(
|
|
212
|
+
next,
|
|
213
|
+
planEncodingRematerialization(next, {
|
|
214
|
+
target,
|
|
215
|
+
channel: "color",
|
|
216
|
+
scale: scale.id
|
|
217
|
+
})
|
|
218
|
+
), [layer]);
|
|
219
|
+
});
|
|
220
|
+
})
|
|
207
221
|
);
|
|
208
222
|
|
|
209
223
|
export function registerColorEncodingAction(ProgramClass) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
2
|
+
import { planColorLegendTransitions, applyColorLegendTransition } from "../../guides/legends/transition.js";
|
|
1
3
|
import {
|
|
2
4
|
BAR_GRAINS,
|
|
3
5
|
inferBarColorLayout,
|
|
@@ -146,3 +148,16 @@ export function resolveColorLayout(layer, requested, barGrain, field) {
|
|
|
146
148
|
}
|
|
147
149
|
return layout;
|
|
148
150
|
}
|
|
151
|
+
|
|
152
|
+
// A new scale identity can also change the existing target's legend family.
|
|
153
|
+
// Keep the same transition validation/style policy used by in-place scale edits.
|
|
154
|
+
export function withColorLegendReassignment(program, layer, scale, apply) {
|
|
155
|
+
const previous = findSemanticScale(program, layer.encoding?.color?.scale);
|
|
156
|
+
if (previous === undefined || previous.id === scale.id) return apply(program);
|
|
157
|
+
const plans = planColorLegendTransitions(program, previous, scale.type, { channel: "color", target: layer.id });
|
|
158
|
+
let next = program;
|
|
159
|
+
for (const plan of plans) next = next.removeLegend({ target: layer.id, channels: ["color"] });
|
|
160
|
+
next = apply(next);
|
|
161
|
+
for (const plan of plans) next = applyColorLegendTransition(next, plan);
|
|
162
|
+
return next;
|
|
163
|
+
}
|
|
@@ -3,11 +3,12 @@ import { isPlainObject } from "../../../core/immutable.js";
|
|
|
3
3
|
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
export function resolveDensityScaleOptions(value, defaults, label) {
|
|
6
|
+
export function resolveDensityScaleOptions(value, defaults, label, program) {
|
|
7
7
|
if (value !== undefined && !isPlainObject(value)) {
|
|
8
8
|
throw new TypeError(`${label} must be a plain object.`);
|
|
9
9
|
}
|
|
10
|
-
const
|
|
10
|
+
const existing = program && value?.id ? findSemanticScale(program, value.id) : undefined;
|
|
11
|
+
const scale = { ...(existing ?? defaults), ...(value ?? {}) };
|
|
11
12
|
if (value?.type === "log" && value.zero === undefined) delete scale.zero;
|
|
12
13
|
return scale;
|
|
13
14
|
}
|
|
@@ -156,7 +156,7 @@ const encodeDensity = /* @__PURE__ */ action(
|
|
|
156
156
|
const valueScale = resolveDensityScaleOptions(
|
|
157
157
|
args.valueScale,
|
|
158
158
|
{ nice: false, zero: false },
|
|
159
|
-
"Density valueScale"
|
|
159
|
+
"Density valueScale", this
|
|
160
160
|
);
|
|
161
161
|
const placement = args.placement === undefined
|
|
162
162
|
? undefined
|
|
@@ -174,7 +174,7 @@ const encodeDensity = /* @__PURE__ */ action(
|
|
|
174
174
|
? resolveDensityScaleOptions(
|
|
175
175
|
args.densityScale,
|
|
176
176
|
{ nice: true, zero: true },
|
|
177
|
-
"Density densityScale"
|
|
177
|
+
"Density densityScale", this
|
|
178
178
|
)
|
|
179
179
|
: undefined;
|
|
180
180
|
const placementScale = placement === undefined
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertIndependentOverlayGeometry } from "../../grammar/overlay.js";
|
|
1
2
|
import { inferSeriesGroup } from "./shared.js";
|
|
2
3
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
3
4
|
import { action } from "../../core/action.js";
|
|
@@ -41,6 +42,7 @@ function createOffsetEncoding(channel) {
|
|
|
41
42
|
["bar", "point", "rule"],
|
|
42
43
|
"offset-compatible mark"
|
|
43
44
|
);
|
|
45
|
+
assertIndependentOverlayGeometry(layer, operation);
|
|
44
46
|
const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
|
|
45
47
|
|
|
46
48
|
if (layer.mark.type === "bar") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertIndependentOverlayGeometry } from "../../grammar/overlay.js";
|
|
1
2
|
import { closedAction } from "../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
4
|
import { validateNonEmptyString } from "../../core/validation.js";
|
|
@@ -86,6 +87,7 @@ const encodePathOrder = /* @__PURE__ */ closedAction(
|
|
|
86
87
|
}
|
|
87
88
|
const order = validatePathOrderDirection(args.order ?? "ascending");
|
|
88
89
|
const layer = resolvePathOrderLayer(this, args.target);
|
|
90
|
+
assertIndependentOverlayGeometry(layer, "encodePathOrder");
|
|
89
91
|
const dataset = assertPathOrderCompatible(this, layer, "encodePathOrder");
|
|
90
92
|
readQuantitativeField(dataset.values, field);
|
|
91
93
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { resolveTarget } from '../shared.js';
|
|
2
|
+
import { findDataset, requireDataset } from '../../../selectors/datasets.js';
|
|
3
|
+
|
|
4
|
+
// Completed-key data is tied to one independent coordinate. Its generated
|
|
5
|
+
// rows are not observations and must never be reused as a different measure.
|
|
6
|
+
export function rebindAlignedAreaPosition(program, channel, args) {
|
|
7
|
+
if (!['x', 'y'].includes(channel) || args.field === undefined ||
|
|
8
|
+
!program.semanticSpec.layers.some(layer => layer.mark.type === 'area')) return program;
|
|
9
|
+
const { layer } = resolveTarget(program, args.target, ['point', 'line', 'bar', 'area', 'rule', 'rect', 'text', 'tick']);
|
|
10
|
+
if (layer.mark.type !== 'area' || layer.encoding?.[`${channel}2`] ||
|
|
11
|
+
!layer.encoding?.[channel === 'x' ? 'y2' : 'x2']) return program;
|
|
12
|
+
const dataset = findDataset(program, layer.data);
|
|
13
|
+
const complete = dataset?.transform?.length === 1 && dataset.transform[0].type === 'complete' ? dataset.transform[0] : undefined;
|
|
14
|
+
if (!complete || complete.key !== layer.encoding[channel]?.field || args.field === complete.key) return program;
|
|
15
|
+
if (!layer.layout?.mode || layer.layout.mode === 'overlay') {
|
|
16
|
+
return program.rebindLayerData({ id: layer.id, data: dataset.source });
|
|
17
|
+
}
|
|
18
|
+
const measureChannel = channel === 'x' ? 'y' : 'x';
|
|
19
|
+
const measures = [layer.encoding[measureChannel]?.field, layer.encoding[`${measureChannel}2`]?.field].filter(Boolean);
|
|
20
|
+
if (measures.includes(args.field)) {
|
|
21
|
+
throw new Error('A stacked Area cannot use its measurement as the completed position key: generated rows need separate key and measurement fields. Choose another position field or Overlay layout.');
|
|
22
|
+
}
|
|
23
|
+
if (complete.groupBy.includes(args.field)) {
|
|
24
|
+
throw new Error('A stacked Area needs at least two positions within each series; its grouping field cannot also be the position key.');
|
|
25
|
+
}
|
|
26
|
+
const source = requireDataset(program, dataset.source);
|
|
27
|
+
const unique = new Set();
|
|
28
|
+
for (const row of source.values) {
|
|
29
|
+
const key = JSON.stringify([...complete.groupBy, args.field].map(field => row[field]));
|
|
30
|
+
if (unique.has(key)) throw new Error('The new Area position has duplicate rows within a series. Reaggregate the original observations at that grain before stacking, or use Overlay layout.');
|
|
31
|
+
unique.add(key);
|
|
32
|
+
}
|
|
33
|
+
const stem = `${layer.id}AlignedPosition`, ids = new Set(program.semanticSpec.datasets.map(data => data.id));
|
|
34
|
+
let id = stem;
|
|
35
|
+
for (let index = 2; ids.has(id); index++) id = `${stem}${index}`;
|
|
36
|
+
const fill = Object.fromEntries(Object.entries(complete.fill).filter(([field]) => field !== args.field));
|
|
37
|
+
let next = program.createCompleteData({ id, source: dataset.source, key: args.field, groupBy: complete.groupBy, fill });
|
|
38
|
+
next = next.rebindLayerData({ id: layer.id, data: id });
|
|
39
|
+
return next._withMarkConfig(layer.id, { ...next.markConfigs[layer.id], alignedPositionData: [
|
|
40
|
+
id, ...(next.markConfigs[layer.id]?.alignedPositionData ?? [])
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { rebindAlignedAreaPosition } from './areaAlignment.js';
|
|
2
|
+
import { assertSharedPositionMeaning } from '../../../selectors/plotComposition.js';
|
|
1
3
|
import { stackLayoutMode } from "../seriesLayout.js";
|
|
2
4
|
import { resolveBarGrain } from "../../../grammar/bars/policy.js";
|
|
3
5
|
import { action } from "../../../core/action.js";
|
|
@@ -9,12 +11,31 @@ import { findLayer } from "../../../selectors/layers.js";
|
|
|
9
11
|
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
10
12
|
import { applyPositionSemantics } from "./apply.js";
|
|
11
13
|
import {
|
|
14
|
+
resolveTarget,
|
|
12
15
|
applyEncodingScale,
|
|
13
16
|
applyDetachedScaleRematerialization,
|
|
14
17
|
rebindPositionGuides
|
|
15
18
|
} from "../shared.js";
|
|
16
19
|
|
|
17
20
|
function encodePosition(program, channel, args, operation) {
|
|
21
|
+
program = rebindAlignedAreaPosition(program, channel, args);
|
|
22
|
+
if (channel === 'theta' && typeof program.encodeChannels === 'function') {
|
|
23
|
+
const { layer } = resolveTarget(program, args.target, ['arc', 'line', 'point']);
|
|
24
|
+
const old = layer.encoding?.theta, color = layer.encoding?.color;
|
|
25
|
+
const scale = findSemanticScale(program, layer.encoding?.radius?.scale);
|
|
26
|
+
if (layer.mark.type === 'arc' && scale?.radialMapping && old?.field &&
|
|
27
|
+
args.field !== old.field && color?.field === old.field &&
|
|
28
|
+
['nominal', 'ordinal'].includes(args.fieldType ?? old.fieldType)) {
|
|
29
|
+
const { target: _target, coordinate, ...theta } = args;
|
|
30
|
+
if (coordinate !== undefined && coordinate !== layer.coordinate) {
|
|
31
|
+
throw new Error('encodeTheta cannot move a measured polar mark to a different coordinate.');
|
|
32
|
+
}
|
|
33
|
+
return program.editSemantic({ property: `layer[${layer.id}].encoding.color.field`, value: args.field })
|
|
34
|
+
.encodeChannels({ target: layer.id, channels: {
|
|
35
|
+
theta, color: { field: args.field, fieldType: args.fieldType ?? old.fieldType }
|
|
36
|
+
} });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
const {
|
|
19
40
|
target,
|
|
20
41
|
layer,
|
|
@@ -34,9 +55,22 @@ function encodePosition(program, channel, args, operation) {
|
|
|
34
55
|
companion,
|
|
35
56
|
clearRadialMapping
|
|
36
57
|
} = resolvePositionEncoding(program, channel, args, operation);
|
|
58
|
+
if (layer.derivedBindings?.overlay) throw new Error("Overlay position follows its source; edit the source or detach the overlay first.");
|
|
37
59
|
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
if (args.scale?.id === undefined) assertSharedPositionMeaning(program, layer, channel, {
|
|
61
|
+
...(hasField ? { field } : { datum }), fieldType, temporalUnit,
|
|
62
|
+
scale: scale.id, aggregate, bin, stack
|
|
63
|
+
});
|
|
64
|
+
let next = program;
|
|
65
|
+
if (previous?.field !== undefined && previous.field !== field && ['theta', 'radius'].includes(channel)) {
|
|
66
|
+
for (const [id, config] of Object.entries(next.guideConfigs.axis ?? {})) {
|
|
67
|
+
for (const part of ['ticks', 'labels']) {
|
|
68
|
+
if (config[part]?.scale !== previous.scale || config[part].mode !== 'values') continue;
|
|
69
|
+
next = next._withGuideConfig(id, part, { ...config[part], inferredValues: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
next = next.createCoordinate({
|
|
40
74
|
id: coordinate.id,
|
|
41
75
|
type: coordinate.type,
|
|
42
76
|
layers: [target]
|
|
@@ -70,7 +104,7 @@ function encodePosition(program, channel, args, operation) {
|
|
|
70
104
|
}
|
|
71
105
|
next = applyEncodingScale(next, scale, requestedScale, {
|
|
72
106
|
reassignment: previous?.scale === scale.id,
|
|
73
|
-
allowTypeChange: layer.mark.type === "area"
|
|
107
|
+
allowTypeChange: layer.mark.type === "area" || previous?.field === field && previous?.fieldType === fieldType
|
|
74
108
|
});
|
|
75
109
|
if (["bar", "area"].includes(layer.mark.type) && Object.hasOwn(args, "stack") && args.stack !== undefined) {
|
|
76
110
|
next = next.editSemantic({ property: `layer[${target}].layout.mode`, value: stackLayoutMode(args.stack) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{validateAggregate as
|
|
1
|
+
import{validateAggregate as L,validateAggregateFieldType as P}from"../../../../grammar/aggregate.js";import{emptyPositionPolicy as g,resolveBin as k}from"./common.js";import{findSemanticScale as A}from"../../../../selectors/scales.js";import{validateCategoricalColorConsumer as C}from"../../../../grammar/scales/colorConsumers.js";import{validateLineMissingScope as B}from"../../../../grammar/lineMissing.js";import{isAggregateLinePosition as v,isHorizontalLine as D}from"../../../../grammar/lineSeries.js";function f(r,e){if(r.stack!==void 0)throw new Error(`Line ${e} encoding does not support stack.`)}function q(r,e){if(r.bin!==void 0)throw new Error(`Line ${e} encoding does not support bin.`)}function S({program:r,layer:e,dataset:a,channel:t,args:n,fieldType:i,orientationNormalized:l=!1}){B({...e,encoding:{...e.encoding,[t]:{...n,fieldType:i}}});for(const o of["color","strokeDash"]){const d=e.encoding?.[o],c=d===void 0?void 0:A(r,d.scale);c!==void 0&&C({...e,encoding:{...e.encoding,[t]:{...e.encoding[t],...n,fieldType:i}}},d,c,o)}const x=["theta","radius"].includes(t),u=r.markConfigs[e.id]??{};if(x){if(u.curve!==void 0&&u.curve!=="linear")throw new Error('Polar line position currently requires curve "linear".');if(q(n,t),f(n,t),n.aggregate!==void 0)throw new Error(`Line ${t} encoding does not support aggregate.`);return g()}if(u.closed===!0)throw new Error("Line closed requires theta/radius Polar position encodings.");const s={...e,encoding:{...e.encoding,[t]:{...n,fieldType:i}}};if(s.encoding.x?.aggregate!==void 0&&s.encoding.y?.aggregate!==void 0)throw new Error("A Line requires one independent position; both axes cannot aggregate.");if(!l&&D(s))return S({program:r,dataset:a,layer:{...e,encoding:{...e.encoding,x:e.encoding?.y,y:e.encoding?.x}},channel:t==="x"?"y":"x",args:n,fieldType:i,orientationNormalized:!0});const w=a.transform?.some(o=>o.type==="regression"),m=a.transform?.some(o=>o.type==="interval"),E=a.transform?.some(o=>o.type==="window"&&o.operations?.some(d=>d.as===n.field));if(t==="x"){if(f(n,"x"),n.aggregate!==void 0)throw new Error("Line x encoding does not support aggregate.");if(n.bin!==void 0){if(i!=="quantitative")throw new Error("Binned line x encoding requires a quantitative field.");if(e.encoding?.y!==void 0&&e.encoding.y.aggregate===void 0)throw new Error("Binned line x encoding requires an aggregate y encoding.");return{bin:k(n.bin),aggregate:void 0,stack:void 0}}if(["nominal","ordinal"].includes(i)){if(e.encoding?.y!==void 0&&e.encoding.y.aggregate===void 0&&e.encoding.y.fieldType!=="quantitative")throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");return g()}const o=e.encoding?.y?.aggregate===void 0&&["quantitative","temporal"].includes(e.encoding?.y?.fieldType)&&i==="quantitative",d=e.encoding?.y===void 0&&i==="quantitative",c=e.encoding?.y?.aggregate!==void 0&&v({fieldType:i});if(i!=="temporal"&&!((w||m||o||d||c)&&i==="quantitative"))throw new Error("Line x encoding requires a categorical or temporal field, or a compatible quantitative field.");return g()}if(q(n,"y"),f(n,"y"),e.encoding?.x?.bin!==void 0&&n.aggregate===void 0)throw new Error("Binned line x encoding requires an aggregate y encoding.");if(["nominal","ordinal"].includes(e.encoding?.x?.fieldType)&&n.aggregate===void 0&&i!=="quantitative")throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");const h=i==="quantitative"&&["quantitative","temporal","nominal","ordinal"].includes(e.encoding?.x?.fieldType)&&e.encoding.x.bin===void 0,b=n.aggregate===void 0&&(i==="temporal"||i==="quantitative"&&(e.encoding?.x===void 0||h));if(m||E&&n.aggregate===void 0||b){if(!["quantitative","temporal"].includes(i))throw new Error("Direct line y encoding requires a quantitative or temporal field.");if(n.aggregate!==void 0)throw new Error("Direct line y encoding does not support aggregate.");return g()}if(w){if(i!=="quantitative")throw new Error("Regression line y encoding requires a quantitative field.");if(n.aggregate!==void 0)throw new Error("Regression line y encoding does not support aggregate.");return g()}if(e.encoding?.x!==void 0&&!v(e.encoding.x))throw new Error("Aggregate line y encoding requires a quantitative, temporal, or categorical x encoding.");const p=L(n.aggregate);return P(p,i),{bin:void 0,aggregate:p,stack:void 0}}export{S as resolveLinePositionPolicy};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertIndependentOverlayGeometry } from "../../grammar/overlay.js";
|
|
1
2
|
import { readAreaEndpoint, validateAreaEndpointPair, normalizeAreaBound } from "../../grammar/areaEndpoints.js";
|
|
2
3
|
import { action } from "../../core/action.js";
|
|
3
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
@@ -83,11 +84,11 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
|
|
|
83
84
|
if (!rule && !area && !rect && (!hasField || hasDatum)) {
|
|
84
85
|
throw new Error(`${operation} requires a field for a ranged mark.`);
|
|
85
86
|
}
|
|
86
|
-
if (rule && args.fieldType === undefined) {
|
|
87
|
+
if (rule && hasField && args.fieldType === undefined) {
|
|
87
88
|
throw new Error(`${operation} requires fieldType for a rule mark.`);
|
|
88
89
|
}
|
|
89
90
|
const fieldType = rule
|
|
90
|
-
? validateSemanticFieldType(args.fieldType)
|
|
91
|
+
? validateSemanticFieldType(args.fieldType ?? "quantitative")
|
|
91
92
|
: layer.mark.type === "rect"
|
|
92
93
|
? args.fieldType ?? primary.fieldType
|
|
93
94
|
: args.fieldType ?? "quantitative";
|
|
@@ -306,6 +307,7 @@ const encodeGroup = /* @__PURE__ */ action(
|
|
|
306
307
|
["line", "area", "bar"],
|
|
307
308
|
"series mark"
|
|
308
309
|
);
|
|
310
|
+
assertIndependentOverlayGeometry(layer, "encodeGroup");
|
|
309
311
|
if ((args.fieldType ?? "nominal") !== "nominal") {
|
|
310
312
|
throw new Error("encodeGroup requires a nominal field.");
|
|
311
313
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {legendBlockChannels} from "../../grammar/legendBlocks.js";
|
|
1
2
|
import { cartesianAxisOwners, axisTargetArgs } from "../../selectors/axes.js";
|
|
2
3
|
import { closedAction } from "../../core/action.js";
|
|
3
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
@@ -41,13 +42,7 @@ const REMOVE_AXIS = Object.freeze({
|
|
|
41
42
|
theta: "removeThetaAxis",
|
|
42
43
|
radius: "removeRadialAxis"
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
-
color: Object.freeze(["gradient", "interval", "colorSize"]),
|
|
46
|
-
stroke: Object.freeze(["strokeGradient", "strokeInterval"]),
|
|
47
|
-
size: Object.freeze(["size", "colorSize"]),
|
|
48
|
-
opacity: Object.freeze(["opacity"]),
|
|
49
|
-
strokeWidth: Object.freeze(["strokeWidth"])
|
|
50
|
-
});
|
|
45
|
+
|
|
51
46
|
|
|
52
47
|
function resolveTarget(program, requested, channel) {
|
|
53
48
|
const candidates = program.semanticSpec.layers.filter(
|
|
@@ -190,9 +185,10 @@ function reconcileCategoricalLegend(program, target, channels) {
|
|
|
190
185
|
|
|
191
186
|
function cleanupLegends(program, target, channels) {
|
|
192
187
|
let next = reconcileCategoricalLegend(program, target, channels);
|
|
193
|
-
const kinds =
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
const kinds = Object.entries(next.guideConfigs.legend ?? {})
|
|
189
|
+
.filter(([kind, config]) => !["series", "color", "stroke"].includes(kind) &&
|
|
190
|
+
config.target === target && legendBlockChannels(kind, config).some(channel => channels.includes(channel)))
|
|
191
|
+
.map(([kind]) => kind);
|
|
196
192
|
return kinds.length === 0 ? next : removeLegendKinds(next, kinds);
|
|
197
193
|
}
|
|
198
194
|
|
|
@@ -221,6 +217,9 @@ export const removeEncoding = /* @__PURE__ */ closedAction(
|
|
|
221
217
|
);
|
|
222
218
|
}
|
|
223
219
|
const layer = resolveTarget(this, args.target, args.channel);
|
|
220
|
+
if (layer.derivedBindings?.overlay && ["x", "y", "x2", "y2", "xOffset", "yOffset", "group", "pathOrder"].includes(args.channel)) {
|
|
221
|
+
throw new Error("Overlay geometry follows its source; edit the source or detach the overlay first.");
|
|
222
|
+
}
|
|
224
223
|
if (args.channel === "group" && layer.layout?.mode !== undefined && layer.layout.mode !== "overlay") throw new Error("Remove active series layout before removing its group.");
|
|
225
224
|
if (args.channel === "theta" && layer.mark.type === "arc" &&
|
|
226
225
|
["count", "sum"].includes(layer.encoding?.radius?.aggregate)) {
|
|
@@ -254,6 +253,10 @@ export const removeEncoding = /* @__PURE__ */ closedAction(
|
|
|
254
253
|
.filter(item => item.scale !== undefined);
|
|
255
254
|
|
|
256
255
|
let next = this;
|
|
256
|
+
if (args.channel === "color" && layer.derivedBindings?.overlay?.inheritColor) {
|
|
257
|
+
next = next.editSemantic({ property: `layer[${layer.id}].derivedBindings.overlay`,
|
|
258
|
+
value: { ...layer.derivedBindings.overlay, inheritColor: false } });
|
|
259
|
+
}
|
|
257
260
|
for (const channel of channels) {
|
|
258
261
|
next = next.editSemantic({
|
|
259
262
|
property: `layer[${layer.id}].encoding.${channel}`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APPEARANCE_MARK_TYPES } from "../../core/vocabulary.js";
|
|
1
|
+
import { APPEARANCE_MARK_TYPES, FIXED_APPEARANCE_MARK_TYPES } from "../../core/vocabulary.js";
|
|
2
2
|
import { action } from "../../core/action.js";
|
|
3
3
|
import { validateRuleStrokeWidth } from "../../grammar/ruleAppearance.js";
|
|
4
4
|
import { readQuantitativeField } from "../../grammar/scales/index.js";
|
|
@@ -39,8 +39,20 @@ const encodeStrokeWidth = /* @__PURE__ */ action(
|
|
|
39
39
|
throw new Error("Constant stroke width does not accept fieldType or scale.");
|
|
40
40
|
}
|
|
41
41
|
validateRuleStrokeWidth(args.value);
|
|
42
|
-
const { id, layer } = resolveTarget(this, args.target,
|
|
42
|
+
const { id, layer } = resolveTarget(this, args.target, FIXED_APPEARANCE_MARK_TYPES.encodeStrokeWidth, "stroked mark");
|
|
43
43
|
assertEncodingSelectionCompatibility(this, id, ["strokeWidth"]);
|
|
44
|
+
if (!APPEARANCE_MARK_TYPES.encodeStrokeWidth.includes(layer.mark.type)) {
|
|
45
|
+
const config = { ...this.markConfigs[id] };
|
|
46
|
+
const appearance = layer.mark.type === "bar" ? { ...config.barAppearance } : config;
|
|
47
|
+
// Width authoring enables an absent outline; existing constant or
|
|
48
|
+
// field-driven strokes keep their color and scale ownership.
|
|
49
|
+
const needsStroke = !layer.encoding?.stroke && (appearance.stroke === false ||
|
|
50
|
+
(["point", "area"].includes(layer.mark.type) && appearance.stroke === undefined));
|
|
51
|
+
if (needsStroke) appearance.stroke = "#000000";
|
|
52
|
+
appearance.strokeWidth = args.value;
|
|
53
|
+
if (layer.mark.type === "bar") config.barAppearance = appearance;
|
|
54
|
+
return rematerializeEncoding(this._withMarkConfig(id, config), id, "strokeWidth", undefined, layer);
|
|
55
|
+
}
|
|
44
56
|
let next = this.guideConfigs.legend?.strokeWidth?.target === id
|
|
45
57
|
? this.removeLegend({ target: id, channels: ["strokeWidth"] })
|
|
46
58
|
: this;
|