Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional options for automargin property #6193

Merged
31 changes: 31 additions & 0 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2622,6 +2622,8 @@ axes.drawOne = function(gd, ax, opts) {
rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax);
}

keepSelectedAutoMargin(ax.automargin, push);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So keepSelectedAutoMargin mutates the push variable.
It was rather difficult to figure out what this step does here.
How about refactoring like this:

        if(typeof automargin === 'string') filterPush(push, ax.automargin);

On another note, wondering if mutating push should be performed earlier in the process so that the value in mirrorPush would be accurate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point of using a mirror if the result would still be cut off. So there is no need to filter the mirrorPush.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think it's correct to filter mirrorPush as well. The whole mirrorPush construction is inside the if(ax.automargin) block - implying that if you disable automargin, we're not going to push the margins for mirror axes regardless of whether they end up cut off. Therefore you should be able to choose which directions this applies to just as you can the regular axis automargin.


Plots.autoMargin(gd, axAutoMarginID(ax), push);
Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush);
Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
Expand All @@ -2636,6 +2638,35 @@ axes.drawOne = function(gd, ax, opts) {
return Lib.syncOrAsync(seq);
};

function keepSelectedAutoMargin(automargin, push) {
if(typeof automargin === 'boolean') return push;

var keepMargin = [];
var mapping = {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
width: ['x', 'r', 'l'],
height: ['y', 't', 'b'],
right: ['r'],
left: ['l'],
top: ['t'],
bottom: ['b']
};

Object.keys(mapping).forEach(function(key) {
if(automargin.includes(key)) {
nickmelnikov82 marked this conversation as resolved.
Show resolved Hide resolved
mapping[key].forEach(function(item) {
keepMargin.push(item);
});
}
});

Object.keys(push).forEach(function(key) {
if(key.length !== 2 && !keepMargin.includes(key)) {
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
push[key] = 0;
}
});
return push;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return push;

}

function getBoundaryVals(ax, vals) {
var out = [];
var i;
Expand Down
9 changes: 8 additions & 1 deletion src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,14 @@ module.exports = {
description: 'Determines whether or not the tick labels are drawn.'
},
automargin: {
valType: 'boolean',
valType: 'enumerated',
values: [
true, false, 'height', 'width',
'top', 'bottom', 'left', 'right',
'top left', 'top width', 'top right',
'left height', 'right height',
'bottom left', 'bottom width', 'bottom right',
],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a flaglist:

valType: 'flaglist',
flags: ['height', 'width', 'left', 'right', 'top', 'bottom'],
extras: [true, false]

That way the order doesn't matter (you use + - bottom+left and left+bottom are both valid), and users could even do bottom+left+right which would mean the same thing as bottom+width

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there pre-existing instances of "flaglist or boolean" in the schema? I want to make sure the Python codegen will not choke on this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flaglist does not support boolean parameters

if(typeof v !== 'string') {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK that's pretty good evidence that we don't currently have any flaglists with non-string extras 😉
We could allow this in coerce pretty easily by pushing the extras test up above that non-string short-circuit.

@nicolaskruchten your concern is warranted, plotly.py also includes the string requirement up front. But I really do think this attribute would be better as a flaglist, I'll be happy to modify the Python validator to allow non-string extras.

I suppose the alternative would be to make this a new attribute like automargindirections and leave automargin as a boolean, but that seems a bit cumbersome...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with tweaking Plotly.py for this 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the schema.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK that's pretty good evidence that we don't currently have any flaglists with non-string extras wink We could allow this in coerce pretty easily by pushing the extras test up above that non-string short-circuit.

@nicolaskruchten your concern is warranted, plotly.py also includes the string requirement up front. But I really do think this attribute would be better as a flaglist, I'll be happy to modify the Python validator to allow non-string extras.

I suppose the alternative would be to make this a new attribute like automargindirections and leave automargin as a boolean, but that seems a bit cumbersome...

Having a separate attribute could be safer for Python, R, Scala, etc. APIs.
It would be nice to possibly test this at plotly.py level before merging this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on a plotly.py PR right now... will post shortly.

dflt: false,
editType: 'ticks',
description: [
Expand Down
126 changes: 126 additions & 0 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
40 changes: 38 additions & 2 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9717,7 +9717,25 @@
"description": "Determines whether long tick labels automatically grow the figure margins.",
"dflt": false,
"editType": "ticks",
"valType": "boolean"
"valType": "enumerated",
"values": [
true,
false,
"height",
"width",
"top",
"bottom",
"left",
"right",
"top left",
"top width",
"top right",
"left height",
"right height",
"bottom left",
"bottom width",
"bottom right"
]
},
"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*.",
Expand Down Expand Up @@ -10956,7 +10974,25 @@
"description": "Determines whether long tick labels automatically grow the figure margins.",
"dflt": false,
"editType": "ticks",
"valType": "boolean"
"valType": "enumerated",
"values": [
true,
false,
"height",
"width",
"top",
"bottom",
"left",
"right",
"top left",
"top width",
"top right",
"left height",
"right height",
"bottom left",
"bottom width",
"bottom right"
]
},
"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*.",
Expand Down