Skip to content

Commit 4d24c36

Browse files
authored
Merge pull request #4578 from plotly/fix4514-preserve-gl3d-scene-aspectratio
Preserve gl3d scene aspectratio after orthographic scroll zoom
2 parents 9e864a5 + 1828797 commit 4d24c36

File tree

2 files changed

+125
-34
lines changed

2 files changed

+125
-34
lines changed

src/plots/gl3d/scene.js

+33-34
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ proto.initializeGLPlot = function() {
246246
y: s * o.y,
247247
z: s * o.z
248248
});
249+
scene.fullSceneLayout.aspectmode = layout[scene.id].aspectmode = 'manual';
249250
}
250251

251252
relayoutCallback(scene);
@@ -470,12 +471,12 @@ proto.recoverContext = function() {
470471
var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ];
471472

472473
function computeTraceBounds(scene, trace, bounds) {
473-
var sceneLayout = scene.fullSceneLayout;
474+
var fullSceneLayout = scene.fullSceneLayout;
474475

475476
for(var d = 0; d < 3; d++) {
476477
var axisName = axisProperties[d];
477478
var axLetter = axisName.charAt(0);
478-
var ax = sceneLayout[axisName];
479+
var ax = fullSceneLayout[axisName];
479480
var coords = trace[axLetter];
480481
var calendar = trace[axLetter + 'calendar'];
481482
var len = trace['_' + axLetter + 'length'];
@@ -508,13 +509,13 @@ function computeTraceBounds(scene, trace, bounds) {
508509
}
509510

510511
function computeAnnotationBounds(scene, bounds) {
511-
var sceneLayout = scene.fullSceneLayout;
512-
var annotations = sceneLayout.annotations || [];
512+
var fullSceneLayout = scene.fullSceneLayout;
513+
var annotations = fullSceneLayout.annotations || [];
513514

514515
for(var d = 0; d < 3; d++) {
515516
var axisName = axisProperties[d];
516517
var axLetter = axisName.charAt(0);
517-
var ax = sceneLayout[axisName];
518+
var ax = fullSceneLayout[axisName];
518519

519520
for(var j = 0; j < annotations.length; j++) {
520521
var ann = annotations[j];
@@ -725,42 +726,40 @@ proto.plot = function(sceneData, fullLayout, layout) {
725726
});
726727
}
727728

728-
var axesScaleRatio = [1, 1, 1];
729-
730-
// Compute axis scale per category
731-
for(i = 0; i < 3; ++i) {
732-
axis = fullSceneLayout[axisProperties[i]];
733-
axisType = axis.type;
734-
var axisRatio = axisTypeRatios[axisType];
735-
axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i];
736-
}
737-
738729
/*
739730
* Dynamically set the aspect ratio depending on the users aspect settings
740731
*/
741-
var axisAutoScaleFactor = 4;
742732
var aspectRatio;
743-
744-
if(fullSceneLayout.aspectmode === 'auto') {
745-
if(Math.max.apply(null, axesScaleRatio) / Math.min.apply(null, axesScaleRatio) <= axisAutoScaleFactor) {
746-
/*
747-
* USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL
748-
*/
749-
750-
aspectRatio = axesScaleRatio;
751-
} else {
752-
/*
753-
* USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL
754-
*/
755-
aspectRatio = [1, 1, 1];
756-
}
757-
} else if(fullSceneLayout.aspectmode === 'cube') {
733+
var aspectmode = fullSceneLayout.aspectmode;
734+
if(aspectmode === 'cube') {
758735
aspectRatio = [1, 1, 1];
759-
} else if(fullSceneLayout.aspectmode === 'data') {
760-
aspectRatio = axesScaleRatio;
761-
} else if(fullSceneLayout.aspectmode === 'manual') {
736+
} else if(aspectmode === 'manual') {
762737
var userRatio = fullSceneLayout.aspectratio;
763738
aspectRatio = [userRatio.x, userRatio.y, userRatio.z];
739+
} else if(aspectmode === 'auto' || aspectmode === 'data') {
740+
var axesScaleRatio = [1, 1, 1];
741+
// Compute axis scale per category
742+
for(i = 0; i < 3; ++i) {
743+
axis = fullSceneLayout[axisProperties[i]];
744+
axisType = axis.type;
745+
var axisRatio = axisTypeRatios[axisType];
746+
axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i];
747+
}
748+
749+
if(aspectmode === 'data') {
750+
aspectRatio = axesScaleRatio;
751+
} else { // i.e. 'auto' option
752+
if(
753+
Math.max.apply(null, axesScaleRatio) /
754+
Math.min.apply(null, axesScaleRatio) <= 4
755+
) {
756+
// USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL
757+
aspectRatio = axesScaleRatio;
758+
} else {
759+
// USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL
760+
aspectRatio = [1, 1, 1];
761+
}
762+
}
764763
} else {
765764
throw new Error('scene.js aspectRatio was not one of the enumerated types');
766765
}

test/jasmine/tests/gl3d_plot_interact_test.js

+92
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,98 @@ describe('Test gl3d drag and wheel interactions', function() {
13841384
.catch(failTest)
13851385
.then(done);
13861386
});
1387+
1388+
it('@gl should preserve aspectratio values when orthographic scroll zoom i.e. after restyle', function(done) {
1389+
var coords = {
1390+
x: [1, 2, 10, 4, 5],
1391+
y: [10, 2, 4, 4, 2],
1392+
z: [10, 2, 4, 8, 16],
1393+
};
1394+
1395+
var mock = {
1396+
data: [{
1397+
type: 'scatter3d',
1398+
x: coords.x,
1399+
y: coords.y,
1400+
z: coords.z,
1401+
mode: 'markers',
1402+
marker: {
1403+
color: 'red',
1404+
size: 16,
1405+
}
1406+
}, {
1407+
type: 'scatter3d',
1408+
x: [coords.x[0]],
1409+
y: [coords.y[0]],
1410+
z: [coords.z[0]],
1411+
mode: 'markers',
1412+
marker: {
1413+
color: 'blue',
1414+
size: 32,
1415+
}
1416+
}],
1417+
layout: {
1418+
width: 400,
1419+
height: 400,
1420+
scene: {
1421+
camera: {
1422+
projection: {
1423+
type: 'orthographic'
1424+
}
1425+
},
1426+
}
1427+
}
1428+
};
1429+
1430+
var sceneTarget;
1431+
var relayoutEvent;
1432+
var relayoutCnt = 0;
1433+
1434+
Plotly.plot(gd, mock)
1435+
.then(function() {
1436+
gd.on('plotly_relayout', function(e) {
1437+
relayoutCnt++;
1438+
relayoutEvent = e;
1439+
});
1440+
1441+
sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas');
1442+
})
1443+
.then(function() {
1444+
var aspectratio = gd._fullLayout.scene.aspectratio;
1445+
expect(aspectratio.x).toBeCloseTo(0.898, 3, 'aspectratio.x');
1446+
expect(aspectratio.y).toBeCloseTo(0.798, 3, 'aspectratio.y');
1447+
expect(aspectratio.z).toBeCloseTo(1.396, 3, 'aspectratio.z');
1448+
})
1449+
.then(function() {
1450+
return scroll(sceneTarget);
1451+
})
1452+
.then(function() {
1453+
expect(relayoutCnt).toEqual(1);
1454+
1455+
var aspectratio = relayoutEvent['scene.aspectratio'];
1456+
expect(aspectratio.x).toBeCloseTo(0.816, 3, 'aspectratio.x');
1457+
expect(aspectratio.y).toBeCloseTo(0.725, 3, 'aspectratio.y');
1458+
expect(aspectratio.z).toBeCloseTo(1.269, 3, 'aspectratio.z');
1459+
})
1460+
.then(function() {
1461+
// select a point
1462+
var i = 2;
1463+
1464+
return Plotly.restyle(gd, {
1465+
x: [[coords.x[i]]],
1466+
y: [[coords.y[i]]],
1467+
z: [[coords.z[i]]],
1468+
}, 1);
1469+
})
1470+
.then(function() {
1471+
var aspectratio = gd._fullLayout.scene.aspectratio;
1472+
expect(aspectratio.x).toBeCloseTo(0.816, 3, 'aspectratio.x');
1473+
expect(aspectratio.y).toBeCloseTo(0.725, 3, 'aspectratio.y');
1474+
expect(aspectratio.z).toBeCloseTo(1.269, 3, 'aspectratio.z');
1475+
})
1476+
.catch(failTest)
1477+
.then(done);
1478+
});
13871479
});
13881480

13891481
describe('Test gl3d relayout calls', function() {

0 commit comments

Comments
 (0)