ggaction 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isDiscretizedColorScaleType,
|
|
5
5
|
isDiscretePositionScaleType,
|
|
6
6
|
isTransformedScaleType,
|
|
7
|
+
normalizeSizeScaleDefinition,
|
|
7
8
|
normalizeScaleDefinition,
|
|
8
9
|
SCALE_ROLES,
|
|
9
10
|
validateColorRange,
|
|
@@ -26,11 +27,20 @@ import {
|
|
|
26
27
|
validateRadialRange,
|
|
27
28
|
validateThetaRange
|
|
28
29
|
} from "../../grammar/polar.js";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
normalizePositionScaleChannel,
|
|
32
|
+
OFFSET_POSITION_CHANNELS
|
|
33
|
+
} from "../../core/vocabulary.js";
|
|
34
|
+
import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
|
|
35
|
+
import { resolveRequestedOffsetPolicy } from
|
|
36
|
+
"../../materialization/scales/policies/offset.js";
|
|
30
37
|
|
|
31
38
|
export function resolveScaleConsumerChannel(consumers, id) {
|
|
32
39
|
const channels = new Set(
|
|
33
|
-
consumers.map(consumer =>
|
|
40
|
+
consumers.map(consumer => {
|
|
41
|
+
const channel = normalizePositionScaleChannel(consumer.channel);
|
|
42
|
+
return channel === "stroke" ? "color" : channel;
|
|
43
|
+
})
|
|
34
44
|
);
|
|
35
45
|
if (channels.size > 1) {
|
|
36
46
|
throw new Error(`Scale "${id}" cannot be shared across channels.`);
|
|
@@ -40,6 +50,7 @@ export function resolveScaleConsumerChannel(consumers, id) {
|
|
|
40
50
|
|
|
41
51
|
function validateRangeForChannel(scale, channel, value) {
|
|
42
52
|
if (value === "auto") return value;
|
|
53
|
+
if (channel === "size") return validateSizeRange(value);
|
|
43
54
|
if (channel === "theta") return validateThetaRange(value);
|
|
44
55
|
if (channel === "radius") return validateRadialRange(value);
|
|
45
56
|
if (scale.type === "sequential") {
|
|
@@ -49,7 +60,7 @@ function validateRangeForChannel(scale, channel, value) {
|
|
|
49
60
|
return validateDiscretizedColorRange(value);
|
|
50
61
|
}
|
|
51
62
|
if (scale.type !== "ordinal") return validateScaleRange(value);
|
|
52
|
-
if (
|
|
63
|
+
if (["color", "stroke"].includes(channel)) return validateColorRange(value);
|
|
53
64
|
if (channel === "shape") return validateShapeRange(value);
|
|
54
65
|
if (channel === "strokeDash") return validateStrokeDashRange(value);
|
|
55
66
|
if (channel === "size") return validateSizeRange(value);
|
|
@@ -66,7 +77,23 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
66
77
|
throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
|
|
67
78
|
}
|
|
68
79
|
for (const consumer of consumers) {
|
|
69
|
-
validateContinuousColorConsumer(
|
|
80
|
+
validateContinuousColorConsumer(
|
|
81
|
+
consumer.layer,
|
|
82
|
+
consumer.encoding,
|
|
83
|
+
{ type: nextType },
|
|
84
|
+
{ channel: consumer.channel }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (nextType === "ordinal" && channel === "color") {
|
|
90
|
+
if (consumers.some(consumer =>
|
|
91
|
+
!["color", "stroke"].includes(consumer.channel) ||
|
|
92
|
+
!["nominal", "ordinal"].includes(consumer.encoding.fieldType)
|
|
93
|
+
)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Scale "${scale.id}" has a consumer incompatible with type "ordinal".`
|
|
96
|
+
);
|
|
70
97
|
}
|
|
71
98
|
return;
|
|
72
99
|
}
|
|
@@ -127,23 +154,65 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
|
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
|
|
130
|
-
function normalizeDefinition(scale, channel, consumers, patch) {
|
|
157
|
+
function normalizeDefinition(program, scale, channel, consumers, patch) {
|
|
131
158
|
if (scale.radialMapping !== undefined && Object.hasOwn(patch, "radialMapping") &&
|
|
132
159
|
patch.radialMapping === undefined && consumers.some(consumer => consumer.encoding.aggregate !== undefined)) {
|
|
133
160
|
throw new Error("Remove measured radius encodings before clearing their scale mapping.");
|
|
134
161
|
}
|
|
162
|
+
if (channel === "size") {
|
|
163
|
+
if (consumers.some(consumer =>
|
|
164
|
+
consumer.layer.mark?.type !== "point" ||
|
|
165
|
+
consumer.encoding.fieldType !== "quantitative"
|
|
166
|
+
)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Scale "${scale.id}" has a consumer incompatible with size mapping.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return normalizeSizeScaleDefinition({ previous: scale, patch });
|
|
172
|
+
}
|
|
135
173
|
const type = patch.type ?? scale.type;
|
|
136
174
|
validateTypeTransition(scale, type, channel, consumers);
|
|
175
|
+
const offset = OFFSET_POSITION_CHANNELS.includes(channel);
|
|
176
|
+
if (offset && Object.hasOwn(patch, "range")) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`${channel} scale range is derived from its parent categorical slot.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (offset && Object.hasOwn(patch, "unknown")) {
|
|
182
|
+
throw new Error(`${channel} scale unknown is not supported for offset positions.`);
|
|
183
|
+
}
|
|
137
184
|
if (type !== scale.type && scale.domain !== "auto" && !Object.hasOwn(patch, "domain") &&
|
|
138
185
|
(["quantile", "threshold"].includes(type) || ["quantile", "threshold"].includes(scale.type))) {
|
|
139
186
|
throw new Error("Color scale type transition requires an explicit domain when its meaning changes.");
|
|
140
187
|
}
|
|
188
|
+
const offsetPolicy = offset
|
|
189
|
+
? normalizeOffsetScalePolicy(
|
|
190
|
+
patch,
|
|
191
|
+
resolveRequestedOffsetPolicy({
|
|
192
|
+
scale,
|
|
193
|
+
consumers,
|
|
194
|
+
markConfigs: program.markConfigs,
|
|
195
|
+
id: scale.id,
|
|
196
|
+
channel
|
|
197
|
+
}),
|
|
198
|
+
channel
|
|
199
|
+
)
|
|
200
|
+
: undefined;
|
|
201
|
+
const normalizedPatch = offset
|
|
202
|
+
? {
|
|
203
|
+
...Object.fromEntries(Object.entries(patch).filter(
|
|
204
|
+
([property]) => property !== "padding"
|
|
205
|
+
)),
|
|
206
|
+
...offsetPolicy
|
|
207
|
+
}
|
|
208
|
+
: patch;
|
|
141
209
|
const definition = normalizeScaleDefinition({
|
|
142
210
|
type,
|
|
143
211
|
previous: scale,
|
|
144
|
-
patch,
|
|
212
|
+
patch: normalizedPatch,
|
|
145
213
|
retainCoreOnTypeChange: true,
|
|
146
214
|
retainCompatibleOnTypeChange: true,
|
|
215
|
+
allowOrdinalBandParameters: offset,
|
|
147
216
|
validateDomain: (scaleType, value) =>
|
|
148
217
|
isDiscretizedColorScaleType(scaleType)
|
|
149
218
|
? validateDiscretizedColorDomain(scaleType, value)
|
|
@@ -162,13 +231,14 @@ function normalizeDefinition(scale, channel, consumers, patch) {
|
|
|
162
231
|
}
|
|
163
232
|
validateSequentialMidpoint(definition.midpoint, type, definition.domain);
|
|
164
233
|
if (definition.midpoint !== undefined && consumers.some(
|
|
165
|
-
consumer =>
|
|
234
|
+
consumer => !["color", "stroke"].includes(consumer.channel) ||
|
|
235
|
+
consumer.encoding.fieldType !== "quantitative"
|
|
166
236
|
)) {
|
|
167
237
|
throw new Error("Scale midpoint requires quantitative color consumers.");
|
|
168
238
|
}
|
|
169
239
|
const typeChanged = type !== scale.type;
|
|
170
|
-
const unknown = Object.hasOwn(
|
|
171
|
-
?
|
|
240
|
+
const unknown = Object.hasOwn(normalizedPatch, "unknown")
|
|
241
|
+
? normalizedPatch.unknown
|
|
172
242
|
: typeChanged ? undefined : scale.unknown;
|
|
173
243
|
if (unknown !== undefined) {
|
|
174
244
|
if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
|
|
@@ -188,7 +258,7 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
|
|
|
188
258
|
}
|
|
189
259
|
if (
|
|
190
260
|
hasPalette &&
|
|
191
|
-
|
|
261
|
+
!["color", "stroke"].includes(channel) &&
|
|
192
262
|
scale.type !== "sequential" &&
|
|
193
263
|
!isDiscretizedColorScaleType(scale.type)
|
|
194
264
|
) {
|
|
@@ -200,5 +270,5 @@ export function prepareScaleEdit(program, scale, channel, consumers, args) {
|
|
|
200
270
|
...Object.entries(args).filter(([key]) => key !== "palette"),
|
|
201
271
|
["range", { palette: args.palette }]
|
|
202
272
|
]);
|
|
203
|
-
return normalizeDefinition(scale, channel, consumers, patch);
|
|
273
|
+
return normalizeDefinition(program, scale, channel, consumers, patch);
|
|
204
274
|
}
|
|
@@ -2,9 +2,15 @@ import { createScale } from "./create.js";
|
|
|
2
2
|
import { editScale } from "./edit.js";
|
|
3
3
|
import { rematerializeScale } from "./materialize.js";
|
|
4
4
|
import { setQuantitativeColorScale } from "./quantitativeColor.js";
|
|
5
|
+
import { registerChannelScaleActions } from "./channels.js";
|
|
6
|
+
import { editParallelScale } from "./parallel.js";
|
|
7
|
+
import { registerOffsetScaleActions } from "./offset.js";
|
|
5
8
|
|
|
6
9
|
export function registerScaleActions(ProgramClass) {
|
|
7
10
|
ProgramClass.prototype.editScale = editScale;
|
|
11
|
+
ProgramClass.prototype.editParallelScale = editParallelScale;
|
|
12
|
+
registerOffsetScaleActions(ProgramClass);
|
|
13
|
+
registerChannelScaleActions(ProgramClass);
|
|
8
14
|
registerBasicScaleActions(ProgramClass);
|
|
9
15
|
}
|
|
10
16
|
|
|
@@ -52,9 +52,10 @@ export const rematerializeScale = action(
|
|
|
52
52
|
) {
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
|
+
const consumerChannel = consumer.channel;
|
|
55
56
|
const materializationMode = getScaleConsumerMaterializationMode(
|
|
56
57
|
consumer.layer,
|
|
57
|
-
|
|
58
|
+
consumerChannel
|
|
58
59
|
);
|
|
59
60
|
if (materializationMode === "rematerialize") {
|
|
60
61
|
if (args.marks !== false) {
|
|
@@ -71,8 +72,8 @@ export const rematerializeScale = action(
|
|
|
71
72
|
}
|
|
72
73
|
next = next.editGraphics({
|
|
73
74
|
target: consumer.layer.id,
|
|
74
|
-
property:
|
|
75
|
-
value: mapScaleConsumerValues(values, resolvedScale,
|
|
75
|
+
property: consumerChannel === "color" ? "fill" : consumerChannel,
|
|
76
|
+
value: mapScaleConsumerValues(values, resolvedScale, consumerChannel)
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
4
|
+
import { requireLayer } from "../../selectors/layers.js";
|
|
5
|
+
import { requireSemanticScale } from "../../selectors/scales.js";
|
|
6
|
+
|
|
7
|
+
const EDITABLE = Object.freeze([
|
|
8
|
+
"domain", "reverse", "padding", "paddingInner", "paddingOuter", "align"
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
function createOffsetScaleEditor(channel) {
|
|
12
|
+
const operation = channel === "xOffset"
|
|
13
|
+
? "editXOffsetScale"
|
|
14
|
+
: "editYOffsetScale";
|
|
15
|
+
return action(
|
|
16
|
+
{
|
|
17
|
+
op: operation,
|
|
18
|
+
description: `Edit the nested ${channel} scale for one mark.`
|
|
19
|
+
},
|
|
20
|
+
function (args = {}) {
|
|
21
|
+
validateOptionObject(args, ["target", ...EDITABLE], operation);
|
|
22
|
+
const target = validateUserId(args.target, `${operation} target`);
|
|
23
|
+
if (!EDITABLE.some(property => Object.hasOwn(args, property))) {
|
|
24
|
+
throw new Error(`${operation} requires at least one editable property.`);
|
|
25
|
+
}
|
|
26
|
+
const layer = requireLayer(this, target, `${operation} target "${target}"`);
|
|
27
|
+
const scaleId = layer.encoding?.[channel]?.scale;
|
|
28
|
+
if (typeof scaleId !== "string") {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`${operation} target "${target}" has no ${channel} scale.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
requireSemanticScale(this, scaleId);
|
|
34
|
+
const patch = Object.fromEntries(Object.entries(args).filter(
|
|
35
|
+
([property]) => property !== "target"
|
|
36
|
+
));
|
|
37
|
+
return this.editScale({ id: scaleId, ...patch });
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const editXOffsetScale = createOffsetScaleEditor("xOffset");
|
|
43
|
+
export const editYOffsetScale = createOffsetScaleEditor("yOffset");
|
|
44
|
+
|
|
45
|
+
export function registerOffsetScaleActions(ProgramClass) {
|
|
46
|
+
ProgramClass.prototype.editXOffsetScale = editXOffsetScale;
|
|
47
|
+
ProgramClass.prototype.editYOffsetScale = editYOffsetScale;
|
|
48
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
4
|
+
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
5
|
+
import { requireLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { requireSemanticScale } from "../../selectors/scales.js";
|
|
7
|
+
|
|
8
|
+
const QUANTITATIVE_OPTIONS = Object.freeze([
|
|
9
|
+
"type", "domain", "range", "nice", "zero", "clamp", "reverse",
|
|
10
|
+
"base", "exponent", "constant", "unknown"
|
|
11
|
+
]);
|
|
12
|
+
const ORDINAL_OPTIONS = Object.freeze([
|
|
13
|
+
"type", "domain", "range", "reverse", "padding", "align", "unknown"
|
|
14
|
+
]);
|
|
15
|
+
const ALL_OPTIONS = Object.freeze([
|
|
16
|
+
"target", "dimension",
|
|
17
|
+
...new Set([...QUANTITATIVE_OPTIONS, ...ORDINAL_OPTIONS])
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function resolveParallelDimension(program, args) {
|
|
21
|
+
validateOptionObject(args, ALL_OPTIONS, "editParallelScale");
|
|
22
|
+
const target = validateUserId(args.target, "Parallel scale target");
|
|
23
|
+
if (typeof args.dimension !== "string" || args.dimension.length === 0) {
|
|
24
|
+
throw new TypeError("Parallel scale dimension must be a non-empty field string.");
|
|
25
|
+
}
|
|
26
|
+
const layer = requireLayer(program, target, `Parallel scale target "${target}"`);
|
|
27
|
+
if (
|
|
28
|
+
findCoordinate(program, layer.coordinate)?.type !== "parallel" ||
|
|
29
|
+
!Array.isArray(layer.encoding?.parallel?.dimensions)
|
|
30
|
+
) {
|
|
31
|
+
throw new Error(`Parallel scale target "${target}" is not a Parallel layer.`);
|
|
32
|
+
}
|
|
33
|
+
const matches = layer.encoding.parallel.dimensions.filter(
|
|
34
|
+
dimension => dimension.field === args.dimension
|
|
35
|
+
);
|
|
36
|
+
if (matches.length !== 1) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
matches.length === 0
|
|
39
|
+
? `Parallel scale target "${target}" has no dimension field "${args.dimension}".`
|
|
40
|
+
: `Parallel scale dimension field "${args.dimension}" is ambiguous on target "${target}".`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const dimension = matches[0];
|
|
44
|
+
requireSemanticScale(program, dimension.scale);
|
|
45
|
+
return dimension;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const editParallelScale = action(
|
|
49
|
+
{
|
|
50
|
+
op: "editParallelScale",
|
|
51
|
+
description: "Edit a Parallel-coordinate dimension scale by field identity."
|
|
52
|
+
},
|
|
53
|
+
function (args = {}) {
|
|
54
|
+
const dimension = resolveParallelDimension(this, args);
|
|
55
|
+
const editable = dimension.fieldType === "quantitative"
|
|
56
|
+
? QUANTITATIVE_OPTIONS
|
|
57
|
+
: ORDINAL_OPTIONS;
|
|
58
|
+
validateOptionObject(
|
|
59
|
+
args,
|
|
60
|
+
["target", "dimension", ...editable],
|
|
61
|
+
"editParallelScale"
|
|
62
|
+
);
|
|
63
|
+
if (!editable.some(property => Object.hasOwn(args, property))) {
|
|
64
|
+
throw new Error("editParallelScale requires at least one editable property.");
|
|
65
|
+
}
|
|
66
|
+
const patch = Object.fromEntries(Object.entries(args).filter(
|
|
67
|
+
([property]) => !["target", "dimension"].includes(property)
|
|
68
|
+
));
|
|
69
|
+
return this.editScale({ id: dimension.scale, ...patch });
|
|
70
|
+
}
|
|
71
|
+
);
|
|
@@ -1,39 +1,140 @@
|
|
|
1
1
|
import { resolveScaleRange } from "../../grammar/scales/index.js";
|
|
2
|
+
import { resolvePolarFrame } from "../../grammar/polar.js";
|
|
2
3
|
import { resolveArcAutoPositionRange, validateMeasuredRadiusConsumers } from "../../materialization/scales/policies/arc.js";
|
|
3
4
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
4
5
|
import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
|
|
5
6
|
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveDataAspectScalePair,
|
|
9
|
+
resolveScaleConsumerBounds
|
|
10
|
+
} from "../../materialization/coordinateBounds.js";
|
|
11
|
+
import { requireCoordinate } from "../../selectors/coordinates.js";
|
|
6
12
|
import { resolveScaleMaterialization } from "../../materialization/scales/resolve.js";
|
|
7
13
|
import { findScale, findScaleConsumers, resolveConsumerCategoryOrder,
|
|
8
14
|
resolveConsumerValues, resolveSeriesLayoutScaleValues } from "./consumers/index.js";
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
function resolveScalePreviewAtBounds(program, id, bounds) {
|
|
11
17
|
const scale = findScale(program, id);
|
|
12
18
|
const consumers = findScaleConsumers(program, id);
|
|
13
19
|
if (consumers.length === 0) throw new Error(`Scale "${id}" has no supported consumers.`);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const consumerChannels = new Set(consumers.map(
|
|
21
|
+
consumer => normalizePositionScaleChannel(consumer.channel)
|
|
22
|
+
));
|
|
23
|
+
const families = new Set([...consumerChannels].map(
|
|
24
|
+
channel => channel === "stroke" ? "color" : channel
|
|
25
|
+
));
|
|
26
|
+
if (families.size !== 1) {
|
|
27
|
+
throw new Error(`Scale "${id}" cannot be shared across channels.`);
|
|
28
|
+
}
|
|
29
|
+
const channel = consumerChannels.size === 1
|
|
30
|
+
? consumerChannels.values().next().value
|
|
31
|
+
: families.values().next().value;
|
|
17
32
|
const valuesByConsumer = consumers.map(consumer => {
|
|
18
|
-
if (
|
|
19
|
-
|
|
33
|
+
if (
|
|
34
|
+
program.markConfigs?.[consumer.layer.id]?.markFilter?.empty === true ||
|
|
35
|
+
program.markConfigs?.[consumer.layer.id]?.statisticalReference !== undefined
|
|
36
|
+
) {
|
|
37
|
+
return {
|
|
38
|
+
consumer,
|
|
39
|
+
values: [],
|
|
40
|
+
categoryOrder: undefined,
|
|
41
|
+
seriesLayout: undefined,
|
|
42
|
+
...(program.markConfigs?.[consumer.layer.id]?.statisticalReference === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { scalePolicy: false })
|
|
45
|
+
};
|
|
20
46
|
}
|
|
21
47
|
return { consumer,
|
|
22
48
|
values: resolveConsumerValues(program, consumer),
|
|
23
49
|
categoryOrder: resolveConsumerCategoryOrder(program, consumer),
|
|
24
50
|
seriesLayout: resolveSeriesLayoutScaleValues(program, consumer) };
|
|
25
51
|
});
|
|
52
|
+
const polarFrame = channel === "radius"
|
|
53
|
+
? resolveSharedRadiusFrame(program, consumers, bounds)
|
|
54
|
+
: undefined;
|
|
26
55
|
const resolvedScale = resolveScaleMaterialization({ id, scale, channel, consumers, valuesByConsumer,
|
|
27
|
-
bounds: ["color", "strokeDash", "strokeWidth", "shape", "size", "opacity", "xOffset", "yOffset"].includes(channel)
|
|
28
|
-
? undefined :
|
|
29
|
-
resolvedScales: program.resolvedScales, markConfigs: program.markConfigs,
|
|
56
|
+
bounds: ["color", "stroke", "strokeDash", "strokeWidth", "shape", "size", "opacity", "xOffset", "yOffset"].includes(channel)
|
|
57
|
+
? undefined : bounds,
|
|
58
|
+
resolvedScales: program.resolvedScales, markConfigs: program.markConfigs, polarFrame,
|
|
59
|
+
thetaScales: scale.radialMapping === undefined ? undefined : Object.fromEntries(consumers.map(({ layer }) =>
|
|
30
60
|
[layer.id, findSemanticScale(program, layer.encoding?.theta?.scale)])) });
|
|
31
61
|
return { channel, consumers, valuesByConsumer, resolvedScale };
|
|
32
62
|
}
|
|
33
63
|
|
|
64
|
+
function resolveSharedRadiusFrame(program, consumers, bounds) {
|
|
65
|
+
const coordinateIds = [...new Set(consumers.map(({ layer }) => layer.coordinate))];
|
|
66
|
+
if (coordinateIds.some(id => id === undefined)) {
|
|
67
|
+
throw new Error("Radius scale consumers require stored Polar coordinates.");
|
|
68
|
+
}
|
|
69
|
+
const frames = coordinateIds.map(id => {
|
|
70
|
+
const coordinate = requireCoordinate(program, id);
|
|
71
|
+
if (coordinate.type !== "polar") {
|
|
72
|
+
throw new Error("Radius scale consumers require Polar coordinates.");
|
|
73
|
+
}
|
|
74
|
+
return resolvePolarFrame(bounds, coordinate.polarFrame);
|
|
75
|
+
});
|
|
76
|
+
if (frames.slice(1).some(
|
|
77
|
+
frame => Math.abs(frame.availableRadius - frames[0].availableRadius) > 1e-9
|
|
78
|
+
)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"A radius scale cannot be shared across coordinates with different Polar frames."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return frames[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function dataAspectScaleIds(program) {
|
|
87
|
+
const ids = new Set();
|
|
88
|
+
for (const coordinate of program.semanticSpec.coordinates) {
|
|
89
|
+
if (coordinate.aspect?.mode !== "data") continue;
|
|
90
|
+
const pair = resolveDataAspectScalePair(program, coordinate);
|
|
91
|
+
ids.add(pair.x);
|
|
92
|
+
ids.add(pair.y);
|
|
93
|
+
}
|
|
94
|
+
return ids;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function resolveScalePreview(program, id) {
|
|
98
|
+
const requestedConsumers = findScaleConsumers(program, id);
|
|
99
|
+
const positionChannels = new Set([
|
|
100
|
+
"x", "y", "x2", "y2", "theta", "radius"
|
|
101
|
+
]);
|
|
102
|
+
const positional = requestedConsumers.some(consumer =>
|
|
103
|
+
consumer.role === "parallelDimension" ||
|
|
104
|
+
positionChannels.has(consumer.channel)
|
|
105
|
+
);
|
|
106
|
+
const allocated = positional ? resolveGraphicBounds(program) : undefined;
|
|
107
|
+
const provisional = resolveScalePreviewAtBounds(program, id, allocated);
|
|
108
|
+
if (!positional) return provisional;
|
|
109
|
+
const resolvedScales = {
|
|
110
|
+
...program.resolvedScales,
|
|
111
|
+
[id]: provisional.resolvedScale
|
|
112
|
+
};
|
|
113
|
+
for (const scaleId of dataAspectScaleIds(program)) {
|
|
114
|
+
if (scaleId === id) continue;
|
|
115
|
+
resolvedScales[scaleId] = resolveScalePreviewAtBounds(
|
|
116
|
+
program,
|
|
117
|
+
scaleId,
|
|
118
|
+
allocated
|
|
119
|
+
).resolvedScale;
|
|
120
|
+
}
|
|
121
|
+
const bounds = resolveScaleConsumerBounds(
|
|
122
|
+
program,
|
|
123
|
+
provisional.consumers,
|
|
124
|
+
{ resolvedScales }
|
|
125
|
+
);
|
|
126
|
+
return resolveScalePreviewAtBounds(program, id, bounds);
|
|
127
|
+
}
|
|
128
|
+
|
|
34
129
|
export function validatePendingMeasuredScale(program, scale, consumers) {
|
|
130
|
+
const bounds = resolveScaleConsumerBounds(program, consumers);
|
|
35
131
|
const range = resolveArcAutoPositionRange({ consumers, scale, channel: "radius",
|
|
36
|
-
range: resolveScaleRange(
|
|
132
|
+
range: resolveScaleRange(
|
|
133
|
+
scale.range,
|
|
134
|
+
"radius",
|
|
135
|
+
bounds,
|
|
136
|
+
resolveSharedRadiusFrame(program, consumers, bounds)
|
|
137
|
+
), markConfigs: program.markConfigs });
|
|
37
138
|
validateMeasuredRadiusConsumers({ scale, domain: scale.domain, range, consumers,
|
|
38
139
|
markConfigs: program.markConfigs, thetaScales: {} });
|
|
39
140
|
}
|