@@ -119,64 +119,115 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
119
119
}
120
120
}
121
121
122
- // even if hoveron is 'fills', only use it if we have polygons too
123
- if ( hoveron . indexOf ( 'fills' ) !== - 1 && trace . _polygons && trace . _polygons . length > 0 ) {
124
- var polygons = trace . _polygons ;
122
+ function isHoverPointInFillElement ( el ) {
123
+ // Uses SVGElement.isPointInFill to accurately determine wether
124
+ // the hover point / cursor is contained in the fill, taking
125
+ // curved or jagged edges into account, which the Polygon-based
126
+ // approach does not.
127
+ if ( ! el ) {
128
+ return false ;
129
+ }
130
+ var svgElement = el . node ( ) ;
131
+ try {
132
+ var domPoint = new DOMPoint ( pt [ 0 ] , pt [ 1 ] ) ;
133
+ return svgElement . isPointInFill ( domPoint ) ;
134
+ } catch ( TypeError ) {
135
+ var svgPoint = svgElement . ownerSVGElement . createSVGPoint ( ) ;
136
+ svgPoint . x = pt [ 0 ] ;
137
+ svgPoint . y = pt [ 1 ] ;
138
+ return svgElement . isPointInFill ( svgPoint ) ;
139
+ }
140
+ }
141
+
142
+ function getHoverLabelPosition ( polygons ) {
143
+ // Uses Polygon s to determine the left- and right-most x-coordinates
144
+ // of the subshape of the fill that contains the hover point / cursor.
145
+ // Doing this with the SVGElement directly is quite tricky, so this falls
146
+ // back to the existing relatively simple code, accepting some small inaccuracies
147
+ // of label positioning for curved/jagged edges.
148
+ var i ;
125
149
var polygonsIn = [ ] ;
126
- var inside = false ;
127
150
var xmin = Infinity ;
128
151
var xmax = - Infinity ;
129
152
var ymin = Infinity ;
130
153
var ymax = - Infinity ;
131
-
132
- var i , j , polygon , pts , xCross , x0 , x1 , y0 , y1 ;
154
+ var yminAll = Infinity ;
155
+ var ymaxAll = - Infinity ;
156
+ var yPos ;
133
157
134
158
for ( i = 0 ; i < polygons . length ; i ++ ) {
135
- polygon = polygons [ i ] ;
136
- // TODO: this is not going to work right for curved edges, it will
137
- // act as though they're straight. That's probably going to need
138
- // the elements themselves to capture the events. Worth it?
159
+ var polygon = polygons [ i ] ;
160
+ // This is not going to work right for curved or jagged edges, it will
161
+ // act as though they're straight.
162
+ yminAll = Math . min ( yminAll , polygon . ymin ) ;
163
+ ymaxAll = Math . max ( ymaxAll , polygon . ymax ) ;
139
164
if ( polygon . contains ( pt ) ) {
140
- inside = ! inside ;
141
- // TODO: need better than just the overall bounding box
142
165
polygonsIn . push ( polygon ) ;
143
166
ymin = Math . min ( ymin , polygon . ymin ) ;
144
167
ymax = Math . max ( ymax , polygon . ymax ) ;
145
168
}
146
169
}
147
170
148
- if ( inside ) {
149
- // constrain ymin/max to the visible plot, so the label goes
150
- // at the middle of the piece you can see
151
- ymin = Math . max ( ymin , 0 ) ;
152
- ymax = Math . min ( ymax , ya . _length ) ;
153
-
154
- // find the overall left-most and right-most points of the
155
- // polygon(s) we're inside at their combined vertical midpoint.
156
- // This is where we will draw the hover label.
157
- // Note that this might not be the vertical midpoint of the
158
- // whole trace, if it's disjoint.
159
- var yAvg = ( ymin + ymax ) / 2 ;
160
- for ( i = 0 ; i < polygonsIn . length ; i ++ ) {
161
- pts = polygonsIn [ i ] . pts ;
162
- for ( j = 1 ; j < pts . length ; j ++ ) {
163
- y0 = pts [ j - 1 ] [ 1 ] ;
164
- y1 = pts [ j ] [ 1 ] ;
165
- if ( ( y0 > yAvg ) !== ( y1 >= yAvg ) ) {
166
- x0 = pts [ j - 1 ] [ 0 ] ;
167
- x1 = pts [ j ] [ 0 ] ;
168
- if ( y1 - y0 ) {
169
- xCross = x0 + ( x1 - x0 ) * ( yAvg - y0 ) / ( y1 - y0 ) ;
170
- xmin = Math . min ( xmin , xCross ) ;
171
- xmax = Math . max ( xmax , xCross ) ;
172
- }
171
+ // The above found no polygon that contains the cursor, but we know that
172
+ // the cursor must be inside the fill as determined by the SVGElement
173
+ // (so we are probably close to a curved/jagged edge...). In this case
174
+ // as a crude approximation, simply consider all polygons for determination
175
+ // of the hover label position.
176
+ // TODO: This might cause some jumpiness of the label close to edges...
177
+ if ( polygonsIn . length === 0 ) {
178
+ polygonsIn = polygons ;
179
+ ymin = yminAll ;
180
+ ymax = ymaxAll ;
181
+ }
182
+
183
+ // constrain ymin/max to the visible plot, so the label goes
184
+ // at the middle of the piece you can see
185
+ ymin = Math . max ( ymin , 0 ) ;
186
+ ymax = Math . min ( ymax , ya . _length ) ;
187
+
188
+ yPos = ( ymin + ymax ) / 2 ;
189
+
190
+ // find the overall left-most and right-most points of the
191
+ // polygon(s) we're inside at their combined vertical midpoint.
192
+ // This is where we will draw the hover label.
193
+ // Note that this might not be the vertical midpoint of the
194
+ // whole trace, if it's disjoint.
195
+ var j , pts , xAtYPos , x0 , x1 , y0 , y1 ;
196
+ for ( i = 0 ; i < polygonsIn . length ; i ++ ) {
197
+ pts = polygonsIn [ i ] . pts ;
198
+ for ( j = 1 ; j < pts . length ; j ++ ) {
199
+ y0 = pts [ j - 1 ] [ 1 ] ;
200
+ y1 = pts [ j ] [ 1 ] ;
201
+ if ( ( y0 > yPos ) !== ( y1 >= yPos ) ) {
202
+ x0 = pts [ j - 1 ] [ 0 ] ;
203
+ x1 = pts [ j ] [ 0 ] ;
204
+ if ( y1 - y0 ) {
205
+ xAtYPos = x0 + ( x1 - x0 ) * ( yPos - y0 ) / ( y1 - y0 ) ;
206
+ xmin = Math . min ( xmin , xAtYPos ) ;
207
+ xmax = Math . max ( xmax , xAtYPos ) ;
173
208
}
174
209
}
175
210
}
211
+ }
212
+
213
+ // constrain xmin/max to the visible plot now too
214
+ xmin = Math . max ( xmin , 0 ) ;
215
+ xmax = Math . min ( xmax , xa . _length ) ;
216
+
217
+ return {
218
+ x0 : xmin ,
219
+ x1 : xmax ,
220
+ y0 : yPos ,
221
+ y1 : yPos ,
222
+ } ;
223
+ }
176
224
177
- // constrain xmin/max to the visible plot now too
178
- xmin = Math . max ( xmin , 0 ) ;
179
- xmax = Math . min ( xmax , xa . _length ) ;
225
+ // even if hoveron is 'fills', only use it if we have a fill element too
226
+ if ( hoveron . indexOf ( 'fills' ) !== - 1 && trace . _fillElement ) {
227
+ var inside = isHoverPointInFillElement ( trace . _fillElement ) && ! isHoverPointInFillElement ( trace . _fillExclusionElement ) ;
228
+
229
+ if ( inside ) {
230
+ var hoverLabelCoords = getHoverLabelPosition ( trace . _polygons ) ;
180
231
181
232
// get only fill or line color for the hover color
182
233
var color = Color . defaultLine ;
@@ -189,10 +240,10 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
189
240
// never let a 2D override 1D type as closest point
190
241
// also: no spikeDistance, it's not allowed for fills
191
242
distance : pointData . maxHoverDistance ,
192
- x0 : xmin ,
193
- x1 : xmax ,
194
- y0 : yAvg ,
195
- y1 : yAvg ,
243
+ x0 : hoverLabelCoords . x0 ,
244
+ x1 : hoverLabelCoords . x1 ,
245
+ y0 : hoverLabelCoords . y0 ,
246
+ y1 : hoverLabelCoords . y1 ,
196
247
color : color ,
197
248
hovertemplate : false
198
249
} ) ;
0 commit comments