diff --git a/draftlogs/7877_fix.md b/draftlogs/7877_fix.md new file mode 100644 index 00000000000..95dc5f5afa8 --- /dev/null +++ b/draftlogs/7877_fix.md @@ -0,0 +1,2 @@ +- Fix missing last period label when using a negative `ticklabelindex` in a chart where the last major tick is not visible [[#7877](https://github.com/plotly/plotly.js/pull/7877)] +- Fix incorrect period label positioning when using `ticklabelindex` in a chart where not enough minor ticks are visible [[#7877](https://github.com/plotly/plotly.js/pull/7877)] \ No newline at end of file diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 08bfda6f367..0de4eb5dcec 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -690,10 +690,21 @@ axes.prepMinorTicks = function(mockAx, ax, opts) { // put back the original range, to use to find the full set of minor ticks mockAx.range = ax.range; } + if(ax.minor?._tick0Init === undefined) { // ensure identical tick0 mockAx.tick0 = ax.tick0; } + if (ax._useTicklabelIndex) { + // these could also always be done but the additional information on the minor + // axis is just necessary for ticklabelindex. + autoTickRound(mockAx); + if(ax.ticklabelmode === 'period') { + mockAx._definedDelta = definedDeltaForTickformat(axes.getTickFormat(mockAx)); + } + delete mockAx.minor; // prevent self-reference + Lib.extendFlat(ax.minor, mockAx); + } }; function isMultiple(bigger, smaller) { @@ -764,9 +775,66 @@ function nMonths(dtick) { return +(dtick.substring(1)); } -function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta - var definedDelta; +// Maps a tick format string to the period length its labels represent. +// Returns undefined if the format has no (or a sub-second/minute) period unit, +// in which case the period length is derived from the actual tick spacing. +function definedDeltaForTickformat(tickformat) { + if( + !tickformat || + /%[fLQsSMX]/.test(tickformat) + // %f: microseconds as a decimal number [000000, 999999] + // %L: milliseconds as a decimal number [000, 999] + // %Q: milliseconds since UNIX epoch + // %s: seconds since UNIX epoch + // %S: second as a decimal number [00,61] + // %M: minute as a decimal number [00,59] + // %X: the locale’s time, such as %-I:%M:%S %p + ) return undefined; + if( + /%[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] + ) return ONEHOUR; + if( + /%p/.test(tickformat) // %p: either AM or PM + ) return HALFDAY; + if( + /%[Aadejuwx]/.test(tickformat) + // %A: full weekday name + // %a: abbreviated weekday name + // %d: zero-padded day of the month as a decimal number [01,31] + // %e: space-padded day of the month as a decimal number [ 1,31] + // %j: day of the year as a decimal number [001,366] + // %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 + ) return ONEDAY; + 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] + ) return ONEWEEK; + if( + /%[Bbm]/.test(tickformat) + // %B: full month name + // %b: abbreviated month name + // %m: month as a decimal number [01,12] + ) return ONEAVGMONTH; + if( + /%[q]/.test(tickformat) + // %q: quarter of the year as a decimal number [1,4] + ) return ONEAVGQUARTER; + 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] + ) return ONEAVGYEAR; + return undefined; +} + +function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta function mDate() { return !( isNumeric(ax.dtick) || @@ -774,80 +842,29 @@ function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta ); } var isMDate = mDate(); - var tickformat = axes.getTickFormat(ax); - if(tickformat) { + var definedDelta = definedDeltaForTickformat(axes.getTickFormat(ax)); + if(definedDelta !== undefined) { var noDtick = ax._dtickInit !== ax.dtick; - if( - !(/%[fLQsSMX]/.test(tickformat)) - // %f: microseconds as a decimal number [000000, 999999] - // %L: milliseconds as a decimal number [000, 999] - // %Q: milliseconds since UNIX epoch - // %s: seconds since UNIX epoch - // %S: second as a decimal number [00,61] - // %M: minute as a decimal number [00,59] - // %X: the locale’s time, such as %-I:%M:%S %p - ) { - if( - /%[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; - if(noDtick && !isMDate && ax.dtick < ONEHOUR) ax.dtick = ONEHOUR; - } else if( - /%p/.test(tickformat) // %p: either AM or PM - ) { - definedDelta = HALFDAY; - if(noDtick && !isMDate && ax.dtick < HALFDAY) ax.dtick = HALFDAY; - } else if( - /%[Aadejuwx]/.test(tickformat) - // %A: full weekday name - // %a: abbreviated weekday name - // %d: zero-padded day of the month as a decimal number [01,31] - // %e: space-padded day of the month as a decimal number [ 1,31] - // %j: day of the year as a decimal number [001,366] - // %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; - 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; - 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; - 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; - 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; - if(noDtick && ( - isMDate ? nMonths(ax.dtick) < 12 : ax.dtick < ONEMINYEAR) - ) ax.dtick = 'M12'; - } + if(definedDelta === ONEHOUR) { + if(noDtick && !isMDate && ax.dtick < ONEHOUR) ax.dtick = ONEHOUR; + } else if(definedDelta === HALFDAY) { + if(noDtick && !isMDate && ax.dtick < HALFDAY) ax.dtick = HALFDAY; + } else if(definedDelta === ONEDAY) { + if(noDtick && !isMDate && ax.dtick < ONEDAY) ax.dtick = ONEDAY; + } else if(definedDelta === ONEWEEK) { + if(noDtick && !isMDate && ax.dtick < ONEWEEK) ax.dtick = ONEWEEK; + } else if(definedDelta === ONEAVGMONTH) { + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 1 : ax.dtick < ONEMINMONTH) + ) ax.dtick = 'M1'; + } else if(definedDelta === ONEAVGQUARTER) { + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 3 : ax.dtick < ONEMINQUARTER) + ) ax.dtick = 'M3'; + } else if(definedDelta === ONEAVGYEAR) { + if(noDtick && ( + isMDate ? nMonths(ax.dtick) < 12 : ax.dtick < ONEMINYEAR) + ) ax.dtick = 'M12'; } } @@ -860,25 +877,40 @@ function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta ax._definedDelta = definedDelta; } -function positionPeriodTicks(tickVals, ax, definedDelta) { +/** + * Calculates the period label position for each tick in tickVals. + * @param {array} tickVals: the list of ticks for which to position the period labels + * @param {object} ax: the axis of the ticks + * @param {number} definedDelta: the defined distance between two ticks + * @param {array (optional)} periodEndTicks: the optional list of neighboring ticks + * for each tick in tickVals. If not provided, the function will use the next + * tick in tickVals as the neighbor. Useful if tickVals is not evenly spaced. + */ +function positionPeriodTicks(tickVals, ax, definedDelta, periodEndTicks) { for(var i = 0; i < tickVals.length; i++) { var v = tickVals[i].value; - - var a = i; - var b = i + 1; - if(i < tickVals.length - 1) { - a = i; - b = i + 1; - } else if(i > 0) { - a = i - 1; - b = i; + var A, B; + if (periodEndTicks != null) { + A = tickVals[i].value; + B = periodEndTicks[i].value; } else { - a = i; - b = i; + var a = i; + var b = i + 1; + if(i < tickVals.length - 1) { + a = i; + b = i + 1; + } else if(i > 0) { + a = i - 1; + b = i; + } else { + a = i; + b = i; + } + + A = tickVals[a].value; + B = tickVals[b].value; } - var A = tickVals[a].value; - var B = tickVals[b].value; var actualDelta = Math.abs(B - A); var delta = definedDelta || actualDelta; var periodLength = 0; @@ -956,6 +988,7 @@ axes.calcTicks = function calcTicks(ax, opts) { var isReversed = ax.range[0] > ax.range[1]; var ticklabelIndex = (!ax.ticklabelindex || Lib.isArrayOrTypedArray(ax.ticklabelindex)) ? ax.ticklabelindex : [ax.ticklabelindex]; + ax._useTicklabelIndex = ticklabelIndex != null && ticklabelIndex !== 0; var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts); var axrev = (rng[1] < rng[0]); var minRange = Math.min(rng[0], rng[1]); @@ -971,12 +1004,16 @@ axes.calcTicks = function calcTicks(ax, opts) { // all ticks for which labels are drawn which is not necessarily the major ticks when // `ticklabelindex` is set. var allTicklabelVals = []; + // for period label positioning when using `ticklabelindex`: + // for each tick in `allTicklabelVals` holds the neighboring period end tick + var periodEndTicks; var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid); // minor ticks should be calculated if they are visible or if ticklabelindex is set because then // the labels are placed at minor ticks (even if invisible) instead of major ticks. - var calcMinor = hasMinor || ticklabelIndex; - + var calcMinor = hasMinor || ax._useTicklabelIndex; + + var majorDtick = ax.dtick; // calc major first for(var major = 1; major >= (calcMinor ? 0 : 1); major--) { var isMinor = !major; @@ -994,6 +1031,7 @@ axes.calcTicks = function calcTicks(ax, opts) { axes.prepMinorTicks(mockAx, ax, opts); } else { axes.prepTicks(mockAx, opts); + majorDtick = mockAx.dtick; } // now that we've figured out the auto values for formatting @@ -1020,9 +1058,11 @@ axes.calcTicks = function calcTicks(ax, opts) { var exRng = expandRange(rng); var startTick = exRng[0]; var endTick = exRng[1]; + var visibleEndTick = endTick; // for period axis, there will be an additional major tick after that + var dtick = mockAx.dtick; - var numDtick = isNumeric(mockAx.dtick); - var isDLog = (type === 'log') && !(numDtick || mockAx.dtick.charAt(0) === 'L'); + var numDtick = isNumeric(dtick); + var isDLog = (type === 'log') && !(numDtick || dtick.charAt(0) === 'L'); // find the first tick var x0 = axes.tickFirst(mockAx, opts); @@ -1066,8 +1106,6 @@ axes.calcTicks = function calcTicks(ax, opts) { ) / _dTick) - 1; } - var dtick = mockAx.dtick; - if(mockAx.rangebreaks && mockAx._tick0Init !== mockAx.tick0) { // adjust tick0 x = moveOutsideBreak(x, ax); @@ -1076,12 +1114,12 @@ axes.calcTicks = function calcTicks(ax, opts) { } } - if((major || ticklabelIndex) && isPeriod) { - // if major: add one item to label period before tick0 - // if minor: add one item for ticklabelindex positioning. positionPeriodTicks requires - // at least 2 ticks to calculate the period length, so we add a dummy tick, ensuring - // that if a tick is labeled, there are always at least 2 ticks. - x = axes.tickIncrement(x, dtick, !axrev, calendar); + if (isPeriod) { + // add an additional major tick and correspondingly many minor ticks + // before the first major tick to be able to label the period before the first visible ticks. + x = axes.tickIncrement(x, majorDtick, !axrev, calendar); + // same after the last major tick to be able to label the period after the last visible tick. + if (ax._useTicklabelIndex) endTick = axes.tickIncrement(endTick, majorDtick, axrev, calendar); if (major) majorId--; } @@ -1112,6 +1150,11 @@ axes.calcTicks = function calcTicks(ax, opts) { var obj = { value: x }; + // mark ticks that were only added for period label positioning as "noTick" so they aren't drawn. + if (axrev ? (x > startTick || x < visibleEndTick) : (x < startTick || x > visibleEndTick)) { + obj.noTick = true; + } + if(major) { if(isDLog && (x !== (x | 0))) { obj.simpleLabel = true; @@ -1130,93 +1173,105 @@ axes.calcTicks = function calcTicks(ax, opts) { } } + function findOverlappingTick(minorTick, majorTicks) { + const majorTick = majorTicks.find((majorTick) => majorTick.value === minorTick.value); + if (majorTick != null) { + return majorTick; + } + // add 10e6 to eliminate problematic digits + const epsilon = 10e6; + for (var i = 0; i < majorTicks.length; i++) { + if (epsilon + majorTicks[i].value === epsilon + minorTick.value) { + return majorTicks[i]; + } + } + return null; + }; + // check if ticklabelIndex makes sense, otherwise ignore it. // It makes sense if in addition to the always present dummy, there are at least 2 minor ticks // with the required distance to each other. - if(!minorTickVals || minorTickVals.length < 3) { - ticklabelIndex = false; - } else { - var diff = (minorTickVals[2].value - minorTickVals[1].value) * (isReversed ? -1 : 1); - if(!periodCompatibleWithTickformat(diff, ax.tickformat)) { - ticklabelIndex = false; - // remove previously added tick before tick0 for handling ticklabelindex positioning - minorTickVals = minorTickVals.slice(1); + const visibleMinorTicks = minorTickVals.filter((minorTick) => minorTick.noTick !== true); + if (ax._useTicklabelIndex) { + if(!visibleMinorTicks || visibleMinorTicks.length < 2) { + ax._useTicklabelIndex = false; + } else { + var diff = (visibleMinorTicks[1].value - visibleMinorTicks[0].value) * (isReversed ? -1 : 1); + if(!periodCompatibleWithTickformat(diff, axes.getTickFormat(ax.minor))) { + ax._useTicklabelIndex = false; + } } } + // Determine for which ticks to draw labels - if(!ticklabelIndex) { + if(!ax._useTicklabelIndex) { allTicklabelVals = tickVals; } else { - // Collect and sort all major and minor ticks, to find the minor ticks `ticklabelIndex` - // steps away from each major tick. For those minor ticks we want to draw the label. - - var allTickVals = tickVals.concat(minorTickVals); - if(isPeriod && tickVals.length) { - // first major tick was just added for period handling - allTickVals = allTickVals.slice(1); - } - - allTickVals = - allTickVals - .sort(function(a, b) { return a.value - b.value; }) - .filter(function(tick, index, self) { - return index === 0 || tick.value !== self[index - 1].value; - }); - - var majorTickIndices = - allTickVals - .map(function(item, index) { - return item.minor === undefined && !item.skipLabel ? index : null; - }) - .filter(function(index) { return index !== null; }); - - majorTickIndices.forEach(function(majorIdx) { - ticklabelIndex.map(function(nextLabelIdx) { - var minorIdx = majorIdx + nextLabelIdx; - if(minorIdx >= 0 && minorIdx < allTickVals.length) { - Lib.pushUnique(allTicklabelVals, allTickVals[minorIdx]); + // For each major tick, find the minor tick `ticklabelIndex` steps away. + // This minor tick will be labeled instead of the major tick. + if (isPeriod) periodEndTicks = []; // for each minor tick at the start of a labeled period this will hold the neighboring period end tick. + const labelTickValsAscending = minorTickVals + .map((minor) => { + // if there is a major tick at the same position, prefer it over the minor tick because overlapping minor ticks are stripped away + // if both minor and major ticks are drawn on the same side. + const major = findOverlappingTick(minor, tickVals); + if (major) { + return major; } - }); - }); - tickVals.forEach(function(tick) { - tick.skipLabel = allTicklabelVals.indexOf(tick) === -1; + return minor; + }) + .toSorted((a, b) => a.value - b.value); + tickVals.forEach(function(majorTick) { + if (!majorTick.skipLabel) { + ticklabelIndex.forEach((labelIndex) => { + if (labelIndex < 0) { + const smallerTicks = labelTickValsAscending.filter((minorTick) => minorTick.value <= majorTick.value); + const absLabelIndex = Math.abs(labelIndex); + const labeledTickIndex = smallerTicks.length - absLabelIndex - 1; + if (absLabelIndex <= smallerTicks.length - 1) { + const labelVisible = !smallerTicks[labeledTickIndex].noTick || (isPeriod && !smallerTicks[labeledTickIndex + 1].noTick); + if (labelVisible) { + allTicklabelVals.push(smallerTicks[labeledTickIndex]); + if (isPeriod) periodEndTicks.push(smallerTicks[labeledTickIndex + 1]); + } + } + } else { // labelIndex >= 0 + const largerTicks = labelTickValsAscending.filter((minorTick) => minorTick.value >= majorTick.value); + if (labelIndex < largerTicks.length - 1) { + const labelVisible = !largerTicks[labelIndex].noTick || (isPeriod && !largerTicks[labelIndex + 1].noTick); + if (labelVisible) { + allTicklabelVals.push(largerTicks[labelIndex]); + if (isPeriod) periodEndTicks.push(largerTicks[labelIndex + 1]); + } + } + } + majorTick.skipLabel = majorTick.skipLabel !== false; + }); + } }); + allTicklabelVals.forEach((t) => t.skipLabel = false); } if(hasMinor) { - var canOverlap = + var allowedToOverlap = (ax.minor.ticks === 'inside' && ax.ticks === 'outside') || (ax.minor.ticks === 'outside' && ax.ticks === 'inside'); - if(!canOverlap) { + if(!allowedToOverlap) { // remove duplicate minors - - var majorValues = tickVals.map(function(d) { return d.value; }); - var list = []; - for(var k = 0; k < minorTickVals.length; k++) { - var T = minorTickVals[k]; - var v = T.value; - if(majorValues.indexOf(v) !== -1) { - continue; - } - var found = false; - for(var q = 0; !found && (q < tickVals.length); q++) { - if( - // add 10e6 to eliminate problematic digits - 10e6 + tickVals[q].value === - 10e6 + v - ) { - found = true; - } + for(var k = 0; k < visibleMinorTicks.length; k++) { + if (findOverlappingTick(visibleMinorTicks[k], tickVals) == null) { + list.push(visibleMinorTicks[k]); } - if(!found) list.push(T); } minorTickVals = list; } } - - if(isPeriod) positionPeriodTicks(allTicklabelVals, ax, ax._definedDelta); + if(isPeriod) { + var periodDefinedDelta = ax._useTicklabelIndex ? ax.minor._definedDelta : ax._definedDelta; + positionPeriodTicks(allTicklabelVals, ax, periodDefinedDelta, periodEndTicks); + } var i; if(ax.rangebreaks) { @@ -1275,7 +1330,7 @@ axes.calcTicks = function calcTicks(ax, opts) { tickVals = tickVals.concat(minorTickVals); function setTickLabel(ax, tickVal) { - var text = axes.tickText( + var labeledTick = axes.tickText( ax, tickVal.value, false, // hover @@ -1283,15 +1338,15 @@ axes.calcTicks = function calcTicks(ax, opts) { ); var p = tickVal.periodX; if(p !== undefined) { - text.periodX = p; + labeledTick.periodX = p; if(p > maxRange || p < minRange) { // hide label if outside the range - if(p > maxRange) text.periodX = maxRange; - if(p < minRange) text.periodX = minRange; + if(p > maxRange) labeledTick.periodX = maxRange; + if(p < minRange) labeledTick.periodX = minRange; - hideLabel(text); + hideLabel(labeledTick); } } - return text; + return labeledTick; } var t; @@ -1300,11 +1355,12 @@ axes.calcTicks = function calcTicks(ax, opts) { var _value = tickVals[i].value; if(_minor) { - if(ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) { + if(ax._useTicklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) { t = setTickLabel(ax, tickVals[i]); } else { - t = { x: _value }; + t = { x: _value, text: ''}; } + t.noTick = tickVals[i].noTick; t.minor = true; minorTicks.push(t); } else { @@ -1313,24 +1369,14 @@ axes.calcTicks = function calcTicks(ax, opts) { if (tickVals[i].skipLabel) { hideLabel(t); } - + t.noTick = tickVals[i].noTick; ticksOut.push(t); } } - if(isPeriod && ticklabelIndex && minorTicks.length) { - // drop very first minor tick that we added to handle ticklabelindex - minorTicks[0].noTick = true; - } - ticksOut = ticksOut.concat(minorTicks); - ax._inCalcTicks = false; - if(isPeriod && ticksOut.length) { - // drop very first tick that we added to handle period - ticksOut[0].noTick = true; - } - + ticksOut = ticksOut.concat(minorTicks); return ticksOut; }; @@ -1867,8 +1913,9 @@ function tickTextObj(ax, x, text) { } function formatDate(ax, out, hover, extraPrecision) { - var tr = ax._tickround; - var fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax); + var periodAxis = ax._useTicklabelIndex ? ax.minor : ax; + var tr = periodAxis._tickround; + var fmt = (hover && ax.hoverformat) || axes.getTickFormat(periodAxis); // Only apply extra precision if no explicit format was provided. extraPrecision = !fmt && extraPrecision; diff --git a/test/image/baselines/date_axes_period2_ticklabelindex.png b/test/image/baselines/date_axes_period2_ticklabelindex.png index c4473a55386..abed6401cde 100644 Binary files a/test/image/baselines/date_axes_period2_ticklabelindex.png and b/test/image/baselines/date_axes_period2_ticklabelindex.png differ diff --git a/test/image/baselines/date_axes_period_ticklabelindex.png b/test/image/baselines/date_axes_period_ticklabelindex.png index 49277798a98..ec1bffef814 100644 Binary files a/test/image/baselines/date_axes_period_ticklabelindex.png and b/test/image/baselines/date_axes_period_ticklabelindex.png differ diff --git a/test/image/baselines/ticklabelindex-2.png b/test/image/baselines/ticklabelindex-2.png new file mode 100644 index 00000000000..9858e71441b Binary files /dev/null and b/test/image/baselines/ticklabelindex-2.png differ diff --git a/test/image/baselines/ticklabelindex.png b/test/image/baselines/ticklabelindex.png index 5cf10b8ec07..f83b04d6680 100644 Binary files a/test/image/baselines/ticklabelindex.png and b/test/image/baselines/ticklabelindex.png differ diff --git a/test/image/mocks/date_axes_period2_ticklabelindex.json b/test/image/mocks/date_axes_period2_ticklabelindex.json index da103bb7bf1..7de1c602f60 100644 --- a/test/image/mocks/date_axes_period2_ticklabelindex.json +++ b/test/image/mocks/date_axes_period2_ticklabelindex.json @@ -44,7 +44,7 @@ "layout": { "showlegend": false, "width": 600, - "height": 500, + "height": 740, "yaxis": { "domain": [0, 0.04] }, diff --git a/test/image/mocks/ticklabelindex-2.json b/test/image/mocks/ticklabelindex-2.json new file mode 100644 index 00000000000..9321d4923ea --- /dev/null +++ b/test/image/mocks/ticklabelindex-2.json @@ -0,0 +1,68 @@ +{ + "data": [ + { + "x": [ + "2020", "2021", "2022" + ], + "y": [ + 3, 2, 1 + ] + }, + { + "mode": "markers+text", + "texttemplate": "%{x|Q%q %y}", + "textposition": "top center", + "x": [ + "2019-07-01", "2019-10-01", "2020-01-01", "2020-04-01", "2020-07-01" + ], + "y": [ + 2, 2, 2, 2, 2 + ], + "yaxis": "y2", + "xaxis": "x2" + } + ], + "layout": { + "width": 600, + "height": 800, + "grid": { + "rows": 2, + "columns": 1, + "pattern": "independent" + }, + "xaxis": { + "dtick": "M12", + "insiderange": [ + "2020-03-27", + "2022-07-21" + ], + "tickformat": "%Y", + "ticklabelindex": -1, + "ticks": "outside", + "ticklen": 20, + "ticklabelmode": "period", + "type": "date", + "title": { + "text": "Should display 2 major ticks and labels 2020, 2021, 2022" + } + }, + "xaxis2": { + "dtick": "M24", + "insiderange": [ + "2019-03-01", + "2021-01-01" + ], + "tick0": "2021-01-01", + "tickformat": "%Y", + "ticklabelindex": -1, + "ticklabelmode": "period", + "ticklen": 20, + "type": "date", + "ticks": "outside", + "minor": { "dtick": "M12", "ticks": "outside", "ticklen": 5 }, + "title": { + "text": "Should display 1 major tick, 1 minor tick and a label 2020 in between." + } + } + } +} diff --git a/test/jasmine/tests/titles_test.js b/test/jasmine/tests/titles_test.js index 9744a517248..2cd25b9dd72 100644 --- a/test/jasmine/tests/titles_test.js +++ b/test/jasmine/tests/titles_test.js @@ -1288,8 +1288,10 @@ describe('Editable titles', function() { var textNode = document.querySelector('.' + className); textNode.dispatchEvent(new window.MouseEvent('click')); - - var editNode = document.querySelector('.plugin-editable.editable'); + // make sure to select the editNode created by the click, not some leftover + // from the previous call (the blur event removes it asynchronously.) + var editNodes = document.querySelectorAll('.plugin-editable.editable'); + var editNode = editNodes[editNodes.length - 1]; editNode.dispatchEvent(new window.FocusEvent('focus')); editNode.textContent = text; editNode.dispatchEvent(new window.FocusEvent('focus'));