-
Notifications
You must be signed in to change notification settings - Fork 319
/
events.js
149 lines (131 loc) · 4.21 KB
/
events.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {distanceBetweenPoints, defined, callback} from 'chart.js/helpers';
const clickHooks = ['click', 'dblclick'];
const moveHooks = ['enter', 'leave'];
export const hooks = clickHooks.concat(moveHooks);
export function updateListeners(chart, state, options) {
state.listened = false;
state.moveListened = false;
hooks.forEach(hook => {
if (typeof options[hook] === 'function') {
state.listened = true;
state.listeners[hook] = options[hook];
} else if (defined(state.listeners[hook])) {
delete state.listeners[hook];
}
});
moveHooks.forEach(hook => {
if (typeof options[hook] === 'function') {
state.moveListened = true;
}
});
if (!state.listened || !state.moveListened) {
state.annotations.forEach(scope => {
if (!state.listened) {
clickHooks.forEach(hook => {
if (typeof scope[hook] === 'function') {
state.listened = true;
}
});
}
if (!state.moveListened) {
moveHooks.forEach(hook => {
if (typeof scope[hook] === 'function') {
state.listened = true;
state.moveListened = true;
}
});
}
});
}
}
export function handleEvent(state, event, options) {
if (state.listened) {
switch (event.type) {
case 'mousemove':
case 'mouseout':
handleMoveEvents(state, event, options);
break;
case 'click':
handleClickEvents(state, event, options);
break;
default:
}
}
}
function handleMoveEvents(state, event, options) {
if (!state.moveListened) {
return;
}
let elements = [];
if (event.type === 'mousemove') {
elements = getItems(state, event, options);
}
const previous = state.hovered;
state.hovered = elements;
const context = {state, event};
dispatchMoveEvents(context, 'leave', previous, elements);
dispatchMoveEvents(context, 'enter', elements, previous);
}
function dispatchMoveEvents({state, event}, hook, elements, checkElements) {
for (const element of elements) {
if (checkElements.indexOf(element) < 0) {
dispatchEvent(element.options[hook] || state.listeners[hook], element, event);
}
}
}
function handleClickEvents(state, event, options) {
const listeners = state.listeners;
const elements = getItems(state, event, options);
for (const element of elements) {
const elOpts = element.options;
const dblclick = elOpts.dblclick || listeners.dblclick;
const click = elOpts.click || listeners.click;
if (element.clickTimeout) {
// 2nd click before timeout, so its a double click
clearTimeout(element.clickTimeout);
delete element.clickTimeout;
dispatchEvent(dblclick, element, event);
} else if (dblclick) {
// if there is a dblclick handler, wait for dblClickSpeed ms before deciding its a click
element.clickTimeout = setTimeout(() => {
delete element.clickTimeout;
dispatchEvent(click, element, event);
}, options.dblClickSpeed);
} else {
// no double click handler, just call the click handler directly
dispatchEvent(click, element, event);
}
}
}
function dispatchEvent(handler, element, event) {
callback(handler, [element.$context, event]);
}
function getItems(state, event, options) {
if (options.interaction.mode === 'point') {
return state.visibleElements.filter((element) => element.inRange(event.x, event.y));
}
const element = getNearestItem(state.visibleElements, event);
if (element) {
return [element];
}
return [];
}
function getNearestItem(elements, position) {
let minDistance = Number.POSITIVE_INFINITY;
return elements
.filter((element) => element.inRange(position.x, position.y))
.reduce((nearestItems, element) => {
const center = element.getCenterPoint();
const distance = distanceBetweenPoints(position, center);
if (distance < minDistance) {
nearestItems = [element];
minDistance = distance;
} else if (distance === minDistance) {
// Can have multiple items at the same distance in which case we sort by size
nearestItems.push(element);
}
return nearestItems;
}, [])
.sort((a, b) => a._index - b._index)
.slice(0, 1)[0]; // return only the top item
}