Skip to content

Commit 307016e

Browse files
committed
Fix a rounding bug in SI-prefix format.
Also, expose d3.formatPrefix so that it's easier for callers to create a formatter for a specific prefix (such as using the "G" prefix for all ticks).
1 parent 4adfae7 commit 307016e

9 files changed

Lines changed: 164 additions & 29 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ d3.core.js: \
7474
src/core/ns.js \
7575
src/core/dispatch.js \
7676
src/core/format.js \
77+
src/core/formatPrefix.js \
7778
src/core/ease.js \
7879
src/core/event.js \
7980
src/core/interpolate.js \

d3.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ try {
1010
d3_style_setProperty.call(this, name, value + "", priority);
1111
};
1212
}
13-
d3 = {version: "2.4.0"}; // semver
13+
d3 = {version: "2.4.1"}; // semver
1414
var d3_array = d3_arraySlice; // conversion for NodeLists
1515

1616
function d3_arrayCopy(pseudoarray) {
@@ -527,9 +527,9 @@ d3.format = function(specifier) {
527527

528528
// Apply the scale, computing it from the value's exponent for si format.
529529
if (scale < 0) {
530-
var exponent = Math.max(-24, Math.min(24, d3_format_exponent(value, precision)));
531-
value *= Math.pow(10, -exponent);
532-
suffix = d3_format_si[8 + exponent / 3];
530+
var prefix = d3.formatPrefix(value, precision);
531+
value *= prefix.scale;
532+
suffix = prefix.symbol;
533533
} else {
534534
value *= scale;
535535
}
@@ -558,8 +558,7 @@ d3.format = function(specifier) {
558558
};
559559

560560
// [[fill]align][sign][#][0][width][,][.precision][type]
561-
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,
562-
d3_format_si = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"];
561+
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
563562

564563
var d3_format_types = {
565564
g: function(x, p) { return x.toPrecision(p); },
@@ -572,13 +571,6 @@ function d3_format_precision(x, p) {
572571
return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1);
573572
}
574573

575-
function d3_format_exponent(x, p) {
576-
if (!x) return 0;
577-
if (p) x = d3.round(x, d3_format_precision(x, p));
578-
var d = 1 + Math.floor(Math.log(1e-9 + x) / Math.LN10);
579-
return Math.floor((d <= 0 ? d + 1 : d - 1) / 3) * 3;
580-
}
581-
582574
function d3_format_typeDefault(x) {
583575
return x + "";
584576
}
@@ -591,6 +583,26 @@ function d3_format_group(value) {
591583
while (i > 0) t.push(value.substring(i -= 3, i + 3));
592584
return t.reverse().join(",") + f;
593585
}
586+
var d3_formatPrefixes = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(d3_formatPrefix);
587+
588+
d3.formatPrefix = function(value, precision) {
589+
var i = 0;
590+
if (value) {
591+
if (value < 0) value *= -1;
592+
if (precision) value = d3.round(value, d3_format_precision(value, precision));
593+
i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
594+
i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
595+
}
596+
return d3_formatPrefixes[8 + i / 3];
597+
};
598+
599+
function d3_formatPrefix(d, i) {
600+
return {
601+
scale: Math.pow(10, (8 - i) * 3),
602+
symbol: d
603+
};
604+
}
605+
594606
/*
595607
* TERMS OF USE - EASING EQUATIONS
596608
*

d3.min.js

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "d3",
3-
"version": "2.4.0",
3+
"version": "2.4.1",
44
"description": "A small, free JavaScript library for manipulating documents based on data.",
55
"keywords": [
66
"dom",

src/core/core.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d3 = {version: "2.4.0"}; // semver
1+
d3 = {version: "2.4.1"}; // semver

src/core/format.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ d3.format = function(specifier) {
4242

4343
// Apply the scale, computing it from the value's exponent for si format.
4444
if (scale < 0) {
45-
var exponent = Math.max(-24, Math.min(24, d3_format_exponent(value, precision)));
46-
value *= Math.pow(10, -exponent);
47-
suffix = d3_format_si[8 + exponent / 3];
45+
var prefix = d3.formatPrefix(value, precision);
46+
value *= prefix.scale;
47+
suffix = prefix.symbol;
4848
} else {
4949
value *= scale;
5050
}
@@ -73,8 +73,7 @@ d3.format = function(specifier) {
7373
};
7474

7575
// [[fill]align][sign][#][0][width][,][.precision][type]
76-
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,
77-
d3_format_si = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"];
76+
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
7877

7978
var d3_format_types = {
8079
g: function(x, p) { return x.toPrecision(p); },
@@ -87,13 +86,6 @@ function d3_format_precision(x, p) {
8786
return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1);
8887
}
8988

90-
function d3_format_exponent(x, p) {
91-
if (!x) return 0;
92-
if (p) x = d3.round(x, d3_format_precision(x, p));
93-
var d = 1 + Math.floor(Math.log(1e-9 + x) / Math.LN10);
94-
return Math.floor((d <= 0 ? d + 1 : d - 1) / 3) * 3;
95-
}
96-
9789
function d3_format_typeDefault(x) {
9890
return x + "";
9991
}

src/core/formatPrefix.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
var d3_formatPrefixes = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(d3_formatPrefix);
2+
3+
d3.formatPrefix = function(value, precision) {
4+
var i = 0;
5+
if (value) {
6+
if (value < 0) value *= -1;
7+
if (precision) value = d3.round(value, d3_format_precision(value, precision));
8+
i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
9+
i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
10+
}
11+
return d3_formatPrefixes[8 + i / 3];
12+
};
13+
14+
function d3_formatPrefix(d, i) {
15+
return {
16+
scale: Math.pow(10, (8 - i) * 3),
17+
symbol: d
18+
};
19+
}
20+

test/core/format-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ suite.addBatch({
9696
assert.strictEqual(f(145999999.999999347), "146M");
9797
assert.strictEqual(f(1e26), "100Y");
9898
assert.strictEqual(f(.000001), "1.00μ");
99+
assert.strictEqual(f(.009995), "0.0100");
99100
var f = format(".4s");
100101
assert.strictEqual(f(999.5), "999.5");
101102
assert.strictEqual(f(999500), "999.5k");
103+
assert.strictEqual(f(.009995), "9.995m");
102104
},
103105
"can output a percentage": function(format) {
104106
var f = format("%");

test/core/formatPrefix-test.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require("../env");
2+
require("../../d3");
3+
4+
var vows = require("vows"),
5+
assert = require("assert");
6+
7+
var suite = vows.describe("d3.formatPrefix");
8+
9+
suite.addBatch({
10+
"formatPrefix": {
11+
topic: function() {
12+
return d3.formatPrefix;
13+
},
14+
"determines the appropriate prefix for small numbers": function(prefix) {
15+
assert.equal(prefix(0).symbol, "");
16+
assert.equal(prefix(1e-00).symbol, "");
17+
assert.equal(prefix(1e-01).symbol, "");
18+
assert.equal(prefix(1e-02).symbol, "");
19+
assert.equal(prefix(1e-03).symbol, "m");
20+
assert.equal(prefix(1e-04).symbol, "m");
21+
assert.equal(prefix(1e-05).symbol, "m");
22+
assert.equal(prefix(1e-06).symbol, "μ");
23+
assert.equal(prefix(1e-07).symbol, "μ");
24+
assert.equal(prefix(1e-08).symbol, "μ");
25+
assert.equal(prefix(1e-09).symbol, "n");
26+
assert.equal(prefix(1e-10).symbol, "n");
27+
assert.equal(prefix(1e-11).symbol, "n");
28+
assert.equal(prefix(1e-12).symbol, "p");
29+
assert.equal(prefix(1e-13).symbol, "p");
30+
assert.equal(prefix(1e-14).symbol, "p");
31+
assert.equal(prefix(1e-15).symbol, "f");
32+
assert.equal(prefix(1e-16).symbol, "f");
33+
assert.equal(prefix(1e-17).symbol, "f");
34+
assert.equal(prefix(1e-18).symbol, "a");
35+
assert.equal(prefix(1e-19).symbol, "a");
36+
assert.equal(prefix(1e-20).symbol, "a");
37+
assert.equal(prefix(1e-21).symbol, "z");
38+
assert.equal(prefix(1e-22).symbol, "z");
39+
assert.equal(prefix(1e-23).symbol, "z");
40+
assert.equal(prefix(1e-24).symbol, "y");
41+
assert.equal(prefix(1e-25).symbol, "y");
42+
assert.equal(prefix(1e-26).symbol, "y");
43+
assert.equal(prefix(1e-27).symbol, "y");
44+
},
45+
"determines the appropriate prefix for large numbers": function(prefix) {
46+
assert.equal(prefix(0).symbol, "");
47+
assert.equal(prefix(1e00).symbol, "");
48+
assert.equal(prefix(1e01).symbol, "");
49+
assert.equal(prefix(1e02).symbol, "");
50+
assert.equal(prefix(1e03).symbol, "k");
51+
assert.equal(prefix(1e04).symbol, "k");
52+
assert.equal(prefix(1e05).symbol, "k");
53+
assert.equal(prefix(1e06).symbol, "M");
54+
assert.equal(prefix(1e07).symbol, "M");
55+
assert.equal(prefix(1e08).symbol, "M");
56+
assert.equal(prefix(1e09).symbol, "G");
57+
assert.equal(prefix(1e10).symbol, "G");
58+
assert.equal(prefix(1e11).symbol, "G");
59+
assert.equal(prefix(1e12).symbol, "T");
60+
assert.equal(prefix(1e13).symbol, "T");
61+
assert.equal(prefix(1e14).symbol, "T");
62+
assert.equal(prefix(1e15).symbol, "P");
63+
assert.equal(prefix(1e16).symbol, "P");
64+
assert.equal(prefix(1e17).symbol, "P");
65+
assert.equal(prefix(1e18).symbol, "E");
66+
assert.equal(prefix(1e19).symbol, "E");
67+
assert.equal(prefix(1e20).symbol, "E");
68+
assert.equal(prefix(1e21).symbol, "Z");
69+
assert.equal(prefix(1e22).symbol, "Z");
70+
assert.equal(prefix(1e23).symbol, "Z");
71+
assert.equal(prefix(1e24).symbol, "Y");
72+
assert.equal(prefix(1e25).symbol, "Y");
73+
assert.equal(prefix(1e26).symbol, "Y");
74+
assert.equal(prefix(1e27).symbol, "Y");
75+
},
76+
"determines the appropriate prefix for negative numbers": function(prefix) {
77+
assert.equal(prefix(-0).symbol, "");
78+
assert.equal(prefix(-1e-00).symbol, "");
79+
assert.equal(prefix(-1e-03).symbol, "m");
80+
assert.equal(prefix(-1e-06).symbol, "μ");
81+
assert.equal(prefix(-1e-09).symbol, "n");
82+
assert.equal(prefix(-1e-12).symbol, "p");
83+
assert.equal(prefix(-1e-15).symbol, "f");
84+
assert.equal(prefix(-1e-18).symbol, "a");
85+
assert.equal(prefix(-1e-21).symbol, "z");
86+
assert.equal(prefix(-1e-24).symbol, "y");
87+
assert.equal(prefix(-1e-27).symbol, "y");
88+
assert.equal(prefix(-1e00).symbol, "");
89+
assert.equal(prefix(-1e03).symbol, "k");
90+
assert.equal(prefix(-1e06).symbol, "M");
91+
assert.equal(prefix(-1e09).symbol, "G");
92+
assert.equal(prefix(-1e12).symbol, "T");
93+
assert.equal(prefix(-1e15).symbol, "P");
94+
assert.equal(prefix(-1e18).symbol, "E");
95+
assert.equal(prefix(-1e21).symbol, "Z");
96+
assert.equal(prefix(-1e24).symbol, "Y");
97+
assert.equal(prefix(-1e27).symbol, "Y");
98+
},
99+
"considers the effect of rounding based on precision": function(prefix) {
100+
assert.equal(prefix(999.5, 3).symbol, "k");
101+
assert.equal(prefix(999.5, 4).symbol, "");
102+
assert.equal(prefix(.009995, 3).symbol, "");
103+
assert.equal(prefix(.009995, 4).symbol, "m");
104+
}
105+
}
106+
});
107+
108+
suite.export(module);

0 commit comments

Comments
 (0)