Skip to content

Commit 9f5733d

Browse files
authored
Merge pull request #3056 from plotly/3034-responsive-flexbox
responsive figures in CSS flexbox and grid
2 parents 88d48c5 + 202f8f9 commit 9f5733d

File tree

5 files changed

+93
-61
lines changed

5 files changed

+93
-61
lines changed

src/plot_api/plot_api.js

+3
Original file line numberDiff line numberDiff line change
@@ -3248,6 +3248,9 @@ function makePlotFramework(gd) {
32483248
var gd3 = d3.select(gd);
32493249
var fullLayout = gd._fullLayout;
32503250

3251+
// Check if gd has a specified height
3252+
fullLayout._hasZeroHeight = gd.clientHeight === 0;
3253+
32513254
// Plot container
32523255
fullLayout._container = gd3.selectAll('.plot-container').data([0]);
32533256
fullLayout._container.enter().insert('div', ':first-child')

src/plot_api/subroutines.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ function lsInner(gd) {
5454
var i, subplot, plotinfo, xa, ya;
5555

5656
fullLayout._paperdiv.style({
57-
width: fullLayout.width + 'px',
58-
height: fullLayout.height + 'px'
57+
width: (fullLayout.autosize) ? '100%' : fullLayout.width + 'px',
58+
height: (fullLayout.autosize && !fullLayout._hasZeroHeight) ? '100%' : fullLayout.height + 'px'
5959
})
6060
.selectAll('.main-svg')
6161
.call(Drawing.setSize, fullLayout.width, fullLayout.height);

src/plots/plots.js

+6-38
Original file line numberDiff line numberDiff line change
@@ -1374,21 +1374,6 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
13741374
// just hide it
13751375
document.body.style.overflow = 'hidden';
13761376
}
1377-
else if(isNumeric(frameMargins) && frameMargins > 0) {
1378-
var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins),
1379-
reservedWidth = reservedMargins.left + reservedMargins.right,
1380-
reservedHeight = reservedMargins.bottom + reservedMargins.top,
1381-
factor = 1 - 2 * frameMargins;
1382-
1383-
var gdBB = fullLayout._container && fullLayout._container.node ?
1384-
fullLayout._container.node().getBoundingClientRect() : {
1385-
width: fullLayout.width,
1386-
height: fullLayout.height
1387-
};
1388-
1389-
newWidth = Math.round(factor * (gdBB.width - reservedWidth));
1390-
newHeight = Math.round(factor * (gdBB.height - reservedHeight));
1391-
}
13921377
else {
13931378
// plotly.js - let the developers do what they want, either
13941379
// provide height and width for the container div,
@@ -1398,6 +1383,12 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
13981383

13991384
newWidth = parseFloat(computedStyle.width) || parseFloat(computedStyle.maxWidth) || fullLayout.width;
14001385
newHeight = parseFloat(computedStyle.height) || parseFloat(computedStyle.maxHeight) || fullLayout.height;
1386+
1387+
if(isNumeric(frameMargins) && frameMargins > 0) {
1388+
var factor = 1 - 2 * frameMargins;
1389+
newWidth = Math.round(factor * newWidth);
1390+
newHeight = Math.round(factor * newHeight);
1391+
}
14011392
}
14021393

14031394
var minWidth = plots.layoutAttributes.width.min,
@@ -1424,29 +1415,6 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
14241415
plots.sanitizeMargins(fullLayout);
14251416
};
14261417

1427-
/**
1428-
* Reduce all reserved margin objects to a single required margin reservation.
1429-
*
1430-
* @param {Object} margins
1431-
* @returns {{left: number, right: number, bottom: number, top: number}}
1432-
*/
1433-
function calculateReservedMargins(margins) {
1434-
var resultingMargin = {left: 0, right: 0, bottom: 0, top: 0},
1435-
marginName;
1436-
1437-
if(margins) {
1438-
for(marginName in margins) {
1439-
if(margins.hasOwnProperty(marginName)) {
1440-
resultingMargin.left += margins[marginName].left || 0;
1441-
resultingMargin.right += margins[marginName].right || 0;
1442-
resultingMargin.bottom += margins[marginName].bottom || 0;
1443-
resultingMargin.top += margins[marginName].top || 0;
1444-
}
1445-
}
1446-
}
1447-
return resultingMargin;
1448-
}
1449-
14501418
plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
14511419
var componentsRegistry = Registry.componentsRegistry;
14521420
var basePlotModules = layoutOut._basePlotModules;

