diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 294bc602b71..82422887afb 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -347,14 +347,14 @@ function handleCamera3d(gd, ev) { var key = sceneId + '.camera'; var scene = fullLayout[sceneId]._scene; - if(attr === 'resetDefault') { - aobj[key] = Lib.extendDeep({}, scene.cameraInitial); - aobj[key].up = null; - aobj[key].eye = null; - aobj[key].center = null; - } - else if(attr === 'resetLastSave') { - aobj[key] = Lib.extendDeep({}, scene.cameraInitial); + if(attr === 'resetLastSave') { + aobj[key + '.up'] = scene.viewInitial.up; + aobj[key + '.eye'] = scene.viewInitial.eye; + aobj[key + '.center'] = scene.viewInitial.center; + } else if(attr === 'resetDefault') { + aobj[key + '.up'] = null; + aobj[key + '.eye'] = null; + aobj[key + '.center'] = null; } } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c8e440b32fc..e2407f94fe8 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1589,7 +1589,7 @@ function _restyle(gd, aobj, traces) { // and figure out what kind of graphics update we need to do for(var ai in aobj) { if(helpers.hasParent(aobj, ai)) { - throw new Error('cannot set ' + ai + 'and a parent attribute simultaneously'); + throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously'); } var vi = aobj[ai]; @@ -2095,7 +2095,7 @@ function _relayout(gd, aobj) { // alter gd.layout for(var ai in aobj) { if(helpers.hasParent(aobj, ai)) { - throw new Error('cannot set ' + ai + 'and a parent attribute simultaneously'); + throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously'); } var p = layoutNP(layout, ai); diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index c99979d7b98..55c390b7619 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -67,9 +67,25 @@ exports.plot = function plotGl3d(gd) { sceneLayout._scene = scene; } - // save 'initial' camera settings for modebar button - if(!scene.cameraInitial) { - scene.cameraInitial = Lib.extendDeep({}, sceneLayout.camera); + // save 'initial' camera view settings for modebar button + if(!scene.viewInitial) { + scene.viewInitial = { + up: { + x: camera.up.x, + y: camera.up.y, + z: camera.up.z + }, + eye: { + x: camera.eye.x, + y: camera.eye.y, + z: camera.eye.z + }, + center: { + x: camera.center.x, + y: camera.center.y, + z: camera.center.z + } + }; } scene.plot(fullSceneData, fullLayout, gd.layout); diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index d7cc4bff620..c1ba16cee4d 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -255,7 +255,7 @@ function initializeGLPlot(scene, camera, pixelRatio, canvas, gl) { if(scene.fullSceneLayout.dragmode === false) return; var update = {}; - update[scene.id + '.camera'] = getLayoutCamera(scene.camera, scene.camera._ortho); + update[scene.id + '.camera'] = getLayoutCamera(scene.camera); scene.saveCamera(gd.layout); scene.graphDiv.emit('plotly_relayout', update); }; @@ -758,19 +758,19 @@ function getOrbitCamera(camera) { // getLayoutCamera :: orbit_camera_coords -> plotly_coords // inverse of getOrbitCamera -function getLayoutCamera(camera, isOrtho) { +function getLayoutCamera(camera) { return { up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]}, center: {x: camera.center[0], y: camera.center[1], z: camera.center[2]}, eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]}, - projection: {type: (isOrtho === true) ? 'orthographic' : 'perspective'} + projection: {type: (camera._ortho === true) ? 'orthographic' : 'perspective'} }; } // get camera position in plotly coords from 'orbit-camera' coords proto.getCamera = function getCamera() { this.glplot.camera.view.recalcMatrix(this.camera.view.lastT()); - return getLayoutCamera(this.glplot.camera, this.glplot.camera._ortho); + return getLayoutCamera(this.glplot.camera); }; // set camera position with a set of plotly coords diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index dbed484ca3c..50e1ba3e21b 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -1043,8 +1043,8 @@ describe('Test gl3d modebar handlers', function() { it('@gl button resetCameraDefault3d should reset camera to default', function(done) { var buttonDefault = selectButton(modeBar, 'resetCameraDefault3d'); - expect(gd._fullLayout.scene._scene.cameraInitial.eye).toEqual({ x: 0.1, y: 0.1, z: 1 }); - expect(gd._fullLayout.scene2._scene.cameraInitial.eye).toEqual({ x: 2.5, y: 2.5, z: 2.5 }); + 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); @@ -1099,8 +1099,8 @@ describe('Test gl3d modebar handlers', 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.cameraInitial; - delete gd._fullLayout.scene2._scene.cameraInitial; + delete gd._fullLayout.scene._scene.viewInitial; + delete gd._fullLayout.scene2._scene.viewInitial; Plotly.relayout(gd, { 'scene.bgcolor': '#d3d3d3', @@ -1489,6 +1489,103 @@ describe('Test gl3d relayout calls', function() { .catch(failTest) .then(done); }); + + it('@gl should maintain projection type when resetCamera buttons clicked after switching projection type from perspective to orthographic', function(done) { + Plotly.plot(gd, { + data: [{ + type: 'surface', + x: [0, 1], + y: [0, 1], + z: [[0, 1], [1, 0]] + }], + layout: { + width: 300, + height: 200, + scene: { + camera: { + eye: { + x: 2, + y: 1, + z: 0.5 + } + } + } + } + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(false, 'perspective'); + }) + .then(function() { + return Plotly.relayout(gd, 'scene.camera.projection.type', 'orthographic'); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(true, 'orthographic'); + }) + .then(function() { + return selectButton(gd._fullLayout._modeBar, 'resetCameraLastSave3d').click(); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(true, 'orthographic'); + }) + .then(function() { + return selectButton(gd._fullLayout._modeBar, 'resetCameraDefault3d').click(); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(true, 'orthographic'); + }) + .catch(failTest) + .then(done); + }); + + it('@gl should maintain projection type when resetCamera buttons clicked after switching projection type from orthographic to perspective', function(done) { + Plotly.plot(gd, { + data: [{ + type: 'surface', + x: [0, 1], + y: [0, 1], + z: [[0, 1], [1, 0]] + }], + layout: { + width: 300, + height: 200, + scene: { + camera: { + eye: { + x: 2, + y: 1, + z: 0.5 + }, + projection: { + type: 'orthographic' + } + } + } + } + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(true, 'orthographic'); + }) + .then(function() { + return Plotly.relayout(gd, 'scene.camera.projection.type', 'perspective'); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(false, 'perspective'); + }) + .then(function() { + return selectButton(gd._fullLayout._modeBar, 'resetCameraLastSave3d').click(); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(false, 'perspective'); + }) + .then(function() { + return selectButton(gd._fullLayout._modeBar, 'resetCameraDefault3d').click(); + }) + .then(function() { + expect(gd._fullLayout.scene._scene.camera._ortho).toEqual(false, 'perspective'); + }) + .catch(failTest) + .then(done); + }); }); describe('Test gl3d annotations', function() {