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
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
|
+
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
3
|
+
import { interpolateNumber } from "../numeric.js";
|
|
4
|
+
import { validateSizeRange, resolveSizeRange } from "./appearance.js";
|
|
5
|
+
import { mapLinearValues } from "./continuous.js";
|
|
6
|
+
import { mapTransformedValues, resolveTransformedDomain } from "./transformed.js";
|
|
7
|
+
import { validateScaleUnknown } from "./policies.js";
|
|
8
|
+
import { validatePair } from "./validation.js";
|
|
9
|
+
|
|
10
|
+
export const CONTINUOUS_SIZE_SCALE_TYPES = cloneAndFreeze([
|
|
11
|
+
"linear", "log", "sqrt", "pow"
|
|
12
|
+
]);
|
|
13
|
+
export const DISCRETE_SIZE_SCALE_TYPES = cloneAndFreeze([
|
|
14
|
+
"quantize", "quantile", "threshold"
|
|
15
|
+
]);
|
|
16
|
+
export const SIZE_SCALE_TYPES = cloneAndFreeze([
|
|
17
|
+
...CONTINUOUS_SIZE_SCALE_TYPES,
|
|
18
|
+
...DISCRETE_SIZE_SCALE_TYPES
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export function isContinuousSizeScaleType(type) {
|
|
22
|
+
return CONTINUOUS_SIZE_SCALE_TYPES.includes(type);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isDiscreteSizeScaleType(type) {
|
|
26
|
+
return DISCRETE_SIZE_SCALE_TYPES.includes(type);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isSizeScaleType(type) {
|
|
30
|
+
return SIZE_SCALE_TYPES.includes(type);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function validateSizeScaleType(type) {
|
|
34
|
+
if (!isSizeScaleType(type)) {
|
|
35
|
+
throw new Error(`Scale type "${type}" is not valid for size.`);
|
|
36
|
+
}
|
|
37
|
+
return type;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function finiteArray(value, label, minimumLength = 1) {
|
|
41
|
+
if (
|
|
42
|
+
!Array.isArray(value) ||
|
|
43
|
+
value.length < minimumLength ||
|
|
44
|
+
!value.every(Number.isFinite)
|
|
45
|
+
) {
|
|
46
|
+
throw new TypeError(`${label} must contain finite numbers.`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function strictlyIncreasing(value, label) {
|
|
52
|
+
finiteArray(value, label);
|
|
53
|
+
if (value.some((item, index) => index > 0 && item <= value[index - 1])) {
|
|
54
|
+
throw new RangeError(`${label} must be strictly increasing.`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateContinuousDomain(type, domain) {
|
|
60
|
+
const validated = validatePair(domain, "Size scale domain");
|
|
61
|
+
if (type !== "linear" && validated[0] === validated[1]) {
|
|
62
|
+
throw new RangeError(
|
|
63
|
+
`Size ${type} scale domain values must be distinct.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (type === "log" && validated.some(value => value <= 0)) {
|
|
67
|
+
throw new RangeError("Size log scale domain must be strictly positive.");
|
|
68
|
+
}
|
|
69
|
+
if (["sqrt", "pow"].includes(type) && validated.some(value => value < 0)) {
|
|
70
|
+
throw new RangeError(
|
|
71
|
+
`Size ${type} scale domain must be non-negative.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return validated;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validateSizeScaleDomain(type, domain) {
|
|
78
|
+
validateSizeScaleType(type);
|
|
79
|
+
if (domain === "auto") {
|
|
80
|
+
if (type === "threshold") {
|
|
81
|
+
throw new Error("Threshold size scale requires an explicit domain.");
|
|
82
|
+
}
|
|
83
|
+
return domain;
|
|
84
|
+
}
|
|
85
|
+
if (isContinuousSizeScaleType(type)) {
|
|
86
|
+
return validateContinuousDomain(type, domain);
|
|
87
|
+
}
|
|
88
|
+
if (type === "quantize") {
|
|
89
|
+
const validated = validatePair(domain, "Quantize size domain");
|
|
90
|
+
if (validated[0] >= validated[1]) {
|
|
91
|
+
throw new RangeError(
|
|
92
|
+
"Quantize size domain must be a strictly increasing pair."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return validated;
|
|
96
|
+
}
|
|
97
|
+
if (type === "quantile") {
|
|
98
|
+
return cloneAndFreeze(finiteArray(domain, "Quantile size domain"));
|
|
99
|
+
}
|
|
100
|
+
return cloneAndFreeze(
|
|
101
|
+
strictlyIncreasing(domain, "Threshold size domain")
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function validateDiscreteSizeRange(range) {
|
|
106
|
+
finiteArray(range, "Discrete size range", 2);
|
|
107
|
+
validateGeneratedItemLimit(range.length, "Discrete size range length");
|
|
108
|
+
if (range.some(value => value < 0)) {
|
|
109
|
+
throw new RangeError(
|
|
110
|
+
"Discrete size range must contain non-negative areas."
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (range.some((value, index) => index > 0 && value < range[index - 1])) {
|
|
114
|
+
throw new RangeError("Discrete size range must be nondecreasing.");
|
|
115
|
+
}
|
|
116
|
+
return cloneAndFreeze(range);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function validateSizeScaleRange(type, range) {
|
|
120
|
+
validateSizeScaleType(type);
|
|
121
|
+
if (isContinuousSizeScaleType(type)) return validateSizeRange(range);
|
|
122
|
+
if (range === "auto") {
|
|
123
|
+
throw new Error(`Size ${type} scale requires an explicit range.`);
|
|
124
|
+
}
|
|
125
|
+
return validateDiscreteSizeRange(range);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sizeTypeFamily(type) {
|
|
129
|
+
return isContinuousSizeScaleType(type) ? "continuous" : type;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requireBoolean(value, label) {
|
|
133
|
+
if (typeof value !== "boolean") {
|
|
134
|
+
throw new TypeError(`${label} must be a boolean.`);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function positiveFinite(value, label) {
|
|
140
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
141
|
+
throw new RangeError(`${label} must be a positive finite number.`);
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeSizeScaleDefinition({ previous = {}, patch = {} } = {}) {
|
|
147
|
+
const type = validateSizeScaleType(patch.type ?? previous.type ?? "linear");
|
|
148
|
+
const hasPrevious = previous.type !== undefined;
|
|
149
|
+
const typeChanged = hasPrevious && previous.type !== type;
|
|
150
|
+
if (typeChanged && sizeTypeFamily(previous.type) !== sizeTypeFamily(type) &&
|
|
151
|
+
!Object.hasOwn(patch, "domain")) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"Size scale type-family transition requires an explicit domain."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (typeChanged && isDiscreteSizeScaleType(type) &&
|
|
157
|
+
!Object.hasOwn(patch, "range")) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Size ${type} scale transition requires an explicit range.`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (typeChanged && isDiscreteSizeScaleType(previous.type) &&
|
|
163
|
+
isContinuousSizeScaleType(type) && !Object.hasOwn(patch, "range")) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"Discrete-to-continuous size scale transition requires an explicit range."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
for (const property of [
|
|
169
|
+
"nice", "zero", "constant", "padding", "paddingInner", "paddingOuter",
|
|
170
|
+
"align", "palette", "interpolate", "midpoint", "radialMapping"
|
|
171
|
+
]) {
|
|
172
|
+
if (Object.hasOwn(patch, property)) {
|
|
173
|
+
throw new Error(`Size scale does not support ${property}.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const rawDomain = Object.hasOwn(patch, "domain")
|
|
178
|
+
? patch.domain
|
|
179
|
+
: Object.hasOwn(previous, "domain")
|
|
180
|
+
? previous.domain
|
|
181
|
+
: "auto";
|
|
182
|
+
const rawRange = Object.hasOwn(patch, "range")
|
|
183
|
+
? patch.range
|
|
184
|
+
: Object.hasOwn(previous, "range")
|
|
185
|
+
? previous.range
|
|
186
|
+
: "auto";
|
|
187
|
+
const definition = {
|
|
188
|
+
type,
|
|
189
|
+
domain: validateSizeScaleDomain(type, rawDomain),
|
|
190
|
+
range: validateSizeScaleRange(type, rawRange)
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const reverse = Object.hasOwn(patch, "reverse")
|
|
194
|
+
? patch.reverse
|
|
195
|
+
: previous.reverse;
|
|
196
|
+
if (reverse !== undefined) {
|
|
197
|
+
definition.reverse = requireBoolean(reverse, "Size scale reverse");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (isContinuousSizeScaleType(type)) {
|
|
201
|
+
const clamp = Object.hasOwn(patch, "clamp")
|
|
202
|
+
? patch.clamp
|
|
203
|
+
: isContinuousSizeScaleType(previous.type)
|
|
204
|
+
? previous.clamp
|
|
205
|
+
: undefined;
|
|
206
|
+
if (clamp !== undefined) {
|
|
207
|
+
definition.clamp = requireBoolean(clamp, "Size scale clamp");
|
|
208
|
+
}
|
|
209
|
+
} else if (Object.hasOwn(patch, "clamp")) {
|
|
210
|
+
throw new Error(`Size ${type} scale does not support clamp.`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (type === "log") {
|
|
214
|
+
const base = Object.hasOwn(patch, "base")
|
|
215
|
+
? patch.base
|
|
216
|
+
: !typeChanged && previous.type === "log"
|
|
217
|
+
? previous.base
|
|
218
|
+
: 10;
|
|
219
|
+
definition.base = positiveFinite(base ?? 10, "Size log scale base");
|
|
220
|
+
if (definition.base === 1) {
|
|
221
|
+
throw new RangeError("Size log scale base must not equal 1.");
|
|
222
|
+
}
|
|
223
|
+
} else if (Object.hasOwn(patch, "base")) {
|
|
224
|
+
throw new Error(`Size ${type} scale does not support base.`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (type === "pow") {
|
|
228
|
+
const exponent = Object.hasOwn(patch, "exponent")
|
|
229
|
+
? patch.exponent
|
|
230
|
+
: !typeChanged && previous.type === "pow"
|
|
231
|
+
? previous.exponent
|
|
232
|
+
: undefined;
|
|
233
|
+
if (exponent === undefined) {
|
|
234
|
+
throw new Error("Size pow scale requires an explicit exponent.");
|
|
235
|
+
}
|
|
236
|
+
definition.exponent = positiveFinite(
|
|
237
|
+
exponent,
|
|
238
|
+
"Size pow scale exponent"
|
|
239
|
+
);
|
|
240
|
+
} else if (Object.hasOwn(patch, "exponent")) {
|
|
241
|
+
throw new Error(`Size ${type} scale does not support exponent.`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const unknown = Object.hasOwn(patch, "unknown")
|
|
245
|
+
? patch.unknown
|
|
246
|
+
: previous.unknown;
|
|
247
|
+
if (unknown !== undefined) {
|
|
248
|
+
definition.unknown = validateScaleUnknown("size", unknown);
|
|
249
|
+
}
|
|
250
|
+
return cloneAndFreeze(definition);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function validateSizeValues(values, type) {
|
|
254
|
+
if (!Array.isArray(values) || !values.every(Number.isFinite)) {
|
|
255
|
+
throw new TypeError("Size scale values must be finite numbers.");
|
|
256
|
+
}
|
|
257
|
+
if (type === "log" && values.some(value => value <= 0)) {
|
|
258
|
+
throw new RangeError("Size log scale values must be strictly positive.");
|
|
259
|
+
}
|
|
260
|
+
if (["sqrt", "pow"].includes(type) && values.some(value => value < 0)) {
|
|
261
|
+
throw new RangeError(`Size ${type} scale values must be non-negative.`);
|
|
262
|
+
}
|
|
263
|
+
return values;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function numericExtent(values) {
|
|
267
|
+
let minimum = values[0];
|
|
268
|
+
let maximum = values[0];
|
|
269
|
+
for (const value of values.slice(1)) {
|
|
270
|
+
minimum = Math.min(minimum, value);
|
|
271
|
+
maximum = Math.max(maximum, value);
|
|
272
|
+
}
|
|
273
|
+
return [minimum, maximum];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveContinuousSizeDomain(definition, values) {
|
|
277
|
+
validateSizeValues(values, definition.type);
|
|
278
|
+
if (definition.domain !== "auto") {
|
|
279
|
+
return validateSizeScaleDomain(definition.type, definition.domain);
|
|
280
|
+
}
|
|
281
|
+
if (values.length === 0) {
|
|
282
|
+
throw new Error("Cannot infer an automatic size scale domain from no values.");
|
|
283
|
+
}
|
|
284
|
+
if (definition.type === "linear") {
|
|
285
|
+
return cloneAndFreeze(numericExtent(values));
|
|
286
|
+
}
|
|
287
|
+
if (["sqrt", "pow"].includes(definition.type) && values.every(value => value === 0)) {
|
|
288
|
+
return cloneAndFreeze([0, 1]);
|
|
289
|
+
}
|
|
290
|
+
const domain = resolveTransformedDomain({
|
|
291
|
+
type: definition.type,
|
|
292
|
+
domain: "auto",
|
|
293
|
+
values,
|
|
294
|
+
...(definition.type === "log" ? { base: definition.base } : {}),
|
|
295
|
+
...(definition.type === "pow" ? { exponent: definition.exponent } : {})
|
|
296
|
+
});
|
|
297
|
+
return validateSizeScaleDomain(definition.type, domain);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function quantile(sorted, probability) {
|
|
301
|
+
const position = (sorted.length - 1) * probability;
|
|
302
|
+
const lower = Math.floor(position);
|
|
303
|
+
const upper = Math.ceil(position);
|
|
304
|
+
return interpolateNumber(
|
|
305
|
+
sorted[lower],
|
|
306
|
+
sorted[upper],
|
|
307
|
+
position - lower
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function resolveSizeScale({
|
|
312
|
+
type,
|
|
313
|
+
domain = "auto",
|
|
314
|
+
range = "auto",
|
|
315
|
+
values = [],
|
|
316
|
+
...options
|
|
317
|
+
} = {}) {
|
|
318
|
+
const definition = normalizeSizeScaleDefinition({
|
|
319
|
+
patch: { type, domain, range, ...options }
|
|
320
|
+
});
|
|
321
|
+
validateSizeValues(values, definition.type);
|
|
322
|
+
if (isContinuousSizeScaleType(definition.type)) {
|
|
323
|
+
const resolvedDomain = resolveContinuousSizeDomain(definition, values);
|
|
324
|
+
const resolvedRange = [...resolveSizeRange(definition.range)];
|
|
325
|
+
if (definition.reverse === true) resolvedRange.reverse();
|
|
326
|
+
return cloneAndFreeze({
|
|
327
|
+
type: definition.type,
|
|
328
|
+
domain: resolvedDomain,
|
|
329
|
+
range: resolvedRange,
|
|
330
|
+
...(definition.clamp === undefined ? {} : { clamp: definition.clamp }),
|
|
331
|
+
...(definition.type === "log" ? { base: definition.base } : {}),
|
|
332
|
+
...(definition.type === "pow" ? { exponent: definition.exponent } : {}),
|
|
333
|
+
...(definition.unknown === undefined ? {} : { unknown: definition.unknown })
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const resolvedRange = [...definition.range];
|
|
338
|
+
if (definition.reverse === true) resolvedRange.reverse();
|
|
339
|
+
const sample = [...values].sort((left, right) => left - right);
|
|
340
|
+
let resolvedDomain;
|
|
341
|
+
let thresholds;
|
|
342
|
+
if (definition.type === "quantize") {
|
|
343
|
+
if (definition.domain === "auto") {
|
|
344
|
+
if (sample.length === 0) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
"Cannot infer an automatic quantize size domain from no values."
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
resolvedDomain = numericExtent(sample);
|
|
350
|
+
if (resolvedDomain[0] === resolvedDomain[1]) {
|
|
351
|
+
throw new RangeError(
|
|
352
|
+
"Quantize size scale requires a non-zero domain span."
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
resolvedDomain = [...definition.domain];
|
|
357
|
+
}
|
|
358
|
+
thresholds = Array.from(
|
|
359
|
+
{ length: resolvedRange.length - 1 },
|
|
360
|
+
(_, index) => interpolateNumber(
|
|
361
|
+
resolvedDomain[0],
|
|
362
|
+
resolvedDomain[1],
|
|
363
|
+
(index + 1) / resolvedRange.length
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
if (thresholds.some((value, index) =>
|
|
367
|
+
value <= (index === 0 ? resolvedDomain[0] : thresholds[index - 1]) ||
|
|
368
|
+
value >= resolvedDomain[1]
|
|
369
|
+
)) {
|
|
370
|
+
throw new RangeError(
|
|
371
|
+
"Quantize size range requests more buckets than its domain can represent."
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
} else if (definition.type === "quantile") {
|
|
375
|
+
const source = definition.domain === "auto"
|
|
376
|
+
? sample
|
|
377
|
+
: [...definition.domain].sort((left, right) => left - right);
|
|
378
|
+
if (source.length === 0) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
"Cannot infer automatic quantile size thresholds from no values."
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
resolvedDomain = source;
|
|
384
|
+
thresholds = Array.from(
|
|
385
|
+
{ length: resolvedRange.length - 1 },
|
|
386
|
+
(_, index) => quantile(
|
|
387
|
+
source,
|
|
388
|
+
(index + 1) / resolvedRange.length
|
|
389
|
+
)
|
|
390
|
+
);
|
|
391
|
+
} else {
|
|
392
|
+
resolvedDomain = [...definition.domain];
|
|
393
|
+
thresholds = [...definition.domain];
|
|
394
|
+
if (resolvedRange.length !== thresholds.length + 1) {
|
|
395
|
+
throw new RangeError(
|
|
396
|
+
"Threshold size range must contain exactly one more area than its domain."
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return cloneAndFreeze({
|
|
401
|
+
type: definition.type,
|
|
402
|
+
domain: resolvedDomain,
|
|
403
|
+
thresholds,
|
|
404
|
+
range: resolvedRange,
|
|
405
|
+
...(definition.unknown === undefined ? {} : { unknown: definition.unknown })
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function discretizedSizeIndex(value, thresholds) {
|
|
410
|
+
if (!Number.isFinite(value)) {
|
|
411
|
+
throw new TypeError("Discrete size values must be finite numbers.");
|
|
412
|
+
}
|
|
413
|
+
let index = 0;
|
|
414
|
+
while (index < thresholds.length && value >= thresholds[index]) index += 1;
|
|
415
|
+
return index;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function validateMappedAreas(areas) {
|
|
419
|
+
if (areas.some(area => !Number.isFinite(area) || area < 0)) {
|
|
420
|
+
throw new RangeError(
|
|
421
|
+
"Size scale mapping must produce non-negative finite areas."
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
return cloneAndFreeze(areas);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function mapSizeValues(values, scale) {
|
|
428
|
+
validateSizeScaleType(scale?.type);
|
|
429
|
+
const hasUnknown = Object.hasOwn(scale, "unknown");
|
|
430
|
+
const finite = values.filter(Number.isFinite);
|
|
431
|
+
validateSizeValues(finite, scale.type);
|
|
432
|
+
if (!hasUnknown && finite.length !== values.length) {
|
|
433
|
+
throw new TypeError("Size scale values must be finite numbers.");
|
|
434
|
+
}
|
|
435
|
+
let mapped;
|
|
436
|
+
if (isDiscreteSizeScaleType(scale.type)) {
|
|
437
|
+
mapped = values.map(value => {
|
|
438
|
+
if (!Number.isFinite(value)) return scale.unknown;
|
|
439
|
+
return scale.range[discretizedSizeIndex(value, scale.thresholds)];
|
|
440
|
+
});
|
|
441
|
+
} else if (scale.type === "linear") {
|
|
442
|
+
mapped = mapLinearValues(values, scale.domain, scale.range, {
|
|
443
|
+
clamp: scale.clamp ?? false,
|
|
444
|
+
...(hasUnknown ? { unknown: scale.unknown } : {})
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
mapped = mapTransformedValues(values, scale.domain, scale.range, {
|
|
448
|
+
type: scale.type,
|
|
449
|
+
clamp: scale.clamp ?? false,
|
|
450
|
+
...(hasUnknown ? { unknown: scale.unknown } : {}),
|
|
451
|
+
...(scale.type === "log" ? { base: scale.base } : {}),
|
|
452
|
+
...(scale.type === "pow" ? { exponent: scale.exponent } : {})
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return validateMappedAreas(mapped);
|
|
456
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
2
|
import { validatePathCommands } from "../pathCommands.js";
|
|
3
3
|
import { validateFillPaint } from "../paint.js";
|
|
4
|
+
import {
|
|
5
|
+
validateLineCap,
|
|
6
|
+
validateLineJoin,
|
|
7
|
+
validateMiterLimit
|
|
8
|
+
} from "../strokeStyle.js";
|
|
4
9
|
import { validateGraphicProperty } from "./graphic.js";
|
|
5
10
|
|
|
6
11
|
const FINITE_PROPERTIES = new Set([
|
|
@@ -42,9 +47,13 @@ export function validateConcreteGraphicValue(type, property, value) {
|
|
|
42
47
|
throw new RangeError(`${type}.opacity must be between 0 and 1.`);
|
|
43
48
|
}
|
|
44
49
|
if (STRING_PROPERTIES.has(property) && (
|
|
45
|
-
typeof value !== "string" || value.length === 0
|
|
50
|
+
typeof value !== "string" || property !== "text" && value.length === 0
|
|
46
51
|
)) {
|
|
47
|
-
throw new TypeError(
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
property === "text"
|
|
54
|
+
? `${type}.${property} must be a string.`
|
|
55
|
+
: `${type}.${property} must be a non-empty string.`
|
|
56
|
+
);
|
|
48
57
|
}
|
|
49
58
|
if (property === "fontWeight" && !(
|
|
50
59
|
(typeof value === "string" && value.length > 0) || Number.isFinite(value)
|
|
@@ -65,6 +74,9 @@ export function validateConcreteGraphicValue(type, property, value) {
|
|
|
65
74
|
`${type}.strokeDash must be an array of non-negative finite numbers.`
|
|
66
75
|
);
|
|
67
76
|
}
|
|
77
|
+
if (property === "lineCap") validateLineCap(value, `${type} graphic`);
|
|
78
|
+
if (property === "lineJoin") validateLineJoin(value, `${type} graphic`);
|
|
79
|
+
if (property === "miterLimit") validateMiterLimit(value, `${type} graphic`);
|
|
68
80
|
if (type === "path" && property === "commands") {
|
|
69
81
|
validatePathCommands(value);
|
|
70
82
|
}
|
|
@@ -27,6 +27,9 @@ const GRAPHIC_PROPERTIES = Object.freeze({
|
|
|
27
27
|
"stroke",
|
|
28
28
|
"strokeWidth",
|
|
29
29
|
"strokeDash",
|
|
30
|
+
"lineCap",
|
|
31
|
+
"lineJoin",
|
|
32
|
+
"miterLimit",
|
|
30
33
|
"fontSize",
|
|
31
34
|
"fontFamily",
|
|
32
35
|
"fontWeight",
|
|
@@ -44,6 +47,9 @@ const GRAPHIC_PROPERTIES = Object.freeze({
|
|
|
44
47
|
"fill",
|
|
45
48
|
"stroke",
|
|
46
49
|
"strokeWidth",
|
|
50
|
+
"lineCap",
|
|
51
|
+
"lineJoin",
|
|
52
|
+
"miterLimit",
|
|
47
53
|
"opacity",
|
|
48
54
|
"length"
|
|
49
55
|
]),
|
|
@@ -55,6 +61,9 @@ const GRAPHIC_PROPERTIES = Object.freeze({
|
|
|
55
61
|
"fill",
|
|
56
62
|
"stroke",
|
|
57
63
|
"strokeWidth",
|
|
64
|
+
"lineCap",
|
|
65
|
+
"lineJoin",
|
|
66
|
+
"miterLimit",
|
|
58
67
|
"opacity",
|
|
59
68
|
"length"
|
|
60
69
|
]),
|
|
@@ -66,6 +75,9 @@ const GRAPHIC_PROPERTIES = Object.freeze({
|
|
|
66
75
|
"stroke",
|
|
67
76
|
"strokeWidth",
|
|
68
77
|
"strokeDash",
|
|
78
|
+
"lineCap",
|
|
79
|
+
"lineJoin",
|
|
80
|
+
"miterLimit",
|
|
69
81
|
"opacity",
|
|
70
82
|
"length"
|
|
71
83
|
]),
|
|
@@ -89,6 +101,9 @@ const GRAPHIC_PROPERTIES = Object.freeze({
|
|
|
89
101
|
"stroke",
|
|
90
102
|
"strokeWidth",
|
|
91
103
|
"strokeDash",
|
|
104
|
+
"lineCap",
|
|
105
|
+
"lineJoin",
|
|
106
|
+
"miterLimit",
|
|
92
107
|
"opacity",
|
|
93
108
|
"length"
|
|
94
109
|
])
|