-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathhover.js
197 lines (151 loc) · 6.26 KB
/
hover.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
'use strict';
var Axes = require('../../plots/cartesian/axes');
var Lib = require('../../lib');
var Fx = require('../../components/fx');
var Color = require('../../components/color');
var fillText = require('../../lib').fillText;
var delta = require('../../constants/delta.js');
var DIRSYMBOL = {
increasing: delta.INCREASING.SYMBOL,
decreasing: delta.DECREASING.SYMBOL
};
function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var trace = cd[0].trace;
if(trace.hoverlabel.split) {
return hoverSplit(pointData, xval, yval, hovermode);
}
return hoverOnPoints(pointData, xval, yval, hovermode);
}
function _getClosestPoint(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var xa = pointData.xa;
var trace = cd[0].trace;
var t = cd[0].t;
var type = trace.type;
var minAttr = type === 'ohlc' ? 'l' : 'min';
var maxAttr = type === 'ohlc' ? 'h' : 'max';
var hoverPseudoDistance, spikePseudoDistance;
// potentially shift xval for grouped candlesticks
var centerShift = t.bPos || 0;
var shiftPos = function(di) { return di.pos + centerShift - xval; };
// ohlc and candlestick call displayHalfWidth different things...
var displayHalfWidth = t.bdPos || t.tickLen;
var hoverHalfWidth = t.wHover;
// if two figures are overlaying, let the narrowest one win
var pseudoDistance = Math.min(1, displayHalfWidth / Math.abs(xa.r2c(xa.range[1]) - xa.r2c(xa.range[0])));
hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
function dx(di) {
var pos = shiftPos(di);
return Fx.inbox(pos - hoverHalfWidth, pos + hoverHalfWidth, hoverPseudoDistance);
}
function dy(di) {
var min = di[minAttr];
var max = di[maxAttr];
return min === max || Fx.inbox(min - yval, max - yval, hoverPseudoDistance);
}
function dxy(di) { return (dx(di) + dy(di)) / 2; }
var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
Fx.getClosest(cd, distfn, pointData);
if(pointData.index === false) return null;
var di = cd[pointData.index];
if(di.empty) return null;
var dir = di.dir;
var container = trace[dir];
var lc = container.line.color;
if(Color.opacity(lc) && container.line.width) pointData.color = lc;
else pointData.color = container.fillcolor;
pointData.x0 = xa.c2p(di.pos + centerShift - displayHalfWidth, true);
pointData.x1 = xa.c2p(di.pos + centerShift + displayHalfWidth, true);
pointData.xLabelVal = di.orig_p !== undefined ? di.orig_p : di.pos;
pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
pointData.xSpike = xa.c2p(di.pos, true);
return pointData;
}
function hoverSplit(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var ya = pointData.ya;
var trace = cd[0].trace;
var t = cd[0].t;
var closeBoxData = [];
var closestPoint = _getClosestPoint(pointData, xval, yval, hovermode);
// skip the rest (for this trace) if we didn't find a close point
if(!closestPoint) return [];
var cdIndex = closestPoint.index;
var di = cd[cdIndex];
var hoverinfo = di.hi || trace.hoverinfo;
var hoverParts = hoverinfo.split('+');
var isAll = hoverinfo === 'all';
var hasY = isAll || hoverParts.indexOf('y') !== -1;
// similar to hoverOnPoints, we return nothing
// if all or y is not present.
if(!hasY) return [];
var attrs = ['high', 'open', 'close', 'low'];
// several attributes can have the same y-coordinate. We will
// bunch them together in a single text block. For this, we keep
// a dictionary mapping y-coord -> point data.
var usedVals = {};
for(var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
var val = trace[attr][closestPoint.index];
var valPx = ya.c2p(val, true);
var pointData2;
if(val in usedVals) {
pointData2 = usedVals[val];
pointData2.yLabel += '<br>' + t.labels[attr] + Axes.hoverLabelText(ya, val, trace.yhoverformat);
} else {
// copy out to a new object for each new y-value to label
pointData2 = Lib.extendFlat({}, closestPoint);
pointData2.y0 = pointData2.y1 = valPx;
pointData2.yLabelVal = val;
pointData2.yLabel = t.labels[attr] + Axes.hoverLabelText(ya, val, trace.yhoverformat);
pointData2.name = '';
closeBoxData.push(pointData2);
usedVals[val] = pointData2;
}
}
return closeBoxData;
}
function hoverOnPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var ya = pointData.ya;
var trace = cd[0].trace;
var t = cd[0].t;
var closestPoint = _getClosestPoint(pointData, xval, yval, hovermode);
// skip the rest (for this trace) if we didn't find a close point
if(!closestPoint) return [];
// we don't make a calcdata point if we're missing any piece (x/o/h/l/c)
// so we need to fix the index here to point to the data arrays
var cdIndex = closestPoint.index;
var di = cd[cdIndex];
var i = closestPoint.index = di.i;
var dir = di.dir;
function getLabelLine(attr) {
return t.labels[attr] + Axes.hoverLabelText(ya, trace[attr][i], trace.yhoverformat);
}
var hoverinfo = di.hi || trace.hoverinfo;
var hoverParts = hoverinfo.split('+');
var isAll = hoverinfo === 'all';
var hasY = isAll || hoverParts.indexOf('y') !== -1;
var hasText = isAll || hoverParts.indexOf('text') !== -1;
var textParts = hasY ? [
getLabelLine('open'),
getLabelLine('high'),
getLabelLine('low'),
getLabelLine('close') + ' ' + DIRSYMBOL[dir]
] : [];
if(hasText) fillText(di, trace, textParts);
// don't make .yLabelVal or .text, since we're managing hoverinfo
// put it all in .extraText
closestPoint.extraText = textParts.join('<br>');
// this puts the label *and the spike* at the midpoint of the box, ie
// halfway between open and close, not between high and low.
closestPoint.y0 = closestPoint.y1 = ya.c2p(di.yc, true);
return [closestPoint];
}
module.exports = {
hoverPoints: hoverPoints,
hoverSplit: hoverSplit,
hoverOnPoints: hoverOnPoints
};