Skip to content

Commit 3b6832e

Browse files
authored
Merge pull request #4292 from plotly/fix4274-orthographic-zoom
Keep orthographic zoom scales in fullLayout
2 parents 99fc02f + 3b28fae commit 3b6832e

File tree

7 files changed

+599
-71
lines changed

7 files changed

+599
-71
lines changed

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"gl-mat4": "^1.2.0",
8282
"gl-mesh3d": "^2.1.1",
8383
"gl-plot2d": "^1.4.2",
84-
"gl-plot3d": "^2.2.2",
84+
"gl-plot3d": "^2.3.0",
8585
"gl-pointcloud2d": "^1.0.2",
8686
"gl-scatter3d": "^1.2.2",
8787
"gl-select-box": "^1.0.3",

src/components/modebar/buttons.js

+17-7
Original file line numberDiff line numberDiff line change
@@ -344,17 +344,27 @@ function handleCamera3d(gd, ev) {
344344

345345
for(var i = 0; i < sceneIds.length; i++) {
346346
var sceneId = sceneIds[i];
347-
var key = sceneId + '.camera';
347+
var camera = sceneId + '.camera';
348+
var aspectratio = sceneId + '.aspectratio';
348349
var scene = fullLayout[sceneId]._scene;
350+
var didUpdate;
349351

350352
if(attr === 'resetLastSave') {
351-
aobj[key + '.up'] = scene.viewInitial.up;
352-
aobj[key + '.eye'] = scene.viewInitial.eye;
353-
aobj[key + '.center'] = scene.viewInitial.center;
353+
aobj[camera + '.up'] = scene.viewInitial.up;
354+
aobj[camera + '.eye'] = scene.viewInitial.eye;
355+
aobj[camera + '.center'] = scene.viewInitial.center;
356+
didUpdate = true;
354357
} else if(attr === 'resetDefault') {
355-
aobj[key + '.up'] = null;
356-
aobj[key + '.eye'] = null;
357-
aobj[key + '.center'] = null;
358+
aobj[camera + '.up'] = null;
359+
aobj[camera + '.eye'] = null;
360+
aobj[camera + '.center'] = null;
361+
didUpdate = true;
362+
}
363+
364+
if(didUpdate) {
365+
aobj[aspectratio + '.x'] = scene.viewInitial.aspectratio.x;
366+
aobj[aspectratio + '.y'] = scene.viewInitial.aspectratio.y;
367+
aobj[aspectratio + '.z'] = scene.viewInitial.aspectratio.z;
358368
}
359369
}
360370

src/plot_api/subroutines.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -578,8 +578,7 @@ exports.doCamera = function(gd) {
578578
var sceneLayout = fullLayout[sceneIds[i]];
579579
var scene = sceneLayout._scene;
580580

581-
var cameraData = sceneLayout.camera;
582-
scene.setCamera(cameraData);
581+
scene.setViewport(sceneLayout);
583582
}
584583
};
585584

src/plots/gl3d/scene.js

+129-40
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
'use strict';
1111

12-
var createCamera = require('gl-plot3d').createCamera;
13-
var createPlot = require('gl-plot3d').createScene;
12+
var glPlot3d = require('gl-plot3d');
13+
var createCamera = glPlot3d.createCamera;
14+
var createPlot = glPlot3d.createScene;
15+
1416
var getContext = require('webgl-context');
1517
var passiveSupported = require('has-passive-events');
1618

@@ -246,10 +248,10 @@ function tryCreatePlot(scene, cameraObject, pixelRatio, canvas, gl) {
246248
return failed < 2;
247249
}
248250

