Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/channel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ascending, descending, rollup, sort} from "d3";
import {first, isColor, isEvery, isIterable, labelof, map, maybeValue, range, valueof} from "./options.js";
import {first, isColor, isEvery, isIterable, labelof, map, maybeValue, range, floatMap, valueof} from "./options.js";
import {registry} from "./scales/index.js";
import {isSymbol, maybeSymbol} from "./symbols.js";
import {maybeReduce} from "./transforms/group.js";
Expand Down Expand Up @@ -121,7 +121,7 @@ function findScaleChannel(channels, scale) {
function difference(channels, k1, k2) {
const X1 = values(channels, k1);
const X2 = values(channels, k2);
return map(X2, (x2, i) => Math.abs(x2 - X1[i]), Float64Array);
return floatMap(X2, (x2, i) => Math.abs(x2 - X1[i]));
}

function values(channels, name, alias) {
Expand Down
6 changes: 3 additions & 3 deletions src/marks/raster.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {blurImage, Delaunay, randomLcg, rgb} from "d3";
import {valueObject} from "../channel.js";
import {create} from "../context.js";
import {map, first, second, third, isTuples, isNumeric, isTemporal, take, identity} from "../options.js";
import {floatMap, first, second, third, isTuples, isNumeric, isTemporal, take, identity} from "../options.js";
import {maybeColorChannel, maybeNumberChannel} from "../options.js";
import {Mark} from "../mark.js";
import {applyAttr, applyDirectStyles, applyIndirectStyles, applyTransform, impliedString} from "../style.js";
Expand Down Expand Up @@ -115,8 +115,8 @@ export class Raster extends AbstractRaster {
if (this.interpolate) {
const kx = w / dx;
const ky = h / dy;
const IX = map(X, (x) => (x - x1) * kx, Float64Array);
const IY = map(Y, (y) => (y - y1) * ky, Float64Array);
const IX = floatMap(X, (x) => (x - x1) * kx);
const IY = floatMap(Y, (y) => (y - y1) * ky);
if (F) F = this.interpolate(index, w, h, IX, IY, F);
if (FO) FO = this.interpolate(index, w, h, IX, IY, FO);
}
Expand Down
38 changes: 28 additions & 10 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ const objectToString = Object.prototype.toString;
/** @jsdoc valueof */
export function valueof(data, value, type) {
const valueType = typeof value;
const access =
type && Object.getPrototypeOf(type) === TypedArray
? (value) => floatMap(data, value, type)
: (value) => map(data, value);
return valueType === "string"
? map(data, field(value), type)
? access(field(value))
: valueType === "function"
? map(data, value, type)
? access(value)
: valueType === "number" || value instanceof Date || valueType === "boolean"
? map(data, constant(value), type)
? access(constant(value))
: value && typeof value.transform === "function"
? arrayify(value.transform(data), type)
: arrayify(value, type); // preserve undefined type
Expand Down Expand Up @@ -89,12 +93,26 @@ export function arrayify(data, type) {
}

// An optimization of type.from(values, f): if the given values are already an
// instanceof the desired array type, the faster values.map method is used. Note
// that we don’t rely on the implicit coercion of typedArray.from, because it
// errors on BigInts.
export function map(values, f, type = Array) {
const g = type === Array ? f : (d, i) => Number(f(d, i));
return values instanceof type ? values.map(g) : type.from(values, g);
// instanceof the desired array type, the faster values.map method is used.
export function map(values, f) {
return values instanceof Array ? values.map(f) : Array.from(values, f);
}

// Unlike Mark’s number, this converts null and undefined to NaN, since the
// result will be stored in a Float64Array and we don’t want null to be coerced
// to zero. Using Number to coerce BigInts.
function toFloat(x) {
return x == null ? NaN : Number(x);
}

export function floatMap(values, f, type = Float64Array) {
return f === undefined
? values instanceof type
? values.map(toFloat)
: type.from(values, toFloat)
: values instanceof type
? values.map((d, i) => toFloat(f(d, i)))
: type.from(values, (d, i) => toFloat(f(d, i)));
}

// An optimization of type.from(values): if the given values are already an
Expand Down Expand Up @@ -234,7 +252,7 @@ export function mid(x1, x2) {
const X2 = x2.transform(data);
return isTemporal(X1) || isTemporal(X2)
? map(X1, (_, i) => new Date((+X1[i] + +X2[i]) / 2))
: map(X1, (_, i) => (+X1[i] + +X2[i]) / 2, Float64Array);
: floatMap(X1, (_, i) => (+X1[i] + +X2[i]) / 2);
},
label: x1.label
};
Expand Down
12 changes: 3 additions & 9 deletions src/scales.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
isScaleOptions,
isTypedArray,
map,
slice
slice,
floatMap
} from "./options.js";
import {registry, color, position, radius, opacity, symbol, length} from "./scales/index.js";
import {
Expand Down Expand Up @@ -505,14 +506,7 @@ function coerceDates(values) {

// If the values are specified as a typed array, no coercion is required.
export function coerceNumbers(values) {
return isTypedArray(values) ? values : map(values, coerceNumber, Float64Array);
}

// Unlike Mark’s number, here we want to convert null and undefined to NaN,
// since the result will be stored in a Float64Array and we don’t want null to
// be coerced to zero.
export function coerceNumber(x) {
return x == null ? NaN : Number(x);
return isTypedArray(values) ? values : floatMap(values);
}

// When coercing strings to dates, we only want to allow the ISO 8601 format
Expand Down
10 changes: 5 additions & 5 deletions src/transforms/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
labelof,
isTemporal,
isIterable,
map
floatMap
} from "../options.js";
import {coerceDate, coerceNumber} from "../scales.js";
import {coerceDate, coerceNumbers} from "../scales.js";
import {basic} from "./basic.js";
import {
hasOutput,
Expand Down Expand Up @@ -230,7 +230,7 @@ function maybeBin(options) {
let V = valueof(data, value);
let T; // bin thresholds
if (isTemporal(V) || isTimeThresholds(thresholds)) {
V = map(V, coerceDate, Float64Array);
V = floatMap(V, coerceDate);
let [min, max] = typeof domain === "function" ? domain(V) : domain;
let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
if (typeof t === "number") t = utcTickInterval(min, max, t);
Expand All @@ -243,7 +243,7 @@ function maybeBin(options) {
}
T = t;
} else {
V = map(V, coerceNumber, Float64Array); // TODO deduplicate with code above
V = coerceNumbers(V);
let [min, max] = typeof domain === "function" ? domain(V) : domain;
let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
if (typeof t === "number") {
Expand Down Expand Up @@ -371,7 +371,7 @@ function Bin(EX, EY) {

// non-cumulative distribution
function bin1(E, T, V) {
T = T.map(coerceNumber); // for faster bisection; TODO skip if already typed
T = coerceNumbers(T); // for faster bisection
return (I) => {
const B = E.map(() => []);
for (const i of I) B[bisect(T, V[i]) - 1]?.push(i); // TODO quantization?
Expand Down
5 changes: 2 additions & 3 deletions src/transforms/dodge.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import IntervalTree from "interval-tree-1d";
import {finite, positive} from "../defined.js";
import {identity, maybeNamed, number, valueof} from "../options.js";
import {coerceNumbers} from "../scales.js";
import {floatMap, maybeNamed, number} from "../options.js";
import {initializer} from "./basic.js";
import {Position} from "../projection.js";

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