Skip to content

Commit d75b534

Browse files
committed
#fi-51 contour plot fill test case
1 parent c21da19 commit d75b534

File tree

6 files changed

+259
-78
lines changed

6 files changed

+259
-78
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"delaunay-triangulate": "^1.1.6",
5555
"es6-promise": "^3.0.2",
5656
"fast-isnumeric": "^1.1.1",
57-
"gl-contour2d": "^1.0.1",
57+
"gl-contour2d": "git://github.com/monfera/gl-contour2d.git#integration",
5858
"gl-error2d": "^1.0.0",
5959
"gl-error3d": "^1.0.0",
6060
"gl-heatmap2d": "^1.0.2",

src/traces/contourgl/convert.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ proto.update = function(fullTrace, calcTrace) {
9999
this.contourOptions.levels = colorOptions.levels;
100100
this.contourOptions.levelColors = colorOptions.levelColors;
101101

102+
// pass on fill information
103+
if(fullTrace.contours.coloring === 'fill') {
104+
// though gl-contour2d automatically defaults to a transparent layer for the last
105+
// band color, it's set manually here in case the gl-contour2 API changes
106+
this.contourOptions.fillColors = colorOptions.levelColors.concat([0,0,0,0])
107+
.map(function(d, i) {return i % 4 === 3 ? d / 2 : d}); // halve the fill alphas
108+
}
109+
102110
// convert text from 2D -> 1D
103111
this.textLabels = [].concat.apply([], fullTrace.text);
104112

test/jasmine/assets/fail_test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
/**
4+
* Errors thrown in promise 'then'-s fail silently unless handled e.g. this way. A silent failure would probably
5+
* make at least some of the test assertions to be bypassed, i.e. a clean jasmine test run could result even if
6+
* some assertions were to fail, had the error been removed.
7+
*
8+
* @example
9+
*
10+
* Plotly.plot(...)
11+
* .then(function(gd) {
12+
* assert(...);
13+
* assert(...);
14+
* })
15+
* .catch(failTest)
16+
* .then(done);
17+
*
18+
* See ./with_setup_teardown.js for a different example.
19+
*/
20+
module.exports = function failTest(error) {
21+
expect(error).toBeUndefined();
22+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
var Plotly = require('@lib/index');
4+
5+
var createGraphDiv = require('./create_graph_div');
6+
var destroyGraphDiv = require('./destroy_graph_div');
7+
8+
var failTest = require('./fail_test');
9+
10+
function teardown(gd) {
11+
12+
Plotly.purge(gd);
13+
14+
destroyGraphDiv();
15+
16+
}
17+
18+
/**
19+
* Provides a safe means of setting up and tearing down a plot (which may or may not have WebGL elements).
20+
* It is a self-contained alternative to the manual setup/teardown process, which uses:
21+
* - createGraphDiv in the beforeEach, assigning it to global variable gd
22+
* - purg, destroyGraphDiv in the afterEach
23+
*
24+
* In theory we could incorporate a .then(done) clause here, as well as a ./failTest but there may be
25+
* test cases that want to call done() manually inside a testing function or somewhere in a .then
26+
*
27+
* @param {Function} runner A function that runs tests, taking two arguments: 'gd' and 'done'
28+
* withSetupTeardown binds the newly setup graph div to gd and
29+
* calls the graph div teardown before calling done. Therefore the actual
30+
* test case doesn't need to know where gd comes from, and can call 'done()'
31+
* as usual.
32+
* @param {Function} done The 'done' function that gets passed to the testcase (describe or it)
33+
* for the purpose of handling asynchronous tests (typically invoked as the
34+
* last expression in the last .then or window.timeout of the test case
35+
*
36+
* @example
37+
*
38+
* it('should respond to drag interactions with mock of unset camera', done => {
39+
* withSetupTeardown(done, (gd, done) => {
40+
* makePlot(gd, require('@mocks/myMock.json'))
41+
* .then(gd => {
42+
* expect(...);
43+
* expect(...);
44+
* expect(...);
45+
* done(); // best ignored, as we still do a failTest and then(done)
46+
* })
47+
* }
48+
* );
49+
* });
50+
*
51+
*/
52+
module.exports = function withSetupTeardown(done, runner) {
53+
54+
var gd = createGraphDiv();
55+
56+
// Passing a teardown-inducing 'done' as a second, optional argument makes possible those test cases where
57+
// the test writer wants to explicitly call 'done', e.g. at the end of a window.setTimeout - cleanup must
58+
// happen in this case too.
59+
var possibleThenable = runner(gd, function() {
60+
teardown(gd);
61+
done();
62+
});
63+
64+
// A test case can only be called 'done' after the teardown had been performed.
65+
// One way of helping ensure that the destroys are not forgotten is that done() is part of
66+
// the teardown, consequently if a test case omits the teardown by accident, the test will
67+
// visibly hang. If the teardown receives no proper arguments, it'll also visibly fail.
68+
69+
if(possibleThenable && possibleThenable.then && typeof possibleThenable.then === 'function') {
70+
71+
return possibleThenable
72+
// needs to catch via failTest otherwise errors inside the .then silently fail (tests may fail to fail)
73+
.then(null, failTest) // current linter balks on .catch with 'dot-notation'; fixme a linter
74+
.then(function() {
75+
teardown(gd);
76+
})
77+
.then(done);
78+
79+
} else {
80+
81+
return possibleThenable;
82+
83+
}
84+
};
Lines changed: 44 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,68 @@
11
'use strict';
22

33
var Plotly = require('@lib/index');
4-
var Plots = require('@src/plots/plots');
54

6-
var createGraphDiv = require('../assets/create_graph_div');
7-
var destroyGraphDiv = require('../assets/destroy_graph_div');
5+
var withSetupTeardown = require('../assets/with_setup_teardown');
86
var mouseEvent = require('../assets/mouse_event');
97

10-
function teardown(gd, done) {
8+
// Expected shape of projection-related data
9+
var cameraStructure = {
10+
up: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
11+
center: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
12+
eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}
13+
};
1114

12-
// The teardown function needs information of what to tear down so afterEach can not be used without global vars.
13-
// In addition to removing the plot from the DOM it also destroy possibly present 2D or 3D scenes
14-
15-
// TODO we should figure out something to only rely on public API calls
16-
// In other words, how can users themselves properly destroy the plot through the API?
17-
// This function is left in this file until the above todo is looked into.
18-
var fullLayout = gd._fullLayout;
19-
20-
Plots.getSubplotIds(fullLayout, 'gl3d').forEach(function(sceneId) {
21-
var scene = fullLayout[sceneId]._scene;
22-
if(scene.glplot) scene.destroy();
23-
});
24-
25-
Plots.getSubplotIds(fullLayout, 'gl2d').forEach(function(sceneId) {
26-
var scene2d = fullLayout._plots[sceneId]._scene2d;
27-
if(scene2d.glplot) {
28-
scene2d.stopped = true;
29-
scene2d.destroy();
30-
}
31-
});
32-
33-
destroyGraphDiv();
34-
35-
// A test case can only be called 'done' when the above destroy methods had been performed.
36-
// One way of helping ensure that the destroys are not forgotten is that done() is part of
37-
// the teardown, consequently if a test case omits the teardown by accident, the test will
38-
// visibly hang. If the teardown receives no proper arguments, it'll also visibly fail.
39-
done();
15+
function makePlot(gd, mock) {
16+
return Plotly.plot(gd, mock.data, mock.layout);
4017
}
4118

42-
describe('Test gl plot interactions', function() {
43-
44-
describe('gl3d plots', function() {
45-
46-
// Expected shape of projection-related data
47-
var cameraStructure = {
48-
up: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
49-
center: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
50-
eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}
51-
};
52-
53-
function makePlot(mock) {
54-
return Plotly.plot(createGraphDiv(), mock.data, mock.layout);
55-
}
19+
function addEventCallback(graphDiv) {
20+
var relayoutCallback = jasmine.createSpy('relayoutCallback');
21+
graphDiv.on('plotly_relayout', relayoutCallback);
22+
return {graphDiv: graphDiv, relayoutCallback: relayoutCallback};
23+
}
5624

57-
function addEventCallback(graphDiv) {
58-
var relayoutCallback = jasmine.createSpy('relayoutCallback');
59-
graphDiv.on('plotly_relayout', relayoutCallback);
60-
return {graphDiv: graphDiv, relayoutCallback: relayoutCallback};
61-
}
25+
function verifyInteractionEffects(tuple) {
6226

63-
function verifyInteractionEffects(tuple) {
27+
// One 'drag': simulating fairly thoroughly as the mouseup event is also needed here
28+
mouseEvent('mousemove', 400, 200);
29+
mouseEvent('mousedown', 400, 200);
30+
mouseEvent('mousemove', 320, 320, {buttons: 1});
31+
mouseEvent('mouseup', 320, 320);
6432

65-
// One 'drag': simulating fairly thoroughly as the mouseup event is also needed here
66-
mouseEvent('mousemove', 400, 200);
67-
mouseEvent('mousedown', 400, 200);
68-
mouseEvent('mousemove', 320, 320, {buttons: 1});
69-
mouseEvent('mouseup', 320, 320);
33+
// Check event emission count
34+
expect(tuple.relayoutCallback).toHaveBeenCalledTimes(1);
7035

71-
// Check event emission count
72-
expect(tuple.relayoutCallback).toHaveBeenCalledTimes(1);
36+
// Check structure of event callback value contents
37+
expect(tuple.relayoutCallback).toHaveBeenCalledWith(jasmine.objectContaining({scene: cameraStructure}));
7338

74-
// Check structure of event callback value contents
75-
expect(tuple.relayoutCallback).toHaveBeenCalledWith(jasmine.objectContaining({scene: cameraStructure}));
39+
// Check camera contents on the DIV layout
40+
var divCamera = tuple.graphDiv.layout.scene.camera;
7641

77-
// Check camera contents on the DIV layout
78-
var divCamera = tuple.graphDiv.layout.scene.camera;
42+
expect(divCamera).toEqual(cameraStructure);
7943

80-
expect(divCamera).toEqual(cameraStructure);
44+
return tuple.graphDiv;
45+
}
8146

82-
return tuple.graphDiv;
83-
}
47+
function testEvents(plot) {
48+
return plot
49+
.then(function(graphDiv) {
50+
var tuple = addEventCallback(graphDiv); // TODO disuse tuple with ES6
51+
verifyInteractionEffects(tuple);
52+
});
53+
}
8454

85-
function testEvents(plot, done) {
86-
plot.then(function(graphDiv) {
87-
var tuple = addEventCallback(graphDiv); // TODO disuse tuple with ES6
88-
verifyInteractionEffects(tuple);
89-
teardown(graphDiv, done);
90-
});
91-
}
55+
describe('gl3d plots', function() {
9256

93-
it('should respond to drag interactions with mock of unset camera', function(done) {
94-
testEvents(makePlot(require('@mocks/gl3d_scatter3d-connectgaps.json')), done);
57+
it('should respond to drag interactions with mock of unset camera', function(done) {
58+
withSetupTeardown(done, function(gd) {
59+
return testEvents(makePlot(gd, require('@mocks/gl3d_scatter3d-connectgaps.json')));
9560
});
61+
});
9662

97-
it('should respond to drag interactions with mock of partially set camera', function(done) {
98-
testEvents(makePlot(require('@mocks/gl3d_errorbars_zx.json')), done);
63+
it('should respond to drag interactions with mock of partially set camera', function(done) {
64+
withSetupTeardown(done, function(gd) {
65+
return testEvents(makePlot(gd, require('@mocks/gl3d_errorbars_zx.json')));
9966
});
10067
});
10168
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
3+
var Plotly = require('@lib/index');
4+
5+
// contourgl is not part of the dist plotly.js bundle initially
6+
Plotly.register(
7+
require('@lib/contourgl')
8+
);
9+
10+
var withSetupTeardown = require('../assets/with_setup_teardown');
11+
12+
var plotData = {
13+
"data": [
14+
{
15+
"type": "contourgl",
16+
"z": [
17+
[
18+
10,
19+
10.625,
20+
12.5,
21+
15.625,
22+
20
23+
],
24+
[
25+
5.625,
26+
6.25,
27+
8.125,
28+
11.25,
29+
15.625
30+
],
31+
[
32+
2.5,
33+
3.125,
34+
5,
35+
8.125,
36+
12.5
37+
],
38+
[
39+
0.625,
40+
1.25,
41+
3.125,
42+
6.25,
43+
10.625
44+
],
45+
[
46+
0,
47+
0.625,
48+
2.5,
49+
5.625,
50+
10
51+
]
52+
],
53+
"colorscale": "Jet",
54+
"contours": {
55+
"start": 2,
56+
"end": 10,
57+
"size": 1
58+
},
59+
"uid": "ad5624",
60+
"zmin": 0,
61+
"zmax": 20
62+
}
63+
],
64+
"layout": {
65+
"xaxis": {
66+
"range": [
67+
0,
68+
4
69+
],
70+
"autorange": true
71+
},
72+
"yaxis": {
73+
"range": [
74+
0,
75+
4
76+
],
77+
"autorange": true
78+
},
79+
"height": 450,
80+
"width": 1000,
81+
"autosize": true
82+
}
83+
};
84+
85+
function makePlot(gd, mock) {
86+
return Plotly.plot(gd, mock.data, mock.layout);
87+
}
88+
89+
fdescribe('contourgl plots', function() {
90+
91+
it('render without raising an error', function(done) {
92+
withSetupTeardown(done, function(gd) {
93+
return makePlot(gd, plotData)
94+
.then(function(gd) {
95+
//debugger;
96+
})
97+
});
98+
});
99+
100+
});

0 commit comments

Comments
 (0)