diff --git a/draftlogs/6193_add.md b/draftlogs/6193_add.md new file mode 100644 index 00000000000..5486f67d04b --- /dev/null +++ b/draftlogs/6193_add.md @@ -0,0 +1 @@ + - Add flaglist options including "left", "right", "top", "bottom", "width" and "height" to control the direction of `automargin` on cartesian axes [[#6193](https://github.com/plotly/plotly.js/pull/6193)] \ No newline at end of file diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 95705c8c8a3..731b16bb631 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -215,14 +215,14 @@ exports.valObjectMeta = { requiredOpts: ['flags'], otherOpts: ['dflt', 'extras', 'arrayOk'], coerceFunction: function(v, propOut, dflt, opts) { - if(typeof v !== 'string') { - propOut.set(dflt); - return; - } if((opts.extras || []).indexOf(v) !== -1) { propOut.set(v); return; } + if(typeof v !== 'string') { + propOut.set(dflt); + return; + } var vParts = v.split('+'); var i = 0; while(i < vParts.length) { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 83c93e5c8e4..a53d42cd8fa 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -39,6 +39,14 @@ var GRID_PATH = { K: 'gridline', L: 'path' }; var MINORGRID_PATH = { K: 'minor-gridline', L: 'path' }; var TICK_PATH = { K: 'tick', L: 'path' }; var TICK_TEXT = { K: 'tick', L: 'text' }; +var MARGIN_MAPPING = { + width: ['x', 'r', 'l', 'xl', 'xr'], + height: ['y', 't', 'b', 'yt', 'yb'], + right: ['r', 'xr'], + left: ['l', 'xl'], + top: ['t', 'yt'], + bottom: ['b', 'yb'] +}; var alignmentConstants = require('../../constants/alignment'); var MID_SHIFT = alignmentConstants.MID_SHIFT; @@ -2622,6 +2630,11 @@ axes.drawOne = function(gd, ax, opts) { rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax); } + if(typeof ax.automargin === 'string') { + filterPush(push, ax.automargin); + filterPush(mirrorPush, ax.automargin); + } + Plots.autoMargin(gd, axAutoMarginID(ax), push); Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush); Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush); @@ -2636,6 +2649,23 @@ axes.drawOne = function(gd, ax, opts) { return Lib.syncOrAsync(seq); }; +function filterPush(push, automargin) { + if(!push) return; + + var keepMargin = Object.keys(MARGIN_MAPPING).reduce(function(data, nextKey) { + if(automargin.indexOf(nextKey) !== -1) { + MARGIN_MAPPING[nextKey].forEach(function(key) { data[key] = 1;}); + } + return data; + }, {}); + Object.keys(push).forEach(function(key) { + if(!keepMargin[key]) { + if(key.length === 1) push[key] = 0; + else delete push[key]; + } + }); +} + function getBoundaryVals(ax, vals) { var out = []; var i; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 9810aca3370..a693fb4cb76 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -625,7 +625,9 @@ module.exports = { description: 'Determines whether or not the tick labels are drawn.' }, automargin: { - valType: 'boolean', + valType: 'flaglist', + flags: ['height', 'width', 'left', 'right', 'top', 'bottom'], + extras: [true, false], dflt: false, editType: 'ticks', description: [ diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 9033adb6b40..019c6351dae 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -4225,6 +4225,132 @@ describe('Test axes', function() { .then(done, done.fail); }); + it('should handle partial automargin', function(done) { + var initialSize; + + function assertSize(msg, actual, exp) { + for(var k in exp) { + var parts = exp[k].split('|'); + var op = parts[0]; + + var method = { + '=': 'toBe', + grew: 'toBeGreaterThan', + }[op]; + + var val = initialSize[k]; + var msgk = msg + ' ' + k + (parts[1] ? ' |' + parts[1] : ''); + var args = op === '~=' ? [val, 1.1, msgk] : [val, msgk, '']; + + expect(actual[k])[method](args[0], args[1], args[2]); + } + } + + function check(msg, relayoutObj, exp) { + return function() { + return Plotly.relayout(gd, relayoutObj).then(function() { + var gs = Lib.extendDeep({}, gd._fullLayout._size); + assertSize(msg, gs, exp); + }); + }; + } + + Plotly.newPlot(gd, [{ + x: [ + 'short label 1', 'loooooong label 1', + 'short label 2', 'loooooong label 2', + 'short label 3', 'loooooong label 3', + 'short label 4', 'loooooongloooooongloooooong label 4', + 'short label 5', 'loooooong label 5' + ], + y: [ + 'short label 1', 'loooooong label 1', + 'short label 2', 'loooooong label 2', + 'short label 3', 'loooooong label 3', + 'short label 4', 'loooooong label 4', + 'short label 5', 'loooooong label 5' + ] + }], { + margin: {l: 0, r: 0, b: 0, t: 0}, + width: 600, height: 600 + }) + .then(function() { + expect(gd._fullLayout.xaxis._tickAngles.xtick).toBe(30); + + var gs = gd._fullLayout._size; + initialSize = Lib.extendDeep({}, gs); + }) + .then(check('automargin y', {'yaxis.automargin': true, 'yaxis.tickangle': 30, 'yaxis.ticklen': 30}, { + t: 'grew', l: 'grew', + b: '=', r: '=' + })) + .then(check('automargin not left', {'yaxis.automargin': 'right+height'}, { + t: 'grew', l: '=', + b: '=', r: '=' + })) + .then(check('automargin keep left height', {'yaxis.automargin': 'left+height'}, { + t: 'grew', l: 'grew', + b: '=', r: '=' + })) + .then(check('automargin keep bottom right', {'yaxis.automargin': 'bottom+right'}, { + t: '=', l: '=', + b: '=', r: '=' + })) + .then(check('automargin keep height', {'yaxis.automargin': 'height'}, { + t: 'grew', l: '=', + b: '=', r: '=' + })) + .then(check('automargin keep top', {'yaxis.automargin': 'top'}, { + t: 'grew', l: '=', + b: '=', r: '=' + })) + .then(check('automargin not top', {'yaxis.automargin': 'bottom+width'}, { + t: '=', l: 'grew', + b: '=', r: '=' + })) + .then(check('automargin keep left', {'yaxis.automargin': 'left'}, { + t: '=', l: 'grew', + b: '=', r: '=' + })) + .then(check('automargin keep width', {'yaxis.automargin': 'width'}, { + t: '=', l: 'grew', + b: '=', r: '=' + })) + .then(check('automargin x', {'xaxis.automargin': true, 'yaxis.automargin': false}, { + t: '=', l: '=', + b: 'grew', r: 'grew' + })) + .then(check('automargin not bottom', {'xaxis.automargin': 'top+width'}, { + t: '=', l: '=', + b: '=', r: 'grew' + })) + .then(check('automargin keep right', {'xaxis.automargin': 'right'}, { + t: '=', l: '=', + b: '=', r: 'grew' + })) + .then(check('automargin keep bottom', {'xaxis.automargin': 'bottom'}, { + t: '=', l: '=', + b: 'grew', r: '=' + })) + .then(check('automargin keep top right', {'xaxis.automargin': 'top+right'}, { + t: '=', l: '=', + b: '=', r: 'grew' + })) + .then(check('automargin keep top left', {'xaxis.automargin': 'top+left'}, { + t: '=', l: '=', + b: '=', r: '=' + })) + .then(check('automargin keep bottom left', {'xaxis.automargin': 'bottom+left'}, { + t: '=', l: '=', + b: 'grew', r: '=' + })) + .then(check('turn off automargin', {'xaxis.automargin': false, 'yaxis.automargin': false}, { + t: '=', l: '=', + b: '=', r: '=' + })) + .then(done, done.fail); + }); + it('should handle cases with free+mirror axes', function(done) { Plotly.newPlot(gd, [{ y: [1, 2, 1] diff --git a/test/plot-schema.json b/test/plot-schema.json index 28dcb2f43ce..e293afda4dc 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -9717,7 +9717,19 @@ "description": "Determines whether long tick labels automatically grow the figure margins.", "dflt": false, "editType": "ticks", - "valType": "boolean" + "extras": [ + true, + false + ], + "flags": [ + "height", + "width", + "left", + "right", + "top", + "bottom" + ], + "valType": "flaglist" }, "autorange": { "description": "Determines whether or not the range of this axis is computed in relation to the input data. See `rangemode` for more info. If `range` is provided, then `autorange` is set to *false*.", @@ -10956,7 +10968,19 @@ "description": "Determines whether long tick labels automatically grow the figure margins.", "dflt": false, "editType": "ticks", - "valType": "boolean" + "extras": [ + true, + false + ], + "flags": [ + "height", + "width", + "left", + "right", + "top", + "bottom" + ], + "valType": "flaglist" }, "autorange": { "description": "Determines whether or not the range of this axis is computed in relation to the input data. See `rangemode` for more info. If `range` is provided, then `autorange` is set to *false*.",