Skip to content

Commit c655f42

Browse files
authored
Merge pull request #2676 from plotly/fix-shift-selection-after-pan
Fix shift selection after pan
2 parents 5ccc83d + 34a0cf7 commit c655f42

File tree

3 files changed

+181
-29
lines changed

3 files changed

+181
-29
lines changed

src/plots/cartesian/dragbox.js

+25-22
Original file line numberDiff line numberDiff line change
@@ -178,27 +178,34 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
178178
prepSelect(e, startX, startY, dragOptions, dragModeNow);
179179
} else {
180180
dragOptions.clickFn = clickFn;
181-
182-
if(allFixedRanges) {
183-
clearSelect(zoomlayer);
184-
} else if(dragModeNow === 'zoom') {
185-
dragOptions.moveFn = zoomMove;
186-
dragOptions.doneFn = zoomDone;
187-
188-
// zoomMove takes care of the threshold, but we need to
189-
// minimize this so that constrained zoom boxes will flip
190-
// orientation at the right place
191-
dragOptions.minDrag = 1;
192-
193-
zoomPrep(e, startX, startY);
194-
} else if(dragModeNow === 'pan') {
195-
dragOptions.moveFn = plotDrag;
196-
dragOptions.doneFn = dragTail;
197-
clearSelect(zoomlayer);
181+
clearAndResetSelect();
182+
183+
if(!allFixedRanges) {
184+
if(dragModeNow === 'zoom') {
185+
dragOptions.moveFn = zoomMove;
186+
dragOptions.doneFn = zoomDone;
187+
188+
// zoomMove takes care of the threshold, but we need to
189+
// minimize this so that constrained zoom boxes will flip
190+
// orientation at the right place
191+
dragOptions.minDrag = 1;
192+
193+
zoomPrep(e, startX, startY);
194+
} else if(dragModeNow === 'pan') {
195+
dragOptions.moveFn = plotDrag;
196+
dragOptions.doneFn = dragTail;
197+
}
198198
}
199199
}
200200
};
201201

202+
function clearAndResetSelect() {
203+
// clear selection polygon cache (if any)
204+
dragOptions.plotinfo.selection = false;
205+
// clear selection outlines
206+
clearSelect(zoomlayer);
207+
}
208+
202209
function clickFn(numClicks, evt) {
203210
removeZoombox(gd);
204211

@@ -278,8 +285,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
278285
zb = makeZoombox(zoomlayer, lum, xs, ys, path0);
279286

280287
corners = makeCorners(zoomlayer, xs, ys);
281-
282-
clearSelect(zoomlayer);
283288
}
284289

285290
function zoomMove(dx0, dy0) {
@@ -391,9 +396,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
391396
return;
392397
}
393398

394-
if(redrawTimer === null) {
395-
clearSelect(zoomlayer);
396-
}
399+
clearAndResetSelect();
397400

