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

Remove internal option layout.autosize='initial' (Fixes #537) #577

Merged
merged 26 commits into from
Jun 9, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f1415f8
autosize: Remove unnecessary guard in plotAutoSize
n-riesco May 25, 2016
3a2c547
autosize: remove internal option 'initial'
n-riesco May 27, 2016
f9140b0
autosize: update baseline image `ternary_simple`
n-riesco May 27, 2016
206888b
test: make `toimage_test.js` stricter
n-riesco May 27, 2016
9c2a0d9
test: add test for issue #537
n-riesco May 27, 2016
53d4b90
test: autosize sets size of main SVG
n-riesco Jun 1, 2016
61aa240
test: supplyDefaults accepts plain objects
n-riesco Jun 1, 2016
a334254
autosize: supplyDefaults accepts plain objects
n-riesco Jun 1, 2016
7815efe
test: autosize, fillFrame and frameMargins
n-riesco Jun 1, 2016
7e95d93
autosize: respect config.autosizable
n-riesco Jun 1, 2016
370ad8b
test: config.autosizable
n-riesco Jun 1, 2016
46d7af5
autosize: supplyDefaults accepts plain objects
n-riesco Jun 1, 2016
94f450a
test: account for default autosizable being false
n-riesco Jun 1, 2016
b286469
Merge branch 'master' into remove-autosize-initial
n-riesco Jun 7, 2016
49bb2cf
autosize: use Lib.isPlotDiv(gd)
n-riesco Jun 7, 2016
0c316ba
autosize: expand description of config.autosizable
n-riesco Jun 7, 2016
3ed69b1
autosize: revert change of variable name
n-riesco Jun 7, 2016
862578e
autosize: fix condition to call sanitizeMargins
n-riesco Jun 7, 2016
172d81d
autosize: restore backwards-compatibility with v1
n-riesco Jun 7, 2016
42a3ed0
test: autosize is backwards-compatible
n-riesco Jun 7, 2016
de69b3d
test: supplyDefaults calls sanitizeMargins once
n-riesco Jun 7, 2016
ada5323
lib: ensure isPlotDiv accepts plain objects
n-riesco Jun 7, 2016
bc1a970
test: Lib.isPlotLib accepts plain objects
n-riesco Jun 7, 2016
6021c43
autosize: use Lib.isPlotDiv
n-riesco Jun 7, 2016
e822ff7
autosize: ensure sanitizeMargins is called once
n-riesco Jun 7, 2016
fbc01c3
autosize: change attribute valType to boolean
n-riesco Jun 9, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 15 additions & 105 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,6 @@ function plotPolar(gd, data, layout) {
if(layout) gd.layout = layout;
Plotly.micropolar.manager.fillLayout(gd);

if(gd._fullLayout.autosize === 'initial' && gd._context.autosizable) {
plotAutoSize(gd, {});
gd._fullLayout.autosize = layout.autosize = true;
}
// resize canvas
paperDiv.style({
width: gd._fullLayout.width + 'px',
Expand Down Expand Up @@ -2158,8 +2154,6 @@ Plotly.relayout = function relayout(gd, astr, val) {
return (fullLayout[axName] || {}).autorange;
}

var hw = ['height', 'width'];

// alter gd.layout
for(var ai in aobj) {
var p = Lib.nestedProperty(layout, ai),
Expand All @@ -2182,14 +2176,8 @@ Plotly.relayout = function relayout(gd, astr, val) {
// op and has no flag.
undoit[ai] = (pleaf === 'reverse') ? vi : p.get();

// check autosize or autorange vs size and range
if(hw.indexOf(ai) !== -1) {
doextra('autosize', false);
}
else if(ai === 'autosize') {
doextra(hw, undefined);
}
else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) {
// check autorange vs range
if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) {
doextra(ptrunk + '.autorange', false);
}
else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) {
Expand Down Expand Up @@ -2367,11 +2355,20 @@ Plotly.relayout = function relayout(gd, astr, val) {
Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]);
}

// calculate autosizing - if size hasn't changed,
// will remove h&w so we don't need to redraw
if(aobj.autosize) aobj = plotAutoSize(gd, aobj);
var oldWidth = gd._fullLayout.width,
oldHeight = gd._fullLayout.height;

if(aobj.height || aobj.width || aobj.autosize) docalc = true;
// coerce the updated layout
Plots.supplyDefaults(gd);

// calculate autosizing
if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, gd._fullLayout);

// avoid unnecessary redraws
var changed = aobj.height || aobj.width ||
(gd._fullLayout.width !== oldWidth) ||
(gd._fullLayout.height !== oldHeight);
if(changed) docalc = true;

// redraw
// first check if there's still anything to do
Expand All @@ -2392,7 +2389,6 @@ Plotly.relayout = function relayout(gd, astr, val) {
}
else if(ak.length) {
// if we didn't need to redraw entirely, just do the needed parts
Plots.supplyDefaults(gd);
fullLayout = gd._fullLayout;

if(dolegend) {
Expand Down Expand Up @@ -2501,85 +2497,6 @@ Plotly.purge = function purge(gd) {
return gd;
};

/**
* Reduce all reserved margin objects to a single required margin reservation.
*
* @param {Object} margins
* @returns {{left: number, right: number, bottom: number, top: number}}
*/
function calculateReservedMargins(margins) {
var resultingMargin = {left: 0, right: 0, bottom: 0, top: 0},
marginName;

if(margins) {
for(marginName in margins) {
if(margins.hasOwnProperty(marginName)) {
resultingMargin.left += margins[marginName].left || 0;
resultingMargin.right += margins[marginName].right || 0;
resultingMargin.bottom += margins[marginName].bottom || 0;
resultingMargin.top += margins[marginName].top || 0;
}
}
}
return resultingMargin;
}

function plotAutoSize(gd, aobj) {
var fullLayout = gd._fullLayout,
context = gd._context,
computedStyle;

var newHeight, newWidth;

gd.emit('plotly_autosize');

// embedded in an iframe - just take the full iframe size
// if we get to this point, with no aspect ratio restrictions
if(gd._context.fillFrame) {
newWidth = window.innerWidth;
newHeight = window.innerHeight;

// somehow we get a few extra px height sometimes...
// just hide it
document.body.style.overflow = 'hidden';
}
else if(isNumeric(context.frameMargins) && context.frameMargins > 0) {
var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins),
reservedWidth = reservedMargins.left + reservedMargins.right,
reservedHeight = reservedMargins.bottom + reservedMargins.top,
gdBB = fullLayout._container.node().getBoundingClientRect(),
factor = 1 - 2 * context.frameMargins;

newWidth = Math.round(factor * (gdBB.width - reservedWidth));
newHeight = Math.round(factor * (gdBB.height - reservedHeight));
}
else {
// plotly.js - let the developers do what they want, either
// provide height and width for the container div,
// specify size in layout, or take the defaults,
// but don't enforce any ratio restrictions
computedStyle = window.getComputedStyle(gd);
newHeight = parseFloat(computedStyle.height) || fullLayout.height;
newWidth = parseFloat(computedStyle.width) || fullLayout.width;
}

if(Math.abs(fullLayout.width - newWidth) > 1 ||
Math.abs(fullLayout.height - newHeight) > 1) {
fullLayout.height = gd.layout.height = newHeight;
fullLayout.width = gd.layout.width = newWidth;
}
// if there's no size change, update layout but
// delete the autosize attr so we don't redraw
else {
delete(aobj.autosize);
fullLayout.autosize = gd.layout.autosize = true;
}

Plots.sanitizeMargins(fullLayout);

return aobj;
}

// -------------------------------------------------------
// makePlotFramework: Create the plot container and axes
// -------------------------------------------------------
Expand All @@ -2599,13 +2516,6 @@ function makePlotFramework(gd) {
.classed('svg-container', true)
.style('position', 'relative');

// Initial autosize
if(fullLayout.autosize === 'initial') {
plotAutoSize(gd, {});
fullLayout.autosize = true;
gd.layout.autosize = true;
}

// Make the graph containers
// start fresh each time we get here, so we know the order comes out
// right, rather than enter/exit which can muck up the order
Expand Down
13 changes: 9 additions & 4 deletions src/plots/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ module.exports = {
autosize: {
valType: 'enumerated',
role: 'info',
// TODO: better handling of 'initial'
values: [true, false, 'initial'],
values: [false, true],
Copy link
Contributor

Choose a reason for hiding this comment

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

we should change the valType to 'boolean' and remove the values field.

dflt: false,
description: [
'Determines whether or not the dimensions of the figure are',
'computed as a function of the display size.'
'Determines whether or not a layout width or height',
'that has been left undefined by the user',
'is initialized on each relayout.',

'Note that, regardless of this attribute,',
'an undefined layout width or height',
'is always initialized on the first call to plot.'
].join(' ')
},
width: {
Expand Down
135 changes: 121 additions & 14 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,26 @@ plots.resize = function(gd) {
if(gd._redrawTimer) clearTimeout(gd._redrawTimer);

gd._redrawTimer = setTimeout(function() {
if((gd._fullLayout || {}).autosize) {
// autosizing doesn't count as a change that needs saving
var oldchanged = gd.changed;
// return if there is nothing to resize
if(gd.layout.width && gd.layout.height) {
resolve(gd);
return;
}

delete gd._fullLayout._initialAutoSizeIsDone;
if(!gd.layout.width) delete (gd._fullLayout || {}).width;
if(!gd.layout.height) delete (gd._fullLayout || {}).height;

// nor should it be included in the undo queue
gd.autoplay = true;
// autosizing doesn't count as a change that needs saving
var oldchanged = gd.changed;

Plotly.relayout(gd, { autosize: true });
// nor should it be included in the undo queue
gd.autoplay = true;

Plotly.plot(gd).then(function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

@n-riesco could you clarify why Plots.resize needs to go through Plotly.plot instead of Plotly.relayout now?

I think it has to do with fullLayout._initialAutoSizeIsDone, but I just want to make sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both Plotly.plot(gd) and Plotly.relayout(gd, { autosize: true }) would have the same effect in terms of resizing, but Plotly.relayout(gd, { autosize: true }) would have the side-effect of setting layout.autosize to true. But:

  • if layout.autosize was true, there is no need to set it to true again;
  • and if layout.autosize was false, both layout.width and layout.height would've already been initialised and setting layout.autosize to true would have no use.

Copy link
Contributor

Choose a reason for hiding this comment

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

Very clear. Thanks!

gd.changed = oldchanged;
resolve(gd);
}
});
}, 100);
});
};
Expand Down Expand Up @@ -455,7 +464,7 @@ plots.sendDataToCloud = function(gd) {
plots.supplyDefaults = function(gd) {
var oldFullLayout = gd._fullLayout || {},
newFullLayout = gd._fullLayout = {},
newLayout = gd.layout || {};
layout = gd.layout || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the newLayout nomenclature here.


var oldFullData = gd._fullData || [],
newFullData = gd._fullData = [],
Expand All @@ -468,7 +477,27 @@ plots.supplyDefaults = function(gd) {

// first fill in what we can of layout without looking at data
// because fullData needs a few things from layout
plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout);

if(oldFullLayout._initialAutoSizeIsDone) {
// coerce the updated layout while preserving width and height
var oldWidth = oldFullLayout.width,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this?

Doesn't plotAutoSize copy the calculated width and height tolayout.widthandlayout.height` respectively?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see that you deleted that block.

I guess one less place where we gd.layout is mutated is a good thing. 👍

Copy link
Contributor

@etpinard etpinard Jun 6, 2016

Choose a reason for hiding this comment

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

... but, it might be considered backward incompatible. I think we should wait for v2.0.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've restored backwards-compatibility and set width and height in gd.layout when layout.autosize is false.

oldHeight = oldFullLayout.height;

plots.supplyLayoutGlobalDefaults(layout, newFullLayout);

if(!layout.width) newFullLayout.width = oldWidth;
if(!layout.height) newFullLayout.height = oldHeight;
}
else {
// coerce the updated layout and autosize if needed
plots.supplyLayoutGlobalDefaults(layout, newFullLayout);

if(!layout.width || !layout.height) {
plots.plotAutoSize(gd, layout, newFullLayout);
}
}

newFullLayout._initialAutoSizeIsDone = true;

// keep track of how many traces are inputted
newFullLayout._dataLength = newData.length;
Expand Down Expand Up @@ -505,7 +534,7 @@ plots.supplyDefaults = function(gd) {
}

// finally, fill in the pieces of layout that may need to look at data
plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData);
plots.supplyLayoutModuleDefaults(layout, newFullLayout, newFullData);

// TODO remove in v2.0.0
// add has-plot-type refs to fullLayout for backward compatibility
Expand All @@ -522,6 +551,7 @@ plots.supplyDefaults = function(gd) {
// relink functions and _ attributes to promote consistency between plots
relinkPrivateKeys(newFullLayout, oldFullLayout);

// TODO may return a promise
plots.doAutoMargin(gd);
Copy link
Contributor

Choose a reason for hiding this comment

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

Making this gd._promises.push(plots.doAutoMargin(gd)); should do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried:

if(!Array.isArray(gd._promises)) gd._promises = [];
gd._promises.push(plots.doAutoMargin(gd));

but it caused a bunch of jasmine tests to fail.

I think this issue with doAutoMargin deserves its own PR, because I've seen other places where a promise is pushed into gd._promises and subsequently wiped out by a call to Plotly.plot (for example in Plotly.relayout there are cases where a call to Image.draw pushes a promise into gd._promises that subsequently gets wiped out by a call to Plotly.plot).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ha. Good to know. Thanks for the clarification. Yes, leaving this for a separate PR is fine.


// can't quite figure out how to get rid of this... each axis needs
Expand Down Expand Up @@ -730,11 +760,9 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
color: globalFont.color
});

var autosize = coerce('autosize',
(layoutIn.width && layoutIn.height) ? false : 'initial');
coerce('autosize');
coerce('width');
coerce('height');

coerce('margin.l');
coerce('margin.r');
coerce('margin.t');
Expand All @@ -743,7 +771,7 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
coerce('margin.autoexpand');

// called in plotAutoSize otherwise
if(autosize !== 'initial') plots.sanitizeMargins(layoutOut);
if(layoutOut.width && layoutOut.height) plots.sanitizeMargins(layoutOut);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still necessary?

Looks like layoutOut.width and layoutOut.height will now always be defined at this stage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch! That should be layoutIn.width and layoutIn.height.


coerce('paper_bgcolor');

Expand All @@ -752,6 +780,85 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
coerce('smith');
};

plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
var context = gd._context || {},
frameMargins = context.frameMargins,
newWidth,
newHeight;

if(typeof gd.emit === 'function') gd.emit('plotly_autosize');
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

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

i.e. that if(typeof gd.emit === 'function')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Otherwise I get errors in the console (perhaps some test are mocking gd with something other than a DOM element?). I will come back to you on this tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good call. Plots.supplyDefaults should work with plain objects.

Could we make this check more robust? I'd vote for using Lib.isPlotDiv.


// embedded in an iframe - just take the full iframe size
// if we get to this point, with no aspect ratio restrictions
if(context.fillFrame) {
newWidth = window.innerWidth;
newHeight = window.innerHeight;

// somehow we get a few extra px height sometimes...
// just hide it
document.body.style.overflow = 'hidden';
}
else if(isNumeric(frameMargins) && frameMargins > 0) {
var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins),
reservedWidth = reservedMargins.left + reservedMargins.right,
reservedHeight = reservedMargins.bottom + reservedMargins.top,
gdBB = fullLayout._container.node().getBoundingClientRect(),
factor = 1 - 2 * frameMargins;

newWidth = Math.round(factor * (gdBB.width - reservedWidth));
newHeight = Math.round(factor * (gdBB.height - reservedHeight));
}
else {
// plotly.js - let the developers do what they want, either
// provide height and width for the container div,
// specify size in layout, or take the defaults,
// but don't enforce any ratio restrictions
var computedStyle;
try {
Copy link
Contributor

@etpinard etpinard May 30, 2016

Choose a reason for hiding this comment

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

why do we need a try-catch here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same reason as above. I will come back to you with more details on this tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For example, here's a test that uses a mocked gd. Since gd isn't a DOM element the call to window.getComputedStyle(gd) throws a TypeError.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think @chriddyp and possibly others have good reasons for calling supplyDefaults outside the DOM so it does seem reasonable to support (and test) this behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've also noticed that restyle in master also throws an error here when tests like this use a mocked gd. Fortunately, although the console shows the exception, this happens in a promise that doesn't affect the test result.

Copy link
Contributor

@etpinard etpinard Jun 6, 2016

Choose a reason for hiding this comment

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

Good point. But I don't think we should worry about making Plotly.restyle not output errors when used with mocked gd.

computedStyle = window.getComputedStyle(gd);
} catch(err) {
computedStyle = {};
}
newWidth = parseFloat(computedStyle.width) || fullLayout.width;
newHeight = parseFloat(computedStyle.height) || fullLayout.height;
}

var widthHasChanged = !layout.width &&
(Math.abs(fullLayout.width - newWidth) > 1),
heightHasChanged = !layout.height &&
(Math.abs(fullLayout.height - newHeight) > 1);

if(heightHasChanged || widthHasChanged) {
if(widthHasChanged) fullLayout.width = newWidth;
if(heightHasChanged) fullLayout.height = newHeight;

plots.sanitizeMargins(fullLayout);
}
};

/**
* Reduce all reserved margin objects to a single required margin reservation.
*
* @param {Object} margins
* @returns {{left: number, right: number, bottom: number, top: number}}
*/
function calculateReservedMargins(margins) {
var resultingMargin = {left: 0, right: 0, bottom: 0, top: 0},
marginName;

if(margins) {
for(marginName in margins) {
if(margins.hasOwnProperty(marginName)) {
resultingMargin.left += margins[marginName].left || 0;
resultingMargin.right += margins[marginName].right || 0;
resultingMargin.bottom += margins[marginName].bottom || 0;
resultingMargin.top += margins[marginName].top || 0;
}
}
}
return resultingMargin;
}

plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) {
var i, _module;

Expand Down