Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Jun 12, 2015
0 parents commit dacab59
Show file tree
Hide file tree
Showing 14 changed files with 598 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.sublime-workspace
.DS_Store
build/
node_modules
npm-debug.log
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
index.sublime-workspace
index.sublime-project
test/
28 changes: 28 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Copyright 2010-2015 Mike Bostock
Copyright 2001 Robert Penner
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
102 changes: 102 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# d3-format

Number formatting is one of those things you don’t normally think about until an ugly 0.30000000000000004 appears on your axis labels. Maybe you want to group thousands and use fixed precision, such as $1,240.10. Or maybe you want to display only the significant digits of a particular number.

Formatting numbers for humans is the purpose of the d3-format module. For example, to create a function that zero-fills to four digits, say:

```javascript
var zeroPad = format("04d");
```

Now you can conveniently format numbers:

```javascript
zeroPad(2); // "0002"
zeroPad(123); // "0123"
```

<a name="format" href="#format">#</a> <b>format</b>(<i>specifier</i>[, <i>locale</i>])

Returns a new format function with the given string *specifier*. TODO Document optional *locale* argument, which defaults to U.S. English if not present.

The returned function takes a number as the only argument, and returns a string representing the formatted number. The format specifier is modeled after Python 3.1’s [format specification mini-language](http://docs.python.org/release/3.1.3/library/string.html#formatspec). The general form of a specifier is:

```
[​[fill]align][sign][symbol][0][width][,][.precision][type]
```

The *fill* can be any character other than `"{"` or `"}"`. The presence of a fill character is signaled by the character following it, which must be one of the *align* options.

The *align* can be:

* (`"<"`) Forces the field to be left-aligned within the available space.
* (`">"`) Forces the field to be right-aligned within the available space. (This is the default).
* (`"^"`) Forces the field to be centered within the available space.

The *sign* can be:

* plus (`"+"`) - a sign should be used for both positive and negative numbers.
* minus (`"-"`) - a sign should be used only for negative numbers. (This is the default.)
* space (`" "`) - a leading space should be used on positive numbers, and a minus sign on negative numbers.

The *symbol* can be:

* currency (`"$"`) - a currency symbol should be prefixed (or suffixed) per the locale.
* base (`"#"`) - for binary, octal, or hexadecimal output, prefix by `"0b"`, `"0o"`, or `"0x"`, respectively.

The `"0"` option enables zero-padding.

The *width* defines the minimum field width. If not specified, then the width will be determined by the content.

The *comma* (`","`) option enables the use of a comma for a thousands separator.

The *precision* indicates how many digits should be displayed after the decimal point for a value formatted with types `"f"` and `"%"`, or before and after the decimal point for a value formatted with types `"g"`, `"r"` and `"p"`.

The available *type* values are:

* exponent (`"e"`) - use [Number.toExponential](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/toExponential).
* general (`"g"`) - use [Number.toPrecision](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/toPrecision).
* fixed (`"f"`) - use [Number.toFixed](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/toFixed).
* integer (`"d"`) - use [Number.toString](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/toString), but ignore any non-integer values.
* rounded (`"r"`) - round to *precision* significant digits, padding with zeroes where necessary in similar fashion to fixed (`"f"`). If no *precision* is specified, falls back to general notation.
* percentage (`"%") - `like fixed, but multiply by 100 and suffix with "%".
* rounded percentage (`"p"`) - like rounded, but multiply by 100 and suffix with "%".
* binary (`"b"`) - outputs the number in base 2.
* octal (`"o"`) - outputs the number in base 8.
* hexadecimal (`"x"`) - outputs the number in base 16, using lower-case letters for the digits above 9.
* hexadecimal (`"X"`) - outputs the number in base 16, using upper-case letters for the digits above 9.
* character (`"c"`) - converts the integer to the corresponding unicode character before printing.
* SI-prefix (`"s"`) - like rounded, but with a unit suffixed such as `"9.5M"` for mega, or `"1.00µ"` for micro.

The type `"n"` is also supported as shorthand for `",g"`.

<a name="formatPrefix" href="#formatPrefix">#</a> <b>formatPrefix</b>(<i>value</i>[, <i>precision</i>])

Returns the [SI prefix](https://en.wikipedia.org/wiki/Metric_prefix) for the specified *value*. If an optional *precision* is specified, the *value* is rounded accordingly using [round](#round) before computing the prefix. The returned prefix object has two properties:

* symbol - the prefix symbol, such as `"M"` for millions.
* scale - the scale function, for converting numbers to the appropriate prefixed scale.

For example:

```js
var prefix = formatPrefix(1.21e9);
console.log(prefix.symbol); // "G"
console.log(prefix.scale(1.21e9)); // 1.21
```

This method is used by [format](#format) for the `"s"` format type.

<a name="round" href="#round">#</a> <b>round</b>(<i>x</i>[, <i>n</i>])

Returns the value *x* rounded to *n* digits after the decimal point. If *n* is omitted, it defaults to zero. The result is a number. Values are rounded to the closest multiple of 10 to the power minus *n*; if two multiples are equally close, the value is rounded up in accordance with the built-in [Math.round](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/round]) function. For example:

```js
round(1.23); // 1
round(1.23, 1); // 1.2
round(1.25, 1); // 1.3
round(12.5, 0); // 13
round(12, -1); // 10
```

Note that the resulting number when converted to a string may be imprecise due to IEEE floating point precision; to format a number to a string with a fixed number of decimal points, use [format](#format) instead.
13 changes: 13 additions & 0 deletions d3-format.sublime-project
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"folders": [
{
"path": ".",
"file_exclude_patterns": [
"*.sublime-workspace"
],
"folder_exclude_patterns": [
"build"
]
}
]
}
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import format from "./src/format";
import formatPrefix from "./src/formatPrefix";
import formatPrecision from "./src/formatPrecision";
import round from "./src/round";

export {
format,
formatPrefix,
formatPrecision,
round
};
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "d3-format",
"version": "0.0.1",
"description": "Format numbers for humans.",
"keywords": [
"d3",
"format"
],
"homepage": "https://github.com/d3/d3-format",
"license": "BSD-3-Clause",
"author": {
"name": "Mike Bostock",
"url": "http://bost.ocks.org/mike"
},
"main": "build/format",
"jsnext:main": "index",
"repository": {
"type": "git",
"url": "https://github.com/d3/d3-format.git"
},
"scripts": {
"pretest": "mkdir -p build && d3-bundler --format=umd --name=format -- index.js > build/format.js",
"test": "faucet `find test -name '*-test.js'`",
"prepublish": "npm run test && uglifyjs build/format.js -c -m -o build/format.min.js"
},
"devDependencies": {
"d3-bundler": "~0.2.5",
"faucet": "0.0",
"tape": "4",
"uglifyjs": "2"
}
}
166 changes: 166 additions & 0 deletions src/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import formatPrefix from "./formatPrefix";
import formatPrecision from "./formatPrecision";
import round from "./round";

var enUS = {
decimal: ".",
thousands: ",",
grouping: [3],
currency: ["$", ""]
};

// [[fill]align][sign][symbol][0][width][,][.precision][type]
var formatRe = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;

var formatTypes = {
b: function(x) { return x.toString(2); },
c: function(x) { return String.fromCharCode(x); },
o: function(x) { return x.toString(8); },
x: function(x) { return x.toString(16); },
X: function(x) { return x.toString(16).toUpperCase(); },
g: function(x, p) { return x.toPrecision(p); },
e: function(x, p) { return x.toExponential(p); },
f: function(x, p) { return x.toFixed(p); },
r: function(x, p) { return (x = round(x, formatPrecision(x, p))).toFixed(Math.max(0, Math.min(20, formatPrecision(x * (1 + 1e-15), p)))); }
};

function stringOf(x) {
return x + "";
}

function identity(x) {
return x;
}

function formatGroup(grouping, thousands) {
return function(value, width) {
var i = value.length,
t = [],
j = 0,
g = grouping[0],
length = 0;

while (i > 0 && g > 0) {
if (length + g + 1 > width) g = Math.max(1, width - length);
t.push(value.substring(i -= g, i + g));
if ((length += g + 1) > width) break;
g = grouping[j = (j + 1) % grouping.length];
}

return t.reverse().join(thousands);
};
}

export default function(specifier, locale) {
if (!locale) locale = enUS;

var group = locale.grouping && locale.thousands ? formatGroup(locale.grouping, locale.thousands) : identity,
match = formatRe.exec(specifier),
fill = match[1] || " ",
align = match[2] || ">",
sign = match[3] || "-",
symbol = match[4] || "",
zfill = match[5],
width = +match[6],
comma = match[7],
precision = match[8],
type = match[9],
scale = 1,
prefix = "",
suffix = "",
integer = false,
exponent = true;

if (precision) precision = +precision.substring(1);

if (zfill || fill === "0" && align === "=") {
zfill = fill = "0";
align = "=";
}

switch (type) {
case "n": comma = true; type = "g"; break;
case "%": scale = 100; suffix = "%"; type = "f"; break;
case "p": scale = 100; suffix = "%"; type = "r"; break;
case "b":
case "o":
case "x":
case "X": if (symbol === "#") prefix = "0" + type.toLowerCase();
case "c": exponent = false;
case "d": integer = true; precision = 0; break;
case "s": scale = -1; type = "r"; break;
}

if (symbol === "$") prefix = locale.currency[0], suffix = locale.currency[1];

// If no precision is specified for r, fallback to general notation.
if (type == "r" && !precision) type = "g";

// Ensure that the requested precision is in the supported range.
if (precision != null) {
if (type == "g") precision = Math.max(1, Math.min(21, precision));
else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
}

type = formatTypes[type] || stringOf;

var zcomma = zfill && comma;

return function(value) {

// Return the empty string for floats formatted as ints.
if (integer && (value % 1)) return "";

// Convert negative to positive, and record the sign prefix.
var valueSign = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign,
valueSuffix = suffix;

// Apply the scale, computing it from the value’s exponent for si format.
// Preserve the existing suffix, if any, such as the currency symbol.
if (scale < 0) {
var valuePrefix = formatPrefix(value, precision);
value = valuePrefix.scale(value);
valueSuffix = valuePrefix.symbol + suffix;
} else {
value *= scale;
}

// Convert to the desired precision.
value = type(value, precision);

// Break the value into the integer part (before) and decimal part (after).
var i = value.lastIndexOf("."),
before,
after;

// If there is no decimal, break on "e" where appropriate.
if (i < 0) {
var j = exponent ? value.lastIndexOf("e") : -1;
if (j < 0) before = value, after = "";
else before = value.substring(0, j), after = value.substring(j);
} else {
before = value.substring(0, i);
after = locale.decimal + value.substring(i + 1);
}

// If the fill character is not "0", grouping is applied before padding.
if (!zfill && comma) before = group(before, Infinity);

var length = prefix.length + before.length + after.length + (zcomma ? 0 : valueSign.length),
padding = length < width ? new Array(length = width - length + 1).join(fill) : "";

// If the fill character is "0", grouping is applied after padding.
if (zcomma) before = group(padding + before, padding.length ? width - after.length : Infinity);

// Apply prefix.
valueSign += prefix;

// Rejoin integer and decimal parts.
value = before + after;

return (align === "<" ? valueSign + value + padding
: align === ">" ? padding + valueSign + value
: align === "^" ? padding.substring(0, length >>= 1) + valueSign + value + padding.substring(length)
: valueSign + (zcomma ? value : padding + value)) + valueSuffix;
};
};
3 changes: 3 additions & 0 deletions src/formatPrecision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function(x, p) {
return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
};
20 changes: 20 additions & 0 deletions src/formatPrefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import formatPrecision from "./formatPrecision";
import round from "./round";

var prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"].map(function(d, i) {
var k = Math.pow(10, Math.abs(8 - i) * 3);
return {
scale: i > 8 ? function(d) { return d / k; } : function(d) { return d * k; },
symbol: d
};
});

export default function(value, precision) {
var i = 0;
if (value = +value) {
if (value < 0) value *= -1;
if (precision) value = round(value, formatPrecision(value, precision));
i = Math.max(-24, Math.min(24, Math.floor((1e-12 + Math.log(value)) / (Math.LN10 * 3)) * 3));
}
return prefixes[8 + i / 3];
};
5 changes: 5 additions & 0 deletions src/round.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function(x, n) {
return n
? Math.round(x * (n = Math.pow(10, n))) / n
: Math.round(x);
};
Loading

0 comments on commit dacab59

Please sign in to comment.