398401
// If a transition is in progress, then disable any behavior:
399402
if(gd._transitioningWithDuration) {

test/jasmine/assets/custom_matchers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function coercePosition(precision) {
203203
}
204204

205205
function arrayToStr(array) {
206-
return '[ ' + array.join(', ') + ' ]';
206+
return '[' + array.join(', ') + ']';
207207
}
208208

209209
beforeAll(function() {

test/jasmine/tests/select_test.js

+155-6
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ function drag(path, options) {
4242
mouseEvent('mouseup', path[len - 1][0], path[len - 1][1], options);
4343
}
4444

45-
function assertSelectionNodes(cornerCnt, outlineCnt) {
45+
function assertSelectionNodes(cornerCnt, outlineCnt, _msg) {
46+
var msg = _msg ? ' - ' + _msg : '';
47+
4648
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
47-
.toBe(cornerCnt, 'selection corner count');
49+
.toBe(cornerCnt, 'selection corner count' + msg);
4850
expect(d3.selectAll('.zoomlayer > .select-outline').size())
49-
.toBe(outlineCnt, 'selection outline count');
51+
.toBe(outlineCnt, 'selection outline count' + msg);
5052
}
5153

5254
var selectingCnt, selectingData, selectedCnt, selectedData, deselectCnt, doubleClickData;
@@ -537,16 +539,27 @@ describe('@flaky Test select box and lasso in general:', function() {
537539
mockCopy.layout.dragmode = 'select';
538540
mockCopy.config = {scrollZoom: true};
539541

540-
Plotly.plot(gd, mockCopy).then(function() {
542+
function _drag() {
541543
resetEvents(gd);
542544
drag(selectPath);
543545
return selectedPromise;
544-
})
545-
.then(function() {
546+
}
547+
548+
function _scroll() {
546549
mouseEvent('mousemove', selectPath[0][0], selectPath[0][1]);
547550
mouseEvent('scroll', selectPath[0][0], selectPath[0][1], {deltaX: 0, deltaY: -20});
551+
}
552+
553+
Plotly.plot(gd, mockCopy)
554+
.then(_drag)
555+
.then(_scroll)
556+
.then(function() {
557+
assertSelectionNodes(0, 0);
548558
})
559+
.then(_drag)
560+
.then(_scroll)
549561
.then(function() {
562+
// make sure it works the 2nd time aroung
550563
assertSelectionNodes(0, 0);
551564
})
552565
.catch(failTest)
@@ -721,6 +734,142 @@ describe('@flaky Test select box and lasso in general:', function() {
721734
.catch(failTest)
722735
.then(done);
723736
});
737+
738+
it('should remember selection polygons from previous select/lasso mode', function(done) {
739+
var gd = createGraphDiv();
740+
var path1 = [[150, 150], [170, 170]];
741+
var path2 = [[193, 193], [213, 193]];
742+
743+
var fig = Lib.extendDeep({}, mock);
744+
fig.layout.margin = {l: 0, t: 0, r: 0, b: 0};
745+
fig.layout.width = 500;
746+
fig.layout.height = 500;
747+
fig.layout.dragmode = 'select';
748+
fig.config = {scrollZoom: true};
749+
750+
// d attr to array of segment [x,y]
751+
function outline2coords(outline) {
752+
if(!outline.size()) return [[]];
753+
754+
return outline.attr('d')
755+
.replace(/Z/g, '')
756+
.split('M')
757+
.filter(Boolean)
758+
.map(function(s) {
759+
return s.split('L')
760+
.map(function(s) { return s.split(',').map(Number); });
761+
})
762+
.reduce(function(a, b) { return a.concat(b); });
763+
}
764+
765+
function _assert(msg, exp) {
766+
var outline = d3.select(gd).select('.zoomlayer').select('.select-outline-1');
767+
768+
if(exp.outline) {
769+
expect(outline2coords(outline)).toBeCloseTo2DArray(exp.outline, 2, msg);
770+
} else {
771+
assertSelectionNodes(0, 0, msg);
772+
}
773+
}
774+
775+
function _drag(path, opts) {
776+
return function() {
777+
resetEvents(gd);
778+
drag(path, opts);
779+
return selectedPromise;
780+
};
781+
}
782+
783+
Plotly.plot(gd, fig)
784+
.then(function() { _assert('base', {outline: false}); })
785+
.then(_drag(path1))
786+
.then(function() {
787+
_assert('select path1', {
788+
outline: [[150, 150], [150, 170], [170, 170], [170, 150], [150, 150]]
789+
});
790+
})
791+
.then(_drag(path2))
792+
.then(function() {
793+
_assert('select path2', {
794+
outline: [[193, 0], [193, 500], [213, 500], [213, 0], [193, 0]]
795+
});
796+
})
797+
.then(_drag(path1))
798+
.then(_drag(path2, {shiftKey: true}))
799+
.then(function() {
800+
_assert('select path1+path2', {
801+
outline: [
802+
[170, 170], [170, 150], [150, 150], [150, 170], [170, 170],
803+
[213, 500], [213, 0], [193, 0], [193, 500], [213, 500]
804+
]
805+
});
806+
})
807+
.then(function() {
808+
return Plotly.relayout(gd, 'dragmode', 'lasso');
809+
})
810+
.then(function() {
811+
// N.B. all relayout calls clear the selection outline at the moment,
812+
// perhaps we could make an exception for select <-> lasso ?
813+
_assert('after relayout -> lasso', {outline: false});
814+
})
815+
.then(_drag(lassoPath, {shiftKey: true}))
816+
.then(function() {
817+
// merged with previous 'select' polygon
818+
_assert('after shift lasso', {
819+
outline: [
820+
[170, 170], [170, 150], [150, 150], [150, 170], [170, 170],
821+
[213, 500], [213, 0], [193, 0], [193, 500], [213, 500],
822+
[335, 243], [328, 169], [316, 171], [318, 239], [335, 243]
823+
]
824+
});
825+
})
826+
.then(_drag(lassoPath))
827+
.then(function() {
828+
_assert('after lasso (no-shift)', {
829+
outline: [[316, 171], [318, 239], [335, 243], [328, 169], [316, 171]]
830+
});
831+
})
832+
.then(function() {
833+
return Plotly.relayout(gd, 'dragmode', 'pan');
834+
})
835+
.then(function() {
836+
_assert('after relayout -> pan', {outline: false});
837+
drag(path2);
838+
_assert('after pan', {outline: false});
839+
return Plotly.relayout(gd, 'dragmode', 'select');
840+
})
841+
.then(function() {
842+
_assert('after relayout back to select', {outline: false});
843+
})
844+
.then(_drag(path1, {shiftKey: true}))
845+
.then(function() {
846+
// this used to merged 'lasso' polygons before (see #2669)
847+
_assert('shift select path1 after pan', {
848+
outline: [[150, 150], [150, 170], [170, 170], [170, 150], [150, 150]]
849+
});
850+
})
851+
.then(_drag(path2, {shiftKey: true}))
852+
.then(function() {
853+
_assert('shift select path1+path2 after pan', {
854+
outline: [
855+
[170, 170], [170, 150], [150, 150], [150, 170], [170, 170],
856+
[213, 500], [213, 0], [193, 0], [193, 500], [213, 500]
857+
]
858+
});
859+
})
860+
.then(function() {
861+
mouseEvent('mousemove', 200, 200);
862+
mouseEvent('scroll', 200, 200, {deltaX: 0, deltaY: -20});
863+
})
864+
.then(_drag(path1, {shiftKey: true}))
865+
.then(function() {
866+
_assert('shift select path1 after scroll', {
867+
outline: [[150, 150], [150, 170], [170, 170], [170, 150], [150, 150]]
868+
});
869+
})
870+
.catch(failTest)
871+
.then(done);
872+
});
724873
});
725874

726875
describe('@flaky Test select box and lasso per trace:', function() {

0 commit comments

Comments
 (0)