From 3a953dbdb3be8995abd79fe6c56888fca1d1f2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 20 Nov 2018 17:27:58 -0500 Subject: [PATCH 01/10] coerce tickson, ... only on `type: 'category'` cartesian axes with visible ticks and/or grid lines. --- src/components/colorbar/draw.js | 1 + src/plots/cartesian/axis_defaults.js | 6 ++++++ src/plots/cartesian/layout_attributes.js | 13 +++++++++++++ src/plots/gl3d/layout/axis_defaults.js | 1 + 4 files changed, 21 insertions(+) diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index a87982e3628..77b90f8c8c2 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -198,6 +198,7 @@ module.exports = function draw(gd, id) { letter: 'y', font: fullLayout.font, noHover: true, + noTickson: true, calendar: fullLayout.calendar // not really necessary (yet?) }; diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index f3691bf8003..ac11666026b 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -28,6 +28,7 @@ var setConvert = require('./set_convert'); * outerTicks: boolean, should ticks default to outside? * showGrid: boolean, should gridlines be shown by default? * noHover: boolean, this axis doesn't support hover effects? + * noTickson: boolean, this axis doesn't support 'tickson' * data: the plot data, used to manage categories * bgColor: the plot background color, to calculate default gridline colors */ @@ -89,5 +90,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, if(options.automargin) coerce('automargin'); + if(!options.noTickson && + containerOut.type === 'category' && (containerOut.ticks || containerOut.showgrid)) { + coerce('tickson'); + } + return containerOut; }; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 404f023eb71..1350a04802f 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -304,6 +304,19 @@ module.exports = { 'the axis lines.' ].join(' ') }, + tickson: { + valType: 'enumerated', + values: ['labels', 'boundaries'], + role: 'info', + editType: 'ticks', + description: [ + 'Determines where ticks and grid lines are drawn with respect to their', + 'corresponding tick labels.', + 'Only has an effect for axes of `type` *category*.', + 'When set to *boundaries*, ticks and grid lines are drawn half a category', + 'to the left/bottom of labels.' + ].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 e28b1a74de4..4a9b7a9c844 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -50,6 +50,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { letter: axName[0], data: options.data, showGrid: true, + noTickson: true, bgColor: options.bgColor, calendar: options.calendar }, From 90c653b207e02e46e99b7e9b42c89570532f85d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 20 Nov 2018 17:28:31 -0500 Subject: [PATCH 02/10] adapt Axes.calcTicks output for tickson:'boundaries' --- src/plots/cartesian/axes.js | 36 +++++++++++++--- test/jasmine/tests/axes_test.js | 74 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 3f1464aa9fb..a9859f45663 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1610,14 +1610,41 @@ axes.drawOne = function(gd, ax, opts) { var subplotsWithAx = axes.getSubplots(gd, ax); var vals = ax._vals = axes.calcTicks(ax); - // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end - // The key case here is removing zero lines when the axis bound is zero - var valsClipped = ax._valsClipped = axes.clipEnds(ax, vals); if(!ax.visible) return; var transFn = axes.makeTransFn(ax); + // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end + // The key case here is removing zero lines when the axis bound is zero + var valsClipped; + var tickVals; + var gridVals; + + if(ax.tickson === 'boundaries') { + // draw ticks and grid lines 1/2 a 'category' to the left, + // add one item at axis tail + var valsBoundaries = vals.map(function(d) { + var d2 = Lib.extendFlat({}, d); + d2.x -= 0.5; + return d2; + }); + // not used for labels; no need to worry about the other tickTextObj keys + var d2 = Lib.extendFlat({}, vals[vals.length - 1]); + d2.x += 0.5; + valsBoundaries.push(d2); + + valsClipped = axes.clipEnds(ax, valsBoundaries); + tickVals = ax.ticks === 'inside' ? valsClipped : valsBoundaries; + gridVals = valsClipped; + } else { + valsClipped = axes.clipEnds(ax, vals); + tickVals = ax.ticks === 'inside' ? valsClipped : vals; + gridVals = valsClipped; + } + + ax._valsClipped = valsClipped; + if(!fullLayout._hasOnlyLargeSploms) { // keep track of which subplots (by main conteraxis) we've already // drawn grids for, so we don't overdraw overlaying subplots @@ -1637,7 +1664,7 @@ axes.drawOne = function(gd, ax, opts) { 'M' + counterAxis._offset + ',0h' + counterAxis._length; axes.drawGrid(gd, ax, { - vals: valsClipped, + vals: gridVals, layer: plotinfo.gridlayer.select('.' + axId), path: gridPath, transFn: transFn @@ -1652,7 +1679,6 @@ axes.drawOne = function(gd, ax, opts) { } var tickSigns = axes.getTickSigns(ax); - var tickVals = ax.ticks === 'inside' ? valsClipped : vals; var tickSubplots = []; if(ax.ticks) { diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 595b4012fb3..0d8a40c76d2 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -3099,6 +3099,80 @@ describe('Test axes', function() { .then(done); }); }); + + describe('*tickson*:', function() { + var gd; + beforeEach(function() { + gd = createGraphDiv(); + }); + afterEach(destroyGraphDiv); + + function getPositions(query) { + var pos = []; + d3.selectAll(query).each(function() { + pos.push(this.getBoundingClientRect().x); + }); + return pos; + } + + function _assert(msg, exp) { + var ticks = getPositions('path.xtick'); + var gridLines = getPositions('path.xgrid'); + var tickLabels = getPositions('.xtick > text'); + + expect(ticks).toBeCloseToArray(exp.ticks, 1, msg + '- ticks'); + expect(gridLines).toBeCloseToArray(exp.gridLines, 1, msg + '- grid lines'); + expect(tickLabels.length).toBe(exp.tickLabels.length, msg + '- # of tick labels'); + tickLabels.forEach(function(tl, i) { + expect(tl).toBeWithin(exp.tickLabels[i], 2, msg + '- tick label ' + i); + }); + } + + it('should respond to relayout', function(done) { + Plotly.plot(gd, [{ + x: ['a', 'b', 'c'], + y: [1, 2, 1] + }], { + xaxis: { + ticks: 'inside', + showgrid: true + } + }) + .then(function() { + _assert('on labels (defaults)', { + ticks: [110.75, 350, 589.25], + gridLines: [110.75, 350, 589.25], + tickLabels: [106.421, 345.671, 585.25] + }); + return Plotly.relayout(gd, 'xaxis.tickson', 'boundaries'); + }) + .then(function() { + _assert('inside on boundaries', { + ticks: [230.369, 469.619], // N.B. first and last tick are clipped + gridLines: [230.369, 469.619], + tickLabels: [106.421875, 345.671875, 585.25] + }); + return Plotly.relayout(gd, 'xaxis.ticks', 'outside'); + }) + .then(function() { + _assert('outside on boundaries', { + ticks: [-8.869, 230.369, 469.619, 708.869], + gridLines: [230.369, 469.619], + tickLabels: [106.421875, 345.671875, 585.25] + }); + return Plotly.restyle(gd, 'x', [[1, 2, 1]]); + }) + .then(function() { + _assert('fallback to *labels* on non-category axes', { + ticks: [110.75, 206.449, 302.149, 397.85, 493.549, 589.25], + gridLines: [110.75, 206.449, 302.149, 397.85, 493.549, 589.25], + tickLabels: [106.421, 197.121, 292.821, 388.521, 484.221, 584.921] + }); + }) + .catch(failTest) + .then(done); + }); + }); }); function getZoomInButton(gd) { From 668f33878efaa41613aea3297e323e8c4836e9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 20 Nov 2018 17:28:40 -0500 Subject: [PATCH 03/10] add tickson boundaries mock --- test/image/baselines/tickson_boundaries.png | Bin 0 -> 21132 bytes test/image/mocks/tickson_boundaries.json | 66 ++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 test/image/baselines/tickson_boundaries.png create mode 100644 test/image/mocks/tickson_boundaries.json diff --git a/test/image/baselines/tickson_boundaries.png b/test/image/baselines/tickson_boundaries.png new file mode 100644 index 0000000000000000000000000000000000000000..177b5b1c499c6ce380f3933957eba80459379135 GIT binary patch literal 21132 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 RzGaeQzp>e#^xbE#{x4{paTWjo literal 0 HcmV?d00001 diff --git a/test/image/mocks/tickson_boundaries.json b/test/image/mocks/tickson_boundaries.json new file mode 100644 index 00000000000..62e0e2a77ca --- /dev/null +++ b/test/image/mocks/tickson_boundaries.json @@ -0,0 +1,66 @@ +{ + "data": [ + { + "type": "box", + "x": ["day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2"], + "y": [0.2, 0.2, 0.6, 1, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3] + }, + { + "type": "box", + "x": ["day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2"], + "y": [0.1, 0.3, 0.1, 0.9, 0.6, 0.6, 0.9, 1, 0.3, 0.6, 0.8, 0.5] + }, + { + "type": "box", + "x": ["day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2"], + "y": [0.6, 0.7, 0.3, 0.6, 0, 0.5, 0.7, 0.9, 0.5, 0.8, 0.7, 0.2] + }, + { + "type": "bar", + "x": [1, 2, 1], + "y": ["apples", "bananas", "clementines"], + "orientation": "h", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "type": "bar", + "x": [1.3, 2.2, 0.8], + "y": ["apples", "bananas", "clementines"], + "orientation": "h", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "type": "bar", + "x": [3, 3.2, 1.8], + "y": ["apples", "bananas", "clementines"], + "orientation": "h", + "xaxis": "x2", + "yaxis": "y2" + } + ], + "layout": { + "boxmode": "group", + "grid": { + "rows": 2, + "columns": 1, + "pattern": "independent", + "ygap": 0.2 + }, + "xaxis": { + "ticks": "outside", + "tickson": "boundaries", + "gridcolor": "white", + "gridwidth": 4 + }, + "yaxis2": { + "ticks": "inside", + "tickson": "boundaries", + "gridcolor": "white", + "gridwidth": 4 + }, + "plot_bgcolor": "lightgrey", + "showlegend": false + } +} From 78ef22ce125429ae0339b355069ad29f6ce60dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 21 Nov 2018 18:05:03 -0500 Subject: [PATCH 04/10] add tickson dflt --- src/plots/cartesian/layout_attributes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 1350a04802f..1e44476b28e 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -308,6 +308,7 @@ module.exports = { valType: 'enumerated', values: ['labels', 'boundaries'], role: 'info', + dflt: 'labels', editType: 'ticks', description: [ 'Determines where ticks and grid lines are drawn with respect to their', From 78ff4d519086101126edb8c211c84965e0dc0f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 22 Nov 2018 16:14:11 -0500 Subject: [PATCH 05/10] don't standoff ax labels under tickons:'boundaries' --- src/plots/cartesian/axes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index a9859f45663..7a173cde4a7 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1946,8 +1946,9 @@ axes.makeTickPath = function(ax, shift, sgn) { axes.makeLabelFns = function(ax, shift, angle) { var axLetter = ax._id.charAt(0); var pad = (ax.linewidth || 1) / 2; + var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside'; - var labelStandoff = ax.ticks === 'outside' ? ax.ticklen : 0; + var labelStandoff = ticksOnOutsideLabels ? ax.ticklen : 0; var labelShift = 0; if(angle && ax.ticks === 'outside') { @@ -1956,7 +1957,7 @@ axes.makeLabelFns = function(ax, shift, angle) { labelShift = ax.ticklen * Math.sin(rad); } - if(ax.showticklabels && (ax.ticks === 'outside' || ax.showline)) { + if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) { labelStandoff += 0.2 * ax.tickfont.size; } From 6d0a3fc926a423061b954611def71abae858e73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 22 Nov 2018 16:15:11 -0500 Subject: [PATCH 06/10] fix dtick!==1 case, mv tickon:'boundaries' to formatCategory --- src/plots/cartesian/axes.js | 38 ++++++++++++++++++++++----------- test/jasmine/tests/axes_test.js | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7a173cde4a7..d5ae3cce6b7 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1163,6 +1163,20 @@ function formatCategory(ax, out) { var tt = ax._categories[Math.round(out.x)]; if(tt === undefined) tt = ''; out.text = String(tt); + + // Setup ticks and grid lines boundaries + // at 1/2 a 'category' to the left/bottom + if(ax.tickson === 'boundaries') { + var inbounds = function(v) { + var p = ax.l2p(v); + return p >= 0 && p <= ax._length ? v : null; + }; + + out.xbnd = [ + inbounds(out.x - 0.5), + inbounds(out.x + ax.dtick - 0.5) + ]; + } } function formatLinear(ax, out, hover, extraPrecision, hideexp) { @@ -1621,18 +1635,18 @@ axes.drawOne = function(gd, ax, opts) { var tickVals; var gridVals; - if(ax.tickson === 'boundaries') { - // draw ticks and grid lines 1/2 a 'category' to the left, - // add one item at axis tail - var valsBoundaries = vals.map(function(d) { - var d2 = Lib.extendFlat({}, d); - d2.x -= 0.5; - return d2; - }); - // not used for labels; no need to worry about the other tickTextObj keys - var d2 = Lib.extendFlat({}, vals[vals.length - 1]); - d2.x += 0.5; - valsBoundaries.push(d2); + if(ax.tickson === 'boundaries' && vals.length) { + // valsBoundaries is not used for labels; + // no need to worry about the other tickTextObj keys + var valsBoundaries = []; + var _push = function(d, bndIndex) { + var xb = d.xbnd[bndIndex]; + if(xb !== null) { + valsBoundaries.push(Lib.extendFlat({}, d, {x: xb})); + } + }; + for(i = 0; i < vals.length; i++) _push(vals[i], 0); + _push(vals[i - 1], 1); valsClipped = axes.clipEnds(ax, valsBoundaries); tickVals = ax.ticks === 'inside' ? valsClipped : valsBoundaries; diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 0d8a40c76d2..79b254914e0 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -3156,7 +3156,7 @@ describe('Test axes', function() { }) .then(function() { _assert('outside on boundaries', { - ticks: [-8.869, 230.369, 469.619, 708.869], + ticks: [230.369, 469.619], gridLines: [230.369, 469.619], tickLabels: [106.421875, 345.671875, 585.25] }); From 09ac230b9698b09bccc2b729aa81f6601ba84633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 22 Nov 2018 16:16:31 -0500 Subject: [PATCH 07/10] rotate labels 90deg when they overlap w/ tick-on-boundaries positions --- src/plots/cartesian/axes.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index d5ae3cce6b7..c1997c011a3 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2059,7 +2059,6 @@ axes.drawTicks = function(gd, ax, opts) { ticks.attr('transform', opts.transFn); }; - /** * Draw axis grid * @@ -2318,6 +2317,7 @@ axes.drawLabels = function(gd, ax, opts) { (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D') ) { var lbbArray = []; + var i; tickLabels.each(function(d) { var s = d3.select(this); @@ -2339,7 +2339,7 @@ axes.drawLabels = function(gd, ax, opts) { }); }); - for(var i = 0; i < lbbArray.length - 1; i++) { + for(i = 0; i < lbbArray.length - 1; i++) { if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { // any overlap at all - set 30 degrees autoangle = 30; @@ -2347,6 +2347,18 @@ axes.drawLabels = function(gd, ax, opts) { } } + if(ax.tickson === 'boundaries') { + for(i = 0; i < lbbArray.length; i++) { + if( + (vals[i].xl !== null && (lbbArray[i].left - ax.l2p(vals[i].xbnd[0])) < 2) || + (vals[i].xr !== null && (ax.l2p(vals[i].xbnd[1]) - lbbArray[i].right) < 2) + ) { + autoangle = 90; + break; + } + } + } + if(autoangle) { var tickspacing = Math.abs( (vals[vals.length - 1].x - vals[0].x) * ax._m From b05f8fbfe16eefe9893ec84e6972b81df65fce9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 22 Nov 2018 16:17:50 -0500 Subject: [PATCH 08/10] test a few more things in tickson_boundaries mock --- test/image/baselines/tickson_boundaries.png | Bin 21132 -> 31759 bytes test/image/mocks/tickson_boundaries.json | 42 +++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/test/image/baselines/tickson_boundaries.png b/test/image/baselines/tickson_boundaries.png index 177b5b1c499c6ce380f3933957eba80459379135..242ee5752302c9635f425226d9f4b7b5a93a9402 100644 GIT binary patch literal 31759 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 RzGaeQzp>e#^xbE#{x4{paTWjo diff --git a/test/image/mocks/tickson_boundaries.json b/test/image/mocks/tickson_boundaries.json index 62e0e2a77ca..d9cce6bf2b6 100644 --- a/test/image/mocks/tickson_boundaries.json +++ b/test/image/mocks/tickson_boundaries.json @@ -15,6 +15,7 @@ "x": ["day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2"], "y": [0.6, 0.7, 0.3, 0.6, 0, 0.5, 0.7, 0.9, 0.5, 0.8, 0.7, 0.2] }, + { "type": "bar", "x": [1, 2, 1], @@ -38,12 +39,31 @@ "orientation": "h", "xaxis": "x2", "yaxis": "y2" + }, + + { + "type": "bar", + "name": "with dtick !== 1", + "x": ["a", "b", "c", "d", "e", "f", "g", "h"], + "y": [1, 2, 1, 2, 1, 3, 4, 1], + "xaxis": "x3", + "yaxis": "y3" + }, + + { + "mode": "markers", + "marker": {"symbol": "square"}, + "name": "with overlapping tick labels", + "x": ["A very long title", "short", "Another very long title"], + "y": [1, 4, 2], + "xaxis": "x4", + "yaxis": "y4" } ], "layout": { "boxmode": "group", "grid": { - "rows": 2, + "rows": 4, "columns": 1, "pattern": "independent", "ygap": 0.2 @@ -60,7 +80,25 @@ "gridcolor": "white", "gridwidth": 4 }, + "xaxis3": { + "ticks": "inside", + "tickson": "boundaries", + "gridcolor": "white", + "gridwidth": 4, + "dtick": 2 + }, + "xaxis4": { + "domain": [0.22, 0.78], + "ticks": "outside", + "ticklen": 20, + "tickson": "boundaries", + "gridcolor": "white", + "gridwidth": 4 + }, "plot_bgcolor": "lightgrey", - "showlegend": false + "showlegend": false, + "width": 500, + "height": 800, + "margin": {"b": 140} } } From bae37be268e043ec26a3879db8bb899b87046814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 26 Nov 2018 14:58:05 -0500 Subject: [PATCH 09/10] move 'maxFontSize' and 'autoangle' to fixLabelOverlaps scope ... as they are not used in the Axes.drawLabels scope --- src/plots/cartesian/axes.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index c1997c011a3..42e545da592 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2191,8 +2191,6 @@ axes.drawLabels = function(gd, ax, opts) { var tickLabels = opts.layer.selectAll('g.' + cls) .data(ax.showticklabels ? vals : [], makeDataFn(ax)); - var maxFontSize = 0; - var autoangle = 0; var labelsReady = []; tickLabels.enter().append('g') @@ -2227,10 +2225,6 @@ axes.drawLabels = function(gd, ax, opts) { tickLabels.exit().remove(); - tickLabels.each(function(d) { - maxFontSize = Math.max(maxFontSize, d.fontSize); - }); - ax._tickLabels = tickLabels; // TODO ?? @@ -2313,9 +2307,10 @@ axes.drawLabels = function(gd, ax, opts) { // check for auto-angling if x labels overlap // don't auto-angle at all for log axes with // base and digit format - if(axLetter === 'x' && !isNumeric(ax.tickangle) && + if(vals.length && axLetter === 'x' && !isNumeric(ax.tickangle) && (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D') ) { + var maxFontSize = 0; var lbbArray = []; var i; @@ -2324,6 +2319,8 @@ axes.drawLabels = function(gd, ax, opts) { var thisLabel = s.select('.text-math-group'); if(thisLabel.empty()) thisLabel = s.select('text'); + maxFontSize = Math.max(maxFontSize, d.fontSize); + var x = ax.l2p(d.x); var bb = Drawing.bBox(thisLabel.node()); @@ -2339,6 +2336,7 @@ axes.drawLabels = function(gd, ax, opts) { }); }); + var autoangle = 0; for(i = 0; i < lbbArray.length - 1; i++) { if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { // any overlap at all - set 30 degrees From eeb90559c9066ff484ba4347aa922e84b5aa9eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 26 Nov 2018 14:59:31 -0500 Subject: [PATCH 10/10] split overlap algo based on tickson + use tickwidth for extra pad --- src/plots/cartesian/axes.js | 34 +++++++------ test/jasmine/tests/axes_test.js | 89 ++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 34 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 42e545da592..e827667aa72 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2337,33 +2337,37 @@ axes.drawLabels = function(gd, ax, opts) { }); var autoangle = 0; - for(i = 0; i < lbbArray.length - 1; i++) { - if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { - // any overlap at all - set 30 degrees - autoangle = 30; - break; - } - } if(ax.tickson === 'boundaries') { + var gap = 2; + if(ax.ticks) gap += ax.tickwidth / 2; + for(i = 0; i < lbbArray.length; i++) { + var xbnd = vals[i].xbnd; + var lbb = lbbArray[i]; if( - (vals[i].xl !== null && (lbbArray[i].left - ax.l2p(vals[i].xbnd[0])) < 2) || - (vals[i].xr !== null && (ax.l2p(vals[i].xbnd[1]) - lbbArray[i].right) < 2) + (xbnd[0] !== null && (lbb.left - ax.l2p(xbnd[0])) < gap) || + (xbnd[1] !== null && (ax.l2p(xbnd[1]) - lbb.right) < gap) ) { autoangle = 90; break; } } + } else { + var vLen = vals.length; + var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1); + var fitBetweenTicks = tickSpacing < maxFontSize * 2.5; + + // any overlap at all - set 30 degrees or 90 degrees + for(i = 0; i < lbbArray.length - 1; i++) { + if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { + autoangle = fitBetweenTicks ? 90 : 30; + break; + } + } } if(autoangle) { - var tickspacing = Math.abs( - (vals[vals.length - 1].x - vals[0].x) * ax._m - ) / (vals.length - 1); - if(tickspacing < maxFontSize * 2.5) { - autoangle = 90; - } positionLabels(tickLabels, autoangle); } ax._lastangle = autoangle; diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 79b254914e0..3097f4d4c3e 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -3102,33 +3102,35 @@ describe('Test axes', function() { describe('*tickson*:', function() { var gd; + beforeEach(function() { gd = createGraphDiv(); }); + afterEach(destroyGraphDiv); - function getPositions(query) { - var pos = []; - d3.selectAll(query).each(function() { - pos.push(this.getBoundingClientRect().x); - }); - return pos; - } + it('should respond to relayout', function(done) { + function getPositions(query) { + var pos = []; + d3.selectAll(query).each(function() { + pos.push(this.getBoundingClientRect().x); + }); + return pos; + } - function _assert(msg, exp) { - var ticks = getPositions('path.xtick'); - var gridLines = getPositions('path.xgrid'); - var tickLabels = getPositions('.xtick > text'); + function _assert(msg, exp) { + var ticks = getPositions('path.xtick'); + var gridLines = getPositions('path.xgrid'); + var tickLabels = getPositions('.xtick > text'); - expect(ticks).toBeCloseToArray(exp.ticks, 1, msg + '- ticks'); - expect(gridLines).toBeCloseToArray(exp.gridLines, 1, msg + '- grid lines'); - expect(tickLabels.length).toBe(exp.tickLabels.length, msg + '- # of tick labels'); - tickLabels.forEach(function(tl, i) { - expect(tl).toBeWithin(exp.tickLabels[i], 2, msg + '- tick label ' + i); - }); - } + expect(ticks).toBeCloseToArray(exp.ticks, 1, msg + '- ticks'); + expect(gridLines).toBeCloseToArray(exp.gridLines, 1, msg + '- grid lines'); + expect(tickLabels.length).toBe(exp.tickLabels.length, msg + '- # of tick labels'); + tickLabels.forEach(function(tl, i) { + expect(tl).toBeWithin(exp.tickLabels[i], 2, msg + '- tick label ' + i); + }); + } - it('should respond to relayout', function(done) { Plotly.plot(gd, [{ x: ['a', 'b', 'c'], y: [1, 2, 1] @@ -3172,6 +3174,55 @@ describe('Test axes', function() { .catch(failTest) .then(done); }); + + it('should rotate labels to avoid overlaps', function(done) { + function _assert(msg, exp) { + var tickLabels = d3.selectAll('.xtick > text'); + + expect(tickLabels.size()).toBe(exp.angle.length, msg + ' - # of tick labels'); + + tickLabels.each(function(_, i) { + var t = d3.select(this).attr('transform'); + var rotate = (t.split('rotate(')[1] || '').split(')')[0]; + var angle = rotate.split(',')[0]; + expect(Number(angle)).toBe(exp.angle[i], msg + ' - node ' + i); + }); + } + + Plotly.plot(gd, [{ + x: ['A very long title', 'short', 'Another very long title'], + y: [1, 4, 2] + }], { + xaxis: { + domain: [0.22, 0.78], + tickson: 'boundaries', + ticks: 'outside' + }, + width: 500, + height: 500 + }) + .then(function() { + _assert('base - rotated', { + angle: [90, 90, 90] + }); + + return Plotly.relayout(gd, 'xaxis.range', [-0.5, 1.5]); + }) + .then(function() { + _assert('narrower range - unrotated', { + angle: [0, 0] + }); + + return Plotly.relayout(gd, 'xaxis.tickwidth', 10); + }) + .then(function() { + _assert('narrow range / wide ticks - rotated', { + angle: [90, 90] + }); + }) + .catch(failTest) + .then(done); + }); }); });