diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index f4071c4b63b..5efb94d4bc4 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -246,6 +246,7 @@ proto.initializeGLPlot = function() { y: s * o.y, z: s * o.z }); + scene.fullSceneLayout.aspectmode = layout[scene.id].aspectmode = 'manual'; } relayoutCallback(scene); @@ -470,12 +471,12 @@ proto.recoverContext = function() { var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ]; function computeTraceBounds(scene, trace, bounds) { - var sceneLayout = scene.fullSceneLayout; + var fullSceneLayout = scene.fullSceneLayout; for(var d = 0; d < 3; d++) { var axisName = axisProperties[d]; var axLetter = axisName.charAt(0); - var ax = sceneLayout[axisName]; + var ax = fullSceneLayout[axisName]; var coords = trace[axLetter]; var calendar = trace[axLetter + 'calendar']; var len = trace['_' + axLetter + 'length']; @@ -508,13 +509,13 @@ function computeTraceBounds(scene, trace, bounds) { } function computeAnnotationBounds(scene, bounds) { - var sceneLayout = scene.fullSceneLayout; - var annotations = sceneLayout.annotations || []; + var fullSceneLayout = scene.fullSceneLayout; + var annotations = fullSceneLayout.annotations || []; for(var d = 0; d < 3; d++) { var axisName = axisProperties[d]; var axLetter = axisName.charAt(0); - var ax = sceneLayout[axisName]; + var ax = fullSceneLayout[axisName]; for(var j = 0; j < annotations.length; j++) { var ann = annotations[j]; @@ -725,42 +726,40 @@ proto.plot = function(sceneData, fullLayout, layout) { }); } - var axesScaleRatio = [1, 1, 1]; - - // Compute axis scale per category - for(i = 0; i < 3; ++i) { - axis = fullSceneLayout[axisProperties[i]]; - axisType = axis.type; - var axisRatio = axisTypeRatios[axisType]; - axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i]; - } - /* * Dynamically set the aspect ratio depending on the users aspect settings */ - var axisAutoScaleFactor = 4; var aspectRatio; - - if(fullSceneLayout.aspectmode === 'auto') { - if(Math.max.apply(null, axesScaleRatio) / Math.min.apply(null, axesScaleRatio) <= axisAutoScaleFactor) { - /* - * USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL - */ - - aspectRatio = axesScaleRatio; - } else { - /* - * USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL - */ - aspectRatio = [1, 1, 1]; - } - } else if(fullSceneLayout.aspectmode === 'cube') { + var aspectmode = fullSceneLayout.aspectmode; + if(aspectmode === 'cube') { aspectRatio = [1, 1, 1]; - } else if(fullSceneLayout.aspectmode === 'data') { - aspectRatio = axesScaleRatio; - } else if(fullSceneLayout.aspectmode === 'manual') { + } else if(aspectmode === 'manual') { var userRatio = fullSceneLayout.aspectratio; aspectRatio = [userRatio.x, userRatio.y, userRatio.z]; + } else if(aspectmode === 'auto' || aspectmode === 'data') { + var axesScaleRatio = [1, 1, 1]; + // Compute axis scale per category + for(i = 0; i < 3; ++i) { + axis = fullSceneLayout[axisProperties[i]]; + axisType = axis.type; + var axisRatio = axisTypeRatios[axisType]; + axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i]; + } + + if(aspectmode === 'data') { + aspectRatio = axesScaleRatio; + } else { // i.e. 'auto' option + if( + Math.max.apply(null, axesScaleRatio) / + Math.min.apply(null, axesScaleRatio) <= 4 + ) { + // USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL + aspectRatio = axesScaleRatio; + } else { + // USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL + aspectRatio = [1, 1, 1]; + } + } } else { throw new Error('scene.js aspectRatio was not one of the enumerated types'); } diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 5f60b2e3130..8b611d706ef 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -1384,6 +1384,98 @@ describe('Test gl3d drag and wheel interactions', function() { .catch(failTest) .then(done); }); + + it('@gl should preserve aspectratio values when orthographic scroll zoom i.e. after restyle', function(done) { + var coords = { + x: [1, 2, 10, 4, 5], + y: [10, 2, 4, 4, 2], + z: [10, 2, 4, 8, 16], + }; + + var mock = { + data: [{ + type: 'scatter3d', + x: coords.x, + y: coords.y, + z: coords.z, + mode: 'markers', + marker: { + color: 'red', + size: 16, + } + }, { + type: 'scatter3d', + x: [coords.x[0]], + y: [coords.y[0]], + z: [coords.z[0]], + mode: 'markers', + marker: { + color: 'blue', + size: 32, + } + }], + layout: { + width: 400, + height: 400, + scene: { + camera: { + projection: { + type: 'orthographic' + } + }, + } + } + }; + + var sceneTarget; + var relayoutEvent; + var relayoutCnt = 0; + + Plotly.plot(gd, mock) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); + }) + .then(function() { + var aspectratio = gd._fullLayout.scene.aspectratio; + expect(aspectratio.x).toBeCloseTo(0.898, 3, 'aspectratio.x'); + expect(aspectratio.y).toBeCloseTo(0.798, 3, 'aspectratio.y'); + expect(aspectratio.z).toBeCloseTo(1.396, 3, 'aspectratio.z'); + }) + .then(function() { + return scroll(sceneTarget); + }) + .then(function() { + expect(relayoutCnt).toEqual(1); + + var aspectratio = relayoutEvent['scene.aspectratio']; + expect(aspectratio.x).toBeCloseTo(0.816, 3, 'aspectratio.x'); + expect(aspectratio.y).toBeCloseTo(0.725, 3, 'aspectratio.y'); + expect(aspectratio.z).toBeCloseTo(1.269, 3, 'aspectratio.z'); + }) + .then(function() { + // select a point + var i = 2; + + return Plotly.restyle(gd, { + x: [[coords.x[i]]], + y: [[coords.y[i]]], + z: [[coords.z[i]]], + }, 1); + }) + .then(function() { + var aspectratio = gd._fullLayout.scene.aspectratio; + expect(aspectratio.x).toBeCloseTo(0.816, 3, 'aspectratio.x'); + expect(aspectratio.y).toBeCloseTo(0.725, 3, 'aspectratio.y'); + expect(aspectratio.z).toBeCloseTo(1.269, 3, 'aspectratio.z'); + }) + .catch(failTest) + .then(done); + }); }); describe('Test gl3d relayout calls', function() {