diff --git a/draftlogs/7006_add.md b/draftlogs/7006_add.md new file mode 100644 index 00000000000..c3faa0ca5c4 --- /dev/null +++ b/draftlogs/7006_add.md @@ -0,0 +1 @@ + - Add property `ticklabelstandoff` and `ticklabelshift` to cartesian axes to adjust positioning of tick labels [[#7006](https://github.com/plotly/plotly.js/pull/7006)] diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index 6e8113421b6..2fab3cd5209 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -111,6 +111,8 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) { var font = layout.font; var opts = { noAutotickangles: true, + noTicklabelshift: true, + noTicklabelstandoff: true, outerTicks: false, font: font }; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index c6d2c478ac8..2b2dffe6840 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2968,20 +2968,39 @@ axes.makeTransTickFn = function(ax) { axes.makeTransTickLabelFn = function(ax) { var uv = getTickLabelUV(ax); + var shift = ax.ticklabelshift || 0; + var standoff = ax.ticklabelstandoff || 0; + var u = uv[0]; var v = uv[1]; + var isReversed = ax.range[0] > ax.range[1]; + var labelsInside = ax.ticklabelposition && ax.ticklabelposition.indexOf('inside') !== -1; + var labelsOutside = !labelsInside; + + if(shift) { + var shiftSign = isReversed ? -1 : 1; + shift = shift * shiftSign; + } + if(standoff) { + var side = ax.side; + var standoffSign = ( + (labelsInside && (side === 'top' || side === 'left')) || + (labelsOutside && (side === 'bottom' || side === 'right')) + ) ? 1 : -1; + standoff = standoff * standoffSign; + } return ax._id.charAt(0) === 'x' ? function(d) { return strTranslate( - u + ax._offset + ax.l2p(getPosX(d)), - v + u + ax._offset + ax.l2p(getPosX(d)) + shift, + v + standoff ); } : function(d) { return strTranslate( - v, - u + ax._offset + ax.l2p(getPosX(d)) + v + standoff, + u + ax._offset + ax.l2p(getPosX(d)) + shift ); }; }; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 2532e0c75cd..33eb6ee1b33 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -698,6 +698,29 @@ module.exports = { 'In other cases the default is *hide past div*.' ].join(' ') }, + ticklabelshift: { + valType: 'integer', + dflt: 0, + editType: 'ticks', + description: [ + 'Shifts the tick labels by the specified number of pixels in parallel to the axis.', + 'Positive values move the labels in the positive direction of the axis.' + ].join(' ') + }, + ticklabelstandoff: { + valType: 'integer', + dflt: 0, + editType: 'ticks', + description: [ + 'Sets the standoff distance (in px) between the axis tick labels and their default position.', + 'A positive `ticklabelstandoff` moves the labels farther away from the plot area', + 'if `ticklabelposition` is *outside*, and deeper into the plot area if', + '`ticklabelposition` is *inside*. A negative `ticklabelstandoff` works in the opposite', + 'direction, moving outside ticks towards the plot area and inside ticks towards', + 'the outside. If the negative value is large enough, inside ticks can even end up', + 'outside and vice versa.' + ].join(' ') + }, mirror: { valType: 'enumerated', values: [true, 'ticks', false, 'all', 'allticks'], diff --git a/src/plots/cartesian/tick_label_defaults.js b/src/plots/cartesian/tick_label_defaults.js index 887daf220b2..7391dd12323 100644 --- a/src/plots/cartesian/tick_label_defaults.js +++ b/src/plots/cartesian/tick_label_defaults.js @@ -16,6 +16,12 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe var showTickLabels = coerce('showticklabels'); if(showTickLabels) { + if(!options.noTicklabelshift) { + coerce('ticklabelshift'); + } + if(!options.noTicklabelstandoff) { + coerce('ticklabelstandoff'); + } var font = options.font || {}; var contColor = containerOut.color; var position = containerOut.ticklabelposition || ''; diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index 7bbc87249d7..ae04bb5fe19 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -44,6 +44,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { noAutotickangles: true, noTickson: true, noTicklabelmode: true, + noTicklabelshift: true, + noTicklabelstandoff: true, noTicklabelstep: true, noTicklabelposition: true, noTicklabeloverflow: true, diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 4585f355889..7b4b9507ca6 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -186,7 +186,9 @@ function handleDefaults(contIn, contOut, coerce, opts) { size: dfltFontSize, family: dfltFontFamily }, - noAutotickangles: axName === 'angularaxis' + noAutotickangles: axName === 'angularaxis', + noTicklabelshift: true, + noTicklabelstandoff: true }); handleTickMarkDefaults(axIn, axOut, coerceAxis, {outerTicks: true}); diff --git a/src/plots/smith/layout_defaults.js b/src/plots/smith/layout_defaults.js index b536c6f85ea..17c8ac71b3e 100644 --- a/src/plots/smith/layout_defaults.js +++ b/src/plots/smith/layout_defaults.js @@ -90,6 +90,8 @@ function handleDefaults(contIn, contOut, coerce, opts) { handleTickLabelDefaults(axIn, axOut, coerceAxis, axOut.type, { noAutotickangles: true, + noTicklabelshift: true, + noTicklabelstandoff: true, noTicklabelstep: true, noAng: !isRealAxis, noExp: true, diff --git a/src/plots/ternary/layout_defaults.js b/src/plots/ternary/layout_defaults.js index 4a59fc0d43b..4afece35e7c 100644 --- a/src/plots/ternary/layout_defaults.js +++ b/src/plots/ternary/layout_defaults.js @@ -91,7 +91,11 @@ function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut handleTickValueDefaults(containerIn, containerOut, coerce, 'linear'); handlePrefixSuffixDefaults(containerIn, containerOut, coerce, 'linear'); - handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', { noAutotickangles: true }); + handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', { + noAutotickangles: true, + noTicklabelshift: true, + noTicklabelstandoff: true + }); handleTickMarkDefaults(containerIn, containerOut, coerce, { outerTicks: true }); diff --git a/src/traces/carpet/ab_defaults.js b/src/traces/carpet/ab_defaults.js index 58fe6e2baf6..48895a4c242 100644 --- a/src/traces/carpet/ab_defaults.js +++ b/src/traces/carpet/ab_defaults.js @@ -31,6 +31,8 @@ function mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor) { var defaultOptions = { noAutotickangles: true, + noTicklabelshift: true, + noTicklabelstandoff: true, noTicklabelstep: true, tickfont: 'x', id: axLetter + 'axis', diff --git a/src/traces/indicator/defaults.js b/src/traces/indicator/defaults.js index ff8db452640..3da64f53514 100644 --- a/src/traces/indicator/defaults.js +++ b/src/traces/indicator/defaults.js @@ -132,7 +132,9 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var opts = { font: layout.font, noAutotickangles: true, - outerTicks: true + outerTicks: true, + noTicklabelshift: true, + noTicklabelstandoff: true }; handleTickValueDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear'); handlePrefixSuffixDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear', opts); diff --git a/test/image/baselines/zzz_ticklabelshift_ticklabelstandoff.png b/test/image/baselines/zzz_ticklabelshift_ticklabelstandoff.png new file mode 100644 index 00000000000..67316c0fed7 Binary files /dev/null and b/test/image/baselines/zzz_ticklabelshift_ticklabelstandoff.png differ diff --git a/test/image/mocks/zzz_ticklabelshift_ticklabelstandoff.json b/test/image/mocks/zzz_ticklabelshift_ticklabelstandoff.json new file mode 100644 index 00000000000..a6fab3d9041 --- /dev/null +++ b/test/image/mocks/zzz_ticklabelshift_ticklabelstandoff.json @@ -0,0 +1,158 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": ["20-12-20", "21-01-20"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": ["20-10", "21-05-15"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": ["20", "23"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": ["20-12-20", "21-01-20"], + "y": [1e-1, 1e+6] + }], + "layout": { + "xaxis": { + "minor": { "showgrid": true }, + "anchor": "y", + "domain": [0, 0.475], + "type": "date", + "ticklabelmode": "period", + "ticklabelstandoff": 20, + "ticklabelshift": 20, + "side": "bottom", + "ticks": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis": { + "minor": { "showgrid": true }, + "anchor": "x", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "log", + "side": "left", + "ticks": "inside", + "ticklabelposition": "outside top", + "ticklabelstandoff": 20, + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis2": { + "minor": { "showgrid": true }, + "anchor": "y2", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "date", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside left", + "ticklabelstandoff": 20, + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis2": { + "minor": { "showgrid": true }, + "anchor": "x2", + "domain": [0, 0.475], + "type": "log", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "ticklabelstandoff": 20, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "xaxis3": { + "minor": { "showgrid": true }, + "anchor": "y3", + "domain": [0.525, 1], + "type": "date", + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside right", + "ticklabelstandoff": 20, + "ticklabelshift": 40, + "tickfont": { "size": 20 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis3": { + "minor": { "showgrid": true }, + "anchor": "x3", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "log", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "ticklabelstandoff": 10, + "ticklabelshift": -10, + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis4": { + "minor": { "showgrid": true }, + "anchor": "y4", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "date", + "ticklabelmode": "period", + "side": "top", + "ticks": "outside", + "ticklabelposition": "outside", + "ticklabelstandoff": -10, + "ticklabelshift": 15, + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis4": { + "minor": { "showgrid": true }, + "anchor": "x4", + "domain": [0.525, 1], + "type": "log", + "side": "left", + "ticks": "outside", + "ticklabelposition": "outside bottom", + "tickangle": 30, + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false + } +} diff --git a/test/plot-schema.json b/test/plot-schema.json index fd2b1659e37..e2beeacfc70 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -15154,6 +15154,18 @@ "inside bottom" ] }, + "ticklabelshift": { + "description": "Shifts the tick labels by the specified number of pixels in parallel to the axis. Positive values move the labels in the positive direction of the axis.", + "dflt": 0, + "editType": "ticks", + "valType": "integer" + }, + "ticklabelstandoff": { + "description": "Sets the standoff distance (in px) between the axis tick labels and their default position. A positive `ticklabelstandoff` moves the labels farther away from the plot area if `ticklabelposition` is *outside*, and deeper into the plot area if `ticklabelposition` is *inside*. A negative `ticklabelstandoff` works in the opposite direction, moving outside ticks towards the plot area and inside ticks towards the outside. If the negative value is large enough, inside ticks can even end up outside and vice versa.", + "dflt": 0, + "editType": "ticks", + "valType": "integer" + }, "ticklabelstep": { "description": "Sets the spacing between tick labels as compared to the spacing between ticks. A value of 1 (default) means each tick gets a label. A value of 2 means shows every 2nd label. A larger value n means only every nth tick is labeled. `tick0` determines which labels are shown. Not implemented for axes with `type` *log* or *multicategory*, or when `tickmode` is *array*.", "dflt": 1, @@ -16463,6 +16475,18 @@ "inside bottom" ] }, + "ticklabelshift": { + "description": "Shifts the tick labels by the specified number of pixels in parallel to the axis. Positive values move the labels in the positive direction of the axis.", + "dflt": 0, + "editType": "ticks", + "valType": "integer" + }, + "ticklabelstandoff": { + "description": "Sets the standoff distance (in px) between the axis tick labels and their default position. A positive `ticklabelstandoff` moves the labels farther away from the plot area if `ticklabelposition` is *outside*, and deeper into the plot area if `ticklabelposition` is *inside*. A negative `ticklabelstandoff` works in the opposite direction, moving outside ticks towards the plot area and inside ticks towards the outside. If the negative value is large enough, inside ticks can even end up outside and vice versa.", + "dflt": 0, + "editType": "ticks", + "valType": "integer" + }, "ticklabelstep": { "description": "Sets the spacing between tick labels as compared to the spacing between ticks. A value of 1 (default) means each tick gets a label. A value of 2 means shows every 2nd label. A larger value n means only every nth tick is labeled. `tick0` determines which labels are shown. Not implemented for axes with `type` *log* or *multicategory*, or when `tickmode` is *array*.", "dflt": 1,