diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 24f06adc781..0bc40c84f9b 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -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'); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 76e5976aabd..a539e3d7e1b 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -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, @@ -161,15 +164,16 @@ 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, @@ -177,6 +181,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { 'stroke-width': 1, opacity: 0 }) + .attr('transform','translate(' + xs + ', ' + ys + ')') .attr('d','M0,0Z'); clearSelect(); @@ -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) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 3c82d1ccdff..b88f1176f6f 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -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, @@ -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') @@ -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'); @@ -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(); @@ -189,6 +194,5 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else { dragOptions.gd.emit('plotly_selected', eventData); } - corners.remove(); }; }; diff --git a/test/jasmine/tests/cartesian_test.js b/test/jasmine/tests/cartesian_test.js new file mode 100644 index 00000000000..38c09608cef --- /dev/null +++ b/test/jasmine/tests/cartesian_test.js @@ -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); + }); +}); diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index 34bf0d71f24..4e0d5795d8f 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -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(); + }); }); it('on nw dragbox should update the axis ranges', function(done) { diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 6eb5b27231e..5cf94265cea 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -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; @@ -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';