diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7ca242fb687..bd872d7f9af 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -804,13 +804,18 @@ axes.calcTicks = function calcTicks(ax) { endtick = (axrev) ? Math.max(-0.5, endtick) : Math.min(ax._categories.length - 0.5, endtick); } + + var xPrevious = null; + var maxTicks = Math.max(1000, ax._length || 0); for(var x = ax._tmin; (axrev) ? (x >= endtick) : (x <= endtick); x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) { - vals.push(x); + // prevent infinite loops - no more than one tick per pixel, + // and make sure each value is different from the previous + if(vals.length > maxTicks || x === xPrevious) break; + xPrevious = x; - // prevent infinite loops - if(vals.length > 1000) break; + vals.push(x); } // save the last tick as well as first, so we can diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index a48525f952f..b76259e1e48 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -2225,6 +2225,38 @@ describe('Test axes', function() { ['2000-01-01 11:00:00.0001', 'Jan 1, 2000, 11:00:00.0001'] ]); }); + + it('avoids infinite loops due to rounding errors', function() { + var textOut = mockCalc({ + type: 'linear', + tickmode: 'linear', + tick0: 1e200, + dtick: 1e-200, + range: [1e200, 2e200] + }); + + // This actually gives text '-Infinity' because it can't + // calculate the first tick properly, but since it's not going to + // be able to do any better with the rest, we don't much care. + expect(textOut.length).toBe(1); + }); + + it('truncates at the greater of 1001 ticks or one per pixel', function() { + var ax = { + type: 'linear', + tickmode: 'linear', + tick0: 0, + dtick: 1, + range: [0, 1e6], + _length: 100 + }; + + expect(mockCalc(ax).length).toBe(1001); + + ax._length = 10000; + + expect(mockCalc(ax).length).toBe(10001); + }); }); describe('autoBin', function() {