Skip to content

Commit c835242

Browse files
authored
Merge pull request #1505 from n-riesco/pr-20170321-events
Add mouse event to `plotly_click`, `plotly_hover` and `plotly_unhover`
2 parents d4b9017 + 9a37276 commit c835242

File tree

14 files changed

+1074
-45
lines changed

14 files changed

+1074
-45
lines changed

Diff for: src/components/dragelement/index.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,22 @@ dragElement.init = function init(options) {
140140
if(options.doneFn) options.doneFn(gd._dragged, numClicks, e);
141141

142142
if(!gd._dragged) {
143-
var e2 = document.createEvent('MouseEvents');
144-
e2.initEvent('click', true, true);
143+
var e2;
144+
145+
try {
146+
e2 = new MouseEvent('click', e);
147+
}
148+
catch(err) {
149+
e2 = document.createEvent('MouseEvents');
150+
e2.initMouseEvent('click',
151+
e.bubbles, e.cancelable,
152+
e.view, e.detail,
153+
e.screenX, e.screenY,
154+
e.clientX, e.clientY,
155+
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
156+
e.button, e.relatedTarget);
157+
}
158+
145159
initialTarget.dispatchEvent(e2);
146160
}
147161

Diff for: src/components/dragelement/unhover.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ unhover.raw = function unhoverRaw(gd, evt) {
4444
gd._hoverdata = undefined;
4545

4646
if(evt.target && oldhoverdata) {
47-
gd.emit('plotly_unhover', {points: oldhoverdata});
47+
gd.emit('plotly_unhover', {
48+
event: evt,
49+
points: oldhoverdata
50+
});
4851
}
4952
};

Diff for: src/plots/cartesian/graph_interact.js

+16-13
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ fx.hover = function(gd, evt, subplot) {
305305
function hover(gd, evt, subplot) {
306306
if(subplot === 'pie') {
307307
gd.emit('plotly_hover', {
308+
event: evt.originalEvent,
308309
points: [evt]
309310
});
310311
return;
@@ -413,12 +414,17 @@ function hover(gd, evt, subplot) {
413414

414415
// [x|y]px: the pixels (from top left) of the mouse location
415416
// on the currently selected plot area
416-
var xpx, ypx;
417+
var hasUserCalledHover = !evt.target,
418+
xpx, ypx;
417419

418-
// mouse event? ie is there a target element with
419-
// clientX and clientY values?
420-
if(evt.target && ('clientX' in evt) && ('clientY' in evt)) {
420+
if(hasUserCalledHover) {
421+
if('xpx' in evt) xpx = evt.xpx;
422+
else xpx = xaArray[0]._length / 2;
421423

424+
if('ypx' in evt) ypx = evt.ypx;
425+
else ypx = yaArray[0]._length / 2;
426+
}
427+
else {
422428
// fire the beforehover event and quit if it returns false
423429
// note that we're only calling this on real mouse events, so
424430
// manual calls to fx.hover will always run.
@@ -437,13 +443,6 @@ function hover(gd, evt, subplot) {
437443
return dragElement.unhoverRaw(gd, evt);
438444
}
439445
}
440-
else {
441-
if('xpx' in evt) xpx = evt.xpx;
442-
else xpx = xaArray[0]._length / 2;
443-
444-
if('ypx' in evt) ypx = evt.ypx;
445-
else ypx = yaArray[0]._length / 2;
446-
}
447446

448447
if('xval' in evt) xvalArray = flat(subplots, evt.xval);
449448
else xvalArray = p2c(xaArray, xpx);
@@ -624,10 +623,14 @@ function hover(gd, evt, subplot) {
624623
if(!hoverChanged(gd, evt, oldhoverdata)) return;
625624

626625
if(oldhoverdata) {
627-
gd.emit('plotly_unhover', { points: oldhoverdata });
626+
gd.emit('plotly_unhover', {
627+
event: evt,
628+
points: oldhoverdata
629+
});
628630
}
629631

630632
gd.emit('plotly_hover', {
633+
event: evt,
631634
points: gd._hoverdata,
632635
xaxes: xaArray,
633636
yaxes: yaArray,
@@ -1350,7 +1353,7 @@ function hoverChanged(gd, evt, oldhoverdata) {
13501353
fx.click = function(gd, evt) {
13511354
var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata);
13521355

1353-
function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata}); }
1356+
function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); }
13541357

13551358
if(gd._hoverdata && evt && evt.target) {
13561359
if(annotationsDone && annotationsDone.then) {

Diff for: src/plots/geo/geo.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,9 @@ proto.plot = function(geoCalcData, fullLayout, promises) {
9191

9292
if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) return;
9393

94-
var evt = {
95-
target: true,
96-
xpx: mouse[0],
97-
ypx: mouse[1]
98-
};
94+
var evt = d3.event;
95+
evt.xpx = mouse[0];
96+
evt.ypx = mouse[1];
9997

10098
_this.xaxis.c2p = function() { return mouse[0]; };
10199
_this.xaxis.p2c = function() { return lonlat[0]; };
@@ -110,7 +108,7 @@ proto.plot = function(geoCalcData, fullLayout, promises) {
110108
});
111109

112110
_this.framework.on('click', function() {
113-
Fx.click(_this.graphDiv, { target: true });
111+
Fx.click(_this.graphDiv, d3.event);
114112
});
115113

116114
topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout);

Diff for: src/plots/mapbox/mapbox.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
166166
Fx.hover(gd, evt, self.id);
167167
});
168168

169-
map.on('click', function() {
170-
Fx.click(gd, { target: true });
169+
map.on('click', function(evt) {
170+
Fx.click(gd, evt.originalEvent);
171171
});
172172

173173
function unhover() {

Diff for: src/traces/pie/plot.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ module.exports = function plot(gd, cdpie) {
8686
hasHoverData = false;
8787

8888
function handleMouseOver(evt) {
89+
evt.originalEvent = d3.event;
90+
8991
// in case fullLayout or fullData has changed without a replot
9092
var fullLayout2 = gd._fullLayout,
9193
trace2 = gd._fullData[trace.index],
@@ -97,6 +99,7 @@ module.exports = function plot(gd, cdpie) {
9799
// or if hover is turned off
98100
if(gd._dragging || fullLayout2.hovermode === false ||
99101
hoverinfo === 'none' || hoverinfo === 'skip' || !hoverinfo) {
102+
Fx.hover(gd, evt, 'pie');
100103
return;
101104
}
102105

@@ -132,7 +135,9 @@ module.exports = function plot(gd, cdpie) {
132135
}
133136

134137
function handleMouseOut(evt) {
138+
evt.originalEvent = d3.event;
135139
gd.emit('plotly_unhover', {
140+
event: d3.event,
136141
points: [evt]
137142
});
138143

@@ -144,8 +149,8 @@ module.exports = function plot(gd, cdpie) {
144149

145150
function handleClick() {
146151
gd._hoverdata = [pt];
147-
gd._hoverdata.trace = cd.trace;
148-
Fx.click(gd, { target: true });
152+
gd._hoverdata.trace = cd0.trace;
153+
Fx.click(gd, d3.event);
149154
}
150155

151156
slicePath.enter().append('path')

Diff for: test/jasmine/assets/click.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
var mouseEvent = require('./mouse_event');
22

3-
module.exports = function click(x, y) {
4-
mouseEvent('mousemove', x, y);
5-
mouseEvent('mousedown', x, y);
6-
mouseEvent('mouseup', x, y);
3+
module.exports = function click(x, y, opts) {
4+
mouseEvent('mousemove', x, y, opts);
5+
mouseEvent('mousedown', x, y, opts);
6+
mouseEvent('mouseup', x, y, opts);
7+
mouseEvent('click', x, y, opts);
78
};

Diff for: test/jasmine/assets/get_client_position.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = function getClientPosition(selector, index) {
2+
index = index || 0;
3+
4+
var selection = document.querySelectorAll(selector),
5+
clientPos = selection[index].getBoundingClientRect(),
6+
x = Math.floor((clientPos.left + clientPos.right) / 2),
7+
y = Math.floor((clientPos.top + clientPos.bottom) / 2);
8+
9+
return [x, y];
10+
};

Diff for: test/jasmine/assets/mouse_event.js

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ module.exports = function(type, x, y, opts) {
99
if(opts && opts.buttons) {
1010
fullOpts.buttons = opts.buttons;
1111
}
12+
if(opts && opts.altKey) {
13+
fullOpts.altKey = opts.altKey;
14+
}
15+
if(opts && opts.ctrlKey) {
16+
fullOpts.ctrlKey = opts.ctrlKey;
17+
}
18+
if(opts && opts.metaKey) {
19+
fullOpts.metaKey = opts.metaKey;
20+
}
21+
if(opts && opts.shiftKey) {
22+
fullOpts.shiftKey = opts.shiftKey;
23+
}
1224

1325
var el = (opts && opts.element) || document.elementFromPoint(x, y),
1426
ev;

Diff for: test/jasmine/tests/click_test.js

+114-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ var customMatchers = require('../assets/custom_matchers');
1616
var click = require('../assets/click');
1717
var doubleClickRaw = require('../assets/double_click');
1818

19+
function move(fromX, fromY, toX, toY, delay) {
20+
return new Promise(function(resolve) {
21+
mouseEvent('mousemove', fromX, fromY);
22+
23+
setTimeout(function() {
24+
mouseEvent('mousemove', toX, toY);
25+
resolve();
26+
}, delay || DBLCLICKDELAY / 4);
27+
});
28+
}
29+
30+
function drag(fromX, fromY, toX, toY, delay) {
31+
return new Promise(function(resolve) {
32+
mouseEvent('mousemove', fromX, fromY);
33+
mouseEvent('mousedown', fromX, fromY);
34+
mouseEvent('mousemove', toX, toY);
35+
36+
setTimeout(function() {
37+
mouseEvent('mouseup', toX, toY);
38+
resolve();
39+
}, delay || DBLCLICKDELAY / 4);
40+
});
41+
}
42+
1943

2044
describe('Test click interactions:', function() {
2145
var mock = require('@mocks/14.json');
@@ -39,19 +63,6 @@ describe('Test click interactions:', function() {
3963

4064
afterEach(destroyGraphDiv);
4165

42-
function drag(fromX, fromY, toX, toY, delay) {
43-
return new Promise(function(resolve) {
44-
mouseEvent('mousemove', fromX, fromY);
45-
mouseEvent('mousedown', fromX, fromY);
46-
mouseEvent('mousemove', toX, toY);
47-
48-
setTimeout(function() {
49-
mouseEvent('mouseup', toX, toY);
50-
resolve();
51-
}, delay || DBLCLICKDELAY / 4);
52-
});
53-
}
54-
5566
function doubleClick(x, y) {
5667
return doubleClickRaw(x, y).then(function() {
5768
return Plotly.Plots.previousPromises(gd);
@@ -87,6 +98,55 @@ describe('Test click interactions:', function() {
8798
expect(pt.pointNumber).toEqual(11);
8899
expect(pt.x).toEqual(0.125);
89100
expect(pt.y).toEqual(2.125);
101+
102+
var evt = futureData.event;
103+
expect(evt.clientX).toEqual(pointPos[0]);
104+
expect(evt.clientY).toEqual(pointPos[1]);
105+
});
106+
});
107+
108+
describe('modified click events', function() {
109+
var clickOpts = {
110+
altKey: true,
111+
ctrlKey: true,
112+
metaKey: true,
113+
shiftKey: true
114+
},
115+
futureData;
116+
117+
beforeEach(function(done) {
118+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
119+
120+
gd.on('plotly_click', function(data) {
121+
futureData = data;
122+
});
123+
});
124+
125+
it('should not be trigged when not on data points', function() {
126+
click(blankPos[0], blankPos[1], clickOpts);
127+
expect(futureData).toBe(undefined);
128+
});
129+
130+
it('should contain the correct fields', function() {
131+
click(pointPos[0], pointPos[1], clickOpts);
132+
expect(futureData.points.length).toEqual(1);
133+
134+
var pt = futureData.points[0];
135+
expect(Object.keys(pt)).toEqual([
136+
'data', 'fullData', 'curveNumber', 'pointNumber',
137+
'x', 'y', 'xaxis', 'yaxis'
138+
]);
139+
expect(pt.curveNumber).toEqual(0);
140+
expect(pt.pointNumber).toEqual(11);
141+
expect(pt.x).toEqual(0.125);
142+
expect(pt.y).toEqual(2.125);
143+
144+
var evt = futureData.event;
145+
expect(evt.clientX).toEqual(pointPos[0]);
146+
expect(evt.clientY).toEqual(pointPos[1]);
147+
Object.getOwnPropertyNames(clickOpts).forEach(function(opt) {
148+
expect(evt[opt]).toEqual(clickOpts[opt], opt);
149+
});
90150
});
91151
});
92152

@@ -191,6 +251,46 @@ describe('Test click interactions:', function() {
191251
expect(pt.pointNumber).toEqual(11);
192252
expect(pt.x).toEqual(0.125);
193253
expect(pt.y).toEqual(2.125);
254+
255+
var evt = futureData.event;
256+
expect(evt.clientX).toEqual(pointPos[0]);
257+
expect(evt.clientY).toEqual(pointPos[1]);
258+
});
259+
});
260+
261+
describe('plotly_unhover event with hoverinfo set to none', function() {
262+
var futureData;
263+
264+
beforeEach(function(done) {
265+
266+
var modifiedMockCopy = Lib.extendDeep({}, mockCopy);
267+
modifiedMockCopy.data[0].hoverinfo = 'none';
268+
Plotly.plot(gd, modifiedMockCopy.data, modifiedMockCopy.layout)
269+
.then(done);
270+
271+
gd.on('plotly_unhover', function(data) {
272+
futureData = data;
273+
});
274+
});
275+
276+
it('should contain the correct fields despite hoverinfo: "none"', function(done) {
277+
move(pointPos[0], pointPos[1], blankPos[0], blankPos[1]).then(function() {
278+
expect(futureData.points.length).toEqual(1);
279+
280+
var pt = futureData.points[0];
281+
expect(Object.keys(pt)).toEqual([
282+
'data', 'fullData', 'curveNumber', 'pointNumber',
283+
'x', 'y', 'xaxis', 'yaxis'
284+
]);
285+
expect(pt.curveNumber).toEqual(0);
286+
expect(pt.pointNumber).toEqual(11);
287+
expect(pt.x).toEqual(0.125);
288+
expect(pt.y).toEqual(2.125);
289+
290+
var evt = futureData.event;
291+
expect(evt.clientX).toEqual(blankPos[0]);
292+
expect(evt.clientY).toEqual(blankPos[1]);
293+
}).then(done);
194294
});
195295
});
196296

@@ -817,6 +917,7 @@ describe('Test click interactions:', function() {
817917
});
818918
});
819919

920+
820921
describe('dragbox', function() {
821922

822923
afterEach(destroyGraphDiv);

0 commit comments

Comments
 (0)