Skip to content

Commit 2576519

Browse files
committed
Merge pull request #285 from plotly/dblclick-reset
Handle autorange axes with 'reset' doubleClick config option [fixes #278]
2 parents a7451ff + 0be5966 commit 2576519

File tree

4 files changed

+260
-32
lines changed

4 files changed

+260
-32
lines changed

src/plots/cartesian/axes.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -202,16 +202,18 @@ axes.saveRangeInitial = function(gd, overwrite) {
202202
var axList = axes.list(gd, '', true),
203203
hasOneAxisChanged = false;
204204

205-
var ax, isNew, hasChanged;
206-
207205
for(var i = 0; i < axList.length; i++) {
208-
ax = axList[i];
206+
var ax = axList[i];
209207

210-
isNew = ax._rangeInitial===undefined;
211-
hasChanged = isNew ||
212-
!(ax.range[0]===ax._rangeInitial[0] && ax.range[1]===ax._rangeInitial[1]);
208+
var isNew = (ax._rangeInitial === undefined);
209+
var hasChanged = (
210+
isNew || !(
211+
ax.range[0] === ax._rangeInitial[0] &&
212+
ax.range[1] === ax._rangeInitial[1]
213+
)
214+
);
213215

214-
if((isNew && ax.autorange===false) || (overwrite && hasChanged)) {
216+
if((isNew && ax.autorange === false) || (overwrite && hasChanged)) {
215217
ax._rangeInitial = ax.range.slice();
216218
hasOneAxisChanged = true;
217219
}

src/plots/cartesian/axis_ids.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var Lib = require('../../lib');
1414
var constants = require('./constants');
1515

1616

17-
// convert between axis names (xaxis, xaxis2, etc, elements of td.layout)
17+
// convert between axis names (xaxis, xaxis2, etc, elements of gd.layout)
1818
// and axis id's (x, x2, etc). Would probably have ditched 'xaxis'
1919
// completely in favor of just 'x' if it weren't ingrained in the API etc.
2020
exports.id2name = function id2name(id) {
@@ -43,8 +43,8 @@ exports.cleanId = function cleanId(id, axLetter) {
4343
// get all axis object names
4444
// optionally restricted to only x or y or z by string axLetter
4545
// and optionally 2D axes only, not those inside 3D scenes
46-
function listNames(td, axLetter, only2d) {
47-
var fullLayout = td._fullLayout;
46+
function listNames(gd, axLetter, only2d) {
47+
var fullLayout = gd._fullLayout;
4848
if(!fullLayout) return [];
4949

5050
function filterAxis(obj, extra) {
@@ -76,23 +76,23 @@ function listNames(td, axLetter, only2d) {
7676
}
7777

7878
// get all axis objects, as restricted in listNames
79-
exports.list = function(td, axletter, only2d) {
80-
return listNames(td, axletter, only2d)
79+
exports.list = function(gd, axletter, only2d) {
80+
return listNames(gd, axletter, only2d)
8181
.map(function(axName) {
82-
return Lib.nestedProperty(td._fullLayout, axName).get();
82+
return Lib.nestedProperty(gd._fullLayout, axName).get();
8383
});
8484
};
8585

8686
// get all axis ids, optionally restricted by letter
8787
// this only makes sense for 2d axes
88-
exports.listIds = function(td, axletter) {
89-
return listNames(td, axletter, true).map(exports.name2id);
88+
exports.listIds = function(gd, axletter) {
89+
return listNames(gd, axletter, true).map(exports.name2id);
9090
};
9191

9292
// get an axis object from its id 'x','x2' etc
9393
// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it
94-
exports.getFromId = function(td, id, type) {
95-
var fullLayout = td._fullLayout;
94+
exports.getFromId = function(gd, id, type) {
95+
var fullLayout = gd._fullLayout;
9696

9797
if(type === 'x') id = id.replace(/y[0-9]*/,'');
9898
else if(type === 'y') id = id.replace(/x[0-9]*/,'');
@@ -101,8 +101,8 @@ exports.getFromId = function(td, id, type) {
101101
};
102102

103103
// get an axis object of specified type from the containing trace
104-
exports.getFromTrace = function(td, fullTrace, type) {
105-
var fullLayout = td._fullLayout;
104+
exports.getFromTrace = function(gd, fullTrace, type) {
105+
var fullLayout = gd._fullLayout;
106106
var ax = null;
107107

108108
if(Plots.traceIs(fullTrace, 'gl3d')) {
@@ -112,7 +112,7 @@ exports.getFromTrace = function(td, fullTrace, type) {
112112
}
113113
}
114114
else {
115-
ax = exports.getFromId(td, fullTrace[type + 'axis'] || type);
115+
ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type);
116116
}
117117

118118
return ax;

src/plots/cartesian/graph_interact.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -1807,15 +1807,24 @@ function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
18071807
else if(doubleClickConfig === 'reset') {
18081808
for(i = 0; i < axList.length; i++) {
18091809
ax = axList[i];
1810-
attrs[ax._name + '.range'] = ax._rangeInitial.slice();
1810+
1811+
if(!ax._rangeInitial) {
1812+
attrs[ax._name + '.autorange'] = true;
1813+
}
1814+
else {
1815+
attrs[ax._name + '.range'] = ax._rangeInitial.slice();
1816+
}
18111817
}
18121818
}
18131819
else if(doubleClickConfig === 'reset+autosize') {
18141820
for(i = 0; i < axList.length; i++) {
18151821
ax = axList[i];
1822+
18161823
if(ax.fixedrange) continue;
18171824
if(ax._rangeInitial === undefined ||
1818-
ax.range[0]===ax._rangeInitial[0] && ax.range[1]===ax._rangeInitial[1]) {
1825+
ax.range[0] === ax._rangeInitial[0] &&
1826+
ax.range[1] === ax._rangeInitial[1]
1827+
) {
18191828
attrs[ax._name + '.autorange'] = true;
18201829
}
18211830
else attrs[ax._name + '.range'] = ax._rangeInitial.slice();

test/jasmine/tests/click_test.js

+227-10
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY;
55
var createGraphDiv = require('../assets/create_graph_div');
66
var destroyGraphDiv = require('../assets/destroy_graph_div');
77
var mouseEvent = require('../assets/mouse_event');
8+
var customMatchers = require('../assets/custom_matchers');
89

910

1011
describe('click interactions', function() {
1112
var mock = require('@mocks/14.json'),
12-
mockCopy = Lib.extendDeep({}, mock),
1313
gd;
1414

1515
var pointPos = [351, 223],
@@ -34,17 +34,16 @@ describe('click interactions', function() {
3434
}, DBLCLICKDELAY / 2);
3535
}
3636

37-
beforeEach(function(done) {
38-
gd = createGraphDiv();
39-
40-
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
41-
.then(done);
42-
});
43-
4437
describe('click events', function() {
4538
var futureData;
4639

47-
beforeEach(function() {
40+
beforeEach(function(done) {
41+
gd = createGraphDiv();
42+
43+
var mockCopy = Lib.extendDeep({}, mock);
44+
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
45+
.then(done);
46+
4847
gd.on('plotly_click', function(data) {
4948
futureData = data;
5049
});
@@ -74,10 +73,17 @@ describe('click interactions', function() {
7473
describe('double click events', function() {
7574
var futureData;
7675

77-
beforeEach(function() {
76+
beforeEach(function(done) {
77+
gd = createGraphDiv();
78+
79+
var mockCopy = Lib.extendDeep({}, mock);
80+
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
81+
.then(done);
82+
7883
gd.on('plotly_doubleclick', function(data) {
7984
futureData = data;
8085
});
86+
8187
});
8288

8389
it('should return null', function(done) {
@@ -87,4 +93,215 @@ describe('click interactions', function() {
8793
});
8894
});
8995
});
96+
97+
describe('double click interactions', function() {
98+
var mockCopy;
99+
100+
var autoRangeX = [-3.011967491973726, 2.1561305597186564],
101+
autoRangeY = [-0.9910086301469277, 1.389382716298284];
102+
103+
var setRangeX = [-3, 1],
104+
setRangeY = [-0.5, 1];
105+
106+
var zoomRangeX = [-2, 0],
107+
zoomRangeY = [0, 0.5];
108+
109+
var update = {
110+
'xaxis.range[0]': zoomRangeX[0],
111+
'xaxis.range[1]': zoomRangeX[1],
112+
'yaxis.range[0]': zoomRangeY[0],
113+
'yaxis.range[1]': zoomRangeY[1]
114+
};
115+
116+
beforeEach(function() {
117+
jasmine.addMatchers(customMatchers);
118+
119+
gd = createGraphDiv();
120+
mockCopy = Lib.extendDeep({}, mock);
121+
});
122+
123+
function setRanges(mockCopy) {
124+
mockCopy.layout.xaxis.autorange = false;
125+
mockCopy.layout.xaxis.range = setRangeX.slice();
126+
127+
mockCopy.layout.yaxis.autorange = false;
128+
mockCopy.layout.yaxis.range = setRangeY.slice();
129+
130+
return mockCopy;
131+
}
132+
133+
it('when set to \'reset+autorange\' (the default) should work when \'autorange\' is on', function(done) {
134+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function(){
135+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
136+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
137+
138+
Plotly.relayout(gd, update).then(function() {
139+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
140+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
141+
142+
doubleClick(blankPos[0], blankPos[1], function() {
143+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
144+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
145+
146+
done();
147+
});
148+
});
149+
});
150+
});
151+
152+
it('when set to \'reset+autorange\' (the default) should reset to set range on double click', function(done) {
153+
mockCopy = setRanges(mockCopy);
154+
155+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function(){
156+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
157+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
158+
159+
Plotly.relayout(gd, update).then(function() {
160+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
161+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
162+
163+
doubleClick(blankPos[0], blankPos[1], function() {
164+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
165+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
166+
167+
done();
168+
});
169+
});
170+
});
171+
});
172+
173+
it('when set to \'reset+autorange\' (the default) should autosize on 1st double click and reset on 2nd', function(done) {
174+
mockCopy = setRanges(mockCopy);
175+
176+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function(){
177+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
178+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
179+
180+
doubleClick(blankPos[0], blankPos[1], function() {
181+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
182+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
183+
184+
doubleClick(blankPos[0], blankPos[1], function() {
185+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
186+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
187+
188+
done();
189+
});
190+
});
191+
});
192+
});
193+
194+
it('when set to \'reset\' should work when \'autorange\' is on', function(done) {
195+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'reset' }).then(function() {
196+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
197+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
198+
199+
Plotly.relayout(gd, update).then(function() {
200+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
201+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
202+
203+
doubleClick(blankPos[0], blankPos[1], function() {
204+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
205+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
206+
207+
done();
208+
});
209+
});
210+
});
211+
});
212+
213+
it('when set to \'reset\' should reset to set range on double click', function(done) {
214+
mockCopy = setRanges(mockCopy);
215+
216+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'reset' }).then(function() {
217+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
218+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
219+
220+
Plotly.relayout(gd, update).then(function() {
221+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
222+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
223+
224+
doubleClick(blankPos[0], blankPos[1], function() {
225+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
226+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
227+
228+
done();
229+
});
230+
});
231+
});
232+
});
233+
234+
it('when set to \'reset\' should reset on all double clicks', function(done) {
235+
mockCopy = setRanges(mockCopy);
236+
237+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'reset' }).then(function() {
238+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
239+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
240+
241+
doubleClick(blankPos[0], blankPos[1], function() {
242+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
243+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
244+
245+
done();
246+
});
247+
});
248+
});
249+
250+
it('when set to \'autosize\' should work when \'autorange\' is on', function(done) {
251+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'autosize' }).then(function() {
252+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
253+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
254+
255+
Plotly.relayout(gd, update).then(function() {
256+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
257+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
258+
259+
doubleClick(blankPos[0], blankPos[1], function() {
260+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
261+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
262+
263+
done();
264+
});
265+
});
266+
});
267+
});
268+
269+
it('when set to \'autosize\' should set to autorange on double click', function(done) {
270+
mockCopy = setRanges(mockCopy);
271+
272+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'autosize' }).then(function() {
273+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
274+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
275+
276+
Plotly.relayout(gd, update).then(function() {
277+
expect(gd.layout.xaxis.range).toBeCloseToArray(zoomRangeX);
278+
expect(gd.layout.yaxis.range).toBeCloseToArray(zoomRangeY);
279+
280+
doubleClick(blankPos[0], blankPos[1], function() {
281+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
282+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
283+
284+
done();
285+
});
286+
});
287+
});
288+
});
289+
290+
it('when set to \'autosize\' should reset on all double clicks', function(done) {
291+
mockCopy = setRanges(mockCopy);
292+
293+
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { doubleClick: 'autosize' }).then(function() {
294+
expect(gd.layout.xaxis.range).toBeCloseToArray(setRangeX);
295+
expect(gd.layout.yaxis.range).toBeCloseToArray(setRangeY);
296+
297+
doubleClick(blankPos[0], blankPos[1], function() {
298+
expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX);
299+
expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY);
300+
301+
done();
302+
});
303+
});
304+
});
305+
306+
});
90307
});

0 commit comments

Comments
 (0)