Commit 68809d3d authored by Mike Bostock's avatar Mike Bostock
Browse files

Add currency support to d3.format. Fixes #777.

You can now prefix the locale’s currency symbol. For example:

  d3.format("+$,.2f")(-2.5e5) // -$250,000.00

This implementation is limited in that it does not support currencies where the
symbol should appear after the value (e.g., "250,000.00 €" as in fr_FR). And
d3.format currently only supports a single locale. But it’s a start.
parent 747c523e
......@@ -19,7 +19,7 @@ benchmark: all
@node test/geo/benchmark.js
src/format/format-localized.js: bin/locale src/format/format-locale.js
LC_NUMERIC=$(LOCALE) locale -ck LC_NUMERIC | bin/locale src/format/format-locale.js > $@
LC_NUMERIC=$(LOCALE) locale -ck LC_NUMERIC LC_MONETARY | bin/locale src/format/format-locale.js > $@
src/time/format-localized.js: bin/locale src/time/format-locale.js
LC_TIME=$(LOCALE) locale -ck LC_TIME | bin/locale src/time/format-locale.js > $@
......
......@@ -22,7 +22,7 @@ function write() {
}
});
puts(fs.readFileSync(process.argv[2], "utf8").replace(/\{([^\}]+)\}/g, function(d, k) {
puts(fs.readFileSync(process.argv[2], "utf8").replace(/\{([a-z_]+)\}/g, function(d, k) {
d = formats[k];
return k === "grouping"
? d === "127" || d === "0" ? null : "[" + d.map(Number).join(", ") + "]"
......
......@@ -1911,7 +1911,7 @@ d3 = function() {
var d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) {
setTimeout(callback, 17);
};
var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ];
var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ], d3_format_currencySymbol = "$";
var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
d3.formatPrefix = function(value, precision) {
var i = 0;
......@@ -1938,7 +1938,7 @@ d3 = function() {
return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
};
d3.format = function(specifier) {
var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", basePrefix = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false;
var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", base = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false;
if (precision) precision = +precision.substring(1);
if (zfill || fill === "0" && align === "=") {
zfill = fill = "0";
......@@ -1967,7 +1967,7 @@ d3 = function() {
case "o":
case "x":
case "X":
if (basePrefix) basePrefix = "0" + type.toLowerCase();
if (base === "#") base = "0" + type.toLowerCase();
case "c":
case "d":
......@@ -1980,7 +1980,7 @@ d3 = function() {
type = "r";
break;
}
if (basePrefix === "#") basePrefix = "";
if (base === "#") base = ""; else if (base === "$") base = d3_format_currencySymbol;
if (type == "r" && !precision) type = "g";
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));
......@@ -1999,14 +1999,14 @@ d3 = function() {
}
value = type(value, precision);
if (!zfill && comma) value = d3_format_group(value);
var length = basePrefix.length + value.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
var length = base.length + value.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
if (zcomma) value = d3_format_group(padding + value);
if (d3_format_decimalPoint) value.replace(".", d3_format_decimalPoint);
negative += basePrefix;
negative += base;
return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + suffix;
};
};
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
var d3_format_types = d3.map({
b: function(x) {
return x.toString(2);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
var d3_format_decimalPoint = {decimal_point},
d3_format_thousandsSeparator = {thousands_sep},
d3_format_grouping = {grouping};
d3_format_grouping = {grouping},
d3_format_currencySymbol = {currency_symbol};
var d3_format_decimalPoint = ".",
d3_format_thousandsSeparator = ",",
d3_format_grouping = [3, 3];
d3_format_grouping = [3, 3],
d3_format_currencySymbol = "$";
......@@ -9,7 +9,7 @@ d3.format = function(specifier) {
fill = match[1] || " ",
align = match[2] || ">",
sign = match[3] || "",
basePrefix = match[4] || "",
base = match[4] || "",
zfill = match[5],
width = +match[6],
comma = match[7],
......@@ -34,13 +34,14 @@ d3.format = function(specifier) {
case "b":
case "o":
case "x":
case "X": if (basePrefix) basePrefix = "0" + type.toLowerCase();
case "X": if (base === "#") base = "0" + type.toLowerCase();
case "c":
case "d": integer = true; precision = 0; break;
case "s": scale = -1; type = "r"; break;
}
if (basePrefix === "#") basePrefix = "";
if (base === "#") base = "";
else if (base === "$") base = d3_format_currencySymbol;
// If no precision is specified for r, fallback to general notation.
if (type == "r" && !precision) type = "g";
......@@ -78,7 +79,7 @@ d3.format = function(specifier) {
// If the fill character is not "0", grouping is applied before padding.
if (!zfill && comma) value = d3_format_group(value);
var length = basePrefix.length + value.length + (zcomma ? 0 : negative.length),
var length = base.length + value.length + (zcomma ? 0 : negative.length),
padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
// If the fill character is "0", grouping is applied after padding.
......@@ -86,7 +87,7 @@ d3.format = function(specifier) {
if (d3_format_decimalPoint) value.replace(".", d3_format_decimalPoint);
negative += basePrefix;
negative += base;
return (align === "<" ? negative + value + padding
: align === ">" ? padding + negative + value
......@@ -95,8 +96,8 @@ d3.format = function(specifier) {
};
};
// [[fill]align][sign][#][0][width][,][.precision][type]
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
// [[fill]align][sign][base][0][width][,][.precision][type]
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
var d3_format_types = d3.map({
b: function(x) { return x.toString(2); },
......
......@@ -99,6 +99,31 @@ suite.addBatch({
assert.strictEqual(f(999500), "999.5k");
assert.strictEqual(f(.009995), "9.995m");
},
"can output a currency": function(format) {
var f = format("$");
assert.strictEqual(f(0), "$0");
assert.strictEqual(f(.042), "$0.042");
assert.strictEqual(f(.42), "$0.42");
assert.strictEqual(f(4.2), "$4.2");
assert.strictEqual(f(-.042), "-$0.042");
assert.strictEqual(f(-.42), "-$0.42");
assert.strictEqual(f(-4.2), "-$4.2");
},
"can output a currency with comma-grouping and sign": function(format) {
var f = format("+$,.2f");
assert.strictEqual(f(0), "+$0.00");
assert.strictEqual(f(0.429), "+$0.43");
assert.strictEqual(f(-0.429), "-$0.43");
assert.strictEqual(f(-1), "-$1.00");
assert.strictEqual(f(1e4), "+$10,000.00");
},
"can output a currency with si-prefix notation": function(format) {
var f = format("$.2s");
assert.strictEqual(f(0), "$0.0");
assert.strictEqual(f(2.5e5), "$250k");
assert.strictEqual(f(-2.5e8), "-$250M");
assert.strictEqual(f(2.5e11), "$250G");
},
"can output a percentage": function(format) {
var f = format("%");
assert.strictEqual(f(0), "0%");
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment