diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index d6ed0a60d43..8272bd0ae2e 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -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', @@ -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), @@ -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$/)) { @@ -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 @@ -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) { @@ -2500,86 +2496,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 - // but can't call layoutStyles for initial autosize - else if(fullLayout.autosize !== 'initial') { - delete(aobj.autosize); - fullLayout.autosize = gd.layout.autosize = true; - } - - Plots.sanitizeMargins(fullLayout); - - return aobj; -} - // ------------------------------------------------------- // makePlotFramework: Create the plot container and axes // ------------------------------------------------------- @@ -2599,13 +2515,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 diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 06b4ab0913c..063ef3fff5a 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -47,11 +47,16 @@ module.exports = { autosize: { valType: 'enumerated', role: 'info', - // TODO: better handling of 'initial' - values: [true, false, 'initial'], + values: [false, true], + 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: { diff --git a/src/plots/plots.js b/src/plots/plots.js index d091511bffe..342f7d3a5a3 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -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() { gd.changed = oldchanged; resolve(gd); - } + }); }, 100); }); }; @@ -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 || {}; var oldFullData = gd._fullData || [], newFullData = gd._fullData = [], @@ -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, + 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; @@ -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 @@ -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); // can't quite figure out how to get rid of this... each axis needs @@ -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'); @@ -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); coerce('paper_bgcolor'); @@ -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'); + + // 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 { + 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; diff --git a/test/image/baselines/ternary_simple.png b/test/image/baselines/ternary_simple.png index c6d5869de40..111ead3bf5f 100644 Binary files a/test/image/baselines/ternary_simple.png and b/test/image/baselines/ternary_simple.png differ diff --git a/test/image/mocks/ternary_simple.json b/test/image/mocks/ternary_simple.json index ea1d78ff2a3..0f092db4a96 100644 --- a/test/image/mocks/ternary_simple.json +++ b/test/image/mocks/ternary_simple.json @@ -46,7 +46,6 @@ "bgcolor": "#eee" }, "height": 450, - "width": 700, - "autosize": true + "width": 700 } } diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 7c92bbad4d0..618d35d076e 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -786,4 +786,29 @@ describe('Test plot api', function() { expect(gd.data[1].contours).toBeUndefined(); }); }); + + describe('Plotly.newPlot', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should respect layout.width and layout.height', function(done) { + + // See issue https://github.com/plotly/plotly.js/issues/537 + var data = [{ + x: [1, 2], + y: [1, 2] + }]; + + Plotly.plot(gd, data).then(function() { + Plotly.newPlot(gd, data, { height: 50 }).then(function() { + expect(gd._fullLayout.height).toBe(50); + }).then(done); + }); + }); + }); }); diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index 32466c1266f..ac6ee12d1f1 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -70,6 +70,8 @@ describe('Plotly.toImage', function() { subplotMock.layout.width = 700; Plotly.plot(gd, subplotMock.data, subplotMock.layout).then(function(gd) { + expect(gd.layout.height).toBe(600); + expect(gd.layout.width).toBe(700); return Plotly.toImage(gd); }).then(function(url) { return new Promise(function(resolve) {