diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js index db84d70b4ac..65718fcbadd 100644 --- a/src/plots/cartesian/autorange.js +++ b/src/plots/cartesian/autorange.js @@ -101,10 +101,11 @@ function getAutoRange(gd, ax) { if(ax.breaks) { var breaksOut = ax.locateBreaks(v0, v1); for(var i = 0; i < breaksOut.length; i++) { - lBreaks += (breaksOut[i].max - breaksOut[i].min); + var brk = breaksOut[i]; + lBreaks += brk.max - brk.min; } } - return lBreaks; + return (axReverse ? -1 : 1) * lBreaks; }; var mbest = 0; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index d1f58fa77a1..8261d36af9b 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -527,7 +527,7 @@ axes.prepTicks = function(ax) { if(ax.tickmode === 'array') nt *= 100; - ax._roughDTick = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / nt; + ax._roughDTick = (Math.abs(rng[1] - rng[0]) - Math.abs(ax._lBreaks || 0)) / nt; axes.autoTicks(ax, ax._roughDTick); // check for a forced minimum dtick @@ -1009,7 +1009,7 @@ axes.tickText = function(ax, x, hover, noSuffixPrefix) { if(arrayMode && Array.isArray(ax.ticktext)) { var rng = Lib.simpleMap(ax.range, ax.r2l); - var minDiff = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / 10000; + var minDiff = (Math.abs(rng[1] - rng[0]) - Math.abs(ax._lBreaks || 0)) / 10000; for(i = 0; i < ax.ticktext.length; i++) { if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 74e3c801c91..117df9affe8 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -987,13 +987,12 @@ function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) { if(axi.fixedrange) continue; if(axi.breaks) { - if(axi._id.charAt(0) === 'y') { - updates[axi._name + '.range[0]'] = axi.l2r(axi.p2l((1 - r0Fraction) * axi._length)); - updates[axi._name + '.range[1]'] = axi.l2r(axi.p2l((1 - r1Fraction) * axi._length)); - } else { - updates[axi._name + '.range[0]'] = axi.l2r(axi.p2l(r0Fraction * axi._length)); - updates[axi._name + '.range[1]'] = axi.l2r(axi.p2l(r1Fraction * axi._length)); - } + var isY = axi._id.charAt(0) === 'y'; + var r0F = isY ? (1 - r0Fraction) : r0Fraction; + var r1F = isY ? (1 - r1Fraction) : r1Fraction; + + updates[axi._name + '.range[0]'] = axi.l2r(axi.p2l(r0F * axi._length)); + updates[axi._name + '.range[1]'] = axi.l2r(axi.p2l(r1F * axi._length)); } else { var axRangeLinear0 = axi._rl[0]; var axRangeLinearSpan = axi._rl[1] - axRangeLinear0; diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index b7989f9b6fa..7f468ec2df0 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -189,67 +189,58 @@ module.exports = function setConvert(ax, fullLayout) { }; if(ax.breaks) { - if(axLetter === 'y') { - l2p = function(v) { - if(!isNumeric(v)) return BADNUM; - if(!ax._breaks.length) return _l2p(v, ax._m, ax._b); - - var b = ax._B[0]; - for(var i = 0; i < ax._breaks.length; i++) { - var brk = ax._breaks[i]; - if(v <= brk.min) b = ax._B[i + 1]; - else if(v > brk.min && v < brk.max) { - // when v falls into break, pick offset 'closest' to it - if(v - brk.min <= brk.max - v) b = ax._B[i + 1]; - else b = ax._B[i]; - break; - } else if(v > brk.max) break; - } - return _l2p(v, -ax._m2, b); - }; - p2l = function(px) { - if(!isNumeric(px)) return BADNUM; - if(!ax._breaks.length) return _p2l(px, ax._m, ax._b); - - var b = ax._B[0]; - for(var i = 0; i < ax._breaks.length; i++) { - var brk = ax._breaks[i]; - if(px >= brk.pmin) b = ax._B[i + 1]; - else if(px < brk.pmax) break; - } - return _p2l(px, -ax._m2, b); - }; - } else { - l2p = function(v) { - if(!isNumeric(v)) return BADNUM; - if(!ax._breaks.length) return _l2p(v, ax._m, ax._b); - - var b = ax._B[0]; - for(var i = 0; i < ax._breaks.length; i++) { - var brk = ax._breaks[i]; - if(v >= brk.max) b = ax._B[i + 1]; - else if(v > brk.min && v < brk.max) { - // when v falls into break, pick offset 'closest' to it - if(v - brk.min <= brk.max - v) b = ax._B[i]; - else b = ax._B[i + 1]; - break; - } else if(v < brk.min) break; + l2p = function(v) { + if(!isNumeric(v)) return BADNUM; + var len = ax._breaks.length; + if(!len) return _l2p(v, ax._m, ax._b); + + var isY = axLetter === 'y'; + var pos = isY ? -v : v; + + var q = 0; + for(var i = 0; i < len; i++) { + var nextI = i + 1; + var brk = ax._breaks[i]; + + var min = isY ? -brk.max : brk.min; + var max = isY ? -brk.min : brk.max; + + if(pos < min) break; + if(pos > max) q = nextI; + else { + // when falls into break, pick 'closest' offset + q = pos > (min + max) / 2 ? nextI : i; + break; } - return _l2p(v, ax._m2, b); - }; - p2l = function(px) { - if(!isNumeric(px)) return BADNUM; - if(!ax._breaks.length) return _p2l(px, ax._m, ax._b); - - var b = ax._B[0]; - for(var i = 0; i < ax._breaks.length; i++) { - var brk = ax._breaks[i]; - if(px >= brk.pmax) b = ax._B[i + 1]; - else if(px < brk.pmin) break; + } + return _l2p(v, (isY ? -1 : 1) * ax._m2, ax._B[q]); + }; + + p2l = function(px) { + if(!isNumeric(px)) return BADNUM; + var len = ax._breaks.length; + if(!len) return _p2l(px, ax._m, ax._b); + + var isY = axLetter === 'y'; + var pos = isY ? -px : px; + + var q = 0; + for(var i = 0; i < len; i++) { + var nextI = i + 1; + var brk = ax._breaks[i]; + + var min = isY ? -brk.pmax : brk.pmin; + var max = isY ? -brk.pmin : brk.pmax; + + if(pos < min) break; + if(pos > max) q = nextI; + else { + q = i; + break; } - return _p2l(px, ax._m2, b); - }; - } + } + return _p2l(px, (isY ? -1 : 1) * ax._m2, ax._B[q]); + }; } // conversions among c/l/p are fairly simple - do them together for all axis types @@ -561,7 +552,7 @@ module.exports = function setConvert(ax, fullLayout) { // set of "N" disjoint breaks inside the range ax._breaks = []; - // length of these breaks in value space + // length of these breaks in value space - negative on reversed axes ax._lBreaks = 0; // l2p slope (same for all intervals) ax._m2 = 0; @@ -575,12 +566,13 @@ module.exports = function setConvert(ax, fullLayout) { Math.min(rl0, rl1), Math.max(rl0, rl1) ); - var signAx = rl0 > rl1 ? -1 : 1; + var axReverse = rl0 > rl1; + var signAx = axReverse ? -1 : 1; if(ax._breaks.length) { for(i = 0; i < ax._breaks.length; i++) { brk = ax._breaks[i]; - ax._lBreaks += (brk.max - brk.min); + ax._lBreaks += brk.max - brk.min; } ax._m2 = ax._length / (rl1 - rl0 - ax._lBreaks * signAx); @@ -597,8 +589,7 @@ module.exports = function setConvert(ax, fullLayout) { brk = ax._breaks[i]; ax._B.push(ax._B[ax._B.length - 1] - ax._m2 * (brk.max - brk.min) * signAx); } - - if(signAx === -1) { + if(axReverse) { ax._B.reverse(); } @@ -606,8 +597,8 @@ module.exports = function setConvert(ax, fullLayout) { // to not have to loop through the _breaks twice during `p2l` for(i = 0; i < ax._breaks.length; i++) { brk = ax._breaks[i]; - brk.pmin = l2p(brk.min); - brk.pmax = l2p(brk.max); + brk.pmin = l2p(axReverse ? brk.max : brk.min); + brk.pmax = l2p(axReverse ? brk.min : brk.max); } } } diff --git a/test/image/baselines/axes_breaks-night_autorange-reversed.png b/test/image/baselines/axes_breaks-night_autorange-reversed.png new file mode 100644 index 00000000000..2c3e3e4536d Binary files /dev/null and b/test/image/baselines/axes_breaks-night_autorange-reversed.png differ diff --git a/test/image/mocks/axes_breaks-night_autorange-reversed.json b/test/image/mocks/axes_breaks-night_autorange-reversed.json new file mode 100644 index 00000000000..e7341919e7d --- /dev/null +++ b/test/image/mocks/axes_breaks-night_autorange-reversed.json @@ -0,0 +1,289 @@ +{ + "data": [ + { + "type": "scatter", + "mode": "lines+markers", + "x": [ + "1970-01-01 00:00", + "1970-01-01 03:00", + "1970-01-01 06:00", + "1970-01-01 09:00", + "1970-01-01 12:00", + "1970-01-01 15:00", + "1970-01-01 18:00", + "1970-01-01 21:00", + "1970-01-02 00:00", + "1970-01-02 03:00", + "1970-01-02 06:00", + "1970-01-02 09:00", + "1970-01-02 12:00", + "1970-01-02 15:00", + "1970-01-02 18:00", + "1970-01-02 21:00", + "1970-01-03 00:00", + "1970-01-03 03:00", + "1970-01-03 06:00", + "1970-01-03 09:00", + "1970-01-03 12:00", + "1970-01-03 15:00", + "1970-01-03 18:00", + "1970-01-03 21:00", + "1970-01-04 00:00", + "1970-01-04 03:00", + "1970-01-04 06:00", + "1970-01-04 09:00", + "1970-01-04 12:00", + "1970-01-04 15:00", + "1970-01-04 18:00", + "1970-01-04 21:00" + ], + "y": [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 + ] + }, + { + "xaxis": "x2", + "yaxis": "y2", + "type": "scatter", + "mode": "lines+markers", + "x": [ + "1970-01-01 00:00", + "1970-01-01 03:00", + "1970-01-01 06:00", + "1970-01-01 09:00", + "1970-01-01 12:00", + "1970-01-01 15:00", + "1970-01-01 18:00", + "1970-01-01 21:00", + "1970-01-02 00:00", + "1970-01-02 03:00", + "1970-01-02 06:00", + "1970-01-02 09:00", + "1970-01-02 12:00", + "1970-01-02 15:00", + "1970-01-02 18:00", + "1970-01-02 21:00", + "1970-01-03 00:00", + "1970-01-03 03:00", + "1970-01-03 06:00", + "1970-01-03 09:00", + "1970-01-03 12:00", + "1970-01-03 15:00", + "1970-01-03 18:00", + "1970-01-03 21:00", + "1970-01-04 00:00", + "1970-01-04 03:00", + "1970-01-04 06:00", + "1970-01-04 09:00", + "1970-01-04 12:00", + "1970-01-04 15:00", + "1970-01-04 18:00", + "1970-01-04 21:00" + ], + "y": [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 + ] + }, + { + "xaxis": "x3", + "yaxis": "y3", + "type": "scatter", + "mode": "lines+markers", + "orientation": "h", + "y": [ + "1970-01-01 00:00", + "1970-01-01 03:00", + "1970-01-01 06:00", + "1970-01-01 09:00", + "1970-01-01 12:00", + "1970-01-01 15:00", + "1970-01-01 18:00", + "1970-01-01 21:00", + "1970-01-02 00:00", + "1970-01-02 03:00", + "1970-01-02 06:00", + "1970-01-02 09:00", + "1970-01-02 12:00", + "1970-01-02 15:00", + "1970-01-02 18:00", + "1970-01-02 21:00", + "1970-01-03 00:00", + "1970-01-03 03:00", + "1970-01-03 06:00", + "1970-01-03 09:00", + "1970-01-03 12:00", + "1970-01-03 15:00", + "1970-01-03 18:00", + "1970-01-03 21:00", + "1970-01-04 00:00", + "1970-01-04 03:00", + "1970-01-04 06:00", + "1970-01-04 09:00", + "1970-01-04 12:00", + "1970-01-04 15:00", + "1970-01-04 18:00", + "1970-01-04 21:00" + ], + "x": [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 + ] + }, + { + "xaxis": "x4", + "yaxis": "y4", + "type": "scatter", + "mode": "lines+markers", + "orientation": "h", + "y": [ + "1970-01-01 00:00", + "1970-01-01 03:00", + "1970-01-01 06:00", + "1970-01-01 09:00", + "1970-01-01 12:00", + "1970-01-01 15:00", + "1970-01-01 18:00", + "1970-01-01 21:00", + "1970-01-02 00:00", + "1970-01-02 03:00", + "1970-01-02 06:00", + "1970-01-02 09:00", + "1970-01-02 12:00", + "1970-01-02 15:00", + "1970-01-02 18:00", + "1970-01-02 21:00", + "1970-01-03 00:00", + "1970-01-03 03:00", + "1970-01-03 06:00", + "1970-01-03 09:00", + "1970-01-03 12:00", + "1970-01-03 15:00", + "1970-01-03 18:00", + "1970-01-03 21:00", + "1970-01-04 00:00", + "1970-01-04 03:00", + "1970-01-04 06:00", + "1970-01-04 09:00", + "1970-01-04 12:00", + "1970-01-04 15:00", + "1970-01-04 18:00", + "1970-01-04 21:00" + ], + "x": [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 + ] + } + ], + "layout": { + "showlegend": false, + "width": 800, + "height": 800, + "xaxis": { + "breaks": [ + { + "pattern": "%H", + "bounds": [ + 18, + 6 + ], + "operation": "[)" + } + ], + "domain": [ + 0, + 0.48 + ] + }, + "xaxis2": { + "breaks": [ + { + "pattern": "%H", + "bounds": [ + 18, + 6 + ], + "operation": "[)" + } + ], + "autorange": "reversed", + "anchor": "y2", + "domain": [ + 0.52, + 1 + ] + }, + "xaxis3": { + "anchor": "y3", + "domain": [ + 0, + 0.48 + ] + }, + "xaxis4": { + "anchor": "y4", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis": { + "domain": [ + 0, + 0.48 + ] + }, + "yaxis2": { + "anchor": "x2", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis3": { + "breaks": [ + { + "pattern": "%H", + "bounds": [ + 18, + 6 + ], + "operation": "[)" + } + ], + "anchor": "x3", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis4": { + "breaks": [ + { + "pattern": "%H", + "bounds": [ + 18, + 6 + ], + "operation": "[)" + } + ], + "autorange": "reversed", + "anchor": "x4", + "domain": [ + 0, + 0.48 + ] + } + } +}