diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index a051d511336..ceaa3492e00 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -136,6 +136,13 @@ module.exports = overrideAll({ tickvals: axesAttrs.tickvals, ticktext: axesAttrs.ticktext, ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), + ticklabeloverflow: extendFlat({}, axesAttrs.ticklabeloverflow, { + description: [ + 'Determines how we handle tick labels that would overflow either the graph div or the domain of the axis.', + 'The default value for inside tick labels is *hide past domain*.', + 'In other cases the default is *hide past div*.' + ].join(' ') + }), ticklabelposition: { valType: 'enumerated', values: [ diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index 670b799d950..0747e0e186e 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -42,7 +42,9 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) { coerce('bordercolor'); coerce('borderwidth'); coerce('bgcolor'); + var ticklabelposition = coerce('ticklabelposition'); + coerce('ticklabeloverflow', ticklabelposition.indexOf('inside') !== -1 ? 'hide past domain' : 'hide past div'); handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index cd6576b9b1e..d725b20b9b2 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -677,6 +677,7 @@ function mockColorBarAxis(gd, opts, zrange) { tickcolor: opts.tickcolor, showticklabels: opts.showticklabels, ticklabelposition: opts.ticklabelposition, + ticklabeloverflow: opts.ticklabeloverflow, tickfont: opts.tickfont, tickangle: opts.tickangle, tickformat: opts.tickformat, diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index f13d8371639..2829f4609e1 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -3020,8 +3020,6 @@ axes.drawLabels = function(gd, ax, opts) { } function positionLabels(s, angle) { - var isInside = insideTicklabelposition(ax); - s.each(function(d) { var thisLabel = d3.select(this); var mathjaxGroup = thisLabel.select('.text-math-group'); @@ -3049,12 +3047,10 @@ axes.drawLabels = function(gd, ax, opts) { 'text-anchor': anchor }); - if(isInside) { - thisText.style('opacity', 1); // visible + thisText.style('opacity', 1); // visible - if(ax._hideOutOfRangeInsideTickLabels) { - ax._hideOutOfRangeInsideTickLabels(); - } + if(ax._adjustTickLabelsOverflow) { + ax._adjustTickLabelsOverflow(); } } else { var mjWidth = Drawing.bBox(mathjaxGroup.node()).width; @@ -3064,63 +3060,73 @@ axes.drawLabels = function(gd, ax, opts) { }); } - ax._hideOutOfRangeInsideTickLabels = function() { - if(insideTicklabelposition(ax)) { + ax._adjustTickLabelsOverflow = function() { + var ticklabeloverflow = ax.ticklabeloverflow; + if(!ticklabeloverflow || ticklabeloverflow === 'allow') return; + + var hideOverflow = ticklabeloverflow.indexOf('hide') !== -1; + + var isX = ax._id.charAt(0) === 'x'; + // div positions + var p0 = 0; + var p1 = isX ? + gd._fullLayout.width : + gd._fullLayout.height; + + if(ticklabeloverflow.indexOf('domain') !== -1) { + // domain positions var rl = Lib.simpleMap(ax.range, ax.r2l); + p0 = ax.l2p(rl[0]) + ax._offset; + p1 = ax.l2p(rl[1]) + ax._offset; + } - // hide inside tick labels that go outside axis end points - var p0 = ax.l2p(rl[0]); - var p1 = ax.l2p(rl[1]); + var min = Math.min(p0, p1); + var max = Math.max(p0, p1); - var min = Math.min(p0, p1) + ax._offset; - var max = Math.max(p0, p1) + ax._offset; + var side = ax.side; - var side = ax.side; - var isX = ax._id.charAt(0) === 'x'; + var visibleLabelMin = Infinity; + var visibleLabelMax = -Infinity; - var visibleLabelMin = Infinity; - var visibleLabelMax = -Infinity; + tickLabels.each(function(d) { + var thisLabel = d3.select(this); + var mathjaxGroup = thisLabel.select('.text-math-group'); - tickLabels.each(function(d) { - var thisLabel = d3.select(this); - var mathjaxGroup = thisLabel.select('.text-math-group'); - - if(mathjaxGroup.empty()) { - var bb = Drawing.bBox(thisLabel.node()); - var hide = false; - if(isX) { - if(bb.right > max) hide = true; - else if(bb.left < min) hide = true; + if(mathjaxGroup.empty()) { + var bb = Drawing.bBox(thisLabel.node()); + var adjust = 0; + if(isX) { + if(bb.right > max) adjust = 1; + else if(bb.left < min) adjust = 1; + } else { + if(bb.bottom > max) adjust = 1; + else if(bb.top + (ax.tickangle ? 0 : d.fontSize / 4) < min) adjust = 1; + } + + var t = thisLabel.select('text'); + if(adjust) { + if(hideOverflow) t.style('opacity', 0); // hidden + } else { + t.style('opacity', 1); // visible + + if(side === 'bottom' || side === 'right') { + visibleLabelMin = Math.min(visibleLabelMin, isX ? bb.top : bb.left); } else { - if(bb.bottom > max) hide = true; - else if(bb.top + (ax.tickangle ? 0 : d.fontSize / 4) < min) hide = true; + visibleLabelMin = -Infinity; } - var t = thisLabel.select('text'); - if(hide) { - t.style('opacity', 0); // hidden + if(side === 'top' || side === 'left') { + visibleLabelMax = Math.max(visibleLabelMax, isX ? bb.bottom : bb.right); } else { - t.style('opacity', 1); // visible - - if(side === 'bottom' || side === 'right') { - visibleLabelMin = Math.min(visibleLabelMin, isX ? bb.top : bb.left); - } else { - visibleLabelMin = -Infinity; - } - - if(side === 'top' || side === 'left') { - visibleLabelMax = Math.max(visibleLabelMax, isX ? bb.bottom : bb.right); - } else { - visibleLabelMax = Infinity; - } + visibleLabelMax = Infinity; } - } // TODO: hide mathjax? - }); + } + } // TODO: hide mathjax? + }); - if(ax._anchorAxis) { - ax._anchorAxis._visibleLabelMin = visibleLabelMin; - ax._anchorAxis._visibleLabelMax = visibleLabelMax; - } + if(ax._anchorAxis) { + ax._anchorAxis._visibleLabelMin = visibleLabelMin; + ax._anchorAxis._visibleLabelMax = visibleLabelMax; } }; diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index de43db22e93..c68adbca305 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -56,8 +56,9 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, } } + var ticklabelposition = ''; if(!options.noTicklabelposition || axType === 'multicategory') { - Lib.coerce(containerIn, containerOut, { + ticklabelposition = Lib.coerce(containerIn, containerOut, { ticklabelposition: { valType: 'enumerated', dflt: 'outside', @@ -75,6 +76,17 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, }, 'ticklabelposition'); } + if(!options.noTicklabeloverflow) { + coerce('ticklabeloverflow', + ticklabelposition.indexOf('inside') !== -1 ? + 'hide past domain' : + axType === 'category' || + axType === 'multicategory' ? + 'allow' : + 'hide past div' + ); + } + setConvert(containerOut, layoutOut); var autorangeDflt = !containerOut.isValidRange(containerIn.range); diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 88a96672e34..fb8ffafcde2 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -495,6 +495,21 @@ module.exports = { 'so that the scales could match.' ].join(' ') }, + ticklabeloverflow: { + valType: 'enumerated', + values: [ + 'allow', + 'hide past div', + 'hide past domain' + ], + editType: 'calc', + description: [ + 'Determines how we handle tick labels that would overflow either the graph div or the domain of the axis.', + 'The default value for inside tick labels is *hide past domain*.', + 'Otherwise on *category* and *multicategory* axes the default is *allow*.', + 'In other cases the default is *hide past div*.' + ].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 3c60b004a51..86740ed858a 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -44,6 +44,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { noTickson: true, noTicklabelmode: true, noTicklabelposition: true, + noTicklabeloverflow: true, bgColor: options.bgColor, calendar: options.calendar }, diff --git a/src/plots/plots.js b/src/plots/plots.js index f58b0e9345d..a3789026460 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2076,14 +2076,14 @@ plots.doAutoMargin = function(gd) { } } - hideInsideTickLabels(gd); + refineTicks(gd); }; -function hideInsideTickLabels(gd) { +function refineTicks(gd) { var axList = axisIDs.list(gd, '', true); [ - '_hideOutOfRangeInsideTickLabels', + '_adjustTickLabelsOverflow', '_hideCounterAxisInsideTickLabels' ].forEach(function(k) { for(var i = 0; i < axList.length; i++) { diff --git a/src/traces/indicator/plot.js b/src/traces/indicator/plot.js index c39805d3258..759d182384b 100644 --- a/src/traces/indicator/plot.js +++ b/src/traces/indicator/plot.js @@ -394,6 +394,7 @@ function drawAngularGauge(gd, plotGroup, cd, opts) { ax.type = 'linear'; ax.range = trace.gauge.axis.range; ax._id = 'xangularaxis'; // or 'y', but I don't think this makes a difference here + ax.ticklabeloverflow = 'allow'; ax.setScale(); // 't'ick to 'g'eometric radians is used all over the place here diff --git a/test/image/baselines/point-selection2.png b/test/image/baselines/point-selection2.png index de39ff51820..508daf9b951 100644 Binary files a/test/image/baselines/point-selection2.png and b/test/image/baselines/point-selection2.png differ diff --git a/test/image/baselines/ticklabeloverflow-0.png b/test/image/baselines/ticklabeloverflow-0.png new file mode 100644 index 00000000000..ffa863b2e41 Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-0.png differ diff --git a/test/image/baselines/ticklabeloverflow-1.png b/test/image/baselines/ticklabeloverflow-1.png new file mode 100644 index 00000000000..379f7cb2b96 Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-1.png differ diff --git a/test/image/baselines/ticklabeloverflow-2.png b/test/image/baselines/ticklabeloverflow-2.png new file mode 100644 index 00000000000..caeba825370 Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-2.png differ diff --git a/test/image/baselines/ticklabeloverflow-3.png b/test/image/baselines/ticklabeloverflow-3.png new file mode 100644 index 00000000000..eb170168297 Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-3.png differ diff --git a/test/image/baselines/ticklabeloverflow-4.png b/test/image/baselines/ticklabeloverflow-4.png new file mode 100644 index 00000000000..8417d7e7bcc Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-4.png differ diff --git a/test/image/baselines/ticklabeloverflow-5.png b/test/image/baselines/ticklabeloverflow-5.png new file mode 100644 index 00000000000..e6ddaec03d2 Binary files /dev/null and b/test/image/baselines/ticklabeloverflow-5.png differ diff --git a/test/image/mocks/ticklabeloverflow-0.json b/test/image/mocks/ticklabeloverflow-0.json new file mode 100644 index 00000000000..83aafdccf61 --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-0.json @@ -0,0 +1,29 @@ +{ + "data": [{ + "x": [-100, 0, 100], + "y": [-100, 0, 100] + }], + "layout": { + "xaxis": { + "range": [-115, 105], + "tickformat": ".3f", + "ticklabeloverflow": "allow" + }, + "yaxis": { + "range": [-115, 105], + "tickangle": 90, + "tickformat": ".3f", + "ticklabeloverflow": "allow" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 300, + "height": 300, + "margin": { + "t": 15, + "b": 15, + "l": 15, + "r": 15 + } + } +} diff --git a/test/image/mocks/ticklabeloverflow-1.json b/test/image/mocks/ticklabeloverflow-1.json new file mode 100644 index 00000000000..bf1abe02c1b --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-1.json @@ -0,0 +1,29 @@ +{ + "data": [{ + "x": [-100, 0, 100], + "y": [-100, 0, 100] + }], + "layout": { + "xaxis": { + "range": [-115, 105], + "tickformat": ".3f", + "ticklabeloverflow": "hide past div" + }, + "yaxis": { + "range": [-115, 105], + "tickangle": 90, + "tickformat": ".3f", + "ticklabeloverflow": "hide past div" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 300, + "height": 300, + "margin": { + "t": 15, + "b": 15, + "l": 15, + "r": 15 + } + } +} diff --git a/test/image/mocks/ticklabeloverflow-2.json b/test/image/mocks/ticklabeloverflow-2.json new file mode 100644 index 00000000000..5bec33ddd73 --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-2.json @@ -0,0 +1,29 @@ +{ + "data": [{ + "x": [-100, 0, 100], + "y": [-100, 0, 100] + }], + "layout": { + "xaxis": { + "range": [-115, 105], + "tickformat": ".3f", + "ticklabeloverflow": "hide past domain" + }, + "yaxis": { + "range": [-115, 105], + "tickangle": 90, + "tickformat": ".3f", + "ticklabeloverflow": "hide past domain" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 300, + "height": 300, + "margin": { + "t": 30, + "b": 30, + "l": 30, + "r": 30 + } + } +} diff --git a/test/image/mocks/ticklabeloverflow-3.json b/test/image/mocks/ticklabeloverflow-3.json new file mode 100644 index 00000000000..4b6acad0247 --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-3.json @@ -0,0 +1,31 @@ +{ + "data": [{ + "colorbar": { + "dtick": 2, + "ticklen": 4, + "tickangle": 90, + "tickformat": ".4f", + "ticklabeloverflow": "allow" + }, + "type": "heatmap", + "x": [-100, 0, 100], + "y": [-100, 0, 100], + "z": [ + [0, 5, 10], + [5, 10, 0], + [10, 5, 0] + ] + }], + "layout": { + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 200, + "height": 300, + "margin": { + "t": 5, + "b": 5, + "l": 45, + "r": 45 + } + } +} diff --git a/test/image/mocks/ticklabeloverflow-4.json b/test/image/mocks/ticklabeloverflow-4.json new file mode 100644 index 00000000000..ceb7d7fa2e3 --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-4.json @@ -0,0 +1,31 @@ +{ + "data": [{ + "colorbar": { + "dtick": 2, + "ticklen": 4, + "tickangle": 90, + "tickformat": ".4f", + "ticklabeloverflow": "hide past div" + }, + "type": "heatmap", + "x": [-100, 0, 100], + "y": [-100, 0, 100], + "z": [ + [0, 5, 10], + [5, 10, 0], + [10, 5, 0] + ] + }], + "layout": { + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 200, + "height": 300, + "margin": { + "t": 5, + "b": 5, + "l": 45, + "r": 45 + } + } +} diff --git a/test/image/mocks/ticklabeloverflow-5.json b/test/image/mocks/ticklabeloverflow-5.json new file mode 100644 index 00000000000..e63cef3230b --- /dev/null +++ b/test/image/mocks/ticklabeloverflow-5.json @@ -0,0 +1,31 @@ +{ + "data": [{ + "colorbar": { + "dtick": 2, + "ticklen": 4, + "tickangle": 90, + "tickformat": ".4f", + "ticklabeloverflow": "hide past domain" + }, + "type": "heatmap", + "x": [-100, 0, 100], + "y": [-100, 0, 100], + "z": [ + [0, 5, 10], + [5, 10, 0], + [10, 5, 0] + ] + }], + "layout": { + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 200, + "height": 300, + "margin": { + "t": 30, + "b": 30, + "l": 45, + "r": 45 + } + } +} diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index 6df55c3c28f..d88479f99e6 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -964,6 +964,12 @@ var list = [ 'tick-percent', 'tickformat', 'tickformatstops', + 'ticklabeloverflow-0', + 'ticklabeloverflow-1', + 'ticklabeloverflow-2', + 'ticklabeloverflow-3', + 'ticklabeloverflow-4', + 'ticklabeloverflow-5', 'ticklabelposition-0', 'ticklabelposition-1', 'ticklabelposition-2', @@ -2057,6 +2063,12 @@ figs['tick-increment'] = require('@mocks/tick-increment'); figs['tick-percent'] = require('@mocks/tick-percent'); figs['tickformat'] = require('@mocks/tickformat'); figs['tickformatstops'] = require('@mocks/tickformatstops'); +figs['ticklabeloverflow-0'] = require('@mocks/ticklabeloverflow-0'); +figs['ticklabeloverflow-1'] = require('@mocks/ticklabeloverflow-1'); +figs['ticklabeloverflow-2'] = require('@mocks/ticklabeloverflow-2'); +figs['ticklabeloverflow-3'] = require('@mocks/ticklabeloverflow-3'); +figs['ticklabeloverflow-4'] = require('@mocks/ticklabeloverflow-4'); +figs['ticklabeloverflow-5'] = require('@mocks/ticklabeloverflow-5'); figs['ticklabelposition-0'] = require('@mocks/ticklabelposition-0'); figs['ticklabelposition-1'] = require('@mocks/ticklabelposition-1'); figs['ticklabelposition-2'] = require('@mocks/ticklabelposition-2');