@@ -119,64 +119,115 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
119119 }
120120 }
121121
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 ;
125149 var polygonsIn = [ ] ;
126- var inside = false ;
127150 var xmin = Infinity ;
128151 var xmax = - Infinity ;
129152 var ymin = Infinity ;
130153 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 ;
133157
134158 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 ) ;
139164 if ( polygon . contains ( pt ) ) {
140- inside = ! inside ;
141- // TODO: need better than just the overall bounding box
142165 polygonsIn . push ( polygon ) ;
143166 ymin = Math . min ( ymin , polygon . ymin ) ;
144167 ymax = Math . max ( ymax , polygon . ymax ) ;
145168 }
146169 }
147170
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 ) ;
173208 }
174209 }
175210 }
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+ }
176224
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 ) ;
180231
181232 // get only fill or line color for the hover color
182233 var color = Color . defaultLine ;
@@ -189,10 +240,10 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
189240 // never let a 2D override 1D type as closest point
190241 // also: no spikeDistance, it's not allowed for fills
191242 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 ,
196247 color : color ,
197248 hovertemplate : false
198249 } ) ;
0 commit comments