@@ -14,190 +14,208 @@ var Fx = require('../../components/fx');
14
14
var Color = require ( '../../components/color' ) ;
15
15
var fillHoverText = require ( '../scatter/fill_hover_text' ) ;
16
16
17
- module . exports = function hoverPoints ( pointData , xval , yval , hovermode ) {
17
+ function hoverPoints ( pointData , xval , yval , hovermode ) {
18
18
var cd = pointData . cd ;
19
- var xa = pointData . xa ;
20
- var ya = pointData . ya ;
21
-
22
19
var trace = cd [ 0 ] . trace ;
23
20
var hoveron = trace . hoveron ;
24
- var marker = trace . marker || { } ;
25
-
26
- // output hover points components
27
21
var closeBoxData = [ ] ;
28
22
var closePtData ;
29
- // x/y/effective distance functions
30
- var dx , dy , distfn ;
31
- // orientation-specific fields
32
- var posLetter , valLetter , posAxis , valAxis ;
33
- // calcdata item
34
- var di ;
35
- // loop indices
36
- var i , j ;
37
23
38
24
if ( hoveron . indexOf ( 'boxes' ) !== - 1 ) {
39
- var t = cd [ 0 ] . t ;
40
-
41
- // closest mode: handicap box plots a little relative to others
42
- // adjust inbox w.r.t. to calculate box size
43
- var boxDelta = ( hovermode === 'closest' ) ? 2.5 * t . bdPos : t . bdPos ;
44
-
45
- if ( trace . orientation === 'h' ) {
46
- dx = function ( di ) {
47
- return Fx . inbox ( di . min - xval , di . max - xval ) ;
48
- } ;
49
- dy = function ( di ) {
50
- var pos = di . pos + t . bPos - yval ;
51
- return Fx . inbox ( pos - boxDelta , pos + boxDelta ) ;
52
- } ;
53
- posLetter = 'y' ;
54
- posAxis = ya ;
55
- valLetter = 'x' ;
56
- valAxis = xa ;
57
- } else {
58
- dx = function ( di ) {
59
- var pos = di . pos + t . bPos - xval ;
60
- return Fx . inbox ( pos - boxDelta , pos + boxDelta ) ;
61
- } ;
62
- dy = function ( di ) {
63
- return Fx . inbox ( di . min - yval , di . max - yval ) ;
64
- } ;
65
- posLetter = 'x' ;
66
- posAxis = xa ;
67
- valLetter = 'y' ;
68
- valAxis = ya ;
69
- }
70
-
71
- distfn = Fx . getDistanceFunction ( hovermode , dx , dy ) ;
72
- Fx . getClosest ( cd , distfn , pointData ) ;
25
+ closeBoxData = closeBoxData . concat ( hoverOnBoxes ( pointData , xval , yval , hovermode ) ) ;
26
+ }
73
27
74
- // skip the rest (for this trace) if we didn't find a close point
75
- // and create the item(s) in closedata for this point
76
- if ( pointData . index !== false ) {
77
- di = cd [ pointData . index ] ;
28
+ if ( hoveron . indexOf ( 'points' ) !== - 1 ) {
29
+ closePtData = hoverOnPoints ( pointData , xval , yval ) ;
30
+ }
78
31
79
- var lc = trace . line . color ;
80
- var mc = marker . color ;
32
+ // If there's a point in range and hoveron has points, show the best single point only.
33
+ // If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
34
+ if ( hovermode === 'closest' ) {
35
+ if ( closePtData ) return [ closePtData ] ;
36
+ return closeBoxData ;
37
+ }
81
38
82
- if ( Color . opacity ( lc ) && trace . line . width ) pointData . color = lc ;
83
- else if ( Color . opacity ( mc ) && trace . boxpoints ) pointData . color = mc ;
84
- else pointData . color = trace . fillcolor ;
39
+ // Otherwise in compare mode, allow a point AND the box stats to be labeled
40
+ // If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
41
+ if ( closePtData ) {
42
+ closeBoxData . push ( closePtData ) ;
43
+ return closeBoxData ;
44
+ }
45
+ return closeBoxData ;
46
+ }
85
47
86
- pointData [ posLetter + '0' ] = posAxis . c2p ( di . pos + t . bPos - t . bdPos , true ) ;
87
- pointData [ posLetter + '1' ] = posAxis . c2p ( di . pos + t . bPos + t . bdPos , true ) ;
48
+ function hoverOnBoxes ( pointData , xval , yval , hovermode ) {
49
+ var cd = pointData . cd ;
50
+ var xa = pointData . xa ;
51
+ var ya = pointData . ya ;
52
+ var trace = cd [ 0 ] . trace ;
53
+ var t = cd [ 0 ] . t ;
54
+ var closeBoxData = [ ] ;
88
55
89
- Axes . tickText ( posAxis , posAxis . c2l ( di . pos ) , 'hover' ) . text ;
90
- pointData [ posLetter + 'LabelVal' ] = di . pos ;
56
+ var pLetter , vLetter , pAxis , vAxis , vVal , pVal , dx , dy ;
57
+
58
+ // closest mode: handicap box plots a little relative to others
59
+ // adjust inbox w.r.t. to calculate box size
60
+ var boxDelta = ( hovermode === 'closest' ) ? 2.5 * t . bdPos : t . bdPos ;
61
+ var shiftPos = function ( di ) { return di . pos + t . bPos - pVal ; } ;
62
+
63
+ var dPos = function ( di ) {
64
+ var pos = shiftPos ( di ) ;
65
+ return Fx . inbox ( pos - boxDelta , pos + boxDelta ) ;
66
+ } ;
67
+
68
+ var dVal = function ( di ) {
69
+ return Fx . inbox ( di . min - vVal , di . max - vVal ) ;
70
+ } ;
71
+
72
+ if ( trace . orientation === 'h' ) {
73
+ vVal = xval ;
74
+ pVal = yval ;
75
+ dx = dVal ;
76
+ dy = dPos ;
77
+ pLetter = 'y' ;
78
+ pAxis = ya ;
79
+ vLetter = 'x' ;
80
+ vAxis = xa ;
81
+ } else {
82
+ vVal = yval ;
83
+ pVal = xval ;
84
+ dx = dPos ;
85
+ dy = dVal ;
86
+ pLetter = 'x' ;
87
+ pAxis = xa ;
88
+ vLetter = 'y' ;
89
+ vAxis = ya ;
90
+ }
91
91
92
- // box plots: each "point" gets many labels
93
- var usedVals = { } ;
94
- var attrs = [ 'med' , 'min' , 'q1' , 'q3' , 'max' ] ;
95
- var prefixes = [ 'median' , 'min' , 'q1' , 'q3' , 'max' ] ;
92
+ var distfn = Fx . getDistanceFunction ( hovermode , dx , dy ) ;
93
+ Fx . getClosest ( cd , distfn , pointData ) ;
96
94
97
- if ( trace . boxmean ) {
98
- attrs . push ( 'mean' ) ;
99
- prefixes . push ( trace . boxmean === 'sd' ? 'mean ± σ' : 'mean' ) ;
100
- }
101
- if ( trace . boxpoints ) {
102
- attrs . push ( 'lf' , 'uf' ) ;
103
- prefixes . push ( 'lower fence' , 'upper fence' ) ;
104
- }
95
+ // skip the rest (for this trace) if we didn't find a close point
96
+ // and create the item(s) in closedata for this point
97
+ if ( pointData . index === false ) return [ ] ;
105
98
106
- for ( i = 0 ; i < attrs . length ; i ++ ) {
107
- var attr = attrs [ i ] ;
99
+ var di = cd [ pointData . index ] ;
100
+ var lc = trace . line . color ;
101
+ var mc = ( trace . marker || { } ) . color ;
108
102
109
- if ( ! ( attr in di ) || ( di [ attr ] in usedVals ) ) continue ;
110
- usedVals [ di [ attr ] ] = true ;
103
+ if ( Color . opacity ( lc ) && trace . line . width ) pointData . color = lc ;
104
+ else if ( Color . opacity ( mc ) && trace . boxpoints ) pointData . color = mc ;
105
+ else pointData . color = trace . fillcolor ;
111
106
112
- // copy out to a new object for each value to label
113
- var val = di [ attr ] ;
114
- var valPx = valAxis . c2p ( val , true ) ;
115
- var pointData2 = Lib . extendFlat ( { } , pointData ) ;
107
+ pointData [ pLetter + '0' ] = pAxis . c2p ( di . pos + t . bPos - t . bdPos , true ) ;
108
+ pointData [ pLetter + '1' ] = pAxis . c2p ( di . pos + t . bPos + t . bdPos , true ) ;
116
109
117
- pointData2 [ valLetter + '0' ] = pointData2 [ valLetter + '1' ] = valPx ;
118
- pointData2 [ valLetter + 'LabelVal' ] = val ;
119
- pointData2 [ valLetter + 'Label' ] = prefixes [ i ] + ': ' + Axes . hoverLabelText ( valAxis , val ) ;
110
+ Axes . tickText ( pAxis , pAxis . c2l ( di . pos ) , 'hover' ) . text ;
111
+ pointData [ pLetter + 'LabelVal' ] = di . pos ;
120
112
121
- if ( attr === 'mean' && ( 'sd' in di ) && trace . boxmean === 'sd' ) {
122
- pointData2 [ valLetter + 'err' ] = di . sd ;
123
- }
124
- // only keep name on the first item (median)
125
- pointData . name = '' ;
113
+ // box plots: each "point" gets many labels
114
+ var usedVals = { } ;
115
+ var attrs = [ 'med' , 'min' , 'q1' , 'q3' , 'max' ] ;
116
+ var prefixes = [ 'median' , 'min' , 'q1' , 'q3' , 'max' ] ;
126
117
127
- closeBoxData . push ( pointData2 ) ;
128
- }
129
- }
118
+ if ( trace . boxmean ) {
119
+ attrs . push ( 'mean' ) ;
120
+ prefixes . push ( trace . boxmean === 'sd' ? 'mean ± σ' : 'mean' ) ;
121
+ }
122
+ if ( trace . boxpoints ) {
123
+ attrs . push ( 'lf' , 'uf' ) ;
124
+ prefixes . push ( 'lower fence' , 'upper fence' ) ;
130
125
}
131
126
132
- if ( hoveron . indexOf ( 'points' ) !== - 1 ) {
133
- var xPx = xa . c2p ( xval ) ;
134
- var yPx = ya . c2p ( yval ) ;
135
-
136
- dx = function ( di ) {
137
- var rad = Math . max ( 3 , di . mrc || 0 ) ;
138
- return Math . max ( Math . abs ( xa . c2p ( di . x ) - xPx ) - rad , 1 - 3 / rad ) ;
139
- } ;
140
- dy = function ( di ) {
141
- var rad = Math . max ( 3 , di . mrc || 0 ) ;
142
- return Math . max ( Math . abs ( ya . c2p ( di . y ) - yPx ) - rad , 1 - 3 / rad ) ;
143
- } ;
144
- distfn = Fx . quadrature ( dx , dy ) ;
145
-
146
- // show one point per trace
147
- var ijClosest = false ;
148
- var pt ;
149
-
150
- for ( i = 0 ; i < cd . length ; i ++ ) {
151
- di = cd [ i ] ;
152
-
153
- for ( j = 0 ; j < ( di . pts || [ ] ) . length ; j ++ ) {
154
- pt = di . pts [ j ] ;
155
-
156
- var newDistance = distfn ( pt ) ;
157
- if ( newDistance <= pointData . distance ) {
158
- pointData . distance = newDistance ;
159
- ijClosest = [ i , j ] ;
160
- }
161
- }
162
- }
127
+ for ( var i = 0 ; i < attrs . length ; i ++ ) {
128
+ var attr = attrs [ i ] ;
129
+
130
+ if ( ! ( attr in di ) || ( di [ attr ] in usedVals ) ) continue ;
131
+ usedVals [ di [ attr ] ] = true ;
132
+
133
+ // copy out to a new object for each value to label
134
+ var val = di [ attr ] ;
135
+ var valPx = vAxis . c2p ( val , true ) ;
136
+ var pointData2 = Lib . extendFlat ( { } , pointData ) ;
137
+
138
+ pointData2 [ vLetter + '0' ] = pointData2 [ vLetter + '1' ] = valPx ;
139
+ pointData2 [ vLetter + 'LabelVal' ] = val ;
140
+ pointData2 [ vLetter + 'Label' ] = prefixes [ i ] + ': ' + Axes . hoverLabelText ( vAxis , val ) ;
163
141
164
- if ( ijClosest ) {
165
- di = cd [ ijClosest [ 0 ] ] ;
166
- pt = di . pts [ ijClosest [ 1 ] ] ;
167
-
168
- var xc = xa . c2p ( pt . x , true ) ;
169
- var yc = ya . c2p ( pt . y , true ) ;
170
- var rad = pt . mrc || 1 ;
171
-
172
- closePtData = Lib . extendFlat ( { } , pointData , {
173
- // corresponds to index in x/y input data array
174
- index : pt . i ,
175
- color : marker . color ,
176
- name : trace . name ,
177
- x0 : xc - rad ,
178
- x1 : xc + rad ,
179
- xLabelVal : pt . x ,
180
- y0 : yc - rad ,
181
- y1 : yc + rad ,
182
- yLabelVal : pt . y
183
- } ) ;
184
- fillHoverText ( pt , trace , closePtData ) ;
142
+ if ( attr === 'mean' && ( 'sd' in di ) && trace . boxmean === 'sd' ) {
143
+ pointData2 [ vLetter + 'err' ] = di . sd ;
185
144
}
186
- }
145
+ // only keep name on the first item (median)
146
+ pointData . name = '' ;
187
147
188
- // In closest mode, show only one point or stats for one box, and points have priority
189
- // If there's a point in range and hoveron has points, show the best single point only.
190
- // If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
191
- if ( hovermode === 'closest' ) {
192
- if ( closePtData ) return [ closePtData ] ;
193
- return closeBoxData ;
148
+ closeBoxData . push ( pointData2 ) ;
194
149
}
195
150
196
- // Otherwise in compare mode, allow a point AND the box stats to be labeled
197
- // If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
198
- if ( closePtData ) {
199
- closeBoxData . push ( closePtData ) ;
200
- return closeBoxData ;
201
- }
202
151
return closeBoxData ;
152
+ }
153
+
154
+ function hoverOnPoints ( pointData , xval , yval ) {
155
+ var cd = pointData . cd ;
156
+ var xa = pointData . xa ;
157
+ var ya = pointData . ya ;
158
+ var trace = cd [ 0 ] . trace ;
159
+ var xPx = xa . c2p ( xval ) ;
160
+ var yPx = ya . c2p ( yval ) ;
161
+ var closePtData ;
162
+
163
+ var dx = function ( di ) {
164
+ var rad = Math . max ( 3 , di . mrc || 0 ) ;
165
+ return Math . max ( Math . abs ( xa . c2p ( di . x ) - xPx ) - rad , 1 - 3 / rad ) ;
166
+ } ;
167
+ var dy = function ( di ) {
168
+ var rad = Math . max ( 3 , di . mrc || 0 ) ;
169
+ return Math . max ( Math . abs ( ya . c2p ( di . y ) - yPx ) - rad , 1 - 3 / rad ) ;
170
+ } ;
171
+ var distfn = Fx . quadrature ( dx , dy ) ;
172
+
173
+ // show one point per trace
174
+ var ijClosest = false ;
175
+ var di , pt ;
176
+
177
+ for ( var i = 0 ; i < cd . length ; i ++ ) {
178
+ di = cd [ i ] ;
179
+
180
+ for ( var j = 0 ; j < ( di . pts || [ ] ) . length ; j ++ ) {
181
+ pt = di . pts [ j ] ;
182
+
183
+ var newDistance = distfn ( pt ) ;
184
+ if ( newDistance <= pointData . distance ) {
185
+ pointData . distance = newDistance ;
186
+ ijClosest = [ i , j ] ;
187
+ }
188
+ }
189
+ }
190
+
191
+ if ( ! ijClosest ) return false ;
192
+
193
+ di = cd [ ijClosest [ 0 ] ] ;
194
+ pt = di . pts [ ijClosest [ 1 ] ] ;
195
+
196
+ var xc = xa . c2p ( pt . x , true ) ;
197
+ var yc = ya . c2p ( pt . y , true ) ;
198
+ var rad = pt . mrc || 1 ;
199
+
200
+ closePtData = Lib . extendFlat ( { } , pointData , {
201
+ // corresponds to index in x/y input data array
202
+ index : pt . i ,
203
+ color : ( trace . marker || { } ) . color ,
204
+ name : trace . name ,
205
+ x0 : xc - rad ,
206
+ x1 : xc + rad ,
207
+ xLabelVal : pt . x ,
208
+ y0 : yc - rad ,
209
+ y1 : yc + rad ,
210
+ yLabelVal : pt . y
211
+ } ) ;
212
+ fillHoverText ( pt , trace , closePtData ) ;
213
+
214
+ return closePtData ;
215
+ }
216
+
217
+ module . exports = {
218
+ hoverPoints : hoverPoints ,
219
+ hoverOnBoxes : hoverOnBoxes ,
220
+ hoverOnPoints : hoverOnPoints
203
221
} ;
0 commit comments