Skip to content

Commit 790f136

Browse files
Filmbostock
andauthored
make bins faster by quantization (#220)
* make bins faster by quantization ref. observablehq/plot#454 * better fractional step * fix quantization for custom domains Co-authored-by: Mike Bostock <[email protected]>
1 parent a8429c4 commit 790f136

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

src/bin.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default function bin() {
1818
var i,
1919
n = data.length,
2020
x,
21+
step,
2122
values = new Array(n);
2223

2324
for (i = 0; i < n; ++i) {
@@ -36,6 +37,11 @@ export default function bin() {
3637
if (domain === extent) [x0, x1] = nice(x0, x1, tn);
3738
tz = ticks(x0, x1, tn);
3839

40+
// If the domain is aligned with the first tick (which it will by
41+
// default), then we can use quantization rather than bisection to bin
42+
// values, which is substantially faster.
43+
if (tz[0] <= x0) step = tickIncrement(x0, x1, tn);
44+
3945
// If the last threshold is coincident with the domain’s upper bound, the
4046
// last bin will be zero-width. If the default domain is used, and this
4147
// last threshold is coincident with the maximum input value, we can
@@ -75,10 +81,25 @@ export default function bin() {
7581
}
7682

7783
// Assign data to bins by value, ignoring any outside the domain.
78-
for (i = 0; i < n; ++i) {
79-
x = values[i];
80-
if (x != null && x0 <= x && x <= x1) {
81-
bins[bisect(tz, x, 0, m)].push(data[i]);
84+
if (isFinite(step)) {
85+
if (step > 0) {
86+
for (i = 0; i < n; ++i) {
87+
if ((x = values[i]) != null && x0 <= x && x <= x1) {
88+
bins[Math.floor((x - x0) / step)].push(data[i]);
89+
}
90+
}
91+
} else if (step < 0) {
92+
for (i = 0; i < n; ++i) {
93+
if ((x = values[i]) != null && x0 <= x && x <= x1) {
94+
bins[Math.floor((x0 - x) * step)].push(data[i]);
95+
}
96+
}
97+
}
98+
} else {
99+
for (i = 0; i < n; ++i) {
100+
if ((x = values[i]) != null && x0 <= x && x <= x1) {
101+
bins[bisect(tz, x, 0, m)].push(data[i]);
102+
}
82103
}
83104
}
84105

test/bin-test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,48 @@ it("bin()() returns bins whose rightmost bin is not too wide", () => {
164164
]);
165165
});
166166

167+
it("bin(data) handles fractional step correctly", () => {
168+
const h = bin().thresholds(10);
169+
assert.deepStrictEqual(h([9.8, 10, 11, 12, 13, 13.2]), [
170+
box([9.8], 9.5, 10),
171+
box([10], 10, 10.5),
172+
box([], 10.5, 11),
173+
box([11], 11, 11.5),
174+
box([], 11.5, 12),
175+
box([12], 12, 12.5),
176+
box([], 12.5, 13),
177+
box([13, 13.2], 13, 13.5)
178+
]);
179+
});
180+
181+
it("bin(data) handles fractional step correctly with a custom, non-aligned domain", () => {
182+
const h = bin().thresholds(10).domain([9.7, 13.3]);
183+
assert.deepStrictEqual(h([9.8, 10, 11, 12, 13, 13.2]), [
184+
box([9.8], 9.7, 10),
185+
box([10], 10, 10.5),
186+
box([], 10.5, 11),
187+
box([11], 11, 11.5),
188+
box([], 11.5, 12),
189+
box([12], 12, 12.5),
190+
box([], 12.5, 13),
191+
box([13, 13.2], 13, 13.3)
192+
]);
193+
});
194+
195+
it("bin(data) handles fractional step correctly with a custom, aligned domain", () => {
196+
const h = bin().thresholds(10).domain([9.5, 13.5]);
197+
assert.deepStrictEqual(h([9.8, 10, 11, 12, 13, 13.2]), [
198+
box([9.8], 9.5, 10),
199+
box([10], 10, 10.5),
200+
box([], 10.5, 11),
201+
box([11], 11, 11.5),
202+
box([], 11.5, 12),
203+
box([12], 12, 12.5),
204+
box([], 12.5, 13),
205+
box([13, 13.2], 13, 13.5)
206+
]);
207+
});
208+
167209
it("bin(data) coerces values to numbers as expected", () => {
168210
const h = bin().thresholds(10);
169211
assert.deepStrictEqual(h(["1", "2", "3", "4", "5"]), [

0 commit comments

Comments
 (0)