diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 156382b6c5d..a748a37310d 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -140,8 +140,22 @@ dragElement.init = function init(options) { if(options.doneFn) options.doneFn(gd._dragged, numClicks, e); if(!gd._dragged) { - var e2 = document.createEvent('MouseEvents'); - e2.initEvent('click', true, true); + var e2; + + try { + e2 = new MouseEvent('click', e); + } + catch(err) { + e2 = document.createEvent('MouseEvents'); + e2.initMouseEvent('click', + e.bubbles, e.cancelable, + e.view, e.detail, + e.screenX, e.screenY, + e.clientX, e.clientY, + e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, + e.button, e.relatedTarget); + } + initialTarget.dispatchEvent(e2); } diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js index 4caf14ce456..1905cdf6825 100644 --- a/src/components/dragelement/unhover.js +++ b/src/components/dragelement/unhover.js @@ -44,6 +44,9 @@ unhover.raw = function unhoverRaw(gd, evt) { gd._hoverdata = undefined; if(evt.target && oldhoverdata) { - gd.emit('plotly_unhover', {points: oldhoverdata}); + gd.emit('plotly_unhover', { + event: evt, + points: oldhoverdata + }); } }; diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 85f0130a5e8..f14bb31b5cf 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -305,6 +305,7 @@ fx.hover = function(gd, evt, subplot) { function hover(gd, evt, subplot) { if(subplot === 'pie') { gd.emit('plotly_hover', { + event: evt.originalEvent, points: [evt] }); return; @@ -413,12 +414,17 @@ function hover(gd, evt, subplot) { // [x|y]px: the pixels (from top left) of the mouse location // on the currently selected plot area - var xpx, ypx; + var hasUserCalledHover = !evt.target, + xpx, ypx; - // mouse event? ie is there a target element with - // clientX and clientY values? - if(evt.target && ('clientX' in evt) && ('clientY' in evt)) { + if(hasUserCalledHover) { + if('xpx' in evt) xpx = evt.xpx; + else xpx = xaArray[0]._length / 2; + if('ypx' in evt) ypx = evt.ypx; + else ypx = yaArray[0]._length / 2; + } + else { // fire the beforehover event and quit if it returns false // note that we're only calling this on real mouse events, so // manual calls to fx.hover will always run. @@ -437,13 +443,6 @@ function hover(gd, evt, subplot) { return dragElement.unhoverRaw(gd, evt); } } - else { - if('xpx' in evt) xpx = evt.xpx; - else xpx = xaArray[0]._length / 2; - - if('ypx' in evt) ypx = evt.ypx; - else ypx = yaArray[0]._length / 2; - } if('xval' in evt) xvalArray = flat(subplots, evt.xval); else xvalArray = p2c(xaArray, xpx); @@ -624,10 +623,14 @@ function hover(gd, evt, subplot) { if(!hoverChanged(gd, evt, oldhoverdata)) return; if(oldhoverdata) { - gd.emit('plotly_unhover', { points: oldhoverdata }); + gd.emit('plotly_unhover', { + event: evt, + points: oldhoverdata + }); } gd.emit('plotly_hover', { + event: evt, points: gd._hoverdata, xaxes: xaArray, yaxes: yaArray, @@ -1350,7 +1353,7 @@ function hoverChanged(gd, evt, oldhoverdata) { fx.click = function(gd, evt) { var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata); - function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata}); } + function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); } if(gd._hoverdata && evt && evt.target) { if(annotationsDone && annotationsDone.then) { diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 56dd3ce42cc..16d760872ae 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -91,11 +91,9 @@ proto.plot = function(geoCalcData, fullLayout, promises) { if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) return; - var evt = { - target: true, - xpx: mouse[0], - ypx: mouse[1] - }; + var evt = d3.event; + evt.xpx = mouse[0]; + evt.ypx = mouse[1]; _this.xaxis.c2p = function() { return mouse[0]; }; _this.xaxis.p2c = function() { return lonlat[0]; }; @@ -110,7 +108,7 @@ proto.plot = function(geoCalcData, fullLayout, promises) { }); _this.framework.on('click', function() { - Fx.click(_this.graphDiv, { target: true }); + Fx.click(_this.graphDiv, d3.event); }); topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout); diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 522cf66b8a6..3c3185b1c44 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -166,8 +166,8 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { Fx.hover(gd, evt, self.id); }); - map.on('click', function() { - Fx.click(gd, { target: true }); + map.on('click', function(evt) { + Fx.click(gd, evt.originalEvent); }); function unhover() { diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 1d58dd2d9b4..8539093101f 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -86,6 +86,8 @@ module.exports = function plot(gd, cdpie) { hasHoverData = false; function handleMouseOver(evt) { + evt.originalEvent = d3.event; + // in case fullLayout or fullData has changed without a replot var fullLayout2 = gd._fullLayout, trace2 = gd._fullData[trace.index], @@ -97,6 +99,7 @@ module.exports = function plot(gd, cdpie) { // or if hover is turned off if(gd._dragging || fullLayout2.hovermode === false || hoverinfo === 'none' || hoverinfo === 'skip' || !hoverinfo) { + Fx.hover(gd, evt, 'pie'); return; } @@ -132,7 +135,9 @@ module.exports = function plot(gd, cdpie) { } function handleMouseOut(evt) { + evt.originalEvent = d3.event; gd.emit('plotly_unhover', { + event: d3.event, points: [evt] }); @@ -144,8 +149,8 @@ module.exports = function plot(gd, cdpie) { function handleClick() { gd._hoverdata = [pt]; - gd._hoverdata.trace = cd.trace; - Fx.click(gd, { target: true }); + gd._hoverdata.trace = cd0.trace; + Fx.click(gd, d3.event); } slicePath.enter().append('path') diff --git a/test/jasmine/assets/click.js b/test/jasmine/assets/click.js index e2cc43e444b..a1dff1f1d6f 100644 --- a/test/jasmine/assets/click.js +++ b/test/jasmine/assets/click.js @@ -1,7 +1,8 @@ var mouseEvent = require('./mouse_event'); -module.exports = function click(x, y) { - mouseEvent('mousemove', x, y); - mouseEvent('mousedown', x, y); - mouseEvent('mouseup', x, y); +module.exports = function click(x, y, opts) { + mouseEvent('mousemove', x, y, opts); + mouseEvent('mousedown', x, y, opts); + mouseEvent('mouseup', x, y, opts); + mouseEvent('click', x, y, opts); }; diff --git a/test/jasmine/assets/get_client_position.js b/test/jasmine/assets/get_client_position.js new file mode 100644 index 00000000000..71da9f50b00 --- /dev/null +++ b/test/jasmine/assets/get_client_position.js @@ -0,0 +1,10 @@ +module.exports = function getClientPosition(selector, index) { + index = index || 0; + + var selection = document.querySelectorAll(selector), + clientPos = selection[index].getBoundingClientRect(), + x = Math.floor((clientPos.left + clientPos.right) / 2), + y = Math.floor((clientPos.top + clientPos.bottom) / 2); + + return [x, y]; +}; diff --git a/test/jasmine/assets/mouse_event.js b/test/jasmine/assets/mouse_event.js index 153314c5abd..776524a84a1 100644 --- a/test/jasmine/assets/mouse_event.js +++ b/test/jasmine/assets/mouse_event.js @@ -9,6 +9,18 @@ module.exports = function(type, x, y, opts) { if(opts && opts.buttons) { fullOpts.buttons = opts.buttons; } + if(opts && opts.altKey) { + fullOpts.altKey = opts.altKey; + } + if(opts && opts.ctrlKey) { + fullOpts.ctrlKey = opts.ctrlKey; + } + if(opts && opts.metaKey) { + fullOpts.metaKey = opts.metaKey; + } + if(opts && opts.shiftKey) { + fullOpts.shiftKey = opts.shiftKey; + } var el = (opts && opts.element) || document.elementFromPoint(x, y), ev; diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index 9e13b9ffd12..bfc06419e1a 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -16,6 +16,30 @@ var customMatchers = require('../assets/custom_matchers'); var click = require('../assets/click'); var doubleClickRaw = require('../assets/double_click'); +function move(fromX, fromY, toX, toY, delay) { + return new Promise(function(resolve) { + mouseEvent('mousemove', fromX, fromY); + + setTimeout(function() { + mouseEvent('mousemove', toX, toY); + resolve(); + }, delay || DBLCLICKDELAY / 4); + }); +} + +function drag(fromX, fromY, toX, toY, delay) { + return new Promise(function(resolve) { + mouseEvent('mousemove', fromX, fromY); + mouseEvent('mousedown', fromX, fromY); + mouseEvent('mousemove', toX, toY); + + setTimeout(function() { + mouseEvent('mouseup', toX, toY); + resolve(); + }, delay || DBLCLICKDELAY / 4); + }); +} + describe('Test click interactions:', function() { var mock = require('@mocks/14.json'); @@ -39,19 +63,6 @@ describe('Test click interactions:', function() { afterEach(destroyGraphDiv); - function drag(fromX, fromY, toX, toY, delay) { - return new Promise(function(resolve) { - mouseEvent('mousemove', fromX, fromY); - mouseEvent('mousedown', fromX, fromY); - mouseEvent('mousemove', toX, toY); - - setTimeout(function() { - mouseEvent('mouseup', toX, toY); - resolve(); - }, delay || DBLCLICKDELAY / 4); - }); - } - function doubleClick(x, y) { return doubleClickRaw(x, y).then(function() { return Plotly.Plots.previousPromises(gd); @@ -87,6 +98,55 @@ describe('Test click interactions:', function() { expect(pt.pointNumber).toEqual(11); expect(pt.x).toEqual(0.125); expect(pt.y).toEqual(2.125); + + var evt = futureData.event; + expect(evt.clientX).toEqual(pointPos[0]); + expect(evt.clientY).toEqual(pointPos[1]); + }); + }); + + describe('modified click events', function() { + var clickOpts = { + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true + }, + futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1], clickOpts); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1], clickOpts); + expect(futureData.points.length).toEqual(1); + + var pt = futureData.points[0]; + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', + 'x', 'y', 'xaxis', 'yaxis' + ]); + expect(pt.curveNumber).toEqual(0); + expect(pt.pointNumber).toEqual(11); + expect(pt.x).toEqual(0.125); + expect(pt.y).toEqual(2.125); + + var evt = futureData.event; + expect(evt.clientX).toEqual(pointPos[0]); + expect(evt.clientY).toEqual(pointPos[1]); + Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { + expect(evt[opt]).toEqual(clickOpts[opt], opt); + }); }); }); @@ -191,6 +251,46 @@ describe('Test click interactions:', function() { expect(pt.pointNumber).toEqual(11); expect(pt.x).toEqual(0.125); expect(pt.y).toEqual(2.125); + + var evt = futureData.event; + expect(evt.clientX).toEqual(pointPos[0]); + expect(evt.clientY).toEqual(pointPos[1]); + }); + }); + + describe('plotly_unhover event with hoverinfo set to none', function() { + var futureData; + + beforeEach(function(done) { + + var modifiedMockCopy = Lib.extendDeep({}, mockCopy); + modifiedMockCopy.data[0].hoverinfo = 'none'; + Plotly.plot(gd, modifiedMockCopy.data, modifiedMockCopy.layout) + .then(done); + + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields despite hoverinfo: "none"', function(done) { + move(pointPos[0], pointPos[1], blankPos[0], blankPos[1]).then(function() { + expect(futureData.points.length).toEqual(1); + + var pt = futureData.points[0]; + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', + 'x', 'y', 'xaxis', 'yaxis' + ]); + expect(pt.curveNumber).toEqual(0); + expect(pt.pointNumber).toEqual(11); + expect(pt.x).toEqual(0.125); + expect(pt.y).toEqual(2.125); + + var evt = futureData.event; + expect(evt.clientX).toEqual(blankPos[0]); + expect(evt.clientY).toEqual(blankPos[1]); + }).then(done); }); }); @@ -817,6 +917,7 @@ describe('Test click interactions:', function() { }); }); + describe('dragbox', function() { afterEach(destroyGraphDiv); diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index 2922c8f978c..b783fda1c45 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -11,11 +11,27 @@ var topojsonUtils = require('@src/lib/topojson_utils'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var customMatchers = require('../assets/custom_matchers'); +var getClientPosition = require('../assets/get_client_position'); var mouseEvent = require('../assets/mouse_event'); +var click = require('../assets/click'); +var DBLCLICKDELAY = require('@src/constants/interactions').DBLCLICKDELAY; var HOVERMINTIME = require('@src/plots/cartesian/constants').HOVERMINTIME; +function move(fromX, fromY, toX, toY, delay) { + return new Promise(function(resolve) { + mouseEvent('mousemove', fromX, fromY); + + setTimeout(function() { + mouseEvent('mousemove', toX, toY); + resolve(); + }, delay || DBLCLICKDELAY / 4); + }); +} + + describe('Test geoaxes', function() { 'use strict'; @@ -1015,3 +1031,194 @@ describe('Test geo interactions', function() { }); }); }); + + +describe('Test event property of interactions on a geo plot:', function() { + var mock = require('@mocks/geo_scattergeo-locations.json'); + + var mockCopy, gd; + + var blankPos = [10, 10], + pointPos, + nearPos; + + beforeAll(function(done) { + jasmine.addMatchers(customMatchers); + + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { + pointPos = getClientPosition('path.point'); + nearPos = [pointPos[0] - 30, pointPos[1] - 30]; + destroyGraphDiv(); + done(); + }); + }); + + beforeEach(function() { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + }); + + afterEach(destroyGraphDiv); + + describe('click events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'location' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(-101.57, 'points[0].lat'); + expect(pt.lon).toEqual(57.75, 'points[0].lon'); + expect(pt.location).toEqual(57.75, 'points[0].location'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('modified click events', function() { + var clickOpts = { + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true + }, + futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1], clickOpts); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1], clickOpts); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'location' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(-101.57, 'points[0].lat'); + expect(pt.lon).toEqual(57.75, 'points[0].lon'); + expect(pt.location).toEqual(57.75, 'points[0].location'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { + expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt); + }); + }); + }); + + describe('hover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_hover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'location' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(-101.57, 'points[0].lat'); + expect(pt.lon).toEqual(57.75, 'points[0].lon'); + expect(pt.location).toEqual(57.75, 'points[0].location'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('unhover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function(done) { + move(pointPos[0], pointPos[1], nearPos[0], nearPos[1], HOVERMINTIME + 10).then(function() { + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'location' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(-101.57, 'points[0].lat'); + expect(pt.lon).toEqual(57.75, 'points[0].lon'); + expect(pt.location).toEqual(57.75, 'points[0].location'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(nearPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(nearPos[1], 'event.clientY'); + }).then(done); + }); + }); +}); diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js index 75610e36834..2891dd31ea0 100644 --- a/test/jasmine/tests/hover_pie_test.js +++ b/test/jasmine/tests/hover_pie_test.js @@ -1,13 +1,73 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var customMatchers = require('../assets/custom_matchers'); + var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); + +var click = require('../assets/click'); +var getClientPosition = require('../assets/get_client_position'); var mouseEvent = require('../assets/mouse_event'); + describe('pie hovering', function() { var mock = require('@mocks/pie_simple.json'); + describe('with hoverinfo set to none', function() { + var mockCopy = Lib.extendDeep({}, mock), + gd; + + mockCopy.data[0].hoverinfo = 'none'; + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + afterEach(destroyGraphDiv); + + it('should fire hover event when moving from one slice to another', function(done) { + var count = 0, + hoverData = []; + + gd.on('plotly_hover', function(data) { + count++; + hoverData.push(data); + }); + + mouseEvent('mouseover', 173, 133); + setTimeout(function() { + mouseEvent('mouseover', 233, 193); + expect(count).toEqual(2); + expect(hoverData[0]).not.toEqual(hoverData[1]); + done(); + }, 100); + }); + + it('should fire unhover event when the mouse moves off the graph', function(done) { + var count = 0, + unhoverData = []; + + gd.on('plotly_unhover', function(data) { + count++; + unhoverData.push(data); + }); + + mouseEvent('mouseover', 173, 133); + mouseEvent('mouseout', 173, 133); + setTimeout(function() { + mouseEvent('mouseover', 233, 193); + mouseEvent('mouseout', 233, 193); + expect(count).toEqual(2); + expect(unhoverData[0]).not.toEqual(unhoverData[1]); + done(); + }, 100); + }); + }); + describe('event data', function() { var mockCopy = Lib.extendDeep({}, mock), width = mockCopy.layout.width, @@ -39,7 +99,8 @@ describe('pie hovering', function() { * px0: [-59.67131372209641,6.2717077960592], * largeArc: 0, * cxFinal: 200, - * cyFinal: 160 + * cyFinal: 160, + * originalEvent: MouseEvent * }]; */ var hoverData, @@ -63,7 +124,8 @@ describe('pie hovering', function() { var fields = [ 'v', 'label', 'color', 'i', 'hidden', 'text', 'px1', 'pxmid', 'midangle', - 'px0', 'largeArc', 'cxFinal', 'cyFinal' + 'px0', 'largeArc', 'cxFinal', 'cyFinal', + 'originalEvent' ]; expect(Object.keys(hoverData.points[0])).toEqual(fields); @@ -165,3 +227,195 @@ describe('pie hovering', function() { }); }); }); + + +describe('Test event property of interactions on a pie plot:', function() { + var mock = require('@mocks/pie_simple.json'); + + var mockCopy, gd; + + var blankPos = [10, 10], + pointPos; + + beforeAll(function(done) { + jasmine.addMatchers(customMatchers); + + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { + pointPos = getClientPosition('g.slicetext'); + destroyGraphDiv(); + done(); + }); + }); + + beforeEach(function() { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + }); + + afterEach(destroyGraphDiv); + + describe('click events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); + expect(futureData.points.length).toEqual(1); + + var trace = futureData.points.trace; + expect(typeof trace).toEqual(typeof {}, 'points.trace'); + + var pt = futureData.points[0]; + expect(Object.keys(pt)).toEqual([ + 'v', 'label', 'color', 'i', 'hidden', 'vTotal', 'text', 't', + 'trace', 'r', 'cx', 'cy', 'px1', 'pxmid', 'midangle', 'px0', + 'largeArc', 'cxFinal', 'cyFinal' + ]); + expect(typeof pt.color).toEqual(typeof '#1f77b4', 'points[0].color'); + expect(pt.cx).toEqual(200, 'points[0].cx'); + expect(pt.cxFinal).toEqual(200, 'points[0].cxFinal'); + expect(pt.cy).toEqual(160, 'points[0].cy'); + expect(pt.cyFinal).toEqual(160, 'points[0].cyFinal'); + expect(pt.hidden).toEqual(false, 'points[0].hidden'); + expect(pt.i).toEqual(4, 'points[0].i'); + expect(pt.label).toEqual('4', 'points[0].label'); + expect(pt.largeArc).toEqual(0, 'points[0].largeArc'); + expect(pt.midangle).toEqual(1.0471975511965976, 'points[0].midangle'); + expect(pt.px0).toEqual([0, -60], 'points[0].px0'); + expect(pt.px1).toEqual([51.96152422706632, 29.999999999999986], 'points[0].px1'); + expect(pt.pxmid).toEqual([51.96152422706631, -30.000000000000007], 'points[0].pxmid'); + expect(pt.r).toEqual(60, 'points[0].r'); + expect(typeof pt.t).toEqual(typeof {}, 'points[0].t'); + expect(pt.text).toEqual('33.3%', 'points[0].text'); + expect(typeof pt.trace).toEqual(typeof {}, 'points[0].trace'); + expect(pt.v).toEqual(5, 'points[0].v'); + expect(pt.vTotal).toEqual(15, 'points[0].vTotal'); + + var evt = futureData.event; + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('modified click events', function() { + var clickOpts = { + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true + }, + futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1], clickOpts); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1], clickOpts); + expect(futureData.points.length).toEqual(1); + + var trace = futureData.points.trace; + expect(typeof trace).toEqual(typeof {}, 'points.trace'); + + var pt = futureData.points[0]; + expect(Object.keys(pt)).toEqual([ + 'v', 'label', 'color', 'i', 'hidden', 'vTotal', 'text', 't', + 'trace', 'r', 'cx', 'cy', 'px1', 'pxmid', 'midangle', 'px0', + 'largeArc', 'cxFinal', 'cyFinal' + ]); + expect(typeof pt.color).toEqual(typeof '#1f77b4', 'points[0].color'); + expect(pt.cx).toEqual(200, 'points[0].cx'); + expect(pt.cxFinal).toEqual(200, 'points[0].cxFinal'); + expect(pt.cy).toEqual(160, 'points[0].cy'); + expect(pt.cyFinal).toEqual(160, 'points[0].cyFinal'); + expect(pt.hidden).toEqual(false, 'points[0].hidden'); + expect(pt.i).toEqual(4, 'points[0].i'); + expect(pt.label).toEqual('4', 'points[0].label'); + expect(pt.largeArc).toEqual(0, 'points[0].largeArc'); + expect(pt.midangle).toEqual(1.0471975511965976, 'points[0].midangle'); + expect(pt.px0).toEqual([0, -60], 'points[0].px0'); + expect(pt.px1).toEqual([51.96152422706632, 29.999999999999986], 'points[0].px1'); + expect(pt.pxmid).toEqual([51.96152422706631, -30.000000000000007], 'points[0].pxmid'); + expect(pt.r).toEqual(60, 'points[0].r'); + expect(typeof pt.t).toEqual(typeof {}, 'points[0].t'); + expect(pt.text).toEqual('33.3%', 'points[0].text'); + expect(typeof pt.trace).toEqual(typeof {}, 'points[0].trace'); + expect(pt.v).toEqual(5, 'points[0].v'); + expect(pt.vTotal).toEqual(15, 'points[0].vTotal'); + + var evt = futureData.event; + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { + expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt); + }); + }); + }); + + describe('hover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_hover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mouseover', pointPos[0], pointPos[1]); + + var point0 = futureData.points[0], + evt = futureData.event; + expect(point0.originalEvent).toEqual(evt, 'points'); + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('unhover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mouseout', pointPos[0], pointPos[1]); + + var point0 = futureData.points[0], + evt = futureData.event; + expect(point0.originalEvent).toEqual(evt, 'points'); + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); +}); diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 716af5581a3..88aa8adfdb0 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -9,6 +9,21 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var customMatchers = require('../assets/custom_matchers'); +var mouseEvent = require('../assets/mouse_event'); +var click = require('../assets/click'); +var HOVERMINTIME = require('@src/plots/cartesian/constants').HOVERMINTIME; + +function move(fromX, fromY, toX, toY, delay) { + return new Promise(function(resolve) { + mouseEvent('mousemove', fromX, fromY); + + setTimeout(function() { + mouseEvent('mousemove', toX, toY); + resolve(); + }, delay || HOVERMINTIME + 10); + }); +} + Plotly.setPlotConfig({ mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }); @@ -600,3 +615,204 @@ describe('@noCI scattermapbox hover', function() { }); }); }); + + +describe('@noCI Test plotly events on a scattermapbox plot:', function() { + var mock = require('@mocks/mapbox_0.json'); + + var mockCopy, gd; + + var blankPos = [10, 10], + pointPos, + nearPos; + + function getPointData(gd) { + var cd = gd.calcdata, + mapbox = gd._fullLayout.mapbox._subplot; + + return { + index: false, + distance: 20, + cd: cd[0], + trace: cd[0][0].trace, + xa: mapbox.xaxis, + ya: mapbox.yaxis + }; + } + + beforeAll(function(done) { + jasmine.addMatchers(customMatchers); + + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { + var bb = gd._fullLayout.mapbox._subplot.div.getBoundingClientRect(), + xval = 10, + yval = 10, + point = ScatterMapbox.hoverPoints(getPointData(gd), xval, yval)[0]; + pointPos = [Math.floor(bb.left + (point.x0 + point.x1) / 2), + Math.floor(bb.top + (point.y0 + point.y1) / 2)]; + nearPos = [pointPos[0] - 30, pointPos[1] - 30]; + }).then(destroyGraphDiv).then(done); + }); + + beforeEach(function() { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + }); + + afterEach(destroyGraphDiv); + + describe('click events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(undefined, 'points[0].lat'); + expect(pt.lon).toEqual(undefined, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('modified click events', function() { + var clickOpts = { + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true + }, + futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1], clickOpts); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1], clickOpts); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(undefined, 'points[0].lat'); + expect(pt.lon).toEqual(undefined, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { + expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt); + }); + }); + }); + + describe('hover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_hover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(undefined, 'points[0].lat'); + expect(pt.lon).toEqual(undefined, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('unhover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function(done) { + move(pointPos[0], pointPos[1], nearPos[0], nearPos[1], HOVERMINTIME + 10).then(function() { + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(undefined, 'points[0].lat'); + expect(pt.lon).toEqual(undefined, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + + expect(evt.clientX).toEqual(nearPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(nearPos[1], 'event.clientY'); + }).then(done); + }); + }); +}); diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index c563a500b1b..4eb9e428cc1 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -10,6 +10,7 @@ var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); var doubleClick = require('../assets/double_click'); var customMatchers = require('../assets/custom_matchers'); +var getClientPosition = require('../assets/get_client_position'); describe('ternary plots', function() { @@ -334,3 +335,207 @@ describe('ternary defaults', function() { expect(layoutOut.ternary.caxis.gridcolor).toEqual('black'); }); }); + + +describe('Test event property of interactions on a ternary plot:', function() { + var mock = require('@mocks/ternary_simple.json'); + + var mockCopy, gd; + + var blankPos = [10, 10], + pointPos; + + beforeAll(function(done) { + jasmine.addMatchers(customMatchers); + + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { + pointPos = getClientPosition('path.point'); + destroyGraphDiv(); + done(); + }); + }); + + beforeEach(function() { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + }); + + afterEach(destroyGraphDiv); + + describe('click events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'xaxis', 'yaxis' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + expect(pt.x).toEqual(undefined, 'points[0].x'); + expect(pt.y).toEqual(undefined, 'points[0].y'); + expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); + expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('modified click events', function() { + var clickOpts = { + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true + }, + futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1], clickOpts); + expect(futureData).toBe(undefined); + }); + + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1], clickOpts); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'xaxis', 'yaxis' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + expect(pt.x).toEqual(undefined, 'points[0].x'); + expect(pt.y).toEqual(undefined, 'points[0].y'); + expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); + expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { + expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt); + }); + }); + }); + + describe('hover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_hover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event, + xaxes0 = futureData.xaxes[0], + xvals0 = futureData.xvals[0], + yaxes0 = futureData.yaxes[0], + yvals0 = futureData.yvals[0]; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'xaxis', 'yaxis' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + expect(pt.x).toEqual(undefined, 'points[0].x'); + expect(pt.y).toEqual(undefined, 'points[0].y'); + expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); + expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); + + expect(xaxes0).toEqual(pt.xaxis, 'xaxes[0]'); + expect(xvals0).toEqual(-0.0016654247744483342, 'xaxes[0]'); + expect(yaxes0).toEqual(pt.yaxis, 'yaxes[0]'); + expect(yvals0).toEqual(0.5013, 'xaxes[0]'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); + + describe('unhover events', function() { + var futureData; + + beforeEach(function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('should contain the correct fields', function() { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + mouseEvent('mouseout', pointPos[0], pointPos[1]); + + var pt = futureData.points[0], + evt = futureData.event; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'xaxis', 'yaxis' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + expect(pt.x).toEqual(undefined, 'points[0].x'); + expect(pt.y).toEqual(undefined, 'points[0].y'); + expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); + expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); + + expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); + expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); + }); + }); +});