Fix #2039: improve [un]interpolateNumber precision.
* uninterpolate: The use of a reciprocal in d3_uninterpolateNumber to avoid division results in a small loss of precision (the following is a paraphrase): function d3_uninterpolateNumber(a, b) { var k = b - a ? 1 / (b - a) : 0; return function(x) { return (x - a) * k; }; } For x = a, there is no problem, since u = (a - a) * k = 0 * k = 0 as expected. For x = b, we have u = (b - a) * k, and since k cannot represent 1 / (b - a) exactly, we don’t get u = 1, but something very close to 1. Instead, if we perform the division within the generated function, we can ensure we always get u = 1 for x = b: function d3_uninterpolateNumber(a, b) { var k = b - a || Infinity; return function(x) { return (x - a) / k; }; } Again, for x = a, we simply have u = (a - a) / k = 0. For x = b, we have u = (b - a) / k = (b - a) / (b - a) = 1. * interpolate: Similarly, for d3_interpolateNumber, we have a small loss of precision, this time due to subtraction. Paraphrased: function d3_interpolateNumber(a, b) { var d = b - a; return function(t) { return a + t * d; }; } There is no issue for t = 0, because we always get i = a + 0 * d = a. However, for t = 1, we get i = a + d, which might not be exactly equal to b as desired. The following will return precisely b for t = 1: function d3_interpolateNumber(a, b) { return function(t) { return a * (1 - t) + b * t; }; }
This commit is contained in:
Родитель
f38ed05731
Коммит
a4429fa041
|
@ -5605,9 +5605,9 @@
|
|||
}
|
||||
d3.interpolateNumber = d3_interpolateNumber;
|
||||
function d3_interpolateNumber(a, b) {
|
||||
b -= a = +a;
|
||||
a = +a, b = +b;
|
||||
return function(t) {
|
||||
return a + b * t;
|
||||
return a * (1 - t) + b * t;
|
||||
};
|
||||
}
|
||||
d3.interpolateString = d3_interpolateString;
|
||||
|
@ -5906,15 +5906,15 @@
|
|||
};
|
||||
}
|
||||
function d3_uninterpolateNumber(a, b) {
|
||||
b = b - (a = +a) ? 1 / (b - a) : 0;
|
||||
b = b - (a = +a) || Infinity;
|
||||
return function(x) {
|
||||
return (x - a) * b;
|
||||
return (x - a) / b;
|
||||
};
|
||||
}
|
||||
function d3_uninterpolateClamp(a, b) {
|
||||
b = b - (a = +a) ? 1 / (b - a) : 0;
|
||||
b = b - (a = +a) || Infinity;
|
||||
return function(x) {
|
||||
return Math.max(0, Math.min(1, (x - a) * b));
|
||||
return Math.max(0, Math.min(1, (x - a) / b));
|
||||
};
|
||||
}
|
||||
d3.layout = {};
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,6 +1,6 @@
|
|||
d3.interpolateNumber = d3_interpolateNumber;
|
||||
|
||||
function d3_interpolateNumber(a, b) {
|
||||
b -= a = +a;
|
||||
return function(t) { return a + b * t; };
|
||||
a = +a, b = +b;
|
||||
return function(t) { return a * (1 - t) + b * t; };
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
function d3_uninterpolateNumber(a, b) {
|
||||
b = b - (a = +a) ? 1 / (b - a) : 0;
|
||||
return function(x) { return (x - a) * b; };
|
||||
b = b - (a = +a) || Infinity;
|
||||
return function(x) { return (x - a) / b; };
|
||||
}
|
||||
|
||||
function d3_uninterpolateClamp(a, b) {
|
||||
b = b - (a = +a) ? 1 / (b - a) : 0;
|
||||
return function(x) { return Math.max(0, Math.min(1, (x - a) * b)); };
|
||||
b = b - (a = +a) || Infinity;
|
||||
return function(x) { return Math.max(0, Math.min(1, (x - a) / b)); };
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ suite.addBatch({
|
|||
|
||||
"when b is a number": {
|
||||
"interpolates numbers": function(d3) {
|
||||
assert.strictEqual(d3.interpolate(2, 12)(.4), 6);
|
||||
assert.strictEqual(d3.interpolate(2, 12)(.25), 4.5);
|
||||
},
|
||||
"coerces a to a number": function(d3) {
|
||||
assert.strictEqual(d3.interpolate("", 1)(.5), .5);
|
||||
assert.strictEqual(d3.interpolate("2", 12)(.4), 6);
|
||||
assert.strictEqual(d3.interpolate([2], 12)(.4), 6);
|
||||
assert.strictEqual(d3.interpolate("2", 12)(.25), 4.5);
|
||||
assert.strictEqual(d3.interpolate([2], 12)(.25), 4.5);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -79,11 +79,11 @@ suite.addBatch({
|
|||
|
||||
"when b is an array": {
|
||||
"interpolates each element in b": function(d3) {
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([2, 4], [12, 24])(.4)), "[6,12]");
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([2, 4], [12, 24])(.25)), "[4.5,9]");
|
||||
},
|
||||
"interpolates arrays, even when both a and b are coercible to numbers": function(d3) {
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([2], [12])(.4)), "[6]");
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([[2]], [[12]])(.4)), "[[6]]");
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([2], [12])(.25)), "[4.5]");
|
||||
assert.strictEqual(JSON.stringify(d3.interpolate([[2]], [[12]])(.25)), "[[4.5]]");
|
||||
},
|
||||
"reuses the returned array during interpolation": function(d3) {
|
||||
var i = d3.interpolate([2], [12]);
|
||||
|
@ -93,10 +93,10 @@ suite.addBatch({
|
|||
|
||||
"when b is an object": {
|
||||
"interpolates each property in b": function(d3) {
|
||||
assert.deepEqual(d3.interpolate({foo: 2, bar: 4}, {foo: 12, bar: 24})(.4), {foo: 6, bar: 12});
|
||||
assert.deepEqual(d3.interpolate({foo: 2, bar: 4}, {foo: 12, bar: 24})(.25), {foo: 4.5, bar: 9});
|
||||
},
|
||||
"interpolates numbers if b is coercible to a number (!isNaN(+b))": function(d3) {
|
||||
assert.strictEqual(d3.interpolate(new Number(2), new Number(12))(.4), 6);
|
||||
assert.strictEqual(d3.interpolate(new Number(2), new Number(12))(.25), 4.5);
|
||||
assert.strictEqual(d3.interpolate(new Date(2012, 0, 1), new Date(2013, 0, 1))(.5), +new Date(2012, 6, 2, 1));
|
||||
assert.strictEqual(d3.interpolate(1, null)(.4), .6); // +null = 0
|
||||
assert.isNaN(d3.interpolate("blue", null)(.4));
|
||||
|
|
|
@ -8,11 +8,11 @@ suite.addBatch({
|
|||
"interpolateNumber": {
|
||||
topic: load("interpolate/number").expression("d3.interpolateNumber"),
|
||||
"interpolates numbers": function(interpolate) {
|
||||
assert.strictEqual(interpolate(2, 12)(.4), 6);
|
||||
assert.strictEqual(interpolate(2, 12)(.6), 8);
|
||||
assert.strictEqual(interpolate(2, 12)(.25), 4.5);
|
||||
assert.strictEqual(interpolate(2, 12)(.75), 9.5);
|
||||
},
|
||||
"coerces strings to numbers": function(interpolate) {
|
||||
assert.strictEqual(interpolate("2", "12")(.4), 6);
|
||||
assert.strictEqual(interpolate("2", "12")(.25), 4.5);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ suite.addBatch({
|
|||
assert.strictEqual(interpolate(" 10/20 30", "50/10 100 ")(.4), "26/16 58 ");
|
||||
},
|
||||
"coerces objects to strings": function(interpolate) {
|
||||
assert.strictEqual(interpolate({toString: function() { return "2px"; }}, {toString: function() { return "12px"; }})(.4), "6px");
|
||||
assert.strictEqual(interpolate({toString: function() { return "2px"; }}, {toString: function() { return "12px"; }})(.25), "4.5px");
|
||||
},
|
||||
"preserves non-numbers in string b": function(interpolate) {
|
||||
assert.strictEqual(interpolate(" 10/20 30", "50/10 foo ")(.2), "18/18 foo ");
|
||||
|
|
Загрузка…
Ссылка в новой задаче