ggaction 0.0.3 → 0.0.5
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 +58 -3
- package/README.md +117 -83
- package/package.json +27 -11
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +31 -35
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +8 -1
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +137 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +29 -2
- package/src/actions/data/regression.js +1 -1
- package/src/actions/data/window.js +50 -0
- package/src/actions/encodings/appearance.js +1 -1
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +167 -0
- package/src/actions/encodings/color/layout.js +99 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +193 -88
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -2
- package/src/actions/encodings/offset.js +1 -1
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/{position.js → position/index.js} +21 -8
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +25 -3
- package/src/actions/encodings/ranged.js +5 -3
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/options.js +1 -1
- package/src/actions/errorBars/options.js +1 -1
- package/src/actions/facets/actions.js +2 -3
- package/src/actions/facets/derive.js +35 -5
- package/src/actions/facets/guides.js +1 -1
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +153 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +12 -7
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/ticks.js +1 -1
- package/src/actions/guides/axes/titles.js +12 -9
- package/src/actions/guides/grids/resolve.js +1 -1
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +48 -11
- package/src/actions/guides/legends/categorical/layout.js +4 -2
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +7 -20
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +6 -31
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/strokeWidth.js +170 -0
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
- package/src/actions/guides/polar/index.js +1 -1
- package/src/actions/guides/polar/resolve.js +1 -1
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/index.js +6 -0
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +2 -2
- package/src/actions/marks/area/actions.js +61 -137
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +100 -81
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +85 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
- package/src/actions/marks/rect/actions.js +4 -1
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +26 -4
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +20 -5
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +20 -22
- package/src/actions/scales/consumers/common.js +109 -0
- package/src/actions/scales/consumers/families.js +58 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +32 -2
- package/src/actions/scales/edit.js +8 -206
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +3 -3
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +28 -3
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/ChartProgram.js +22 -236
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +7 -2
- package/src/grammar/arcs.js +50 -13
- package/src/grammar/areaSeries.js +42 -6
- package/src/grammar/bars/aggregate.js +1 -1
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +253 -27
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/{facets.js → facets/index.js} +8 -6
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +70 -1
- package/src/grammar/paint.js +88 -0
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/polarLineCommands.js +1 -1
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +2 -1
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/transforms.js +101 -12
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/bars/aggregate.js +1 -1
- package/src/materialization/bars/grouped.js +1 -1
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +15 -101
- package/src/materialization/encodings.js +2 -2
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +161 -0
- package/src/materialization/marks/index.js +108 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +91 -0
- package/src/materialization/rect.js +1 -1
- package/src/materialization/rowEncoding.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +102 -0
- package/src/materialization/scales/map.js +1 -1
- package/src/materialization/scales/policies/series.js +1 -1
- package/src/materialization/scales/resolve.js +3 -1
- package/src/materialization/selection/items/bar.js +1 -1
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +21 -10
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/renderers/canvas/text.js +14 -1
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +10 -1
- package/types/index.d.ts +76 -0
- package/types/program.d.ts +632 -10
- package/src/actions/encodings/color.js +0 -424
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/facetGuides.js +0 -426
- package/src/materialization/marks.js +0 -316
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
|
+
|
|
3
|
+
export function requireRegressionField(field, label) {
|
|
4
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
5
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
6
|
+
}
|
|
7
|
+
return field;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeRegressionParameters({
|
|
11
|
+
method = "linear",
|
|
12
|
+
degree,
|
|
13
|
+
span,
|
|
14
|
+
confidence,
|
|
15
|
+
interval
|
|
16
|
+
} = {}) {
|
|
17
|
+
if (!["linear", "polynomial", "loess"].includes(method)) {
|
|
18
|
+
throw new Error(`Unsupported regression method "${method}".`);
|
|
19
|
+
}
|
|
20
|
+
if (method === "loess") {
|
|
21
|
+
if (degree !== undefined) {
|
|
22
|
+
throw new Error("Regression degree requires the polynomial method.");
|
|
23
|
+
}
|
|
24
|
+
if (confidence !== undefined || interval !== undefined) {
|
|
25
|
+
throw new Error("LOESS regression does not support confidence intervals.");
|
|
26
|
+
}
|
|
27
|
+
const resolvedSpan = span ?? 0.75;
|
|
28
|
+
if (!Number.isFinite(resolvedSpan) || resolvedSpan <= 0 || resolvedSpan > 1) {
|
|
29
|
+
throw new RangeError(
|
|
30
|
+
"Regression LOESS span must be greater than zero and at most one."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return cloneAndFreeze({ method, span: resolvedSpan });
|
|
34
|
+
}
|
|
35
|
+
if (span !== undefined) {
|
|
36
|
+
throw new Error("Regression span requires the loess method.");
|
|
37
|
+
}
|
|
38
|
+
const resolvedConfidence = confidence ?? 0.95;
|
|
39
|
+
if (
|
|
40
|
+
!Number.isFinite(resolvedConfidence) ||
|
|
41
|
+
resolvedConfidence <= 0 ||
|
|
42
|
+
resolvedConfidence >= 1
|
|
43
|
+
) {
|
|
44
|
+
throw new RangeError("Regression confidence must be between 0 and 1.");
|
|
45
|
+
}
|
|
46
|
+
const resolvedInterval = interval ?? "mean";
|
|
47
|
+
if (!["mean", "prediction"].includes(resolvedInterval)) {
|
|
48
|
+
throw new Error(`Unsupported regression interval "${resolvedInterval}".`);
|
|
49
|
+
}
|
|
50
|
+
if (method === "polynomial") {
|
|
51
|
+
const resolvedDegree = degree ?? 2;
|
|
52
|
+
if (!Number.isInteger(resolvedDegree) || resolvedDegree < 1) {
|
|
53
|
+
throw new RangeError(
|
|
54
|
+
"Regression polynomial degree must be a positive integer."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return cloneAndFreeze({
|
|
58
|
+
method,
|
|
59
|
+
degree: resolvedDegree,
|
|
60
|
+
confidence: resolvedConfidence,
|
|
61
|
+
interval: resolvedInterval
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (degree !== undefined) {
|
|
65
|
+
throw new Error("Regression degree requires the polynomial method.");
|
|
66
|
+
}
|
|
67
|
+
return cloneAndFreeze({
|
|
68
|
+
method,
|
|
69
|
+
confidence: resolvedConfidence,
|
|
70
|
+
interval: resolvedInterval
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateRegressionTransform(transform) {
|
|
75
|
+
const supported = [
|
|
76
|
+
"type", "method", "x", "y", "groupBy", "confidence", "interval",
|
|
77
|
+
"degree", "span"
|
|
78
|
+
];
|
|
79
|
+
const unknown = Object.keys(transform).find(key => !supported.includes(key));
|
|
80
|
+
if (unknown !== undefined) {
|
|
81
|
+
throw new Error(`Unknown regression transform property "${unknown}".`);
|
|
82
|
+
}
|
|
83
|
+
if (transform.type !== "regression") {
|
|
84
|
+
throw new Error(`Unsupported regression transform "${transform.type}".`);
|
|
85
|
+
}
|
|
86
|
+
requireRegressionField(transform.x, "Regression x field");
|
|
87
|
+
requireRegressionField(transform.y, "Regression y field");
|
|
88
|
+
if (transform.groupBy !== undefined) {
|
|
89
|
+
requireRegressionField(transform.groupBy, "Regression groupBy field");
|
|
90
|
+
}
|
|
91
|
+
const normalized = normalizeRegressionParameters({
|
|
92
|
+
method: transform.method,
|
|
93
|
+
degree: transform.degree,
|
|
94
|
+
span: transform.span,
|
|
95
|
+
confidence: transform.confidence,
|
|
96
|
+
interval: transform.interval
|
|
97
|
+
});
|
|
98
|
+
if (normalized.method === "loess") return transform;
|
|
99
|
+
if (!Number.isFinite(transform.confidence)) {
|
|
100
|
+
throw new RangeError("Regression confidence must be between 0 and 1.");
|
|
101
|
+
}
|
|
102
|
+
if (transform.interval === undefined) {
|
|
103
|
+
throw new Error(`Unsupported regression interval "${transform.interval}".`);
|
|
104
|
+
}
|
|
105
|
+
return transform;
|
|
106
|
+
}
|
package/src/grammar/rules.js
CHANGED
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
readQuantitativeField,
|
|
7
7
|
readTemporalField,
|
|
8
8
|
validateSemanticFieldType
|
|
9
|
-
} from "./scales.js";
|
|
9
|
+
} from "./scales/index.js";
|
|
10
10
|
|
|
11
11
|
const ENDPOINT_CHANNELS = Object.freeze(["x", "y", "x2", "y2"]);
|
|
12
12
|
const VALUE_CHANNELS = Object.freeze([
|
|
13
13
|
...ENDPOINT_CHANNELS,
|
|
14
14
|
"strokeDash",
|
|
15
|
+
"strokeWidth",
|
|
15
16
|
"opacity"
|
|
16
17
|
]);
|
|
17
18
|
|
|
@@ -20,6 +20,7 @@ export const NAMED_DASH_PATTERNS = cloneAndFreeze({
|
|
|
20
20
|
export { POINT_SHAPES } from "../pointShapes.js";
|
|
21
21
|
export const DEFAULT_SIZE_RANGE = cloneAndFreeze([24, 196]);
|
|
22
22
|
export const DEFAULT_OPACITY_RANGE = cloneAndFreeze([0.2, 1]);
|
|
23
|
+
export const DEFAULT_STROKE_WIDTH_RANGE = cloneAndFreeze([1, 8]);
|
|
23
24
|
|
|
24
25
|
export function validateColorRange(range) {
|
|
25
26
|
if (range === "auto") return range;
|
|
@@ -123,6 +124,21 @@ export function validateOpacityRange(range) {
|
|
|
123
124
|
return cloneAndFreeze(range);
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
export function validateStrokeWidthRange(range) {
|
|
128
|
+
if (range === "auto") return range;
|
|
129
|
+
if (
|
|
130
|
+
!Array.isArray(range) ||
|
|
131
|
+
range.length !== 2 ||
|
|
132
|
+
!range.every(value => Number.isFinite(value) && value >= 0) ||
|
|
133
|
+
range[0] > range[1]
|
|
134
|
+
) {
|
|
135
|
+
throw new TypeError(
|
|
136
|
+
"StrokeWidth range must be an ascending pair of non-negative finite widths."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return cloneAndFreeze(range);
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
export function validateOpacityValue(value, label = "Opacity") {
|
|
127
143
|
return validateUnitInterval(value, label);
|
|
128
144
|
}
|
|
@@ -166,6 +182,11 @@ export function resolveOpacityRange(range) {
|
|
|
166
182
|
return validated === "auto" ? DEFAULT_OPACITY_RANGE : validated;
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
export function resolveStrokeWidthRange(range) {
|
|
186
|
+
const validated = validateStrokeWidthRange(range);
|
|
187
|
+
return validated === "auto" ? DEFAULT_STROKE_WIDTH_RANGE : validated;
|
|
188
|
+
}
|
|
189
|
+
|
|
169
190
|
export function mapOrdinalValues(values, domain, range, options = {}) {
|
|
170
191
|
const hasUnknown = Object.hasOwn(options, "unknown");
|
|
171
192
|
const indices = new Map(domain.map((value, index) => [value, index]));
|
|
@@ -277,8 +277,10 @@ export function validateSequentialColorRange(value) {
|
|
|
277
277
|
throw new TypeError("Sequential color range requires colors or a palette descriptor.");
|
|
278
278
|
}
|
|
279
279
|
const palette = normalizePalette(value.palette);
|
|
280
|
-
if (palette.count !== undefined) {
|
|
281
|
-
throw new
|
|
280
|
+
if (palette.count !== undefined && palette.count < 2) {
|
|
281
|
+
throw new RangeError(
|
|
282
|
+
"Sequential palette count must be an integer of at least 2."
|
|
283
|
+
);
|
|
282
284
|
}
|
|
283
285
|
return cloneAndFreeze({ palette });
|
|
284
286
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
2
|
import { validatePathCommands } from "../pathCommands.js";
|
|
3
|
+
import { validateFillPaint } from "../paint.js";
|
|
3
4
|
import { validateGraphicProperty } from "./graphic.js";
|
|
4
5
|
|
|
5
6
|
const FINITE_PROPERTIES = new Set([
|
|
@@ -10,7 +11,7 @@ const NON_NEGATIVE_PROPERTIES = new Set([
|
|
|
10
11
|
"width", "height", "radius", "strokeWidth", "fontSize", "gap"
|
|
11
12
|
]);
|
|
12
13
|
const STRING_PROPERTIES = new Set([
|
|
13
|
-
"background", "
|
|
14
|
+
"background", "stroke", "fontFamily", "text", "textAlign",
|
|
14
15
|
"textBaseline"
|
|
15
16
|
]);
|
|
16
17
|
const TEXT_ALIGNS = new Set(["left", "right", "center", "start", "end"]);
|
|
@@ -19,6 +20,15 @@ const TEXT_BASELINES = new Set([
|
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
export function validateConcreteGraphicValue(type, property, value) {
|
|
23
|
+
if (property === "fill") {
|
|
24
|
+
if (typeof value === "string") {
|
|
25
|
+
validateFillPaint(value, `${type}.fill`);
|
|
26
|
+
} else if (type === "rect" || type === "path") {
|
|
27
|
+
validateFillPaint(value, `${type}.fill`);
|
|
28
|
+
} else {
|
|
29
|
+
throw new TypeError(`${type}.fill must be a non-empty string.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
if (FINITE_PROPERTIES.has(property) && !Number.isFinite(value)) {
|
|
23
33
|
throw new TypeError(`${type}.${property} must be a finite number.`);
|
|
24
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveTextBounds } from "../../core/textMetrics.js";
|
|
2
2
|
import { findGraphic, requireGraphic } from "./graphicTree.js";
|
|
3
3
|
|
|
4
4
|
function unionBounds(bounds) {
|
|
@@ -74,6 +74,17 @@ function pathPoints(commands) {
|
|
|
74
74
|
return points;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export function resolvePathCommandBounds(commands) {
|
|
78
|
+
const points = pathPoints(commands);
|
|
79
|
+
if (points.length === 0) return undefined;
|
|
80
|
+
return {
|
|
81
|
+
left: Math.min(...points.map(point => point.x)),
|
|
82
|
+
right: Math.max(...points.map(point => point.x)),
|
|
83
|
+
top: Math.min(...points.map(point => point.y)),
|
|
84
|
+
bottom: Math.max(...points.map(point => point.y))
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
function primitiveBounds(type, properties = {}) {
|
|
78
89
|
const strokeExtent = (properties.strokeWidth ?? 0) / 2;
|
|
79
90
|
if (type === "circle") {
|
|
@@ -115,47 +126,18 @@ function primitiveBounds(type, properties = {}) {
|
|
|
115
126
|
!Number.isFinite(properties.fontSize) ||
|
|
116
127
|
typeof properties.text !== "string"
|
|
117
128
|
) return undefined;
|
|
118
|
-
|
|
119
|
-
const align = properties.textAlign ?? "left";
|
|
120
|
-
const left = align === "center" ? -width / 2 :
|
|
121
|
-
["right", "end"].includes(align) ? -width : 0;
|
|
122
|
-
const baseline = properties.textBaseline ?? "alphabetic";
|
|
123
|
-
const [top, bottom] = baseline === "middle"
|
|
124
|
-
? [-properties.fontSize / 2, properties.fontSize / 2]
|
|
125
|
-
: ["top", "hanging"].includes(baseline)
|
|
126
|
-
? [0, properties.fontSize]
|
|
127
|
-
: ["bottom", "ideographic"].includes(baseline)
|
|
128
|
-
? [-properties.fontSize, 0]
|
|
129
|
-
: [-properties.fontSize * 0.8, properties.fontSize * 0.2];
|
|
130
|
-
const rotation = properties.rotation ?? 0;
|
|
131
|
-
const cosine = Math.cos(rotation);
|
|
132
|
-
const sine = Math.sin(rotation);
|
|
133
|
-
const corners = [
|
|
134
|
-
[left, top],
|
|
135
|
-
[left + width, top],
|
|
136
|
-
[left + width, bottom],
|
|
137
|
-
[left, bottom]
|
|
138
|
-
].map(([x, y]) => ({
|
|
139
|
-
x: properties.x + x * cosine - y * sine,
|
|
140
|
-
y: properties.y + x * sine + y * cosine
|
|
141
|
-
}));
|
|
142
|
-
return {
|
|
143
|
-
left: Math.min(...corners.map(point => point.x)),
|
|
144
|
-
right: Math.max(...corners.map(point => point.x)),
|
|
145
|
-
top: Math.min(...corners.map(point => point.y)),
|
|
146
|
-
bottom: Math.max(...corners.map(point => point.y))
|
|
147
|
-
};
|
|
129
|
+
return resolveTextBounds(properties);
|
|
148
130
|
}
|
|
149
131
|
if (type === "path") {
|
|
150
132
|
const commands = properties.commands;
|
|
151
133
|
if (!Array.isArray(commands)) return undefined;
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
134
|
+
const bounds = resolvePathCommandBounds(commands);
|
|
135
|
+
if (bounds === undefined) return undefined;
|
|
154
136
|
return {
|
|
155
|
-
left:
|
|
156
|
-
right:
|
|
157
|
-
top:
|
|
158
|
-
bottom:
|
|
137
|
+
left: bounds.left - strokeExtent,
|
|
138
|
+
right: bounds.right + strokeExtent,
|
|
139
|
+
top: bounds.top - strokeExtent,
|
|
140
|
+
bottom: bounds.bottom + strokeExtent
|
|
159
141
|
};
|
|
160
142
|
}
|
|
161
143
|
return undefined;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "../../core/vocabulary.js";
|
|
7
7
|
|
|
8
8
|
const ENCODING_PATHS = Object.freeze([
|
|
9
|
-
...ENCODING_CHANNELS.flatMap(channel => [
|
|
9
|
+
...ENCODING_CHANNELS.filter(channel => channel !== "pathOrder").flatMap(channel => [
|
|
10
10
|
`encoding.${channel}.field`,
|
|
11
11
|
`encoding.${channel}.datum`,
|
|
12
12
|
`encoding.${channel}.fieldType`,
|
|
@@ -20,8 +20,15 @@ const ENCODING_PATHS = Object.freeze([
|
|
|
20
20
|
"encoding.x.bin.boundaries",
|
|
21
21
|
"encoding.color.layout",
|
|
22
22
|
"encoding.color.aggregate",
|
|
23
|
+
"encoding.pathOrder.field",
|
|
24
|
+
"encoding.pathOrder.fieldType",
|
|
25
|
+
"encoding.pathOrder.order",
|
|
23
26
|
"encoding.theta.aggregate",
|
|
27
|
+
"encoding.theta.weight",
|
|
24
28
|
"encoding.text.format",
|
|
29
|
+
"encoding.parallel.dimensions",
|
|
30
|
+
"encoding.parallel.key",
|
|
31
|
+
"encoding.parallel.missing",
|
|
25
32
|
...CARTESIAN_POSITION_CHANNELS.flatMap(channel => [
|
|
26
33
|
`encoding.${channel}.aggregate`,
|
|
27
34
|
`encoding.${channel}.stack`
|
|
@@ -36,7 +43,8 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
36
43
|
layer: {
|
|
37
44
|
collection: "layers",
|
|
38
45
|
removableContainers: new Set(
|
|
39
|
-
ENCODING_CHANNELS.map(channel => `encoding.${channel}`)
|
|
46
|
+
[...ENCODING_CHANNELS.map(channel => `encoding.${channel}`),
|
|
47
|
+
"encoding.parallel"]
|
|
40
48
|
),
|
|
41
49
|
properties: new Set([
|
|
42
50
|
"data",
|
|
@@ -74,10 +82,15 @@ const GUIDE_PATHS = new Set([
|
|
|
74
82
|
"axis.radius.scale",
|
|
75
83
|
"axis.radius.coordinate",
|
|
76
84
|
"axis.radius.title",
|
|
85
|
+
"axis.parallel.target",
|
|
86
|
+
"axis.parallel.coordinate",
|
|
87
|
+
"axis.parallel.scales",
|
|
77
88
|
"legend.color.scale",
|
|
78
89
|
"legend.color.title",
|
|
79
90
|
"legend.size.scale",
|
|
80
91
|
"legend.size.title",
|
|
92
|
+
"legend.strokeWidth.scale",
|
|
93
|
+
"legend.strokeWidth.title",
|
|
81
94
|
"legend.opacity.scale",
|
|
82
95
|
"legend.opacity.title",
|
|
83
96
|
"legend.series.channels",
|
|
@@ -98,12 +111,14 @@ const GUIDE_REMOVABLE_CONTAINERS = new Set([
|
|
|
98
111
|
"axis.y",
|
|
99
112
|
"axis.theta",
|
|
100
113
|
"axis.radius",
|
|
114
|
+
"axis.parallel",
|
|
101
115
|
"grid.horizontal",
|
|
102
116
|
"grid.vertical",
|
|
103
117
|
"grid.theta",
|
|
104
118
|
"grid.radial",
|
|
105
119
|
"legend.color",
|
|
106
120
|
"legend.size",
|
|
121
|
+
"legend.strokeWidth",
|
|
107
122
|
"legend.opacity",
|
|
108
123
|
"legend.series"
|
|
109
124
|
]);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const LANCZOS = Object.freeze([
|
|
2
|
+
676.5203681218851,
|
|
3
|
+
-1259.1392167224028,
|
|
4
|
+
771.3234287776531,
|
|
5
|
+
-176.6150291621406,
|
|
6
|
+
12.507343278686905,
|
|
7
|
+
-0.13857109526572012,
|
|
8
|
+
9.984369578019572e-6,
|
|
9
|
+
1.5056327351493116e-7
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
function logGamma(value) {
|
|
13
|
+
if (value < 0.5) {
|
|
14
|
+
return Math.log(Math.PI) - Math.log(Math.sin(Math.PI * value)) -
|
|
15
|
+
logGamma(1 - value);
|
|
16
|
+
}
|
|
17
|
+
const shifted = value - 1;
|
|
18
|
+
let sum = 0.9999999999998099;
|
|
19
|
+
for (const [index, coefficient] of LANCZOS.entries()) {
|
|
20
|
+
sum += coefficient / (shifted + index + 1);
|
|
21
|
+
}
|
|
22
|
+
const base = shifted + LANCZOS.length - 0.5;
|
|
23
|
+
return 0.5 * Math.log(2 * Math.PI) +
|
|
24
|
+
(shifted + 0.5) * Math.log(base) - base + Math.log(sum);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function betaContinuedFraction(a, b, value) {
|
|
28
|
+
const maximumIterations = 200;
|
|
29
|
+
const epsilon = 3e-14;
|
|
30
|
+
const floor = 1e-300;
|
|
31
|
+
const sum = a + b;
|
|
32
|
+
let c = 1;
|
|
33
|
+
let d = 1 - sum * value / (a + 1);
|
|
34
|
+
if (Math.abs(d) < floor) d = floor;
|
|
35
|
+
d = 1 / d;
|
|
36
|
+
let result = d;
|
|
37
|
+
|
|
38
|
+
for (let iteration = 1; iteration <= maximumIterations; iteration += 1) {
|
|
39
|
+
const even = 2 * iteration;
|
|
40
|
+
let coefficient = iteration * (b - iteration) * value /
|
|
41
|
+
((a + even - 1) * (a + even));
|
|
42
|
+
d = 1 + coefficient * d;
|
|
43
|
+
if (Math.abs(d) < floor) d = floor;
|
|
44
|
+
c = 1 + coefficient / c;
|
|
45
|
+
if (Math.abs(c) < floor) c = floor;
|
|
46
|
+
d = 1 / d;
|
|
47
|
+
result *= d * c;
|
|
48
|
+
|
|
49
|
+
coefficient = -(a + iteration) * (sum + iteration) * value /
|
|
50
|
+
((a + even) * (a + even + 1));
|
|
51
|
+
d = 1 + coefficient * d;
|
|
52
|
+
if (Math.abs(d) < floor) d = floor;
|
|
53
|
+
c = 1 + coefficient / c;
|
|
54
|
+
if (Math.abs(c) < floor) c = floor;
|
|
55
|
+
d = 1 / d;
|
|
56
|
+
const delta = d * c;
|
|
57
|
+
result *= delta;
|
|
58
|
+
if (Math.abs(delta - 1) <= epsilon) return result;
|
|
59
|
+
}
|
|
60
|
+
throw new Error("Student-t calculation did not converge.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function regularizedIncompleteBeta(value, a, b) {
|
|
64
|
+
if (value === 0 || value === 1) return value;
|
|
65
|
+
const factor = Math.exp(
|
|
66
|
+
logGamma(a + b) - logGamma(a) - logGamma(b) +
|
|
67
|
+
a * Math.log(value) + b * Math.log1p(-value)
|
|
68
|
+
);
|
|
69
|
+
if (value < (a + 1) / (a + b + 2)) {
|
|
70
|
+
return factor * betaContinuedFraction(a, b, value) / a;
|
|
71
|
+
}
|
|
72
|
+
return 1 - factor * betaContinuedFraction(b, a, 1 - value) / b;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function studentTCdf(value, degreesOfFreedom) {
|
|
76
|
+
if (value === 0) return 0.5;
|
|
77
|
+
const ratio = degreesOfFreedom /
|
|
78
|
+
(degreesOfFreedom + value * value);
|
|
79
|
+
const tail = regularizedIncompleteBeta(
|
|
80
|
+
ratio,
|
|
81
|
+
degreesOfFreedom / 2,
|
|
82
|
+
0.5
|
|
83
|
+
) / 2;
|
|
84
|
+
return value > 0 ? 1 - tail : tail;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function studentTCriticalValue(level, degreesOfFreedom) {
|
|
88
|
+
const probability = (1 + level) / 2;
|
|
89
|
+
let low = 0;
|
|
90
|
+
let high = 1;
|
|
91
|
+
while (studentTCdf(high, degreesOfFreedom) < probability) high *= 2;
|
|
92
|
+
for (let iteration = 0; iteration < 100; iteration += 1) {
|
|
93
|
+
const midpoint = (low + high) / 2;
|
|
94
|
+
if (studentTCdf(midpoint, degreesOfFreedom) < probability) low = midpoint;
|
|
95
|
+
else high = midpoint;
|
|
96
|
+
}
|
|
97
|
+
return (low + high) / 2;
|
|
98
|
+
}
|
|
@@ -1,29 +1,118 @@
|
|
|
1
1
|
import { isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { validateBoxTransform } from "./boxPlot.js";
|
|
3
|
+
import {
|
|
4
|
+
requestedBin2DTransform,
|
|
5
|
+
validateBin2DTransform
|
|
6
|
+
} from "./bin2d.js";
|
|
3
7
|
import { validateDensityTransform } from "./density.js";
|
|
4
8
|
import { validateFilterTransform } from "./filter.js";
|
|
9
|
+
import {
|
|
10
|
+
requestedGradientProfileTransform,
|
|
11
|
+
validateGradientProfileTransform
|
|
12
|
+
} from "./gradientProfile.js";
|
|
5
13
|
import { validateIntervalTransform } from "./interval.js";
|
|
14
|
+
import {
|
|
15
|
+
requestedHorizonTransform,
|
|
16
|
+
validateHorizonTransform
|
|
17
|
+
} from "./horizon.js";
|
|
6
18
|
import { validateMarkFilterTransform } from "./markFilter.js";
|
|
7
|
-
import { validateRegressionTransform } from "./regression.js";
|
|
19
|
+
import { validateRegressionTransform } from "./regression/index.js";
|
|
20
|
+
import { validateWindowTransform } from "./window.js";
|
|
8
21
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
function requestedDensityTransform(transform) {
|
|
23
|
+
const { resolved: _resolved, ...requested } = transform;
|
|
24
|
+
return requested;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function facetHorizonTransform(transform, { scales = {} } = {}) {
|
|
28
|
+
if (
|
|
29
|
+
transform.extent !== "auto" ||
|
|
30
|
+
(scales.y ?? "shared") !== "shared" ||
|
|
31
|
+
!Array.isArray(transform.resolved?.extents)
|
|
32
|
+
) return transform;
|
|
33
|
+
const extent = Math.max(...transform.resolved.extents.map(item => item.extent));
|
|
34
|
+
if (!(extent > 0)) return transform;
|
|
35
|
+
return {
|
|
36
|
+
...requestedHorizonTransform(transform),
|
|
37
|
+
extent
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const TRANSFORM_POLICIES = Object.freeze({
|
|
42
|
+
bin2d: Object.freeze({
|
|
43
|
+
validate: validateBin2DTransform,
|
|
44
|
+
materializeOp: "materializeBin2DData",
|
|
45
|
+
facetTopology: "statistical",
|
|
46
|
+
replayTransform: requestedBin2DTransform
|
|
47
|
+
}),
|
|
48
|
+
boxOutlier: Object.freeze({
|
|
49
|
+
validate: validateBoxTransform,
|
|
50
|
+
materializeOp: "materializeBoxOutlierData",
|
|
51
|
+
facetTopology: "statistical"
|
|
52
|
+
}),
|
|
53
|
+
boxSummary: Object.freeze({
|
|
54
|
+
validate: validateBoxTransform,
|
|
55
|
+
materializeOp: "materializeBoxSummaryData",
|
|
56
|
+
facetTopology: "statistical"
|
|
57
|
+
}),
|
|
58
|
+
density: Object.freeze({
|
|
59
|
+
validate: validateDensityTransform,
|
|
60
|
+
materializeOp: "materializeDensityData",
|
|
61
|
+
facetTopology: "statistical",
|
|
62
|
+
replayTransform: requestedDensityTransform
|
|
63
|
+
}),
|
|
64
|
+
filter: Object.freeze({
|
|
65
|
+
validate: validateFilterTransform,
|
|
66
|
+
materializeOp: "materializeFilteredData",
|
|
67
|
+
facetTopology: "rowPreserving"
|
|
68
|
+
}),
|
|
69
|
+
gradientProfile: Object.freeze({
|
|
70
|
+
validate: validateGradientProfileTransform,
|
|
71
|
+
materializeOp: "materializeGradientProfileData",
|
|
72
|
+
facetTopology: "statistical",
|
|
73
|
+
replayTransform: requestedGradientProfileTransform
|
|
74
|
+
}),
|
|
75
|
+
horizon: Object.freeze({
|
|
76
|
+
validate: validateHorizonTransform,
|
|
77
|
+
materializeOp: "materializeHorizonData",
|
|
78
|
+
facetTopology: "statistical",
|
|
79
|
+
replayTransform: requestedHorizonTransform,
|
|
80
|
+
facetReplayTransform: facetHorizonTransform
|
|
81
|
+
}),
|
|
82
|
+
interval: Object.freeze({
|
|
83
|
+
validate: validateIntervalTransform,
|
|
84
|
+
materializeOp: "materializeIntervalData",
|
|
85
|
+
facetTopology: "statistical"
|
|
86
|
+
}),
|
|
87
|
+
markFilter: Object.freeze({
|
|
88
|
+
validate: validateMarkFilterTransform,
|
|
89
|
+
materializeOp: "materializeMarkFilteredData",
|
|
90
|
+
provenanceTransparent: true
|
|
91
|
+
}),
|
|
92
|
+
regression: Object.freeze({
|
|
93
|
+
validate: validateRegressionTransform,
|
|
94
|
+
materializeOp: "materializeRegressionData",
|
|
95
|
+
facetTopology: "statistical"
|
|
96
|
+
}),
|
|
97
|
+
window: Object.freeze({
|
|
98
|
+
validate: validateWindowTransform,
|
|
99
|
+
materializeOp: "materializeWindowData",
|
|
100
|
+
facetTopology: "statistical"
|
|
101
|
+
})
|
|
17
102
|
});
|
|
18
103
|
|
|
104
|
+
export function findTransformPolicy(type) {
|
|
105
|
+
return TRANSFORM_POLICIES[type];
|
|
106
|
+
}
|
|
107
|
+
|
|
19
108
|
export function findTransformValidator(type) {
|
|
20
|
-
return
|
|
109
|
+
return findTransformPolicy(type)?.validate;
|
|
21
110
|
}
|
|
22
111
|
|
|
23
112
|
export function validateDatasetTransforms(value) {
|
|
24
|
-
if (!Array.isArray(value) || value.length
|
|
113
|
+
if (!Array.isArray(value) || value.length !== 1 || !isPlainObject(value[0])) {
|
|
25
114
|
throw new TypeError(
|
|
26
|
-
"Dataset transform must
|
|
115
|
+
"Dataset transform must contain exactly one plain object."
|
|
27
116
|
);
|
|
28
117
|
}
|
|
29
118
|
for (const transform of value) {
|