Skip to content

Commit 34438d7

Browse files
committed
Merge pull request #258 from plotly/subplot-modebar
Subplot mode bars
2 parents 4221a2a + da5ea21 commit 34438d7

File tree

14 files changed

+953
-186
lines changed

14 files changed

+953
-186
lines changed

src/components/modebar/buttons.js

+109-63
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function handleCartesian(gd, ev) {
267267
modeBarButtons.zoom3d = {
268268
name: 'zoom3d',
269269
title: 'Zoom',
270-
attr: 'dragmode',
270+
attr: 'scene.dragmode',
271271
val: 'zoom',
272272
icon: Icons.zoombox,
273273
click: handleDrag3d
@@ -276,7 +276,7 @@ modeBarButtons.zoom3d = {
276276
modeBarButtons.pan3d = {
277277
name: 'pan3d',
278278
title: 'Pan',
279-
attr: 'dragmode',
279+
attr: 'scene.dragmode',
280280
val: 'pan',
281281
icon: Icons.pan,
282282
click: handleDrag3d
@@ -285,7 +285,7 @@ modeBarButtons.pan3d = {
285285
modeBarButtons.orbitRotation = {
286286
name: 'orbitRotation',
287287
title: 'orbital rotation',
288-
attr: 'dragmode',
288+
attr: 'scene.dragmode',
289289
val: 'orbit',
290290
icon: Icons['3d_rotate'],
291291
click: handleDrag3d
@@ -294,7 +294,7 @@ modeBarButtons.orbitRotation = {
294294
modeBarButtons.tableRotation = {
295295
name: 'tableRotation',
296296
title: 'turntable rotation',
297-
attr: 'dragmode',
297+
attr: 'scene.dragmode',
298298
val: 'turntable',
299299
icon: Icons['z-axis'],
300300
click: handleDrag3d
@@ -304,14 +304,16 @@ function handleDrag3d(gd, ev) {
304304
var button = ev.currentTarget,
305305
attr = button.getAttribute('data-attr'),
306306
val = button.getAttribute('data-val') || true,
307+
fullLayout = gd._fullLayout,
308+
sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
307309
layoutUpdate = {};
308310

309-
layoutUpdate[attr] = val;
311+
var parts = attr.split('.');
312+
313+
for(var i = 0; i < sceneIds.length; i++) {
314+
layoutUpdate[sceneIds[i] + '.' + parts[1]] = val;
315+
}
310316

311-
/*
312-
* Dragmode will go through the relayout -> doplot -> scene.plot()
313-
* routine where the dragmode will be set in scene.plot()
314-
*/
315317
Plotly.relayout(gd, layoutUpdate);
316318
}
317319

@@ -334,29 +336,19 @@ modeBarButtons.resetCameraLastSave3d = {
334336
function handleCamera3d(gd, ev) {
335337
var button = ev.currentTarget,
336338
attr = button.getAttribute('data-attr'),
337-
layout = gd.layout,
338339
fullLayout = gd._fullLayout,
339340
sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');
340341

341342
for(var i = 0; i < sceneIds.length; i++) {
342343
var sceneId = sceneIds[i],
343-
sceneLayout = layout[sceneId],
344344
fullSceneLayout = fullLayout[sceneId],
345345
scene = fullSceneLayout._scene;
346346

347-
if(!sceneLayout || attr==='resetDefault') scene.setCameraToDefault();
347+
if(attr === 'resetDefault') scene.setCameraToDefault();
348348
else if(attr === 'resetLastSave') {
349-
350-
var cameraPos = sceneLayout.camera;
351-
if(cameraPos) scene.setCamera(cameraPos);
352-
else scene.setCameraToDefault();
349+
scene.setCamera(fullSceneLayout.camera);
353350
}
354351
}
355-
356-
/*
357-
* TODO have a sceneLastTouched in _fullLayout to only
358-
* update the camera of the scene last touched by the user
359-
*/
360352
}
361353

362354
modeBarButtons.hoverClosest3d = {
@@ -367,50 +359,58 @@ modeBarButtons.hoverClosest3d = {
367359
toggle: true,
368360
icon: Icons.tooltip_basic,
369361
gravity: 'ne',
370-
click: function(gd, ev) {
371-
var button = ev.currentTarget,
372-
val = JSON.parse(button.getAttribute('data-val')) || false,
373-
fullLayout = gd._fullLayout,
374-
sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');
375-
376-
var axes = ['xaxis', 'yaxis', 'zaxis'],
377-
spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];
378-
379-
// initialize 'current spike' object to be stored in the DOM
380-
var currentSpikes = {},
381-
axisSpikes = {},
382-
layoutUpdate = {};
383-
384-
if(val) {
385-
layoutUpdate = val;
386-
button.setAttribute('data-val', JSON.stringify(null));
387-
}
388-
else {
389-
layoutUpdate = {'allaxes.showspikes': false};
390-
391-
for(var i = 0; i < sceneIds.length; i++) {
392-
var sceneId = sceneIds[i],
393-
sceneLayout = fullLayout[sceneId],
394-
sceneSpikes = currentSpikes[sceneId] = {};
395-
396-
// copy all the current spike attrs
397-
for(var j = 0; j < 3; j++) {
398-
var axis = axes[j];
399-
axisSpikes = sceneSpikes[axis] = {};
400-
401-
for(var k = 0; k < spikeAttrs.length; k++) {
402-
var spikeAttr = spikeAttrs[k];
403-
axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
404-
}
362+
click: handleHover3d
363+
};
364+
365+
function handleHover3d(gd, ev) {
366+
var button = ev.currentTarget,
367+
val = button._previousVal || false,
368+
layout = gd.layout,
369+
fullLayout = gd._fullLayout,
370+
sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');
371+
372+
var axes = ['xaxis', 'yaxis', 'zaxis'],
373+
spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];
374+
375+
// initialize 'current spike' object to be stored in the DOM
376+
var currentSpikes = {},
377+
axisSpikes = {},
378+
layoutUpdate = {};
379+
380+
if(val) {
381+
layoutUpdate = Lib.extendDeep(layout, val);
382+
button._previousVal = null;
383+
}
384+
else {
385+
layoutUpdate = {
386+
'allaxes.showspikes': false
387+
};
388+
389+
for(var i = 0; i < sceneIds.length; i++) {
390+
var sceneId = sceneIds[i],
391+
sceneLayout = fullLayout[sceneId],
392+
sceneSpikes = currentSpikes[sceneId] = {};
393+
394+
sceneSpikes.hovermode = sceneLayout.hovermode;
395+
layoutUpdate[sceneId + '.hovermode'] = false;
396+
397+
// copy all the current spike attrs
398+
for(var j = 0; j < 3; j++) {
399+
var axis = axes[j];
400+
axisSpikes = sceneSpikes[axis] = {};
401+
402+
for(var k = 0; k < spikeAttrs.length; k++) {
403+
var spikeAttr = spikeAttrs[k];
404+
axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
405405
}
406406
}
407-
408-
button.setAttribute('data-val', JSON.stringify(currentSpikes));
409407
}
410408

411-
Plotly.relayout(gd, layoutUpdate);
409+
button._previousVal = Lib.extendDeep({}, currentSpikes);
412410
}
413-
};
411+
412+
Plotly.relayout(gd, layoutUpdate);
413+
}
414414

415415
modeBarButtons.zoomInGeo = {
416416
name: 'zoomInGeo',
@@ -447,7 +447,7 @@ modeBarButtons.hoverClosestGeo = {
447447
toggle: true,
448448
icon: Icons.tooltip_basic,
449449
gravity: 'ne',
450-
click: handleGeo
450+
click: toggleHover
451451
};
452452

453453
function handleGeo(gd, ev) {
@@ -468,7 +468,6 @@ function handleGeo(gd, ev) {
468468
geo.render();
469469
}
470470
else if(attr === 'reset') geo.zoomReset();
471-
else if(attr === 'hovermode') geo.showHover = !geo.showHover;
472471
}
473472
}
474473

@@ -494,7 +493,54 @@ modeBarButtons.hoverClosestPie = {
494493
};
495494

496495
function toggleHover(gd) {
497-
var newHover = gd._fullLayout.hovermode ? false : 'closest';
496+
var fullLayout = gd._fullLayout;
497+
498+
var onHoverVal;
499+
if(fullLayout._hasCartesian) {
500+
onHoverVal = fullLayout._isHoriz ? 'y' : 'x';
501+
}
502+
else onHoverVal = 'closest';
503+
504+
var newHover = gd._fullLayout.hovermode ? false : onHoverVal;
498505

499506
Plotly.relayout(gd, 'hovermode', newHover);
500507
}
508+
509+
// buttons when more then one plot types are present
510+
511+
modeBarButtons.toggleHover = {
512+
name: 'toggleHover',
513+
title: 'Toggle show closest data on hover',
514+
attr: 'hovermode',
515+
val: null,
516+
toggle: true,
517+
icon: Icons.tooltip_basic,
518+
gravity: 'ne',
519+
click: function(gd, ev) {
520+
toggleHover(gd);
521+
522+
// the 3d hovermode update must come
523+
// last so that layout.hovermode update does not
524+
// override scene?.hovermode?.layout.
525+
handleHover3d(gd, ev);
526+
}
527+
};
528+
529+
modeBarButtons.resetViews = {
530+
name: 'resetViews',
531+
title: 'Reset views',
532+
icon: Icons.home,
533+
click: function(gd, ev) {
534+
var button = ev.currentTarget;
535+
536+
button.setAttribute('data-attr', 'zoom');
537+
button.setAttribute('data-val', 'reset');
538+
handleCartesian(gd, ev);
539+
540+
button.setAttribute('data-attr', 'resetLastSave');
541+
handleCamera3d(gd, ev);
542+
543+
// N.B handleCamera3d also triggers a replot for
544+
// geo subplots.
545+
}
546+
};

src/components/modebar/index.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
'use strict';
1111

12-
var Plotly = require('../../plotly');
1312
var d3 = require('d3');
1413

14+
var Lib = require('../../lib');
1515
var Icons = require('../../../build/ploticon');
1616

1717

@@ -204,7 +204,11 @@ proto.updateActiveButton = function(buttonClicked) {
204204
}
205205
}
206206
else {
207-
button3.classed('active', fullLayout[dataAttr]===thisval);
207+
var val = (dataAttr === null) ?
208+
dataAttr :
209+
Lib.nestedProperty(fullLayout, dataAttr).get();
210+
211+
button3.classed('active', val === thisval);
208212
}
209213

210214
});
@@ -260,7 +264,7 @@ proto.removeAllButtons = function() {
260264
};
261265

262266
proto.destroy = function() {
263-
Plotly.Lib.removeElement(this.container.querySelector('.modebar'));
267+
Lib.removeElement(this.container.querySelector('.modebar'));
264268
};
265269

266270
function createModeBar(gd, buttons) {

0 commit comments

Comments
 (0)