From 756ff5c77ad9dd6473ef66f671851b4e94676cad Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 15 May 2013 15:48:13 -0700 Subject: [PATCH] Fix #1247 - more robust interpolation, again. This removes the use of interpolateString if the start and end values passed to d3.interpolate are different types; the interpolation behavior should be based solely on the end value. Now if you interpolate an undefined attribute (the empty string) to a number, it will use number interpolation as expected. This also fixes interpolateString such that it always returns a string, even if both the start and end value are coercible to numbers. --- src/interpolate/interpolate.js | 2 +- src/interpolate/string.js | 4 ++- test/interpolate/interpolate-test.js | 42 +++++++++++++++------------- test/interpolate/string-test.js | 14 +++++----- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/interpolate/interpolate.js b/src/interpolate/interpolate.js index 9c6e241faeefd..bf1fcc861027b 100644 --- a/src/interpolate/interpolate.js +++ b/src/interpolate/interpolate.js @@ -24,7 +24,7 @@ function d3_interpolateByName(name) { d3.interpolators = [ function(a, b) { var t = typeof b; - return (t === "string" || t !== typeof a ? (d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString) + return (t === "string" ? (d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString) : b instanceof d3_Color ? d3_interpolateRgb : t === "object" ? (Array.isArray(b) ? d3_interpolateArray : d3_interpolateObject) : d3_interpolateNumber)(a, b); diff --git a/src/interpolate/string.js b/src/interpolate/string.js index dd125bcc46594..843870b3a25cf 100644 --- a/src/interpolate/string.js +++ b/src/interpolate/string.js @@ -73,7 +73,9 @@ function d3_interpolateString(a, b) { // Special optimization for only a single match. if (s.length === 1) { - return s[0] == null ? q[0].x : function() { return b; }; + return s[0] == null + ? (o = q[0].x, function(t) { return o(t) + ""; }) + : function() { return b; }; } // Otherwise, interpolate each of the numbers and rejoin the string. diff --git a/test/interpolate/interpolate-test.js b/test/interpolate/interpolate-test.js index bad0d345d9cc9..b826a4e5a0bd4 100644 --- a/test/interpolate/interpolate-test.js +++ b/test/interpolate/interpolate-test.js @@ -8,13 +8,18 @@ suite.addBatch({ "interpolate": { topic: load("interpolate/interpolate").document(), - "when a and b are numbers": { + "when b is a number": { "interpolates numbers": function(d3) { assert.strictEqual(d3.interpolate(2, 12)(.4), 6); + }, + "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); } }, - "when a and b are color strings": { + "when b is a color string": { "interpolates RGB values and returns a hexadecimal string": function(d3) { assert.strictEqual(d3.interpolate("#ff0000", "#008000")(.4), "#993300"); }, @@ -26,10 +31,13 @@ suite.addBatch({ }, "interpolates decimal HSL colors in RGB": function(d3) { assert.strictEqual(d3.interpolate("hsl(0,100%,50%)", "hsl(120,100%,25%)")(.4), "#993300"); + }, + "coerces a to a color": function(d3) { + assert.strictEqual(d3.interpolate({toString: function() { return "red"; }}, "green")(.4), "#993300"); } }, - "when a and b are color objects": { + "when b is a color object": { "interpolates RGB values and returns a hexadecimal string": function(d3) { assert.strictEqual(d3.interpolate(d3.rgb(255, 0, 0), d3.rgb(0, 128, 0))(.4), "#993300"); }, @@ -41,16 +49,19 @@ suite.addBatch({ }, "interpolates d3.hcl in RGB": function(d3) { assert.strictEqual(d3.interpolate(d3.hcl("red"), d3.hcl("green"))(.4), "#993300"); + }, + "coerces a to a color": function(d3) { + assert.strictEqual(d3.interpolate({toString: function() { return "red"; }}, "green")(.4), "#993300"); } }, - "when a and b are strings": { + "when b is a string": { "interpolates matching numbers in both strings": function(d3) { assert.strictEqual(d3.interpolate(" 10/20 30", "50/10 100 ")(.4), "26/16 58 "); }, - "if a and b are coercible to numbers, interpolates numbers rather than strings": function(d3) { - assert.strictEqual(d3.interpolate("1.", "2.")(.5), 1.5); - assert.strictEqual(d3.interpolate("1e+3", "1e+4")(.5), 5500); + "if b is coercible to a number, still returns a string": function(d3) { + assert.strictEqual(d3.interpolate("1.", "2.")(.5), "1.5"); + assert.strictEqual(d3.interpolate("1e+3", "1e+4")(.5), "5500"); }, "preserves non-numbers in string b": function(d3) { assert.strictEqual(d3.interpolate(" 10/20 30", "50/10 foo ")(.4), "26/16 foo "); @@ -60,10 +71,13 @@ suite.addBatch({ }, "preserves equal-value numbers in both strings": function(d3) { assert.strictEqual(d3.interpolate(" 10/20 100 20", "50/10 100, 20 ")(.4), "26/16 100, 20 "); + }, + "coerces a to a string": function(d3) { + assert.strictEqual(d3.interpolate({toString: function() { return "1."; }}, "2.")(.5), "1.5"); } }, - "when a and b are arrays": { + "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]"); }, @@ -77,7 +91,7 @@ suite.addBatch({ } }, - "when a and b are objects": { + "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}); }, @@ -93,16 +107,6 @@ suite.addBatch({ } }, - "when a and b are different types": { - "coerces both types to strings": function(d3) { - assert.strictEqual(d3.interpolate("2", 12)(.4), 6); - assert.strictEqual(d3.interpolate("2px", 12)(.4), 6); - assert.strictEqual(d3.interpolate([2], 12)(.4), 6); - assert.strictEqual(d3.interpolate({valueOf: function() { return 2; }}, 12)(.4), 6); - assert.strictEqual(d3.interpolate({toString: function() { return 2; }}, 12)(.4), 6); - } - }, - "may or may not interpolate between enumerable and non-enumerable properties": function(d3) { var a = Object.create({}, {foo: {value: 1, enumerable: true}}), b = Object.create({}, {foo: {value: 2, enumerable: false}}); diff --git a/test/interpolate/string-test.js b/test/interpolate/string-test.js index e9ff224875d97..5e7b106705201 100644 --- a/test/interpolate/string-test.js +++ b/test/interpolate/string-test.js @@ -27,15 +27,15 @@ suite.addBatch({ assert.strictEqual(interpolate(" 10/20 100 20", "50/10 100, 20 ")(.4), "26/16 100, 20 "); }, "interpolates decimal notation correctly": function(interpolate) { - assert.strictEqual(interpolate("1.", "2.")(.5), 1.5); + assert.strictEqual(interpolate("1.", "2.")(.5), "1.5"); }, "interpolates exponent notation correctly": function(interpolate) { - assert.strictEqual(interpolate("1e+3", "1e+4")(.5), 5500); - assert.strictEqual(interpolate("1e-3", "1e-4")(.5), 0.00055); - assert.strictEqual(interpolate("1.e-3", "1.e-4")(.5), 0.00055); - assert.strictEqual(interpolate("-1.e-3", "-1.e-4")(.5), -0.00055); - assert.strictEqual(interpolate("+1.e-3", "+1.e-4")(.5), 0.00055); - assert.strictEqual(interpolate(".1e-2", ".1e-3")(.5), 0.00055); + assert.strictEqual(interpolate("1e+3", "1e+4")(.5), "5500"); + assert.strictEqual(interpolate("1e-3", "1e-4")(.5), "0.00055"); + assert.strictEqual(interpolate("1.e-3", "1.e-4")(.5), "0.00055"); + assert.strictEqual(interpolate("-1.e-3", "-1.e-4")(.5), "-0.00055"); + assert.strictEqual(interpolate("+1.e-3", "+1.e-4")(.5), "0.00055"); + assert.strictEqual(interpolate(".1e-2", ".1e-3")(.5), "0.00055"); } } });