Skip to content

Add mouse event to plotly_click, plotly_hover and plotly_unhover #1505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 30, 2017
18 changes: 16 additions & 2 deletions src/components/dragelement/index.js
Original file line number Diff line number Diff line change
@@ -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);
}

5 changes: 4 additions & 1 deletion src/components/dragelement/unhover.js
Original file line number Diff line number Diff line change
@@ -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
});
}
};
29 changes: 16 additions & 13 deletions src/plots/cartesian/graph_interact.js
Original file line number Diff line number Diff line change
@@ -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) {
10 changes: 4 additions & 6 deletions src/plots/geo/geo.js
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 2 additions & 2 deletions src/plots/mapbox/mapbox.js
Original file line number Diff line number Diff line change
@@ -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() {
9 changes: 7 additions & 2 deletions src/traces/pie/plot.js
Original file line number Diff line number Diff line change
@@ -86,6 +86,8 @@ module.exports = function plot(gd, cdpie) {
hasHoverData = false;

function handleMouseOver(evt) {
evt.originalEvent = d3.event;
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!


// 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')
9 changes: 5 additions & 4 deletions test/jasmine/assets/click.js
Original file line number Diff line number Diff line change
@@ -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);
};
10 changes: 10 additions & 0 deletions test/jasmine/assets/get_client_position.js
Original file line number Diff line number Diff line change
@@ -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];
};
12 changes: 12 additions & 0 deletions test/jasmine/assets/mouse_event.js
Original file line number Diff line number Diff line change
@@ -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;
127 changes: 114 additions & 13 deletions test/jasmine/tests/click_test.js
Original file line number Diff line number Diff line change
@@ -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);
207 changes: 207 additions & 0 deletions test/jasmine/tests/geo_test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
258 changes: 256 additions & 2 deletions test/jasmine/tests/hover_pie_test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
216 changes: 216 additions & 0 deletions test/jasmine/tests/scattermapbox_test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
205 changes: 205 additions & 0 deletions test/jasmine/tests/ternary_test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
});