249-
function initializeGLPlot(scene, pixelRatio, canvas, gl) {
251+
function initializeGLPlot(scene, canvas, gl) {
250252
scene.initializeGLCamera();
251253

252-
var success = tryCreatePlot(scene, scene.camera, pixelRatio, canvas, gl);
254+
var success = tryCreatePlot(scene, scene.camera, scene.pixelRatio, canvas, gl);
253255
/*
254256
* createPlot will throw when webgl is not enabled in the client.
255257
* Lets return an instance of the module with all functions noop'd.
@@ -259,22 +261,48 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) {
259261
if(!success) return showNoWebGlMsg(scene);
260262

261263
var gd = scene.graphDiv;
264+
var layout = gd.layout;
265+
266+
var makeUpdate = function() {
267+
var update = {};
268+
269+
if(scene.isCameraChanged(layout)) {
270+
// camera updates
271+
update[scene.id + '.camera'] = scene.getCamera();
272+
}
273+
274+
if(scene.isAspectChanged(layout)) {
275+
// scene updates
276+
update[scene.id + '.aspectratio'] = scene.glplot.getAspectratio();
277+
}
278+
279+
return update;
280+
};
262281

263282
var relayoutCallback = function(scene) {
264283
if(scene.fullSceneLayout.dragmode === false) return;
265284

266-
var update = {};
267-
update[scene.id + '.camera'] = getLayoutCamera(scene.camera);
268-
scene.saveCamera(gd.layout);
285+
var update = makeUpdate();
286+
scene.saveLayout(layout);
269287
scene.graphDiv.emit('plotly_relayout', update);
270288
};
271289

272290
scene.glplot.canvas.addEventListener('mouseup', function() {
273291
relayoutCallback(scene);
274292
});
275293

276-
scene.glplot.canvas.addEventListener('wheel', function() {
294+
scene.glplot.canvas.addEventListener('wheel', function(e) {
277295
if(gd._context._scrollZoom.gl3d) {
296+
if(scene.glplot.camera._ortho) {
297+
var s = (e.deltaX > e.deltaY) ? 1.1 : 1.0 / 1.1;
298+
var o = scene.glplot.getAspectratio();
299+
scene.glplot.setAspectratio({
300+
x: s * o.x,
301+
y: s * o.y,
302+
z: s * o.z
303+
});
304+
}
305+
278306
relayoutCallback(scene);
279307
}
280308
}, passiveSupported ? {passive: false} : false);
@@ -283,8 +311,7 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) {
283311
if(scene.fullSceneLayout.dragmode === false) return;
284312
if(scene.camera.mouseListener.buttons === 0) return;
285313

286-
var update = {};
287-
update[scene.id + '.camera'] = getLayoutCamera(scene.camera);
314+
var update = makeUpdate();
288315
scene.graphDiv.emit('plotly_relayouting', update);
289316
});
290317

@@ -366,7 +393,7 @@ function Scene(options, fullLayout) {
366393
this.convertAnnotations = Registry.getComponentMethod('annotations3d', 'convert');
367394
this.drawAnnotations = Registry.getComponentMethod('annotations3d', 'draw');
368395

369-
initializeGLPlot(this, this.pixelRatio);
396+
initializeGLPlot(this);
370397
}
371398

372399
var proto = Scene.prototype;
@@ -390,16 +417,15 @@ proto.recoverContext = function() {
390417
var scene = this;
391418
var gl = this.glplot.gl;
392419
var canvas = this.glplot.canvas;
393-
var camera = this.glplot.camera;
394-
var pixelRatio = this.glplot.pixelRatio;
420+
395421
this.glplot.dispose();
396422

397423
function tryRecover() {
398424
if(gl.isContextLost()) {
399425
requestAnimationFrame(tryRecover);
400426
return;
401427
}
402-
if(!initializeGLPlot(scene, camera, pixelRatio, canvas, gl)) {
428+
if(!initializeGLPlot(scene, canvas, gl)) {
403429
Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.');
404430
return;
405431
}
@@ -496,7 +522,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
496522
this.spikeOptions.merge(fullSceneLayout);
497523

498524
// Update camera and camera mode
499-
this.setCamera(fullSceneLayout.camera);
525+
this.setViewport(fullSceneLayout);
500526
this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode);
501527
this.camera.enableWheel = this.graphDiv._context._scrollZoom.gl3d;
502528

@@ -720,8 +746,16 @@ proto.plot = function(sceneData, fullLayout, layout) {
720746
* Finally assign the computed aspecratio to the glplot module. This will have an effect
721747
* on the next render cycle.
722748
*/
723-
this.glplot.aspect = aspectRatio;
724-
749+
this.glplot.setAspectratio(fullSceneLayout.aspectratio);
750+
751+
// save 'initial' camera view settings for modebar button
752+
if(!this.viewInitial.aspectratio) {
753+
this.viewInitial.aspectratio = {
754+
x: fullSceneLayout.aspectratio.x,
755+
y: fullSceneLayout.aspectratio.y,
756+
z: fullSceneLayout.aspectratio.z
757+
};
758+
}
725759

726760
// Update frame position for multi plots
727761
var domain = fullSceneLayout.domain || null;
@@ -751,18 +785,18 @@ proto.destroy = function() {
751785
this.glplot = null;
752786
};
753787

754-
// getOrbitCamera :: plotly_coords -> orbit_camera_coords
788+
// getCameraArrays :: plotly_coords -> gl-plot3d_coords
755789
// inverse of getLayoutCamera
756-
function getOrbitCamera(camera) {
790+
function getCameraArrays(camera) {
757791
return [
758792
[camera.eye.x, camera.eye.y, camera.eye.z],
759793
[camera.center.x, camera.center.y, camera.center.z],
760794
[camera.up.x, camera.up.y, camera.up.z]
761795
];
762796
}
763797

764-
// getLayoutCamera :: orbit_camera_coords -> plotly_coords
765-
// inverse of getOrbitCamera
798+
// getLayoutCamera :: gl-plot3d_coords -> plotly_coords
799+
// inverse of getCameraArrays
766800
function getLayoutCamera(camera) {
767801
return {
768802
up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]},
@@ -772,24 +806,25 @@ function getLayoutCamera(camera) {
772806
};
773807
}
774808

775-
// get camera position in plotly coords from 'orbit-camera' coords
776-
proto.getCamera = function getCamera() {
809+
// get camera position in plotly coords from 'gl-plot3d' coords
810+
proto.getCamera = function() {
777811
this.glplot.camera.view.recalcMatrix(this.camera.view.lastT());
778812
return getLayoutCamera(this.glplot.camera);
779813
};
780814

781-
// set camera position with a set of plotly coords
782-
proto.setCamera = function setCamera(cameraData) {
783-
this.glplot.camera.lookAt.apply(this, getOrbitCamera(cameraData));
815+
// set gl-plot3d camera position and scene aspects with a set of plotly coords
816+
proto.setViewport = function(sceneLayout) {
817+
var cameraData = sceneLayout.camera;
818+
819+
this.glplot.camera.lookAt.apply(this, getCameraArrays(cameraData));
820+
this.glplot.setAspectratio(sceneLayout.aspectratio);
784821

785822
var newOrtho = (cameraData.projection.type === 'orthographic');
786823
var oldOrtho = this.glplot.camera._ortho;
787824

788825
if(newOrtho !== oldOrtho) {
789826
this.glplot.redraw();
790827

791-
var pixelRatio = this.glplot.pixelRatio;
792-
793828
var RGBA = this.glplot.clearColor;
794829
this.glplot.gl.clearColor(
795830
RGBA[0], RGBA[1], RGBA[2], RGBA[3]
@@ -801,32 +836,30 @@ proto.setCamera = function setCamera(cameraData) {
801836

802837
this.glplot.dispose();
803838

804-
initializeGLPlot(this, pixelRatio);
839+
initializeGLPlot(this);
805840
this.glplot.camera._ortho = newOrtho;
806841
}
807842
};
808843

809-
// save camera to user layout (i.e. gd.layout)
810-
proto.saveCamera = function saveCamera(layout) {
811-
var fullLayout = this.fullLayout;
844+
proto.isCameraChanged = function(layout) {
812845
var cameraData = this.getCamera();
813846
var cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera');
814847
var cameraDataLastSave = cameraNestedProp.get();
815-
var hasChanged = false;
816848

817849
function same(x, y, i, j) {
818850
var vectors = ['up', 'center', 'eye'];
819851
var components = ['x', 'y', 'z'];
820852
return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]);
821853
}
822854

855+
var changed = false;
823856
if(cameraDataLastSave === undefined) {
824-
hasChanged = true;
857+
changed = true;
825858
} else {
826859
for(var i = 0; i < 3; i++) {
827860
for(var j = 0; j < 3; j++) {
828861
if(!same(cameraData, cameraDataLastSave, i, j)) {
829-
hasChanged = true;
862+
changed = true;
830863
break;
831864
}
832865
}
@@ -835,19 +868,75 @@ proto.saveCamera = function saveCamera(layout) {
835868
if(!cameraDataLastSave.projection || (
836869
cameraData.projection &&
837870
cameraData.projection.type !== cameraDataLastSave.projection.type)) {
838-
hasChanged = true;
871+
changed = true;
839872
}
840873
}
841874

875+
return changed;
876+
};
877+
878+
proto.isAspectChanged = function(layout) {
879+
var aspectData = this.glplot.getAspectratio();
880+
var aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio');
881+
var aspectDataLastSave = aspectNestedProp.get();
882+
883+
return (
884+
aspectDataLastSave === undefined || (
885+
aspectDataLastSave.x !== aspectData.x ||
886+
aspectDataLastSave.y !== aspectData.y ||
887+
aspectDataLastSave.z !== aspectData.z
888+
));
889+
};
890+
891+
// save camera to user layout (i.e. gd.layout)
892+
proto.saveLayout = function(layout) {
893+
var fullLayout = this.fullLayout;
894+
895+
var cameraData;
896+
var cameraNestedProp;
897+
var cameraDataLastSave;
898+
899+
var aspectData;
900+
var aspectNestedProp;
901+
var aspectDataLastSave;
902+
903+
var cameraChanged = this.isCameraChanged(layout);
904+
var aspectChanged = this.isAspectChanged(layout);
905+
906+
var hasChanged = cameraChanged || aspectChanged;
842907
if(hasChanged) {
843908
var preGUI = {};
844-
preGUI[this.id + '.camera'] = cameraDataLastSave;
909+
if(cameraChanged) {
910+
cameraData = this.getCamera();
911+
cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera');
912+
cameraDataLastSave = cameraNestedProp.get();
913+
914+
preGUI[this.id + '.camera'] = cameraDataLastSave;
915+
}
916+
if(aspectChanged) {
917+
aspectData = this.glplot.getAspectratio();
918+
aspectNestedProp = Lib.nestedProperty(layout, this.id + '.aspectratio');
919+
aspectDataLastSave = aspectNestedProp.get();
920+
921+
preGUI[this.id + '.aspectratio'] = aspectDataLastSave;
922+
}
845923
Registry.call('_storeDirectGUIEdit', layout, fullLayout._preGUI, preGUI);
846924

847-
cameraNestedProp.set(cameraData);
925+
if(cameraChanged) {
926+
cameraNestedProp.set(cameraData);
848927

849-
var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera');
850-
cameraFullNP.set(cameraData);
928+
var cameraFullNP = Lib.nestedProperty(fullLayout, this.id + '.camera');
929+
cameraFullNP.set(cameraData);
930+
}
931+
932+
if(aspectChanged) {
933+
aspectNestedProp.set(aspectData);
934+
935+
var aspectFullNP = Lib.nestedProperty(fullLayout, this.id + '.aspectratio');
936+
aspectFullNP.set(aspectData);
937+
938+
this.glplot.redraw();
939+
}
851940
}
852941

853942
return hasChanged;

0 commit comments

Comments
 (0)