diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index a51ff198456..cc3c6ccf7ee 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -445,23 +445,25 @@ module.exports = function setConvert(ax, fullLayout) { var bounds = Lib.simpleMap([minallowed, maxallowed], ax.r2l); - if(minallowed !== undefined && rng[0] < bounds[0]) range[axrev ? 1 : 0] = minallowed; - if(maxallowed !== undefined && rng[1] > bounds[1]) range[axrev ? 0 : 1] = maxallowed; - - if(range[0] === range[1]) { - var minL = ax.l2r(minallowed); - var maxL = ax.l2r(maxallowed); + if(minallowed !== undefined && rng[0] < bounds[0]) { + range[axrev ? 1 : 0] = minallowed; + rng[0] = bounds[0]; + } + if(maxallowed !== undefined && rng[1] > bounds[1]) { + range[axrev ? 0 : 1] = maxallowed; + rng[1] = bounds[1]; + } + // If clamping collapsed or inverted the range, extend the opposite end + // so we preserve the original orientation. Otherwise a default range + // entirely below minallowed (or above maxallowed) would flip the axis. + if(rng[0] >= rng[1]) { if(minallowed !== undefined) { - var _max = minL + 1; - if(maxallowed !== undefined) _max = Math.min(_max, maxL); - range[axrev ? 1 : 0] = _max; - } - - if(maxallowed !== undefined) { - var _min = maxL + 1; - if(minallowed !== undefined) _min = Math.max(_min, minL); - range[axrev ? 0 : 1] = _min; + var _max = bounds[0] + 1; + if(maxallowed !== undefined) _max = Math.min(_max, bounds[1]); + range[axrev ? 0 : 1] = ax.l2r(_max); + } else if(maxallowed !== undefined) { + range[axrev ? 1 : 0] = ax.l2r(bounds[1] - 1); } } }; diff --git a/test/image/baselines/allowed-range-small.png b/test/image/baselines/allowed-range-small.png index 22e662ea4cd..25593765a8d 100644 Binary files a/test/image/baselines/allowed-range-small.png and b/test/image/baselines/allowed-range-small.png differ diff --git a/test/image/baselines/allowed-range.png b/test/image/baselines/allowed-range.png index 05c73586d37..7e0f13d48e8 100644 Binary files a/test/image/baselines/allowed-range.png and b/test/image/baselines/allowed-range.png differ diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 4d85a14fc37..cc71057ca4a 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -1733,6 +1733,65 @@ describe('Test axes', function() { }); }); + describe('minallowed / maxallowed', function() { + // regression test for https://github.com/plotly/plotly.js/issues/7717 + var gd; + + beforeEach(function() { gd = createGraphDiv(); }); + afterEach(destroyGraphDiv); + + function expectForward(done, minallowed) { + Plotly.newPlot(gd, [{ + x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }], { + xaxis: { minallowed: minallowed } + }).then(function() { + var r = gd._fullLayout.xaxis.range; + expect(r[0]).toBeLessThan(r[1], 'axis should not be reversed for minallowed=' + minallowed); + expect(r[0]).toBe(minallowed, 'axis min should be pinned at minallowed'); + }).then(done, done.fail); + } + + it('does not reverse axis when minallowed exceeds default range max', function(done) { + expectForward(done, 7); + }); + + it('does not reverse axis when minallowed equals default range max', function(done) { + expectForward(done, 6); + }); + + it('does not reverse axis when minallowed is well above default range max', function(done) { + expectForward(done, 100); + }); + + it('keeps explicit autorange:reversed even when minallowed is set', function(done) { + Plotly.newPlot(gd, [{ + x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }], { + xaxis: { autorange: 'reversed', minallowed: 7 } + }).then(function() { + var r = gd._fullLayout.xaxis.range; + expect(r[0]).toBeGreaterThan(r[1], 'axis should remain reversed'); + expect(r[1]).toBe(7, 'min slot (range[1] when reversed) should be minallowed'); + }).then(done, done.fail); + }); + + it('does not reverse axis when maxallowed is below default range min', function(done) { + Plotly.newPlot(gd, [{ + x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }], { + xaxis: { maxallowed: -2 } + }).then(function() { + var r = gd._fullLayout.xaxis.range; + expect(r[0]).toBeLessThan(r[1], 'axis should not be reversed'); + expect(r[1]).toBe(-2, 'axis max should be pinned at maxallowed'); + }).then(done, done.fail); + }); + }); + describe('constraints relayout', function() { var gd;