diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index e950d614426..2bda2d7eabc 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -105,7 +105,8 @@ exports.plot = function(gd, data, layout, config) { // hook class for plots main container (in case of plotly.js // this won't be #embedded-graph or .js-tab-contents) - d3.select(gd).classed('js-plotly-plot', true); + d3.select(gd).classed('js-plotly-plot', true) + .style('position', 'relative'); // off-screen getBoundingClientRect testing space, // in #js-plotly-tester (and stored as Drawing.tester) @@ -244,8 +245,6 @@ exports.plot = function(gd, data, layout, config) { 'position': 'absolute', 'top': 0, 'left': 0, - 'width': '100%', - 'height': '100%', 'overflow': 'visible', 'pointer-events': 'none' }); @@ -538,8 +537,7 @@ function plotLegacyPolar(gd, data, layout) { var paperDiv = plotContainer.selectAll('.svg-container') .data([0]); paperDiv.enter().append('div') - .classed('svg-container', true) - .style('position', 'relative'); + .classed('svg-container', true); // empty it everytime for now paperDiv.html(''); @@ -3744,8 +3742,7 @@ function makePlotFramework(gd) { // Make the svg container fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]); fullLayout._paperdiv.enter().append('div') - .classed('svg-container', true) - .style('position', 'relative'); + .classed('svg-container', true); // Make the graph containers // start fresh each time we get here, so we know the order comes out diff --git a/test/jasmine/tests/config_test.js b/test/jasmine/tests/config_test.js index d414bd33593..cdfaf2d5d7c 100644 --- a/test/jasmine/tests/config_test.js +++ b/test/jasmine/tests/config_test.js @@ -551,230 +551,232 @@ describe('config argument', function() { }); }); - describe('responsive figure', function() { - var gd; - var data = [{type: 'scatter', x: [1, 2, 3, 4], y: [5, 10, 2, 8]}]; - var width = 960; - var height = 800; - - var parent, elWidth, elHeight; - - beforeEach(function() { - viewport.set(width, height); - - // Prepare a parent container that fills the viewport - parent = document.createElement('div'); - parent.style.width = '100vw'; - parent.style.height = '100vh'; - parent.style.position = 'fixed'; - parent.style.top = '0'; - parent.style.left = '0'; - }); - - afterEach(function() { - Plotly.purge(gd); // Needed to remove all event listeners - document.body.removeChild(parent); - viewport.reset(); - }); + ['scatter', 'scattergl'].forEach(function(traceType) { + describe('responsive ' + traceType + ' trace', function() { + var gd; + var data = [{type: traceType, x: [1, 2, 3, 4], y: [5, 10, 2, 8]}]; + var width = 960; + var height = 800; + + var parent, elWidth, elHeight; + + beforeEach(function() { + viewport.set(width, height); + + // Prepare a parent container that fills the viewport + parent = document.createElement('div'); + parent.style.width = '100vw'; + parent.style.height = '100vh'; + parent.style.position = 'fixed'; + parent.style.top = '0'; + parent.style.left = '0'; + }); - function checkLayoutSize(width, height) { - expect(gd._fullLayout.width).toBe(width); - expect(gd._fullLayout.height).toBe(height); - } + afterEach(function() { + Plotly.purge(gd); // Needed to remove all event listeners + document.body.removeChild(parent); + viewport.reset(); + }); - function checkElementsSize(nodeList, width, height) { - var i; - for(i = 0; i < nodeList.length; i++) { - var domRect = nodeList[i].getBoundingClientRect(); - expect(domRect.width).toBe(width); - expect(domRect.height).toBe(height); - expect(+nodeList[i].getAttribute('width')).toBe(width); - expect(+nodeList[i].getAttribute('height')).toBe(height); + function checkLayoutSize(width, height) { + expect(gd._fullLayout.width).toBe(width); + expect(gd._fullLayout.height).toBe(height); } - } - - function testResponsive() { - checkLayoutSize(elWidth, elHeight); - viewport.set(width / 2, height / 2); - return Promise.resolve() - .then(delay(RESIZE_DELAY)) - .then(function() { - checkLayoutSize(elWidth / 2, elHeight / 2); + function checkElementsSize(nodeList, width, height) { + var i; + for(i = 0; i < nodeList.length; i++) { + var domRect = nodeList[i].getBoundingClientRect(); + expect(domRect.width).toBe(width); + expect(domRect.height).toBe(height); + expect(+nodeList[i].getAttribute('width')).toBe(width); + expect(+nodeList[i].getAttribute('height')).toBe(height); + } + } - var mainSvgs = document.getElementsByClassName('main-svg'); - checkElementsSize(mainSvgs, elWidth / 2, elHeight / 2); + function testResponsive() { + checkLayoutSize(elWidth, elHeight); + viewport.set(width / 2, height / 2); - var canvases = document.getElementsByTagName('canvas'); - checkElementsSize(canvases, elWidth / 2, elHeight / 2); + return Promise.resolve() + .then(delay(RESIZE_DELAY)) + .then(function() { + checkLayoutSize(elWidth / 2, elHeight / 2); - }) - .catch(failTest); - } + var mainSvgs = document.getElementsByClassName('main-svg'); + checkElementsSize(mainSvgs, elWidth / 2, elHeight / 2); - function fillParent(numRows, numCols, cb) { - elWidth = width / numCols, elHeight = height / numRows; + var canvases = document.getElementsByTagName('canvas'); + checkElementsSize(canvases, elWidth / 2, elHeight / 2); - // Fill parent - for(var i = 0; i < (numCols * numRows); i++) { - var col = document.createElement('div'); - col.style.height = '100%'; - col.style.width = '100%'; - if(typeof(cb) === typeof(Function)) cb.call(col, i); - parent.appendChild(col); + }) + .catch(failTest); } - document.body.appendChild(parent); - gd = parent.childNodes[0]; - } - it('@flaky should resize when the viewport width/height changes', function(done) { - fillParent(1, 1); - Plotly.plot(gd, data, {}, {responsive: true}) - .then(testResponsive) - .then(done); - }); - - it('@flaky should still be responsive if the plot is edited', function(done) { - fillParent(1, 1); - Plotly.plot(gd, data, {}, {responsive: true}) - .then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);}) - .then(testResponsive) - .then(done); - }); + function fillParent(numRows, numCols, cb) { + elWidth = width / numCols, elHeight = height / numRows; + + // Fill parent + for(var i = 0; i < (numCols * numRows); i++) { + var col = document.createElement('div'); + col.style.height = '100%'; + col.style.width = '100%'; + if(typeof(cb) === typeof(Function)) cb.call(col, i); + parent.appendChild(col); + } + document.body.appendChild(parent); + gd = parent.childNodes[0]; + } - it('@flaky should still be responsive if the plot is purged and replotted', function(done) { - fillParent(1, 1); - Plotly.plot(gd, data, {}, {responsive: true}) - .then(function() {return Plotly.newPlot(gd, data, {}, {responsive: true});}) - .then(testResponsive) - .then(done); - }); + it('@flaky should resize when the viewport width/height changes', function(done) { + fillParent(1, 1); + Plotly.plot(gd, data, {}, {responsive: true}) + .then(testResponsive) + .then(done); + }); - it('@flaky should only have one resize handler when plotted more than once', function(done) { - fillParent(1, 1); - var cntWindowResize = 0; - window.addEventListener('resize', function() {cntWindowResize++;}); - spyOn(Plotly.Plots, 'resize').and.callThrough(); + it('@flaky should still be responsive if the plot is edited', function(done) { + fillParent(1, 1); + Plotly.plot(gd, data, {}, {responsive: true}) + .then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);}) + .then(testResponsive) + .then(done); + }); - Plotly.plot(gd, data, {}, {responsive: true}) - .then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);}) - .then(function() {viewport.set(width / 2, width / 2);}) - .then(delay(RESIZE_DELAY)) - // .then(function() {viewport.set(newWidth, 2 * newHeight);}).then(delay(200)) - .then(function() { - expect(cntWindowResize).toBe(1); - expect(Plotly.Plots.resize.calls.count()).toBe(1); - }) - .catch(failTest) - .then(done); - }); + it('@flaky should still be responsive if the plot is purged and replotted', function(done) { + fillParent(1, 1); + Plotly.plot(gd, data, {}, {responsive: true}) + .then(function() {return Plotly.newPlot(gd, data, {}, {responsive: true});}) + .then(testResponsive) + .then(done); + }); - it('@flaky should become responsive if configured as such via Plotly.react', function(done) { - fillParent(1, 1); - Plotly.plot(gd, data, {}, {responsive: false}) - .then(function() {return Plotly.react(gd, data, {}, {responsive: true});}) - .then(testResponsive) - .then(done); - }); + it('@flaky should only have one resize handler when plotted more than once', function(done) { + fillParent(1, 1); + var cntWindowResize = 0; + window.addEventListener('resize', function() {cntWindowResize++;}); + spyOn(Plotly.Plots, 'resize').and.callThrough(); + + Plotly.plot(gd, data, {}, {responsive: true}) + .then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);}) + .then(function() {viewport.set(width / 2, width / 2);}) + .then(delay(RESIZE_DELAY)) + // .then(function() {viewport.set(newWidth, 2 * newHeight);}).then(delay(200)) + .then(function() { + expect(cntWindowResize).toBe(1); + expect(Plotly.Plots.resize.calls.count()).toBe(1); + }) + .catch(failTest) + .then(done); + }); - it('@flaky should stop being responsive if configured as such via Plotly.react', function(done) { - fillParent(1, 1); - Plotly.plot(gd, data, {}, {responsive: true}) - // Check initial size - .then(function() {checkLayoutSize(width, height);}) - // Turn off responsiveness - .then(function() {return Plotly.react(gd, data, {}, {responsive: false});}) - // Resize viewport - .then(function() {viewport.set(width / 2, height / 2);}) - // Wait for resize to happen (Plotly.resize has an internal timeout) - .then(delay(RESIZE_DELAY)) - // Check that final figure's size hasn't changed - .then(function() {checkLayoutSize(width, height);}) - .catch(failTest) - .then(done); - }); + it('@flaky should become responsive if configured as such via Plotly.react', function(done) { + fillParent(1, 1); + Plotly.plot(gd, data, {}, {responsive: false}) + .then(function() {return Plotly.react(gd, data, {}, {responsive: true});}) + .then(testResponsive) + .then(done); + }); - // Testing fancier CSS layouts - it('@flaky should resize horizontally in a flexbox when responsive: true', function(done) { - parent.style.display = 'flex'; - parent.style.flexDirection = 'row'; - fillParent(1, 2, function() { - this.style.flexGrow = '1'; + it('@flaky should stop being responsive if configured as such via Plotly.react', function(done) { + fillParent(1, 1); + Plotly.plot(gd, data, {}, {responsive: true}) + // Check initial size + .then(function() {checkLayoutSize(width, height);}) + // Turn off responsiveness + .then(function() {return Plotly.react(gd, data, {}, {responsive: false});}) + // Resize viewport + .then(function() {viewport.set(width / 2, height / 2);}) + // Wait for resize to happen (Plotly.resize has an internal timeout) + .then(delay(RESIZE_DELAY)) + // Check that final figure's size hasn't changed + .then(function() {checkLayoutSize(width, height);}) + .catch(failTest) + .then(done); }); - Plotly.plot(gd, data, {}, { responsive: true }) - .then(testResponsive) - .then(done); - }); + // Testing fancier CSS layouts + it('@flaky should resize horizontally in a flexbox when responsive: true', function(done) { + parent.style.display = 'flex'; + parent.style.flexDirection = 'row'; + fillParent(1, 2, function() { + this.style.flexGrow = '1'; + }); - it('@flaky should resize vertically in a flexbox when responsive: true', function(done) { - parent.style.display = 'flex'; - parent.style.flexDirection = 'column'; - fillParent(2, 1, function() { - this.style.flexGrow = '1'; + Plotly.plot(gd, data, {}, { responsive: true }) + .then(testResponsive) + .then(done); }); - Plotly.plot(gd, data, {}, { responsive: true }) - .then(testResponsive) - .then(done); - }); - - it('@flaky should resize in both direction in a grid when responsive: true', function(done) { - var numCols = 2; - var numRows = 2; - parent.style.display = 'grid'; - parent.style.gridTemplateColumns = 'repeat(' + numCols + ', 1fr)'; - parent.style.gridTemplateRows = 'repeat(' + numRows + ', 1fr)'; - fillParent(numRows, numCols); + it('@flaky should resize vertically in a flexbox when responsive: true', function(done) { + parent.style.display = 'flex'; + parent.style.flexDirection = 'column'; + fillParent(2, 1, function() { + this.style.flexGrow = '1'; + }); - Plotly.plot(gd, data, {}, { responsive: true }) - .then(testResponsive) - .then(done); - }); + Plotly.plot(gd, data, {}, { responsive: true }) + .then(testResponsive) + .then(done); + }); - it('@flaky should provide a fixed non-zero width/height when autosize/responsive: true and container\' size is zero', function(done) { - fillParent(1, 1, function() { - this.style.display = 'inline-block'; - this.style.width = null; - this.style.height = null; + it('@flaky should resize in both direction in a grid when responsive: true', function(done) { + var numCols = 2; + var numRows = 2; + parent.style.display = 'grid'; + parent.style.gridTemplateColumns = 'repeat(' + numCols + ', 1fr)'; + parent.style.gridTemplateRows = 'repeat(' + numRows + ', 1fr)'; + fillParent(numRows, numCols); + + Plotly.plot(gd, data, {}, { responsive: true }) + .then(testResponsive) + .then(done); }); - Plotly.plot(gd, data, {autosize: true}, {responsive: true}) - .then(function() { - checkLayoutSize(700, 450); - expect(gd.clientWidth).toBe(700); - expect(gd.clientHeight).toBe(450); - }) - .then(function() { - return Plotly.newPlot(gd, data, {autosize: true}, {responsive: true}); - }) - // It is important to test newPlot to make sure an initially zero size container - // is still considered to have zero size after a plot is drawn into it. - .then(function() { - checkLayoutSize(700, 450); - expect(gd.clientWidth).toBe(700); - expect(gd.clientHeight).toBe(450); - }) - .catch(failTest) - .then(done); - }); - // The following test is to guarantee we're not breaking the existing (although maybe not ideal) behaviour. - // In a future version, one may prefer responsive/autosize:true winning over an explicit width/height when embedded in a webpage. - it('@flaky should use the explicitly provided width/height even if autosize/responsive:true', function(done) { - fillParent(1, 1, function() { - this.style.width = '1000px'; - this.style.height = '500px'; + it('@flaky should provide a fixed non-zero width/height when autosize/responsive: true and container\' size is zero', function(done) { + fillParent(1, 1, function() { + this.style.display = 'inline-block'; + this.style.width = null; + this.style.height = null; + }); + Plotly.plot(gd, data, {autosize: true}, {responsive: true}) + .then(function() { + checkLayoutSize(700, 450); + expect(gd.clientWidth).toBe(700); + expect(gd.clientHeight).toBe(450); + }) + .then(function() { + return Plotly.newPlot(gd, data, {autosize: true}, {responsive: true}); + }) + // It is important to test newPlot to make sure an initially zero size container + // is still considered to have zero size after a plot is drawn into it. + .then(function() { + checkLayoutSize(700, 450); + expect(gd.clientWidth).toBe(700); + expect(gd.clientHeight).toBe(450); + }) + .catch(failTest) + .then(done); }); - Plotly.plot(gd, data, {autosize: true, width: 1200, height: 700}, {responsive: true}) - .then(function() { - expect(gd.clientWidth).toBe(1000); - expect(gd.clientHeight).toBe(500); - // The plot should overflow the container! - checkLayoutSize(1200, 700); - }) - .catch(failTest) - .then(done); + // The following test is to guarantee we're not breaking the existing (although maybe not ideal) behaviour. + // In a future version, one may prefer responsive/autosize:true winning over an explicit width/height when embedded in a webpage. + it('@flaky should use the explicitly provided width/height even if autosize/responsive:true', function(done) { + fillParent(1, 1, function() { + this.style.width = '1000px'; + this.style.height = '500px'; + }); + + Plotly.plot(gd, data, {autosize: true, width: 1200, height: 700}, {responsive: true}) + .then(function() { + expect(gd.clientWidth).toBe(1000); + expect(gd.clientHeight).toBe(500); + // The plot should overflow the container! + checkLayoutSize(1200, 700); + }) + .catch(failTest) + .then(done); + }); }); });