diff --git a/src/lib/dates.js b/src/lib/dates.js index 4fc8387d2cb..7664c00e0e9 100644 --- a/src/lib/dates.js +++ b/src/lib/dates.js @@ -45,9 +45,23 @@ function isWorldCalendar(calendar) { /* * dateTick0: get the canonical tick for this calendar * + * integer weekdays : Saturday: 0, Sunday: 1, Monday: 2, etc. + */ +exports.dateTick0 = function(calendar, dayOfWeek) { + var tick0 = _dateTick0(calendar, !!dayOfWeek); + if(dayOfWeek < 2) return tick0; + + var v = exports.dateTime2ms(tick0, calendar); + v += ONEDAY * (dayOfWeek - 1); // shift Sunday to Monday, etc. + return exports.ms2DateTime(v, 0, calendar); +}; + +/* + * _dateTick0: get the canonical tick for this calendar + * * bool sunday is for week ticks, shift it to a Sunday. */ -exports.dateTick0 = function(calendar, sunday) { +function _dateTick0(calendar, sunday) { if(isWorldCalendar(calendar)) { return sunday ? Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : @@ -55,7 +69,7 @@ exports.dateTick0 = function(calendar, sunday) { } else { return sunday ? '2000-01-02' : '2000-01-01'; } -}; +} /* * dfltRange: for each calendar, give a valid default range diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index eb6d7a675a2..40a1e1e5e96 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -630,12 +630,14 @@ axes.calcTicks = function calcTicks(ax, opts) { generateTicks(); var isPeriod = ax.ticklabelmode === 'period'; - if(isPeriod) { + 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) { @@ -778,7 +780,7 @@ axes.calcTicks = function calcTicks(ax, opts) { ticksOut.push(t); } - if(isPeriod) { + if(isPeriod && addedPreTick0Label) { var removedPreTick0Label = false; for(i = 0; i < ticksOut.length; i++) { @@ -965,7 +967,7 @@ axes.autoTicks = function(ax, roughDTick) { } if(ax.type === 'date') { - ax.tick0 = Lib.dateTick0(ax.calendar); + 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; @@ -982,14 +984,11 @@ axes.autoTicks = function(ax, roughDTick) { // get week ticks on sunday // this will also move the base tick off 2000-01-01 if dtick is // 2 or 3 days... but that's a weird enough case that we'll ignore it. - ax.tick0 = Lib.dateTick0(ax.calendar, true); - var tickformat = axes.getTickFormat(ax); if(/%[uVW]/.test(tickformat)) { - // replace Sunday with Monday for ISO and Monday-based formats - var len = ax.tick0.length; - var lastD = +ax.tick0[len - 1]; - ax.tick0 = ax.tick0.substring(0, len - 2) + String(lastD + 1); + ax.tick0 = Lib.dateTick0(ax.calendar, 2); // Monday + } else { + ax.tick0 = Lib.dateTick0(ax.calendar, 1); // Sunday } } else if(roughX2 > ONEHOUR) { ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24); diff --git a/src/plots/cartesian/clean_ticks.js b/src/plots/cartesian/clean_ticks.js index 02cdc1de820..6e88e61828b 100644 --- a/src/plots/cartesian/clean_ticks.js +++ b/src/plots/cartesian/clean_ticks.js @@ -10,7 +10,9 @@ var isNumeric = require('fast-isnumeric'); var Lib = require('../../lib'); -var ONEDAY = require('../../constants/numerical').ONEDAY; +var constants = require('../../constants/numerical'); +var ONEDAY = constants.ONEDAY; +var ONEWEEK = constants.ONEWEEK; /** * Return a validated dtick value for this axis @@ -75,7 +77,9 @@ exports.dtick = function(dtick, axType) { */ exports.tick0 = function(tick0, axType, calendar, dtick) { if(axType === 'date') { - return Lib.cleanDate(tick0, Lib.dateTick0(calendar)); + return Lib.cleanDate(tick0, + Lib.dateTick0(calendar, (dtick % ONEWEEK === 0) ? 1 : 0) + ); } if(dtick === 'D1' || dtick === 'D2') { // D1 and D2 modes ignore tick0 entirely diff --git a/src/traces/scatter/period_defaults.js b/src/traces/scatter/period_defaults.js index cb02dc0f936..474784ed365 100644 --- a/src/traces/scatter/period_defaults.js +++ b/src/traces/scatter/period_defaults.js @@ -13,8 +13,10 @@ var numConstants = require('../../constants/numerical'); var ONEWEEK = numConstants.ONEWEEK; function getPeriod0Dflt(period, calendar) { - var n = period / ONEWEEK; - return dateTick0(calendar, Math.round(n) === n); + if(period % ONEWEEK === 0) { + return dateTick0(calendar, 1); // Sunday + } + return dateTick0(calendar, 0); } module.exports = function handlePeriodDefaults(traceIn, traceOut, layout, coerce, opts) { diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 49d8ccb0948..ad6d2509bc5 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -13,8 +13,10 @@ var Cartesian = require('@src/plots/cartesian'); var Axes = require('@src/plots/cartesian/axes'); var Fx = require('@src/components/fx'); var supplyLayoutDefaults = require('@src/plots/cartesian/layout_defaults'); -var BADNUM = require('@src/constants/numerical').BADNUM; -var ONEDAY = require('@src/constants/numerical').ONEDAY; +var numerical = require('@src/constants/numerical'); +var BADNUM = numerical.BADNUM; +var ONEDAY = numerical.ONEDAY; +var ONEWEEK = numerical.ONEWEEK; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -6020,6 +6022,42 @@ describe('Test axes', function() { .then(done); }); }); + + [undefined, '%U', '%V', '%W'].forEach(function(tickformat) { + it('with ' + tickformat + ' tickformat, should default tick0 on a Sunday when dtick is a round number of weeks', function(done) { + var fig = { + data: [ + { + showlegend: false, + type: 'bar', + width: ONEWEEK, + xperiod: ONEWEEK, + x: [ + '2020-09-16', + '2020-09-24', + '2020-09-30' + ], + y: [3, 2, 4] + } + ], + layout: { + xaxis: { + tickformat: tickformat, + dtick: ONEWEEK, + ticklabelmode: 'period', + showgrid: true, + } + } + }; + + Plotly.newPlot(gd, fig) + .then(function() { + expect(gd._fullLayout.xaxis.tick0).toBe('2000-01-02'); + }) + .catch(failTest) + .then(done); + }); + }); }); });