Skip to content
1 change: 1 addition & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,7 @@ function makePlotFramework(gd) {
// these are in a different svg element normally, but get collapsed into a single
// svg when exporting (after inserting 3D)
fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true);
fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);

gd.emit('plotly_framework');
Expand Down
13 changes: 9 additions & 4 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {

dragElement.init(dragOptions);

var x0,
var zoomlayer = gd._fullLayout._zoomlayer,
xs = plotinfo.x()._offset,
ys = plotinfo.y()._offset,
x0,
y0,
box,
lum,
Expand All @@ -161,22 +164,24 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dimmed = false;
zoomMode = 'xy';

zb = plotinfo.plot.append('path')
zb = zoomlayer.append('path')
.attr('class', 'zoombox')
.style({
'fill': lum>0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
'stroke-width': 0
})
.attr('transform','translate(' + xs + ', ' + ys + ')')
.attr('d', path0 + 'Z');

corners = plotinfo.plot.append('path')
corners = zoomlayer.append('path')
.attr('class', 'zoombox-corners')
.style({
fill: Color.background,
stroke: Color.defaultLine,
'stroke-width': 1,
opacity: 0
})
.attr('transform','translate(' + xs + ', ' + ys + ')')
.attr('d','M0,0Z');

clearSelect();
Expand All @@ -187,7 +192,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
// until we get around to persistent selections, remove the outline
// here. The selection itself will be removed when the plot redraws
// at the end.
plotinfo.plot.selectAll('.select-outline').remove();
zoomlayer.selectAll('.select-outline').remove();
}

function zoomMove(dx0, dy0) {
Expand Down
8 changes: 6 additions & 2 deletions src/plots/cartesian/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ var MINSELECT = constants.MINSELECT;
function getAxId(ax) { return ax._id; }

module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
var plot = dragOptions.plotinfo.plot,
var plot = dragOptions.gd._fullLayout._zoomlayer,
dragBBox = dragOptions.element.getBoundingClientRect(),
xs = dragOptions.plotinfo.x()._offset,
ys = dragOptions.plotinfo.y()._offset,
x0 = startX - dragBBox.left,
y0 = startY - dragBBox.top,
x1 = x0,
Expand All @@ -45,6 +47,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
outlines.enter()
.append('path')
.attr('class', function(d) { return 'select-outline select-outline-' + d; })
.attr('transform','translate(' + xs + ', ' + ys + ')')
.attr('d', path0 + 'Z');

var corners = plot.append('path')
Expand All @@ -54,6 +57,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
stroke: color.defaultLine,
'stroke-width': 1
})
.attr('transform','translate(' + xs + ', ' + ys + ')')
.attr('d','M0,0Z');


Expand Down Expand Up @@ -176,6 +180,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
};

dragOptions.doneFn = function(dragged, numclicks) {
corners.remove();
if(!dragged && numclicks === 2) {
// clear selection on doubleclick
outlines.remove();
Expand All @@ -189,6 +194,5 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
else {
dragOptions.gd.emit('plotly_selected', eventData);
}
corners.remove();
};
};
51 changes: 51 additions & 0 deletions test/jasmine/tests/cartesian_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
var d3 = require('d3');

var Plotly = require('@lib/index');
var Lib = require('@src/lib');

var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var mouseEvent = require('../assets/mouse_event');


describe('zoom box element', function() {
var mock = require('@mocks/14.json');

var gd;
beforeEach(function(done) {
gd = createGraphDiv();

var mockCopy = Lib.extendDeep({}, mock);
mockCopy.layout.dragmode = 'zoom';

Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
});

afterEach(destroyGraphDiv);

it('should be appended to the zoom layer', function() {
var x0 = 100;
var y0 = 200;
var x1 = 150;
var y1 = 200;

mouseEvent('mousemove', x0, y0);
expect(d3.selectAll('.zoomlayer > .zoombox').size())
.toEqual(0);
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);

mouseEvent('mousedown', x0, y0);
mouseEvent('mousemove', x1, y1);
expect(d3.selectAll('.zoomlayer > .zoombox').size())
.toEqual(1);
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(1);

mouseEvent('mouseup', x1, y1);
expect(d3.selectAll('.zoomlayer > .zoombox').size())
.toEqual(0);
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);
});
});
8 changes: 7 additions & 1 deletion test/jasmine/tests/click_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,13 @@ describe('Test click interactions:', function() {

describe('drag interactions', function() {
beforeEach(function(done) {
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
// Do not let the notifier hide the drag elements
var tooltip = document.querySelector('.notifier-note');
if(tooltip) tooltip.style.display = 'None';

done();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful. Thanks for figuring this out 🍻

});
});

it('on nw dragbox should update the axis ranges', function(done) {
Expand Down
100 changes: 100 additions & 0 deletions test/jasmine/tests/select_test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var d3 = require('d3');

var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY;
Expand Down Expand Up @@ -54,6 +56,104 @@ describe('select box and lasso', function() {
expect(actual.y).toBeCloseToArray(expected.y, PRECISION);
}

describe('select elements', function() {
var mockCopy = Lib.extendDeep({}, mock);
mockCopy.layout.dragmode = 'select';

var gd;
beforeEach(function(done) {
gd = createGraphDiv();

Plotly.plot(gd, mockCopy.data, mockCopy.layout)
.then(done);
});

it('should be appended to the zoom layer', function(done) {
var x0 = 100,
y0 = 200,
x1 = 150,
y1 = 250,
x2 = 50,
y2 = 50;

gd.once('plotly_selecting', function() {
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(1);
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(2);
});

gd.once('plotly_selected', function() {
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(2);
});

gd.once('plotly_deselect', function() {
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(0);
});

mouseEvent('mousemove', x0, y0);
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);

drag([[x0, y0], [x1, y1]]);

doubleClick(x2, y2, done);
});
});

describe('lasso elements', function() {
var mockCopy = Lib.extendDeep({}, mock);
mockCopy.layout.dragmode = 'lasso';

var gd;
beforeEach(function(done) {
gd = createGraphDiv();

Plotly.plot(gd, mockCopy.data, mockCopy.layout)
.then(done);
});

it('should be appended to the zoom layer', function(done) {
var x0 = 100,
y0 = 200,
x1 = 150,
y1 = 250,
x2 = 50,
y2 = 50;

gd.once('plotly_selecting', function() {
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(1);
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(2);
});

gd.once('plotly_selected', function() {
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(2);
});

gd.once('plotly_deselect', function() {
expect(d3.selectAll('.zoomlayer > .select-outline').size())
.toEqual(0);
});

mouseEvent('mousemove', x0, y0);
expect(d3.selectAll('.zoomlayer > .zoombox-corners').size())
.toEqual(0);

drag([[x0, y0], [x1, y1]]);

doubleClick(x2, y2, done);
});
});

describe('select events', function() {
var mockCopy = Lib.extendDeep({}, mock);
mockCopy.layout.dragmode = 'select';
Expand Down