From 8e160722a9f278e426baf298a9aafc997612cebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 28 Aug 2024 15:03:11 +0200 Subject: [PATCH] add BigInt support to the normalize transform closes #2154 --- src/options.js | 2 +- src/transforms/normalize.js | 5 +- test/output/bigintNormalize.svg | 117 ++++++++++++++++++++++++++++ test/output/bigintNormalizeMean.svg | 86 ++++++++++++++++++++ test/plots/bigint.ts | 25 ++++++ test/transforms/normalize-test.js | 8 ++ 6 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 test/output/bigintNormalize.svg create mode 100644 test/output/bigintNormalizeMean.svg diff --git a/src/options.js b/src/options.js index ac9caca472..9fb056364f 100644 --- a/src/options.js +++ b/src/options.js @@ -54,7 +54,7 @@ function maybeTypedMap(data, f, type) { return map(data, isNumberType(type) ? (d, i) => coerceNumber(f(d, i)) : f, type); // allow conversion from BigInt } -function maybeTypedArrayify(data, type) { +export function maybeTypedArrayify(data, type) { return type === undefined ? arrayify(data) // preserve undefined type : isArrowVector(data) diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index f4d225cd11..846d2c6f43 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -1,6 +1,6 @@ import {extent, deviation, max, mean, median, min, sum} from "d3"; import {defined} from "../defined.js"; -import {percentile, taker} from "../options.js"; +import {maybeTypedArrayify, percentile, taker} from "../options.js"; import {mapX, mapY} from "./map.js"; export function normalizeX(basis, options) { @@ -43,7 +43,8 @@ export function normalize(basis) { function normalizeBasis(basis) { return { mapIndex(I, S, T) { - const b = +basis(I, S); + S = maybeTypedArrayify(S, Float64Array); + const b = basis(I, S); for (const i of I) { T[i] = S[i] === null ? NaN : S[i] / b; } diff --git a/test/output/bigintNormalize.svg b/test/output/bigintNormalize.svg new file mode 100644 index 0000000000..5c5b6435c8 --- /dev/null +++ b/test/output/bigintNormalize.svg @@ -0,0 +1,117 @@ + + + + + 0.0 + 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 0.6 + 0.7 + 0.8 + 0.9 + 1.0 + + + ↑ count + + + + 1.2 + 1.25 + 1.3 + 1.35 + 1.4 + 1.45 + 1.5 + 1.55 + 1.6 + 1.65 + 1.7 + 1.75 + 1.8 + 1.85 + 1.9 + 1.95 + 2 + 2.05 + 2.1 + 2.15 + 2.2 + + + weightclass + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/bigintNormalizeMean.svg b/test/output/bigintNormalizeMean.svg new file mode 100644 index 0000000000..846be5a5aa --- /dev/null +++ b/test/output/bigintNormalizeMean.svg @@ -0,0 +1,86 @@ + + + + + 0.0 + 0.5 + 1.0 + 1.5 + 2.0 + 2.5 + 3.0 + 3.5 + + + ↑ relative to mean + + + + 1.2 + 1.35 + 1.5 + 1.65 + 1.8 + 1.95 + 2.1 + + + weightclass → + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/bigint.ts b/test/plots/bigint.ts index 0d526f75ca..201d54a850 100644 --- a/test/plots/bigint.ts +++ b/test/plots/bigint.ts @@ -25,3 +25,28 @@ export async function bigintOrdinal() { export async function bigintStack() { return Plot.barY(integers, {x: (d, i) => i % 5, y: "big1"}).plot(); } + +async function olympiansByWeight() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return d3 + .bin()(olympians.map((d) => d.height)) + .map((bin) => ({weightclass: bin.x0, count: BigInt(bin.length)})); +} + +export async function bigintNormalize() { + return Plot.rectY( + d3.sort(await olympiansByWeight(), (d) => -d.count), + Plot.normalizeY({x: "weightclass", y: "count"}) + ).plot(); +} + +export async function bigintNormalizeMean() { + return Plot.plot({ + x: {interval: 0.05}, + y: {label: "relative to mean"}, + marks: [ + Plot.barY(await olympiansByWeight(), Plot.normalizeY("mean", {x: "weightclass", y: "count"})), + Plot.ruleY([1], {stroke: "red"}) + ] + }); +} diff --git a/test/transforms/normalize-test.js b/test/transforms/normalize-test.js index 43eb6c1ee0..9e11f520c8 100644 --- a/test/transforms/normalize-test.js +++ b/test/transforms/normalize-test.js @@ -5,6 +5,10 @@ it("Plot.normalize first", () => { testNormalize([1, 2, 4, 5], "first", [1, 2, 4, 5]); }); +it("Plot.normalize first BigInt", () => { + testNormalize([1n, 2n, 4n, 5n], "first", [1, 2, 4, 5]); +}); + it("Plot.normalize last", () => { testNormalize([1, 2, 4, 5], "last", [0.2, 0.4, 0.8, 1]); }); @@ -17,6 +21,10 @@ it("Plot.normalize median", () => { testNormalize([1, 2, 6, 6], "median", [0.25, 0.5, 1.5, 1.5]); }); +it("Plot.normalize median BigInt", () => { + testNormalize([1n, 2n, 6n, 6n], "median", [0.25, 0.5, 1.5, 1.5]); +}); + it("Plot.normalize min", () => { testNormalize([10, 6, 2], "min", [5, 3, 1]); });