From bcc756a4b5613bdf78f8bc96e730cda4b064f5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:08:23 -0400 Subject: [PATCH 01/10] add a few missing @gl tags --- test/jasmine/tests/gl2d_scatterplot_contour_test.js | 10 ++++------ test/jasmine/tests/gl3dlayout_test.js | 4 ++-- test/jasmine/tests/mesh3d_test.js | 2 +- test/jasmine/tests/scatterpolargl_test.js | 2 +- test/jasmine/tests/volume_test.js | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/test/jasmine/tests/gl2d_scatterplot_contour_test.js b/test/jasmine/tests/gl2d_scatterplot_contour_test.js index 91355e3a92e..cdcb97c7cff 100644 --- a/test/jasmine/tests/gl2d_scatterplot_contour_test.js +++ b/test/jasmine/tests/gl2d_scatterplot_contour_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/gl3dlayout_test.js b/test/jasmine/tests/gl3dlayout_test.js index 6957da72adb..46fe32e1421 100644 --- a/test/jasmine/tests/gl3dlayout_test.js +++ b/test/jasmine/tests/gl3dlayout_test.js @@ -277,10 +277,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/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/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]; From 627751fad8cf3372818036ffc12682b24d609611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:12:56 -0400 Subject: [PATCH 02/10] rename gl2d_scatterplot_contour_test.js -> contourgl_test.js --- .../tests/{gl2d_scatterplot_contour_test.js => contourgl_test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/jasmine/tests/{gl2d_scatterplot_contour_test.js => contourgl_test.js} (100%) diff --git a/test/jasmine/tests/gl2d_scatterplot_contour_test.js b/test/jasmine/tests/contourgl_test.js similarity index 100% rename from test/jasmine/tests/gl2d_scatterplot_contour_test.js rename to test/jasmine/tests/contourgl_test.js From c227b7d28d2ead5ba198b28fac1b8ab47f1386f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:13:59 -0400 Subject: [PATCH 03/10] rename gl2d_pointcloud_test.js -> pointcloud_test.js --- .../jasmine/tests/{gl2d_pointcloud_test.js => pointcloud_test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/jasmine/tests/{gl2d_pointcloud_test.js => pointcloud_test.js} (100%) 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 From 2879e92a62087098048d0b9610bbba63e315f924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:14:35 -0400 Subject: [PATCH 04/10] rename gl2d_double_click_test.js -> scattergl_select_test.js --- .../tests/{gl2d_double_click_test.js => scattergl_select_test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/jasmine/tests/{gl2d_double_click_test.js => scattergl_select_test.js} (100%) diff --git a/test/jasmine/tests/gl2d_double_click_test.js b/test/jasmine/tests/scattergl_select_test.js similarity index 100% rename from test/jasmine/tests/gl2d_double_click_test.js rename to test/jasmine/tests/scattergl_select_test.js From 9aa035710312fc45160503e3282a80a4b4ac9bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:32:53 -0400 Subject: [PATCH 05/10] split gl3d_plot_interact_test.js - add new gl3d_hover_click_test.js - mv a few scatter3d-only test to scatter3d_test.js --- test/jasmine/tests/gl3d_hover_click_test.js | 542 +++++++++++++ test/jasmine/tests/gl3d_plot_interact_test.js | 716 +----------------- test/jasmine/tests/scatter3d_test.js | 226 +++++- 3 files changed, 768 insertions(+), 716 deletions(-) create mode 100644 test/jasmine/tests/gl3d_hover_click_test.js 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..335e95ea125 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, { 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); + }); +}); From e0761e599acb4b433c1cad263f9321fc3468feaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:35:44 -0400 Subject: [PATCH 06/10] merge gl_plot_interact_basic_test.js into gl3d_plot_interact_test.js --- test/jasmine/tests/gl3d_plot_interact_test.js | 71 ++++++++++++++++ .../tests/gl_plot_interact_basic_test.js | 81 ------------------- 2 files changed, 71 insertions(+), 81 deletions(-) delete mode 100644 test/jasmine/tests/gl_plot_interact_basic_test.js diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 335e95ea125..821406f09bf 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -1562,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/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); - }); -}); From 2a15dad95c5cdfcdeb9aa557ab58298eee88c288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:38:46 -0400 Subject: [PATCH 07/10] merge gl3daxes_test.js into gl3dlayout_test.js --- test/jasmine/tests/gl3daxes_test.js | 110 -------------------------- test/jasmine/tests/gl3dlayout_test.js | 109 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 110 deletions(-) delete mode 100644 test/jasmine/tests/gl3daxes_test.js 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 46fe32e1421..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'; From bbfa7a9eef8009e4cbb21c79aa5ff94567ff3d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 11:42:09 -0400 Subject: [PATCH 08/10] :hocho: gl2d_date_axis_render_test.js ... which tests the old scattergl fancy / fast logic in the pre-regl scattergl sub-modules --- .../tests/gl2d_date_axis_render_test.js | 105 ------------------ 1 file changed, 105 deletions(-) delete mode 100644 test/jasmine/tests/gl2d_date_axis_render_test.js 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(); - }); -}); From 28d44b0565291d02a6743b8a5db637cbad60f1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 12:15:06 -0400 Subject: [PATCH 09/10] mv scattergl-only tests in gl2d_plot_interact_test.js ... in scattergl_test.js and scattergl_select_test.js --- test/jasmine/tests/gl2d_plot_interact_test.js | 822 ++---------------- test/jasmine/tests/scattergl_select_test.js | 228 +++-- test/jasmine/tests/scattergl_test.js | 594 ++++++++++++- 3 files changed, 824 insertions(+), 820 deletions(-) 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/scattergl_select_test.js b/test/jasmine/tests/scattergl_select_test.js index 3881223558d..8714ea9fe0f 100644 --- a/test/jasmine/tests/scattergl_select_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); + }); + }); }); From a27c51987a8964f1e178b78e22b81ef19bf46a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 May 2019 13:24:18 -0400 Subject: [PATCH 10/10] bump parallelism to 3 in test-jasmine2 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: