diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2630b369840..b72ab47f0b7 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -53,7 +53,7 @@ jobs:
docker:
# need '-browsers' version to test in real (xvfb-wrapped) browsers
- image: circleci/node:10.9.0-browsers
- parallelism: 2
+ parallelism: 3
working_directory: ~/plotly.js
steps:
- attach_workspace:
diff --git a/test/jasmine/tests/gl2d_scatterplot_contour_test.js b/test/jasmine/tests/contourgl_test.js
similarity index 95%
rename from test/jasmine/tests/gl2d_scatterplot_contour_test.js
rename to test/jasmine/tests/contourgl_test.js
index 91355e3a92e..cdcb97c7cff 100644
--- a/test/jasmine/tests/gl2d_scatterplot_contour_test.js
+++ b/test/jasmine/tests/contourgl_test.js
@@ -1,5 +1,3 @@
-'use strict';
-
var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var d3 = require('d3');
@@ -197,25 +195,25 @@ describe('contourgl plots', function() {
makePlot(gd, mockCopy, done);
});
- it('render without raising an error (coloring: "lines")', function(done) {
+ it('@gl render without raising an error (coloring: "lines")', function(done) {
var mock = Lib.extendDeep({}, plotDataElliptical(0));
mock.data[0].contours.coloring = 'lines'; // 'fill' is the default
makePlot(gd, mock, done);
});
- it('render smooth, regular ellipses without raising an error (coloring: "fill")', function(done) {
+ it('@gl render smooth, regular ellipses without raising an error (coloring: "fill")', function(done) {
var mock = plotDataElliptical(0);
makePlot(gd, mock, done);
});
- it('render ellipses with added noise without raising an error (coloring: "fill")', function(done) {
+ it('@gl render ellipses with added noise without raising an error (coloring: "fill")', function(done) {
var mock = plotDataElliptical(0.5);
mock.data[0].contours.coloring = 'fill'; // 'fill' is the default
mock.data[0].line = {smoothing: 0};
makePlot(gd, mock, done);
});
- it('should update properly', function(done) {
+ it('@gl should update properly', function(done) {
var mock = plotDataElliptical(0);
var scene2d;
diff --git a/test/jasmine/tests/gl2d_date_axis_render_test.js b/test/jasmine/tests/gl2d_date_axis_render_test.js
deleted file mode 100644
index 4e71e04b3a4..00000000000
--- a/test/jasmine/tests/gl2d_date_axis_render_test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-var Plotly = require('@lib');
-
-var createGraphDiv = require('../assets/create_graph_div');
-var destroyGraphDiv = require('../assets/destroy_graph_div');
-
-describe('date axis', function() {
- var gd;
-
- beforeEach(function() {
- gd = createGraphDiv();
- });
-
- afterEach(destroyGraphDiv);
-
- it('@gl should use the fancy gl-vis/gl-scatter2d', function() {
- Plotly.plot(gd, [{
- type: 'scattergl',
- 'marker': {
- 'color': 'rgb(31, 119, 180)',
- 'size': 18,
- 'symbol': [
- 'diamond',
- 'cross'
- ]
- },
- x: [new Date('2016-10-10'), new Date('2016-10-12')],
- y: [15, 16]
- }]);
-
- expect(gd._fullLayout.xaxis.type).toBe('date');
- expect(gd._fullLayout.yaxis.type).toBe('linear');
- expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
-
- // one way of check which renderer - fancy vs not - we're
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.scatter2d).toBeDefined();
- expect(scene.markerOptions[0].positions.length).toEqual(4);
- expect(scene.line2d).not.toBeUndefined();
- });
-
- it('@gl should use the fancy gl-vis/gl-scatter2d once again', function() {
- Plotly.plot(gd, [{
- type: 'scattergl',
- 'marker': {
- 'color': 'rgb(31, 119, 180)',
- 'size': 36,
- 'symbol': [
- 'circle',
- 'cross'
- ]
- },
- x: [new Date('2016-10-10'), new Date('2016-10-11')],
- y: [15, 16]
- }]);
-
- expect(gd._fullLayout.xaxis.type).toBe('date');
- expect(gd._fullLayout.yaxis.type).toBe('linear');
- expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
-
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.scatter2d).toBeDefined();
- expect(scene.markerOptions[0].positions.length).toEqual(4);
- expect(scene.line2d).toBeDefined();
- });
-
- it('@gl should now use the non-fancy gl-vis/gl-scatter2d', function() {
- Plotly.plot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: [new Date('2016-10-10'), new Date('2016-10-11')],
- y: [15, 16]
- }]);
-
- expect(gd._fullLayout.xaxis.type).toBe('date');
- expect(gd._fullLayout.yaxis.type).toBe('linear');
- expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
-
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.scatter2d).toBeDefined();
- expect(scene.markerOptions[0].positions.length).toEqual(4);
- expect(scene.line2d).toBeDefined();
- });
-
- it('@gl should use the non-fancy gl-vis/gl-scatter2d with string dates', function() {
- Plotly.plot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: ['2016-10-10', '2016-10-11'],
- y: [15, 16]
- }]);
-
- expect(gd._fullLayout.xaxis.type).toBe('date');
- expect(gd._fullLayout.yaxis.type).toBe('linear');
- expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
-
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.scatter2d).toBeDefined();
- expect(scene.markerOptions[0].positions.length).toEqual(4);
- expect(scene.line2d).toBeDefined();
- });
-});
diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js
index 2ff8b2e4f46..ad0ad94d800 100644
--- a/test/jasmine/tests/gl2d_plot_interact_test.js
+++ b/test/jasmine/tests/gl2d_plot_interact_test.js
@@ -4,17 +4,14 @@ var Plotly = require('@lib/index');
var Plots = require('@src/plots/plots');
var Lib = require('@src/lib');
var Drawing = require('@src/components/drawing');
-var ScatterGl = require('@src/traces/scattergl');
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
-var mouseEvent = require('../assets/mouse_event');
var touchEvent = require('../assets/touch_event');
var drag = require('../assets/drag');
var selectButton = require('../assets/modebar_button');
var delay = require('../assets/delay');
-var readPixel = require('../assets/read_pixel');
function countCanvases() {
return d3.selectAll('canvas').size();
@@ -313,9 +310,81 @@ describe('Test gl plot side effects', function() {
.catch(failTest)
.then(done);
});
+
+ it('@gl should be able to toggle from svg to gl', function(done) {
+ Plotly.plot(gd, [{
+ y: [1, 2, 1],
+ }])
+ .then(function() {
+ expect(countCanvases()).toBe(0);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
+
+ return Plotly.restyle(gd, 'type', 'scattergl');
+ })
+ .then(function() {
+ expect(countCanvases()).toBe(3);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0);
+
+ return Plotly.restyle(gd, 'type', 'scatter');
+ })
+ .then(function() {
+ expect(countCanvases()).toBe(0);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should create two WebGL contexts per graph', function(done) {
+ var fig = Lib.extendDeep({}, require('@mocks/gl2d_stacked_subplots.json'));
+
+ Plotly.plot(gd, fig).then(function() {
+ var cnt = 0;
+ d3.select(gd).selectAll('canvas').each(function(d) {
+ if(d.regl) cnt++;
+ });
+ expect(cnt).toBe(2);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should clear canvases on *replot* edits', function(done) {
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ y: [1, 2, 1]
+ }, {
+ type: 'scattergl',
+ y: [2, 1, 2]
+ }])
+ .then(function() {
+ expect(gd._fullLayout._glcanvas).toBeDefined();
+ expect(gd._fullLayout._glcanvas.size()).toBe(3);
+
+ expect(gd._fullLayout._glcanvas.data()[0].regl).toBeDefined();
+ expect(gd._fullLayout._glcanvas.data()[1].regl).toBeDefined();
+ // this is canvas is for parcoords only
+ expect(gd._fullLayout._glcanvas.data()[2].regl).toBeUndefined();
+
+ spyOn(gd._fullLayout._glcanvas.data()[0].regl, 'clear').and.callThrough();
+ spyOn(gd._fullLayout._glcanvas.data()[1].regl, 'clear').and.callThrough();
+
+ return Plotly.update(gd,
+ {visible: [false]},
+ {'xaxis.title': 'Tsdads', 'yaxis.ditck': 0.2},
+ [0]
+ );
+ })
+ .then(function() {
+ expect(gd._fullLayout._glcanvas.data()[0].regl.clear).toHaveBeenCalledTimes(1);
+ expect(gd._fullLayout._glcanvas.data()[1].regl.clear).toHaveBeenCalledTimes(1);
+ })
+ .catch(failTest)
+ .then(done);
+ });
});
-describe('Test gl2d plots', function() {
+describe('Test gl2d plot interactions:', function() {
var gd;
var mock = require('@mocks/gl2d_10.json');
@@ -337,26 +406,6 @@ describe('Test gl2d plots', function() {
return drag(node, dx, dy, null, p0[0], p0[1]);
}
- function select(path) {
- return new Promise(function(resolve) {
- gd.once('plotly_selected', resolve);
-
- var len = path.length;
-
- // do selection
- Lib.clearThrottle();
- mouseEvent('mousemove', path[0][0], path[0][1]);
- mouseEvent('mousedown', path[0][0], path[0][1]);
-
- path.slice(1, len).forEach(function(pt) {
- Lib.clearThrottle();
- mouseEvent('mousemove', pt[0], pt[1]);
- });
-
- mouseEvent('mouseup', path[len - 1][0], path[len - 1][1]);
- });
- }
-
it('@gl should respond to drag interactions', function(done) {
var _mock = Lib.extendDeep({}, mock);
@@ -463,195 +512,6 @@ describe('Test gl2d plots', function() {
.then(done);
});
- it('@gl should be able to toggle visibility', function(done) {
- var _mock = Lib.extendDeep({}, mock);
- _mock.data[0].line.width = 5;
-
- function assertDrawCall(msg, exp) {
- var draw = gd._fullLayout._plots.xy._scene.scatter2d.draw;
- expect(draw).toHaveBeenCalledTimes(exp, msg);
- draw.calls.reset();
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(30))
- .then(function() {
- spyOn(gd._fullLayout._plots.xy._scene.scatter2d, 'draw');
- return Plotly.restyle(gd, 'visible', 'legendonly');
- })
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0);
- assertDrawCall('legendonly', 0);
-
- return Plotly.restyle(gd, 'visible', true);
- })
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
- assertDrawCall('back to visible', 1);
-
- return Plotly.restyle(gd, 'visible', false);
- })
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0);
- assertDrawCall('visible false', 0);
-
- return Plotly.restyle(gd, 'visible', true);
- })
- .then(function() {
- assertDrawCall('back up', 1);
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should be able to toggle trace with different modes', function(done) {
- Plotly.newPlot(gd, [{
- // a trace with all regl2d objects
- type: 'scattergl',
- mode: 'lines+markers+text',
- y: [1, 2, 1],
- text: ['a', 'b', 'c'],
- error_x: {value: 10},
- error_y: {value: 10},
- fill: 'tozeroy'
- }, {
- type: 'scattergl',
- mode: 'markers',
- y: [0, 1, -1]
- }])
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- spyOn(scene.fill2d, 'draw');
- spyOn(scene.line2d, 'draw');
- spyOn(scene.error2d, 'draw');
- spyOn(scene.scatter2d, 'draw');
- spyOn(scene.glText[0], 'render');
-
- return Plotly.restyle(gd, 'visible', 'legendonly', [0]);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.fill2d.draw).toHaveBeenCalledTimes(0);
- expect(scene.line2d.draw).toHaveBeenCalledTimes(0);
- expect(scene.error2d.draw).toHaveBeenCalledTimes(0);
- expect(scene.glText[0].render).toHaveBeenCalledTimes(0);
- expect(scene.scatter2d.draw).toHaveBeenCalledTimes(1);
-
- return Plotly.restyle(gd, 'visible', true, [0]);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- expect(scene.fill2d.draw).toHaveBeenCalledTimes(1);
- expect(scene.line2d.draw).toHaveBeenCalledTimes(1);
- expect(scene.error2d.draw).toHaveBeenCalledTimes(2, 'twice for x AND y');
- expect(scene.glText[0].render).toHaveBeenCalledTimes(1);
- expect(scene.scatter2d.draw).toHaveBeenCalledTimes(3, 'both traces have markers');
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should display selection of big number of regular points', function(done) {
- // generate large number of points
- var x = [];
- var y = [];
- var n = 2e2;
- var N = n * n;
- for(var i = 0; i < N; i++) {
- x.push((i % n) / n);
- y.push(i / N);
- }
-
- var mock = {
- data: [{
- x: x, y: y, type: 'scattergl', mode: 'markers'
- }],
- layout: {
- dragmode: 'select'
- }
- };
-
- Plotly.plot(gd, mock)
- .then(select([[160, 100], [180, 100]]))
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 168, 100)[3]).toBe(0);
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 158, 100)[3]).not.toBe(0);
- expect(readPixel(gd.querySelector('.gl-canvas-focus'), 168, 100)[3]).not.toBe(0);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should display selection of big number of miscellaneous points', function(done) {
- var colorList = [
- '#006385', '#F06E75', '#90ed7d', '#f7a35c', '#8085e9',
- '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1',
- '#5DA5DA', '#F06E75', '#F15854', '#B2912F', '#B276B2',
- '#DECF3F', '#FAA43A', '#4D4D4D', '#F17CB0', '#60BD68'
- ];
-
- // generate large number of points
- var x = [];
- var y = [];
- var n = 2e2;
- var N = n * n;
- var color = [];
- var symbol = [];
- var size = [];
- for(var i = 0; i < N; i++) {
- x.push((i % n) / n);
- y.push(i / N);
- color.push(colorList[i % colorList.length]);
- symbol.push('x');
- size.push(6);
- }
-
- var mock = {
- data: [{
- x: x, y: y, type: 'scattergl', mode: 'markers',
- marker: {symbol: symbol, size: size, color: color}
- }],
- layout: {
- dragmode: 'select'
- }
- };
-
- Plotly.plot(gd, mock)
- .then(select([[160, 100], [180, 100]]))
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 168, 100)[3]).toBe(0);
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 158, 100)[3]).not.toBe(0);
- expect(readPixel(gd.querySelector('.gl-canvas-focus'), 168, 100)[3]).not.toBe(0);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should be able to toggle from svg to gl', function(done) {
- Plotly.plot(gd, [{
- y: [1, 2, 1],
- }])
- .then(function() {
- expect(countCanvases()).toBe(0);
- expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
-
- return Plotly.restyle(gd, 'type', 'scattergl');
- })
- .then(function() {
- expect(countCanvases()).toBe(3);
- expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0);
-
- return Plotly.restyle(gd, 'type', 'scatter');
- })
- .then(function() {
- expect(countCanvases()).toBe(0);
- expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
- })
- .catch(failTest)
- .then(done);
- });
-
it('@gl supports 1D and 2D Zoom', function(done) {
var centerX;
var centerY;
@@ -772,19 +632,6 @@ describe('Test gl2d plots', function() {
.then(done);
});
- it('@gl should change plot type with incomplete data', function(done) {
- Plotly.plot(gd, [{}]);
- expect(function() {
- Plotly.restyle(gd, {type: 'scattergl', x: [[1]]}, 0);
- }).not.toThrow();
-
- expect(function() {
- Plotly.restyle(gd, {y: [[1]]}, 0);
- }).not.toThrow();
-
- done();
- });
-
it('@gl data-referenced annotations should update on drag', function(done) {
function assertAnnotation(xy) {
var ann = d3.select('g.annotation-text-g').select('g');
@@ -881,527 +728,4 @@ describe('Test gl2d plots', function() {
.catch(failTest)
.then(done);
});
-
- it('@gl should restyle opacity', function(done) {
- // #2299
- spyOn(ScatterGl, 'calc').and.callThrough();
-
- var dat = [{
- 'x': [1, 2, 3],
- 'y': [1, 2, 3],
- 'type': 'scattergl',
- 'mode': 'markers'
- }];
-
- Plotly.plot(gd, dat, {width: 500, height: 500})
- .then(function() {
- expect(ScatterGl.calc).toHaveBeenCalledTimes(1);
-
- return Plotly.restyle(gd, {'opacity': 0.1});
- })
- .then(function() {
- expect(ScatterGl.calc).toHaveBeenCalledTimes(2);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should update selected points', function(done) {
- // #2298
- var dat = [{
- 'x': [1],
- 'y': [1],
- 'type': 'scattergl',
- 'mode': 'markers',
- 'selectedpoints': [0]
- }];
-
- Plotly.plot(gd, dat, {
- width: 500,
- height: 500,
- dragmode: 'select'
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
-
- expect(scene.count).toBe(1);
- expect(scene.selectBatch).toEqual([[0]]);
- expect(scene.unselectBatch).toEqual([[]]);
- spyOn(scene.scatter2d, 'draw');
-
- var trace = {
- x: [2],
- y: [1],
- text: ['a'],
- type: 'scattergl',
- mode: 'markers+text',
- marker: {color: 'red'}
- };
-
- return Plotly.addTraces(gd, trace);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
-
- expect(scene.count).toBe(2);
- expect(scene.selectBatch).toEqual([[0], []]);
- expect(scene.unselectBatch).toEqual([[], []]);
- expect(scene.markerOptions.length).toBe(2);
- expect(scene.markerOptions[1].color).toEqual(new Uint8Array([255, 0, 0, 255]));
- expect(scene.textOptions.length).toBe(2);
- expect(scene.textOptions[1].color).toEqual('#444');
- expect(scene.scatter2d.draw).toHaveBeenCalled();
-
- return Plotly.restyle(gd, 'selectedpoints', null);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- var msg = 'clearing under dragmode select';
-
- expect(scene.selectBatch).toEqual([[], []], msg);
- expect(scene.unselectBatch).toEqual([[], []], msg);
-
- // scattergl uses different pathways for select/lasso & zoom/pan
- return Plotly.relayout(gd, 'dragmode', 'pan');
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- var msg = 'cleared under dragmode pan';
-
- expect(scene.selectBatch).toEqual([[], []], msg);
- expect(scene.unselectBatch).toEqual([[], []], msg);
-
- return Plotly.restyle(gd, 'selectedpoints', [[1, 2], [0]]);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- var msg = 'selecting via API under dragmode pan';
-
- expect(scene.selectBatch).toEqual([[1, 2], [0]], msg);
- expect(scene.unselectBatch).toEqual([[0], []], msg);
-
- return Plotly.restyle(gd, 'selectedpoints', null);
- })
- .then(function() {
- var scene = gd._fullLayout._plots.xy._scene;
- var msg = 'clearing under dragmode pan';
-
- expect(scene.selectBatch).toEqual([[], []], msg);
- expect(scene.unselectBatch).toEqual([[], []], msg);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should remove fill2d', function(done) {
- var mock = require('@mocks/gl2d_axes_labels2.json');
-
- Plotly.plot(gd, mock.data, mock.layout)
- .then(delay(1000))
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 100, 80)[0]).not.toBe(0);
-
- return Plotly.restyle(gd, {fill: 'none'});
- })
- .then(function() {
- expect(readPixel(gd.querySelector('.gl-canvas-context'), 100, 80)[0]).toBe(0);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should be able to draw more than 4096 colors', function(done) {
- var x = [];
- var color = [];
- var N = 1e5;
- var w = 500;
- var h = 500;
-
- Lib.seedPseudoRandom();
-
- for(var i = 0; i < N; i++) {
- x.push(i);
- color.push(Lib.pseudoRandom());
- }
-
- Plotly.newPlot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: x,
- y: color,
- marker: {
- color: color,
- colorscale: [
- [0, 'rgb(255, 0, 0)'],
- [0.5, 'rgb(0, 255, 0)'],
- [1.0, 'rgb(0, 0, 255)']
- ]
- }
- }], {
- width: w,
- height: h,
- margin: {l: 0, t: 0, b: 0, r: 0}
- })
- .then(function() {
- var total = readPixel(gd.querySelector('.gl-canvas-context'), 0, 0, w, h)
- .reduce(function(acc, v) { return acc + v; }, 0);
-
- // the total value was 3777134 before PR
- // https://github.com/plotly/plotly.js/pull/2377
- // and 105545275 after.
- expect(total).toBeGreaterThan(4e6);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should work with typed array', function(done) {
- Plotly.plot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: new Float32Array([1, 2, 3]),
- y: new Float32Array([1, 2, 1]),
- marker: {
- size: 20,
- colorscale: [[0, 'gray'], [1, 'red']],
- cmin: 0,
- cmax: 1,
- showscale: true,
- color: new Float32Array([0, 0.5, 1.0])
- }
- }])
- .then(function() {
- var opts = gd.calcdata[0][0].t._scene.markerOptions[0];
-
- expect(opts.colors).toBeCloseTo2DArray([
- [0.5, 0.5, 0.5, 1],
- [0.75, 0.25, 0.25, 1],
- [1, 0, 0, 1]
- ]);
-
- expect(opts.positions)
- .toBeCloseToArray([1, 1, 2, 2, 3, 1]);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should create two WebGL contexts per graph', function(done) {
- var fig = Lib.extendDeep({}, require('@mocks/gl2d_stacked_subplots.json'));
-
- Plotly.plot(gd, fig).then(function() {
- var cnt = 0;
- d3.select(gd).selectAll('canvas').each(function(d) {
- if(d.regl) cnt++;
- });
- expect(cnt).toBe(2);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should handle transform traces properly (calcTransform case)', function(done) {
- spyOn(ScatterGl, 'calc').and.callThrough();
-
- Plotly.plot(gd, [{
- type: 'scattergl',
- x: [1, 2, 3],
- y: [1, 2, 1],
- transforms: [{
- type: 'filter',
- target: 'x',
- operation: '>',
- value: 1
- }]
- }])
- .then(function() {
- expect(ScatterGl.calc).toHaveBeenCalledTimes(2);
-
- var opts = gd.calcdata[0][0].t._scene.markerOptions;
- // length === 2 before #2677
- expect(opts.length).toBe(1);
-
- return Plotly.restyle(gd, 'selectedpoints', [[1]]);
- })
- .then(function() {
- // was === 1 before #2677
- var scene = gd.calcdata[0][0].t._scene;
- expect(scene.selectBatch[0]).toEqual([0]);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should handle transform traces properly (default transform case)', function(done) {
- spyOn(ScatterGl, 'calc').and.callThrough();
-
- Plotly.plot(gd, [{
- type: 'scattergl',
- x: [1, 2, 3],
- y: [1, 2, 1],
- transforms: [{
- type: 'groupby',
- groups: ['a', 'b', 'a']
- }]
- }])
- .then(function() {
- // twice per 'expanded' trace
- expect(ScatterGl.calc).toHaveBeenCalledTimes(4);
-
- // 'scene' from opts0 and opts1 is linked to the same object,
- // which has two items, one for each 'expanded' trace
- var opts0 = gd.calcdata[0][0].t._scene.markerOptions;
- expect(opts0.length).toBe(2);
-
- var opts1 = gd.calcdata[1][0].t._scene.markerOptions;
- expect(opts1.length).toBe(2);
-
- return Plotly.restyle(gd, 'selectedpoints', [[1]]);
- })
- .then(function() {
- var scene = gd.calcdata[0][0].t._scene;
- expect(scene.selectBatch).toEqual([[], [0]]);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should clear canvases on *replot* edits', function(done) {
- Plotly.plot(gd, [{
- type: 'scattergl',
- y: [1, 2, 1]
- }, {
- type: 'scattergl',
- y: [2, 1, 2]
- }])
- .then(function() {
- expect(gd._fullLayout._glcanvas).toBeDefined();
- expect(gd._fullLayout._glcanvas.size()).toBe(3);
-
- expect(gd._fullLayout._glcanvas.data()[0].regl).toBeDefined();
- expect(gd._fullLayout._glcanvas.data()[1].regl).toBeDefined();
- // this is canvas is for parcoords only
- expect(gd._fullLayout._glcanvas.data()[2].regl).toBeUndefined();
-
- spyOn(gd._fullLayout._glcanvas.data()[0].regl, 'clear').and.callThrough();
- spyOn(gd._fullLayout._glcanvas.data()[1].regl, 'clear').and.callThrough();
-
- return Plotly.update(gd,
- {visible: [false]},
- {'xaxis.title': 'Tsdads', 'yaxis.ditck': 0.2},
- [0]
- );
- })
- .then(function() {
- expect(gd._fullLayout._glcanvas.data()[0].regl.clear).toHaveBeenCalledTimes(1);
- expect(gd._fullLayout._glcanvas.data()[1].regl.clear).toHaveBeenCalledTimes(1);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should not cause infinite loops when coordinate arrays start/end with NaN', function(done) {
- function _assertPositions(msg, cont, exp) {
- var pos = gd._fullLayout._plots.xy._scene[cont]
- .map(function(opt) { return opt.positions; });
- expect(pos).toBeCloseTo2DArray(exp, 2, msg);
- }
-
- Plotly.plot(gd, [{
- type: 'scattergl',
- mode: 'lines',
- x: [1, 2, 3],
- y: [null, null, null]
- }, {
- type: 'scattergl',
- mode: 'lines',
- x: [1, 2, 3],
- y: [1, 2, null]
- }, {
- type: 'scattergl',
- mode: 'lines',
- x: [null, 2, 3],
- y: [1, 2, 3]
- }, {
- type: 'scattergl',
- mode: 'lines',
- x: [null, null, null],
- y: [1, 2, 3]
- }, {
- }])
- .then(function() {
- _assertPositions('base', 'lineOptions', [
- [],
- [1, 1, 2, 2],
- [2, 2, 3, 3],
- []
- ]);
-
- return Plotly.restyle(gd, 'fill', 'tozerox');
- })
- .then(function() {
- _assertPositions('tozerox', 'lineOptions', [
- [],
- [1, 1, 2, 2],
- [2, 2, 3, 3],
- []
- ]);
- _assertPositions('tozerox', 'fillOptions', [
- [0, undefined, 0, undefined],
- [0, 1, 1, 1, 2, 2, 0, 2],
- [0, 2, 2, 2, 3, 3, 0, 3],
- [0, undefined, 0, undefined]
- ]);
-
- return Plotly.restyle(gd, 'fill', 'tozeroy');
- })
- .then(function() {
- _assertPositions('tozeroy', 'lineOptions', [
- [],
- [1, 1, 2, 2],
- [2, 2, 3, 3],
- []
- ]);
- _assertPositions('tozeroy', 'fillOptions', [
- [undefined, 0, undefined, 0],
- [1, 0, 1, 1, 2, 2, 2, 0],
- [2, 0, 2, 2, 3, 3, 3, 0],
- [undefined, 0, undefined, 0]
- ]);
- })
- .catch(failTest)
- .then(done);
- });
-});
-
-describe('Test scattergl autorange:', function() {
- describe('should return the same value as SVG scatter for ~small~ data', function() {
- var gd;
-
- beforeEach(function() {
- jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
- gd = createGraphDiv();
- });
-
- afterEach(function() {
- Plotly.purge(gd);
- destroyGraphDiv();
- });
-
- var specs = [
- {name: 'lines+markers', fig: require('@mocks/gl2d_10.json')},
- {name: 'bubbles', fig: require('@mocks/gl2d_12.json')},
- {name: 'line on log axes', fig: require('@mocks/gl2d_14.json')},
- {name: 'fill to zero', fig: require('@mocks/gl2d_axes_labels2.json')},
- {name: 'annotations', fig: require('@mocks/gl2d_annotations.json')}
- ];
-
- specs.forEach(function(s) {
- it('@gl - case ' + s.name, function(done) {
- var glRangeX;
- var glRangeY;
-
- // ensure the mocks have auto-range turned on
- var glFig = Lib.extendDeep({}, s.fig);
- Lib.extendDeep(glFig.layout, {xaxis: {autorange: true}});
- Lib.extendDeep(glFig.layout, {yaxis: {autorange: true}});
-
- var svgFig = Lib.extendDeep({}, glFig);
- svgFig.data.forEach(function(t) { t.type = 'scatter'; });
-
- Plotly.newPlot(gd, glFig).then(function() {
- glRangeX = gd._fullLayout.xaxis.range;
- glRangeY = gd._fullLayout.yaxis.range;
- })
- .then(function() {
- return Plotly.newPlot(gd, svgFig);
- })
- .then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray(glRangeX, 'x range');
- expect(gd._fullLayout.yaxis.range).toBeCloseToArray(glRangeY, 'y range');
- })
- .catch(failTest)
- .then(done);
- });
- });
- });
-
- describe('should return the approximative values for ~big~ data', function() {
- var gd;
-
- beforeEach(function() {
- jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
- gd = createGraphDiv();
- // to avoid expansive draw calls (which could be problematic on CI)
- spyOn(ScatterGl, 'plot').and.callFake(function(gd) {
- gd._fullLayout._plots.xy._scene.scatter2d = {draw: function() {}};
- gd._fullLayout._plots.xy._scene.line2d = {draw: function() {}};
- });
- });
-
- afterEach(function() {
- Plotly.purge(gd);
- destroyGraphDiv();
- });
-
- // threshold for 'fast' axis expansion routine
- var N = 1e5;
- var x = new Array(N);
- var y = new Array(N);
- var ms = new Array(N);
-
- Lib.seedPseudoRandom();
-
- for(var i = 0; i < N; i++) {
- x[i] = Lib.pseudoRandom();
- y[i] = Lib.pseudoRandom();
- ms[i] = 10 * Lib.pseudoRandom() + 20;
- }
-
- it('@gl - case scalar marker.size', function(done) {
- Plotly.newPlot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: x,
- y: y,
- marker: {size: 10}
- }])
- .then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.079, 1.079], 2, 'x range');
- expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.105, 1.105], 2, 'y range');
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl - case array marker.size', function(done) {
- Plotly.newPlot(gd, [{
- type: 'scattergl',
- mode: 'markers',
- x: x,
- y: y,
- marker: {size: ms}
- }])
- .then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.119, 1.119], 2, 'x range');
- expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.199, 1.199], 2, 'y range');
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl - case mode:lines', function(done) {
- Plotly.newPlot(gd, [{
- type: 'scattergl',
- mode: 'lines',
- y: y,
- }])
- .then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([0, N - 1], 2, 'x range');
- expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.0555, 1.0555], 2, 'y range');
- })
- .catch(failTest)
- .then(done);
- });
- });
});
diff --git a/test/jasmine/tests/gl3d_hover_click_test.js b/test/jasmine/tests/gl3d_hover_click_test.js
new file mode 100644
index 00000000000..2492b76a8f0
--- /dev/null
+++ b/test/jasmine/tests/gl3d_hover_click_test.js
@@ -0,0 +1,542 @@
+var Plotly = require('@lib/index');
+var Lib = require('@src/lib');
+
+var d3 = require('d3');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+var failTest = require('../assets/fail_test');
+var mouseEvent = require('../assets/mouse_event');
+var delay = require('../assets/delay');
+
+var customAssertions = require('../assets/custom_assertions');
+var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
+var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+
+var mock = require('@mocks/gl3d_marker-arrays.json');
+var multipleScatter3dMock = require('@mocks/gl3d_multiple-scatter3d-traces.json');
+
+// lines, markers, text, error bars and surfaces each
+// correspond to one glplot object
+var mock2 = Lib.extendDeep({}, mock);
+mock2.data[0].mode = 'lines+markers+text';
+mock2.data[0].error_z = { value: 10 };
+mock2.data[0].surfaceaxis = 2;
+mock2.layout.showlegend = true;
+
+var mock3 = require('@mocks/gl3d_autocolorscale');
+
+describe('Test gl3d trace click/hover:', function() {
+ var gd, ptData;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ ptData = {};
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000;
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ function assertHoverText(xLabel, yLabel, zLabel, textLabel) {
+ var content = [];
+ if(xLabel) content.push(xLabel);
+ if(yLabel) content.push(yLabel);
+ if(zLabel) content.push(zLabel);
+ if(textLabel) content.push(textLabel);
+ assertHoverLabelContent({nums: content.join('\n')});
+ }
+
+ function assertEventData(x, y, z, curveNumber, pointNumber, extra) {
+ expect(Object.keys(ptData)).toEqual(jasmine.arrayContaining([
+ 'x', 'y', 'z',
+ 'data', 'fullData', 'curveNumber', 'pointNumber'
+ ]), 'correct hover data fields');
+
+ expect(ptData.x).toEqual(x, 'x val');
+ expect(ptData.y).toEqual(y, 'y val');
+ expect(ptData.z).toEqual(z, 'z val');
+ expect(ptData.curveNumber).toEqual(curveNumber, 'curveNumber');
+ expect(ptData.pointNumber).toEqual(pointNumber, 'pointNumber');
+
+ Object.keys(extra || {}).forEach(function(k) {
+ expect(ptData[k]).toBe(extra[k], k + ' val');
+ });
+ }
+
+ it('@gl should display correct hover labels of the second point of the very first scatter3d trace', function(done) {
+ var _mock = Lib.extendDeep({}, multipleScatter3dMock);
+
+ function _hover() {
+ mouseEvent('mouseover', 300, 200);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverLabelContent(
+ {
+ nums: ['x: 0', 'y: 0', 'z: 0'].join('\n'),
+ name: 'trace 0'
+ }
+ );
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should honor *hoverlabel.namelength*', function(done) {
+ var _mock = Lib.extendDeep({}, multipleScatter3dMock);
+
+ function _hover() {
+ mouseEvent('mouseover', 300, 200);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() { return Plotly.restyle(gd, 'hoverlabel.namelength', 3); })
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverLabelContent(
+ {
+ nums: ['x: 0', 'y: 0', 'z: 0'].join('\n'),
+ name: 'tra'
+ }
+ );
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should display correct hover labels and emit correct event data (scatter3d case)', function(done) {
+ var _mock = Lib.extendDeep({}, mock2);
+
+ function _hover() {
+ mouseEvent('mouseover', 0, 0);
+ mouseEvent('mouseover', 655, 221);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 100.75', 'y: −102.63', 'z: −102.63');
+ assertEventData(100.75, -102.63, -102.63, 0, 0, {
+ 'marker.symbol': 'circle',
+ 'marker.size': 10,
+ 'marker.color': 'blue',
+ 'marker.line.color': 'black'
+ });
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(0, 0, 255)',
+ bordercolor: 'rgb(255, 255, 255)',
+ fontSize: 13,
+ fontFamily: 'Arial',
+ fontColor: 'rgb(255, 255, 255)'
+ }, 'initial');
+
+ return Plotly.restyle(gd, {
+ x: [['2016-01-11', '2016-01-12', '2017-01-01', '2017-02-01']]
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: Jan 11, 2016', 'y: −102.63', 'z: −102.63');
+
+ return Plotly.restyle(gd, {
+ x: [[new Date(2017, 2, 1), new Date(2017, 2, 2), new Date(2017, 2, 3), new Date(2017, 2, 4)]]
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: Mar 1, 2017', 'y: −102.63', 'z: −102.63');
+
+ return Plotly.update(gd, {
+ y: [['a', 'b', 'c', 'd']],
+ z: [[10, 1e3, 1e5, 1e10]]
+ }, {
+ 'scene.zaxis.type': 'log'
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: Mar 1, 2017', 'y: a', 'z: 10');
+
+ return Plotly.relayout(gd, 'scene.xaxis.calendar', 'chinese');
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10');
+
+ return Plotly.restyle(gd, 'text', [['A', 'B', 'C', 'D']]);
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'A');
+
+ return Plotly.restyle(gd, 'hovertext', [['Apple', 'Banana', 'Clementine', 'Dragon fruit']]);
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'Apple');
+
+ return Plotly.restyle(gd, {
+ 'hoverlabel.bgcolor': [['red', 'blue', 'green', 'yellow']],
+ 'hoverlabel.font.size': 20
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(255, 0, 0)',
+ bordercolor: 'rgb(255, 255, 255)',
+ fontSize: 20,
+ fontFamily: 'Arial',
+ fontColor: 'rgb(255, 255, 255)'
+ }, 'restyled');
+
+ return Plotly.relayout(gd, {
+ 'hoverlabel.bordercolor': 'yellow',
+ 'hoverlabel.font.color': 'cyan',
+ 'hoverlabel.font.family': 'Roboto'
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(255, 0, 0)',
+ bordercolor: 'rgb(255, 255, 0)',
+ fontSize: 20,
+ fontFamily: 'Roboto',
+ fontColor: 'rgb(0, 255, 255)'
+ }, 'restyle #2');
+
+ return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'y', null]]);
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ var label = d3.selectAll('g.hovertext');
+
+ expect(label.size()).toEqual(1);
+ expect(label.select('text').text()).toEqual('x: 二 4, 2017y: az: 10Apple');
+
+ return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'dont+know', null]]);
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'Apple');
+
+ return Plotly.restyle(gd, 'hoverinfo', 'text');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'Apple');
+
+ return Plotly.restyle(gd, 'hovertext', 'HEY');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'HEY');
+
+ return Plotly.restyle(gd, 'hoverinfo', 'z');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, '10');
+
+ return Plotly.restyle(gd, 'hovertemplate', 'THIS Y -- %{y}');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'THIS Y -- a');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should display correct hover labels and emit correct event data (surface case with connectgaps enabled)', function(done) {
+ var surfaceConnectgaps = require('@mocks/gl3d_surface_connectgaps');
+ var _mock = Lib.extendDeep({}, surfaceConnectgaps);
+
+ function _hover() {
+ mouseEvent('mouseover', 300, 200);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 0.2', 'y: 2', 'z: 1,001.25');
+ assertEventData(0.2, 2, 1001.25, 0, [1, 2]);
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(68, 68, 68)',
+ bordercolor: 'rgb(255, 255, 255)',
+ fontSize: 13,
+ fontFamily: 'Arial',
+ fontColor: 'rgb(255, 255, 255)'
+ }, 'initial');
+ })
+ .then(done);
+ });
+
+ it('@gl should display correct hover labels and emit correct event data (surface case)', function(done) {
+ var _mock = Lib.extendDeep({}, mock3);
+
+ function _hover() {
+ mouseEvent('mouseover', 605, 271);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ gd.on('plotly_hover', function(eventData) {
+ ptData = eventData.points[0];
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 1', 'y: 2', 'z: 43', 'one two');
+ assertEventData(1, 2, 43, 0, [1, 2]);
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(68, 68, 68)',
+ bordercolor: 'rgb(255, 255, 255)',
+ fontSize: 13,
+ fontFamily: 'Arial',
+ fontColor: 'rgb(255, 255, 255)'
+ }, 'initial');
+
+ return Plotly.restyle(gd, {
+ 'hoverinfo': [[
+ ['all', 'all', 'all'],
+ ['all', 'all', 'y'],
+ ['all', 'all', 'all']
+ ]],
+ 'hoverlabel.bgcolor': 'white',
+ 'hoverlabel.font.size': 9,
+ 'hoverlabel.font.color': [[
+ ['red', 'blue', 'green'],
+ ['pink', 'purple', 'cyan'],
+ ['black', 'orange', 'yellow']
+ ]]
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertEventData(1, 2, 43, 0, [1, 2], {
+ 'hoverinfo': 'y',
+ 'hoverlabel.font.color': 'cyan'
+ });
+ assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
+ bgcolor: 'rgb(255, 255, 255)',
+ bordercolor: 'rgb(68, 68, 68)',
+ fontSize: 9,
+ fontFamily: 'Arial',
+ fontColor: 'rgb(0, 255, 255)'
+ }, 'restyle');
+
+ var label = d3.selectAll('g.hovertext');
+
+ expect(label.size()).toEqual(1);
+ expect(label.select('text').text()).toEqual('2');
+
+ return Plotly.restyle(gd, {
+ 'colorbar.tickvals': [[25]],
+ 'colorbar.ticktext': [['single tick!']]
+ });
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertEventData(1, 2, 43, 0, [1, 2], {
+ 'hoverinfo': 'y',
+ 'hoverlabel.font.color': 'cyan',
+ 'colorbar.tickvals': undefined,
+ 'colorbar.ticktext': undefined
+ });
+
+ return Plotly.restyle(gd, 'hoverinfo', 'z');
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, '43');
+
+ return Plotly.restyle(gd, 'hoverinfo', 'text');
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'one two');
+
+ return Plotly.restyle(gd, 'text', 'yo!');
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'yo!');
+
+ return Plotly.restyle(gd, 'hovertext', 'ONE TWO');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'ONE TWO');
+
+ return Plotly.restyle(gd, 'hovertemplate', '!!! %{z} !!!');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, '!!! 43 !!!');
+ })
+ .then(done);
+ });
+
+ it('@gl should emit correct event data on click (scatter3d case)', function(done) {
+ var _mock = Lib.extendDeep({}, mock2);
+
+ // N.B. gl3d click events are 'mouseover' events
+ // with button 1 pressed
+ function _click() {
+ mouseEvent('mouseover', 605, 271, {buttons: 1});
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ gd.on('plotly_click', function(eventData) {
+ ptData = eventData.points[0];
+ });
+ })
+ .then(delay(20))
+ .then(_click)
+ .then(delay(20))
+ .then(function() {
+ assertEventData(134.03, -163.59, -163.59, 0, 3);
+ })
+ .then(done);
+ });
+
+ it('@gl should display correct hover labels (mesh3d case)', function(done) {
+ var x = [1, 1, 2, 3, 4, 2];
+ var y = [2, 1, 3, 4, 5, 3];
+ var z = [3, 7, 4, 5, 3.5, 2];
+ var text = x.map(function(_, i) {
+ return [
+ 'ts: ' + x[i],
+ 'hz: ' + y[i],
+ 'ftt:' + z[i]
+ ].join('
');
+ });
+
+ function _hover() {
+ mouseEvent('mouseover', 250, 250);
+ }
+
+ Plotly.newPlot(gd, [{
+ type: 'mesh3d',
+ x: x,
+ y: y,
+ z: z,
+ text: text
+ }], {
+ width: 500,
+ height: 500
+ })
+ .then(delay(20))
+ .then(_hover)
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('x: 4', 'y: 5', 'z: 3.5', 'ts: 4\nhz: 5\nftt:3.5');
+ })
+ .then(function() {
+ return Plotly.restyle(gd, 'hoverinfo', 'x+y');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('(4, 5)');
+ })
+ .then(function() {
+ return Plotly.restyle(gd, 'hoverinfo', 'text');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText('ts: 4\nhz: 5\nftt:3.5');
+ })
+ .then(function() {
+ return Plotly.restyle(gd, 'text', 'yo!');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'yo!');
+ })
+ .then(function() {
+ return Plotly.restyle(gd, 'hovertext', [
+ text.map(function(tx) { return tx + ' !!'; })
+ ]);
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, 'ts: 4\nhz: 5\nftt:3.5 !!');
+ })
+ .then(function() {
+ return Plotly.restyle(gd, 'hovertemplate', '%{x}-%{y}-%{z}');
+ })
+ .then(delay(20))
+ .then(function() {
+ assertHoverText(null, null, null, '4-5-3.5');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+});
diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js
index 56d14c72202..821406f09bf 100644
--- a/test/jasmine/tests/gl3d_plot_interact_test.js
+++ b/test/jasmine/tests/gl3d_plot_interact_test.js
@@ -13,14 +13,6 @@ var touchEvent = require('../assets/touch_event');
var selectButton = require('../assets/modebar_button');
var delay = require('../assets/delay');
-var customAssertions = require('../assets/custom_assertions');
-var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
-var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
-
-function countCanvases() {
- return d3.selectAll('canvas').size();
-}
-
describe('Test gl3d before/after plot', function() {
var gd;
@@ -148,51 +140,10 @@ describe('Test gl3d before/after plot', function() {
});
describe('Test gl3d plots', function() {
- var gd, ptData;
-
- var mock = require('@mocks/gl3d_marker-arrays.json');
- var multipleScatter3dMock = require('@mocks/gl3d_multiple-scatter3d-traces.json');
-
- // lines, markers, text, error bars and surfaces each
- // correspond to one glplot object
- var mock2 = Lib.extendDeep({}, mock);
- mock2.data[0].mode = 'lines+markers+text';
- mock2.data[0].error_z = { value: 10 };
- mock2.data[0].surfaceaxis = 2;
- mock2.layout.showlegend = true;
-
- var mock3 = require('@mocks/gl3d_autocolorscale');
-
- function assertHoverText(xLabel, yLabel, zLabel, textLabel) {
- var content = [];
- if(xLabel) content.push(xLabel);
- if(yLabel) content.push(yLabel);
- if(zLabel) content.push(zLabel);
- if(textLabel) content.push(textLabel);
- assertHoverLabelContent({nums: content.join('\n')});
- }
-
- function assertEventData(x, y, z, curveNumber, pointNumber, extra) {
- expect(Object.keys(ptData)).toEqual(jasmine.arrayContaining([
- 'x', 'y', 'z',
- 'data', 'fullData', 'curveNumber', 'pointNumber'
- ]), 'correct hover data fields');
-
- expect(ptData.x).toEqual(x, 'x val');
- expect(ptData.y).toEqual(y, 'y val');
- expect(ptData.z).toEqual(z, 'z val');
- expect(ptData.curveNumber).toEqual(curveNumber, 'curveNumber');
- expect(ptData.pointNumber).toEqual(pointNumber, 'pointNumber');
-
- Object.keys(extra || {}).forEach(function(k) {
- expect(ptData[k]).toBe(extra[k], k + ' val');
- });
- }
+ var gd;
beforeEach(function() {
gd = createGraphDiv();
- ptData = {};
-
jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000;
});
@@ -201,481 +152,6 @@ describe('Test gl3d plots', function() {
destroyGraphDiv();
});
- it('@gl should display correct hover labels of the second point of the very first scatter3d trace', function(done) {
- var _mock = Lib.extendDeep({}, multipleScatter3dMock);
-
- function _hover() {
- mouseEvent('mouseover', 300, 200);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- gd.on('plotly_hover', function(eventData) {
- ptData = eventData.points[0];
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverLabelContent(
- {
- nums: ['x: 0', 'y: 0', 'z: 0'].join('\n'),
- name: 'trace 0'
- }
- );
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should honor *hoverlabel.namelength*', function(done) {
- var _mock = Lib.extendDeep({}, multipleScatter3dMock);
-
- function _hover() {
- mouseEvent('mouseover', 300, 200);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() { return Plotly.restyle(gd, 'hoverlabel.namelength', 3); })
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverLabelContent(
- {
- nums: ['x: 0', 'y: 0', 'z: 0'].join('\n'),
- name: 'tra'
- }
- );
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should display correct hover labels and emit correct event data (scatter3d case)', function(done) {
- var _mock = Lib.extendDeep({}, mock2);
-
- function _hover() {
- mouseEvent('mouseover', 0, 0);
- mouseEvent('mouseover', 655, 221);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- gd.on('plotly_hover', function(eventData) {
- ptData = eventData.points[0];
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 100.75', 'y: −102.63', 'z: −102.63');
- assertEventData(100.75, -102.63, -102.63, 0, 0, {
- 'marker.symbol': 'circle',
- 'marker.size': 10,
- 'marker.color': 'blue',
- 'marker.line.color': 'black'
- });
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(0, 0, 255)',
- bordercolor: 'rgb(255, 255, 255)',
- fontSize: 13,
- fontFamily: 'Arial',
- fontColor: 'rgb(255, 255, 255)'
- }, 'initial');
-
- return Plotly.restyle(gd, {
- x: [['2016-01-11', '2016-01-12', '2017-01-01', '2017-02-01']]
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: Jan 11, 2016', 'y: −102.63', 'z: −102.63');
-
- return Plotly.restyle(gd, {
- x: [[new Date(2017, 2, 1), new Date(2017, 2, 2), new Date(2017, 2, 3), new Date(2017, 2, 4)]]
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: Mar 1, 2017', 'y: −102.63', 'z: −102.63');
-
- return Plotly.update(gd, {
- y: [['a', 'b', 'c', 'd']],
- z: [[10, 1e3, 1e5, 1e10]]
- }, {
- 'scene.zaxis.type': 'log'
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: Mar 1, 2017', 'y: a', 'z: 10');
-
- return Plotly.relayout(gd, 'scene.xaxis.calendar', 'chinese');
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10');
-
- return Plotly.restyle(gd, 'text', [['A', 'B', 'C', 'D']]);
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'A');
-
- return Plotly.restyle(gd, 'hovertext', [['Apple', 'Banana', 'Clementine', 'Dragon fruit']]);
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'Apple');
-
- return Plotly.restyle(gd, {
- 'hoverlabel.bgcolor': [['red', 'blue', 'green', 'yellow']],
- 'hoverlabel.font.size': 20
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(255, 0, 0)',
- bordercolor: 'rgb(255, 255, 255)',
- fontSize: 20,
- fontFamily: 'Arial',
- fontColor: 'rgb(255, 255, 255)'
- }, 'restyled');
-
- return Plotly.relayout(gd, {
- 'hoverlabel.bordercolor': 'yellow',
- 'hoverlabel.font.color': 'cyan',
- 'hoverlabel.font.family': 'Roboto'
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(255, 0, 0)',
- bordercolor: 'rgb(255, 255, 0)',
- fontSize: 20,
- fontFamily: 'Roboto',
- fontColor: 'rgb(0, 255, 255)'
- }, 'restyle #2');
-
- return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'y', null]]);
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- var label = d3.selectAll('g.hovertext');
-
- expect(label.size()).toEqual(1);
- expect(label.select('text').text()).toEqual('x: 二 4, 2017y: az: 10Apple');
-
- return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'dont+know', null]]);
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 二 4, 2017', 'y: a', 'z: 10', 'Apple');
-
- return Plotly.restyle(gd, 'hoverinfo', 'text');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'Apple');
-
- return Plotly.restyle(gd, 'hovertext', 'HEY');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'HEY');
-
- return Plotly.restyle(gd, 'hoverinfo', 'z');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, '10');
-
- return Plotly.restyle(gd, 'hovertemplate', 'THIS Y -- %{y}');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'THIS Y -- a');
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should display correct hover labels and emit correct event data (surface case with connectgaps enabled)', function(done) {
- var surfaceConnectgaps = require('@mocks/gl3d_surface_connectgaps');
- var _mock = Lib.extendDeep({}, surfaceConnectgaps);
-
- function _hover() {
- mouseEvent('mouseover', 300, 200);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- gd.on('plotly_hover', function(eventData) {
- ptData = eventData.points[0];
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 0.2', 'y: 2', 'z: 1,001.25');
- assertEventData(0.2, 2, 1001.25, 0, [1, 2]);
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(68, 68, 68)',
- bordercolor: 'rgb(255, 255, 255)',
- fontSize: 13,
- fontFamily: 'Arial',
- fontColor: 'rgb(255, 255, 255)'
- }, 'initial');
- })
- .then(done);
- });
-
- it('@gl should display correct hover labels and emit correct event data (surface case)', function(done) {
- var _mock = Lib.extendDeep({}, mock3);
-
- function _hover() {
- mouseEvent('mouseover', 605, 271);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- gd.on('plotly_hover', function(eventData) {
- ptData = eventData.points[0];
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 1', 'y: 2', 'z: 43', 'one two');
- assertEventData(1, 2, 43, 0, [1, 2]);
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(68, 68, 68)',
- bordercolor: 'rgb(255, 255, 255)',
- fontSize: 13,
- fontFamily: 'Arial',
- fontColor: 'rgb(255, 255, 255)'
- }, 'initial');
-
- return Plotly.restyle(gd, {
- 'hoverinfo': [[
- ['all', 'all', 'all'],
- ['all', 'all', 'y'],
- ['all', 'all', 'all']
- ]],
- 'hoverlabel.bgcolor': 'white',
- 'hoverlabel.font.size': 9,
- 'hoverlabel.font.color': [[
- ['red', 'blue', 'green'],
- ['pink', 'purple', 'cyan'],
- ['black', 'orange', 'yellow']
- ]]
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertEventData(1, 2, 43, 0, [1, 2], {
- 'hoverinfo': 'y',
- 'hoverlabel.font.color': 'cyan'
- });
- assertHoverLabelStyle(d3.selectAll('g.hovertext'), {
- bgcolor: 'rgb(255, 255, 255)',
- bordercolor: 'rgb(68, 68, 68)',
- fontSize: 9,
- fontFamily: 'Arial',
- fontColor: 'rgb(0, 255, 255)'
- }, 'restyle');
-
- var label = d3.selectAll('g.hovertext');
-
- expect(label.size()).toEqual(1);
- expect(label.select('text').text()).toEqual('2');
-
- return Plotly.restyle(gd, {
- 'colorbar.tickvals': [[25]],
- 'colorbar.ticktext': [['single tick!']]
- });
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertEventData(1, 2, 43, 0, [1, 2], {
- 'hoverinfo': 'y',
- 'hoverlabel.font.color': 'cyan',
- 'colorbar.tickvals': undefined,
- 'colorbar.ticktext': undefined
- });
-
- return Plotly.restyle(gd, 'hoverinfo', 'z');
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, '43');
-
- return Plotly.restyle(gd, 'hoverinfo', 'text');
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'one two');
-
- return Plotly.restyle(gd, 'text', 'yo!');
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'yo!');
-
- return Plotly.restyle(gd, 'hovertext', 'ONE TWO');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'ONE TWO');
-
- return Plotly.restyle(gd, 'hovertemplate', '!!! %{z} !!!');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, '!!! 43 !!!');
- })
- .then(done);
- });
-
- it('@gl should emit correct event data on click (scatter3d case)', function(done) {
- var _mock = Lib.extendDeep({}, mock2);
-
- // N.B. gl3d click events are 'mouseover' events
- // with button 1 pressed
- function _click() {
- mouseEvent('mouseover', 605, 271, {buttons: 1});
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- gd.on('plotly_click', function(eventData) {
- ptData = eventData.points[0];
- });
- })
- .then(delay(20))
- .then(_click)
- .then(delay(20))
- .then(function() {
- assertEventData(134.03, -163.59, -163.59, 0, 3);
- })
- .then(done);
- });
-
- it('@gl should display correct hover labels (mesh3d case)', function(done) {
- var x = [1, 1, 2, 3, 4, 2];
- var y = [2, 1, 3, 4, 5, 3];
- var z = [3, 7, 4, 5, 3.5, 2];
- var text = x.map(function(_, i) {
- return [
- 'ts: ' + x[i],
- 'hz: ' + y[i],
- 'ftt:' + z[i]
- ].join('
');
- });
-
- function _hover() {
- mouseEvent('mouseover', 250, 250);
- }
-
- Plotly.newPlot(gd, [{
- type: 'mesh3d',
- x: x,
- y: y,
- z: z,
- text: text
- }], {
- width: 500,
- height: 500
- })
- .then(delay(20))
- .then(_hover)
- .then(delay(20))
- .then(function() {
- assertHoverText('x: 4', 'y: 5', 'z: 3.5', 'ts: 4\nhz: 5\nftt:3.5');
- })
- .then(function() {
- return Plotly.restyle(gd, 'hoverinfo', 'x+y');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText('(4, 5)');
- })
- .then(function() {
- return Plotly.restyle(gd, 'hoverinfo', 'text');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText('ts: 4\nhz: 5\nftt:3.5');
- })
- .then(function() {
- return Plotly.restyle(gd, 'text', 'yo!');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'yo!');
- })
- .then(function() {
- return Plotly.restyle(gd, 'hovertext', [
- text.map(function(tx) { return tx + ' !!'; })
- ]);
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, 'ts: 4\nhz: 5\nftt:3.5 !!');
- })
- .then(function() {
- return Plotly.restyle(gd, 'hovertemplate', '%{x}-%{y}-%{z}');
- })
- .then(delay(20))
- .then(function() {
- assertHoverText(null, null, null, '4-5-3.5');
- })
- .catch(failTest)
- .then(done);
- });
-
it('@gl should set the camera dragmode to orbit if the camera.up.z vector is set to be tilted', function(done) {
Plotly.plot(gd, {
data: [{
@@ -905,196 +381,6 @@ describe('Test gl3d plots', function() {
.then(done);
});
- it('@gl should be able to reversibly change trace type', function(done) {
- var _mock = Lib.extendDeep({}, mock2);
- var sceneLayout = { aspectratio: { x: 1, y: 1, z: 1 } };
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- expect(countCanvases()).toEqual(1);
- expect(gd.layout.scene).toEqual(sceneLayout);
- expect(gd.layout.xaxis === undefined).toBe(true);
- expect(gd.layout.yaxis === undefined).toBe(true);
- expect(gd._fullLayout._has('gl3d')).toBe(true);
- expect(gd._fullLayout.scene._scene).toBeDefined();
- expect(gd._fullLayout.scene._scene.camera).toBeDefined(true);
-
- return Plotly.restyle(gd, 'type', 'scatter');
- })
- .then(function() {
- expect(countCanvases()).toEqual(0);
- expect(gd.layout.scene).toEqual(sceneLayout);
- expect(gd.layout.xaxis).toBeDefined();
- expect(gd.layout.yaxis).toBeDefined();
- expect(gd._fullLayout._has('gl3d')).toBe(false);
- expect(gd._fullLayout.scene === undefined).toBe(true);
-
- return Plotly.restyle(gd, 'type', 'scatter3d');
- })
- .then(function() {
- expect(countCanvases()).toEqual(1);
- expect(gd.layout.scene).toEqual(sceneLayout);
- expect(gd.layout.xaxis).toBeDefined();
- expect(gd.layout.yaxis).toBeDefined();
- expect(gd._fullLayout._has('gl3d')).toBe(true);
- expect(gd._fullLayout.scene._scene).toBeDefined();
- })
- .then(done);
- });
-
- it('@gl should be able to delete the last trace', function(done) {
- var _mock = Lib.extendDeep({}, mock2);
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- return Plotly.deleteTraces(gd, [0]);
- })
- .then(function() {
- expect(countCanvases()).toEqual(0);
- expect(gd._fullLayout._has('gl3d')).toBe(false);
- expect(gd._fullLayout.scene === undefined).toBe(true);
- })
- .then(done);
- });
-
- it('@gl should be able to toggle visibility', function(done) {
- var _mock = Lib.extendDeep({}, mock2);
- _mock.data[0].x = [0, 1, 3];
- _mock.data[0].y = [0, 1, 2];
- _mock.data.push({
- type: 'surface',
- z: [[1, 2, 3], [1, 2, 3], [2, 1, 2]]
- }, {
- type: 'mesh3d',
- x: [0, 1, 2, 0], y: [0, 0, 1, 2], z: [0, 2, 0, 1],
- i: [0, 0, 0, 1], j: [1, 2, 3, 2], k: [2, 3, 1, 3]
- });
-
- // scatter3d traces are made of 5 gl-vis objects,
- // surface and mesh3d are made of 1 gl-vis object each.
- var order0 = [0, 0, 0, 0, 0, 1, 2];
-
- function assertObjects(expected) {
- var objects = gd._fullLayout.scene._scene.glplot.objects;
- var actual = objects.map(function(o) {
- return o._trace.data.index;
- });
-
- expect(actual).toEqual(expected);
- }
-
- Plotly.plot(gd, _mock)
- .then(delay(20))
- .then(function() {
- assertObjects(order0);
-
- return Plotly.restyle(gd, 'visible', 'legendonly');
- })
- .then(function() {
- assertObjects([]);
-
- return Plotly.restyle(gd, 'visible', true);
- })
- .then(function() {
- assertObjects(order0);
-
- return Plotly.restyle(gd, 'visible', false, [0]);
- })
- .then(function() {
- assertObjects([1, 2]);
-
- return Plotly.restyle(gd, 'visible', true, [0]);
- })
- .then(function() {
- assertObjects(order0);
-
- return Plotly.restyle(gd, 'visible', 'legendonly', [1]);
- })
- .then(function() {
- assertObjects([0, 0, 0, 0, 0, 2]);
-
- return Plotly.restyle(gd, 'visible', true, [1]);
- })
- .then(function() {
- assertObjects(order0);
- })
- .then(done);
- });
-
- it('@gl should avoid passing blank texts to webgl', function(done) {
- function assertIsFilled(msg) {
- var fullLayout = gd._fullLayout;
- expect(fullLayout.scene._scene.glplot.objects[0].glyphBuffer.length).not.toBe(0, msg);
- }
- Plotly.plot(gd, [{
- type: 'scatter3d',
- mode: 'text',
- x: [1, 2, 3],
- y: [2, 3, 1],
- z: [3, 1, 2]
- }])
- .then(function() {
- assertIsFilled('not to be empty text');
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should avoid passing empty lines to webgl', function(done) {
- var obj;
-
- Plotly.plot(gd, [{
- type: 'scatter3d',
- mode: 'lines',
- x: [1],
- y: [2],
- z: [3]
- }])
- .then(function() {
- obj = gd._fullLayout.scene._scene.glplot.objects[0];
- spyOn(obj.vao, 'draw').and.callThrough();
-
- expect(obj.vertexCount).toBe(0, '# of vertices');
-
- return Plotly.restyle(gd, 'line.color', 'red');
- })
- .then(function() {
- expect(obj.vertexCount).toBe(0, '# of vertices');
- // calling this with no vertex causes WebGL warnings,
- // see https://github.com/plotly/plotly.js/issues/1976
- expect(obj.vao.draw).toHaveBeenCalledTimes(0);
- })
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should only accept texts for textposition otherwise textposition is set to middle center before passing to webgl', function(done) {
- Plotly.plot(gd, [{
- type: 'scatter3d',
- mode: 'markers+text+lines',
- x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
- y: [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16],
- z: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
- text: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'],
- textposition: ['left top', 'right top', 'left bottom', 'right bottom', null, undefined, true, false, [], {}, NaN, Infinity, 0, 1.2]
- }])
- .then(function() {
- var AllTextpositions = gd._fullData[0].textposition;
-
- expect(AllTextpositions[0]).toBe('top left', 'is not top left');
- expect(AllTextpositions[1]).toBe('top right', 'is not top right');
- expect(AllTextpositions[2]).toBe('bottom left', 'is not bottom left');
- expect(AllTextpositions[3]).toBe('bottom right', 'is not bottom right');
- for(var i = 4; i < AllTextpositions.length; i++) {
- expect(AllTextpositions[i]).toBe('middle center', 'is not middle center');
- }
- })
- .catch(failTest)
- .then(done);
- });
-
it('@gl should not set _length to NaN and dtick should be defined.', function(done) {
Plotly.plot(gd,
{
@@ -2276,3 +1562,74 @@ describe('Test removal of gl contexts', function() {
.then(done);
});
});
+
+describe('Test gl3d drag events', function() {
+ var gd;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ // Expected shape of projection-related data
+ var cameraStructure = {
+ projection: {type: jasmine.any(String)},
+ up: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
+ center: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
+ eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}
+ };
+
+ function makePlot(gd, mock) {
+ return Plotly.plot(gd, mock.data, mock.layout);
+ }
+
+ function addEventCallback(graphDiv) {
+ var relayoutCallback = jasmine.createSpy('relayoutCallback');
+ graphDiv.on('plotly_relayout', relayoutCallback);
+ return {graphDiv: graphDiv, relayoutCallback: relayoutCallback};
+ }
+
+ function verifyInteractionEffects(tuple) {
+ // One 'drag': simulating fairly thoroughly as the mouseup event is also needed here
+ mouseEvent('mousemove', 400, 200);
+ mouseEvent('mousedown', 400, 200);
+ mouseEvent('mousemove', 320, 320, {buttons: 1});
+ mouseEvent('mouseup', 320, 320);
+
+ // Check event emission count
+ expect(tuple.relayoutCallback).toHaveBeenCalledTimes(1);
+
+ // Check structure of event callback value contents
+ expect(tuple.relayoutCallback).toHaveBeenCalledWith(jasmine.objectContaining({'scene.camera': cameraStructure}));
+
+ // Check camera contents on the DIV layout
+ var divCamera = tuple.graphDiv.layout.scene.camera;
+
+ expect(divCamera).toEqual(cameraStructure);
+
+ return tuple.graphDiv;
+ }
+
+ function testEvents(plot) {
+ return plot.then(function(graphDiv) {
+ var tuple = addEventCallback(graphDiv);
+ verifyInteractionEffects(tuple);
+ });
+ }
+
+ it('@gl should respond to drag interactions with mock of unset camera', function(done) {
+ testEvents(makePlot(gd, require('@mocks/gl3d_scatter3d-connectgaps.json')))
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should respond to drag interactions with mock of partially set camera', function(done) {
+ testEvents(makePlot(gd, require('@mocks/gl3d_errorbars_zx.json')))
+ .catch(failTest)
+ .then(done);
+ });
+});
diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js
deleted file mode 100644
index ea8f219e834..00000000000
--- a/test/jasmine/tests/gl3daxes_test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-var supplyLayoutDefaults = require('@src/plots/gl3d/layout/axis_defaults');
-
-
-describe('Test Gl3dAxes', function() {
- 'use strict';
-
- describe('supplyLayoutDefaults supplies defaults', function() {
- var layoutIn,
- layoutOut;
-
- var options = {
- font: 'Open Sans',
- scene: {id: 'scene'},
- data: [{x: [], y: []}],
- bgColor: '#fff',
- fullLayout: {_dfltTitle: {x: 'xxx', y: 'yyy', colorbar: 'cbbb'}}
- };
-
- beforeEach(function() {
- layoutOut = {};
- });
-
- it('should define specific default set with empty initial layout', function() {
- layoutIn = {};
-
- var expected = {
- 'xaxis': {
- 'showline': false,
- 'showgrid': true,
- 'gridcolor': 'rgb(204, 204, 204)',
- 'gridwidth': 1,
- 'showspikes': true,
- 'spikesides': true,
- 'spikethickness': 2,
- 'spikecolor': '#444',
- 'showbackground': false,
- 'showaxeslabels': true
- },
- 'yaxis': {
- 'showline': false,
- 'showgrid': true,
- 'gridcolor': 'rgb(204, 204, 204)',
- 'gridwidth': 1,
- 'showspikes': true,
- 'spikesides': true,
- 'spikethickness': 2,
- 'spikecolor': '#444',
- 'showbackground': false,
- 'showaxeslabels': true
- },
- 'zaxis': {
- 'showline': false,
- 'showgrid': true,
- 'gridcolor': 'rgb(204, 204, 204)',
- 'gridwidth': 1,
- 'showspikes': true,
- 'spikesides': true,
- 'spikethickness': 2,
- 'spikecolor': '#444',
- 'showbackground': false,
- 'showaxeslabels': true
- }
- };
-
- function checkKeys(validObject, testObject) {
- var keys = Object.keys(validObject);
- for(var i = 0; i < keys.length; i++) {
- var k = keys[i];
- expect(validObject[k]).toBe(testObject[k]);
- }
- return true;
- }
-
- supplyLayoutDefaults(layoutIn, layoutOut, options);
- ['xaxis', 'yaxis', 'zaxis'].forEach(function(axis) {
- checkKeys(expected[axis], layoutOut[axis]);
- });
- });
-
- it('should inherit layout.calendar', function() {
- layoutIn = {
- xaxis: {type: 'date'},
- yaxis: {type: 'date'},
- zaxis: {type: 'date'}
- };
- options.calendar = 'taiwan';
-
- supplyLayoutDefaults(layoutIn, layoutOut, options);
-
- expect(layoutOut.xaxis.calendar).toBe('taiwan');
- expect(layoutOut.yaxis.calendar).toBe('taiwan');
- expect(layoutOut.zaxis.calendar).toBe('taiwan');
- });
-
- it('should accept its own calendar', function() {
- layoutIn = {
- xaxis: {type: 'date', calendar: 'hebrew'},
- yaxis: {type: 'date', calendar: 'ummalqura'},
- zaxis: {type: 'date', calendar: 'discworld'}
- };
- options.calendar = 'taiwan';
-
- supplyLayoutDefaults(layoutIn, layoutOut, options);
-
- expect(layoutOut.xaxis.calendar).toBe('hebrew');
- expect(layoutOut.yaxis.calendar).toBe('ummalqura');
- expect(layoutOut.zaxis.calendar).toBe('discworld');
- });
- });
-});
diff --git a/test/jasmine/tests/gl3dlayout_test.js b/test/jasmine/tests/gl3dlayout_test.js
index 6957da72adb..06232dab190 100644
--- a/test/jasmine/tests/gl3dlayout_test.js
+++ b/test/jasmine/tests/gl3dlayout_test.js
@@ -8,6 +8,115 @@ var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
+describe('Test gl3d axes defaults', function() {
+ 'use strict';
+
+ var supplyLayoutDefaults = require('@src/plots/gl3d/layout/axis_defaults');
+
+ describe('supplyLayoutDefaults supplies defaults', function() {
+ var layoutIn,
+ layoutOut;
+
+ var options = {
+ font: 'Open Sans',
+ scene: {id: 'scene'},
+ data: [{x: [], y: []}],
+ bgColor: '#fff',
+ fullLayout: {_dfltTitle: {x: 'xxx', y: 'yyy', colorbar: 'cbbb'}}
+ };
+
+ beforeEach(function() {
+ layoutOut = {};
+ });
+
+ it('should define specific default set with empty initial layout', function() {
+ layoutIn = {};
+
+ var expected = {
+ 'xaxis': {
+ 'showline': false,
+ 'showgrid': true,
+ 'gridcolor': 'rgb(204, 204, 204)',
+ 'gridwidth': 1,
+ 'showspikes': true,
+ 'spikesides': true,
+ 'spikethickness': 2,
+ 'spikecolor': '#444',
+ 'showbackground': false,
+ 'showaxeslabels': true
+ },
+ 'yaxis': {
+ 'showline': false,
+ 'showgrid': true,
+ 'gridcolor': 'rgb(204, 204, 204)',
+ 'gridwidth': 1,
+ 'showspikes': true,
+ 'spikesides': true,
+ 'spikethickness': 2,
+ 'spikecolor': '#444',
+ 'showbackground': false,
+ 'showaxeslabels': true
+ },
+ 'zaxis': {
+ 'showline': false,
+ 'showgrid': true,
+ 'gridcolor': 'rgb(204, 204, 204)',
+ 'gridwidth': 1,
+ 'showspikes': true,
+ 'spikesides': true,
+ 'spikethickness': 2,
+ 'spikecolor': '#444',
+ 'showbackground': false,
+ 'showaxeslabels': true
+ }
+ };
+
+ function checkKeys(validObject, testObject) {
+ var keys = Object.keys(validObject);
+ for(var i = 0; i < keys.length; i++) {
+ var k = keys[i];
+ expect(validObject[k]).toBe(testObject[k]);
+ }
+ return true;
+ }
+
+ supplyLayoutDefaults(layoutIn, layoutOut, options);
+ ['xaxis', 'yaxis', 'zaxis'].forEach(function(axis) {
+ checkKeys(expected[axis], layoutOut[axis]);
+ });
+ });
+
+ it('should inherit layout.calendar', function() {
+ layoutIn = {
+ xaxis: {type: 'date'},
+ yaxis: {type: 'date'},
+ zaxis: {type: 'date'}
+ };
+ options.calendar = 'taiwan';
+
+ supplyLayoutDefaults(layoutIn, layoutOut, options);
+
+ expect(layoutOut.xaxis.calendar).toBe('taiwan');
+ expect(layoutOut.yaxis.calendar).toBe('taiwan');
+ expect(layoutOut.zaxis.calendar).toBe('taiwan');
+ });
+
+ it('should accept its own calendar', function() {
+ layoutIn = {
+ xaxis: {type: 'date', calendar: 'hebrew'},
+ yaxis: {type: 'date', calendar: 'ummalqura'},
+ zaxis: {type: 'date', calendar: 'discworld'}
+ };
+ options.calendar = 'taiwan';
+
+ supplyLayoutDefaults(layoutIn, layoutOut, options);
+
+ expect(layoutOut.xaxis.calendar).toBe('hebrew');
+ expect(layoutOut.yaxis.calendar).toBe('ummalqura');
+ expect(layoutOut.zaxis.calendar).toBe('discworld');
+ });
+ });
+});
describe('Test Gl3d layout defaults', function() {
'use strict';
@@ -277,10 +386,10 @@ describe('Test Gl3d layout defaults', function() {
describe('Gl3d layout edge cases', function() {
var gd;
- beforeEach(function() {gd = createGraphDiv(); });
+ beforeEach(function() { gd = createGraphDiv(); });
afterEach(destroyGraphDiv);
- it('should handle auto aspect ratio correctly on data changes', function(done) {
+ it('@gl should handle auto aspect ratio correctly on data changes', function(done) {
Plotly.plot(gd, [{x: [1, 2], y: [1, 3], z: [1, 4], type: 'scatter3d'}])
.then(function() {
var aspect = gd.layout.scene.aspectratio;
diff --git a/test/jasmine/tests/gl_plot_interact_basic_test.js b/test/jasmine/tests/gl_plot_interact_basic_test.js
deleted file mode 100644
index 33658f79069..00000000000
--- a/test/jasmine/tests/gl_plot_interact_basic_test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-'use strict';
-
-var Plotly = require('@lib/index');
-var mouseEvent = require('../assets/mouse_event');
-
-// Test utilities
-var createGraphDiv = require('../assets/create_graph_div');
-var destroyGraphDiv = require('../assets/destroy_graph_div');
-var failTest = require('../assets/fail_test');
-
-
-// Expected shape of projection-related data
-var cameraStructure = {
- projection: {type: jasmine.any(String)},
- up: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
- center: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)},
- eye: {x: jasmine.any(Number), y: jasmine.any(Number), z: jasmine.any(Number)}
-};
-
-function makePlot(gd, mock) {
- return Plotly.plot(gd, mock.data, mock.layout);
-}
-
-function addEventCallback(graphDiv) {
- var relayoutCallback = jasmine.createSpy('relayoutCallback');
- graphDiv.on('plotly_relayout', relayoutCallback);
- return {graphDiv: graphDiv, relayoutCallback: relayoutCallback};
-}
-
-function verifyInteractionEffects(tuple) {
- // One 'drag': simulating fairly thoroughly as the mouseup event is also needed here
- mouseEvent('mousemove', 400, 200);
- mouseEvent('mousedown', 400, 200);
- mouseEvent('mousemove', 320, 320, {buttons: 1});
- mouseEvent('mouseup', 320, 320);
-
- // Check event emission count
- expect(tuple.relayoutCallback).toHaveBeenCalledTimes(1);
-
- // Check structure of event callback value contents
- expect(tuple.relayoutCallback).toHaveBeenCalledWith(jasmine.objectContaining({'scene.camera': cameraStructure}));
-
- // Check camera contents on the DIV layout
- var divCamera = tuple.graphDiv.layout.scene.camera;
-
- expect(divCamera).toEqual(cameraStructure);
-
- return tuple.graphDiv;
-}
-
-function testEvents(plot) {
- return plot.then(function(graphDiv) {
- var tuple = addEventCallback(graphDiv);
- verifyInteractionEffects(tuple);
- });
-}
-
-describe('gl3d plots', function() {
- var gd;
-
- beforeEach(function() {
- gd = createGraphDiv();
- });
-
- afterEach(function() {
- Plotly.purge(gd);
- destroyGraphDiv();
- });
-
- it('@gl should respond to drag interactions with mock of unset camera', function(done) {
- testEvents(makePlot(gd, require('@mocks/gl3d_scatter3d-connectgaps.json')))
- .catch(failTest)
- .then(done);
- });
-
- it('@gl should respond to drag interactions with mock of partially set camera', function(done) {
- testEvents(makePlot(gd, require('@mocks/gl3d_errorbars_zx.json')))
- .catch(failTest)
- .then(done);
- });
-});
diff --git a/test/jasmine/tests/mesh3d_test.js b/test/jasmine/tests/mesh3d_test.js
index 5f98c2cf4ac..a21affd6bb6 100644
--- a/test/jasmine/tests/mesh3d_test.js
+++ b/test/jasmine/tests/mesh3d_test.js
@@ -9,7 +9,7 @@ describe('Test mesh3d', function() {
describe('restyle', function() {
afterEach(destroyGraphDiv);
- it('should clear *cauto* when restyle *cmin* and/or *cmax*', function(done) {
+ it('@gl should clear *cauto* when restyle *cmin* and/or *cmax*', function(done) {
var gd = createGraphDiv();
function _assert(user, full) {
diff --git a/test/jasmine/tests/gl2d_pointcloud_test.js b/test/jasmine/tests/pointcloud_test.js
similarity index 100%
rename from test/jasmine/tests/gl2d_pointcloud_test.js
rename to test/jasmine/tests/pointcloud_test.js
diff --git a/test/jasmine/tests/scatter3d_test.js b/test/jasmine/tests/scatter3d_test.js
index 0c610773274..abe1a04bde4 100644
--- a/test/jasmine/tests/scatter3d_test.js
+++ b/test/jasmine/tests/scatter3d_test.js
@@ -1,7 +1,18 @@
-var Scatter3D = require('@src/traces/scatter3d');
+var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var Color = require('@src/components/color');
+var Scatter3D = require('@src/traces/scatter3d');
+
+var d3 = require('d3');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+var failTest = require('../assets/fail_test');
+var delay = require('../assets/delay');
+
+function countCanvases() {
+ return d3.selectAll('canvas').size();
+}
describe('Scatter3D defaults', function() {
'use strict';
@@ -89,3 +100,216 @@ describe('Scatter3D defaults', function() {
expect(out.zcalendar).toBe('mayan');
});
});
+
+describe('Test scatter3d interactions:', function() {
+ var gd;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000;
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ // lines, markers, text, error bars and surfaces each
+ // correspond to one glplot object
+ var mock = require('@mocks/gl3d_marker-arrays.json');
+ var mock2 = Lib.extendDeep({}, mock);
+ mock2.data[0].mode = 'lines+markers+text';
+ mock2.data[0].error_z = { value: 10 };
+ mock2.data[0].surfaceaxis = 2;
+ mock2.layout.showlegend = true;
+
+ it('@gl should be able to reversibly change trace type', function(done) {
+ var _mock = Lib.extendDeep({}, mock2);
+ var sceneLayout = { aspectratio: { x: 1, y: 1, z: 1 } };
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ expect(countCanvases()).toEqual(1);
+ expect(gd.layout.scene).toEqual(sceneLayout);
+ expect(gd.layout.xaxis === undefined).toBe(true);
+ expect(gd.layout.yaxis === undefined).toBe(true);
+ expect(gd._fullLayout._has('gl3d')).toBe(true);
+ expect(gd._fullLayout.scene._scene).toBeDefined();
+ expect(gd._fullLayout.scene._scene.camera).toBeDefined(true);
+
+ return Plotly.restyle(gd, 'type', 'scatter');
+ })
+ .then(function() {
+ expect(countCanvases()).toEqual(0);
+ expect(gd.layout.scene).toEqual(sceneLayout);
+ expect(gd.layout.xaxis).toBeDefined();
+ expect(gd.layout.yaxis).toBeDefined();
+ expect(gd._fullLayout._has('gl3d')).toBe(false);
+ expect(gd._fullLayout.scene === undefined).toBe(true);
+
+ return Plotly.restyle(gd, 'type', 'scatter3d');
+ })
+ .then(function() {
+ expect(countCanvases()).toEqual(1);
+ expect(gd.layout.scene).toEqual(sceneLayout);
+ expect(gd.layout.xaxis).toBeDefined();
+ expect(gd.layout.yaxis).toBeDefined();
+ expect(gd._fullLayout._has('gl3d')).toBe(true);
+ expect(gd._fullLayout.scene._scene).toBeDefined();
+ })
+ .then(done);
+ });
+
+ it('@gl should be able to delete the last trace', function(done) {
+ var _mock = Lib.extendDeep({}, mock2);
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ return Plotly.deleteTraces(gd, [0]);
+ })
+ .then(function() {
+ expect(countCanvases()).toEqual(0);
+ expect(gd._fullLayout._has('gl3d')).toBe(false);
+ expect(gd._fullLayout.scene === undefined).toBe(true);
+ })
+ .then(done);
+ });
+
+ it('@gl should be able to toggle visibility', function(done) {
+ var _mock = Lib.extendDeep({}, mock2);
+ _mock.data[0].x = [0, 1, 3];
+ _mock.data[0].y = [0, 1, 2];
+ _mock.data.push({
+ type: 'surface',
+ z: [[1, 2, 3], [1, 2, 3], [2, 1, 2]]
+ }, {
+ type: 'mesh3d',
+ x: [0, 1, 2, 0], y: [0, 0, 1, 2], z: [0, 2, 0, 1],
+ i: [0, 0, 0, 1], j: [1, 2, 3, 2], k: [2, 3, 1, 3]
+ });
+
+ // scatter3d traces are made of 5 gl-vis objects,
+ // surface and mesh3d are made of 1 gl-vis object each.
+ var order0 = [0, 0, 0, 0, 0, 1, 2];
+
+ function assertObjects(expected) {
+ var objects = gd._fullLayout.scene._scene.glplot.objects;
+ var actual = objects.map(function(o) {
+ return o._trace.data.index;
+ });
+
+ expect(actual).toEqual(expected);
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(20))
+ .then(function() {
+ assertObjects(order0);
+
+ return Plotly.restyle(gd, 'visible', 'legendonly');
+ })
+ .then(function() {
+ assertObjects([]);
+
+ return Plotly.restyle(gd, 'visible', true);
+ })
+ .then(function() {
+ assertObjects(order0);
+
+ return Plotly.restyle(gd, 'visible', false, [0]);
+ })
+ .then(function() {
+ assertObjects([1, 2]);
+
+ return Plotly.restyle(gd, 'visible', true, [0]);
+ })
+ .then(function() {
+ assertObjects(order0);
+
+ return Plotly.restyle(gd, 'visible', 'legendonly', [1]);
+ })
+ .then(function() {
+ assertObjects([0, 0, 0, 0, 0, 2]);
+
+ return Plotly.restyle(gd, 'visible', true, [1]);
+ })
+ .then(function() {
+ assertObjects(order0);
+ })
+ .then(done);
+ });
+
+ it('@gl should avoid passing blank texts to webgl', function(done) {
+ function assertIsFilled(msg) {
+ var fullLayout = gd._fullLayout;
+ expect(fullLayout.scene._scene.glplot.objects[0].glyphBuffer.length).not.toBe(0, msg);
+ }
+ Plotly.plot(gd, [{
+ type: 'scatter3d',
+ mode: 'text',
+ x: [1, 2, 3],
+ y: [2, 3, 1],
+ z: [3, 1, 2]
+ }])
+ .then(function() {
+ assertIsFilled('not to be empty text');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should avoid passing empty lines to webgl', function(done) {
+ var obj;
+
+ Plotly.plot(gd, [{
+ type: 'scatter3d',
+ mode: 'lines',
+ x: [1],
+ y: [2],
+ z: [3]
+ }])
+ .then(function() {
+ obj = gd._fullLayout.scene._scene.glplot.objects[0];
+ spyOn(obj.vao, 'draw').and.callThrough();
+
+ expect(obj.vertexCount).toBe(0, '# of vertices');
+
+ return Plotly.restyle(gd, 'line.color', 'red');
+ })
+ .then(function() {
+ expect(obj.vertexCount).toBe(0, '# of vertices');
+ // calling this with no vertex causes WebGL warnings,
+ // see https://github.com/plotly/plotly.js/issues/1976
+ expect(obj.vao.draw).toHaveBeenCalledTimes(0);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should only accept texts for textposition otherwise textposition is set to middle center before passing to webgl', function(done) {
+ Plotly.plot(gd, [{
+ type: 'scatter3d',
+ mode: 'markers+text+lines',
+ x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
+ y: [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16],
+ z: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
+ text: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'],
+ textposition: ['left top', 'right top', 'left bottom', 'right bottom', null, undefined, true, false, [], {}, NaN, Infinity, 0, 1.2]
+ }])
+ .then(function() {
+ var AllTextpositions = gd._fullData[0].textposition;
+
+ expect(AllTextpositions[0]).toBe('top left', 'is not top left');
+ expect(AllTextpositions[1]).toBe('top right', 'is not top right');
+ expect(AllTextpositions[2]).toBe('bottom left', 'is not bottom left');
+ expect(AllTextpositions[3]).toBe('bottom right', 'is not bottom right');
+ for(var i = 4; i < AllTextpositions.length; i++) {
+ expect(AllTextpositions[i]).toBe('middle center', 'is not middle center');
+ }
+ })
+ .catch(failTest)
+ .then(done);
+ });
+});
diff --git a/test/jasmine/tests/gl2d_double_click_test.js b/test/jasmine/tests/scattergl_select_test.js
similarity index 83%
rename from test/jasmine/tests/gl2d_double_click_test.js
rename to test/jasmine/tests/scattergl_select_test.js
index 3881223558d..8714ea9fe0f 100644
--- a/test/jasmine/tests/gl2d_double_click_test.js
+++ b/test/jasmine/tests/scattergl_select_test.js
@@ -6,76 +6,49 @@ var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test.js');
-// cartesian click events events use the hover data
-// from the mousemove events and then simulate
-// a click event on mouseup
var doubleClick = require('../assets/double_click');
var delay = require('../assets/delay');
var mouseEvent = require('../assets/mouse_event');
var readPixel = require('../assets/read_pixel');
-// contourgl is not part of the dist plotly.js bundle initially
-Plotly.register([
- require('@lib/contourgl')
-]);
+function drag(gd, path) {
+ var len = path.length;
+ var el = d3.select(gd).select('rect.nsewdrag').node();
+ var opts = {element: el};
-describe('Test gl2d lasso/select:', function() {
- var mockFancy = require('@mocks/gl2d_14.json');
- delete mockFancy.layout.xaxis.autorange;
- delete mockFancy.layout.yaxis.autorange;
- mockFancy.layout.xaxis.range = [-2.951309064136961, 2.0954721318818916];
- mockFancy.layout.yaxis.range = [-0.9248866483012275, 1.3232607344525835];
+ Lib.clearThrottle();
+ mouseEvent('mousemove', path[0][0], path[0][1], opts);
+ mouseEvent('mousedown', path[0][0], path[0][1], opts);
- var mockFast = Lib.extendDeep({}, mockFancy, {
- data: [{mode: 'markers'}],
- layout: {
- xaxis: {
- type: 'linear',
- range: [-3.869222222222223, 73.55522222222223]
- },
- yaxis: {
- type: 'linear',
- range: [-0.7402222222222222, 17.144222222222222]
- }
- }
+ path.slice(1, len).forEach(function(pt) {
+ Lib.clearThrottle();
+ mouseEvent('mousemove', pt[0], pt[1], opts);
});
+ mouseEvent('mouseup', path[len - 1][0], path[len - 1][1], opts);
+}
+
+function select(gd, path) {
+ return new Promise(function(resolve, reject) {
+ gd.once('plotly_selected', resolve);
+ setTimeout(function() { reject('did not trigger *plotly_selected*');}, 200);
+ drag(gd, path);
+ });
+}
+
+describe('Test gl2d lasso/select:', function() {
var gd;
var selectPath = [[98, 193], [108, 193]];
var selectPath2 = [[118, 193], [128, 193]];
var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]];
var lassoPath2 = [[98, 193], [108, 193], [108, 500], [98, 500], [98, 193]];
- afterEach(function() {
+ afterEach(function(done) {
Plotly.purge(gd);
destroyGraphDiv();
+ setTimeout(done, 500);
});
- function drag(path) {
- var len = path.length;
- var el = d3.select(gd).select('rect.nsewdrag').node();
- var opts = {element: el};
-
- Lib.clearThrottle();
- mouseEvent('mousemove', path[0][0], path[0][1], opts);
- mouseEvent('mousedown', path[0][0], path[0][1], opts);
-
- path.slice(1, len).forEach(function(pt) {
- Lib.clearThrottle();
- mouseEvent('mousemove', pt[0], pt[1], opts);
- });
-
- mouseEvent('mouseup', path[len - 1][0], path[len - 1][1], opts);
- }
-
- function select(path) {
- return new Promise(function(resolve, reject) {
- gd.once('plotly_selected', resolve);
- setTimeout(function() { reject('did not trigger *plotly_selected*');}, 200);
- drag(path);
- });
- }
-
function assertEventData(actual, expected) {
expect(actual.points.length).toBe(expected.points.length);
@@ -88,6 +61,27 @@ describe('Test gl2d lasso/select:', function() {
});
}
+ var mockFancy = require('@mocks/gl2d_14.json');
+ delete mockFancy.layout.xaxis.autorange;
+ delete mockFancy.layout.yaxis.autorange;
+ mockFancy.layout.xaxis.range = [-2.951309064136961, 2.0954721318818916];
+ mockFancy.layout.yaxis.range = [-0.9248866483012275, 1.3232607344525835];
+
+ var mockFast = Lib.extendDeep({}, mockFancy, {
+ data: [{mode: 'markers'}],
+ layout: {
+ xaxis: {
+ type: 'linear',
+ range: [-3.869222222222223, 73.55522222222223]
+ },
+ yaxis: {
+ type: 'linear',
+ range: [-0.7402222222222222, 17.144222222222222]
+ }
+ }
+ });
+
+
it('@gl should work under fast mode with *select* dragmode', function(done) {
var _mock = Lib.extendDeep({}, mockFast);
_mock.layout.dragmode = 'select';
@@ -98,7 +92,7 @@ describe('Test gl2d lasso/select:', function() {
.then(function() {
expect(gd._fullLayout._plots.xy._scene.select2d).not.toBe(undefined, 'scatter2d renderer');
- return select(selectPath);
+ return select(gd, selectPath);
})
.then(delay(20))
.then(function(eventData) {
@@ -122,7 +116,7 @@ describe('Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(20))
.then(function() {
- return select(lassoPath2);
+ return select(gd, lassoPath2);
})
.then(delay(20))
.then(function(eventData) {
@@ -146,7 +140,7 @@ describe('Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(20))
.then(function() {
- return select(selectPath2);
+ return select(gd, selectPath2);
})
.then(delay(20))
.then(function(eventData) {
@@ -166,7 +160,7 @@ describe('Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(20))
.then(function() {
- return select(lassoPath);
+ return select(gd, lassoPath);
})
.then(function(eventData) {
assertEventData(eventData, {
@@ -187,7 +181,7 @@ describe('Test gl2d lasso/select:', function() {
Plotly.plot(gd, fig)
.then(delay(20))
- .then(function() { return select([[100, 100], [250, 250]]); })
+ .then(function() { return select(gd, [[100, 100], [250, 250]]); })
.then(function(eventData) {
assertEventData(eventData, {
points: [
@@ -227,7 +221,7 @@ describe('Test gl2d lasso/select:', function() {
]
});
})
- .then(function() { return select([[100, 100], [250, 250]]); })
+ .then(function() { return select(gd, [[100, 100], [250, 250]]); })
.then(function(eventData) {
assertEventData(eventData, {
points: [{x: 1, y: 2}]
@@ -256,7 +250,7 @@ describe('Test gl2d lasso/select:', function() {
.then(function() {
return Plotly.restyle(gd, 'selected.textfont.color', 'red');
})
- .then(function() { return select([[100, 100], [250, 250]]); })
+ .then(function() { return select(gd, [[100, 100], [250, 250]]); })
.then(function() {
_assertGlTextOpts('after selection - with set selected.textfont.color', {
rgba: [
@@ -317,7 +311,7 @@ describe('Test gl2d lasso/select:', function() {
]
});
})
- .then(function() { return select([[100, 10], [250, 100]]); })
+ .then(function() { return select(gd, [[100, 10], [250, 100]]); })
.then(function(eventData) {
assertEventData(eventData, {
points: [{x: 1, y: 2}]
@@ -341,7 +335,7 @@ describe('Test gl2d lasso/select:', function() {
.then(function() {
return Plotly.restyle(gd, 'selected.textfont.color', 'red');
})
- .then(function() { return select([[100, 10], [250, 100]]); })
+ .then(function() { return select(gd, [[100, 10], [250, 100]]); })
.then(function() {
_assertGlTextOpts('after selection - with set selected.textfont.color', {
rgba: [
@@ -362,10 +356,22 @@ describe('Test gl2d lasso/select:', function() {
.catch(failTest)
.then(done);
});
+});
- it('@gl should work after a width/height relayout', function(done) {
+describe('Test displayed selections:', function() {
+ var gd;
+
+ beforeEach(function() {
gd = createGraphDiv();
+ });
+
+ afterEach(function(done) {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ setTimeout(done, 500);
+ });
+ it('@gl should work after a width/height relayout', function(done) {
var w = 500;
var h = 500;
var w2 = 800;
@@ -396,7 +402,7 @@ describe('Test gl2d lasso/select:', function() {
expect(readContext()).toBeGreaterThan(1e4, 'base context');
expect(readFocus()).toBe(0, 'base focus');
})
- .then(function() { return select([[pad, pad], [w - pad, h - pad]]); })
+ .then(function() { return select(gd, [[pad, pad], [w - pad, h - pad]]); })
.then(function() {
expect(readContext()).toBe(0, 'select context');
expect(readFocus()).toBeGreaterThan(1e4, 'select focus');
@@ -411,7 +417,7 @@ describe('Test gl2d lasso/select:', function() {
expect(readContext()).toBeGreaterThan(1e4, 'update context');
expect(readFocus()).toBe(0, 'update focus');
})
- .then(function() { return select([[pad, pad], [w2 - pad, h2 - pad]]); })
+ .then(function() { return select(gd, [[pad, pad], [w2 - pad, h2 - pad]]); })
.then(function() {
// make sure full w2/h2 context canvas is cleared!
// from https://github.com/plotly/plotly.js/issues/2731
@@ -422,6 +428,92 @@ describe('Test gl2d lasso/select:', function() {
.then(done);
});
+ it('@gl should display selection of big number of regular points', function(done) {
+ // generate large number of points
+ var x = [];
+ var y = [];
+ var n = 2e2;
+ var N = n * n;
+ for(var i = 0; i < N; i++) {
+ x.push((i % n) / n);
+ y.push(i / N);
+ }
+
+ var mock = {
+ data: [{
+ x: x, y: y, type: 'scattergl', mode: 'markers'
+ }],
+ layout: {
+ dragmode: 'select'
+ }
+ };
+
+ Plotly.plot(gd, mock)
+ .then(select(gd, [[160, 100], [180, 100]]))
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 168, 100)[3]).toBe(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 158, 100)[3]).not.toBe(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-focus'), 168, 100)[3]).not.toBe(0);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should display selection of big number of miscellaneous points', function(done) {
+ var colorList = [
+ '#006385', '#F06E75', '#90ed7d', '#f7a35c', '#8085e9',
+ '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1',
+ '#5DA5DA', '#F06E75', '#F15854', '#B2912F', '#B276B2',
+ '#DECF3F', '#FAA43A', '#4D4D4D', '#F17CB0', '#60BD68'
+ ];
+
+ // generate large number of points
+ var x = [];
+ var y = [];
+ var n = 2e2;
+ var N = n * n;
+ var color = [];
+ var symbol = [];
+ var size = [];
+ for(var i = 0; i < N; i++) {
+ x.push((i % n) / n);
+ y.push(i / N);
+ color.push(colorList[i % colorList.length]);
+ symbol.push('x');
+ size.push(6);
+ }
+
+ var mock = {
+ data: [{
+ x: x, y: y, type: 'scattergl', mode: 'markers',
+ marker: {symbol: symbol, size: size, color: color}
+ }],
+ layout: {
+ dragmode: 'select'
+ }
+ };
+
+ Plotly.plot(gd, mock)
+ .then(select(gd, [[160, 100], [180, 100]]))
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 168, 100)[3]).toBe(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 158, 100)[3]).not.toBe(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-focus'), 168, 100)[3]).not.toBe(0);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+});
+
+describe('Test selections during funky scenarios', function() {
+ var gd;
+
+ afterEach(function(done) {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ setTimeout(done, 500);
+ });
+
function grabScene() {
return gd.calcdata[0][0].t._scene;
}
@@ -514,7 +606,7 @@ describe('Test gl2d lasso/select:', function() {
drawArgs: []
});
})
- .then(function() { return select([[20, 20], [480, 250]]); })
+ .then(function() { return select(gd, [[20, 20], [480, 250]]); })
.then(function() {
var scene = grabScene();
_assert('after select', {
@@ -556,7 +648,7 @@ describe('Test gl2d lasso/select:', function() {
drawArgs: []
});
})
- .then(function() { return drag([[200, 200], [250, 250]]); })
+ .then(function() { return drag(gd, [[200, 200], [250, 250]]); })
.then(function() {
var scene = grabScene();
_assert('after pan', {
@@ -652,7 +744,7 @@ describe('Test gl2d lasso/select:', function() {
drawArgs: []
});
})
- .then(function() { return select([[20, 20], [480, 250]]); })
+ .then(function() { return select(gd, [[20, 20], [480, 250]]); })
.then(function() { return doubleClick(250, 250); })
.then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); })
.then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); })
@@ -738,7 +830,7 @@ describe('Test gl2d lasso/select:', function() {
['select2d', [[[], []]]]
]);
})
- .then(function() { return select([[20, 20], [480, 250]]); })
+ .then(function() { return select(gd, [[20, 20], [480, 250]]); })
.then(function() {
_assert('on selection', [
['scatter2d', [[[0, 2], []]]],
@@ -791,7 +883,7 @@ describe('Test gl2d lasso/select:', function() {
spyOn(scene.scatter2d, 'draw');
spyOn(scene2.scatter2d, 'draw');
})
- .then(function() { return select([[20, 20], [380, 250]]); })
+ .then(function() { return select(gd, [[20, 20], [380, 250]]); })
.then(function() {
expect(scene.scatter2d.draw).toHaveBeenCalledTimes(1);
expect(scene2.scatter2d.draw).toHaveBeenCalledTimes(1);
diff --git a/test/jasmine/tests/scattergl_test.js b/test/jasmine/tests/scattergl_test.js
index 5cea5515c4c..bc973b9c219 100644
--- a/test/jasmine/tests/scattergl_test.js
+++ b/test/jasmine/tests/scattergl_test.js
@@ -1,7 +1,13 @@
var Plotly = require('@lib/index');
+var Lib = require('@src/lib');
+
+var ScatterGl = require('@src/traces/scattergl');
+
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
+var delay = require('../assets/delay');
+var readPixel = require('../assets/read_pixel');
describe('end-to-end scattergl tests', function() {
var gd;
@@ -10,9 +16,13 @@ describe('end-to-end scattergl tests', function() {
gd = createGraphDiv();
});
- afterEach(destroyGraphDiv);
+ afterEach(function(done) {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ setTimeout(done, 500);
+ });
- it('should create a plot with text labels', function(done) {
+ it('@gl should create a plot with text labels', function(done) {
Plotly.react(gd, [{
type: 'scattergl',
mode: 'text+lines',
@@ -27,7 +37,7 @@ describe('end-to-end scattergl tests', function() {
}).catch(failTest).then(done);
});
- it('should update a plot with text labels', function(done) {
+ it('@gl should update a plot with text labels', function(done) {
Plotly.react(gd, [{
type: 'scattergl',
mode: 'text+lines',
@@ -94,4 +104,582 @@ describe('end-to-end scattergl tests', function() {
expect(scene.glText.length).toEqual(2);
}).catch(failTest).then(done);
});
+
+ it('@gl should be able to toggle visibility', function(done) {
+ var mock = require('@mocks/gl2d_10.json');
+ var _mock = Lib.extendDeep({}, mock);
+ _mock.data[0].line.width = 5;
+
+ function assertDrawCall(msg, exp) {
+ var draw = gd._fullLayout._plots.xy._scene.scatter2d.draw;
+ expect(draw).toHaveBeenCalledTimes(exp, msg);
+ draw.calls.reset();
+ }
+
+ Plotly.plot(gd, _mock)
+ .then(delay(30))
+ .then(function() {
+ spyOn(gd._fullLayout._plots.xy._scene.scatter2d, 'draw');
+ return Plotly.restyle(gd, 'visible', 'legendonly');
+ })
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0);
+ assertDrawCall('legendonly', 0);
+
+ return Plotly.restyle(gd, 'visible', true);
+ })
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
+ assertDrawCall('back to visible', 1);
+
+ return Plotly.restyle(gd, 'visible', false);
+ })
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0);
+ assertDrawCall('visible false', 0);
+
+ return Plotly.restyle(gd, 'visible', true);
+ })
+ .then(function() {
+ assertDrawCall('back up', 1);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should be able to toggle trace with different modes', function(done) {
+ Plotly.newPlot(gd, [{
+ // a trace with all regl2d objects
+ type: 'scattergl',
+ mode: 'lines+markers+text',
+ y: [1, 2, 1],
+ text: ['a', 'b', 'c'],
+ error_x: {value: 10},
+ error_y: {value: 10},
+ fill: 'tozeroy'
+ }, {
+ type: 'scattergl',
+ mode: 'markers',
+ y: [0, 1, -1]
+ }])
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ spyOn(scene.fill2d, 'draw');
+ spyOn(scene.line2d, 'draw');
+ spyOn(scene.error2d, 'draw');
+ spyOn(scene.scatter2d, 'draw');
+ spyOn(scene.glText[0], 'render');
+
+ return Plotly.restyle(gd, 'visible', 'legendonly', [0]);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.fill2d.draw).toHaveBeenCalledTimes(0);
+ expect(scene.line2d.draw).toHaveBeenCalledTimes(0);
+ expect(scene.error2d.draw).toHaveBeenCalledTimes(0);
+ expect(scene.glText[0].render).toHaveBeenCalledTimes(0);
+ expect(scene.scatter2d.draw).toHaveBeenCalledTimes(1);
+
+ return Plotly.restyle(gd, 'visible', true, [0]);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.fill2d.draw).toHaveBeenCalledTimes(1);
+ expect(scene.line2d.draw).toHaveBeenCalledTimes(1);
+ expect(scene.error2d.draw).toHaveBeenCalledTimes(2, 'twice for x AND y');
+ expect(scene.glText[0].render).toHaveBeenCalledTimes(1);
+ expect(scene.scatter2d.draw).toHaveBeenCalledTimes(3, 'both traces have markers');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should change plot type with incomplete data', function(done) {
+ Plotly.plot(gd, [{}]);
+ expect(function() {
+ Plotly.restyle(gd, {type: 'scattergl', x: [[1]]}, 0);
+ }).not.toThrow();
+
+ expect(function() {
+ Plotly.restyle(gd, {y: [[1]]}, 0);
+ }).not.toThrow();
+
+ done();
+ });
+
+ it('@gl should restyle opacity', function(done) {
+ // #2299
+ spyOn(ScatterGl, 'calc').and.callThrough();
+
+ var dat = [{
+ 'x': [1, 2, 3],
+ 'y': [1, 2, 3],
+ 'type': 'scattergl',
+ 'mode': 'markers'
+ }];
+
+ Plotly.plot(gd, dat, {width: 500, height: 500})
+ .then(function() {
+ expect(ScatterGl.calc).toHaveBeenCalledTimes(1);
+
+ return Plotly.restyle(gd, {'opacity': 0.1});
+ })
+ .then(function() {
+ expect(ScatterGl.calc).toHaveBeenCalledTimes(2);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should update selected points', function(done) {
+ // #2298
+ var dat = [{
+ 'x': [1],
+ 'y': [1],
+ 'type': 'scattergl',
+ 'mode': 'markers',
+ 'selectedpoints': [0]
+ }];
+
+ Plotly.plot(gd, dat, {
+ width: 500,
+ height: 500,
+ dragmode: 'select'
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+
+ expect(scene.count).toBe(1);
+ expect(scene.selectBatch).toEqual([[0]]);
+ expect(scene.unselectBatch).toEqual([[]]);
+ spyOn(scene.scatter2d, 'draw');
+
+ var trace = {
+ x: [2],
+ y: [1],
+ text: ['a'],
+ type: 'scattergl',
+ mode: 'markers+text',
+ marker: {color: 'red'}
+ };
+
+ return Plotly.addTraces(gd, trace);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+
+ expect(scene.count).toBe(2);
+ expect(scene.selectBatch).toEqual([[0], []]);
+ expect(scene.unselectBatch).toEqual([[], []]);
+ expect(scene.markerOptions.length).toBe(2);
+ expect(scene.markerOptions[1].color).toEqual(new Uint8Array([255, 0, 0, 255]));
+ expect(scene.textOptions.length).toBe(2);
+ expect(scene.textOptions[1].color).toEqual('#444');
+ expect(scene.scatter2d.draw).toHaveBeenCalled();
+
+ return Plotly.restyle(gd, 'selectedpoints', null);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ var msg = 'clearing under dragmode select';
+
+ expect(scene.selectBatch).toEqual([[], []], msg);
+ expect(scene.unselectBatch).toEqual([[], []], msg);
+
+ // scattergl uses different pathways for select/lasso & zoom/pan
+ return Plotly.relayout(gd, 'dragmode', 'pan');
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ var msg = 'cleared under dragmode pan';
+
+ expect(scene.selectBatch).toEqual([[], []], msg);
+ expect(scene.unselectBatch).toEqual([[], []], msg);
+
+ return Plotly.restyle(gd, 'selectedpoints', [[1, 2], [0]]);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ var msg = 'selecting via API under dragmode pan';
+
+ expect(scene.selectBatch).toEqual([[1, 2], [0]], msg);
+ expect(scene.unselectBatch).toEqual([[0], []], msg);
+
+ return Plotly.restyle(gd, 'selectedpoints', null);
+ })
+ .then(function() {
+ var scene = gd._fullLayout._plots.xy._scene;
+ var msg = 'clearing under dragmode pan';
+
+ expect(scene.selectBatch).toEqual([[], []], msg);
+ expect(scene.unselectBatch).toEqual([[], []], msg);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should remove fill2d', function(done) {
+ var mock = require('@mocks/gl2d_axes_labels2.json');
+
+ Plotly.plot(gd, mock.data, mock.layout)
+ .then(delay(1000))
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 100, 80)[0]).not.toBe(0);
+
+ return Plotly.restyle(gd, {fill: 'none'});
+ })
+ .then(function() {
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 100, 80)[0]).toBe(0);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should be able to draw more than 4096 colors', function(done) {
+ var x = [];
+ var color = [];
+ var N = 1e5;
+ var w = 500;
+ var h = 500;
+
+ Lib.seedPseudoRandom();
+
+ for(var i = 0; i < N; i++) {
+ x.push(i);
+ color.push(Lib.pseudoRandom());
+ }
+
+ Plotly.newPlot(gd, [{
+ type: 'scattergl',
+ mode: 'markers',
+ x: x,
+ y: color,
+ marker: {
+ color: color,
+ colorscale: [
+ [0, 'rgb(255, 0, 0)'],
+ [0.5, 'rgb(0, 255, 0)'],
+ [1.0, 'rgb(0, 0, 255)']
+ ]
+ }
+ }], {
+ width: w,
+ height: h,
+ margin: {l: 0, t: 0, b: 0, r: 0}
+ })
+ .then(function() {
+ var total = readPixel(gd.querySelector('.gl-canvas-context'), 0, 0, w, h)
+ .reduce(function(acc, v) { return acc + v; }, 0);
+
+ // the total value was 3777134 before PR
+ // https://github.com/plotly/plotly.js/pull/2377
+ // and 105545275 after.
+ expect(total).toBeGreaterThan(4e6);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should work with typed array', function(done) {
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ mode: 'markers',
+ x: new Float32Array([1, 2, 3]),
+ y: new Float32Array([1, 2, 1]),
+ marker: {
+ size: 20,
+ colorscale: [[0, 'gray'], [1, 'red']],
+ cmin: 0,
+ cmax: 1,
+ showscale: true,
+ color: new Float32Array([0, 0.5, 1.0])
+ }
+ }])
+ .then(function() {
+ var opts = gd.calcdata[0][0].t._scene.markerOptions[0];
+
+ expect(opts.colors).toBeCloseTo2DArray([
+ [0.5, 0.5, 0.5, 1],
+ [0.75, 0.25, 0.25, 1],
+ [1, 0, 0, 1]
+ ]);
+
+ expect(opts.positions)
+ .toBeCloseToArray([1, 1, 2, 2, 3, 1]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should handle transform traces properly (calcTransform case)', function(done) {
+ spyOn(ScatterGl, 'calc').and.callThrough();
+
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ x: [1, 2, 3],
+ y: [1, 2, 1],
+ transforms: [{
+ type: 'filter',
+ target: 'x',
+ operation: '>',
+ value: 1
+ }]
+ }])
+ .then(function() {
+ expect(ScatterGl.calc).toHaveBeenCalledTimes(2);
+
+ var opts = gd.calcdata[0][0].t._scene.markerOptions;
+ // length === 2 before #2677
+ expect(opts.length).toBe(1);
+
+ return Plotly.restyle(gd, 'selectedpoints', [[1]]);
+ })
+ .then(function() {
+ // was === 1 before #2677
+ var scene = gd.calcdata[0][0].t._scene;
+ expect(scene.selectBatch[0]).toEqual([0]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should handle transform traces properly (default transform case)', function(done) {
+ spyOn(ScatterGl, 'calc').and.callThrough();
+
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ x: [1, 2, 3],
+ y: [1, 2, 1],
+ transforms: [{
+ type: 'groupby',
+ groups: ['a', 'b', 'a']
+ }]
+ }])
+ .then(function() {
+ // twice per 'expanded' trace
+ expect(ScatterGl.calc).toHaveBeenCalledTimes(4);
+
+ // 'scene' from opts0 and opts1 is linked to the same object,
+ // which has two items, one for each 'expanded' trace
+ var opts0 = gd.calcdata[0][0].t._scene.markerOptions;
+ expect(opts0.length).toBe(2);
+
+ var opts1 = gd.calcdata[1][0].t._scene.markerOptions;
+ expect(opts1.length).toBe(2);
+
+ return Plotly.restyle(gd, 'selectedpoints', [[1]]);
+ })
+ .then(function() {
+ var scene = gd.calcdata[0][0].t._scene;
+ expect(scene.selectBatch).toEqual([[], [0]]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should not cause infinite loops when coordinate arrays start/end with NaN', function(done) {
+ function _assertPositions(msg, cont, exp) {
+ var pos = gd._fullLayout._plots.xy._scene[cont]
+ .map(function(opt) { return opt.positions; });
+ expect(pos).toBeCloseTo2DArray(exp, 2, msg);
+ }
+
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ mode: 'lines',
+ x: [1, 2, 3],
+ y: [null, null, null]
+ }, {
+ type: 'scattergl',
+ mode: 'lines',
+ x: [1, 2, 3],
+ y: [1, 2, null]
+ }, {
+ type: 'scattergl',
+ mode: 'lines',
+ x: [null, 2, 3],
+ y: [1, 2, 3]
+ }, {
+ type: 'scattergl',
+ mode: 'lines',
+ x: [null, null, null],
+ y: [1, 2, 3]
+ }, {
+ }])
+ .then(function() {
+ _assertPositions('base', 'lineOptions', [
+ [],
+ [1, 1, 2, 2],
+ [2, 2, 3, 3],
+ []
+ ]);
+
+ return Plotly.restyle(gd, 'fill', 'tozerox');
+ })
+ .then(function() {
+ _assertPositions('tozerox', 'lineOptions', [
+ [],
+ [1, 1, 2, 2],
+ [2, 2, 3, 3],
+ []
+ ]);
+ _assertPositions('tozerox', 'fillOptions', [
+ [0, undefined, 0, undefined],
+ [0, 1, 1, 1, 2, 2, 0, 2],
+ [0, 2, 2, 2, 3, 3, 0, 3],
+ [0, undefined, 0, undefined]
+ ]);
+
+ return Plotly.restyle(gd, 'fill', 'tozeroy');
+ })
+ .then(function() {
+ _assertPositions('tozeroy', 'lineOptions', [
+ [],
+ [1, 1, 2, 2],
+ [2, 2, 3, 3],
+ []
+ ]);
+ _assertPositions('tozeroy', 'fillOptions', [
+ [undefined, 0, undefined, 0],
+ [1, 0, 1, 1, 2, 2, 2, 0],
+ [2, 0, 2, 2, 3, 3, 3, 0],
+ [undefined, 0, undefined, 0]
+ ]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+});
+
+describe('Test scattergl autorange:', function() {
+ describe('should return the same value as SVG scatter for ~small~ data', function() {
+ var gd;
+
+ beforeEach(function() {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+ gd = createGraphDiv();
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ var specs = [
+ {name: 'lines+markers', fig: require('@mocks/gl2d_10.json')},
+ {name: 'bubbles', fig: require('@mocks/gl2d_12.json')},
+ {name: 'line on log axes', fig: require('@mocks/gl2d_14.json')},
+ {name: 'fill to zero', fig: require('@mocks/gl2d_axes_labels2.json')},
+ {name: 'annotations', fig: require('@mocks/gl2d_annotations.json')}
+ ];
+
+ specs.forEach(function(s) {
+ it('@gl - case ' + s.name, function(done) {
+ var glRangeX;
+ var glRangeY;
+
+ // ensure the mocks have auto-range turned on
+ var glFig = Lib.extendDeep({}, s.fig);
+ Lib.extendDeep(glFig.layout, {xaxis: {autorange: true}});
+ Lib.extendDeep(glFig.layout, {yaxis: {autorange: true}});
+
+ var svgFig = Lib.extendDeep({}, glFig);
+ svgFig.data.forEach(function(t) { t.type = 'scatter'; });
+
+ Plotly.newPlot(gd, glFig).then(function() {
+ glRangeX = gd._fullLayout.xaxis.range;
+ glRangeY = gd._fullLayout.yaxis.range;
+ })
+ .then(function() {
+ return Plotly.newPlot(gd, svgFig);
+ })
+ .then(function() {
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray(glRangeX, 'x range');
+ expect(gd._fullLayout.yaxis.range).toBeCloseToArray(glRangeY, 'y range');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+ });
+ });
+
+ describe('should return the approximative values for ~big~ data', function() {
+ var gd;
+
+ beforeEach(function() {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+ gd = createGraphDiv();
+ // to avoid expansive draw calls (which could be problematic on CI)
+ spyOn(ScatterGl, 'plot').and.callFake(function(gd) {
+ gd._fullLayout._plots.xy._scene.scatter2d = {draw: function() {}};
+ gd._fullLayout._plots.xy._scene.line2d = {draw: function() {}};
+ });
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ // threshold for 'fast' axis expansion routine
+ var N = 1e5;
+ var x = new Array(N);
+ var y = new Array(N);
+ var ms = new Array(N);
+
+ Lib.seedPseudoRandom();
+
+ for(var i = 0; i < N; i++) {
+ x[i] = Lib.pseudoRandom();
+ y[i] = Lib.pseudoRandom();
+ ms[i] = 10 * Lib.pseudoRandom() + 20;
+ }
+
+ it('@gl - case scalar marker.size', function(done) {
+ Plotly.newPlot(gd, [{
+ type: 'scattergl',
+ mode: 'markers',
+ x: x,
+ y: y,
+ marker: {size: 10}
+ }])
+ .then(function() {
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.079, 1.079], 2, 'x range');
+ expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.105, 1.105], 2, 'y range');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl - case array marker.size', function(done) {
+ Plotly.newPlot(gd, [{
+ type: 'scattergl',
+ mode: 'markers',
+ x: x,
+ y: y,
+ marker: {size: ms}
+ }])
+ .then(function() {
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.119, 1.119], 2, 'x range');
+ expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.199, 1.199], 2, 'y range');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl - case mode:lines', function(done) {
+ Plotly.newPlot(gd, [{
+ type: 'scattergl',
+ mode: 'lines',
+ y: y,
+ }])
+ .then(function() {
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([0, N - 1], 2, 'x range');
+ expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.0555, 1.0555], 2, 'y range');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+ });
});
diff --git a/test/jasmine/tests/scatterpolargl_test.js b/test/jasmine/tests/scatterpolargl_test.js
index f56bdfe34d9..be878adece3 100644
--- a/test/jasmine/tests/scatterpolargl_test.js
+++ b/test/jasmine/tests/scatterpolargl_test.js
@@ -108,7 +108,7 @@ describe('Test scatterpolargl hover:', function() {
name: 'Trial 3'
}]
.forEach(function(specs) {
- it('should generate correct hover labels ' + specs.desc, function(done) {
+ it('@gl should generate correct hover labels ' + specs.desc, function(done) {
run(specs).catch(failTest).then(done);
});
});
diff --git a/test/jasmine/tests/volume_test.js b/test/jasmine/tests/volume_test.js
index bcb58d947d0..eef221ab97d 100644
--- a/test/jasmine/tests/volume_test.js
+++ b/test/jasmine/tests/volume_test.js
@@ -264,7 +264,7 @@ describe('Test volume', function() {
destroyGraphDiv();
});
- it('should clear *cauto* when restyle *cmin* and/or *cmax*', function(done) {
+ it('@gl should clear *cauto* when restyle *cmin* and/or *cmax*', function(done) {
function _assert(user, full) {
var trace = gd.data[0];
var fullTrace = gd._fullData[0];