test/jasmine/tests/config_test.js

+81-20
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('config argument', function() {
145145
fillFrame: false,
146146
frameMargins: 0.1
147147
};
148-
var layoutHeight = 360;
148+
var layoutHeight = Math.round(0.8 * containerHeightBeforePlot);
149149
var relayoutHeight = layoutHeight;
150150

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

163163
testAutosize(autosize, config, layoutHeight, relayoutHeight, done);
164164
});
@@ -543,23 +543,26 @@ describe('config argument', function() {
543543
});
544544

545545
describe('responsive figure', function() {
546-
var gd;
547-
var startWidth = 960, startHeight = 400;
548-
var newWidth = 400, newHeight = 700;
549-
var data = [{x: [1, 2, 3, 4], y: [5, 10, 2, 8]}];
546+
var gd, data = [{x: [1, 2, 3, 4], y: [5, 10, 2, 8]}];
547+
var width = 960, height = 800;
548+
549+
var parent, elWidth, elHeight;
550550

551551
beforeEach(function() {
552-
viewport.set(startWidth, startHeight);
553-
gd = createGraphDiv();
552+
viewport.set(width, height);
554553

555-
// Make the graph fill the parent
556-
gd.style.width = '100%';
557-
gd.style.height = '100%';
554+
// Prepare a parent container that fills the viewport
555+
parent = document.createElement('div');
556+
parent.style.width = '100vw';
557+
parent.style.height = '100vh';
558+
parent.style.position = 'fixed';
559+
parent.style.top = '0';
560+
parent.style.left = '0';
558561
});
559562

560563
afterEach(function() {
561564
Plotly.purge(gd); // Needed to remove all event listeners
562-
destroyGraphDiv();
565+
document.body.removeChild(parent);
563566
viewport.reset();
564567
});
565568

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

575578
function testResponsive() {
576-
checkLayoutSize(startWidth, startHeight);
577-
viewport.set(newWidth, newHeight);
579+
checkLayoutSize(elWidth, elHeight);
580+
viewport.set(width / 2, height / 2);
578581

579582
return Promise.resolve()
580583
.then(delay(200))
581584
.then(function() {
582-
checkLayoutSize(newWidth, newHeight);
585+
checkLayoutSize(elWidth / 2, elHeight / 2);
583586
})
584587
.catch(failTest);
585588
}
586589

590+
function fillParent(numRows, numCols, cb) {
591+
elWidth = width / numCols, elHeight = height / numRows;
592+
593+
// Fill parent
594+
for(var i = 0; i < (numCols * numRows); i++) {
595+
var col = document.createElement('div');
596+
col.style.height = '100%';
597+
col.style.width = '100%';
598+
if(typeof(cb) === typeof(Function)) cb.call(col, i);
599+
parent.appendChild(col);
600+
}
601+
document.body.appendChild(parent);
602+
gd = parent.childNodes[0];
603+
}
604+
587605
it('should resize when the viewport width/height changes', function(done) {
606+
fillParent(1, 1);
588607
Plotly.plot(gd, data, {}, {responsive: true})
589608
.then(testResponsive)
590609
.then(done);
591610
});
592611

593612
it('should still be responsive if the plot is edited', function(done) {
613+
fillParent(1, 1);
594614
Plotly.plot(gd, data, {}, {responsive: true})
595615
.then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);})
596616
.then(testResponsive)
597617
.then(done);
598618
});
599619

600620
it('should still be responsive if the plot is purged and replotted', function(done) {
621+
fillParent(1, 1);
601622
Plotly.plot(gd, data, {}, {responsive: true})
602623
.then(function() {return Plotly.newPlot(gd, data, {}, {responsive: true});})
603624
.then(testResponsive)
604625
.then(done);
605626
});
606627

