ggaction 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +21 -10
- package/src/actions/errorBars/edit.js +355 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +106 -2
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +34 -3
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +7 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +41 -7
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facets.js +8 -1
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { confidenceCriticalValue } from
|
|
2
|
+
"../statistics/confidenceInterval.js";
|
|
2
3
|
import {
|
|
3
4
|
maximumMagnitude,
|
|
4
5
|
requireFiniteResult,
|
|
@@ -56,7 +57,9 @@ function linearPrediction(model, xValue) {
|
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function fitLinearGroup(rows, {
|
|
60
|
+
function fitLinearGroup(rows, {
|
|
61
|
+
x, y, group, confidenceMethod, level
|
|
62
|
+
}) {
|
|
60
63
|
const count = rows.length;
|
|
61
64
|
const groupLabel = group === undefined ? "all" : String(group);
|
|
62
65
|
const label = `Regression group "${groupLabel}"`;
|
|
@@ -152,7 +155,11 @@ function fitLinearGroup(rows, { x, y, group, confidence }) {
|
|
|
152
155
|
return Object.assign(model, {
|
|
153
156
|
residualSumSquares,
|
|
154
157
|
residualStandardError: Math.sqrt(residualSumSquares / degreesOfFreedom),
|
|
155
|
-
critical:
|
|
158
|
+
critical: confidenceCriticalValue({
|
|
159
|
+
method: confidenceMethod,
|
|
160
|
+
level,
|
|
161
|
+
degreesOfFreedom
|
|
162
|
+
})
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
165
|
|
|
@@ -230,7 +237,9 @@ function polynomialResponse(design, values, size, scale = 1) {
|
|
|
230
237
|
));
|
|
231
238
|
}
|
|
232
239
|
|
|
233
|
-
function fitPolynomialGroup(rows, {
|
|
240
|
+
function fitPolynomialGroup(rows, {
|
|
241
|
+
x, y, group, confidenceMethod, level, degree
|
|
242
|
+
}) {
|
|
234
243
|
const count = rows.length;
|
|
235
244
|
const parameterCount = degree + 1;
|
|
236
245
|
const groupLabel = group === undefined ? "all" : String(group);
|
|
@@ -310,7 +319,11 @@ function fitPolynomialGroup(rows, { x, y, group, confidence, degree }) {
|
|
|
310
319
|
inverse,
|
|
311
320
|
residualSumSquares,
|
|
312
321
|
residualStandardError: Math.sqrt(residualVariance),
|
|
313
|
-
critical:
|
|
322
|
+
critical: confidenceCriticalValue({
|
|
323
|
+
method: confidenceMethod,
|
|
324
|
+
level,
|
|
325
|
+
degreesOfFreedom
|
|
326
|
+
})
|
|
314
327
|
};
|
|
315
328
|
if (!normalizedXByDifference) model.normalizedXByDifference = false;
|
|
316
329
|
if (stableResponse) Object.assign(model, { responseScale, scaledCoefficients });
|
|
@@ -416,7 +429,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
|
|
|
416
429
|
x,
|
|
417
430
|
y,
|
|
418
431
|
group,
|
|
419
|
-
|
|
432
|
+
confidenceMethod: parameters.confidenceMethod,
|
|
433
|
+
level: parameters.level
|
|
420
434
|
});
|
|
421
435
|
}
|
|
422
436
|
if (parameters.method === "polynomial") {
|
|
@@ -424,7 +438,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
|
|
|
424
438
|
x,
|
|
425
439
|
y,
|
|
426
440
|
group,
|
|
427
|
-
|
|
441
|
+
confidenceMethod: parameters.confidenceMethod,
|
|
442
|
+
level: parameters.level,
|
|
428
443
|
degree: parameters.degree
|
|
429
444
|
});
|
|
430
445
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
2
|
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
3
|
+
import { normalizeConfidenceInterval } from
|
|
4
|
+
"../statistics/confidenceInterval.js";
|
|
3
5
|
|
|
4
6
|
const MAX_POLYNOMIAL_DEGREE = 32;
|
|
5
7
|
|
|
@@ -14,6 +16,8 @@ export function normalizeRegressionParameters({
|
|
|
14
16
|
method = "linear",
|
|
15
17
|
degree,
|
|
16
18
|
span,
|
|
19
|
+
confidenceMethod,
|
|
20
|
+
level,
|
|
17
21
|
confidence,
|
|
18
22
|
interval
|
|
19
23
|
} = {}) {
|
|
@@ -24,7 +28,10 @@ export function normalizeRegressionParameters({
|
|
|
24
28
|
if (degree !== undefined) {
|
|
25
29
|
throw new Error("Regression degree requires the polynomial method.");
|
|
26
30
|
}
|
|
27
|
-
if (
|
|
31
|
+
if (
|
|
32
|
+
confidenceMethod !== undefined || level !== undefined ||
|
|
33
|
+
confidence !== undefined || interval !== undefined
|
|
34
|
+
) {
|
|
28
35
|
throw new Error("LOESS regression does not support confidence intervals.");
|
|
29
36
|
}
|
|
30
37
|
const resolvedSpan = span ?? 0.75;
|
|
@@ -38,14 +45,18 @@ export function normalizeRegressionParameters({
|
|
|
38
45
|
if (span !== undefined) {
|
|
39
46
|
throw new Error("Regression span requires the loess method.");
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
resolvedConfidence >= 1
|
|
46
|
-
) {
|
|
47
|
-
throw new RangeError("Regression confidence must be between 0 and 1.");
|
|
48
|
+
if (level !== undefined && confidence !== undefined && level !== confidence) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Regression level and confidence alias must match when both are provided."
|
|
51
|
+
);
|
|
48
52
|
}
|
|
53
|
+
const confidenceInterval = normalizeConfidenceInterval({
|
|
54
|
+
method: confidenceMethod,
|
|
55
|
+
level: level ?? confidence
|
|
56
|
+
}, {
|
|
57
|
+
defaultMethod: "student-t",
|
|
58
|
+
label: "Regression confidence interval"
|
|
59
|
+
});
|
|
49
60
|
const resolvedInterval = interval ?? "mean";
|
|
50
61
|
if (!["mean", "prediction"].includes(resolvedInterval)) {
|
|
51
62
|
throw new Error(`Unsupported regression interval "${resolvedInterval}".`);
|
|
@@ -65,7 +76,8 @@ export function normalizeRegressionParameters({
|
|
|
65
76
|
return cloneAndFreeze({
|
|
66
77
|
method,
|
|
67
78
|
degree: resolvedDegree,
|
|
68
|
-
|
|
79
|
+
confidenceMethod: confidenceInterval.method,
|
|
80
|
+
level: confidenceInterval.level,
|
|
69
81
|
interval: resolvedInterval
|
|
70
82
|
});
|
|
71
83
|
}
|
|
@@ -74,14 +86,16 @@ export function normalizeRegressionParameters({
|
|
|
74
86
|
}
|
|
75
87
|
return cloneAndFreeze({
|
|
76
88
|
method,
|
|
77
|
-
|
|
89
|
+
confidenceMethod: confidenceInterval.method,
|
|
90
|
+
level: confidenceInterval.level,
|
|
78
91
|
interval: resolvedInterval
|
|
79
92
|
});
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
export function validateRegressionTransform(transform) {
|
|
83
96
|
const supported = [
|
|
84
|
-
"type", "method", "x", "y", "groupBy", "
|
|
97
|
+
"type", "method", "x", "y", "groupBy", "confidenceMethod", "level",
|
|
98
|
+
"confidence", "interval",
|
|
85
99
|
"degree", "span"
|
|
86
100
|
];
|
|
87
101
|
const unknown = Object.keys(transform).find(key => !supported.includes(key));
|
|
@@ -96,16 +110,29 @@ export function validateRegressionTransform(transform) {
|
|
|
96
110
|
if (transform.groupBy !== undefined) {
|
|
97
111
|
requireRegressionField(transform.groupBy, "Regression groupBy field");
|
|
98
112
|
}
|
|
113
|
+
if (
|
|
114
|
+
transform.confidence !== undefined &&
|
|
115
|
+
(transform.confidenceMethod !== undefined || transform.level !== undefined)
|
|
116
|
+
) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Regression confidence provenance must use either the legacy confidence field or method and level."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
99
121
|
const normalized = normalizeRegressionParameters({
|
|
100
122
|
method: transform.method,
|
|
101
123
|
degree: transform.degree,
|
|
102
124
|
span: transform.span,
|
|
125
|
+
confidenceMethod: transform.confidenceMethod,
|
|
126
|
+
level: transform.level,
|
|
103
127
|
confidence: transform.confidence,
|
|
104
128
|
interval: transform.interval
|
|
105
129
|
});
|
|
106
130
|
if (normalized.method === "loess") return transform;
|
|
107
|
-
if (
|
|
108
|
-
|
|
131
|
+
if (
|
|
132
|
+
transform.confidence === undefined &&
|
|
133
|
+
(transform.confidenceMethod === undefined || transform.level === undefined)
|
|
134
|
+
) {
|
|
135
|
+
throw new Error("Regression confidence provenance requires method and level.");
|
|
109
136
|
}
|
|
110
137
|
if (transform.interval === undefined) {
|
|
111
138
|
throw new Error(`Unsupported regression interval "${transform.interval}".`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const UNITS = new Set(["radians", "degrees"]);
|
|
2
|
+
|
|
3
|
+
function toRadians(value, unit) {
|
|
4
|
+
return unit === "degrees" ? value * Math.PI / 180 : value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function resolveRotation(value, label = "Rotation", {
|
|
8
|
+
legacyUnit = "radians"
|
|
9
|
+
} = {}) {
|
|
10
|
+
if (!UNITS.has(legacyUnit)) {
|
|
11
|
+
throw new TypeError(`${label} legacy unit must be radians or degrees.`);
|
|
12
|
+
}
|
|
13
|
+
if (Number.isFinite(value)) return toRadians(value, legacyUnit);
|
|
14
|
+
if (
|
|
15
|
+
value !== null &&
|
|
16
|
+
typeof value === "object" &&
|
|
17
|
+
Object.getPrototypeOf(value) === Object.prototype &&
|
|
18
|
+
Object.keys(value).length === 2 &&
|
|
19
|
+
Object.hasOwn(value, "value") &&
|
|
20
|
+
Object.hasOwn(value, "unit") &&
|
|
21
|
+
Number.isFinite(value.value) &&
|
|
22
|
+
UNITS.has(value.unit)
|
|
23
|
+
) {
|
|
24
|
+
return toRadians(value.value, value.unit);
|
|
25
|
+
}
|
|
26
|
+
throw new TypeError(
|
|
27
|
+
`${label} must be a finite legacy number or { value, unit: "radians" | "degrees" }.`
|
|
28
|
+
);
|
|
29
|
+
}
|
package/src/grammar/rules.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import { normalizePositionDatum } from "./positionDatum.js";
|
|
1
2
|
import { cloneAndFreeze } from "../core/immutable.js";
|
|
2
3
|
import {
|
|
3
|
-
isNominalValue,
|
|
4
|
-
normalizeTemporalValue,
|
|
5
4
|
readNominalField,
|
|
6
5
|
readQuantitativeField,
|
|
7
|
-
readTemporalField
|
|
8
|
-
validateSemanticFieldType
|
|
6
|
+
readTemporalField
|
|
9
7
|
} from "./scales/index.js";
|
|
10
8
|
|
|
11
9
|
const ENDPOINT_CHANNELS = Object.freeze(["x", "y", "x2", "y2"]);
|
|
@@ -39,26 +37,13 @@ function readField(rows, encoding) {
|
|
|
39
37
|
return readNominalField(rows, encoding.field);
|
|
40
38
|
}
|
|
41
39
|
if (encoding.fieldType === "temporal") {
|
|
42
|
-
return readTemporalField(rows, encoding.field);
|
|
40
|
+
return readTemporalField(rows, encoding.field, encoding.temporalUnit);
|
|
43
41
|
}
|
|
44
42
|
return readQuantitativeField(rows, encoding.field);
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
export function normalizeRuleDatum(value, fieldType, channel) {
|
|
48
|
-
|
|
49
|
-
if (fieldType === "quantitative") {
|
|
50
|
-
if (!Number.isFinite(value)) {
|
|
51
|
-
throw new TypeError(`Rule ${channel} datum must be a finite number.`);
|
|
52
|
-
}
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
if (fieldType === "temporal") {
|
|
56
|
-
return normalizeTemporalValue(value, `${channel} datum`, 0);
|
|
57
|
-
}
|
|
58
|
-
if (!isNominalValue(value)) {
|
|
59
|
-
throw new TypeError(`Rule ${channel} datum must be a nominal value.`);
|
|
60
|
-
}
|
|
61
|
-
return value;
|
|
45
|
+
export function normalizeRuleDatum(value, fieldType, channel, temporalUnit) {
|
|
46
|
+
return normalizePositionDatum(value, fieldType, channel, temporalUnit, "Rule");
|
|
62
47
|
}
|
|
63
48
|
|
|
64
49
|
export function deriveRuleValues(rows, layer) {
|
|
@@ -83,7 +68,8 @@ export function deriveRuleValues(rows, layer) {
|
|
|
83
68
|
const datum = normalizeRuleDatum(
|
|
84
69
|
encoding.datum,
|
|
85
70
|
encoding.fieldType,
|
|
86
|
-
channel
|
|
71
|
+
channel,
|
|
72
|
+
encoding.temporalUnit
|
|
87
73
|
);
|
|
88
74
|
values[channel] = Array.from({ length }, () => datum);
|
|
89
75
|
}
|
|
@@ -325,6 +325,23 @@ export function interpolateColorStops(stops, position, interpolation = "rgb") {
|
|
|
325
325
|
return interpolateColors(colors, position, method, true);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
export function validateSequentialMidpoint(value, type, domain = "auto") {
|
|
329
|
+
if (value === undefined || value === "auto") return undefined;
|
|
330
|
+
if (type !== "sequential") {
|
|
331
|
+
throw new Error(`Scale type "${type}" does not support midpoint.`);
|
|
332
|
+
}
|
|
333
|
+
if (!Number.isFinite(value)) {
|
|
334
|
+
throw new TypeError("Scale midpoint must be finite or auto.");
|
|
335
|
+
}
|
|
336
|
+
if (domain !== "auto" && (
|
|
337
|
+
!Array.isArray(domain) || domain.length !== 2 || !domain.every(Number.isFinite) ||
|
|
338
|
+
!(value > Math.min(...domain) && value < Math.max(...domain))
|
|
339
|
+
)) {
|
|
340
|
+
throw new RangeError("Scale midpoint must lie strictly inside its resolved domain.");
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}
|
|
344
|
+
|
|
328
345
|
export function mapSequentialColors(
|
|
329
346
|
values,
|
|
330
347
|
domain,
|
|
@@ -342,6 +359,7 @@ export function mapSequentialColors(
|
|
|
342
359
|
if (typeof shouldClamp !== "boolean") {
|
|
343
360
|
throw new TypeError("Sequential color clamp must be a boolean.");
|
|
344
361
|
}
|
|
362
|
+
const midpoint = validateSequentialMidpoint(options.midpoint, "sequential", domain);
|
|
345
363
|
const [start, end] = domain;
|
|
346
364
|
const constant = start === end;
|
|
347
365
|
const method = validateContinuousColorInterpolation(interpolation);
|
|
@@ -354,7 +372,11 @@ export function mapSequentialColors(
|
|
|
354
372
|
if (hasUnknown) return options.unknown;
|
|
355
373
|
throw new TypeError("Sequential color values must be finite numbers.");
|
|
356
374
|
}
|
|
357
|
-
const raw =
|
|
375
|
+
const raw = midpoint === undefined
|
|
376
|
+
? constant ? 0.5 : inverseLerp(value, start, end)
|
|
377
|
+
: (end > start ? value <= midpoint : value >= midpoint)
|
|
378
|
+
? inverseLerp(value, start, midpoint) / 2
|
|
379
|
+
: 0.5 + inverseLerp(value, midpoint, end) / 2;
|
|
358
380
|
return interpolateColors(colors, raw, method, shouldClamp);
|
|
359
381
|
}));
|
|
360
382
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BAR_GRAINS, resolveBarChannels, resolveBarGrain } from "../bars/policy.js";
|
|
2
|
+
import { validateAggregate, validateAggregateFieldType } from "../aggregate.js";
|
|
3
|
+
import { isDiscretizedColorScaleType } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function validateContinuousColorConsumer(layer, encoding, scale, { inferAggregate = false } = {}) {
|
|
6
|
+
const kind = layer.mark?.type;
|
|
7
|
+
if (!["point", "bar", "rect"].includes(kind)) {
|
|
8
|
+
throw new Error("Continuous color requires a Point, aggregate Bar, or Rect consumer.");
|
|
9
|
+
}
|
|
10
|
+
if (!["quantitative", "temporal"].includes(encoding.fieldType) ||
|
|
11
|
+
(isDiscretizedColorScaleType(scale.type) && encoding.fieldType !== "quantitative")) {
|
|
12
|
+
throw new Error(`Scale type "${scale.type}" has an incompatible color field type.`);
|
|
13
|
+
}
|
|
14
|
+
if (Object.hasOwn(scale, "unknown") && kind !== "point") {
|
|
15
|
+
throw new Error("Continuous color scale unknown currently requires a row-owned point mark.");
|
|
16
|
+
}
|
|
17
|
+
if (kind !== "bar") {
|
|
18
|
+
if (encoding.aggregate !== undefined) throw new Error(`${kind} continuous color does not support aggregate.`);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)) {
|
|
22
|
+
throw new Error("Continuous bar color cannot replace an existing nominal color layout.");
|
|
23
|
+
}
|
|
24
|
+
if (encoding.fieldType !== "quantitative") {
|
|
25
|
+
throw new Error("Aggregate bar color currently requires a quantitative field.");
|
|
26
|
+
}
|
|
27
|
+
if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
|
|
28
|
+
throw new Error("Continuous bar color requires a complete categorical aggregate bar.");
|
|
29
|
+
}
|
|
30
|
+
const measure = layer.encoding?.[resolveBarChannels(layer).measure];
|
|
31
|
+
const aggregate = encoding.aggregate ?? (inferAggregate && measure?.field === encoding.field ? measure.aggregate : undefined);
|
|
32
|
+
if (aggregate === undefined) {
|
|
33
|
+
throw new Error("Continuous bar color requires aggregate when its field differs from the measure field.");
|
|
34
|
+
}
|
|
35
|
+
validateAggregate(aggregate);
|
|
36
|
+
validateAggregateFieldType(aggregate, encoding.fieldType);
|
|
37
|
+
return aggregate;
|
|
38
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateMeasuredRadiusScale } from "./radial.js";
|
|
2
|
+
import { validateContinuousColorInterpolation, validateSequentialMidpoint } from "./color.js";
|
|
2
3
|
import { isTransformedScaleType } from "./mapping.js";
|
|
3
4
|
import {
|
|
4
5
|
normalizeTransformParameters,
|
|
@@ -137,6 +138,11 @@ export function normalizeScaleDefinition({
|
|
|
137
138
|
throw new Error(`Scale type "${type}" does not support interpolate.`);
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
const midpoint = validateSequentialMidpoint(
|
|
142
|
+
retainedValue(previous, patch, defaults, "midpoint", typeChanged), type, definition.domain
|
|
143
|
+
);
|
|
144
|
+
if (midpoint !== undefined) definition.midpoint = midpoint;
|
|
145
|
+
|
|
140
146
|
if (type === "band") {
|
|
141
147
|
validateBandParameters(definition, previous, patch, defaults, typeChanged);
|
|
142
148
|
} else if (type === "point") {
|
|
@@ -149,5 +155,13 @@ export function normalizeScaleDefinition({
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
|
|
158
|
+
const radialMapping = Object.hasOwn(patch, "radialMapping") ? patch.radialMapping : previous.radialMapping;
|
|
159
|
+
if (radialMapping !== undefined) {
|
|
160
|
+
definition.radialMapping = radialMapping;
|
|
161
|
+
validateMeasuredRadiusScale({ ...definition,
|
|
162
|
+
...(Object.hasOwn(patch, "unknown") || Object.hasOwn(previous, "unknown")
|
|
163
|
+
? { unknown: patch.unknown ?? previous.unknown } : {})
|
|
164
|
+
}, { allowAuto: true });
|
|
165
|
+
}
|
|
152
166
|
return definition;
|
|
153
167
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
2
|
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
3
3
|
import { interpolateNumber } from "../numeric.js";
|
|
4
|
+
import { formatValue, validateValueFormat } from "../valueFormat.js";
|
|
4
5
|
import { resolveColorRange, validateColorRange } from "./appearance.js";
|
|
5
6
|
import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
|
|
6
7
|
|
|
@@ -162,17 +163,22 @@ export function mapDiscretizedColors(values, scale) {
|
|
|
162
163
|
}));
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
export function formatDiscretizedIntervals(thresholds) {
|
|
166
|
+
export function formatDiscretizedIntervals(thresholds, format = "auto") {
|
|
166
167
|
nondecreasing(thresholds, "Discretized color thresholds");
|
|
168
|
+
const resolvedFormat = validateValueFormat(format, "Legend label format");
|
|
167
169
|
let resolution = Infinity;
|
|
168
170
|
for (let index = 1; index < thresholds.length; index += 1) {
|
|
169
171
|
const gap = thresholds[index] - thresholds[index - 1];
|
|
170
172
|
if (Number.isFinite(gap) && gap > 0) resolution = Math.min(resolution, gap);
|
|
171
173
|
}
|
|
172
|
-
let labels =
|
|
173
|
-
Number.isInteger(value) ? String(value) : value.toFixed(1)
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
let labels = resolvedFormat === "auto"
|
|
175
|
+
? thresholds.map(value => Number.isInteger(value) ? String(value) : value.toFixed(1))
|
|
176
|
+
: thresholds.map(value => formatValue(value, {
|
|
177
|
+
format: resolvedFormat,
|
|
178
|
+
valueType: "quantitative",
|
|
179
|
+
label: "Legend label format"
|
|
180
|
+
}));
|
|
181
|
+
if (resolvedFormat === "auto" && thresholds.some((value, index) => {
|
|
176
182
|
const rounded = Number(labels[index]);
|
|
177
183
|
return value !== 0 && rounded === 0 ||
|
|
178
184
|
index > 0 && value !== thresholds[index - 1] &&
|
|
@@ -81,9 +81,37 @@ function validTimestamp(value) {
|
|
|
81
81
|
return Number.isFinite(value) && Number.isFinite(new Date(value).getTime());
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function
|
|
84
|
+
export function validateTemporalUnit(unit) {
|
|
85
|
+
if (unit !== undefined && !["auto", "year", "timestamp"].includes(unit)) {
|
|
86
|
+
throw new Error(`Unsupported temporalUnit "${unit}"; use auto, year, or timestamp.`);
|
|
87
|
+
}
|
|
88
|
+
return unit;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function resolveTemporalUnit(options, fieldType, previous) {
|
|
92
|
+
if (fieldType !== "temporal") {
|
|
93
|
+
if (Object.hasOwn(options, "temporalUnit")) {
|
|
94
|
+
throw new Error("temporalUnit requires a temporal fieldType.");
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
validateTemporalUnit(options.temporalUnit);
|
|
99
|
+
const sameBinding = ["field", "datum"].some(key =>
|
|
100
|
+
Object.hasOwn(options, key) && Object.hasOwn(previous ?? {}, key) &&
|
|
101
|
+
options[key] === previous[key]);
|
|
102
|
+
return options.temporalUnit ?? (sameBinding && previous?.fieldType === "temporal"
|
|
103
|
+
? previous.temporalUnit : undefined);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeTemporalValue(value, field = "value", index = 0, temporalUnit = "auto") {
|
|
107
|
+
validateTemporalUnit(temporalUnit);
|
|
85
108
|
let timestamp;
|
|
86
|
-
if (
|
|
109
|
+
if (temporalUnit === "year") {
|
|
110
|
+
const year = typeof value === "string" && /^\d{4}$/.test(value) ? Number(value) : value;
|
|
111
|
+
if (Number.isInteger(year) && year >= 0 && year <= 9999) timestamp = utcDate(year);
|
|
112
|
+
} else if (temporalUnit === "timestamp") {
|
|
113
|
+
if (typeof value === "number") timestamp = value;
|
|
114
|
+
} else if (typeof value === "number") {
|
|
87
115
|
timestamp = Number.isInteger(value) && value >= 1000 && value <= 9999
|
|
88
116
|
? utcDate(value)
|
|
89
117
|
: value;
|
|
@@ -99,21 +127,22 @@ export function normalizeTemporalValue(value, field = "value", index = 0) {
|
|
|
99
127
|
if (!validTimestamp(timestamp)) {
|
|
100
128
|
throw new TypeError(
|
|
101
129
|
`Field "${field}" must contain a temporal string or finite timestamp ` +
|
|
102
|
-
`(including a valid date or four-digit year) at row ${index}.`
|
|
130
|
+
`(including a valid date or four-digit year) at row ${index}; temporalUnit is ${temporalUnit}.`
|
|
103
131
|
);
|
|
104
132
|
}
|
|
105
133
|
return timestamp;
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
export function readTemporalField(rows, field) {
|
|
136
|
+
export function readTemporalField(rows, field, temporalUnit) {
|
|
109
137
|
validateField(field);
|
|
138
|
+
validateTemporalUnit(temporalUnit);
|
|
110
139
|
return cloneAndFreeze(rows.map((row, index) => {
|
|
111
140
|
if (!Object.hasOwn(row, field)) {
|
|
112
141
|
throw new TypeError(
|
|
113
142
|
`Field "${field}" must contain a temporal string or finite timestamp at row ${index}.`
|
|
114
143
|
);
|
|
115
144
|
}
|
|
116
|
-
return normalizeTemporalValue(row[field], field, index);
|
|
145
|
+
return normalizeTemporalValue(row[field], field, index, temporalUnit);
|
|
117
146
|
}));
|
|
118
147
|
}
|
|
119
148
|
|
|
@@ -129,10 +158,11 @@ export function readNominalField(rows, field) {
|
|
|
129
158
|
}));
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
export function readScaleField(rows, field, fieldType, { allowUnknown = false } = {}) {
|
|
161
|
+
export function readScaleField(rows, field, fieldType, { allowUnknown = false, temporalUnit } = {}) {
|
|
133
162
|
validateSemanticFieldType(fieldType);
|
|
163
|
+
validateTemporalUnit(temporalUnit);
|
|
134
164
|
if (!allowUnknown) {
|
|
135
|
-
if (fieldType === "temporal") return readTemporalField(rows, field);
|
|
165
|
+
if (fieldType === "temporal") return readTemporalField(rows, field, temporalUnit);
|
|
136
166
|
if (["nominal", "ordinal"].includes(fieldType)) {
|
|
137
167
|
return readNominalField(rows, field);
|
|
138
168
|
}
|
|
@@ -149,7 +179,7 @@ export function readScaleField(rows, field, fieldType, { allowUnknown = false }
|
|
|
149
179
|
return isNominalValue(value) ? value : undefined;
|
|
150
180
|
}
|
|
151
181
|
try {
|
|
152
|
-
return normalizeTemporalValue(value, field, index);
|
|
182
|
+
return normalizeTemporalValue(value, field, index, temporalUnit);
|
|
153
183
|
} catch {
|
|
154
184
|
return undefined;
|
|
155
185
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapMeasuredRadiusValues } from "./radial.js";
|
|
1
2
|
import { mapLinearValues } from "./continuous.js";
|
|
2
3
|
import { mapTransformedValues } from "./transformed.js";
|
|
3
4
|
import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
|
|
@@ -9,6 +10,7 @@ export function isTransformedScaleType(type) {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function mapContinuousScaleValues(values, scale) {
|
|
13
|
+
if (scale?.radialMapping !== undefined) return mapMeasuredRadiusValues(values, scale);
|
|
12
14
|
if (isTransformedScaleType(scale?.type)) {
|
|
13
15
|
validateScaleTypeForRole(scale.type, SCALE_ROLES.quantitativePosition);
|
|
14
16
|
return mapTransformedValues(values, scale.domain, scale.range, {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
|
+
import { interpolateNumber } from "../numeric.js";
|
|
3
|
+
|
|
4
|
+
export function validateRadialMapping(mapping) {
|
|
5
|
+
if (!["area", "radius-length"].includes(mapping)) {
|
|
6
|
+
throw new Error('Radial mapping must be "area" or "radius-length".');
|
|
7
|
+
}
|
|
8
|
+
return mapping;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function validateMeasuredRadiusScale(scale, { allowAuto = false } = {}) {
|
|
12
|
+
validateRadialMapping(scale.radialMapping);
|
|
13
|
+
if (scale.type !== "linear" || (scale.nice !== undefined && scale.nice !== false) ||
|
|
14
|
+
(scale.zero !== undefined && scale.zero !== true) ||
|
|
15
|
+
(scale.reverse !== undefined && scale.reverse !== false) || Object.hasOwn(scale, "unknown")) {
|
|
16
|
+
throw new Error("Measured radius requires a linear, zero-based, non-reversed scale without nice or unknown.");
|
|
17
|
+
}
|
|
18
|
+
if (!(allowAuto && scale.domain === "auto") && (!Array.isArray(scale.domain) || scale.domain.length !== 2 ||
|
|
19
|
+
scale.domain[0] !== 0 || !Number.isFinite(scale.domain[1]) || scale.domain[1] <= 0)) {
|
|
20
|
+
throw new RangeError("Measured radius domain must be [0, positive finite maximum].");
|
|
21
|
+
}
|
|
22
|
+
if (!(allowAuto && scale.range === "auto") && (!Array.isArray(scale.range) || scale.range.length !== 2 ||
|
|
23
|
+
!scale.range.every(Number.isFinite) || scale.range[0] < 0 || scale.range[0] >= scale.range[1])) {
|
|
24
|
+
throw new RangeError("Measured radius range must satisfy 0 <= inner < outer with finite radii.");
|
|
25
|
+
}
|
|
26
|
+
if (scale.clamp !== undefined && typeof scale.clamp !== "boolean") {
|
|
27
|
+
throw new TypeError("Measured radius clamp must be a boolean.");
|
|
28
|
+
}
|
|
29
|
+
return scale;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function mapMeasuredRadiusValues(values, scale) {
|
|
33
|
+
validateMeasuredRadiusScale(scale);
|
|
34
|
+
const [inner, outer] = scale.range;
|
|
35
|
+
const ratio = inner / outer;
|
|
36
|
+
// Normalize before squaring so finite large radii do not overflow.
|
|
37
|
+
const annulus = (1 - ratio) * (1 + ratio);
|
|
38
|
+
return cloneAndFreeze(values.map(value => {
|
|
39
|
+
if (!Number.isFinite(value)) throw new TypeError("Measured radius values must be finite.");
|
|
40
|
+
const proportion = value / scale.domain[1];
|
|
41
|
+
const t = scale.clamp ? Math.max(0, Math.min(1, proportion)) : proportion;
|
|
42
|
+
if (!Number.isFinite(t) || t < 0 || t > 1) {
|
|
43
|
+
throw new RangeError("Measured radius value must lie inside its zero-based domain.");
|
|
44
|
+
}
|
|
45
|
+
if (t === 0 && value <= 0) return inner;
|
|
46
|
+
if (t === 1) return outer;
|
|
47
|
+
const radius = scale.radialMapping === "area"
|
|
48
|
+
? outer * Math.sqrt(Math.min(1, ratio * ratio + t * annulus))
|
|
49
|
+
: interpolateNumber(inner, outer, t);
|
|
50
|
+
if (radius <= inner) {
|
|
51
|
+
throw new RangeError("Positive measured radius cannot be represented distinctly from the inner radius at this numeric precision.");
|
|
52
|
+
}
|
|
53
|
+
return Math.min(outer, radius);
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
@@ -27,6 +27,7 @@ export const COMPLETE_SCALE_TYPES = Object.freeze([
|
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
29
|
const SCALE_PROPERTY_TYPES = Object.freeze({
|
|
30
|
+
midpoint: Object.freeze(["sequential"]),
|
|
30
31
|
nice: Object.freeze(["linear", "log", "pow", "sqrt", "symlog", "time"]),
|
|
31
32
|
zero: Object.freeze(["linear", "pow", "sqrt", "symlog"]),
|
|
32
33
|
clamp: Object.freeze([
|
|
@@ -20,12 +20,21 @@ const ENCODING_PATHS = Object.freeze([
|
|
|
20
20
|
"encoding.x.bin.boundaries",
|
|
21
21
|
"encoding.color.layout",
|
|
22
22
|
"encoding.color.aggregate",
|
|
23
|
+
"encoding.group.fields",
|
|
24
|
+
"encoding.group.inferredFrom",
|
|
25
|
+
...["x", "y", "x2", "y2", "theta", "color"].map(channel =>
|
|
26
|
+
`encoding.${channel}.temporalUnit`
|
|
27
|
+
),
|
|
23
28
|
"encoding.pathOrder.field",
|
|
24
29
|
"encoding.pathOrder.fieldType",
|
|
25
30
|
"encoding.pathOrder.order",
|
|
26
31
|
"encoding.theta.aggregate",
|
|
27
32
|
"encoding.theta.weight",
|
|
33
|
+
"encoding.radius.aggregate",
|
|
34
|
+
"encoding.theta.categoryOrder",
|
|
28
35
|
"encoding.text.format",
|
|
36
|
+
"encoding.text.content",
|
|
37
|
+
"encoding.text.normalizeBy",
|
|
29
38
|
"encoding.parallel.dimensions",
|
|
30
39
|
"encoding.parallel.key",
|
|
31
40
|
"encoding.parallel.missing",
|
|
@@ -45,7 +54,7 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
45
54
|
collection: "layers",
|
|
46
55
|
removableContainers: new Set(
|
|
47
56
|
[...ENCODING_CHANNELS.map(channel => `encoding.${channel}`),
|
|
48
|
-
"encoding.parallel"]
|
|
57
|
+
"encoding.parallel", "layout"]
|
|
49
58
|
),
|
|
50
59
|
properties: new Set([
|
|
51
60
|
"data",
|
|
@@ -53,6 +62,8 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
53
62
|
"coordinate",
|
|
54
63
|
"transform",
|
|
55
64
|
"mark.type",
|
|
65
|
+
"mark.missing",
|
|
66
|
+
"layout.mode",
|
|
56
67
|
...ENCODING_PATHS
|
|
57
68
|
])
|
|
58
69
|
},
|
|
@@ -60,7 +71,7 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
60
71
|
collection: "scales",
|
|
61
72
|
properties: new Set([
|
|
62
73
|
"type", "domain", "range", "nice", "zero", "clamp", "reverse",
|
|
63
|
-
"unknown", "base", "exponent", "constant", "interpolate",
|
|
74
|
+
"unknown", "base", "exponent", "constant", "interpolate", "radialMapping", "midpoint",
|
|
64
75
|
"paddingInner", "paddingOuter", "padding", "align"
|
|
65
76
|
])
|
|
66
77
|
},
|
|
@@ -86,8 +97,10 @@ const GUIDE_PATHS = new Set([
|
|
|
86
97
|
"axis.parallel.target",
|
|
87
98
|
"axis.parallel.coordinate",
|
|
88
99
|
"axis.parallel.scales",
|
|
100
|
+
"axis.parallel.titles",
|
|
89
101
|
"legend.color.scale",
|
|
90
102
|
"legend.color.title",
|
|
103
|
+
"legend.color.order",
|
|
91
104
|
"legend.size.scale",
|
|
92
105
|
"legend.size.title",
|
|
93
106
|
"legend.strokeWidth.scale",
|
|
@@ -97,6 +110,7 @@ const GUIDE_PATHS = new Set([
|
|
|
97
110
|
"legend.series.channels",
|
|
98
111
|
"legend.series.scales",
|
|
99
112
|
"legend.series.title",
|
|
113
|
+
"legend.series.order",
|
|
100
114
|
"grid.horizontal.scale",
|
|
101
115
|
"grid.horizontal.coordinate",
|
|
102
116
|
"grid.vertical.scale",
|
|
@@ -137,7 +151,7 @@ export function parseSemanticPath(property, { allowContainer = false } = {}) {
|
|
|
137
151
|
|
|
138
152
|
if (allowContainer) {
|
|
139
153
|
const entityMatch = property.match(
|
|
140
|
-
new RegExp(`^(dataset|layer)\\[(${USER_ID_SOURCE})\\]$`)
|
|
154
|
+
new RegExp(`^(dataset|layer|scale)\\[(${USER_ID_SOURCE})\\]$`)
|
|
141
155
|
);
|
|
142
156
|
if (entityMatch) {
|
|
143
157
|
const [, kind, id] = entityMatch;
|