Skip to content

Commit 0dc3613

Browse files
authored
Merge pull request #728 from plotly/robust-dragbox-fixedrange
Make dragmode 'lasso' & 'select' work with fixedrange axes
2 parents b753de6 + 7f9bc9c commit 0dc3613

File tree

4 files changed

+148
-87
lines changed

4 files changed

+148
-87
lines changed

src/components/modebar/buttons.js

+1-20
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
var Plotly = require('../../plotly');
1313
var Lib = require('../../lib');
14-
var setCursor = require('../../lib/setcursor');
1514
var downloadImage = require('../../snapshot/download');
1615
var Icons = require('../../../build/ploticon');
1716

@@ -171,13 +170,6 @@ modeBarButtons.hoverCompareCartesian = {
171170
click: handleCartesian
172171
};
173172

174-
var DRAGCURSORS = {
175-
pan: 'move',
176-
zoom: 'crosshair',
177-
select: 'crosshair',
178-
lasso: 'crosshair'
179-
};
180-
181173
function handleCartesian(gd, ev) {
182174
var button = ev.currentTarget,
183175
astr = button.getAttribute('data-attr'),
@@ -227,18 +219,7 @@ function handleCartesian(gd, ev) {
227219
aobj[astr] = val;
228220
}
229221

230-
Plotly.relayout(gd, aobj).then(function() {
231-
if(astr === 'dragmode') {
232-
if(fullLayout._has('cartesian')) {
233-
setCursor(
234-
fullLayout._paper.select('.nsewdrag'),
235-
DRAGCURSORS[val]
236-
);
237-
}
238-
Plotly.Fx.supplyLayoutDefaults(gd.layout, fullLayout, gd._fullData);
239-
Plotly.Fx.init(gd);
240-
}
241-
});
222+
Plotly.relayout(gd, aobj);
242223
}
243224

