Skip to content

Commit 6172d64

Browse files
Filmbostock
andauthored
more support for bigint (#1299)
Co-authored-by: Mike Bostock <[email protected]>
1 parent fd18371 commit 6172d64

File tree

14 files changed

+266
-78
lines changed

14 files changed

+266
-78
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3151,7 +3151,7 @@ Given an iterable *data* and some *value* accessor, returns an array (a column)
31513151
* an array of values - returning the same
31523152
* null or undefined - returning the same
31533153
3154-
If *type* is specified, it must be Array or a similar class that implements the [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) interface such as a typed array. When *type* is Array or a typed array class, the return value of valueof will be an instance of the same (or null or undefined). If *type* is not specified, valueof may return either an array or a typed array (or null or undefined).
3154+
If *type* is specified, it must be Array or a similar class that implements the [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) interface such as a typed array. When *type* is Array or a typed array class, the return value of valueof will be an instance of the same (or null or undefined). When *type* is a typed array, values will be implicitly coerced numbers, and if *type* is Float64Array, Float32Array, or a subclass of the same, null values will be implicitly replaced with NaN. If *type* is not specified, valueof may return either an array or a typed array (or null or undefined).
31553155
31563156
Plot.valueof is not guaranteed to return a new array. When a transform method is used, or when the given *value* is an array that is compatible with the requested *type*, the array may be returned as-is without making a copy.
31573157

src/marks/contour.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {blur2, contours, geoPath, map, max, min, nice, range, ticks, thresholdSturges} from "d3";
1+
import {blur2, contours, geoPath, max, min, nice, range, ticks, thresholdSturges} from "d3";
22
import {Channels} from "../channel.js";
33
import {create} from "../context.js";
4-
import {labelof, identity, arrayify} from "../options.js";
4+
import {labelof, identity, arrayify, map} from "../options.js";
55
import {Position} from "../projection.js";
66
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, styles} from "../style.js";
77
import {initializer} from "../transforms/basic.js";
@@ -156,7 +156,9 @@ function contourGeometry({thresholds, interval, ...options}) {
156156
const {contour} = contours().size([w, h]).smooth(this.smooth);
157157
const contourData = [];
158158
const contourFacets = [];
159-
for (const V of VV) contourFacets.push(range(contourData.length, contourData.push(...T.map((t) => contour(V, t)))));
159+
for (const V of VV) {
160+
contourFacets.push(range(contourData.length, contourData.push(...map(T, (t) => contour(V, t)))));
161+
}
160162

161163
// Rescale the contour multipolygon from grid to screen coordinates.
162164
for (const {coordinates} of contourData) {
@@ -188,7 +190,7 @@ function contourGeometry({thresholds, interval, ...options}) {
188190
function maybeTicks(thresholds, V, min, max) {
189191
if (typeof thresholds?.range === "function") return thresholds.range(thresholds.floor(min), max);
190192
if (typeof thresholds === "function") thresholds = thresholds(V, min, max);
191-
if (typeof thresholds !== "number") return arrayify(thresholds, Array);
193+
if (typeof thresholds !== "number") return arrayify(thresholds);
192194
const tz = ticks(...nice(min, max, thresholds), thresholds);
193195
while (tz[tz.length - 1] >= max) tz.pop();
194196
while (tz[1] < min) tz.shift();

src/marks/density.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {contourDensity, create, geoPath} from "d3";
22
import {Mark} from "../mark.js";
3-
import {isTypedArray, maybeTuple, maybeZ} from "../options.js";
3+
import {coerceNumbers, maybeTuple, maybeZ, TypedArray} from "../options.js";
44
import {Position} from "../projection.js";
5-
import {coerceNumbers} from "../scales.js";
65
import {
76
applyFrameAnchor,
87
applyDirectStyles,
@@ -129,7 +128,7 @@ function densityInitializer(options, fillDensity, strokeDensity) {
129128
// If explicit thresholds were not specified, find the maximum density of
130129
// all grids and use this to compute thresholds.
131130
let T = thresholds;
132-
if (!isTypedArray(T)) {
131+
if (!(T instanceof TypedArray)) {
133132
let maxValue = 0;
134133
for (const facetContours of facetsContours) {
135134
for (const [, contour] of facetContours) {

src/marks/line.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import {geoPath, line as shapeLine} from "d3";
22
import {create} from "../context.js";
33
import {curveAuto, PathCurve} from "../curve.js";
44
import {Mark} from "../mark.js";
5-
import {indexOf, identity, maybeTuple, maybeZ} from "../options.js";
6-
import {coerceNumbers} from "../scales.js";
5+
import {coerceNumbers, indexOf, identity, maybeTuple, maybeZ} from "../options.js";
76
import {
87
applyDirectStyles,
98
applyIndirectStyles,

src/marks/link.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {geoPath, pathRound as path} from "d3";
22
import {create} from "../context.js";
33
import {curveAuto, PathCurve} from "../curve.js";
44
import {Mark} from "../mark.js";
5-
import {coerceNumbers} from "../scales.js";
5+
import {coerceNumbers} from "../options.js";
66
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
77
import {markers, applyMarkers} from "./marker.js";
88

src/options.js

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,39 @@ import {color, descending, range as rangei, quantile} from "d3";
33
import {maybeTimeInterval, maybeUtcInterval} from "./time.js";
44

55
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
6-
const TypedArray = Object.getPrototypeOf(Uint8Array);
6+
export const TypedArray = Object.getPrototypeOf(Uint8Array);
77
const objectToString = Object.prototype.toString;
88

99
/** @jsdoc valueof */
1010
export function valueof(data, value, type) {
1111
const valueType = typeof value;
1212
return valueType === "string"
13-
? map(data, field(value), type)
13+
? maybeTypedMap(data, field(value), type)
1414
: valueType === "function"
15-
? map(data, value, type)
15+
? maybeTypedMap(data, value, type)
1616
: valueType === "number" || value instanceof Date || valueType === "boolean"
1717
? map(data, constant(value), type)
1818
: value && typeof value.transform === "function"
19-
? arrayify(value.transform(data), type)
20-
: arrayify(value, type); // preserve undefined type
19+
? maybeTypedArrayify(value.transform(data), type)
20+
: maybeTypedArrayify(value, type);
21+
}
22+
23+
function maybeTypedMap(data, f, type) {
24+
return map(data, type?.prototype instanceof TypedArray ? floater(f) : f, type);
25+
}
26+
27+
function maybeTypedArrayify(data, type) {
28+
return type === undefined
29+
? arrayify(data) // preserve undefined type
30+
: data instanceof type
31+
? data
32+
: type.prototype instanceof TypedArray && !(data instanceof TypedArray)
33+
? type.from(data, coerceNumber)
34+
: type.from(data);
35+
}
36+
37+
function floater(f) {
38+
return (d, i) => coerceNumber(f(d, i));
2139
}
2240

2341
export const field = (name) => (d) => d[name];
@@ -42,6 +60,38 @@ export function percentile(reduce) {
4260
return (I, f) => quantile(I, p, f);
4361
}
4462

63+
// If the values are specified as a typed array, no coercion is required.
64+
export function coerceNumbers(values) {
65+
return values instanceof TypedArray ? values : map(values, coerceNumber, Float64Array);
66+
}
67+
68+
// Unlike Mark’s number, here we want to convert null and undefined to NaN since
69+
// the result will be stored in a Float64Array and we don’t want null to be
70+
// coerced to zero. We use Number instead of unary + to allow BigInt coercion.
71+
function coerceNumber(x) {
72+
return x == null ? NaN : Number(x);
73+
}
74+
75+
export function coerceDates(values) {
76+
return map(values, coerceDate);
77+
}
78+
79+
// When coercing strings to dates, we only want to allow the ISO 8601 format
80+
// since the built-in string parsing of the Date constructor varies across
81+
// browsers. (In the future, this could be made more liberal if desired, though
82+
// it is still generally preferable to do date parsing yourself explicitly,
83+
// rather than rely on Plot.) Any non-string values are coerced to number first
84+
// and treated as milliseconds since UNIX epoch.
85+
export function coerceDate(x) {
86+
return x instanceof Date && !isNaN(x)
87+
? x
88+
: typeof x === "string"
89+
? isoParse(x)
90+
: x == null || isNaN((x = +x))
91+
? undefined
92+
: new Date(x);
93+
}
94+
4595
// Some channels may allow a string constant to be specified; to differentiate
4696
// string constants (e.g., "red") from named fields (e.g., "date"), this
4797
// function tests whether the given value is a CSS color string and returns a
@@ -72,20 +122,9 @@ export function keyword(input, name, allowed) {
72122
return i;
73123
}
74124

75-
// Promotes the specified data to an array or typed array as needed. If an array
76-
// type is provided (e.g., Array), then the returned array will strictly be of
77-
// the specified type; otherwise, any array or typed array may be returned. If
78-
// the specified data is null or undefined, returns the value as-is.
79-
export function arrayify(data, type) {
80-
return data == null
81-
? data
82-
: type === undefined
83-
? data instanceof Array || data instanceof TypedArray
84-
? data
85-
: Array.from(data)
86-
: data instanceof type
87-
? data
88-
: type.from(data);
125+
// Promotes the specified data to an array as needed.
126+
export function arrayify(data) {
127+
return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data);
89128
}
90129

91130
// An optimization of type.from(values, f): if the given values are already an
@@ -100,10 +139,6 @@ export function slice(values, type = Array) {
100139
return values instanceof type ? values.slice() : type.from(values);
101140
}
102141

103-
export function isTypedArray(values) {
104-
return values instanceof TypedArray;
105-
}
106-
107142
// Disambiguates an options object (e.g., {y: "x2"}) from a primitive value.
108143
export function isObject(option) {
109144
return option?.toString === objectToString;

src/projection.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import {
1818
geoTransverseMercator
1919
} from "d3";
2020
import {valueObject} from "./channel.js";
21-
import {constant, isObject} from "./options.js";
22-
import {coerceNumbers} from "./scales.js";
21+
import {coerceNumbers, constant, isObject} from "./options.js";
2322
import {warn} from "./warnings.js";
2423

2524
const pi = Math.PI;

src/scales.js

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import {parse as isoParse} from "isoformat";
21
import {
32
isOrdinal,
43
isTemporal,
54
isTemporalString,
65
isNumericString,
76
isScaleOptions,
8-
isTypedArray,
97
map,
10-
slice
8+
slice,
9+
coerceNumbers,
10+
coerceDates
1111
} from "./options.js";
1212
import {registry, color, position, radius, opacity, symbol, length} from "./scales/index.js";
1313
import {
@@ -499,38 +499,6 @@ function coerceSymbols(values) {
499499
return map(values, maybeSymbol);
500500
}
501501

502-
function coerceDates(values) {
503-
return map(values, coerceDate);
504-
}
505-
506-
// If the values are specified as a typed array, no coercion is required.
507-
export function coerceNumbers(values) {
508-
return isTypedArray(values) ? values : map(values, coerceNumber, Float64Array);
509-
}
510-
511-
// Unlike Mark’s number, here we want to convert null and undefined to NaN,
512-
// since the result will be stored in a Float64Array and we don’t want null to
513-
// be coerced to zero.
514-
export function coerceNumber(x) {
515-
return x == null ? NaN : Number(x);
516-
}
517-
518-
// When coercing strings to dates, we only want to allow the ISO 8601 format
519-
// since the built-in string parsing of the Date constructor varies across
520-
// browsers. (In the future, this could be made more liberal if desired, though
521-
// it is still generally preferable to do date parsing yourself explicitly,
522-
// rather than rely on Plot.) Any non-string values are coerced to number first
523-
// and treated as milliseconds since UNIX epoch.
524-
export function coerceDate(x) {
525-
return x instanceof Date && !isNaN(x)
526-
? x
527-
: typeof x === "string"
528-
? isoParse(x)
529-
: x == null || isNaN((x = +x))
530-
? undefined
531-
: new Date(x);
532-
}
533-
534502
/** @jsdoc scale */
535503
export function scale(options = {}) {
536504
let scale;

src/transforms/bin.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
import {
1212
valueof,
1313
identity,
14+
coerceDate,
15+
coerceNumbers,
1416
maybeColumn,
1517
maybeInterval,
1618
maybeTuple,
@@ -22,7 +24,6 @@ import {
2224
isIterable,
2325
map
2426
} from "../options.js";
25-
import {coerceDate, coerceNumber} from "../scales.js";
2627
import {basic} from "./basic.js";
2728
import {
2829
hasOutput,
@@ -230,7 +231,7 @@ function maybeBin(options) {
230231
let V = valueof(data, value);
231232
let T; // bin thresholds
232233
if (isTemporal(V) || isTimeThresholds(thresholds)) {
233-
V = map(V, coerceDate, Float64Array);
234+
V = map(V, coerceDate, Float64Array); // like coerceDates, but faster
234235
let [min, max] = typeof domain === "function" ? domain(V) : domain;
235236
let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
236237
if (typeof t === "number") t = utcTickInterval(min, max, t);
@@ -243,7 +244,7 @@ function maybeBin(options) {
243244
}
244245
T = t;
245246
} else {
246-
V = map(V, coerceNumber, Float64Array); // TODO deduplicate with code above
247+
V = coerceNumbers(V);
247248
let [min, max] = typeof domain === "function" ? domain(V) : domain;
248249
let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
249250
if (typeof t === "number") {
@@ -371,7 +372,7 @@ function Bin(EX, EY) {
371372

372373
// non-cumulative distribution
373374
function bin1(E, T, V) {
374-
T = T.map(coerceNumber); // for faster bisection; TODO skip if already typed
375+
T = coerceNumbers(T); // for faster bisection
375376
return (I) => {
376377
const B = E.map(() => []);
377378
for (const i of I) B[bisect(T, V[i]) - 1]?.push(i); // TODO quantization?

src/transforms/dodge.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import IntervalTree from "interval-tree-1d";
22
import {finite, positive} from "../defined.js";
33
import {identity, maybeNamed, number, valueof} from "../options.js";
4-
import {coerceNumbers} from "../scales.js";
54
import {initializer} from "./basic.js";
65
import {Position} from "../projection.js";
76

@@ -73,7 +72,7 @@ function dodge(y, x, anchor, padding, options) {
7372
if (!channels[x]) throw new Error(`missing channel: ${x}`);
7473
({[x]: X} = Position(channels, scales, context));
7574
const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3;
76-
if (R) R = coerceNumbers(valueof(R.value, scales[R.scale] || identity));
75+
if (R) R = valueof(R.value, scales[R.scale] || identity, Float64Array);
7776
let [ky, ty] = anchor(dimensions);
7877
const compare = ky ? compareAscending : compareSymmetric;
7978
const Y = new Float64Array(X.length);

0 commit comments

Comments
 (0)