From 8e3829ab0f13c440b7ea37be686a57c304b977da Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 1 Apr 2022 13:22:15 -0700 Subject: [PATCH 1/2] reuse lcg during pack --- src/array.js | 5 +---- src/pack/enclose.js | 7 ++++++- src/pack/index.js | 14 ++++++++------ src/pack/siblings.js | 9 +++++---- test/pack/deterministic-test.js | 18 ++++++++++-------- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/array.js b/src/array.js index d83970a0..a90d2d0a 100644 --- a/src/array.js +++ b/src/array.js @@ -1,13 +1,10 @@ -import lcg from "./lcg.js"; - export default function(x) { return typeof x === "object" && "length" in x ? x // Array, TypedArray, NodeList, array-like : Array.from(x); // Map, Set, iterable, string, or anything else } -export function shuffle(array) { - const random = lcg(); +export function shuffle(array, random = Math.random) { let m = array.length, t, i; diff --git a/src/pack/enclose.js b/src/pack/enclose.js index 86231caa..ae2d7f26 100644 --- a/src/pack/enclose.js +++ b/src/pack/enclose.js @@ -1,7 +1,12 @@ import {shuffle} from "../array.js"; +import lcg from "../lcg.js"; export default function(circles) { - var i = 0, n = (circles = shuffle(Array.from(circles))).length, B = [], p, e; + return packEncloseRandom(circles, lcg()); +} + +export function packEncloseRandom(circles, random) { + var i = 0, n = (circles = shuffle(Array.from(circles), random)).length, B = [], p, e; while (i < n) { p = circles[i]; diff --git a/src/pack/index.js b/src/pack/index.js index 6ef2d7c9..8b710b3f 100644 --- a/src/pack/index.js +++ b/src/pack/index.js @@ -1,6 +1,7 @@ -import {packEnclose} from "./siblings.js"; import {optional} from "../accessors.js"; import constant, {constantZero} from "../constant.js"; +import lcg from "../lcg.js"; +import {packSiblingsRandom} from "./siblings.js"; function defaultRadius(d) { return Math.sqrt(d.value); @@ -13,15 +14,16 @@ export default function() { padding = constantZero; function pack(root) { + const random = lcg(); root.x = dx / 2, root.y = dy / 2; if (radius) { root.eachBefore(radiusLeaf(radius)) - .eachAfter(packChildren(padding, 0.5)) + .eachAfter(packChildrenRandom(padding, 0.5, random)) .eachBefore(translateChild(1)); } else { root.eachBefore(radiusLeaf(defaultRadius)) - .eachAfter(packChildren(constantZero, 1)) - .eachAfter(packChildren(padding, root.r / Math.min(dx, dy))) + .eachAfter(packChildrenRandom(constantZero, 1, random)) + .eachAfter(packChildrenRandom(padding, root.r / Math.min(dx, dy), random)) .eachBefore(translateChild(Math.min(dx, dy) / (2 * root.r))); } return root; @@ -50,7 +52,7 @@ function radiusLeaf(radius) { }; } -function packChildren(padding, k) { +function packChildrenRandom(padding, k, random) { return function(node) { if (children = node.children) { var children, @@ -60,7 +62,7 @@ function packChildren(padding, k) { e; if (r) for (i = 0; i < n; ++i) children[i].r += r; - e = packEnclose(children); + e = packSiblingsRandom(children, random); if (r) for (i = 0; i < n; ++i) children[i].r -= r; node.r = e + r; } diff --git a/src/pack/siblings.js b/src/pack/siblings.js index 05047cfd..e7f5df46 100644 --- a/src/pack/siblings.js +++ b/src/pack/siblings.js @@ -1,5 +1,6 @@ import array from "../array.js"; -import enclose from "./enclose.js"; +import lcg from "../lcg.js"; +import {packEncloseRandom} from "./enclose.js"; function place(b, a, c) { var dx = b.x - a.x, x, a2, @@ -45,7 +46,7 @@ function Node(circle) { this.previous = null; } -export function packEnclose(circles) { +export function packSiblingsRandom(circles, random) { if (!(n = (circles = array(circles)).length)) return 0; var a, b, c, n, aa, ca, i, j, k, sj, sk; @@ -105,7 +106,7 @@ export function packEnclose(circles) { } // Compute the enclosing circle of the front chain. - a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = enclose(a); + a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = packEncloseRandom(a, random); // Translate the circles to put the enclosing circle around the origin. for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y; @@ -114,6 +115,6 @@ export function packEnclose(circles) { } export default function(circles) { - packEnclose(circles); + packSiblingsRandom(circles, lcg()); return circles; } diff --git a/test/pack/deterministic-test.js b/test/pack/deterministic-test.js index dc97985b..119eb3d4 100644 --- a/test/pack/deterministic-test.js +++ b/test/pack/deterministic-test.js @@ -2,12 +2,14 @@ import assert from "assert"; import {hierarchy, stratify, pack} from "../../src/index.js"; it("pack is deterministic", () => { - const data = stratify().path((d) => d)([41, 41, 11, 11, 4, 4] - .flatMap((n, i) => Array.from({ length: n }, (_, j) => ({ i, j }))) - .map(({ i, j }) => `/${i}/${i}-${j}`)); - const root = hierarchy(data); - root.sum(() => 1); - const packed = Array.from(pack().size([100, 100]).padding(0)(root), - ({ x, y }) => [+x.toPrecision(5), +y.toPrecision(5)]); - assert.deepStrictEqual(packed, [[50,50],[25,50],[75,50],[50,80.443],[50,19.557],[27.269,16.539],[72.731,83.461],[21.875,50],[28.125,50],[25,55.413],[25,44.587],[18.75,44.587],[31.25,44.587],[31.25,55.413],[18.75,55.413],[15.625,50],[34.375,50],[12.5,55.413],[37.5,44.587],[28.125,60.825],[21.875,39.175],[28.125,39.175],[21.875,60.825],[25,66.238],[12.5,44.587],[25,33.762],[37.5,55.413],[34.375,60.825],[15.625,60.825],[15.625,39.175],[34.375,39.175],[40.625,60.825],[9.375,39.175],[40.625,39.175],[9.375,60.825],[9.375,50],[6.25,44.587],[40.625,50],[43.75,55.413],[31.25,66.238],[18.75,33.762],[31.25,33.762],[18.75,66.238],[21.875,28.349],[28.125,28.349],[28.125,71.651],[21.875,71.651],[6.25,55.413],[71.875,50],[78.125,50],[75,55.413],[75,44.587],[68.75,44.587],[81.25,44.587],[81.25,55.413],[68.75,55.413],[65.625,50],[84.375,50],[62.5,55.413],[87.5,44.587],[78.125,60.825],[71.875,39.175],[78.125,39.175],[71.875,60.825],[75,66.238],[62.5,44.587],[75,33.762],[87.5,55.413],[84.375,60.825],[65.625,60.825],[65.625,39.175],[84.375,39.175],[90.625,60.825],[59.375,39.175],[90.625,39.175],[59.375,60.825],[59.375,50],[56.25,44.587],[90.625,50],[93.75,55.413],[81.25,66.238],[68.75,33.762],[81.25,33.762],[68.75,66.238],[71.875,28.349],[78.125,28.349],[78.125,71.651],[71.875,71.651],[56.25,55.413],[48.438,77.736],[54.688,77.736],[51.563,83.149],[51.563,72.324],[45.313,72.324],[57.813,72.324],[57.813,83.149],[45.313,83.149],[42.188,77.736],[60.938,77.736],[39.063,83.149],[48.438,16.851],[54.688,16.851],[51.563,22.264],[51.563,11.438],[45.313,11.438],[57.813,11.438],[57.813,22.264],[45.313,22.264],[42.188,16.851],[60.938,16.851],[39.063,22.264],[24.144,16.539],[30.394,16.539],[27.269,21.952],[27.269,11.127],[69.606,83.461],[75.856,83.461],[72.731,88.873],[72.731,78.048]]); + const data = stratify().path((d) => d)( + [41, 41, 11, 11, 4, 4] + .flatMap((n, i) => Array.from({length: n}, (_, j) => ({i, j}))) + .map(({i, j}) => `/${i}/${i}-${j}`) + ); + const packer = pack().size([100, 100]).padding(0); + const pack1 = packer(hierarchy(data).count()); + for (let i = 0; i < 40; ++i) { + assert.deepStrictEqual(packer(hierarchy(data).count()), pack1); + } }); From a21def0c2d525fbe4cb23083f4fb01ba4bdecf09 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 1 Apr 2022 14:24:21 -0700 Subject: [PATCH 2/2] remove unused default --- src/array.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array.js b/src/array.js index a90d2d0a..b46d1b9c 100644 --- a/src/array.js +++ b/src/array.js @@ -4,7 +4,7 @@ export default function(x) { : Array.from(x); // Map, Set, iterable, string, or anything else } -export function shuffle(array, random = Math.random) { +export function shuffle(array, random) { let m = array.length, t, i;