244225
modeBarButtons.zoom3d = {

src/plot_api/plot_api.js

+3
Original file line numberDiff line numberDiff line change
@@ -2429,6 +2429,9 @@ Plotly.relayout = function relayout(gd, astr, val) {
24292429
var subplotIds;
24302430
manageModeBar(gd);
24312431

2432+
Plotly.Fx.supplyLayoutDefaults(gd.layout, fullLayout, gd._fullData);
2433+
Plotly.Fx.init(gd);
2434+
24322435
subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
24332436
for(i = 0; i < subplotIds.length; i++) {
24342437
scene = fullLayout[subplotIds[i]]._scene;

src/plots/cartesian/dragbox.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
5252
pw = xa[0]._length,
5353
ph = ya[0]._length,
5454
MINDRAG = constants.MINDRAG,
55-
MINZOOM = constants.MINZOOM;
55+
MINZOOM = constants.MINZOOM,
56+
isMainDrag = (ns + ew === 'nsew');
5657

5758
for(var i = 1; i < subplots.length; i++) {
5859
var subplotXa = subplots[i].x(),
@@ -75,21 +76,24 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
7576
dragClass = ns + ew + 'drag';
7677

7778
var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]);
79+
7880
dragger3.enter().append('rect')
7981
.classed('drag', true)
8082
.classed(dragClass, true)
8183
.style({fill: 'transparent', 'stroke-width': 0})
8284
.attr('data-subplot', plotinfo.id);
85+
8386
dragger3.call(Drawing.setRect, x, y, w, h)
8487
.call(setCursor, cursor);
88+
8589
var dragger = dragger3.node();
8690

8791
// still need to make the element if the axes are disabled
8892
// but nuke its events (except for maindrag which needs them for hover)
8993
// and stop there
90-
if(!yActive && !xActive) {
94+
if(!yActive && !xActive && !isSelectOrLasso(fullLayout.dragmode)) {
9195
dragger.onmousedown = null;
92-
dragger.style.pointerEvents = (ns + ew === 'nsew') ? 'all' : 'none';
96+
dragger.style.pointerEvents = isMainDrag ? 'all' : 'none';
9397
return dragger;
9498
}
9599

@@ -107,7 +111,8 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
107111
doubleclick: doubleClick,
108112
prepFn: function(e, startX, startY) {
109113
var dragModeNow = gd._fullLayout.dragmode;
110-
if(ns + ew === 'nsew') {
114+
115+
if(isMainDrag) {
111116
// main dragger handles all drag modes, and changes
112117
// to pan (or to zoom if it already is pan) on shift
113118
if(e.shiftKey) {
@@ -131,7 +136,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
131136
dragOptions.doneFn = dragDone;
132137
clearSelect();
133138
}
134-
else if(dragModeNow === 'select' || dragModeNow === 'lasso') {
139+
else if(isSelectOrLasso(dragModeNow)) {
135140
prepSelect(e, startX, startY, dragOptions, dragModeNow);
136141
}
137142
}
@@ -668,3 +673,9 @@ function removeZoombox(gd) {
668673
.selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
669674
.remove();
670675
}
676+
677+
function isSelectOrLasso(dragmode) {
678+
var modes = ['lasso', 'select'];
679+
680+
return modes.indexOf(dragmode) !== -1;
681+
}

test/jasmine/tests/fx_test.js

+128-62
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,135 @@
1-
var Fx = require('@src/plots/cartesian/graph_interact');
1+
var Plotly = require('@lib/index');
22
var Plots = require('@src/plots/plots');
33

4+
var Fx = require('@src/plots/cartesian/graph_interact');
5+
6+
var d3 = require('d3');
7+
var createGraphDiv = require('../assets/create_graph_div');
8+
var destroyGraphDiv = require('../assets/destroy_graph_div');
9+
10+
11+
describe('Fx defaults', function() {
12+
'use strict';
13+
14+
var layoutIn, layoutOut, fullData;
15+
16+
beforeEach(function() {
17+
layoutIn = {};
18+
layoutOut = {
19+
_has: Plots._hasPlotType
20+
};
21+
fullData = [{}];
22+
});
23+
24+
it('should default (blank version)', function() {
25+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
26+
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
27+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
28+
});
29+
30+
it('should default (cartesian version)', function() {
31+
layoutOut._basePlotModules = [{ name: 'cartesian' }];
32+
33+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
34+
expect(layoutOut.hovermode).toBe('x', 'hovermode to x');
35+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
36+
expect(layoutOut._isHoriz).toBe(false, 'isHoriz to false');
37+
});
38+
39+
it('should default (cartesian horizontal version)', function() {
40+
layoutOut._basePlotModules = [{ name: 'cartesian' }];
41+
fullData[0] = { orientation: 'h' };
42+
43+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
44+
expect(layoutOut.hovermode).toBe('y', 'hovermode to y');
45+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
46+
expect(layoutOut._isHoriz).toBe(true, 'isHoriz to true');
47+
});
48+
49+
it('should default (gl3d version)', function() {
50+
layoutOut._basePlotModules = [{ name: 'gl3d' }];
51+
52+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
53+
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
54+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
55+
});
56+
57+
it('should default (geo version)', function() {
58+
layoutOut._basePlotModules = [{ name: 'geo' }];
459

5-
describe('Test FX', function() {
60+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
61+
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
62+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
63+
});
64+
65+
it('should default (multi plot type version)', function() {
66+
layoutOut._basePlotModules = [{ name: 'cartesian' }, { name: 'gl3d' }];
67+
68+
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
69+
expect(layoutOut.hovermode).toBe('x', 'hovermode to x');
70+
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
71+
});
72+
});
73+
74+
describe('relayout', function() {
675
'use strict';
776

8-
describe('defaults', function() {
9-
10-
var layoutIn, layoutOut, fullData;
11-
12-
beforeEach(function() {
13-
layoutIn = {};
14-
layoutOut = {
15-
_has: Plots._hasPlotType
16-
};
17-
fullData = [{}];
18-
});
19-
20-
it('should default (blank version)', function() {
21-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
22-
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
23-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
24-
});
25-
26-
it('should default (cartesian version)', function() {
27-
layoutOut._basePlotModules = [{ name: 'cartesian' }];
28-
29-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
30-
expect(layoutOut.hovermode).toBe('x', 'hovermode to x');
31-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
32-
expect(layoutOut._isHoriz).toBe(false, 'isHoriz to false');
33-
});
34-
35-
it('should default (cartesian horizontal version)', function() {
36-
layoutOut._basePlotModules = [{ name: 'cartesian' }];
37-
fullData[0] = { orientation: 'h' };
38-
39-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
40-
expect(layoutOut.hovermode).toBe('y', 'hovermode to y');
41-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
42-
expect(layoutOut._isHoriz).toBe(true, 'isHoriz to true');
43-
});
44-
45-
it('should default (gl3d version)', function() {
46-
layoutOut._basePlotModules = [{ name: 'gl3d' }];
47-
48-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
49-
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
50-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
51-
});
52-
53-
it('should default (geo version)', function() {
54-
layoutOut._basePlotModules = [{ name: 'geo' }];
55-
56-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
57-
expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest');
58-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
59-
});
60-
61-
it('should default (multi plot type version)', function() {
62-
layoutOut._basePlotModules = [{ name: 'cartesian' }, { name: 'gl3d' }];
63-
64-
Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65-
expect(layoutOut.hovermode).toBe('x', 'hovermode to x');
66-
expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom');
67-
});
77+
var gd;
78+
79+
beforeEach(function() {
80+
gd = createGraphDiv();
81+
});
82+
83+
afterEach(destroyGraphDiv);
84+
85+
it('should update main drag with correct', function(done) {
86+
87+
function assertMainDrag(cursor, isActive) {
88+
expect(d3.selectAll('rect.nsewdrag').size()).toEqual(1, 'number of nodes');
89+
var mainDrag = d3.select('rect.nsewdrag'),
90+
node = mainDrag.node();
91+
92+
expect(mainDrag.classed('cursor-' + cursor)).toBe(true, 'cursor ' + cursor);
93+
expect(mainDrag.style('pointer-events')).toEqual('all', 'pointer event');
94+
expect(!!node.onmousedown).toBe(isActive, 'mousedown handler');
95+
}
96+
97+
Plotly.plot(gd, [{
98+
y: [2, 1, 2]
99+
}]).then(function() {
100+
assertMainDrag('crosshair', true);
101+
102+
return Plotly.relayout(gd, 'dragmode', 'pan');
103+
}).then(function() {
104+
assertMainDrag('move', true);
105+
106+
return Plotly.relayout(gd, 'dragmode', 'drag');
107+
}).then(function() {
108+
assertMainDrag('crosshair', true);
109+
110+
return Plotly.relayout(gd, 'xaxis.fixedrange', true);
111+
}).then(function() {
112+
assertMainDrag('ns-resize', true);
113+
114+
return Plotly.relayout(gd, 'yaxis.fixedrange', true);
115+
}).then(function() {
116+
assertMainDrag('pointer', false);
117+
118+
return Plotly.relayout(gd, 'dragmode', 'drag');
119+
}).then(function() {
120+
assertMainDrag('pointer', false);
121+
122+
return Plotly.relayout(gd, 'dragmode', 'lasso');
123+
}).then(function() {
124+
assertMainDrag('pointer', true);
125+
126+
return Plotly.relayout(gd, 'dragmode', 'select');
127+
}).then(function() {
128+
assertMainDrag('pointer', true);
129+
130+
return Plotly.relayout(gd, 'xaxis.fixedrange', false);
131+
}).then(function() {
132+
assertMainDrag('ew-resize', true);
133+
}).then(done);
68134
});
69135
});

0 commit comments

Comments
 (0)