Skip to content

Commit

Permalink
Merge pull request #2974 from plotly/2969-responsive-charts
Browse files Browse the repository at this point in the history
add support for responsive charts
  • Loading branch information
antoinerg committed Sep 7, 2018
2 parents 70fcaad + 1262172 commit cfc720b
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 1 deletion.
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -142,6 +142,7 @@
"karma-jasmine-spec-tags": "^1.0.1",
"karma-spec-reporter": "0.0.32",
"karma-verbose-reporter": "0.0.6",
"karma-viewport": "^1.0.2",
"madge": "^3.2.0",
"minify-stream": "^1.2.0",
"minimist": "^1.2.0",
Expand Down
21 changes: 21 additions & 0 deletions src/lib/clear_responsive.js
@@ -0,0 +1,21 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

/**
* Clear responsive handlers (if any).
*
* @param {DOM node or object} gd : graph div object
*/
module.exports = function clearResponsive(gd) {
if(gd._responsiveChartHandler) {
window.removeEventListener('resize', gd._responsiveChartHandler);
delete gd._responsiveChartHandler;
}
};
2 changes: 2 additions & 0 deletions src/lib/index.js
Expand Up @@ -128,6 +128,8 @@ lib.clearThrottle = throttleModule.clear;

lib.getGraphDiv = require('./get_graph_div');

lib.clearResponsive = require('./clear_responsive');

lib.makeTraceGroups = require('./make_trace_groups');

lib._ = require('./localize');
Expand Down
13 changes: 13 additions & 0 deletions src/plot_api/plot_api.js
Expand Up @@ -191,6 +191,19 @@ exports.plot = function(gd, data, layout, config) {
gd.calcdata[i][0].trace = gd._fullData[i];
}

// make the figure responsive
if(gd._context.responsive) {
if(!gd._responsiveChartHandler) {
// Keep a reference to the resize handler to purge it down the road
gd._responsiveChartHandler = function() {Plots.resize(gd);};

// Listen to window resize
window.addEventListener('resize', gd._responsiveChartHandler);
}
} else {
Lib.clearResponsive(gd);
}

/*
* start async-friendly code - now we're actually drawing things
*/
Expand Down
3 changes: 3 additions & 0 deletions src/plot_api/plot_config.js
Expand Up @@ -57,6 +57,9 @@ module.exports = {
*/
autosizable: false,

// responsive: determines whether to change the layout size when window is resized
responsive: false,

// set the length of the undo/redo queue
queueLength: 0,

Expand Down
3 changes: 3 additions & 0 deletions src/plots/plots.js
Expand Up @@ -1542,6 +1542,9 @@ plots.purge = function(gd) {
// remove any planned throttles
Lib.clearThrottle();

// remove responsive handler
Lib.clearResponsive(gd);

// data and layout
delete gd.data;
delete gd.layout;
Expand Down
3 changes: 3 additions & 0 deletions test/jasmine/.eslintrc
Expand Up @@ -3,5 +3,8 @@
"env": {
"browser": true,
"jasmine": true
},
"globals": {
"viewport": true
}
}
2 changes: 1 addition & 1 deletion test/jasmine/karma.conf.js
Expand Up @@ -137,7 +137,7 @@ func.defaultConfig = {

// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'jasmine-spec-tags', 'browserify'],
frameworks: ['jasmine', 'jasmine-spec-tags', 'browserify', 'viewport'],

// list of files / patterns to load in the browser
//
Expand Down
105 changes: 105 additions & 0 deletions test/jasmine/tests/config_test.js
Expand Up @@ -6,6 +6,7 @@ var destroyGraphDiv = require('../assets/destroy_graph_div');
var click = require('../assets/click');
var mouseEvent = require('../assets/mouse_event');
var failTest = require('../assets/fail_test');
var delay = require('../assets/delay');

describe('config argument', function() {

Expand Down Expand Up @@ -529,4 +530,108 @@ 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]}];

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

// Make the graph fill the parent
gd.style.width = '100%';
gd.style.height = '100%';
});

afterEach(function() {
Plotly.purge(gd); // Needed to remove all event listeners
destroyGraphDiv();
viewport.reset();
});

function checkLayoutSize(width, height) {
expect(gd._fullLayout.width).toBe(width);
expect(gd._fullLayout.height).toBe(height);

var svg = document.getElementsByClassName('main-svg')[0];
expect(+svg.getAttribute('width')).toBe(width);
expect(+svg.getAttribute('height')).toBe(height);
}

function testResponsive() {
checkLayoutSize(startWidth, startHeight);
viewport.set(newWidth, newHeight);

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

it('should resize when the viewport width/height changes', function(done) {
Plotly.plot(gd, data, {}, {responsive: true})
.then(testResponsive)
.then(done);
});

it('should still be responsive if the plot is edited', function(done) {
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) {
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) {
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(delay(200))
// .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('should become responsive if configured as such via Plotly.react', function(done) {
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) {
Plotly.plot(gd, data, {}, {responsive: true})
// Check initial size
.then(function() {checkLayoutSize(startWidth, startHeight);})
// Turn off responsiveness
.then(function() {return Plotly.react(gd, data, {}, {responsive: false});})
// Resize viewport
.then(function() {viewport.set(newWidth, newHeight);})
// 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);})
.catch(failTest)
.then(done);
});
});
});

0 comments on commit cfc720b

Please sign in to comment.