Skip to content

Commit eaed66d

Browse files
committed
Coerce quantile scale domain to numbers.
Also only coerce values to numbers once in d3.{mean,median}.
1 parent d3ed04d commit eaed66d

File tree

10 files changed

+58
-20
lines changed

10 files changed

+58
-20
lines changed

d3.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,17 @@
8888
return s;
8989
};
9090
function d3_number(x) {
91-
return x != null && !isNaN(x);
91+
return x === null ? NaN : +x;
92+
}
93+
function d3_numeric(x) {
94+
return !isNaN(x);
9295
}
9396
d3.mean = function(array, f) {
9497
var s = 0, n = array.length, a, i = -1, j = n;
9598
if (arguments.length === 1) {
96-
while (++i < n) if (d3_number(a = array[i])) s += +a; else --j;
99+
while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
97100
} else {
98-
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += +a; else --j;
101+
while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
99102
}
100103
return j ? s / j : undefined;
101104
};
@@ -106,9 +109,9 @@
106109
d3.median = function(array, f) {
107110
var array1 = [], n = array.length, a, i = -1;
108111
if (arguments.length === 1) {
109-
while (++i < n) if (d3_number(a = array[i])) array1.push(+a);
112+
while (++i < n) if (d3_numeric(a = d3_number(array[i]))) array1.push(a);
110113
} else {
111-
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) array1.push(+a);
114+
while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) array1.push(a);
112115
}
113116
return array1.length ? d3.quantile(array1.sort(d3_ascending), .5) : undefined;
114117
};
@@ -7687,7 +7690,7 @@
76877690
}
76887691
scale.domain = function(x) {
76897692
if (!arguments.length) return domain;
7690-
domain = x.filter(d3_number).sort(d3_ascending);
7693+
domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
76917694
return rescale();
76927695
};
76937696
scale.range = function(x) {

d3.min.js

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/arrays/mean.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ d3.mean = function(array, f) {
77
i = -1,
88
j = n;
99
if (arguments.length === 1) {
10-
while (++i < n) if (d3_number(a = array[i])) s += +a; else --j;
10+
while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
1111
} else {
12-
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += +a; else --j;
12+
while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
1313
}
1414
return j ? s / j : undefined;
1515
};

src/arrays/median.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ d3.median = function(array, f) {
99
i = -1;
1010

1111
if (arguments.length === 1) {
12-
while (++i < n) if (d3_number(a = array[i])) array1.push(+a);
12+
while (++i < n) if (d3_numeric(a = d3_number(array[i]))) array1.push(a);
1313
} else {
14-
while (++i < n) if (d3_number(a = f.call(array, array[i], i))) array1.push(+a);
14+
while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) array1.push(a);
1515
}
1616

1717
return array1.length ? d3.quantile(array1.sort(d3_ascending), .5) : undefined;

src/math/number.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
function d3_number(x) {
2-
return x != null && !isNaN(x);
2+
return x === null ? NaN : +x;
3+
}
4+
5+
function d3_numeric(x) {
6+
return !isNaN(x);
37
}

src/scale/quantile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function d3_scale_quantile(domain, range) {
2525

2626
scale.domain = function(x) {
2727
if (!arguments.length) return domain;
28-
domain = x.filter(d3_number).sort(d3_ascending);
28+
domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
2929
return rescale();
3030
};
3131

test/arrays/mean-test.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var vows = require("vows"),
22
load = require("../load"),
3-
assert = require("../assert");
3+
assert = require("../assert"),
4+
OneTimeNumber = require("./one-time-number");
45

56
var suite = vows.describe("d3.mean");
67

@@ -29,11 +30,16 @@ suite.addBatch({
2930
assert.equal(mean([[1, 2, 3, 4, 5], [2, 4, 6, 8, 10]], function(d) { return mean(d); }), 4.5);
3031
assert.equal(mean([1, 2, 3, 4, 5], function(d, i) { return i; }), 2);
3132
},
32-
"coerces strings to numbers": function(mean) {
33+
"coerces values to numbers": function(mean) {
3334
assert.equal(mean(["1"]), 1);
3435
assert.equal(mean(["5", "1", "2", "3", "4"]), 3);
3536
assert.equal(mean(["20", "3"]), 11.5);
3637
assert.equal(mean(["3", "20"]), 11.5);
38+
},
39+
"coerces values exactly once": function(mean) {
40+
var array = [1, new OneTimeNumber(3)];
41+
assert.equal(mean(array), 2);
42+
assert.equal(mean(array), 1);
3743
}
3844
}
3945
});

test/arrays/median-test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var vows = require("vows"),
22
load = require("../load"),
3-
assert = require("../assert");
3+
assert = require("../assert"),
4+
OneTimeNumber = require("./one-time-number");
45

56
var suite = vows.describe("d3.median");
67

@@ -40,6 +41,11 @@ suite.addBatch({
4041
assert.equal(median(["3", "20"]), 11.5);
4142
assert.equal(median(["2", "3", "20"]), 3);
4243
assert.equal(median(["20", "3", "2"]), 3);
44+
},
45+
"coerces values exactly once": function(median) {
46+
var array = [1, new OneTimeNumber(3)];
47+
assert.equal(median(array), 2);
48+
assert.equal(median(array), 1);
4349
}
4450
}
4551
});

test/arrays/one-time-number.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = OneTimeNumber;
2+
3+
function OneTimeNumber(value) {
4+
this.value = value;
5+
}
6+
7+
OneTimeNumber.prototype.valueOf = function() {
8+
var v = this.value;
9+
this.value = NaN;
10+
return v;
11+
};

test/scale/quantile-test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ suite.addBatch({
2929
var x = quantile().domain([6, 3, 7, 8, 8, 13, 20, 15, 16, 10]);
3030
assert.deepEqual(x.domain(), [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]);
3131
},
32+
"domain values are coerced to numbers": function(quantile) {
33+
var x = quantile().domain(["6", "13", "20"]);
34+
assert.deepEqual(x.domain(), [6, 13, 20]);
35+
},
36+
"domain values are allowed to be zero": function(quantile) {
37+
var x = quantile().domain([1, 2, 0, 0, null]);
38+
assert.deepEqual(x.domain(), [0, 0, 1, 2]);
39+
},
3240
"non-numeric domain values are ignored": function(quantile) {
3341
var x = quantile().domain([6, 3, NaN, undefined, 7, 8, 8, 13, null, 20, 15, 16, 10, NaN]);
3442
assert.deepEqual(x.domain(), [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]);

0 commit comments

Comments
 (0)