diff --git a/draftlogs/7006_add.md b/draftlogs/7006_add.md index c3faa0ca5c4..98a572f10d5 100644 --- a/draftlogs/7006_add.md +++ b/draftlogs/7006_add.md @@ -1 +1 @@ - - Add property `ticklabelstandoff` and `ticklabelshift` to cartesian axes to adjust positioning of tick labels [[#7006](https://github.com/plotly/plotly.js/pull/7006)] + - Add property `ticklabelstandoff` and `ticklabelshift` to cartesian axes to adjust positioning of tick labels, with thanks to @my-tien for the contribution! [[#7006](https://github.com/plotly/plotly.js/pull/7006)] diff --git a/draftlogs/7036_add.md b/draftlogs/7036_add.md new file mode 100644 index 00000000000..f71229e3848 --- /dev/null +++ b/draftlogs/7036_add.md @@ -0,0 +1 @@ + - Add axis property `ticklabelindex` for drawing the label for each minor tick n positions away from a major tick, with thanks to @my-tien for the contribution! [[#7036](https://github.com/plotly/plotly.js/pull/7036)] \ No newline at end of file diff --git a/src/constants/numerical.js b/src/constants/numerical.js index e7e0a4febfe..1edd86e97e4 100644 --- a/src/constants/numerical.js +++ b/src/constants/numerical.js @@ -37,7 +37,8 @@ module.exports = { ONEHOUR: 3600000, ONEMIN: 60000, ONESEC: 1000, - + ONEMILLI: 1, + ONEMICROSEC: 0.001, /* * For fast conversion btwn world calendars and epoch ms, the Julian Day Number * of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD() diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 2b2dffe6840..90f7b288de5 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -31,6 +31,8 @@ var HALFDAY = ONEDAY / 2; var ONEHOUR = constants.ONEHOUR; var ONEMIN = constants.ONEMIN; var ONESEC = constants.ONESEC; +var ONEMILLI = constants.ONEMILLI; +var ONEMICROSEC = constants.ONEMICROSEC; var MINUS_SIGN = constants.MINUS_SIGN; var BADNUM = constants.BADNUM; @@ -908,7 +910,9 @@ axes.calcTicks = function calcTicks(ax, opts) { var calendar = ax.calendar; var ticklabelstep = ax.ticklabelstep; var isPeriod = ax.ticklabelmode === 'period'; - + var isReversed = ax.range[0] > ax.range[1]; + var ticklabelIndex = (!ax.ticklabelindex || Lib.isArrayOrTypedArray(ax.ticklabelindex)) ? + ax.ticklabelindex : [ax.ticklabelindex]; 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]); @@ -921,6 +925,9 @@ axes.calcTicks = function calcTicks(ax, opts) { var tickVals = []; var minorTickVals = []; + // all ticks for which labels are drawn which is not necessarily the major ticks when + // `ticklabelindex` is set. + var allTicklabelVals = []; var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid); @@ -1075,6 +1082,52 @@ axes.calcTicks = function calcTicks(ax, opts) { } } + // check if ticklabelIndex makes sense, otherwise ignore it + if(!minorTickVals || minorTickVals.length < 2) { + ticklabelIndex = false; + } else { + var diff = (minorTickVals[1].value - minorTickVals[0].value) * (isReversed ? -1 : 1); + if(!periodCompatibleWithTickformat(diff, ax.tickformat)) { + ticklabelIndex = false; + } + } + // Determine for which ticks to draw labels + if(!ticklabelIndex) { + 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]); + } + }); + }); + } + if(hasMinor) { var canOverlap = (ax.minor.ticks === 'inside' && ax.ticks === 'outside') || @@ -1108,7 +1161,7 @@ axes.calcTicks = function calcTicks(ax, opts) { } } - if(isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta); + if(isPeriod) positionPeriodTicks(allTicklabelVals, ax, ax._definedDelta); var i; if(ax.rangebreaks) { @@ -1166,38 +1219,44 @@ axes.calcTicks = function calcTicks(ax, opts) { tickVals = tickVals.concat(minorTickVals); - var t, p; + function setTickLabel(ax, tickVal) { + var text = axes.tickText( + ax, + tickVal.value, + false, // hover + tickVal.simpleLabel // noSuffixPrefix + ); + var p = tickVal.periodX; + if(p !== undefined) { + text.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; + + hideLabel(text); + } + } + return text; + } + + var t; for(i = 0; i < tickVals.length; i++) { var _minor = tickVals[i].minor; var _value = tickVals[i].value; if(_minor) { - minorTicks.push({ - x: _value, - minor: true - }); + if(ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) { + t = setTickLabel(ax, tickVals[i]); + } else { + t = { x: _value }; + } + t.minor = true; + minorTicks.push(t); } else { lastVisibleHead = ax._prevDateHead; - - t = axes.tickText( - ax, - _value, - false, // hover - tickVals[i].simpleLabel // 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; - - hideLabel(t); - } - } - - if(tickVals[i].skipLabel) { + t = setTickLabel(ax, tickVals[i]); + if(tickVals[i].skipLabel || + ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) === -1) { hideLabel(t); } @@ -4542,3 +4601,28 @@ function setShiftVal(ax, axShifts) { axShifts[ax.overlaying][ax.side] : (ax.shift || 0); } + +/** + * Checks if the given period is at least the period described by the tickformat or larger. If that + * is the case, they are compatible, because then the tickformat can be used to describe the period. + * E.g. it doesn't make sense to put a year label on a period spanning only a month. + * @param {number} period in ms + * @param {string} tickformat + * @returns {boolean} + */ +function periodCompatibleWithTickformat(period, tickformat) { + return ( + /%f/.test(tickformat) ? period >= ONEMICROSEC : + /%L/.test(tickformat) ? period >= ONEMILLI : + /%[SX]/.test(tickformat) ? period >= ONESEC : + /%M/.test(tickformat) ? period >= ONEMIN : + /%[HI]/.test(tickformat) ? period >= ONEHOUR : + /%p/.test(tickformat) ? period >= HALFDAY : + /%[Aadejuwx]/.test(tickformat) ? period >= ONEDAY : + /%[UVW]/.test(tickformat) ? period >= ONEWEEK : + /%[Bbm]/.test(tickformat) ? period >= ONEMINMONTH : + /%[q]/.test(tickformat) ? period >= ONEMINQUARTER : + /%[Yy]/.test(tickformat) ? period >= ONEMINYEAR : + true + ); +} diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index 0b03fecf376..8c5c75d8832 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -59,6 +59,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, } } + if(!options.noTicklabelindex && (axType === 'date' || axType === 'linear')) { + coerce('ticklabelindex'); + } + var ticklabelposition = ''; if(!options.noTicklabelposition || axType === 'multicategory') { ticklabelposition = Lib.coerce(containerIn, containerOut, { diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 33eb6ee1b33..6fe04d31406 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -721,6 +721,21 @@ module.exports = { 'outside and vice versa.' ].join(' ') }, + ticklabelindex: { + // in the future maybe add `extras: ['all', 'minor']` to allow showing labels for all ticks + // or for all minor ticks. + valType: 'integer', + arrayOk: true, + editType: 'calc', + description: [ + 'Only for axes with `type` *date* or *linear*.', + 'Instead of drawing the major tick label, draw the label for the minor tick', + 'that is n positions away from the major tick. E.g. to always draw the label for the', + 'minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date', + 'axes with `ticklabelmode` *period* if you want to label the period that ends with each', + 'major tick instead of the period that begins there.' + ].join(' ') + }, mirror: { valType: 'enumerated', values: [true, 'ticks', false, 'all', 'allticks'], diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index ae04bb5fe19..e3d71be24cb 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -42,6 +42,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { data: options.data, showGrid: true, noAutotickangles: true, + noTicklabelindex: true, noTickson: true, noTicklabelmode: true, noTicklabelshift: true, diff --git a/test/image/baselines/zzz_date_axes-ticklabelstep-ticklabelindex.png b/test/image/baselines/zzz_date_axes-ticklabelstep-ticklabelindex.png new file mode 100644 index 00000000000..42e95c0a2b3 Binary files /dev/null and b/test/image/baselines/zzz_date_axes-ticklabelstep-ticklabelindex.png differ diff --git a/test/image/baselines/zzz_date_axes_period2_ticklabelindex.png b/test/image/baselines/zzz_date_axes_period2_ticklabelindex.png new file mode 100644 index 00000000000..493508bb782 Binary files /dev/null and b/test/image/baselines/zzz_date_axes_period2_ticklabelindex.png differ diff --git a/test/image/baselines/zzz_date_axes_period_ticklabelindex.png b/test/image/baselines/zzz_date_axes_period_ticklabelindex.png new file mode 100644 index 00000000000..f891c89a761 Binary files /dev/null and b/test/image/baselines/zzz_date_axes_period_ticklabelindex.png differ diff --git a/test/image/baselines/zzz_date_axes_ticklabelindex.png b/test/image/baselines/zzz_date_axes_ticklabelindex.png new file mode 100644 index 00000000000..5aff9c14a01 Binary files /dev/null and b/test/image/baselines/zzz_date_axes_ticklabelindex.png differ diff --git a/test/image/baselines/zzz_ticklabelindex.png b/test/image/baselines/zzz_ticklabelindex.png new file mode 100644 index 00000000000..6ba30bf59e3 Binary files /dev/null and b/test/image/baselines/zzz_ticklabelindex.png differ diff --git a/test/image/baselines/zzz_ticklabelindex_period_axes.png b/test/image/baselines/zzz_ticklabelindex_period_axes.png new file mode 100644 index 00000000000..ebcedf1e3a9 Binary files /dev/null and b/test/image/baselines/zzz_ticklabelindex_period_axes.png differ diff --git a/test/image/mocks/zzz_date_axes-ticklabelstep-ticklabelindex.json b/test/image/mocks/zzz_date_axes-ticklabelstep-ticklabelindex.json new file mode 100644 index 00000000000..b4c6ee6f9b7 --- /dev/null +++ b/test/image/mocks/zzz_date_axes-ticklabelstep-ticklabelindex.json @@ -0,0 +1,224 @@ +{ + "data": [ + { + "x": [ + "1900-01-01", + "2000-01-01", + "2100-01-01" + ], + "y": [ + 1, + 3, + 2 + ] + }, + { + "x": [ + "2013-05-01", + "2013-09-01", + "2014-01-01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x2", + "yaxis": "y2" + }, + { + "x": [ + "2013-11-17", + "2013-12-15", + "2014-01-12" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "x": [ + "2013-01-01", + "2013-01-02", + "2013-01-03" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "x": [ + "2013-07-01 18:00", + "2013-07-02 00:00", + "2013-07-02 06:00" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "x": [ + "2013-01-01 23:59", + "2013-01-02 00:00", + "2013-01-02 00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "x": [ + "2013-07-01 23:59:59", + "2013-07-02 00:00:00", + "2013-07-02 00:00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x7", + "yaxis": "y7" + } + ], + "layout": { + "showlegend": false, + "width": 600, + "height": 500, + "yaxis": { + "ticklabelstep": 2, + "domain": [ + 0, + 0.04 + ] + }, + "yaxis2": { + "ticklabelstep": 2, + "domain": [ + 0.16, + 0.2 + ] + }, + "yaxis3": { + "ticklabelstep": 2, + "domain": [ + 0.32, + 0.36 + ] + }, + "yaxis4": { + "ticklabelstep": 2, + "domain": [ + 0.48, + 0.52 + ] + }, + "yaxis5": { + "ticklabelstep": 2, + "domain": [ + 0.64, + 0.68 + ] + }, + "yaxis6": { + "ticklabelstep": 2, + "domain": [ + 0.80, + 0.84 + ] + }, + "yaxis7": { + "ticklabelstep": 2, + "domain": [ + 0.96, + 1 + ] + }, + "xaxis": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "ticklabelindex": 4, + "anchor": "y" + }, + "xaxis2": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y2", + "ticklabelindex": 1 + }, + "xaxis3": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y3", + "ticklabelindex": 1 + }, + "xaxis4": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y4", + "ticklabelindex": 3 + }, + "xaxis5": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y5", + "ticklabelindex": -2 + }, + "xaxis6": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y6", + "ticklabelindex": 2 + }, + "xaxis7": { + "ticklabelstep": 2, + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "tickcolor": "red", + "anchor": "y7", + "ticklabelindex": -3 + } + } +} diff --git a/test/image/mocks/zzz_date_axes_period2_ticklabelindex.json b/test/image/mocks/zzz_date_axes_period2_ticklabelindex.json new file mode 100644 index 00000000000..8f30096f44a --- /dev/null +++ b/test/image/mocks/zzz_date_axes_period2_ticklabelindex.json @@ -0,0 +1,224 @@ +{ + "data": [ + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ] + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x2", + "yaxis": "y2" + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "x": [ + "2017-01-01", + "2023-01-01" + ], + "y": [ + 0, + 1 + ], + "xaxis": "x7", + "yaxis": "y7" + } + ], + "layout": { + "showlegend": false, + "width": 600, + "height": 500, + "yaxis": { + "domain": [ + 0, + 0.04 + ] + }, + "yaxis2": { + "domain": [ + 0.16, + 0.2 + ] + }, + "yaxis3": { + "domain": [ + 0.32, + 0.36 + ] + }, + "yaxis4": { + "domain": [ + 0.48, + 0.52 + ] + }, + "yaxis5": { + "domain": [ + 0.64, + 0.68 + ] + }, + "yaxis6": { + "domain": [ + 0.80, + 0.84 + ] + }, + "yaxis7": { + "domain": [ + 0.96, + 1 + ] + }, + "xaxis": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2019-12-24", + "2020-01-06" + ], + "ticklabelmode": "period", + "tickformat": "%b %d, %Y", + "ticklabelindex": 1, + "tickangle": 30 + }, + "xaxis2": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2019-12-29", + "2020-01-04" + ], + "ticklabelmode": "period", + "anchor": "y2", + "ticklabelindex": -1, + "tickangle": 20 + }, + "xaxis3": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2020-01-03", + "2019-12-28" + ], + "ticklabelmode": "period", + "anchor": "y3", + "ticklabelindex": 2, + "tickangle": 20 + }, + "xaxis4": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2020-03-01", + "2020-11-01" + ], + "ticklabelmode": "period", + "anchor": "y4", + "ticklabelindex": 1, + "tickangle": 20 + }, + "xaxis5": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2016-09-01", + "2017-06-01" + ], + "ticklabelmode": "period", + "anchor": "y5", + "ticklabelindex": -1, + "tickangle": 20 + }, + "xaxis6": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2016-05-01", + "2019-09-01" + ], + "ticklabelmode": "period", + "anchor": "y6", + "ticklabelindex": 2, + "tickangle": 20 + }, + "xaxis7": { + "minor": { "ticks": "inside" }, + "tickcolor": "black", + "gridcolor": "orange", + "range": [ + "2016-05-01", + "2021-09-01" + ], + "ticklabelmode": "period", + "anchor": "y7", + "ticklabelindex": -2, + "tickangle": 20 + } + } +} diff --git a/test/image/mocks/zzz_date_axes_period_ticklabelindex.json b/test/image/mocks/zzz_date_axes_period_ticklabelindex.json new file mode 100644 index 00000000000..16cc3ba2262 --- /dev/null +++ b/test/image/mocks/zzz_date_axes_period_ticklabelindex.json @@ -0,0 +1,224 @@ +{ + "data": [ + { + "x": [ + "1900-01-01", + "2000-01-01", + "2100-01-01" + ], + "y": [ + 1, + 3, + 2 + ] + }, + { + "x": [ + "2013-05-01", + "2013-09-01", + "2014-01-01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x2", + "yaxis": "y2" + }, + { + "x": [ + "2013-11-17", + "2013-12-15", + "2014-01-12" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "x": [ + "2013-01-01", + "2013-01-02", + "2013-01-03" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "x": [ + "2013-07-01 18:00", + "2013-07-02 00:00", + "2013-07-02 06:00" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "x": [ + "2013-01-01 23:59", + "2013-01-02 00:00", + "2013-01-02 00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "x": [ + "2013-07-01 23:59:59", + "2013-07-02 00:00:00", + "2013-07-02 00:00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x7", + "yaxis": "y7" + } + ], + "layout": { + "showlegend": false, + "width": 600, + "height": 500, + "yaxis": { + "domain": [ + 0, + 0.04 + ] + }, + "yaxis2": { + "domain": [ + 0.16, + 0.2 + ] + }, + "yaxis3": { + "domain": [ + 0.32, + 0.36 + ] + }, + "yaxis4": { + "domain": [ + 0.48, + 0.52 + ] + }, + "yaxis5": { + "domain": [ + 0.64, + 0.68 + ] + }, + "yaxis6": { + "domain": [ + 0.80, + 0.84 + ] + }, + "yaxis7": { + "domain": [ + 0.96, + 1 + ] + }, + "xaxis": { + "anchor": "y", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": 4 + }, + "xaxis2": { + "anchor": "y2", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": -1 + }, + "xaxis3": { + "anchor": "y3", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": -1 + }, + "xaxis4": { + "anchor": "y4", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": 2 + }, + "xaxis5": { + "anchor": "y5", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": 1 + }, + "xaxis6": { + "anchor": "y6", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": -3 + }, + "xaxis7": { + "anchor": "y7", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticks": "outside", + "ticklabelmode": "period", + "tickcolor": "red", + "ticklabelindex": 5 + } + } +} diff --git a/test/image/mocks/zzz_date_axes_ticklabelindex.json b/test/image/mocks/zzz_date_axes_ticklabelindex.json new file mode 100644 index 00000000000..7aa5b0b7ec8 --- /dev/null +++ b/test/image/mocks/zzz_date_axes_ticklabelindex.json @@ -0,0 +1,216 @@ +{ + "data": [ + { + "x": [ + "1900-01-01", + "2000-01-01", + "2100-01-01" + ], + "y": [ + 1, + 3, + 2 + ] + }, + { + "x": [ + "2013-05-01", + "2013-09-01", + "2014-01-01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x2", + "yaxis": "y2" + }, + { + "x": [ + "2013-11-17", + "2013-12-15", + "2014-01-12" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "x": [ + "2013-01-01", + "2013-01-02", + "2013-01-03" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "x": [ + "2013-07-01 18:00", + "2013-07-02 00:00", + "2013-07-02 06:00" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "x": [ + "2013-01-01 23:59", + "2013-01-02 00:00", + "2013-01-02 00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "x": [ + "2013-07-01 23:59:59", + "2013-07-02 00:00:00", + "2013-07-02 00:00:01" + ], + "y": [ + 1, + 3, + 2 + ], + "xaxis": "x7", + "yaxis": "y7" + } + ], + "layout": { + "showlegend": false, + "width": 600, + "height": 500, + "yaxis": { + "domain": [ + 0, + 0.04 + ] + }, + "yaxis2": { + "domain": [ + 0.16, + 0.2 + ] + }, + "yaxis3": { + "domain": [ + 0.32, + 0.36 + ] + }, + "yaxis4": { + "domain": [ + 0.48, + 0.52 + ] + }, + "yaxis5": { + "domain": [ + 0.64, + 0.68 + ] + }, + "yaxis6": { + "domain": [ + 0.80, + 0.84 + ] + }, + "yaxis7": { + "domain": [ + 0.96, + 1 + ] + }, + "xaxis": { + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": 2 + }, + "xaxis2": { + "anchor": "y2", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": 1 + }, + "xaxis3": { + "anchor": "y3", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": -1 + }, + "xaxis4": { + "anchor": "y4", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": 3 + }, + "xaxis5": { + "anchor": "y5", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": 2 + }, + "xaxis6": { + "anchor": "y6", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": -2 + }, + "xaxis7": { + "anchor": "y7", + "ticks": "outside", + "tickcolor": "red", + "minor": { + "ticks": "outside", + "tickcolor": "black" + }, + "ticklabelindex": 4 + } + } +} diff --git a/test/image/mocks/zzz_ticklabelindex.json b/test/image/mocks/zzz_ticklabelindex.json new file mode 100644 index 00000000000..3e90aaf050e --- /dev/null +++ b/test/image/mocks/zzz_ticklabelindex.json @@ -0,0 +1,197 @@ +{ + "data": [ + { + "x": [ + "2019-10-01T00:00:00", + "2020-01-01T00:00:00", + "2020-04-01T00:00:00", + "2020-07-01T00:00:00", + "2020-10-01T00:00:00", + "2021-01-01T00:00:00", + "2021-04-01T00:00:00", + "2021-07-01T00:00:00", + "2021-10-01T00:00:00", + "2022-01-01T00:00:00", + "2022-04-01T00:00:00", + "2022-07-01T00:00:00", + "2022-10-01T00:00:00" + ], + "xperiod": "M3", + "xperiodalignment": "middle", + "y": [ + 0, + -2.30469, + -22.1636, + -9.04912, + -5.73645, + -13.9468, + -2.3389, + -0.38651, + 1.666218, + 4.903598, + 6.082659, + 6.040196, + 5.86461 + ], + "type": "bar" + }, + { + "x": [ + "2019-10-01T00:00:00", + "2020-01-01T00:00:00", + "2020-04-01T00:00:00", + "2020-07-01T00:00:00", + "2020-10-01T00:00:00", + "2021-01-01T00:00:00", + "2021-04-01T00:00:00", + "2021-07-01T00:00:00", + "2021-10-01T00:00:00", + "2022-01-01T00:00:00", + "2022-04-01T00:00:00", + "2022-07-01T00:00:00", + "2022-10-01T00:00:00" + ], + "xperiod": "M3", + "xperiodalignment": "middle", + "y": [ + 0, + -0.36063, + 0.095131, + -0.73126, + -1.13661, + -2.23413, + -3.79368, + -5.15376, + -6.65433, + -8.4567, + -10.2163, + -11.2734, + -12.1921 + ], + "type": "bar" + }, + { + "x": [ + "2019-10-01T00:00:00", + "2020-01-01T00:00:00", + "2020-04-01T00:00:00", + "2020-07-01T00:00:00", + "2020-10-01T00:00:00", + "2021-01-01T00:00:00", + "2021-04-01T00:00:00", + "2021-07-01T00:00:00", + "2021-10-01T00:00:00", + "2022-01-01T00:00:00", + "2022-04-01T00:00:00", + "2022-07-01T00:00:00", + "2022-10-01T00:00:00" + ], + "xperiod": "M3", + "xperiodalignment": "middle", + "y": [ + 0, + 0.313137, + 17.67733, + 8.389384, + 3.892285, + 20.28805, + 7.832283, + 6.055129, + 4.563399, + 4.248476, + 4.281987, + 4.308401, + 4.948447 + ], + "type": "bar", + "xaxis": "x2" + }, + { + "x": [ + "2019-10-01T00:00:00", + "2020-01-01T00:00:00", + "2020-04-01T00:00:00", + "2020-07-01T00:00:00", + "2020-10-01T00:00:00", + "2021-01-01T00:00:00", + "2021-04-01T00:00:00", + "2021-07-01T00:00:00", + "2021-10-01T00:00:00", + "2022-01-01T00:00:00", + "2022-04-01T00:00:00", + "2022-07-01T00:00:00", + "2022-10-01T00:00:00" + ], + "xperiod": "M3", + "xperiodalignment": "middle", + "y": [ + 0, + 0.76038, + -6.86916, + -0.92843, + 1.618134, + -2.89594, + 2.360814, + 4.274154, + 5.982815, + 5.197389, + 6.249781, + 7.882427, + 8.592399 + ], + "type": "bar", + "xaxis": "x2" + } + ], + "layout": { + "bargap": 0.1, + "xaxis": { + "title": { + "text": "xaxis: `ticklabelindex` is ignored because tickformat is incompatible with minor ticks" + }, + "dtick": "M12", + "minor": { + "dtick": "M3", + "ticklen": 9, + "tickmode": "linear", + "ticks": "inside" + }, + "tickformat": "%Y", + "ticklabelmode": "period", + "ticklabelindex": -1, + "ticklen": 18, + "type": "date" + }, + "xaxis2": { + "title": { + "text": "xaxis2: Period before, after each major tick and in the center between major ticks are labeled" + }, + "dtick": "M12", + "minor": { + "dtick": "M1", + "ticklen": 9, + "tickmode": "linear", + "ticks": "outside" + }, + "tickformat": "%b %Y", + "autorange": "reversed", + "ticklabelindex": [-1, 0, 6], + "tickangle": 90, + "ticklabelmode": "period", + "ticklen": 18, + "ticks": "inside", + "side": "top", + "overlaying": "x", + "type": "date" + }, + "yaxis": { + "ticks": "outside", + "ticklabelindex": 5, + "ticklen": 10, + "minor": { + "dtick": 1, + "ticks": "outside" + } + } + } +} diff --git a/test/plot-schema.json b/test/plot-schema.json index e2beeacfc70..929904f8d9a 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -15116,6 +15116,17 @@ }, "role": "object" }, + "ticklabelindex": { + "arrayOk": true, + "description": "Only for axes with `type` *date* or *linear*. Instead of drawing the major tick label, draw the label for the minor tick that is n positions away from the major tick. E.g. to always draw the label for the minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date axes with `ticklabelmode` *period* if you want to label the period that ends with each major tick instead of the period that begins there.", + "editType": "calc", + "valType": "integer" + }, + "ticklabelindexsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `ticklabelindex`.", + "editType": "none", + "valType": "string" + }, "ticklabelmode": { "description": "Determines where tick labels are drawn with respect to their corresponding ticks and grid lines. Only has an effect for axes of `type` *date* When set to *period*, tick labels are drawn in the middle of the period between ticks.", "dflt": "instant", @@ -16437,6 +16448,17 @@ }, "role": "object" }, + "ticklabelindex": { + "arrayOk": true, + "description": "Only for axes with `type` *date* or *linear*. Instead of drawing the major tick label, draw the label for the minor tick that is n positions away from the major tick. E.g. to always draw the label for the minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date axes with `ticklabelmode` *period* if you want to label the period that ends with each major tick instead of the period that begins there.", + "editType": "calc", + "valType": "integer" + }, + "ticklabelindexsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `ticklabelindex`.", + "editType": "none", + "valType": "string" + }, "ticklabelmode": { "description": "Determines where tick labels are drawn with respect to their corresponding ticks and grid lines. Only has an effect for axes of `type` *date* When set to *period*, tick labels are drawn in the middle of the period between ticks.", "dflt": "instant",