Automatic precision for custom tick formats.

You can now pass a format specifier to scale.tickFormat (for linear, pow and
identity scales). If the format specifier doesn't have a defined precision, the
precision will be set automatically by the scale, returning the appropriate
format. This provides a convenient, declarative way of specifying a format whose
precision will be automatically set by the scale.

This works with axes, too! For example, `axis.ticks(10, "%")` will now use a
percentage format rather than the default format, while still computing the
appropriate precision.

This commit also includes a fix to make d3.format more robust when unreasonable
precisions are specified. Rather than throwing an error, the nearest reasonable
value is used instead.

Fixes #912.
This commit is contained in:
Mike Bostock 2013-03-11 18:01:35 -07:00
Родитель a82fa305bc
Коммит d0441e6170
8 изменённых файлов: 57 добавлений и 28 удалений

24
d3.js поставляемый
Просмотреть файл

@ -638,6 +638,9 @@ d3 = function() {
}
if (basePrefix === "#") basePrefix = "";
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));
}
type = d3_format_types.get(type) || d3_format_typeDefault;
var zcomma = zfill && comma;
return function(value) {
@ -659,7 +662,7 @@ d3 = function() {
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)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
var d3_format_types = d3.map({
b: function(x) {
return x.toString(2);
@ -2447,8 +2450,8 @@ d3 = function() {
scale.ticks = function(m) {
return d3_scale_linearTicks(domain, m);
};
scale.tickFormat = function(m) {
return d3_scale_linearTickFormat(domain, m);
scale.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(domain, m, format);
};
scale.nice = function() {
d3_scale_nice(domain, d3_scale_linearNice);
@ -2484,8 +2487,11 @@ d3 = function() {
function d3_scale_linearTicks(domain, m) {
return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
}
function d3_scale_linearTickFormat(domain, m) {
return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f");
function d3_scale_linearTickFormat(domain, m, format) {
var precision = -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01);
return d3.format(format ? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) {
return [ b, c, d, e, f, g, h, i || "." + (precision - (j === "%") * 2), j ].join("");
}) : ",." + precision + "f");
}
function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
@ -2611,8 +2617,8 @@ d3 = function() {
scale.ticks = function(m) {
return d3_scale_linearTicks(scale.domain(), m);
};
scale.tickFormat = function(m) {
return d3_scale_linearTickFormat(scale.domain(), m);
scale.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(scale.domain(), m, format);
};
scale.nice = function() {
return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice));
@ -2837,8 +2843,8 @@ d3 = function() {
identity.ticks = function(m) {
return d3_scale_linearTicks(domain, m);
};
identity.tickFormat = function(m) {
return d3_scale_linearTickFormat(domain, m);
identity.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(domain, m, format);
};
identity.copy = function() {
return d3_scale_identity(domain);

10
d3.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -39,6 +39,12 @@ d3.format = function(specifier) {
// 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 = d3_format_types.get(type) || d3_format_typeDefault;
var zcomma = zfill && comma;
@ -84,7 +90,7 @@ d3.format = function(specifier) {
};
// [[fill]align][sign][#][0][width][,][.precision][type]
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
var d3_format_types = d3.map({
b: function(x) { return x.toString(2); },

Просмотреть файл

@ -18,8 +18,8 @@ function d3_scale_identity(domain) {
return d3_scale_linearTicks(domain, m);
};
identity.tickFormat = function(m) {
return d3_scale_linearTickFormat(domain, m);
identity.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(domain, m, format);
};
identity.copy = function() {

Просмотреть файл

@ -55,8 +55,8 @@ function d3_scale_linear(domain, range, interpolate, clamp) {
return d3_scale_linearTicks(domain, m);
};
scale.tickFormat = function(m) {
return d3_scale_linearTickFormat(domain, m);
scale.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(domain, m, format);
};
scale.nice = function() {
@ -105,6 +105,9 @@ function d3_scale_linearTicks(domain, m) {
return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
}
function d3_scale_linearTickFormat(domain, m) {
return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f");
function d3_scale_linearTickFormat(domain, m, format) {
var precision = -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01);
return d3.format(format
? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) { return [b, c, d, e, f, g, h, i || "." + (precision - (j === "%") * 2), j].join(""); })
: ",." + precision + "f");
}

Просмотреть файл

@ -24,8 +24,8 @@ function d3_scale_pow(linear, exponent) {
return d3_scale_linearTicks(scale.domain(), m);
};
scale.tickFormat = function(m) {
return d3_scale_linearTickFormat(scale.domain(), m);
scale.tickFormat = function(m, format) {
return d3_scale_linearTickFormat(scale.domain(), m, format);
};
scale.nice = function() {

Просмотреть файл

@ -325,6 +325,12 @@ suite.addBatch({
assert.strictEqual(f(-42), "-42");
assert.strictEqual(f(-4200000), "-4,200,000");
assert.strictEqual(f(-42000000), "-42,000,000");
},
"unreasonable precision values are clamped to reasonable values": function(format) {
assert.strictEqual(format(".30f")(0), "0.00000000000000000000");
assert.strictEqual(format(".0g")(1), "1");
assert.strictEqual(format(",.-1f")(12345), "12,345");
assert.strictEqual(format("+,.-1%")(123.45), "+12,345%");
}
}
});

Просмотреть файл

@ -207,19 +207,19 @@ suite.addBatch({
assert.equal(t.length, 2);
},
"passes any arguments to the scale's ticks function": function(axis) {
var x = d3.scale.linear(), b = {}, a = axis().ticks(b, 42).scale(x), aa = [],
var x = d3.scale.linear(), b = {}, a = axis().ticks(b, "%").scale(x), aa = [],
g = d3.select("body").html("").append("svg:g");
x.ticks = function() { aa.push(arguments); return [42]; };
g.call(a);
assert.equal(aa.length, 1);
assert.equal(aa[0].length, 2);
assert.equal(aa[0][0], b);
assert.equal(aa[0][1], 42);
assert.equal(aa[0][1], "%");
},
"passes any arguments to the scale's tickFormat function": function(axis) {
var b = {},
x = d3.scale.linear(),
a = axis().scale(x).ticks(b, 42),
a = axis().scale(x).ticks(b, "%"),
g = d3.select("body").html("").append("svg:g"),
aa = [];
@ -232,13 +232,21 @@ suite.addBatch({
assert.equal(aa.length, 1);
assert.equal(aa[0].length, 2);
assert.equal(aa[0][0], b);
assert.equal(aa[0][1], 42);
assert.equal(aa[0][1], "%");
},
"affects the generated ticks": function(axis) {
var a = axis().ticks(20),
var a = axis().ticks(20, "%"),
g = d3.select("body").html("").append("svg:g").call(a),
t = g.selectAll("g");
assert.equal(t[0].length, 21);
assert.equal(t[0][0].textContent, "0%");
},
"only substitutes precision if not specified": function(axis) {
var a = axis().ticks(20, ".5%"),
g = d3.select("body").html("").append("svg:g").call(a),
t = g.selectAll("g");
assert.equal(t[0].length, 21);
assert.equal(t[0][0].textContent, "0.00000%");
}
},