diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 40a1e1e5e96..1c1915ff3b9 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -521,6 +521,9 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { axes.prepTicks = function(ax, opts) { var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts); + ax._dtickInit = ax.dtick; + ax._tick0Init = ax.tick0; + // calculate max number of (auto) ticks to display based on plot size if(ax.tickmode === 'auto' || !ax.dtick) { var nt = ax.nticks; @@ -545,7 +548,7 @@ axes.prepTicks = function(ax, opts) { 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]) / nt; axes.autoTicks(ax, ax._roughDTick); // check for a forced minimum dtick @@ -568,6 +571,10 @@ axes.prepTicks = function(ax, opts) { autoTickRound(ax); }; +function nMonths(dtick) { + return +(dtick.substring(1)); +} + // calculate the ticks: text, values, positioning // if ticks are set to automatic, determine the right values (tick0,dtick) // in any case, set tickround to # of digits to round tick labels to, @@ -580,15 +587,17 @@ axes.calcTicks = function calcTicks(ax, opts) { // in case we're missing some ticktext, we can break out for array ticks if(ax.tickmode === 'array') return arrayTicks(ax); - // find the first tick - ax._tmin = axes.tickFirst(ax, opts); - // add a tiny bit so we get ticks which may have rounded out var exRng = expandRange(rng); var startTick = exRng[0]; var endTick = exRng[1]; // check for reversed axis var axrev = (rng[1] < rng[0]); + var minRange = Math.min(rng[0], rng[1]); + var maxRange = Math.max(rng[0], rng[1]); + + // find the first tick + ax._tmin = axes.tickFirst(ax, opts); // No visible ticks? Quit. // I've only seen this on category axes with all categories off the edge. @@ -601,112 +610,13 @@ axes.calcTicks = function calcTicks(ax, opts) { } var isDLog = (ax.type === 'log') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'L'); + var isMDate = (ax.type === 'date') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'M'); - var tickVals; - function generateTicks() { - var xPrevious = null; - var maxTicks = Math.max(1000, ax._length || 0); - tickVals = []; - for(var x = ax._tmin; - (axrev) ? (x >= endTick) : (x <= endTick); - x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) { - // prevent infinite loops - no more than one tick per pixel, - // and make sure each value is different from the previous - if(tickVals.length > maxTicks || x === xPrevious) break; - xPrevious = x; - - var minor = false; - if(isDLog && (x !== (x | 0))) { - minor = true; - } - - tickVals.push({ - minor: minor, - value: x - }); - } - } - - generateTicks(); - + var tickformat = axes.getTickFormat(ax); var isPeriod = ax.ticklabelmode === 'period'; - var addedPreTick0Label = false; - if(isPeriod && tickVals[0]) { - // add one label to show pre tick0 period - tickVals.unshift({ - minor: false, - value: axes.tickIncrement(tickVals[0].value, ax.dtick, !axrev, ax.caldendar) - }); - addedPreTick0Label = true; - } - - if(ax.rangebreaks) { - // replace ticks inside breaks that would get a tick - // and reduce ticks - var len = tickVals.length; - if(len) { - var tf = 0; - if(ax.tickmode === 'auto') { - tf = - (ax._id.charAt(0) === 'y' ? 2 : 6) * - (ax.tickfont ? ax.tickfont.size : 12); - } - - var newTickVals = []; - var prevPos; - - var dir = axrev ? 1 : -1; - var first = axrev ? 0 : len - 1; - var last = axrev ? len - 1 : 0; - for(var q = first; dir * q <= dir * last; q += dir) { - var tickVal = tickVals[q]; - if(ax.maskBreaks(tickVal.value) === BADNUM) { - tickVal.value = moveOutsideBreak(tickVal.value, ax); - - if(ax._rl && ( - ax._rl[0] === tickVal.value || - ax._rl[1] === tickVal.value - )) continue; - } - - var pos = ax.c2p(tickVal.value); - - if(pos === prevPos) { - if(newTickVals[newTickVals.length - 1].value < tickVal.value) { - newTickVals[newTickVals.length - 1] = tickVal; - } - } else if(prevPos === undefined || Math.abs(pos - prevPos) > tf) { - prevPos = pos; - newTickVals.push(tickVal); - } - } - tickVals = newTickVals.reverse(); - } - } - - // If same angle over a full circle, the last tick vals is a duplicate. - // TODO must do something similar for angular date axes. - if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) { - tickVals.pop(); - } - - // save the last tick as well as first, so we can - // show the exponent only on the last one - ax._tmax = (tickVals[tickVals.length - 1] || {}).value; - - // for showing the rest of a date when the main tick label is only the - // latter part: ax._prevDateHead holds what we showed most recently. - // Start with it cleared and mark that we're in calcTicks (ie calculating a - // whole string of these so we should care what the previous date head was!) - ax._prevDateHead = ''; - ax._inCalcTicks = true; - - var minRange = Math.min(rng[0], rng[1]); - var maxRange = Math.max(rng[0], rng[1]); - var definedDelta; - var tickformat = axes.getTickFormat(ax); if(isPeriod && tickformat) { + var noDtick = ax._dtickInit !== ax.dtick; if( !(/%[fLQsSMX]/.test(tickformat)) // %f: microseconds as a decimal number [000000, 999999] @@ -721,11 +631,15 @@ axes.calcTicks = function calcTicks(ax, opts) { /%[HI]/.test(tickformat) // %H: hour (24-hour clock) as a decimal number [00,23] // %I: hour (12-hour clock) as a decimal number [01,12] - ) definedDelta = ONEHOUR; - else if( + ) { + definedDelta = ONEHOUR; + if(noDtick && !isMDate && ax.dtick < ONEHOUR) ax.dtick = ONEHOUR; + } else if( /%p/.test(tickformat) // %p: either AM or PM - ) definedDelta = HALFDAY; - else if( + ) { + definedDelta = HALFDAY; + if(noDtick && !isMDate && ax.dtick < HALFDAY) ax.dtick = HALFDAY; + } else if( /%[Aadejuwx]/.test(tickformat) // %A: full weekday name // %a: abbreviated weekday name @@ -735,60 +649,101 @@ axes.calcTicks = function calcTicks(ax, opts) { // %u: Monday-based (ISO 8601) weekday as a decimal number [1,7] // %w: Sunday-based weekday as a decimal number [0,6] // %x: the locale’s date, such as %-m/%-d/%Y - ) definedDelta = ONEDAY; - else if( + ) { + definedDelta = ONEDAY; + if(noDtick && !isMDate && ax.dtick < ONEDAY) ax.dtick = ONEDAY; + } else if( /%[UVW]/.test(tickformat) // %U: Sunday-based week of the year as a decimal number [00,53] // %V: ISO 8601 week of the year as a decimal number [01, 53] // %W: Monday-based week of the year as a decimal number [00,53] - ) definedDelta = ONEWEEK; - else if( + ) { + definedDelta = ONEWEEK; + if(noDtick && !isMDate && ax.dtick < ONEWEEK) ax.dtick = ONEWEEK; + } else if( /%[Bbm]/.test(tickformat) // %B: full month name // %b: abbreviated month name // %m: month as a decimal number [01,12] - ) definedDelta = ONEAVGMONTH; - else if( + ) { + definedDelta = ONEAVGMONTH; + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 1 : ax.dtick < ONEMINMONTH) + ) ax.dtick = 'M1'; + } else if( /%[q]/.test(tickformat) // %q: quarter of the year as a decimal number [1,4] - ) definedDelta = ONEAVGQUARTER; - else if( + ) { + definedDelta = ONEAVGQUARTER; + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 3 : ax.dtick < ONEMINQUARTER) + ) ax.dtick = 'M3'; + } else if( /%[Yy]/.test(tickformat) // %Y: year with century as a decimal number, such as 1999 // %y: year without century as a decimal number [00,99] - ) definedDelta = ONEAVGYEAR; + ) { + definedDelta = ONEAVGYEAR; + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 12 : ax.dtick < ONEMINYEAR) + ) ax.dtick = 'M12'; + } } } - var ticksOut = []; - var i; - var prevText; - for(i = 0; i < tickVals.length; i++) { - var _minor = tickVals[i].minor; - var _value = tickVals[i].value; + var maxTicks = Math.max(1000, ax._length || 0); + var tickVals = []; + var xPrevious = null; + var x = ax._tmin; - var t = axes.tickText( - ax, - _value, - false, // hover - _minor // noSuffixPrefix - ); - - if(isPeriod && prevText === t.text) continue; - prevText = t.text; + if(ax.rangebreaks && ax._tick0Init !== ax.tick0) { + // adjust tick0 + x = moveOutsideBreak(x, ax); + if(!axrev) { + x = axes.tickIncrement(x, ax.dtick, !axrev, ax.calendar); + } + } - ticksOut.push(t); + if(isPeriod) { + // add one item to label period before tick0 + x = axes.tickIncrement(x, ax.dtick, !axrev, ax.calendar); } - if(isPeriod && addedPreTick0Label) { - var removedPreTick0Label = false; + for(; + (axrev) ? (x >= endTick) : (x <= endTick); + x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar) + ) { + if(ax.rangebreaks) { + if(!axrev) { + if(x < startTick) continue; + if(ax.maskBreaks(x) === BADNUM && moveOutsideBreak(x, ax) >= maxRange) break; + } + } + + // prevent infinite loops - no more than one tick per pixel, + // and make sure each value is different from the previous + if(tickVals.length > maxTicks || x === xPrevious) break; + xPrevious = x; + + var minor = false; + if(isDLog && (x !== (x | 0))) { + minor = true; + } + + tickVals.push({ + minor: minor, + value: x + }); + } - for(i = 0; i < ticksOut.length; i++) { - var v = ticksOut[i].x; + var i; + if(isPeriod) { + for(i = 0; i < tickVals.length; i++) { + var v = tickVals[i].value; var a = i; var b = i + 1; - if(i < ticksOut.length - 1) { + if(i < tickVals.length - 1) { a = i; b = i + 1; } else if(i > 0) { @@ -799,8 +754,8 @@ axes.calcTicks = function calcTicks(ax, opts) { b = i; } - var A = ticksOut[a].x; - var B = ticksOut[b].x; + var A = tickVals[a].value; + var B = tickVals[b].value; var actualDelta = Math.abs(B - A); var delta = definedDelta || actualDelta; var periodLength = 0; @@ -833,48 +788,112 @@ axes.calcTicks = function calcTicks(ax, opts) { periodLength = ONEHOUR; } - if(periodLength && ax.rangebreaks) { - var nFirstHalf = 0; - var nSecondHalf = 0; - var nAll = 2 * 3 * 7; // number of samples + var inBetween; + if(periodLength >= actualDelta) { + // ensure new label positions remain between ticks + periodLength = actualDelta; + inBetween = true; + } + + var endPeriod = v + periodLength; + if(ax.rangebreaks && periodLength > 0) { + var nAll = 84; // highly divisible 7 * 12 + var n = 0; for(var c = 0; c < nAll; c++) { - var r = c / nAll; - if(ax.maskBreaks(A * (1 - r) + B * r) !== BADNUM) { - if(r < 0.5) { - nFirstHalf++; - } else { - nSecondHalf++; - } - } + var r = (c + 0.5) / nAll; + if(ax.maskBreaks(v * (1 - r) + r * endPeriod) !== BADNUM) n++; } + periodLength *= n / nAll; - if(nSecondHalf) { - periodLength *= (nFirstHalf + nSecondHalf) / nAll; + if(!periodLength) { + tickVals[i].drop = true; } + + if(inBetween && actualDelta > ONEWEEK) periodLength = actualDelta; // center monthly & longer periods } - if(periodLength <= actualDelta) { // i.e. to ensure new label positions remain between ticks - v += periodLength / 2; + if( + periodLength > 0 || // not instant + i === 0 // taking care first tick added + ) { + tickVals[i].periodX = v + periodLength / 2; + } + } + } + + if(ax.rangebreaks) { + var flip = ax._id.charAt(0) === 'y'; + + var fontSize = 1; // one pixel minimum + if(ax.tickmode === 'auto') { + fontSize = ax.tickfont ? ax.tickfont.size : 12; + } + + var prevL = NaN; + for(i = tickVals.length - 1; i > -1; i--) { + if(tickVals[i].drop) { + tickVals.splice(i, 1); + continue; } - ticksOut[i].periodX = v; + tickVals[i].value = moveOutsideBreak(tickVals[i].value, ax); - if(v > maxRange || v < minRange) { // hide label if outside the range - ticksOut[i].text = ' '; // don't use an empty string here which can confuse automargin (issue 5132) - removedPreTick0Label = true; + // avoid overlaps + var l = ax.c2p(tickVals[i].value); + if(flip ? + (prevL > l - fontSize) : + (prevL < l + fontSize) + ) { // ensure one pixel minimum + tickVals.splice(axrev ? i + 1 : i, 1); + } else { + prevL = l; } } + } - if(removedPreTick0Label) { - for(i = 0; i < ticksOut.length; i++) { - if(ticksOut[i].periodX <= maxRange && ticksOut[i].periodX >= minRange) { - // redo first visible tick - ax._prevDateHead = ''; - ticksOut[i].text = axes.tickText(ax, ticksOut[i].x).text; - break; - } + // If same angle over a full circle, the last tick vals is a duplicate. + // TODO must do something similar for angular date axes. + if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) { + tickVals.pop(); + } + + // save the last tick as well as first, so we can + // show the exponent only on the last one + ax._tmax = (tickVals[tickVals.length - 1] || {}).value; + + // for showing the rest of a date when the main tick label is only the + // latter part: ax._prevDateHead holds what we showed most recently. + // Start with it cleared and mark that we're in calcTicks (ie calculating a + // whole string of these so we should care what the previous date head was!) + ax._prevDateHead = ''; + ax._inCalcTicks = true; + + var ticksOut = []; + var t, p; + for(i = 0; i < tickVals.length; i++) { + var _minor = tickVals[i].minor; + var _value = tickVals[i].value; + + t = axes.tickText( + ax, + _value, + false, // hover + _minor // noSuffixPrefix + ); + + p = tickVals[i].periodX; + if(p !== undefined) { + t.periodX = p; + if(p > maxRange || p < minRange) { // hide label if outside the range + if(p > maxRange) t.periodX = maxRange; + if(p < minRange) t.periodX = minRange; + + t.text = ' '; // don't use an empty string here which can confuse automargin (issue 5132) + ax._prevDateHead = ''; } } + + ticksOut.push(t); } ax._inCalcTicks = false; @@ -968,6 +987,7 @@ axes.autoTicks = function(ax, roughDTick) { if(ax.type === 'date') { ax.tick0 = Lib.dateTick0(ax.calendar, 0); + // the criteria below are all based on the rough spacing we calculate // being > half of the final unit - so precalculate twice the rough val var roughX2 = 2 * roughDTick; @@ -2411,8 +2431,8 @@ axes.makeTransPeriodFn = function(ax) { var axLetter = ax._id.charAt(0); var offset = ax._offset; return axLetter === 'x' ? - function(d) { return 'translate(' + (offset + ax.l2p(d.periodX)) + ',0)'; } : - function(d) { return 'translate(0,' + (offset + ax.l2p(d.periodX)) + ')'; }; + function(d) { return 'translate(' + (offset + ax.l2p(d.periodX !== undefined ? d.periodX : d.x)) + ',0)'; } : + function(d) { return 'translate(0,' + (offset + ax.l2p(d.periodX !== undefined ? d.periodX : d.x)) + ')'; }; }; /** diff --git a/test/image/baselines/axes_breaks-candlestick2.png b/test/image/baselines/axes_breaks-candlestick2.png index e93fee533ae..326bf1dd071 100644 Binary files a/test/image/baselines/axes_breaks-candlestick2.png and b/test/image/baselines/axes_breaks-candlestick2.png differ diff --git a/test/image/baselines/axes_breaks-dtick_auto.png b/test/image/baselines/axes_breaks-dtick_auto.png index 18808479908..523b13a3170 100644 Binary files a/test/image/baselines/axes_breaks-dtick_auto.png and b/test/image/baselines/axes_breaks-dtick_auto.png differ diff --git a/test/image/baselines/axes_breaks-finance.png b/test/image/baselines/axes_breaks-finance.png index dffbeceebe9..d50156127af 100644 Binary files a/test/image/baselines/axes_breaks-finance.png and b/test/image/baselines/axes_breaks-finance.png differ diff --git a/test/image/baselines/axes_breaks-night_autorange-reversed.png b/test/image/baselines/axes_breaks-night_autorange-reversed.png index 464e78edb37..37fe6c40862 100644 Binary files a/test/image/baselines/axes_breaks-night_autorange-reversed.png and b/test/image/baselines/axes_breaks-night_autorange-reversed.png differ diff --git a/test/image/baselines/axes_breaks-overlap.png b/test/image/baselines/axes_breaks-overlap.png index eed0f683fd7..3cc73f6915b 100644 Binary files a/test/image/baselines/axes_breaks-overlap.png and b/test/image/baselines/axes_breaks-overlap.png differ diff --git a/test/image/baselines/axes_breaks-rangeslider.png b/test/image/baselines/axes_breaks-rangeslider.png index 10c47a94354..73600339ce6 100644 Binary files a/test/image/baselines/axes_breaks-rangeslider.png and b/test/image/baselines/axes_breaks-rangeslider.png differ diff --git a/test/image/baselines/axes_breaks-reversed-without-pattern.png b/test/image/baselines/axes_breaks-reversed-without-pattern.png index 497b91fd460..29fec4b988a 100644 Binary files a/test/image/baselines/axes_breaks-reversed-without-pattern.png and b/test/image/baselines/axes_breaks-reversed-without-pattern.png differ diff --git a/test/image/baselines/axes_breaks-round-weekdays.png b/test/image/baselines/axes_breaks-round-weekdays.png index 9f20ece5dfa..f4288baf687 100644 Binary files a/test/image/baselines/axes_breaks-round-weekdays.png and b/test/image/baselines/axes_breaks-round-weekdays.png differ diff --git a/test/image/baselines/axes_breaks-values.png b/test/image/baselines/axes_breaks-values.png index b3c9e48c6d8..c7c47d0e295 100644 Binary files a/test/image/baselines/axes_breaks-values.png and b/test/image/baselines/axes_breaks-values.png differ diff --git a/test/image/baselines/axes_breaks-weekends-weeknights.png b/test/image/baselines/axes_breaks-weekends-weeknights.png index 427913aa1f4..530bc27375f 100644 Binary files a/test/image/baselines/axes_breaks-weekends-weeknights.png and b/test/image/baselines/axes_breaks-weekends-weeknights.png differ diff --git a/test/image/baselines/axes_breaks-weekends_autorange-reversed.png b/test/image/baselines/axes_breaks-weekends_autorange-reversed.png index 33a32e12aa6..2892d889414 100644 Binary files a/test/image/baselines/axes_breaks-weekends_autorange-reversed.png and b/test/image/baselines/axes_breaks-weekends_autorange-reversed.png differ diff --git a/test/image/baselines/axes_breaks.png b/test/image/baselines/axes_breaks.png index 722307c1624..5f34b4c8e68 100644 Binary files a/test/image/baselines/axes_breaks.png and b/test/image/baselines/axes_breaks.png differ diff --git a/test/image/baselines/date_axes_period_breaks_automargin.png b/test/image/baselines/date_axes_period_breaks_automargin.png index 4ccf3e75f3f..2b7679051da 100644 Binary files a/test/image/baselines/date_axes_period_breaks_automargin.png and b/test/image/baselines/date_axes_period_breaks_automargin.png differ diff --git a/test/image/baselines/period_positioning3.png b/test/image/baselines/period_positioning3.png index 967ddbc0824..82f414d893b 100644 Binary files a/test/image/baselines/period_positioning3.png and b/test/image/baselines/period_positioning3.png differ diff --git a/test/image/baselines/period_positioning4.png b/test/image/baselines/period_positioning4.png index 25895a1608e..f885f15c384 100644 Binary files a/test/image/baselines/period_positioning4.png and b/test/image/baselines/period_positioning4.png differ diff --git a/test/image/mocks/axes_breaks-contour1d.json b/test/image/mocks/axes_breaks-contour1d.json index dbc9c4f052b..c218b2d504c 100644 --- a/test/image/mocks/axes_breaks-contour1d.json +++ b/test/image/mocks/axes_breaks-contour1d.json @@ -856,6 +856,7 @@ "text": "1D-z-array contour with rangebreaks" }, "xaxis": { + "dtick": 1800000, "rangebreaks": [ { "pattern": "hour", diff --git a/test/image/mocks/axes_breaks-contour2d.json b/test/image/mocks/axes_breaks-contour2d.json index 94823f364c4..d4dcf9c7661 100644 --- a/test/image/mocks/axes_breaks-contour2d.json +++ b/test/image/mocks/axes_breaks-contour2d.json @@ -128,6 +128,7 @@ "text": "2D-z-array contour with rangebreaks" }, "xaxis": { + "dtick": 1800000, "rangebreaks": [ { "pattern": "hour", diff --git a/test/image/mocks/axes_breaks-heatmap1d.json b/test/image/mocks/axes_breaks-heatmap1d.json index d1bb6cc6a9a..52f8d0f8675 100644 --- a/test/image/mocks/axes_breaks-heatmap1d.json +++ b/test/image/mocks/axes_breaks-heatmap1d.json @@ -856,6 +856,7 @@ "text": "1D-z-array heatmap with rangebreaks" }, "xaxis": { + "dtick": 1800000, "rangebreaks": [ { "pattern": "hour", diff --git a/test/image/mocks/axes_breaks-heatmap2d.json b/test/image/mocks/axes_breaks-heatmap2d.json index 30fcad62abc..1e49b93f6cb 100644 --- a/test/image/mocks/axes_breaks-heatmap2d.json +++ b/test/image/mocks/axes_breaks-heatmap2d.json @@ -128,6 +128,7 @@ "text": "2D-z-array heatmap with rangebreaks" }, "xaxis": { + "dtick": 1800000, "rangebreaks": [ { "pattern": "hour", diff --git a/test/image/mocks/axes_breaks-histogram2d.json b/test/image/mocks/axes_breaks-histogram2d.json index c9cf100821d..846ba0d5848 100644 --- a/test/image/mocks/axes_breaks-histogram2d.json +++ b/test/image/mocks/axes_breaks-histogram2d.json @@ -482,6 +482,7 @@ "2020-01-02 16:55", "2020-01-03 09:55" ], + "dtick": 1800000, "rangebreaks": [{ "pattern": "hour", "bounds": [18, 9] diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index ad6d2509bc5..bd1c5c4a1cd 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -5039,12 +5039,14 @@ describe('Test axes', function() { afterEach(destroyGraphDiv); - function _assert(msg, exp) { + function _assert(msg, exp, autorange) { var fullLayout = gd._fullLayout; var xa = fullLayout.xaxis; - expect(xa._vals.map(function(d) { return d.x; })) - .withContext(msg).toEqual(exp.tickVals); + var vals = xa._vals.map(function(d) { return Lib.ms2DateTime(d.x); }); + if(autorange === 'reversed') vals.reverse(); + + expect(vals).withContext(msg).toEqual(exp.tickVals); } it('should not include requested ticks that fall within rangebreaks', function(done) { @@ -5066,7 +5068,7 @@ describe('Test axes', function() { }) .then(function() { _assert('base', { - tickVals: [0, 50, 100, 150, 200] + tickVals: ['1970-01-01', '1970-01-01 00:00:00.05', '1970-01-01 00:00:00.1', '1970-01-01 00:00:00.15', '1970-01-01 00:00:00.2'] }); }) .then(function() { @@ -5086,7 +5088,7 @@ describe('Test axes', function() { }) .then(function() { _assert('with two rangebreaks', { - tickVals: [0, 5, 90, 95, 190, 195, 200] + tickVals: ['1970-01-01', '1970-01-01 00:00:00.089', '1970-01-01 00:00:00.1', '1970-01-01 00:00:00.189', '1970-01-01 00:00:00.2'] }); }) .catch(failTest) @@ -5116,7 +5118,7 @@ describe('Test axes', function() { }], layout: { width: 1600, - height: 1600 + height: 400 } }; @@ -5147,8 +5149,8 @@ describe('Test axes', function() { '1970-01-12 08:00', '1970-01-12 12:00', '1970-01-12 16:00', '1970-01-13 08:00', '1970-01-13 12:00', '1970-01-13 16:00', '1970-01-14 08:00', '1970-01-14 12:00', '1970-01-14 16:00' - ].map(Lib.dateTime2ms) - }); + ] + }, autorange); }) .then(function() { fig.layout.xaxis = { @@ -5178,8 +5180,8 @@ describe('Test axes', function() { '1970-01-12 08:00', '1970-01-12 11:00', '1970-01-12 14:00', '1970-01-13 08:00', '1970-01-13 11:00', '1970-01-13 14:00', '1970-01-14 08:00', '1970-01-14 11:00', '1970-01-14 14:00' - ].map(Lib.dateTime2ms) - }); + ] + }, autorange); }) .then(function() { fig.layout.xaxis = { @@ -5209,8 +5211,8 @@ describe('Test axes', function() { '1970-01-12 08:00', '1970-01-12 10:00', '1970-01-12 12:00', '1970-01-12 14:00', '1970-01-12 16:00', '1970-01-13 08:00', '1970-01-13 10:00', '1970-01-13 12:00', '1970-01-13 14:00', '1970-01-13 16:00', '1970-01-14 08:00', '1970-01-14 10:00', '1970-01-14 12:00', '1970-01-14 14:00', '1970-01-14 16:00' - ].map(Lib.dateTime2ms) - }); + ] + }, autorange); }) .catch(failTest) .then(done); @@ -5264,11 +5266,15 @@ describe('Test axes', function() { function _assert(msg, expPositions, expLabels) { var ax = gd._fullLayout.xaxis; - var positions = ax._vals.map(function(d) { return ax.c2d(d.periodX); }); - expect(positions).withContext(msg).toEqual(expPositions); - + var positions = ax._vals.map(function(d) { return ax.c2d(d.periodX !== undefined ? d.periodX : d.x); }); var labels = ax._vals.map(function(d) { return d.text; }); - expect(labels).withContext(msg).toEqual(expLabels); + + for(var i = 0; i < labels.length; i++) { + expect(labels[i]).withContext(msg).toBe(expLabels[i]); + if(labels[i] !== ' ') { + expect(positions[i]).withContext(msg).toBe(expPositions[i]); + } + } } ['%Y', '%y'].forEach(function(formatter, i) { @@ -5473,7 +5479,7 @@ describe('Test axes', function() { }); ['%U', '%V', '%W'].forEach(function(formatter, i) { - it('should move weekly labels by one day (i.e. to help center the labels) when *day of week* rangebreak is present', function(done) { + it('should position weekly labels in the middle when *day of week* rangebreak is present', function(done) { Plotly.newPlot(gd, { data: [{ hovertemplate: hovertemplate, @@ -5522,13 +5528,13 @@ describe('Test axes', function() { }) .then(function() { _assert('', [ - ['2019-12-31 04:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'], - ['2020-01-01 12:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'], - ['2020-01-01 12:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'] + ['2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'], + ['2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'], + ['2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'] ][i], [ - [' ', 'Jan-W01', 'Jan-W02', 'Jan-W03', 'Jan-W04'], - ['Dec-W01', 'Jan-W02', 'Jan-W03', 'Jan-W04', 'Jan-W05'], - ['Dec-W52', 'Jan-W01', 'Jan-W02', 'Jan-W03', 'Jan-W04'] + ['Jan-W01', 'Jan-W02', 'Jan-W03', 'Jan-W04'], + ['Jan-W02', 'Jan-W03', 'Jan-W04', 'Jan-W05'], + ['Jan-W01', 'Jan-W02', 'Jan-W03', 'Jan-W04'] ][i]); }) .catch(failTest) @@ -5616,7 +5622,6 @@ describe('Test axes', function() { }); }); - [ { formatter: '%H', @@ -5630,28 +5635,28 @@ describe('Test axes', function() { }, { formatter: '%p', - positions: ['2019-12-31 21:00', '2020-01-01 06:00', '2020-01-01 18:00', '2020-01-02 06:00'], + positions: ['2019-12-31 18:00', '2020-01-01 06:00', '2020-01-01 18:00', '2020-01-02 06:00'], labels: [' ', 'Wed-AM', 'Wed-PM', ' '] }, { formatter: '%M', - positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'], - labels: [' ', 'Wed-00', ' '] + positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'], + labels: [' ', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Thu-00'] }, { formatter: '%S', - positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'], - labels: [' ', 'Wed-00', ' '] + positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'], + labels: [' ', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Thu-00'] }, { formatter: '%L', - positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'], - labels: [' ', 'Wed-000', ' '] + positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'], + labels: [' ', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Thu-000'] }, { formatter: '%f', - positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'], - labels: [' ', 'Wed-0', ' '] + positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'], + labels: [' ', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Thu-0'] } ].forEach(function(t) { it('should respect time tickformat that includes ' + t.formatter, function(done) { @@ -5921,48 +5926,48 @@ describe('Test axes', function() { [ { range: ['2020-12-14 08:00', '2022-12-14 08:00'], - positions: ['2020-12-06 10:26:47.1429', '2021-03-07 09:50:21.4286', '2021-06-06 16:26:47.1429', '2021-09-06 16:26:47.1429', '2021-12-07 09:50:21.4286', '2022-03-06 16:26:47.1429', '2022-06-06 16:26:47.1429', '2022-09-07 01:08:34.2857', '2022-12-07 01:08:34.2857'], - labels: [' ', 'Mar 2021', 'Jun 2021', 'Sep 2021', 'Dec 2021', 'Mar 2022', 'Jun 2022', 'Sep 2022', 'Dec 2022'] + positions: ['2021-01-06 07:45', '2021-04-06 07:45', '2021-07-06 07:45', '2021-10-06 07:45', '2022-01-06 07:45', '2022-04-06 07:45', '2022-07-06 07:45', '2022-10-06 07:45'], + labels: ['Jan 2021', 'Apr 2021', 'Jul 2021', 'Oct 2021', 'Jan 2022', 'Apr 2022', 'Jul 2022', 'Oct 2022'] }, { range: ['2020-12-14 08:00', '2021-08-14 08:00'], - positions: ['2020-12-06 04:17:08.5714', '2020-12-27 22:00', '2021-01-24 22:00', '2021-02-21 22:00', '2021-03-21 22:00', '2021-04-18 22:00', '2021-05-16 22:00', '2021-06-13 22:00', '2021-07-11 22:00', '2021-08-08 22:00'], - labels: [' ', 'Dec 21
2020', 'Jan 18
2021', 'Feb 15', 'Mar 15', 'Apr 12', 'May 10', 'Jun 7', 'Jul 5', 'Aug 2'] + positions: ['2021-01-16 18:00', '2021-02-15 06:00', '2021-03-16 18:00', '2021-04-16 06:00', '2021-05-16 18:00', '2021-06-16 06:00', '2021-07-16 18:00', '2021-08-16 18:00'], + labels: ['Jan 2021', 'Feb 2021', 'Mar 2021', 'Apr 2021', 'May 2021', 'Jun 2021', 'Jul 2021', ' '] }, { range: ['2020-12-14 08:00', '2021-04-14 08:00'], - positions: ['2020-12-13 03:42:51.4286', '2020-12-21 11:42:51.4286', '2021-01-04 11:42:51.4286', '2021-01-18 11:42:51.4286', '2021-02-01 11:42:51.4286', '2021-02-15 11:42:51.4286', '2021-03-01 11:42:51.4286', '2021-03-15 11:42:51.4286', '2021-03-29 11:42:51.4286', '2021-04-12 11:42:51.4286'], - labels: [' ', 'Dec 21
2020', 'Jan 4
2021', 'Jan 18', 'Feb 1', 'Feb 15', 'Mar 1', 'Mar 15', 'Mar 29', 'Apr 12'] + positions: ['2020-12-21 12:00', '2021-01-04 12:00', '2021-01-18 12:00', '2021-02-01 12:00', '2021-02-15 12:00', '2021-03-01 12:00', '2021-03-15 12:00', '2021-03-29 12:00', '2021-04-12 12:00'], + labels: ['Dec 21
2020', 'Jan 4
2021', 'Jan 18', 'Feb 1', 'Feb 15', 'Mar 1', 'Mar 15', 'Mar 29', 'Apr 12'] }, { range: ['2020-12-14 08:00', '2021-02-14 08:00'], - positions: ['2020-12-13 03:42:51.4286', '2020-12-21 10:17:08.5714', '2020-12-28 10:17:08.5714', '2021-01-04 10:17:08.5714', '2021-01-11 10:17:08.5714', '2021-01-18 10:17:08.5714', '2021-01-25 10:17:08.5714', '2021-02-01 10:17:08.5714', '2021-02-08 11:42:51.4286', '2021-02-14 13:42:51.4286'], - labels: [' ', 'Dec 21
2020', 'Dec 28', 'Jan 4
2021', 'Jan 11', 'Jan 18', 'Jan 25', 'Feb 1', 'Feb 8', ' '] + positions: ['2020-12-21 12:00', '2020-12-28 12:00', '2021-01-04 12:00', '2021-01-11 12:00', '2021-01-18 12:00', '2021-01-25 12:00', '2021-02-01 12:00', '2021-02-08 12:00'], + labels: ['Dec 21
2020', 'Dec 28', 'Jan 4
2021', 'Jan 11', 'Jan 18', 'Jan 25', 'Feb 1', 'Feb 8'] }, { range: ['2020-12-14 08:00', '2021-01-14 08:00'], - positions: ['2020-12-14 05:08:34.2857', '2020-12-16 12:17:08.5714', '2020-12-18 09:08:34.2857', '2020-12-22 12:17:08.5714', '2020-12-24 18:00', '2020-12-28 12:17:08.5714', '2020-12-30 12:17:08.5714', '2021-01-01 09:08:34.2857', '2021-01-05 12:17:08.5714', '2021-01-07 18:00', '2021-01-11 12:17:08.5714', '2021-01-13 12:17:08.5714'], - labels: [' ', 'Dec 16
2020', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', 'Jan 1
2021', 'Jan 5', 'Jan 7', 'Jan 11', 'Jan 13'] + positions: ['2020-12-21 12:00', '2020-12-28 12:00', '2021-01-04 12:00', '2021-01-11 12:00'], + labels: ['Dec 21
2020', 'Dec 28', 'Jan 4
2021', 'Jan 11'] }, { range: ['2020-12-14 08:00', '2021-01-01 08:00'], - positions: ['2020-12-14 05:08:34.2857', '2020-12-16 12:17:08.5714', '2020-12-18 09:08:34.2857', '2020-12-22 12:17:08.5714', '2020-12-24 18:00', '2020-12-28 12:17:08.5714', '2020-12-30 12:17:08.5714', '2021-01-01 12:17:08.5714'], - labels: [' ', 'Dec 16
2020', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', ' '] + positions: ['2020-12-16 12:00', '2020-12-18 12:00', '2020-12-22 12:00', '2020-12-24 12:00', '2020-12-28 12:00', '2020-12-30 12:00', '2021-01-01 12:00'], + labels: ['Dec 16
2020', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', ' '] }, { range: ['2020-12-14 08:00', '2020-12-22 08:00'], - positions: ['2020-12-14 04:51:25.7143', '2020-12-15 18:00', '2020-12-16 18:00', '2020-12-17 18:00', '2020-12-18 18:00', '2020-12-21 18:00', '2020-12-22 18:00'], - labels: [' ', '06:00
Dec 15, 2020', '06:00
Dec 16, 2020', '06:00
Dec 17, 2020', '06:00
Dec 18, 2020', '06:00
Dec 21, 2020', ' '] + positions: ['2020-12-15 12:00', '2020-12-16 12:00', '2020-12-17 12:00', '2020-12-18 12:00', '2020-12-21 12:00', '2020-12-22 12:00'], + labels: ['Dec 15
2020', 'Dec 16', 'Dec 17', 'Dec 18', 'Dec 21', ' '] }, { range: ['2020-12-14 08:00', '2020-12-18 08:00'], - positions: ['2020-12-14 06:00', '2020-12-14 12:00', '2020-12-15 06:00', '2020-12-15 12:00', '2020-12-16 06:00', '2020-12-16 12:00', '2020-12-17 06:00', '2020-12-17 12:00', '2020-12-18 06:00'], - labels: [' ', '12:00
Dec 14, 2020', '06:00
Dec 15, 2020', '12:00', '06:00
Dec 16, 2020', '12:00', '06:00
Dec 17, 2020', '12:00', '06:00
Dec 18, 2020'] + positions: ['2020-12-14 12:00', '2020-12-15 06:00', '2020-12-15 12:00', '2020-12-16 06:00', '2020-12-16 12:00', '2020-12-17 06:00', '2020-12-17 12:00', '2020-12-18 06:00'], + labels: ['12:00
Dec 14, 2020', '06:00
Dec 15, 2020', '12:00', '06:00
Dec 16, 2020', '12:00', '06:00
Dec 17, 2020', '12:00', '06:00
Dec 18, 2020'] }, { range: ['2020-12-14 08:00', '2020-12-16 08:00'], - positions: ['2020-12-14 06:00', '2020-12-14 09:00', '2020-12-14 12:00', '2020-12-14 15:00', '2020-12-15 06:00', '2020-12-15 09:00', '2020-12-15 12:00', '2020-12-15 15:00', '2020-12-16 06:00'], - labels: [' ', '09:00
Dec 14, 2020', '12:00', '15:00', '06:00
Dec 15, 2020', '09:00', '12:00', '15:00', '06:00
Dec 16, 2020'] + positions: ['2020-12-14 12:00', '2020-12-15 06:00', '2020-12-15 12:00', '2020-12-16 06:00'], + labels: ['12:00
Dec 14, 2020', '06:00
Dec 15, 2020', '12:00', '06:00
Dec 16, 2020'] } ].forEach(function(t) { it('should position auto labels with rangebreaks | range:' + t.range, function(done) {