diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js index 394ba8fc87c..7e9d0227bca 100644 --- a/src/plots/cartesian/autorange.js +++ b/src/plots/cartesian/autorange.js @@ -14,7 +14,9 @@ var Lib = require('../../lib'); var FP_SAFE = require('../../constants/numerical').FP_SAFE; var Registry = require('../../registry'); -var getFromId = require('./axis_ids').getFromId; +var axIds = require('./axis_ids'); +var getFromId = axIds.getFromId; +var isLinked = axIds.isLinked; module.exports = { getAutoRange: getAutoRange, @@ -56,8 +58,9 @@ function getAutoRange(gd, ax) { var i, j; var newRange = []; - var getPadMin = makePadFn(ax, 0); - var getPadMax = makePadFn(ax, 1); + var fullLayout = gd._fullLayout; + var getPadMin = makePadFn(fullLayout, ax, 0); + var getPadMax = makePadFn(fullLayout, ax, 1); var extremes = concatExtremes(gd, ax); var minArray = extremes.min; var maxArray = extremes.max; @@ -202,13 +205,15 @@ function calcBreaksLength(ax, v0, v1) { * calculate the pixel padding for ax._min and ax._max entries with * optional extrapad as 5% of the total axis length */ -function makePadFn(ax, max) { +function makePadFn(fullLayout, ax, max) { // 5% padding for points that specify extrapad: true var extrappad = 0.05 * ax._length; + var anchorAxis = ax._anchorAxis || {}; + if( (ax.ticklabelposition || '').indexOf('inside') !== -1 || - ((ax._anchorAxis || {}).ticklabelposition || '').indexOf('inside') !== -1 + (anchorAxis.ticklabelposition || '').indexOf('inside') !== -1 ) { var axReverse = ax.autorange === 'reversed'; if(!axReverse) { @@ -218,8 +223,12 @@ function makePadFn(ax, max) { if(axReverse) max = !max; } - var A = padInsideLabelsOnAnchorAxis(ax, max); - var B = padInsideLabelsOnThisAxis(ax, max); + var A = 0; + var B = 0; + if(!isLinked(fullLayout, ax._id)) { + A = padInsideLabelsOnAnchorAxis(ax, max); + B = padInsideLabelsOnThisAxis(ax, max); + } var zero = Math.max(A, B); extrappad = Math.max(zero, extrappad); @@ -273,7 +282,7 @@ function padInsideLabelsOnThisAxis(ax, max) { function padInsideLabelsOnAnchorAxis(ax, max) { var pad = 0; - var anchorAxis = (ax._anchorAxis || {}); + var anchorAxis = ax._anchorAxis || {}; if((anchorAxis.ticklabelposition || '').indexOf('inside') !== -1) { // increase padding to make more room for inside tick labels of the counter axis if(( diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 143b11d8fdb..601ce279b26 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -56,6 +56,10 @@ axes.setConvert = require('./set_convert'); var autoType = require('./axis_autotype'); var axisIds = require('./axis_ids'); +var idSort = axisIds.idSort; +var isLinked = axisIds.isLinked; + +// tight coupling to chart studio axes.id2name = axisIds.id2name; axes.name2id = axisIds.name2id; axes.cleanId = axisIds.cleanId; @@ -2907,7 +2911,7 @@ axes.drawZeroLine = function(gd, ax, opts) { // If several zerolines enter at the same time we will sort once per, // but generally this should be a minimal overhead. opts.layer.selectAll('path').sort(function(da, db) { - return axisIds.idSort(da.id, db.id); + return idSort(da.id, db.id); }); }); @@ -3205,7 +3209,8 @@ axes.drawLabels = function(gd, ax, opts) { var anchorAx = ax._anchorAxis; if( anchorAx && anchorAx.autorange && - (ax.ticklabelposition || '').indexOf('inside') !== -1 + (ax.ticklabelposition || '').indexOf('inside') !== -1 && + !isLinked(fullLayout, ax._id) ) { if(!fullLayout._insideTickLabelsAutorange) { fullLayout._insideTickLabelsAutorange = {}; diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index db2c3705275..fd245f6c2cf 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -136,3 +136,19 @@ exports.ref2id = function(ar) { // return the axis ID. Otherwise it returns false. return (/^[xyz]/.test(ar)) ? ar.split(' ')[0] : false; }; + +function isFound(axId, list) { + if(list && list.length) { + for(var i = 0; i < list.length; i++) { + if(list[i][axId]) return true; + } + } + return false; +} + +exports.isLinked = function(fullLayout, axId) { + return ( + isFound(axId, fullLayout._axisMatchGroups) || + isFound(axId, fullLayout._axisConstraintGroups) + ); +}; diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js index 90ead3b020c..66cdfd9e07b 100644 --- a/src/plots/cartesian/constraints.js +++ b/src/plots/cartesian/constraints.js @@ -565,8 +565,8 @@ exports.enforce = function enforce(gd) { // *are* expanding to the full domain var outerMin = rangeCenter - halfRange * factor * 1.0001; var outerMax = rangeCenter + halfRange * factor * 1.0001; - var getPadMin = autorange.makePadFn(ax, 0); - var getPadMax = autorange.makePadFn(ax, 1); + var getPadMin = autorange.makePadFn(fullLayout, ax, 0); + var getPadMax = autorange.makePadFn(fullLayout, ax, 1); updateDomain(ax, factor); var m = Math.abs(ax._m); diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 5e808e148e1..b137e92d656 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -525,7 +525,10 @@ module.exports = { 'top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*.', 'Similarly', 'left or right has no effect on y axes or when `ticklabelmode` is set to *period*.', - 'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.' + 'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.', + 'When used on axes linked by `matches` or `scaleanchor`,', + 'no extra padding for inside labels would be added by autorange,', + 'so that the scales could match.' ].join(' ') }, mirror: { diff --git a/test/image/baselines/axes_chain_scaleanchor_matches2_inside-ticklabels.png b/test/image/baselines/axes_chain_scaleanchor_matches2_inside-ticklabels.png new file mode 100644 index 00000000000..2f9ac8cb2ab Binary files /dev/null and b/test/image/baselines/axes_chain_scaleanchor_matches2_inside-ticklabels.png differ diff --git a/test/image/baselines/axes_chain_scaleanchor_matches_inside-ticklabels.png b/test/image/baselines/axes_chain_scaleanchor_matches_inside-ticklabels.png new file mode 100644 index 00000000000..267ff1ec670 Binary files /dev/null and b/test/image/baselines/axes_chain_scaleanchor_matches_inside-ticklabels.png differ diff --git a/test/image/baselines/ticklabelposition-3.png b/test/image/baselines/ticklabelposition-3.png index 5c9d187fb62..a1af77385e1 100644 Binary files a/test/image/baselines/ticklabelposition-3.png and b/test/image/baselines/ticklabelposition-3.png differ diff --git a/test/image/mocks/axes_chain_scaleanchor_matches2_inside-ticklabels.json b/test/image/mocks/axes_chain_scaleanchor_matches2_inside-ticklabels.json new file mode 100644 index 00000000000..165b2836c6d --- /dev/null +++ b/test/image/mocks/axes_chain_scaleanchor_matches2_inside-ticklabels.json @@ -0,0 +1,82 @@ +{ + "data": [ + {"y":[1,2], "marker": {"color": "red"}}, + {"y":[1,2],"xaxis":"x2","yaxis":"y2", "marker": {"color": "red"}}, + {"y":[1,2],"xaxis":"x3","yaxis":"y3", "marker": {"color": "red"}}, + {"y":[1,2],"xaxis":"x4","yaxis":"y4", "marker": {"color": "red"}}, + {"y":[1,2],"xaxis":"x5","yaxis":"y5", "marker": {"color": "green"}}, + {"y":[1,2],"xaxis":"x6","yaxis":"y6", "marker": {"color": "green"}}, + {"y":[1,2],"xaxis":"x7","yaxis":"y7", "marker": {"color": "green"}}, + {"y":[1,2],"xaxis":"x8","yaxis":"y8", "marker": {"color": "green"}}, + {"y":[1,2],"xaxis":"x9","yaxis":"y9", "marker": {"color": "blue"}}, + {"y":[1,2],"xaxis":"x10","yaxis":"y10", "marker": {"color": "blue"}}, + {"y":[1,2],"xaxis":"x11","yaxis":"y11", "marker": {"color": "blue"}}, + {"y":[1,2],"xaxis":"x12","yaxis":"y12", "marker": {"color": "blue"}}, + {"y":[1,2],"xaxis":"x13","yaxis":"y13", "marker": {"color": "black"}}, + {"y":[1,2],"xaxis":"x14","yaxis":"y14", "marker": {"color": "black"}}, + {"y":[1,2],"xaxis":"x15","yaxis":"y15", "marker": {"color": "black"}}, + {"y":[1,2],"xaxis":"x16","yaxis":"y16", "marker": {"color": "black"}} + ], + "layout": { + "xaxis": {"ticklabelposition": "inside", "domain": [0, 0.2], "anchor": "y"}, + "yaxis": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "x", "matches": "x"}, + "xaxis2": {"ticklabelposition": "inside", "domain": [0.25, 0.45], "anchor": "y2", "scaleanchor": "y"}, + "yaxis2": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "x2", "matches": "x2"}, + "xaxis3": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "y3", "scaleanchor": "y2"}, + "yaxis3": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "x3", "matches": "x3"}, + "xaxis4": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "y4", "scaleanchor": "y3"}, + "yaxis4": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "x4", "matches": "x4"}, + + "xaxis5": {"ticklabelposition": "inside", "domain": [0, 0.2], "anchor": "y5", "constrain": "domain"}, + "yaxis5": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "x5", "matches": "x5", "constrain": "domain"}, + "xaxis6": {"ticklabelposition": "inside", "domain": [0.25, 0.45], "anchor": "y6", "scaleanchor": "y5", "constrain": "domain"}, + "yaxis6": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "x6", "matches": "x6", "constrain": "domain"}, + "xaxis7": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "y7", "scaleanchor": "y6", "constrain": "domain"}, + "yaxis7": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "x7", "matches": "x7", "constrain": "domain"}, + "xaxis8": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "y8", "scaleanchor": "y7", "constrain": "domain"}, + "yaxis8": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "x8", "matches": "x8", "constrain": "domain"}, + + "xaxis9": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "y9"}, + "yaxis9": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "x9", "matches": "x9"}, + "xaxis10": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "y10", "scaleanchor": "y9"}, + "yaxis10": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "x10", "matches": "x10"}, + "xaxis11": {"ticklabelposition": "inside", "domain": [0.5, 0.65], "anchor": "y11", "scaleanchor": "y10"}, + "yaxis11": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "x11", "matches": "x11"}, + "xaxis12": {"ticklabelposition": "inside", "domain": [0.75, 0.9], "anchor": "y12", "scaleanchor": "y11"}, + "yaxis12": {"ticklabelposition": "inside", "domain": [0.5, 0.7], "anchor": "x12", "matches": "x12"}, + + "xaxis13": {"ticklabelposition": "inside", "domain": [0, 0.15], "anchor": "y13", "constrain": "domain"}, + "yaxis13": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "x13", "matches": "x13", "constrain": "domain"}, + "xaxis14": {"ticklabelposition": "inside", "domain": [0.25, 0.4], "anchor": "y14", "scaleanchor": "y13", "constrain": "domain"}, + "yaxis14": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "x14", "matches": "x14", "constrain": "domain"}, + "xaxis15": {"ticklabelposition": "inside", "domain": [0.5, 0.65], "anchor": "y15", "scaleanchor": "y14", "constrain": "domain"}, + "yaxis15": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "x15", "matches": "x15", "constrain": "domain"}, + "xaxis16": {"ticklabelposition": "inside", "domain": [0.75, 0.9], "anchor": "y16", "scaleanchor": "y15", "constrain": "domain"}, + "yaxis16": {"ticklabelposition": "inside", "domain": [0.75, 0.95], "anchor": "x16", "matches": "x16", "constrain": "domain"}, + + "shapes": [ + {"x0": 0, "x1": 0.2, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.25, "x1": 0.45, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.5, "x1": 0.7, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.75, "x1": 0.95, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0, "x1": 0.2, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.25, "x1": 0.45, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.5, "x1": 0.7, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.75, "x1": 0.95, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0, "x1": 0.15, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.25, "x1": 0.4, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.5, "x1": 0.65, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.75, "x1": 0.9, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0, "x1": 0.15, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.25, "x1": 0.4, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.5, "x1": 0.65, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}, + {"x0": 0.75, "x1": 0.9, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}} + ], + "annotations": [{"x": 0, "y": 1, "xanchor": "left", "yanchor": "top", "xref": "paper", "yref": "paper", "showarrow": false, "align": "left", "text": "y matches same x, x scales to the previous y. Subplot aspect ratios compound"}], + + "width": 500, + "height": 500, + "margin": {"l": 50, "r": 0, "t": 0, "b": 50}, + "showlegend": false + } +} diff --git a/test/image/mocks/axes_chain_scaleanchor_matches_inside-ticklabels.json b/test/image/mocks/axes_chain_scaleanchor_matches_inside-ticklabels.json new file mode 100644 index 00000000000..3abf6a4c042 --- /dev/null +++ b/test/image/mocks/axes_chain_scaleanchor_matches_inside-ticklabels.json @@ -0,0 +1,26 @@ +{ + "data": [ + {"z": [[1, 2], [3, 4], [5, 6]], "type": "heatmap", "showscale": false}, + {"z": [[1, 2, 3], [6, 5, 4]], "type": "heatmap", "xaxis": "x2", "yaxis": "y2", "showscale": false}, + {"z": [[1, 6], [2, 5], [3, 4]], "type": "heatmap", "xaxis": "x3", "yaxis": "y3", "showscale": false}, + {"z": [[1, 2, 3], [4, 5, 6]], "type": "heatmap", "xaxis": "x4", "yaxis": "y4", "showscale": false} + ], + "layout": { + "xaxis": {"ticklabelposition": "inside", "domain": [0, 0.4], "constrain": "domain"}, + "yaxis": {"ticklabelposition": "inside", "domain": [0, 0.3], "constrain": "domain", "scaleanchor": "x", "title": {"text": "constrain domain"}}, + "xaxis2": {"ticklabelposition": "inside", "domain": [0.6, 1], "matches": "x", "anchor": "y2", "title": {"text": "<- each right subplot matches
the axes left and below
and all are constrained
to square bricks."}}, + "yaxis2": {"ticklabelposition": "inside", "domain": [0.2, 0.5], "matches": "y", "anchor": "x2"}, + "xaxis3": {"ticklabelposition": "inside", "domain": [0, 0.4], "constrain": "range", "anchor": "y3"}, + "yaxis3": {"ticklabelposition": "inside", "domain": [0.5, 0.8], "constrain": "range", "scaleanchor": "x3", "anchor": "x3", "title": {"text": "constrain range"}}, + "xaxis4": {"ticklabelposition": "inside", "domain": [0.6, 1], "matches": "x3", "anchor": "y4"}, + "yaxis4": {"ticklabelposition": "inside", "domain": [0.7, 1], "matches": "y3", "anchor": "x4"}, + "shapes": [ + {"x0": 0, "x1": 0.4, "y0": 0, "y1": 0.3, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}}, + {"x0": 0.6, "x1": 1, "y0": 0.2, "y1": 0.5, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}}, + {"x0": 0, "x1": 0.4, "y0": 0.5, "y1": 0.8, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}}, + {"x0": 0.6, "x1": 1, "y0": 0.7, "y1": 1, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}} + ], + "width": 700, + "height": 600 + } +} diff --git a/test/image/mocks/ticklabelposition-3.json b/test/image/mocks/ticklabelposition-3.json index 8d697d3e8a9..aa8c106b0e6 100644 --- a/test/image/mocks/ticklabelposition-3.json +++ b/test/image/mocks/ticklabelposition-3.json @@ -28,10 +28,15 @@ }, { "xaxis": "x3", "yaxis": "y3", - "type": "scatter", - "mode": "lines", - "x": [-100, 0, 100], - "y": [-100, 0, 100] + "type": "image", + "z": [ + [[255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255], [255, 0, 255]], + [[255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255], [255, 0, 255], [255, 0, 0]], + [[0, 255, 0], [0, 255, 255], [0, 0, 255], [255, 0, 255], [255, 0, 0], [255, 255, 0]], + [[0, 255, 255], [0, 0, 255], [255, 0, 255], [255, 0, 0], [255, 255, 0], [0, 255, 0]], + [[0, 0, 255], [255, 0, 255], [255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255]], + [[255, 0, 255], [255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255]] + ] }, { "xaxis": "x4", "yaxis": "y4", diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index e619d5dabe3..fecedd0d498 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -84,6 +84,8 @@ var list = [ 'axes_category_null', 'axes_chain_scaleanchor_matches', 'axes_chain_scaleanchor_matches2', + 'axes_chain_scaleanchor_matches_inside-ticklabels', + 'axes_chain_scaleanchor_matches2_inside-ticklabels', 'axes_custom-ticks_log-date', 'axes_enumerated_ticks', 'axes_free_default', @@ -1172,6 +1174,8 @@ figs['axes_category_categoryarray_truncated_tails'] = require('@mocks/axes_categ figs['axes_category_null'] = require('@mocks/axes_category_null'); figs['axes_chain_scaleanchor_matches'] = require('@mocks/axes_chain_scaleanchor_matches'); figs['axes_chain_scaleanchor_matches2'] = require('@mocks/axes_chain_scaleanchor_matches2'); +figs['axes_chain_scaleanchor_matches_inside-ticklabels'] = require('@mocks/axes_chain_scaleanchor_matches_inside-ticklabels'); +figs['axes_chain_scaleanchor_matches2_inside-ticklabels'] = require('@mocks/axes_chain_scaleanchor_matches2_inside-ticklabels'); figs['axes_custom-ticks_log-date'] = require('@mocks/axes_custom-ticks_log-date'); figs['axes_enumerated_ticks'] = require('@mocks/axes_enumerated_ticks'); figs['axes_free_default'] = require('@mocks/axes_free_default');