Skip to content

responsive figures in CSS flexbox and grid #3056

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

Merged
merged 5 commits into from
Oct 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3248,6 +3248,9 @@ function makePlotFramework(gd) {
var gd3 = d3.select(gd);
var fullLayout = gd._fullLayout;

// Check if gd has a specified height
fullLayout._hasZeroHeight = gd.clientHeight === 0;

// Plot container
fullLayout._container = gd3.selectAll('.plot-container').data([0]);
fullLayout._container.enter().insert('div', ':first-child')
Expand Down
4 changes: 2 additions & 2 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ function lsInner(gd) {
var i, subplot, plotinfo, xa, ya;

fullLayout._paperdiv.style({
width: fullLayout.width + 'px',
height: fullLayout.height + 'px'
width: (fullLayout.autosize) ? '100%' : fullLayout.width + 'px',
height: (fullLayout.autosize && !fullLayout._hasZeroHeight) ? '100%' : fullLayout.height + 'px'
})
.selectAll('.main-svg')
.call(Drawing.setSize, fullLayout.width, fullLayout.height);
Expand Down
44 changes: 6 additions & 38 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1368,21 +1368,6 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
// 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,
factor = 1 - 2 * frameMargins;

var gdBB = fullLayout._container && fullLayout._container.node ?
fullLayout._container.node().getBoundingClientRect() : {
width: fullLayout.width,
height: fullLayout.height
};

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,
Expand All @@ -1392,6 +1377,12 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {

newWidth = parseFloat(computedStyle.width) || parseFloat(computedStyle.maxWidth) || fullLayout.width;
newHeight = parseFloat(computedStyle.height) || parseFloat(computedStyle.maxHeight) || fullLayout.height;

if(isNumeric(frameMargins) && frameMargins > 0) {
var factor = 1 - 2 * frameMargins;
newWidth = Math.round(factor * newWidth);
newHeight = Math.round(factor * newHeight);
}
}

var minWidth = plots.layoutAttributes.width.min,
Expand All @@ -1418,29 +1409,6 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
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, transitionData) {
var componentsRegistry = Registry.componentsRegistry;
var basePlotModules = layoutOut._basePlotModules;
Expand Down
101 changes: 81 additions & 20 deletions test/jasmine/tests/config_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('config argument', function() {
fillFrame: false,
frameMargins: 0.1
};
var layoutHeight = 360;
var layoutHeight = Math.round(0.8 * containerHeightBeforePlot);
var relayoutHeight = layoutHeight;

testAutosize(autosize, config, layoutHeight, relayoutHeight, done);
Expand All @@ -157,8 +157,8 @@ describe('config argument', function() {
fillFrame: false,
frameMargins: 0.1
};
var layoutHeight = 360;
var relayoutHeight = 288;
var layoutHeight = Math.round(0.8 * containerHeightBeforePlot);
var relayoutHeight = Math.round(0.8 * containerHeightBeforeRelayout);

testAutosize(autosize, config, layoutHeight, relayoutHeight, done);
});
Expand Down Expand Up @@ -543,23 +543,26 @@ describe('config argument', function() {
});

describe('responsive figure', function() {
var gd;
var startWidth = 960, startHeight = 400;
var newWidth = 400, newHeight = 700;
var data = [{x: [1, 2, 3, 4], y: [5, 10, 2, 8]}];
var gd, data = [{x: [1, 2, 3, 4], y: [5, 10, 2, 8]}];
var width = 960, height = 800;

var parent, elWidth, elHeight;

beforeEach(function() {
viewport.set(startWidth, startHeight);
gd = createGraphDiv();
viewport.set(width, height);

// Make the graph fill the parent
gd.style.width = '100%';
gd.style.height = '100%';
// 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
destroyGraphDiv();
document.body.removeChild(parent);
viewport.reset();
});

Expand All @@ -573,45 +576,64 @@ describe('config argument', function() {
}

function testResponsive() {
checkLayoutSize(startWidth, startHeight);
viewport.set(newWidth, newHeight);
checkLayoutSize(elWidth, elHeight);
viewport.set(width / 2, height / 2);

return Promise.resolve()
.then(delay(200))
.then(function() {
checkLayoutSize(newWidth, newHeight);
checkLayoutSize(elWidth / 2, elHeight / 2);
})
.catch(failTest);
}

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('should resize when the viewport width/height changes', function(done) {
fillParent(1, 1);
Plotly.plot(gd, data, {}, {responsive: true})
.then(testResponsive)
.then(done);
});

it('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);
});

it('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('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(newWidth, newHeight);})
.then(function() {viewport.set(width / 2, width / 2);})
.then(delay(200))
// .then(function() {viewport.set(newWidth, 2 * newHeight);}).then(delay(200))
.then(function() {
Expand All @@ -623,26 +645,65 @@ describe('config argument', function() {
});

it('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('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(startWidth, startHeight);})
.then(function() {checkLayoutSize(width, height);})
// Turn off responsiveness
.then(function() {return Plotly.react(gd, data, {}, {responsive: false});})
// Resize viewport
.then(function() {viewport.set(newWidth, newHeight);})
.then(function() {viewport.set(width / 2, height / 2);})
// Wait for resize to happen (Plotly.resize has an internal timeout)
.then(delay(200))
// Check that final figure's size hasn't changed
.then(function() {checkLayoutSize(startWidth, startHeight);})
.then(function() {checkLayoutSize(width, height);})
.catch(failTest)
.then(done);
});

// Testing fancier CSS layouts
it('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';
});

Plotly.plot(gd, data, {}, { responsive: true })
.then(testResponsive)
.then(done);
});

it('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);
});

it('should resize in both direction in a grid when responsive: true', function(done) {
var numCols = 2, 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);
});
});
});
2 changes: 1 addition & 1 deletion test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2021,7 +2021,7 @@ describe('hover updates', function() {
size: 16,
color: colors0.slice()
}
}])
}], { width: 700, height: 450 })
.then(function() {

gd.on('plotly_hover', function(eventData) {
Expand Down