From 4a649f01f21fbf3ec814242a0b1f93ca9d2f8067 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 21 Oct 2019 11:39:10 -0400 Subject: [PATCH 1/8] record glplot aspect in fullLayout aspectratio --- package-lock.json | 5 ++--- package.json | 2 +- src/plots/gl3d/scene.js | 22 ++++++++++++++++++- test/jasmine/tests/gl3d_plot_interact_test.js | 18 +++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0c338db239..43306246d5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4646,9 +4646,8 @@ } }, "gl-plot3d": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.2.2.tgz", - "integrity": "sha512-is8RoDVUEbUM7kJ2qjhKJlfGLECH3ML9pTCW1V7ylUdmUACmcZ4lzJrQr/NIRkHC5WcUNOp3QJKPjBND3ngZ2A==", + "version": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", + "from": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", "requires": { "3d-view": "^2.0.0", "a-big-triangle": "^1.0.3", diff --git a/package.json b/package.json index 13dc9f775a7..d6f52a21839 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.1.1", "gl-plot2d": "^1.4.2", - "gl-plot3d": "^2.2.2", + "gl-plot3d": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", "gl-pointcloud2d": "^1.0.2", "gl-scatter3d": "^1.2.2", "gl-select-box": "^1.0.3", diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 8ef7cc2d424..069ba218c12 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -11,6 +11,7 @@ var createCamera = require('gl-plot3d').createCamera; var createPlot = require('gl-plot3d').createScene; +var mouseWheel = require('mouse-wheel'); var getContext = require('webgl-context'); var passiveSupported = require('has-passive-events'); @@ -243,7 +244,26 @@ function tryCreatePlot(scene, cameraObject, pixelRatio, canvas, gl) { } } - return failed < 2; + if(failed < 2) { + scene.wheelListener = mouseWheel(scene.glplot.canvas, function(dx, dy) { + // TODO remove now that we can disable scroll via scrollZoom? + if(scene.glplot.camera.keyBindingMode === false) return; + if(!scene.glplot.camera.enableWheel) return; + + if(scene.glplot.camera._ortho) { + var s = (dx > dy) ? 1.1 : 1.0 / 1.1; + + scene.fullSceneLayout.aspectratio.x = scene.glplot.aspect[0] *= s; + scene.fullSceneLayout.aspectratio.y = scene.glplot.aspect[1] *= s; + scene.fullSceneLayout.aspectratio.z = scene.glplot.aspect[2] *= s; + + scene.glplot.redraw(); + } + }, true); + + return true; + } + return false; } function initializeGLPlot(scene, pixelRatio, canvas, gl) { diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 0f3f80acacb..afd58808fbb 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -906,6 +906,24 @@ describe('Test gl3d drag and wheel interactions', function() { return scroll(sceneTarget2); }) .then(function() { + expect( + gd._fullLayout.scene2.aspectratio.x + ).toEqual( + gd._fullLayout.scene2._scene.glplot.aspect[0] + ); + + expect( + gd._fullLayout.scene2.aspectratio.y + ).toEqual( + gd._fullLayout.scene2._scene.glplot.aspect[1] + ); + + expect( + gd._fullLayout.scene2.aspectratio.z + ).toEqual( + gd._fullLayout.scene2._scene.glplot.aspect[2] + ); + _assertAndReset(2); }) .catch(failTest) From fe19e47430efc9ebab484ce5113bd7841b62a5cd Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 23 Oct 2019 15:16:11 -0400 Subject: [PATCH 2/8] Store direct aspectratio edits and emit relayout - combine two wheel listeners - use gl-plot3d aspect setter and getter --- package-lock.json | 4 +- package.json | 2 +- src/plot_api/subroutines.js | 3 +- src/plots/gl3d/scene.js | 109 +++++++++++------- test/jasmine/tests/gl3d_plot_interact_test.js | 5 +- test/jasmine/tests/plot_api_react_test.js | 13 ++- 6 files changed, 86 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 43306246d5d..7a777dd75a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4646,8 +4646,8 @@ } }, "gl-plot3d": { - "version": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", - "from": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", + "version": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", + "from": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", "requires": { "3d-view": "^2.0.0", "a-big-triangle": "^1.0.3", diff --git a/package.json b/package.json index d6f52a21839..0adb4c40067 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.1.1", "gl-plot2d": "^1.4.2", - "gl-plot3d": "git://github.com/gl-vis/gl-plot3d.git#4d41ca21b3c0a25db8776ce92261995323bb62e8", + "gl-plot3d": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", "gl-pointcloud2d": "^1.0.2", "gl-scatter3d": "^1.2.2", "gl-select-box": "^1.0.3", diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index df87fbcd474..f8cfecddc7e 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -578,8 +578,7 @@ exports.doCamera = function(gd) { var sceneLayout = fullLayout[sceneIds[i]]; var scene = sceneLayout._scene; - var cameraData = sceneLayout.camera; - scene.setCamera(cameraData); + scene.setViewport(sceneLayout); } }; diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 069ba218c12..ff36bc62891 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -9,9 +9,10 @@ 'use strict'; -var createCamera = require('gl-plot3d').createCamera; -var createPlot = require('gl-plot3d').createScene; -var mouseWheel = require('mouse-wheel'); +var glPlot3d = require('gl-plot3d'); +var createCamera = glPlot3d.createCamera; +var createPlot = glPlot3d.createScene; + var getContext = require('webgl-context'); var passiveSupported = require('has-passive-events'); @@ -244,26 +245,7 @@ function tryCreatePlot(scene, cameraObject, pixelRatio, canvas, gl) { } } - if(failed < 2) { - scene.wheelListener = mouseWheel(scene.glplot.canvas, function(dx, dy) { - // TODO remove now that we can disable scroll via scrollZoom? - if(scene.glplot.camera.keyBindingMode === false) return; - if(!scene.glplot.camera.enableWheel) return; - - if(scene.glplot.camera._ortho) { - var s = (dx > dy) ? 1.1 : 1.0 / 1.1; - - scene.fullSceneLayout.aspectratio.x = scene.glplot.aspect[0] *= s; - scene.fullSceneLayout.aspectratio.y = scene.glplot.aspect[1] *= s; - scene.fullSceneLayout.aspectratio.z = scene.glplot.aspect[2] *= s; - - scene.glplot.redraw(); - } - }, true); - - return true; - } - return false; + return failed < 2; } function initializeGLPlot(scene, pixelRatio, canvas, gl) { @@ -280,12 +262,23 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { var gd = scene.graphDiv; + var makeUpdate = function() { + var update = {}; + + // camera updates + update[scene.id + '.camera'] = getLayoutCamera(scene.camera); + + // scene updates + update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio(); + + return update; + }; + var relayoutCallback = function(scene) { if(scene.fullSceneLayout.dragmode === false) return; - var update = {}; - update[scene.id + '.camera'] = getLayoutCamera(scene.camera); - scene.saveCamera(gd.layout); + var update = makeUpdate(); + scene.saveLayout(gd.layout); scene.graphDiv.emit('plotly_relayout', update); }; @@ -293,8 +286,21 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { relayoutCallback(scene); }); - scene.glplot.canvas.addEventListener('wheel', function() { + scene.glplot.canvas.addEventListener('wheel', function(e) { if(gd._context._scrollZoom.gl3d) { + if(scene.glplot.camera._ortho) { + var s = (e.deltaX > e.deltaY) ? 1.1 : 1.0 / 1.1; + + var aspectratio = scene.fullSceneLayout.aspectratio; + + aspectratio.x = scene.glplot.aspect[0] *= s; + aspectratio.y = scene.glplot.aspect[1] *= s; + aspectratio.z = scene.glplot.aspect[2] *= s; + + scene.glplot.setAspectratio(aspectratio); + scene.glplot.redraw(); + } + relayoutCallback(scene); } }, passiveSupported ? {passive: false} : false); @@ -303,8 +309,7 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { if(scene.fullSceneLayout.dragmode === false) return; if(scene.camera.mouseListener.buttons === 0) return; - var update = {}; - update[scene.id + '.camera'] = getLayoutCamera(scene.camera); + var update = makeUpdate(); scene.graphDiv.emit('plotly_relayouting', update); }); @@ -516,7 +521,7 @@ proto.plot = function(sceneData, fullLayout, layout) { this.spikeOptions.merge(fullSceneLayout); // Update camera and camera mode - this.setCamera(fullSceneLayout.camera); + this.setViewport(fullSceneLayout); this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode); this.camera.enableWheel = this.graphDiv._context._scrollZoom.gl3d; @@ -740,8 +745,7 @@ proto.plot = function(sceneData, fullLayout, layout) { * Finally assign the computed aspecratio to the glplot module. This will have an effect * on the next render cycle. */ - this.glplot.aspect = aspectRatio; - + this.glplot.setAspectratio(fullSceneLayout.aspectratio); // Update frame position for multi plots var domain = fullSceneLayout.domain || null; @@ -771,9 +775,9 @@ proto.destroy = function() { this.glplot = null; }; -// getOrbitCamera :: plotly_coords -> orbit_camera_coords +// getCameraArrays :: plotly_coords -> orbit_camera_coords // inverse of getLayoutCamera -function getOrbitCamera(camera) { +function getCameraArrays(camera) { return [ [camera.eye.x, camera.eye.y, camera.eye.z], [camera.center.x, camera.center.y, camera.center.z], @@ -782,7 +786,7 @@ function getOrbitCamera(camera) { } // getLayoutCamera :: orbit_camera_coords -> plotly_coords -// inverse of getOrbitCamera +// inverse of getCameraArrays function getLayoutCamera(camera) { return { up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]}, @@ -798,9 +802,12 @@ proto.getCamera = function getCamera() { return getLayoutCamera(this.glplot.camera); }; -// set camera position with a set of plotly coords -proto.setCamera = function setCamera(cameraData) { - this.glplot.camera.lookAt.apply(this, getOrbitCamera(cameraData)); +// set gl-plot3d camera position and scene aspects with a set of plotly coords +proto.setViewport = function setViewport(sceneLayout) { + var cameraData = sceneLayout.camera; + + this.glplot.camera.lookAt.apply(this, getCameraArrays(cameraData)); + this.glplot.setAspectratio(sceneLayout.aspectratio); var newOrtho = (cameraData.projection.type === 'orthographic'); var oldOrtho = this.glplot.camera._ortho; @@ -827,11 +834,17 @@ proto.setCamera = function setCamera(cameraData) { }; // save camera to user layout (i.e. gd.layout) -proto.saveCamera = function saveCamera(layout) { +proto.saveLayout = function saveLayout(layout) { var fullLayout = this.fullLayout; + var cameraData = this.getCamera(); var cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); var cameraDataLastSave = cameraNestedProp.get(); + + var aspectData = this.glplot.getAspectratio(); + var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); + var aspectDataLastSave = aspectNestedProp.get(); + var hasChanged = false; function same(x, y, i, j) { @@ -859,15 +872,33 @@ proto.saveCamera = function saveCamera(layout) { } } + if(!hasChanged) { + if(aspectDataLastSave === undefined) { + hasChanged = true; + } else { + if( + aspectDataLastSave.x !== aspectData.x || + aspectDataLastSave.y !== aspectData.y || + aspectDataLastSave.z !== aspectData.z + ) { + hasChanged = true; + } + } + } + if(hasChanged) { var preGUI = {}; preGUI[this.id + '.camera'] = cameraDataLastSave; + preGUI[this.id + '.aspectratio'] = aspectDataLastSave; Registry.call('_storeDirectGUIEdit', layout, fullLayout._preGUI, preGUI); cameraNestedProp.set(cameraData); var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera'); cameraFullNP.set(cameraData); + + var aspectFullNP = Lib.nestedProperty(fullLayout, this.id + '.aspectratio'); + aspectFullNP.set(aspectData); } return hasChanged; diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index afd58808fbb..bd0fc33889e 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -1187,7 +1187,10 @@ describe('Test gl3d annotations', function() { var camera = scene.getCamera(); camera.eye = {x: x, y: y, z: z}; - scene.setCamera(camera); + scene.setViewport({ + camera: camera, + aspectratio: gd._fullLayout.scene.aspectratio + }); // need a fairly long delay to let the camera update here // 300 was not robust for me (AJ), 500 seems to be. return delay(500)(); diff --git a/test/jasmine/tests/plot_api_react_test.js b/test/jasmine/tests/plot_api_react_test.js index b3e047d1283..e622f3bb175 100644 --- a/test/jasmine/tests/plot_api_react_test.js +++ b/test/jasmine/tests/plot_api_react_test.js @@ -1968,11 +1968,14 @@ describe('Test Plotly.react + interactions under uirevision:', function() { function _mouseup() { var sceneLayout = gd._fullLayout.scene; var cameraOld = sceneLayout.camera; - sceneLayout._scene.setCamera({ - projection: {type: 'perspective'}, - eye: {x: 2, y: 2, z: 2}, - center: cameraOld.center, - up: cameraOld.up + sceneLayout._scene.setViewport({ + camera: { + projection: {type: 'perspective'}, + eye: {x: 2, y: 2, z: 2}, + center: cameraOld.center, + up: cameraOld.up + }, + aspectratio: gd._fullLayout.scene.aspectratio }); var target = gd.querySelector('.svg-container .gl-container #scene canvas'); From b92230a0b99f883dd009e89890afc7555450a7a2 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 23 Oct 2019 16:33:41 -0400 Subject: [PATCH 3/8] Save camera and aspectratio only if they change before redraw the scene - consider aspectratio changes in modebar home and initial views - bump gl-plot3d 2.3.0 --- package-lock.json | 5 +- package.json | 2 +- src/components/modebar/buttons.js | 24 ++++-- src/plots/gl3d/scene.js | 84 +++++++++++-------- test/jasmine/tests/gl3d_plot_interact_test.js | 18 ---- 5 files changed, 68 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a777dd75a7..d5e1167a128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4646,8 +4646,9 @@ } }, "gl-plot3d": { - "version": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", - "from": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.3.0.tgz", + "integrity": "sha512-qg269QiLpaw16d2D5Gz9fa8vsLcA8kbX/cv1u9S7BsH6jD9qGYxsY8iWJ8ea9/68WhPS5En2kUavkXINkmHsOQ==", "requires": { "3d-view": "^2.0.0", "a-big-triangle": "^1.0.3", diff --git a/package.json b/package.json index 0adb4c40067..b30b2848d01 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.1.1", "gl-plot2d": "^1.4.2", - "gl-plot3d": "git://github.com/gl-vis/gl-plot3d.git#83e9c9406976e5ee92bf1f1851e4366e516c06f7", + "gl-plot3d": "^2.3.0", "gl-pointcloud2d": "^1.0.2", "gl-scatter3d": "^1.2.2", "gl-select-box": "^1.0.3", diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 5a9fb54164c..bc9e259f1fc 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -344,17 +344,27 @@ function handleCamera3d(gd, ev) { for(var i = 0; i < sceneIds.length; i++) { var sceneId = sceneIds[i]; - var key = sceneId + '.camera'; + var camera = sceneId + '.camera'; + var aspectratio = sceneId + '.aspectratio'; var scene = fullLayout[sceneId]._scene; + var didUpdate; if(attr === 'resetLastSave') { - aobj[key + '.up'] = scene.viewInitial.up; - aobj[key + '.eye'] = scene.viewInitial.eye; - aobj[key + '.center'] = scene.viewInitial.center; + aobj[camera + '.up'] = scene.viewInitial.up; + aobj[camera + '.eye'] = scene.viewInitial.eye; + aobj[camera + '.center'] = scene.viewInitial.center; + didUpdate = true; } else if(attr === 'resetDefault') { - aobj[key + '.up'] = null; - aobj[key + '.eye'] = null; - aobj[key + '.center'] = null; + aobj[camera + '.up'] = null; + aobj[camera + '.eye'] = null; + aobj[camera + '.center'] = null; + didUpdate = true; + } + + if(didUpdate) { + aobj[aspectratio + '.x'] = scene.viewInitial.aspectratio.x; + aobj[aspectratio + '.y'] = scene.viewInitial.aspectratio.y; + aobj[aspectratio + '.z'] = scene.viewInitial.aspectratio.z; } } diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index ff36bc62891..cff0720a88a 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -268,8 +268,10 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { // camera updates update[scene.id + '.camera'] = getLayoutCamera(scene.camera); - // scene updates - update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio(); + if(scene.camera._ortho === true) { + // scene updates + update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio(); + } return update; }; @@ -290,15 +292,12 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { if(gd._context._scrollZoom.gl3d) { if(scene.glplot.camera._ortho) { var s = (e.deltaX > e.deltaY) ? 1.1 : 1.0 / 1.1; - - var aspectratio = scene.fullSceneLayout.aspectratio; - - aspectratio.x = scene.glplot.aspect[0] *= s; - aspectratio.y = scene.glplot.aspect[1] *= s; - aspectratio.z = scene.glplot.aspect[2] *= s; - - scene.glplot.setAspectratio(aspectratio); - scene.glplot.redraw(); + var o = scene.glplot.getAspectratio(); + scene.glplot.setAspectratio({ + x: s * o.x, + y: s * o.y, + z: s * o.z + }); } relayoutCallback(scene); @@ -747,6 +746,15 @@ proto.plot = function(sceneData, fullLayout, layout) { */ this.glplot.setAspectratio(fullSceneLayout.aspectratio); + // save 'initial' camera view settings for modebar button + if(!this.viewInitial.aspectratio) { + this.viewInitial.aspectratio = { + x: fullSceneLayout.aspectratio.x, + y: fullSceneLayout.aspectratio.y, + z: fullSceneLayout.aspectratio.z + }; + } + // Update frame position for multi plots var domain = fullSceneLayout.domain || null; var size = fullLayout._size || null; @@ -841,25 +849,25 @@ proto.saveLayout = function saveLayout(layout) { var cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); var cameraDataLastSave = cameraNestedProp.get(); + var aspectData = this.glplot.getAspectratio(); - var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); + var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio'); var aspectDataLastSave = aspectNestedProp.get(); - var hasChanged = false; - function same(x, y, i, j) { var vectors = ['up', 'center', 'eye']; var components = ['x', 'y', 'z']; return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]); } + var cameraChanged = false; if(cameraDataLastSave === undefined) { - hasChanged = true; + cameraChanged = true; } else { for(var i = 0; i < 3; i++) { for(var j = 0; j < 3; j++) { if(!same(cameraData, cameraDataLastSave, i, j)) { - hasChanged = true; + cameraChanged = true; break; } } @@ -868,37 +876,39 @@ proto.saveLayout = function saveLayout(layout) { if(!cameraDataLastSave.projection || ( cameraData.projection && cameraData.projection.type !== cameraDataLastSave.projection.type)) { - hasChanged = true; + cameraChanged = true; } } - if(!hasChanged) { - if(aspectDataLastSave === undefined) { - hasChanged = true; - } else { - if( - aspectDataLastSave.x !== aspectData.x || - aspectDataLastSave.y !== aspectData.y || - aspectDataLastSave.z !== aspectData.z - ) { - hasChanged = true; - } - } - } + var aspectChanged = ( + aspectDataLastSave === undefined || ( + aspectDataLastSave.x !== aspectData.x || + aspectDataLastSave.y !== aspectData.y || + aspectDataLastSave.z !== aspectData.z + )); + var hasChanged = cameraChanged || aspectChanged; if(hasChanged) { var preGUI = {}; - preGUI[this.id + '.camera'] = cameraDataLastSave; - preGUI[this.id + '.aspectratio'] = aspectDataLastSave; + if(cameraChanged) preGUI[this.id + '.camera'] = cameraDataLastSave; + if(aspectChanged) preGUI[this.id + '.aspectratio'] = aspectDataLastSave; Registry.call('_storeDirectGUIEdit', layout, fullLayout._preGUI, preGUI); - cameraNestedProp.set(cameraData); + if(cameraChanged) { + cameraNestedProp.set(cameraData); - var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera'); - cameraFullNP.set(cameraData); + var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera'); + cameraFullNP.set(cameraData); + } + + if(aspectChanged) { + aspectNestedProp.set(aspectData); - var aspectFullNP = Lib.nestedProperty(fullLayout, this.id + '.aspectratio'); - aspectFullNP.set(aspectData); + var aspectFullNP = Lib.nestedProperty(fullLayout, this.id + '.aspectratio'); + aspectFullNP.set(aspectData); + + this.glplot.redraw(); + } } return hasChanged; diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index bd0fc33889e..69f34fc1d6b 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -906,24 +906,6 @@ describe('Test gl3d drag and wheel interactions', function() { return scroll(sceneTarget2); }) .then(function() { - expect( - gd._fullLayout.scene2.aspectratio.x - ).toEqual( - gd._fullLayout.scene2._scene.glplot.aspect[0] - ); - - expect( - gd._fullLayout.scene2.aspectratio.y - ).toEqual( - gd._fullLayout.scene2._scene.glplot.aspect[1] - ); - - expect( - gd._fullLayout.scene2.aspectratio.z - ).toEqual( - gd._fullLayout.scene2._scene.glplot.aspect[2] - ); - _assertAndReset(2); }) .catch(failTest) From d825c0e18de420cb15cd30747f366e394729bff7 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 23 Oct 2019 22:04:13 -0400 Subject: [PATCH 4/8] improve relayout update object - now depends on changed parameter --- src/plots/gl3d/scene.js | 81 ++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index cff0720a88a..46b5a50e05e 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -261,14 +261,17 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { if(!success) return showNoWebGlMsg(scene); var gd = scene.graphDiv; + var layout = gd.layout; var makeUpdate = function() { var update = {}; - // camera updates - update[scene.id + '.camera'] = getLayoutCamera(scene.camera); + if(scene.isCameraChanged(layout)) { + // camera updates + update[scene.id + '.camera'] = scene.getCamera(); + } - if(scene.camera._ortho === true) { + if(scene.isAspectChanged(layout)) { // scene updates update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio(); } @@ -280,7 +283,7 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { if(scene.fullSceneLayout.dragmode === false) return; var update = makeUpdate(); - scene.saveLayout(gd.layout); + scene.saveLayout(layout); scene.graphDiv.emit('plotly_relayout', update); }; @@ -783,7 +786,7 @@ proto.destroy = function() { this.glplot = null; }; -// getCameraArrays :: plotly_coords -> orbit_camera_coords +// getCameraArrays :: plotly_coords -> gl-plot3d_coords // inverse of getLayoutCamera function getCameraArrays(camera) { return [ @@ -793,7 +796,7 @@ function getCameraArrays(camera) { ]; } -// getLayoutCamera :: orbit_camera_coords -> plotly_coords +// getLayoutCamera :: gl-plot3d_coords -> plotly_coords // inverse of getCameraArrays function getLayoutCamera(camera) { return { @@ -804,14 +807,14 @@ function getLayoutCamera(camera) { }; } -// get camera position in plotly coords from 'orbit-camera' coords -proto.getCamera = function getCamera() { +// get camera position in plotly coords from 'gl-plot3d' coords +proto.getCamera = function() { this.glplot.camera.view.recalcMatrix(this.camera.view.lastT()); return getLayoutCamera(this.glplot.camera); }; // set gl-plot3d camera position and scene aspects with a set of plotly coords -proto.setViewport = function setViewport(sceneLayout) { +proto.setViewport = function(sceneLayout) { var cameraData = sceneLayout.camera; this.glplot.camera.lookAt.apply(this, getCameraArrays(cameraData)); @@ -841,33 +844,25 @@ proto.setViewport = function setViewport(sceneLayout) { } }; -// save camera to user layout (i.e. gd.layout) -proto.saveLayout = function saveLayout(layout) { - var fullLayout = this.fullLayout; - +proto.isCameraChanged = function(layout) { var cameraData = this.getCamera(); var cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); var cameraDataLastSave = cameraNestedProp.get(); - - var aspectData = this.glplot.getAspectratio(); - var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio'); - var aspectDataLastSave = aspectNestedProp.get(); - function same(x, y, i, j) { var vectors = ['up', 'center', 'eye']; var components = ['x', 'y', 'z']; return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]); } - var cameraChanged = false; + var changed = false; if(cameraDataLastSave === undefined) { - cameraChanged = true; + changed = true; } else { for(var i = 0; i < 3; i++) { for(var j = 0; j < 3; j++) { if(!same(cameraData, cameraDataLastSave, i, j)) { - cameraChanged = true; + changed = true; break; } } @@ -876,22 +871,58 @@ proto.saveLayout = function saveLayout(layout) { if(!cameraDataLastSave.projection || ( cameraData.projection && cameraData.projection.type !== cameraDataLastSave.projection.type)) { - cameraChanged = true; + changed = true; } } - var aspectChanged = ( + return changed; +}; + +proto.isAspectChanged = function(layout) { + var aspectData = this.glplot.getAspectratio(); + var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio'); + var aspectDataLastSave = aspectNestedProp.get(); + + return ( aspectDataLastSave === undefined || ( aspectDataLastSave.x !== aspectData.x || aspectDataLastSave.y !== aspectData.y || aspectDataLastSave.z !== aspectData.z )); +}; + +// save camera to user layout (i.e. gd.layout) +proto.saveLayout = function(layout) { + var fullLayout = this.fullLayout; + + var cameraData; + var cameraNestedProp; + var cameraDataLastSave; + + var aspectData; + var aspectNestedProp; + var aspectDataLastSave; + + var cameraChanged = this.isCameraChanged(layout); + var aspectChanged = this.isAspectChanged(layout); var hasChanged = cameraChanged || aspectChanged; if(hasChanged) { var preGUI = {}; - if(cameraChanged) preGUI[this.id + '.camera'] = cameraDataLastSave; - if(aspectChanged) preGUI[this.id + '.aspectratio'] = aspectDataLastSave; + if(cameraChanged) { + cameraData = this.getCamera(); + cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'); + cameraDataLastSave = cameraNestedProp.get(); + + preGUI[this.id + '.camera'] = cameraDataLastSave; + } + if(aspectChanged) { + aspectData = this.glplot.getAspectratio(); + aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio'); + aspectDataLastSave = aspectNestedProp.get(); + + preGUI[this.id + '.aspectratio'] = aspectDataLastSave; + } Registry.call('_storeDirectGUIEdit', layout, fullLayout._preGUI, preGUI); if(cameraChanged) { From d486dcd56560feca1c8e486bffee31e822f8fda4 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 23 Oct 2019 22:18:02 -0400 Subject: [PATCH 5/8] correct initializeGLPlot argument --- src/plots/gl3d/scene.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 46b5a50e05e..50ba440390d 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -417,7 +417,6 @@ proto.recoverContext = function() { var scene = this; var gl = this.glplot.gl; var canvas = this.glplot.canvas; - var camera = this.glplot.camera; var pixelRatio = this.glplot.pixelRatio; this.glplot.dispose(); @@ -426,7 +425,7 @@ proto.recoverContext = function() { requestAnimationFrame(tryRecover); return; } - if(!initializeGLPlot(scene, camera, pixelRatio, canvas, gl)) { + if(!initializeGLPlot(scene, pixelRatio, canvas, gl)) { Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.'); return; } From 496059ca3fe1c9baf4a6d690be7987c5cf5cb39a Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 23 Oct 2019 22:43:00 -0400 Subject: [PATCH 6/8] simplify gl3d pixelRatio pass --- src/plots/gl3d/scene.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 50ba440390d..d24abe7b41b 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -248,10 +248,10 @@ function tryCreatePlot(scene, cameraObject, pixelRatio, canvas, gl) { return failed < 2; } -function initializeGLPlot(scene, pixelRatio, canvas, gl) { +function initializeGLPlot(scene, canvas, gl) { scene.initializeGLCamera(); - var success = tryCreatePlot(scene, scene.camera, pixelRatio, canvas, gl); + var success = tryCreatePlot(scene, scene.camera, scene.pixelRatio, canvas, gl); /* * createPlot will throw when webgl is not enabled in the client. * Lets return an instance of the module with all functions noop'd. @@ -393,7 +393,7 @@ function Scene(options, fullLayout) { this.convertAnnotations = Registry.getComponentMethod('annotations3d', 'convert'); this.drawAnnotations = Registry.getComponentMethod('annotations3d', 'draw'); - initializeGLPlot(this, this.pixelRatio); + initializeGLPlot(this); } var proto = Scene.prototype; @@ -417,7 +417,7 @@ proto.recoverContext = function() { var scene = this; var gl = this.glplot.gl; var canvas = this.glplot.canvas; - var pixelRatio = this.glplot.pixelRatio; + this.glplot.dispose(); function tryRecover() { @@ -425,7 +425,7 @@ proto.recoverContext = function() { requestAnimationFrame(tryRecover); return; } - if(!initializeGLPlot(scene, pixelRatio, canvas, gl)) { + if(!initializeGLPlot(scene, canvas, gl)) { Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.'); return; } @@ -825,8 +825,6 @@ proto.setViewport = function(sceneLayout) { if(newOrtho !== oldOrtho) { this.glplot.redraw(); - var pixelRatio = this.glplot.pixelRatio; - var RGBA = this.glplot.clearColor; this.glplot.gl.clearColor( RGBA[0], RGBA[1], RGBA[2], RGBA[3] @@ -838,7 +836,7 @@ proto.setViewport = function(sceneLayout) { this.glplot.dispose(); - initializeGLPlot(this, pixelRatio); + initializeGLPlot(this); this.glplot.camera._ortho = newOrtho; } }; From e4a30be950715eb10980d12f5ff7885f7965be79 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 24 Oct 2019 11:34:42 -0400 Subject: [PATCH 7/8] add new tests to cover both orthographic and perspective cases - test modebar with orthographic and add checks for initial aspect ratios - make sure plotly_relayout is emitted after each interaction --- test/jasmine/tests/gl3d_plot_interact_test.js | 386 +++++++++++++++++- 1 file changed, 374 insertions(+), 12 deletions(-) diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 69f34fc1d6b..4721c924f86 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -412,7 +412,7 @@ describe('Test gl3d plots', function() { }); }); -describe('Test gl3d modebar handlers', function() { +describe('Test gl3d modebar handlers - perspective case', function() { var gd, modeBar; function assertScenes(cont, attr, val) { @@ -444,8 +444,17 @@ describe('Test gl3d modebar handlers', function() { { type: 'surface', scene: 'scene2' } ], layout: { - scene: { camera: { eye: { x: 0.1, y: 0.1, z: 1 }}}, - scene2: { camera: { eye: { x: 2.5, y: 2.5, z: 2.5 }}} + scene: { + camera: { + eye: { x: 0.1, y: 0.1, z: 1 } + } + }, + scene2: { + camera: { + eye: { x: 2.5, y: 2.5, z: 2.5 } + }, + aspectratio: { x: 3, y: 2, z: 1 } + } } }; @@ -566,6 +575,242 @@ describe('Test gl3d modebar handlers', function() { buttonDefault.click(); }); + it('@gl button resetCameraDefault3d should reset to initial aspectratios', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + + expect(gd._fullLayout.scene._scene.viewInitial.aspectratio).toEqual({ x: 1, y: 1, z: 1 }); + expect(gd._fullLayout.scene2._scene.viewInitial.aspectratio).toEqual({ x: 3, y: 2, z: 1 }); + + gd.once('plotly_relayout', function() { + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().x).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().y).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().z).toBeCloseTo(1); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().x).toBeCloseTo(3); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().y).toBeCloseTo(2); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().z).toBeCloseTo(1); + + done(); + }); + + buttonDefault.click(); + }); + + it('@gl button resetCameraLastSave3d should reset to initial aspectratios', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + + expect(gd._fullLayout.scene._scene.viewInitial.aspectratio).toEqual({ x: 1, y: 1, z: 1 }); + expect(gd._fullLayout.scene2._scene.viewInitial.aspectratio).toEqual({ x: 3, y: 2, z: 1 }); + + gd.once('plotly_relayout', function() { + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().x).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().y).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().z).toBeCloseTo(1); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().x).toBeCloseTo(3); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().y).toBeCloseTo(2); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().z).toBeCloseTo(1); + + done(); + }); + + buttonDefault.click(); + }); + + it('@gl button resetCameraLastSave3d should reset camera to default', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + var buttonLastSave = selectButton(modeBar, 'resetCameraLastSave3d'); + + Plotly.relayout(gd, { + 'scene.camera.eye.z': 4, + 'scene2.camera.eye.z': 5 + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 0.1, 0.1, 4); + assertCameraEye(gd._fullLayout.scene2, 2.5, 2.5, 5); + + return new Promise(function(resolve) { + gd.once('plotly_relayout', resolve); + buttonLastSave.click(); + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 0.1, 0.1, 1); + assertCameraEye(gd._fullLayout.scene2, 2.5, 2.5, 2.5); + + return new Promise(function(resolve) { + gd.once('plotly_relayout', resolve); + buttonDefault.click(); + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 1.25, 1.25, 1.25); + assertCameraEye(gd._fullLayout.scene2, 1.25, 1.25, 1.25); + + return new Promise(function(resolve) { + gd.once('plotly_relayout', resolve); + buttonLastSave.click(); + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 0.1, 0.1, 1); + assertCameraEye(gd._fullLayout.scene2, 2.5, 2.5, 2.5); + + delete gd._fullLayout.scene._scene.viewInitial; + delete gd._fullLayout.scene2._scene.viewInitial; + + Plotly.relayout(gd, { + 'scene.bgcolor': '#d3d3d3', + 'scene.camera.eye.z': 4, + 'scene2.camera.eye.z': 5 + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 0.1, 0.1, 4); + assertCameraEye(gd._fullLayout.scene2, 2.5, 2.5, 5); + + return new Promise(function(resolve) { + gd.once('plotly_relayout', resolve); + buttonDefault.click(); + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 1.25, 1.25, 1.25); + assertCameraEye(gd._fullLayout.scene2, 1.25, 1.25, 1.25); + + return new Promise(function(resolve) { + gd.once('plotly_relayout', resolve); + buttonLastSave.click(); + }); + }) + .then(function() { + assertCameraEye(gd._fullLayout.scene, 0.1, 0.1, 4); + assertCameraEye(gd._fullLayout.scene2, 2.5, 2.5, 5); + }) + .then(done); + }); +}); + + +describe('Test gl3d modebar handlers - orthographic case', function() { + var gd, modeBar; + + function assertScenes(cont, attr, val) { + var sceneIds = cont._subplots.gl3d; + + sceneIds.forEach(function(sceneId) { + var thisVal = Lib.nestedProperty(cont[sceneId], attr).get(); + expect(thisVal).toEqual(val); + }); + } + + function assertCameraEye(sceneLayout, eyeX, eyeY, eyeZ) { + expect(sceneLayout.camera.eye.x).toEqual(eyeX); + expect(sceneLayout.camera.eye.y).toEqual(eyeY); + expect(sceneLayout.camera.eye.z).toEqual(eyeZ); + + var camera = sceneLayout._scene.getCamera(); + expect(camera.eye.x).toBeCloseTo(eyeX); + expect(camera.eye.y).toBeCloseTo(eyeY); + expect(camera.eye.z).toBeCloseTo(eyeZ); + } + + beforeEach(function(done) { + gd = createGraphDiv(); + + var mock = { + data: [ + { type: 'scatter3d' }, + { type: 'surface', scene: 'scene2' } + ], + layout: { + scene: { + camera: { + eye: { x: 0.1, y: 0.1, z: 1 }, + projection: {type: 'orthographic'} + } + }, + scene2: { + camera: { + eye: { x: 2.5, y: 2.5, z: 2.5 }, + projection: {type: 'orthographic'} + }, + aspectratio: { x: 3, y: 2, z: 1 } + } + } + }; + + Plotly.plot(gd, mock) + .then(delay(20)) + .then(function() { + modeBar = gd._fullLayout._modeBar; + }) + .then(done); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('@gl button resetCameraDefault3d should reset camera to default', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + + expect(gd._fullLayout.scene._scene.viewInitial.eye).toEqual({ x: 0.1, y: 0.1, z: 1 }); + expect(gd._fullLayout.scene2._scene.viewInitial.eye).toEqual({ x: 2.5, y: 2.5, z: 2.5 }); + + gd.once('plotly_relayout', function() { + assertScenes(gd._fullLayout, 'camera.eye.x', 1.25); + assertScenes(gd._fullLayout, 'camera.eye.y', 1.25); + assertScenes(gd._fullLayout, 'camera.eye.z', 1.25); + + expect(gd._fullLayout.scene._scene.getCamera().eye.z).toBeCloseTo(1.25); + expect(gd._fullLayout.scene2._scene.getCamera().eye.z).toBeCloseTo(1.25); + + done(); + }); + + buttonDefault.click(); + }); + + it('@gl button resetCameraDefault3d should reset to initial aspectratios', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + + expect(gd._fullLayout.scene._scene.viewInitial.aspectratio).toEqual({ x: 1, y: 1, z: 1 }); + expect(gd._fullLayout.scene2._scene.viewInitial.aspectratio).toEqual({ x: 3, y: 2, z: 1 }); + + gd.once('plotly_relayout', function() { + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().x).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().y).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().z).toBeCloseTo(1); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().x).toBeCloseTo(3); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().y).toBeCloseTo(2); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().z).toBeCloseTo(1); + + done(); + }); + + buttonDefault.click(); + }); + + it('@gl button resetCameraLastSave3d should reset to initial aspectratios', function(done) { + var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); + + expect(gd._fullLayout.scene._scene.viewInitial.aspectratio).toEqual({ x: 1, y: 1, z: 1 }); + expect(gd._fullLayout.scene2._scene.viewInitial.aspectratio).toEqual({ x: 3, y: 2, z: 1 }); + + gd.once('plotly_relayout', function() { + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().x).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().y).toBeCloseTo(1); + expect(gd._fullLayout.scene._scene.glplot.getAspectratio().z).toBeCloseTo(1); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().x).toBeCloseTo(3); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().y).toBeCloseTo(2); + expect(gd._fullLayout.scene2._scene.glplot.getAspectratio().z).toBeCloseTo(1); + + done(); + }); + + buttonDefault.click(); + }); + it('@gl button resetCameraLastSave3d should reset camera to default', function(done) { var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); var buttonLastSave = selectButton(modeBar, 'resetCameraLastSave3d'); @@ -773,22 +1018,28 @@ describe('Test gl3d drag and wheel interactions', function() { return Plotly.relayout(gd, {'scene.dragmode': 'orbit', 'scene2.dragmode': 'turntable'}); }) .then(function() { - expect(relayoutCallback).toHaveBeenCalledTimes(1); - relayoutCallback.calls.reset(); + _assertAndReset(1); return drag({node: sceneTarget, pos0: [0, 0], posN: [100, 100], noCover: true}); }) .then(function() { + _assertAndReset(1); + return drag({node: sceneTarget2, pos0: [0, 0], posN: [100, 100], noCover: true}); }) .then(function() { - _assertAndReset(2); + _assertAndReset(1); + return Plotly.plot(gd, [], {}, {scrollZoom: false}); }) .then(function() { + _assertAndReset(0); + return scroll(sceneTarget); }) .then(function() { + _assertAndReset(0); + return scroll(sceneTarget2); }) .then(function() { @@ -796,13 +1047,17 @@ describe('Test gl3d drag and wheel interactions', function() { return Plotly.plot(gd, [], {}, {scrollZoom: 'gl3d'}); }) .then(function() { + _assertAndReset(0); + return scroll(sceneTarget); }) .then(function() { + _assertAndReset(1); + return scroll(sceneTarget2); }) .then(function() { - _assertAndReset(2); + _assertAndReset(1); }) .catch(failTest) .then(done); @@ -877,22 +1132,28 @@ describe('Test gl3d drag and wheel interactions', function() { return Plotly.relayout(gd, {'scene.dragmode': 'orbit', 'scene2.dragmode': 'turntable'}); }) .then(function() { - expect(relayoutCallback).toHaveBeenCalledTimes(1); - relayoutCallback.calls.reset(); + _assertAndReset(1); return drag({node: sceneTarget, pos0: [0, 0], posN: [100, 100], noCover: true}); }) .then(function() { + _assertAndReset(1); + return drag({node: sceneTarget2, pos0: [0, 0], posN: [100, 100], noCover: true}); }) .then(function() { - _assertAndReset(2); + _assertAndReset(1); + return Plotly.plot(gd, [], {}, {scrollZoom: false}); }) .then(function() { + _assertAndReset(0); + return scroll(sceneTarget); }) .then(function() { + _assertAndReset(0); + return scroll(sceneTarget2); }) .then(function() { @@ -903,16 +1164,117 @@ describe('Test gl3d drag and wheel interactions', function() { return scroll(sceneTarget); }) .then(function() { + _assertAndReset(1); + return scroll(sceneTarget2); }) .then(function() { - _assertAndReset(2); + _assertAndReset(1); }) .catch(failTest) .then(done); }); - it('@gl should fire plotly_relayouting events', function(done) { + it('@gl should fire plotly_relayouting events when dragged - perspective case', function(done) { + var sceneTarget, relayoutEvent; + + var nsteps = 10; + var relayoutCnt = 0; + var events = []; + + var mock = { + data: [ + { type: 'scatter3d', x: [1, 2, 3], y: [2, 3, 1], z: [3, 1, 2] } + ], + layout: { + scene: { camera: { projection: {type: 'perspective'}, eye: { x: 0.1, y: 0.1, z: 1 }}}, + width: 400, height: 400 + } + }; + + Plotly.plot(gd, mock) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); + + return drag({ + node: sceneTarget, + pos0: [200, 200], + posN: [100, 100], + nsteps: nsteps, + buttons: 1, + noCover: true + }); + }) + .then(function() { + expect(events.length).toEqual(nsteps); + expect(relayoutCnt).toEqual(1); + Object.keys(relayoutEvent).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); + }) + .catch(failTest) + .then(done); + }); + + it('@gl should fire plotly_relayouting events when dragged - orthographic case', function(done) { + var sceneTarget, relayoutEvent; + + var nsteps = 10; + var relayoutCnt = 0; + var events = []; + + var mock = { + data: [ + { type: 'scatter3d', x: [1, 2, 3], y: [2, 3, 1], z: [3, 1, 2] } + ], + layout: { + scene: { camera: { projection: {type: 'orthographic'}, eye: { x: 0.1, y: 0.1, z: 1 }}}, + width: 400, height: 400 + } + }; + + Plotly.plot(gd, mock) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); + + return drag({ + node: sceneTarget, + pos0: [200, 200], + posN: [100, 100], + nsteps: nsteps, + buttons: 1, + noCover: true + }); + }) + .then(function() { + expect(events.length).toEqual(nsteps); + expect(relayoutCnt).toEqual(1); + Object.keys(relayoutEvent).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); + }) + .catch(failTest) + .then(done); + }); + + + it('@gl should fire plotly_relayouting events when dragged - orthographic case', function(done) { var sceneTarget, relayoutEvent; var nsteps = 10; From 3b28faef2ddd72d1ce6f6d2812f5b1f01917075f Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 24 Oct 2019 18:11:57 -0400 Subject: [PATCH 8/8] add few more tests to lock down the orthographic relayout issue --- test/jasmine/tests/gl3d_plot_interact_test.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 4721c924f86..91915c621f8 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -1175,6 +1175,65 @@ describe('Test gl3d drag and wheel interactions', function() { .then(done); }); + it('@gl should update the scene aspectratio when zooming with scroll wheel i.e. orthographic case', function(done) { + var sceneLayout, sceneLayout2, sceneTarget, sceneTarget2; + + var mock = { + data: [ + { type: 'scatter3d', x: [1, 2, 3], y: [2, 3, 1], z: [3, 1, 2] }, + { type: 'surface', scene: 'scene2', x: [1, 2], y: [2, 1], z: [[1, 2], [2, 1]] } + ], + layout: { + scene: { camera: { projection: {type: 'orthographic'}}}, + scene2: { camera: { projection: {type: 'orthographic'}}, aspectratio: { x: 3, y: 2, z: 1 }} + } + }; + + var aspectratio; + var relayoutEvent; + var relayoutCnt = 0; + + Plotly.plot(gd, mock) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + + sceneLayout = gd._fullLayout.scene; + sceneLayout2 = gd._fullLayout.scene2; + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); + sceneTarget2 = gd.querySelector('.svg-container .gl-container #scene2 canvas'); + + expect(sceneLayout.aspectratio).toEqual({x: 1, y: 1, z: 1}); + expect(sceneLayout2.aspectratio).toEqual({x: 3, y: 2, z: 1}); + }) + .then(function() { + return scroll(sceneTarget); + }) + .then(function() { + expect(relayoutCnt).toEqual(1); + + aspectratio = relayoutEvent['scene.aspectratio']; + expect(aspectratio.x).toBeCloseTo(0.909, 3, 'aspectratio.x'); + expect(aspectratio.y).toBeCloseTo(0.909, 3, 'aspectratio.y'); + expect(aspectratio.z).toBeCloseTo(0.909, 3, 'aspectratio.z'); + }) + .then(function() { + return scroll(sceneTarget2); + }) + .then(function() { + expect(relayoutCnt).toEqual(2); + + aspectratio = relayoutEvent['scene2.aspectratio']; + expect(aspectratio.x).toBeCloseTo(2.727, 3, 'aspectratio.x'); + expect(aspectratio.y).toBeCloseTo(1.818, 3, 'aspectratio.y'); + expect(aspectratio.z).toBeCloseTo(0.909, 3, 'aspectratio.z'); + }) + .catch(failTest) + .then(done); + }); + it('@gl should fire plotly_relayouting events when dragged - perspective case', function(done) { var sceneTarget, relayoutEvent; @@ -1216,8 +1275,10 @@ describe('Test gl3d drag and wheel interactions', function() { .then(function() { expect(events.length).toEqual(nsteps); expect(relayoutCnt).toEqual(1); + Object.keys(relayoutEvent).sort().forEach(function(key) { expect(Object.keys(events[0])).toContain(key); + expect(key).not.toBe('scene.aspectratio'); }); }) .catch(failTest) @@ -1267,6 +1328,7 @@ describe('Test gl3d drag and wheel interactions', function() { expect(relayoutCnt).toEqual(1); Object.keys(relayoutEvent).sort().forEach(function(key) { expect(Object.keys(events[0])).toContain(key); + expect(key).not.toBe('scene.aspectratio'); }); }) .catch(failTest)