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
package/src/grammar/interval.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { aggregateScalarValues } from "./aggregate.js";
|
|
3
|
+
import { studentTCriticalValue } from "./statistics/studentT.js";
|
|
3
4
|
|
|
4
5
|
const CENTER_VALUES = Object.freeze(["mean", "median"]);
|
|
5
6
|
const EXTENT_VALUES = Object.freeze(["stderr", "stdev", "ci", "iqr"]);
|
|
@@ -133,92 +134,6 @@ export function normalizeIntervalParameters({
|
|
|
133
134
|
});
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const LANCZOS = Object.freeze([
|
|
137
|
-
676.5203681218851,
|
|
138
|
-
-1259.1392167224028,
|
|
139
|
-
771.3234287776531,
|
|
140
|
-
-176.6150291621406,
|
|
141
|
-
12.507343278686905,
|
|
142
|
-
-0.13857109526572012,
|
|
143
|
-
9.984369578019572e-6,
|
|
144
|
-
1.5056327351493116e-7
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
function logGamma(value) {
|
|
148
|
-
if (value < 0.5) {
|
|
149
|
-
return Math.log(Math.PI) - Math.log(Math.sin(Math.PI * value)) -
|
|
150
|
-
logGamma(1 - value);
|
|
151
|
-
}
|
|
152
|
-
const shifted = value - 1;
|
|
153
|
-
let sum = 0.9999999999998099;
|
|
154
|
-
for (const [index, coefficient] of LANCZOS.entries()) {
|
|
155
|
-
sum += coefficient / (shifted + index + 1);
|
|
156
|
-
}
|
|
157
|
-
const t = shifted + LANCZOS.length - 0.5;
|
|
158
|
-
return 0.5 * Math.log(2 * Math.PI) +
|
|
159
|
-
(shifted + 0.5) * Math.log(t) - t + Math.log(sum);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function betaFraction(a, b, x) {
|
|
163
|
-
const maximumIterations = 200;
|
|
164
|
-
const epsilon = 3e-14;
|
|
165
|
-
const floor = 1e-300;
|
|
166
|
-
const sum = a + b;
|
|
167
|
-
let c = 1;
|
|
168
|
-
let d = 1 - sum * x / (a + 1);
|
|
169
|
-
if (Math.abs(d) < floor) d = floor;
|
|
170
|
-
d = 1 / d;
|
|
171
|
-
let result = d;
|
|
172
|
-
|
|
173
|
-
for (let iteration = 1; iteration <= maximumIterations; iteration += 1) {
|
|
174
|
-
const even = 2 * iteration;
|
|
175
|
-
let coefficient = iteration * (b - iteration) * x /
|
|
176
|
-
((a + even - 1) * (a + even));
|
|
177
|
-
d = 1 + coefficient * d;
|
|
178
|
-
if (Math.abs(d) < floor) d = floor;
|
|
179
|
-
c = 1 + coefficient / c;
|
|
180
|
-
if (Math.abs(c) < floor) c = floor;
|
|
181
|
-
d = 1 / d;
|
|
182
|
-
result *= d * c;
|
|
183
|
-
|
|
184
|
-
coefficient = -(a + iteration) * (sum + iteration) * x /
|
|
185
|
-
((a + even) * (a + even + 1));
|
|
186
|
-
d = 1 + coefficient * d;
|
|
187
|
-
if (Math.abs(d) < floor) d = floor;
|
|
188
|
-
c = 1 + coefficient / c;
|
|
189
|
-
if (Math.abs(c) < floor) c = floor;
|
|
190
|
-
d = 1 / d;
|
|
191
|
-
const delta = d * c;
|
|
192
|
-
result *= delta;
|
|
193
|
-
if (Math.abs(delta - 1) <= epsilon) return result;
|
|
194
|
-
}
|
|
195
|
-
throw new Error("Student-t calculation did not converge.");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function regularizedBeta(x, a, b) {
|
|
199
|
-
if (x <= 0) return 0;
|
|
200
|
-
if (x >= 1) return 1;
|
|
201
|
-
const front = Math.exp(
|
|
202
|
-
logGamma(a + b) - logGamma(a) - logGamma(b) +
|
|
203
|
-
a * Math.log(x) + b * Math.log1p(-x)
|
|
204
|
-
);
|
|
205
|
-
return x < (a + 1) / (a + b + 2)
|
|
206
|
-
? front * betaFraction(a, b, x) / a
|
|
207
|
-
: 1 - front * betaFraction(b, a, 1 - x) / b;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function studentTCdf(value, degreesOfFreedom) {
|
|
211
|
-
if (value === 0) return 0.5;
|
|
212
|
-
const x = degreesOfFreedom /
|
|
213
|
-
(degreesOfFreedom + value * value);
|
|
214
|
-
const tail = 0.5 * regularizedBeta(
|
|
215
|
-
x,
|
|
216
|
-
degreesOfFreedom / 2,
|
|
217
|
-
0.5
|
|
218
|
-
);
|
|
219
|
-
return value > 0 ? 1 - tail : tail;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
137
|
export function studentTCritical(degreesOfFreedom, level) {
|
|
223
138
|
if (!Number.isInteger(degreesOfFreedom) || degreesOfFreedom < 1) {
|
|
224
139
|
throw new RangeError("Student-t degreesOfFreedom must be a positive integer.");
|
|
@@ -226,16 +141,7 @@ export function studentTCritical(degreesOfFreedom, level) {
|
|
|
226
141
|
if (!Number.isFinite(level) || level <= 0 || level >= 1) {
|
|
227
142
|
throw new RangeError("Student-t level must be between 0 and 1.");
|
|
228
143
|
}
|
|
229
|
-
|
|
230
|
-
let low = 0;
|
|
231
|
-
let high = 1;
|
|
232
|
-
while (studentTCdf(high, degreesOfFreedom) < target) high *= 2;
|
|
233
|
-
for (let iteration = 0; iteration < 100; iteration += 1) {
|
|
234
|
-
const midpoint = (low + high) / 2;
|
|
235
|
-
if (studentTCdf(midpoint, degreesOfFreedom) < target) low = midpoint;
|
|
236
|
-
else high = midpoint;
|
|
237
|
-
}
|
|
238
|
-
return (low + high) / 2;
|
|
144
|
+
return studentTCriticalValue(level, degreesOfFreedom);
|
|
239
145
|
}
|
|
240
146
|
|
|
241
147
|
function isMissing(value) {
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { validateNonEmptyString } from "../core/validation.js";
|
|
3
|
+
|
|
4
|
+
const UINT32_RANGE = 0x1_0000_0000;
|
|
5
|
+
const CATEGORICAL_SCALE_TYPES = new Set(["band", "point", "ordinal"]);
|
|
6
|
+
|
|
7
|
+
export const JITTER_ALGORITHM = "jitter-uniform-v1";
|
|
8
|
+
|
|
9
|
+
export function canonicalJitterScalar(value, label = "Jitter identity") {
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
return `string:${new TextEncoder().encode(value).length}:${value}`;
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
14
|
+
return `number:${Object.is(value, -0) ? "0" : String(value)}`;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === "boolean") return `boolean:${value}`;
|
|
17
|
+
throw new TypeError(`${label} must be a string, finite number, or boolean.`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function stableUint32HashV1(value) {
|
|
21
|
+
const bytes = new TextEncoder().encode(value);
|
|
22
|
+
let hash = 0x811c9dc5;
|
|
23
|
+
for (const byte of bytes) {
|
|
24
|
+
hash ^= byte;
|
|
25
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
26
|
+
}
|
|
27
|
+
return hash;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function jitterIdentity({ target, channel, seed, identity }) {
|
|
31
|
+
return [
|
|
32
|
+
canonicalJitterScalar(target, "Jitter target"),
|
|
33
|
+
canonicalJitterScalar(channel, "Jitter channel"),
|
|
34
|
+
canonicalJitterScalar(seed, "Jitter seed"),
|
|
35
|
+
canonicalJitterScalar(identity, "Jitter key")
|
|
36
|
+
].join("\0");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requestedOffset(options, maximum) {
|
|
40
|
+
const hash = stableUint32HashV1(jitterIdentity(options));
|
|
41
|
+
return (2 * (hash / UINT32_RANGE) - 1) * maximum;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizePointJitterPolicy(options = {}) {
|
|
45
|
+
if (!isPlainObject(options)) {
|
|
46
|
+
throw new TypeError("Point jitter options must be a plain object.");
|
|
47
|
+
}
|
|
48
|
+
if (!["x", "y"].includes(options.channel)) {
|
|
49
|
+
throw new Error('Point jitter channel must be "x" or "y".');
|
|
50
|
+
}
|
|
51
|
+
const maxOffset = options.maxOffset;
|
|
52
|
+
if (!isPlainObject(maxOffset)) {
|
|
53
|
+
throw new TypeError("Point jitter maxOffset must be a plain object.");
|
|
54
|
+
}
|
|
55
|
+
const keys = Object.keys(maxOffset);
|
|
56
|
+
if (
|
|
57
|
+
keys.length !== 1 ||
|
|
58
|
+
!["pixels", "band"].includes(keys[0])
|
|
59
|
+
) {
|
|
60
|
+
throw new Error("Point jitter maxOffset requires exactly one of pixels or band.");
|
|
61
|
+
}
|
|
62
|
+
const kind = keys[0];
|
|
63
|
+
const value = maxOffset[kind];
|
|
64
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
65
|
+
throw new RangeError(`Point jitter ${kind} must be positive and finite.`);
|
|
66
|
+
}
|
|
67
|
+
if (kind === "band" && value > 0.5) {
|
|
68
|
+
throw new RangeError("Point jitter band must be at most 0.5.");
|
|
69
|
+
}
|
|
70
|
+
const seed = options.seed ?? 0;
|
|
71
|
+
if (!(
|
|
72
|
+
typeof seed === "string" ||
|
|
73
|
+
(typeof seed === "number" && Number.isFinite(seed))
|
|
74
|
+
)) {
|
|
75
|
+
throw new TypeError("Point jitter seed must be a string or finite number.");
|
|
76
|
+
}
|
|
77
|
+
const key = options.key === undefined
|
|
78
|
+
? undefined
|
|
79
|
+
: validateNonEmptyString(options.key, "Point jitter key");
|
|
80
|
+
return cloneAndFreeze({
|
|
81
|
+
algorithm: JITTER_ALGORITHM,
|
|
82
|
+
channel: options.channel,
|
|
83
|
+
maxOffset: { [kind]: value },
|
|
84
|
+
seed,
|
|
85
|
+
...(key === undefined ? {} : { key })
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveJitterSlotWidth(scale) {
|
|
90
|
+
if (!CATEGORICAL_SCALE_TYPES.has(scale?.type)) return undefined;
|
|
91
|
+
const width = scale.bandwidth > 0
|
|
92
|
+
? scale.bandwidth
|
|
93
|
+
: Math.abs(scale.step);
|
|
94
|
+
return Number.isFinite(width) && width > 0 ? width : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function resolveJitterMaximum(policy, scale) {
|
|
98
|
+
if (policy.maxOffset.pixels !== undefined) {
|
|
99
|
+
return policy.maxOffset.pixels;
|
|
100
|
+
}
|
|
101
|
+
const slotWidth = resolveJitterSlotWidth(scale);
|
|
102
|
+
if (slotWidth === undefined) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"Band point jitter requires a categorical position scale with a positive slot."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return slotWidth * policy.maxOffset.band;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function containOffset({
|
|
111
|
+
base,
|
|
112
|
+
requested,
|
|
113
|
+
plotMinimum,
|
|
114
|
+
plotMaximum,
|
|
115
|
+
halfExtent,
|
|
116
|
+
slotWidth
|
|
117
|
+
}) {
|
|
118
|
+
let minimum = plotMinimum + halfExtent;
|
|
119
|
+
let maximum = plotMaximum - halfExtent;
|
|
120
|
+
if (slotWidth !== undefined) {
|
|
121
|
+
minimum = Math.max(minimum, base - slotWidth / 2 + halfExtent);
|
|
122
|
+
maximum = Math.min(maximum, base + slotWidth / 2 - halfExtent);
|
|
123
|
+
}
|
|
124
|
+
if (minimum > maximum) {
|
|
125
|
+
return { offset: 0, clamped: true, available: false };
|
|
126
|
+
}
|
|
127
|
+
const final = Math.max(minimum, Math.min(maximum, base + requested));
|
|
128
|
+
return {
|
|
129
|
+
offset: final - base,
|
|
130
|
+
clamped: final !== base + requested,
|
|
131
|
+
available: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function resolvePointJitter({
|
|
136
|
+
target,
|
|
137
|
+
policy,
|
|
138
|
+
scale,
|
|
139
|
+
entries,
|
|
140
|
+
plotMinimum,
|
|
141
|
+
plotMaximum
|
|
142
|
+
}) {
|
|
143
|
+
validateNonEmptyString(target, "Point jitter target");
|
|
144
|
+
if (!Array.isArray(entries)) {
|
|
145
|
+
throw new TypeError("Point jitter entries must be an array.");
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
!Number.isFinite(plotMinimum) ||
|
|
149
|
+
!Number.isFinite(plotMaximum) ||
|
|
150
|
+
plotMaximum < plotMinimum
|
|
151
|
+
) {
|
|
152
|
+
throw new RangeError("Point jitter plot bounds are invalid.");
|
|
153
|
+
}
|
|
154
|
+
const maximum = resolveJitterMaximum(policy, scale);
|
|
155
|
+
const slotWidth = resolveJitterSlotWidth(scale);
|
|
156
|
+
const items = entries.map((entry, itemIndex) => {
|
|
157
|
+
if (!Number.isInteger(entry.index) || entry.index < 0) {
|
|
158
|
+
throw new TypeError(`Point jitter entry ${itemIndex} index must be non-negative.`);
|
|
159
|
+
}
|
|
160
|
+
if (!Number.isFinite(entry.base) || !Number.isFinite(entry.halfExtent)) {
|
|
161
|
+
throw new TypeError(`Point jitter entry ${itemIndex} geometry must be finite.`);
|
|
162
|
+
}
|
|
163
|
+
if (entry.halfExtent < 0) {
|
|
164
|
+
throw new RangeError(`Point jitter entry ${itemIndex} extent must not be negative.`);
|
|
165
|
+
}
|
|
166
|
+
const requested = requestedOffset({
|
|
167
|
+
target,
|
|
168
|
+
channel: policy.channel,
|
|
169
|
+
seed: policy.seed,
|
|
170
|
+
identity: entry.identity
|
|
171
|
+
}, maximum);
|
|
172
|
+
const contained = containOffset({
|
|
173
|
+
base: entry.base,
|
|
174
|
+
requested,
|
|
175
|
+
plotMinimum,
|
|
176
|
+
plotMaximum,
|
|
177
|
+
halfExtent: entry.halfExtent,
|
|
178
|
+
slotWidth
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
index: entry.index,
|
|
182
|
+
identity: entry.identity,
|
|
183
|
+
requestedOffset: requested,
|
|
184
|
+
finalOffset: contained.offset,
|
|
185
|
+
clamped: contained.clamped,
|
|
186
|
+
available: contained.available
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
return cloneAndFreeze({
|
|
190
|
+
algorithm: JITTER_ALGORITHM,
|
|
191
|
+
maximum,
|
|
192
|
+
itemCount: items.length,
|
|
193
|
+
clampedItemCount: items.filter(item => item.clamped).length,
|
|
194
|
+
unavailableItemCount: items.filter(item => !item.available).length,
|
|
195
|
+
items
|
|
196
|
+
});
|
|
197
|
+
}
|
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
readNominalField,
|
|
4
4
|
readQuantitativeField,
|
|
5
5
|
readTemporalField
|
|
6
|
-
} from "./scales.js";
|
|
6
|
+
} from "./scales/index.js";
|
|
7
7
|
import {
|
|
8
8
|
aggregateRows,
|
|
9
9
|
isAggregate,
|
|
10
10
|
validateAggregateFieldType,
|
|
11
11
|
validateAggregateFieldValues,
|
|
12
12
|
} from "./aggregate.js";
|
|
13
|
+
import { stableOrderPathValues } from "./pathOrder.js";
|
|
13
14
|
|
|
14
15
|
const SERIES_CHANNELS = Object.freeze(["group", "color", "strokeDash"]);
|
|
15
16
|
|
|
@@ -101,6 +102,51 @@ function deriveCartesianLineSeries(rows, layer) {
|
|
|
101
102
|
: readQuantitativeField(rows, y.field);
|
|
102
103
|
const seriesFields = readSeriesFields(rows, layer);
|
|
103
104
|
|
|
105
|
+
const pathOrder = layer.encoding?.pathOrder;
|
|
106
|
+
if (pathOrder !== undefined) {
|
|
107
|
+
if (isAggregate) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Line mark "${layer.id}" cannot combine aggregate y and path order.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const orderValues = readQuantitativeField(rows, pathOrder.field);
|
|
113
|
+
const groups = new Map();
|
|
114
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
115
|
+
const dimensions = seriesFields.fields.map(
|
|
116
|
+
field => seriesFields.values.get(field)[index]
|
|
117
|
+
);
|
|
118
|
+
const key = groupKey(dimensions);
|
|
119
|
+
const series = groups.get(key) ?? {
|
|
120
|
+
key: Object.fromEntries(
|
|
121
|
+
seriesFields.fields.map((field, item) => [field, dimensions[item]])
|
|
122
|
+
),
|
|
123
|
+
values: [],
|
|
124
|
+
orderValues: []
|
|
125
|
+
};
|
|
126
|
+
series.values.push({ x: xValues[index], y: yValues[index] });
|
|
127
|
+
series.orderValues.push(orderValues[index]);
|
|
128
|
+
groups.set(key, series);
|
|
129
|
+
}
|
|
130
|
+
const series = [...groups.values()].flatMap(item => {
|
|
131
|
+
const values = stableOrderPathValues(
|
|
132
|
+
item.values,
|
|
133
|
+
item.orderValues,
|
|
134
|
+
pathOrder.order
|
|
135
|
+
);
|
|
136
|
+
return values.length < 2 ? [] : [{ key: item.key, values }];
|
|
137
|
+
});
|
|
138
|
+
if (series.length === 0) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Line series on mark "${layer.id}" requires at least two ordered points.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return cloneAndFreeze({
|
|
144
|
+
xValues: series.flatMap(item => item.values.map(value => value.x)),
|
|
145
|
+
yValues: series.flatMap(item => item.values.map(value => value.y)),
|
|
146
|
+
series
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
104
150
|
if (directTemporal) {
|
|
105
151
|
const groups = new Map();
|
|
106
152
|
for (let index = 0; index < rows.length; index += 1) {
|
|
@@ -249,6 +295,11 @@ function thetaOrder(values, fieldType, domain) {
|
|
|
249
295
|
}
|
|
250
296
|
|
|
251
297
|
export function derivePolarLineSeries(rows, layer, { thetaDomain } = {}) {
|
|
298
|
+
if (layer.encoding?.pathOrder !== undefined) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Polar line mark "${layer.id}" does not support path order.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
252
303
|
const { theta, radius } = requirePolarLineEncoding(layer);
|
|
253
304
|
const thetaValues = readThetaValues(rows, theta);
|
|
254
305
|
const radiusValues = readQuantitativeField(rows, radius.field);
|
|
@@ -303,3 +354,21 @@ export function deriveLineSeries(rows, layer, options) {
|
|
|
303
354
|
? derivePolarLineSeries(rows, layer, options)
|
|
304
355
|
: deriveCartesianLineSeries(rows, layer);
|
|
305
356
|
}
|
|
357
|
+
|
|
358
|
+
export function deriveLineSeriesFieldValues(rows, layer, derived, field) {
|
|
359
|
+
const values = readQuantitativeField(rows, field);
|
|
360
|
+
if (values.some(value => value < 0)) {
|
|
361
|
+
throw new RangeError(`Line strokeWidth field "${field}" cannot contain negative values.`);
|
|
362
|
+
}
|
|
363
|
+
return cloneAndFreeze(derived.series.map(series => {
|
|
364
|
+
const matching = values.filter((_, index) => Object.entries(series.key)
|
|
365
|
+
.every(([key, value]) => rows[index]?.[key] === value));
|
|
366
|
+
const unique = [...new Set(matching)];
|
|
367
|
+
if (unique.length !== 1) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Line strokeWidth field "${field}" must have one value within each series.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return unique[0];
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isPlainObject } from "../core/immutable.js";
|
|
2
|
+
|
|
3
|
+
const PAINT_KEYS = Object.freeze(["type", "from", "to", "stops"]);
|
|
4
|
+
const POINT_KEYS = Object.freeze(["x", "y"]);
|
|
5
|
+
const STOP_KEYS = Object.freeze(["offset", "color"]);
|
|
6
|
+
|
|
7
|
+
function hasExactKeys(value, keys) {
|
|
8
|
+
const actual = Object.keys(value);
|
|
9
|
+
return actual.length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function validateUnitPoint(value, label) {
|
|
13
|
+
if (!isPlainObject(value) || !hasExactKeys(value, POINT_KEYS)) {
|
|
14
|
+
throw new TypeError(`${label} must be a plain { x, y } object.`);
|
|
15
|
+
}
|
|
16
|
+
for (const key of POINT_KEYS) {
|
|
17
|
+
if (!Number.isFinite(value[key]) || value[key] < 0 || value[key] > 1) {
|
|
18
|
+
throw new RangeError(`${label}.${key} must be a finite number from 0 to 1.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validateStop(stop, index) {
|
|
24
|
+
const label = `Linear gradient stops[${index}]`;
|
|
25
|
+
if (!isPlainObject(stop) || !hasExactKeys(stop, STOP_KEYS)) {
|
|
26
|
+
throw new TypeError(`${label} must be a plain { offset, color } object.`);
|
|
27
|
+
}
|
|
28
|
+
if (!Number.isFinite(stop.offset) || stop.offset < 0 || stop.offset > 1) {
|
|
29
|
+
throw new RangeError(`${label}.offset must be a finite number from 0 to 1.`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof stop.color !== "string" || stop.color.length === 0) {
|
|
32
|
+
throw new TypeError(`${label}.color must be a non-empty string.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isLinearGradientPaint(value) {
|
|
37
|
+
return isPlainObject(value) && value.type === "linear-gradient";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateFillPaint(value, label = "fill") {
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
if (value.length === 0) throw new TypeError(`${label} must be a non-empty string or linear gradient.`);
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
if (!isPlainObject(value) || !hasExactKeys(value, PAINT_KEYS)) {
|
|
46
|
+
throw new TypeError(`${label} must be a non-empty string or linear gradient.`);
|
|
47
|
+
}
|
|
48
|
+
if (value.type !== "linear-gradient") {
|
|
49
|
+
throw new Error(`Unsupported fill paint type "${value.type}".`);
|
|
50
|
+
}
|
|
51
|
+
validateUnitPoint(value.from, "Linear gradient from");
|
|
52
|
+
validateUnitPoint(value.to, "Linear gradient to");
|
|
53
|
+
if (value.from.x === value.to.x && value.from.y === value.to.y) {
|
|
54
|
+
throw new Error("Linear gradient from and to must differ.");
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(value.stops) || value.stops.length < 2) {
|
|
57
|
+
throw new TypeError("Linear gradient stops must contain at least two entries.");
|
|
58
|
+
}
|
|
59
|
+
value.stops.forEach(validateStop);
|
|
60
|
+
for (let index = 1; index < value.stops.length; index += 1) {
|
|
61
|
+
if (value.stops[index].offset < value.stops[index - 1].offset) {
|
|
62
|
+
throw new Error("Linear gradient stop offsets must be nondecreasing.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveLinearGradientCoordinates(paint, bounds) {
|
|
69
|
+
validateFillPaint(paint, "Linear gradient paint");
|
|
70
|
+
if (!isLinearGradientPaint(paint)) {
|
|
71
|
+
throw new TypeError("Linear gradient coordinates require a structured paint.");
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
!isPlainObject(bounds) ||
|
|
75
|
+
![bounds.left, bounds.right, bounds.top, bounds.bottom].every(Number.isFinite) ||
|
|
76
|
+
bounds.left > bounds.right ||
|
|
77
|
+
bounds.top > bounds.bottom
|
|
78
|
+
) {
|
|
79
|
+
throw new TypeError("Linear gradient bounds must be finite and ordered.");
|
|
80
|
+
}
|
|
81
|
+
const width = bounds.right - bounds.left;
|
|
82
|
+
const height = bounds.bottom - bounds.top;
|
|
83
|
+
const point = value => Object.freeze({
|
|
84
|
+
x: bounds.left + width * value.x,
|
|
85
|
+
y: bounds.top + height * value.y
|
|
86
|
+
});
|
|
87
|
+
return Object.freeze({ from: point(paint.from), to: point(paint.to) });
|
|
88
|
+
}
|
package/src/grammar/palettes.js
CHANGED
|
@@ -190,16 +190,14 @@ export function resolvePalette(value, domainCount) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export function resolveContinuousPalette(value, count = 31) {
|
|
193
|
-
if (!Number.isInteger(count) || count < 2) {
|
|
194
|
-
throw new RangeError("Continuous palette count must be an integer of at least 2.");
|
|
195
|
-
}
|
|
196
193
|
const palette = normalizePalette(value);
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
const resolvedCount = palette.count ?? count;
|
|
195
|
+
if (!Number.isInteger(resolvedCount) || resolvedCount < 2) {
|
|
196
|
+
throw new RangeError("Continuous palette count must be an integer of at least 2.");
|
|
199
197
|
}
|
|
200
198
|
const discrete = DISCRETE[palette.name];
|
|
201
|
-
if (discrete !== undefined) return discrete;
|
|
202
|
-
return resolvePalette({ ...palette, count },
|
|
199
|
+
if (discrete !== undefined && palette.count === undefined) return discrete;
|
|
200
|
+
return resolvePalette({ ...palette, count: resolvedCount }, resolvedCount);
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
export function paletteFamily(name) {
|