Skip to content

Commit dca69f9

Browse files
committed
Merge pull request #466 from monfera/30a-emit-relayout-event-on-layout-change
Emit a plotly_relayout' event upon mouseup, wheel
2 parents 67c7e73 + 2a13e92 commit dca69f9

File tree

9 files changed

+509
-184
lines changed

9 files changed

+509
-184
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
"fs-extra": "^0.28.0",
9494
"fuse.js": "^2.2.0",
9595
"glob": "^7.0.0",
96-
"jasmine-core": "^2.3.4",
96+
"jasmine-core": "^2.4.1",
9797
"karma": "^0.13.15",
9898
"karma-browserify": "^5.0.1",
9999
"karma-chrome-launcher": "^0.2.1",

src/components/modebar/buttons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ function handleCamera3d(gd, ev) {
350350

351351
if(attr === 'resetDefault') scene.setCameraToDefault();
352352
else if(attr === 'resetLastSave') {
353+
// This handler looks in the un-updated fullLayout.scene.camera object to reset the camera
354+
// to the last saved position.
353355
scene.setCamera(fullSceneLayout.camera);
354356
}
355357
}

src/plots/gl2d/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ exports.plot = function plotGl2d(gd) {
5050
// If Scene is not instantiated, create one!
5151
if(scene === undefined) {
5252
scene = new Scene2D({
53-
container: gd.querySelector('.gl-container'),
5453
id: subplotId,
54+
graphDiv: gd,
55+
container: gd.querySelector('.gl-container'),
5556
staticPlot: gd._context.staticPlot,
5657
plotGlPixelRatio: gd._context.plotGlPixelRatio
5758
},

src/plots/gl2d/scene2d.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var STATIC_CANVAS, STATIC_CONTEXT;
2727

2828
function Scene2D(options, fullLayout) {
2929
this.container = options.container;
30+
this.graphDiv = options.graphDiv;
3031
this.pixelRatio = options.plotGlPixelRatio || window.devicePixelRatio;
3132
this.id = options.id;
3233
this.staticPlot = !!options.staticPlot;
@@ -268,6 +269,25 @@ proto.updateFx = function(options) {
268269
fullLayout.hovermode = options.hovermode;
269270
};
270271

272+
var relayoutCallback = function(scene) {
273+
274+
var xrange = scene.xaxis.range,
275+
yrange = scene.yaxis.range;
276+
277+
// Update the layout on the DIV
278+
scene.graphDiv.layout.xaxis.range = xrange.slice(0);
279+
scene.graphDiv.layout.yaxis.range = yrange.slice(0);
280+
281+
// Make a meaningful value to be passed on to the possible 'plotly_relayout' subscriber(s)
282+
var update = { // scene.camera has no many useful projection or scale information
283+
lastInputTime: scene.camera.lastInputTime // helps determine which one is the latest input (if async)
284+
};
285+
update[scene.xaxis._name] = xrange.slice();
286+
update[scene.yaxis._name] = yrange.slice();
287+
288+
scene.graphDiv.emit('plotly_relayout', update);
289+
};
290+
271291
proto.cameraChanged = function() {
272292
var camera = this.camera,
273293
xrange = this.xaxis.range,
@@ -285,6 +305,7 @@ proto.cameraChanged = function() {
285305
this.glplotOptions.ticks = nextTicks;
286306
this.glplotOptions.dataBox = camera.dataBox;
287307
this.glplot.update(this.glplotOptions);
308+
relayoutCallback(this);
288309
}
289310
};
290311

src/plots/gl3d/scene.js

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
*/
88

99

10-
/*eslint block-scoped-var: 0*/
11-
/*eslint no-redeclare: 0*/
12-
1310
'use strict';
1411

1512
var createPlot = require('gl-plot3d');
@@ -34,6 +31,8 @@ var STATIC_CANVAS, STATIC_CONTEXT;
3431

3532
function render(scene) {
3633

34+
var trace;
35+
3736
// update size of svg container
3837
var svgContainer = scene.svgContainer;
3938
var clientRect = scene.container.getBoundingClientRect();
@@ -50,7 +49,7 @@ function render(scene) {
5049
var lastPicked = null;
5150
var selection = scene.glplot.selection;
5251
for(var i = 0; i < keys.length; ++i) {
53-
var trace = scene.traces[keys[i]];
52+
trace = scene.traces[keys[i]];
5453
if(trace.handlePick(selection)) {
5554
lastPicked = trace;
5655
}
@@ -68,9 +67,9 @@ function render(scene) {
6867
var oldEventData;
6968

7069
if(lastPicked !== null) {
71-
var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate),
72-
trace = lastPicked.data,
73-
hoverinfo = trace.hoverinfo;
70+
var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate);
71+
trace = lastPicked.data;
72+
var hoverinfo = trace.hoverinfo;
7473

7574
var xVal = formatter('xaxis', selection.traceCoordinate[0]),
7675
yVal = formatter('yaxis', selection.traceCoordinate[1]),
@@ -172,6 +171,16 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) {
172171
showNoWebGlMsg(scene);
173172
}
174173

174+
var relayoutCallback = function(scene) {
175+
var update = {};
176+
update[scene.id] = getLayoutCamera(scene.camera);
177+
scene.saveCamera(scene.graphDiv.layout);
178+
scene.graphDiv.emit('plotly_relayout', update);
179+
};
180+
181+
scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene));
182+
scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene));
183+
175184
if(!scene.staticMode) {
176185
scene.glplot.canvas.addEventListener('webglcontextlost', function(ev) {
177186
console.log('lost context');
@@ -255,7 +264,7 @@ function Scene(options, fullLayout) {
255264

256265
this.contourLevels = [ [], [], [] ];
257266

258-
if(!initializeGLPlot(this, fullLayout)) return;
267+
if(!initializeGLPlot(this, fullLayout)) return; // todo check the necessity for this line
259268
}
260269

261270
var proto = Scene.prototype;
@@ -283,18 +292,19 @@ proto.recoverContext = function() {
283292
var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ];
284293

285294
function coordinateBound(axis, coord, d, bounds) {
295+
var x;
286296
for(var i=0; i<coord.length; ++i) {
287297
if(Array.isArray(coord[i])) {
288298
for(var j=0; j<coord[i].length; ++j) {
289-
var x = axis.d2l(coord[i][j]);
299+
x = axis.d2l(coord[i][j]);
290300
if(!isNaN(x) && isFinite(x)) {
291301
bounds[0][d] = Math.min(bounds[0][d], x);
292302
bounds[1][d] = Math.max(bounds[1][d], x);
293303
}
294304
}
295305
}
296306
else {
297-
var x = axis.d2l(coord[i]);
307+
x = axis.d2l(coord[i]);
298308
if(!isNaN(x) && isFinite(x)) {
299309
bounds[0][d] = Math.min(bounds[0][d], x);
300310
bounds[1][d] = Math.max(bounds[1][d], x);
@@ -317,7 +327,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
317327
if(this.glplot.contextLost) return;
318328

319329
var data, trace;
320-
var i, j;
330+
var i, j, axis, axisType;
321331
var fullSceneLayout = fullLayout[this.id];
322332
var sceneLayout = layout[this.id];
323333

@@ -341,7 +351,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
341351

342352
// Update axes functions BEFORE updating traces
343353
for(i = 0; i < 3; ++i) {
344-
var axis = fullSceneLayout[axisProperties[i]];
354+
axis = fullSceneLayout[axisProperties[i]];
345355
setConvert(axis);
346356
}
347357

@@ -354,14 +364,14 @@ proto.plot = function(sceneData, fullLayout, layout) {
354364
[Infinity, Infinity, Infinity],
355365
[-Infinity, -Infinity, -Infinity]
356366
];
357-
for(var i=0; i<sceneData.length; ++i) {
358-
var data = sceneData[i];
367+
for(i=0; i<sceneData.length; ++i) {
368+
data = sceneData[i];
359369
if(data.visible !== true) continue;
360370

361371
computeTraceBounds(this, data, dataBounds);
362372
}
363373
var dataScale = [1,1,1];
364-
for(var j=0; j<3; ++j) {
374+
for(j=0; j<3; ++j) {
365375
if(dataBounds[0][j] > dataBounds[1][j]) {
366376
dataScale[j] = 1.0;
367377
}
@@ -379,7 +389,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
379389
this.dataScale = dataScale;
380390

381391
//Update traces
382-
for(var i = 0; i < sceneData.length; ++i) {
392+
for(i = 0; i < sceneData.length; ++i) {
383393
data = sceneData[i];
384394
if(data.visible!==true) {
385395
continue;
@@ -416,8 +426,8 @@ proto.plot = function(sceneData, fullLayout, layout) {
416426
axisTypeRatios = {};
417427

418428
for(i = 0; i < 3; ++i) {
419-
var axis = fullSceneLayout[axisProperties[i]];
420-
var axisType = axis.type;
429+
axis = fullSceneLayout[axisProperties[i]];
430+
axisType = axis.type;
421431

422432
if(axisType in axisTypeRatios) {
423433
axisTypeRatios[axisType].acc *= dataScale[i];
@@ -471,9 +481,9 @@ proto.plot = function(sceneData, fullLayout, layout) {
471481
var axesScaleRatio = [1, 1, 1];
472482

473483
//Compute axis scale per category
474-
for(var i=0; i<3; ++i) {
475-
var axis = fullSceneLayout[axisProperties[i]];
476-
var axisType = axis.type;
484+
for(i=0; i<3; ++i) {
485+
axis = fullSceneLayout[axisProperties[i]];
486+
axisType = axis.type;
477487
var axisRatio = axisTypeRatios[axisType];
478488
axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0/axisRatio.count) / dataScale[i];
479489
}
@@ -567,33 +577,35 @@ proto.setCameraToDefault = function setCameraToDefault() {
567577
});
568578
};
569579

570-
// get camera position in plotly coords from 'orbit-camera' coords
571-
proto.getCamera = function getCamera() {
572-
this.glplot.camera.view.recalcMatrix(this.camera.view.lastT());
573-
574-
var up = this.glplot.camera.up;
575-
var center = this.glplot.camera.center;
576-
var eye = this.glplot.camera.eye;
580+
// getOrbitCamera :: plotly_coords -> orbit_camera_coords
581+
// inverse of getLayoutCamera
582+
function getOrbitCamera(camera) {
583+
return [
584+
[camera.eye.x, camera.eye.y, camera.eye.z],
585+
[camera.center.x, camera.center.y, camera.center.z],
586+
[camera.up.x, camera.up.y, camera.up.z]
587+
];
588+
}
577589

590+
// getLayoutCamera :: orbit_camera_coords -> plotly_coords
591+
// inverse of getOrbitCamera
592+
function getLayoutCamera(camera) {
578593
return {
579-
up: {x: up[0], y: up[1], z: up[2]},
580-
center: {x: center[0], y: center[1], z: center[2]},
581-
eye: {x: eye[0], y: eye[1], z: eye[2]}
594+
up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]},
595+
center: {x: camera.center[0], y: camera.center[1], z: camera.center[2]},
596+
eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]}
582597
};
598+
}
599+
600+
// get camera position in plotly coords from 'orbit-camera' coords
601+
proto.getCamera = function getCamera() {
602+
this.glplot.camera.view.recalcMatrix(this.camera.view.lastT());
603+
return getLayoutCamera(this.glplot.camera);
583604
};
584605

585606
// set camera position with a set of plotly coords
586607
proto.setCamera = function setCamera(cameraData) {
587608

588-
// getOrbitCamera :: plotly_coords -> orbit_camera_coords
589-
function getOrbitCamera(camera) {
590-
return [
591-
[camera.eye.x, camera.eye.y, camera.eye.z],
592-
[camera.center.x, camera.center.y, camera.center.z],
593-
[camera.up.x, camera.up.y, camera.up.z]
594-
];
595-
}
596-
597609
var update = {};
598610

599611
update[this.id] = cameraData;
@@ -612,7 +624,7 @@ proto.saveCamera = function saveCamera(layout) {
612624
function same(x, y, i, j) {
613625
var vectors = ['up', 'center', 'eye'],
614626
components = ['x', 'y', 'z'];
615-
return x[vectors[i]][components[j]] === y[vectors[i]][components[j]];
627+
return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]);
616628
}
617629

618630
if(cameraDataLastSave === undefined) hasChanged = true;

test/jasmine/karma.ciconf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function func(config) {
1414
* exclude them from the CircleCI test bundle.
1515
*
1616
*/
17-
func.defaultConfig.exclude = ['tests/gl_plot_interact_test.js'];
17+
func.defaultConfig.exclude = ['tests/gl_plot_interact_test.js', 'tests/gl_plot_interact_basic_test.js'];
1818

1919
// if true, Karma captures browsers, runs the tests and exits
2020
func.defaultConfig.singleRun = true;

0 commit comments

Comments
 (0)