607628
it('should only have one resize handler when plotted more than once', function(done) {
629+
fillParent(1, 1);
608630
var cntWindowResize = 0;
609631
window.addEventListener('resize', function() {cntWindowResize++;});
610632
spyOn(Plotly.Plots, 'resize').and.callThrough();
611633

612634
Plotly.plot(gd, data, {}, {responsive: true})
613635
.then(function() {return Plotly.restyle(gd, 'y[0]', data[0].y[0] + 2);})
614-
.then(function() {viewport.set(newWidth, newHeight);})
636+
.then(function() {viewport.set(width / 2, width / 2);})
615637
.then(delay(200))
616638
// .then(function() {viewport.set(newWidth, 2 * newHeight);}).then(delay(200))
617639
.then(function() {
@@ -623,26 +645,65 @@ describe('config argument', function() {
623645
});
624646

625647
it('should become responsive if configured as such via Plotly.react', function(done) {
648+
fillParent(1, 1);
626649
Plotly.plot(gd, data, {}, {responsive: false})
627650
.then(function() {return Plotly.react(gd, data, {}, {responsive: true});})
628651
.then(testResponsive)
629652
.then(done);
630653
});
631654

632655
it('should stop being responsive if configured as such via Plotly.react', function(done) {
656+
fillParent(1, 1);
633657
Plotly.plot(gd, data, {}, {responsive: true})
634658
// Check initial size
635-
.then(function() {checkLayoutSize(startWidth, startHeight);})
659+
.then(function() {checkLayoutSize(width, height);})
636660
// Turn off responsiveness
637661
.then(function() {return Plotly.react(gd, data, {}, {responsive: false});})
638662
// Resize viewport
639-
.then(function() {viewport.set(newWidth, newHeight);})
663+
.then(function() {viewport.set(width / 2, height / 2);})
640664
// Wait for resize to happen (Plotly.resize has an internal timeout)
641665
.then(delay(200))
642666
// Check that final figure's size hasn't changed
643-
.then(function() {checkLayoutSize(startWidth, startHeight);})
667+
.then(function() {checkLayoutSize(width, height);})
644668
.catch(failTest)
645669
.then(done);
646670
});
671+
672+
// Testing fancier CSS layouts
673+
it('should resize horizontally in a flexbox when responsive: true', function(done) {
674+
parent.style.display = 'flex';
675+
parent.style.flexDirection = 'row';
676+
fillParent(1, 2, function() {
677+
this.style.flexGrow = '1';
678+
});
679+
680+
Plotly.plot(gd, data, {}, { responsive: true })
681+
.then(testResponsive)
682+
.then(done);
683+
});
684+
685+
it('should resize vertically in a flexbox when responsive: true', function(done) {
686+
parent.style.display = 'flex';
687+
parent.style.flexDirection = 'column';
688+
fillParent(2, 1, function() {
689+
this.style.flexGrow = '1';
690+
});
691+
692+
Plotly.plot(gd, data, {}, { responsive: true })
693+
.then(testResponsive)
694+
.then(done);
695+
});
696+
697+
it('should resize in both direction in a grid when responsive: true', function(done) {
698+
var numCols = 2, numRows = 2;
699+
parent.style.display = 'grid';
700+
parent.style.gridTemplateColumns = 'repeat(' + numCols + ', 1fr)';
701+
parent.style.gridTemplateRows = 'repeat(' + numRows + ', 1fr)';
702+
fillParent(numRows, numCols);
703+
704+
Plotly.plot(gd, data, {}, { responsive: true })
705+
.then(testResponsive)
706+
.then(done);
707+
});
647708
});
648709
});

test/jasmine/tests/hover_label_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2021,7 +2021,7 @@ describe('hover updates', function() {
20212021
size: 16,
20222022
color: colors0.slice()
20232023
}
2024-
}])
2024+
}], { width: 700, height: 450 })
20252025
.then(function() {
20262026

20272027
gd.on('plotly_hover', function(eventData) {

0 commit comments

Comments
 (0)