@@ -299,6 +299,16 @@ exports.loneHover = function loneHover(hoverItems, opts) {
299299 return multiHover ? hoverLabel : hoverLabel . node ( ) ;
300300} ;
301301
302+ // Helper function for use with spikeline logic
303+ // Returns true if either axis has spikeline settings which allow a spikeline to be drawn
304+ // to a point that is not currently hovered over; returns false otherwise.
305+ function canSpikeToNonHoveredPoint ( xa , ya ) {
306+ return Boolean (
307+ ( xa ?. showspikes && xa . spikesnap !== 'hovered data' ) ||
308+ ( ya ?. showspikes && ya . spikesnap !== 'hovered data' )
309+ ) ;
310+ }
311+
302312// The actual implementation is here:
303313function _hover ( gd , evt , subplot , noHoverEvent , eventTarget ) {
304314 if ( ! subplot ) subplot = 'xy' ;
@@ -729,53 +739,58 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
729739 }
730740 }
731741
732- // Now if there is range to look in, find the points to draw the spikelines
733- // Do it only if there is no hoverData
734- if ( hasCartesian && spikedistance !== 0 ) {
735- if ( hoverData . length === 0 ) {
736- pointData . distance = spikedistance ;
737- pointData . index = false ;
738- var closestPoints = trace . _module . hoverPoints ( pointData , xval , yval , 'closest' , {
739- hoverLayer : fullLayout . _hoverlayer
742+ // If there is no hoverData, and if the axis spikeline settings permit us to draw
743+ // a spikeline to the nearest non-hovered point, search for points to spike to.
744+ // This search is _expensive_ (especially when spikedistance is -1 which requires
745+ // a search over all points), so we should only do it when absolutely required
746+ if ( hasCartesian
747+ && hoverData . length === 0
748+ && spikedistance !== 0
749+ && canSpikeToNonHoveredPoint ( pointData . xa , pointData . ya )
750+ ) {
751+ // Find the points to draw the spikelines
752+ pointData . distance = spikedistance ;
753+ pointData . index = false ;
754+ var closestPoints = trace . _module . hoverPoints ( pointData , xval , yval , 'closest' , {
755+ hoverLayer : fullLayout . _hoverlayer
756+ } ) ;
757+ if ( closestPoints ) {
758+ closestPoints = closestPoints . filter ( function ( point ) {
759+ // some hover points, like scatter fills, do not allow spikes,
760+ // so will generate a hover point but without a valid spikeDistance
761+ return point . spikeDistance <= spikedistance ;
740762 } ) ;
741- if ( closestPoints ) {
742- closestPoints = closestPoints . filter ( function ( point ) {
743- // some hover points, like scatter fills, do not allow spikes,
744- // so will generate a hover point but without a valid spikeDistance
745- return point . spikeDistance <= spikedistance ;
746- } ) ;
747- }
748- if ( closestPoints && closestPoints . length ) {
749- var tmpPoint ;
750- var closestVPoints = closestPoints . filter ( function ( point ) {
751- return point . xa . showspikes && point . xa . spikesnap !== 'hovered data' ;
752- } ) ;
753- if ( closestVPoints . length ) {
754- var closestVPt = closestVPoints [ 0 ] ;
755- if ( isNumeric ( closestVPt . x0 ) && isNumeric ( closestVPt . y0 ) ) {
756- tmpPoint = fillSpikePoint ( closestVPt ) ;
757- if (
758- ! spikePoints . vLinePoint ||
759- spikePoints . vLinePoint . spikeDistance > tmpPoint . spikeDistance
760- ) {
761- spikePoints . vLinePoint = tmpPoint ;
762- }
763+ }
764+ if ( closestPoints ?. length ) {
765+ var tmpPoint ;
766+ var closestVPoints = closestPoints . filter ( function ( point ) {
767+ return point . xa . showspikes && point . xa . spikesnap !== 'hovered data' ;
768+ } ) ;
769+ if ( closestVPoints . length ) {
770+ var closestVPt = closestVPoints [ 0 ] ;
771+ if ( isNumeric ( closestVPt . x0 ) && isNumeric ( closestVPt . y0 ) ) {
772+ tmpPoint = fillSpikePoint ( closestVPt ) ;
773+ if (
774+ ! spikePoints . vLinePoint ||
775+ spikePoints . vLinePoint . spikeDistance > tmpPoint . spikeDistance
776+ ) {
777+ spikePoints . vLinePoint = tmpPoint ;
763778 }
764779 }
780+ }
765781
766- var closestHPoints = closestPoints . filter ( function ( point ) {
767- return point . ya . showspikes && point . ya . spikesnap !== 'hovered data' ;
768- } ) ;
769- if ( closestHPoints . length ) {
770- var closestHPt = closestHPoints [ 0 ] ;
771- if ( isNumeric ( closestHPt . x0 ) && isNumeric ( closestHPt . y0 ) ) {
772- tmpPoint = fillSpikePoint ( closestHPt ) ;
773- if (
774- ! spikePoints . hLinePoint ||
775- spikePoints . hLinePoint . spikeDistance > tmpPoint . spikeDistance
776- ) {
777- spikePoints . hLinePoint = tmpPoint ;
778- }
782+ var closestHPoints = closestPoints . filter ( function ( point ) {
783+ return point . ya . showspikes && point . ya . spikesnap !== 'hovered data' ;
784+ } ) ;
785+ if ( closestHPoints . length ) {
786+ var closestHPt = closestHPoints [ 0 ] ;
787+ if ( isNumeric ( closestHPt . x0 ) && isNumeric ( closestHPt . y0 ) ) {
788+ tmpPoint = fillSpikePoint ( closestHPt ) ;
789+ if (
790+ ! spikePoints . hLinePoint ||
791+ spikePoints . hLinePoint . spikeDistance > tmpPoint . spikeDistance
792+ ) {
793+ spikePoints . hLinePoint = tmpPoint ;
779794 }
780795 }
781796 }
@@ -1679,12 +1694,12 @@ function createHoverText(hoverData, opts) {
16791694 var mainText = ! unifiedhovertitleText
16801695 ? t0
16811696 : Lib . hovertemplateString ( {
1682- data :
1683- hovermode === 'x unified' ? [ { xa : item0 . xa , x : item0 . xVal } ] : [ { ya : item0 . ya , y : item0 . yVal } ] ,
1684- fallback : item0 . trace . hovertemplatefallback ,
1685- locale : fullLayout . _d3locale ,
1686- template : unifiedhovertitleText
1687- } ) ;
1697+ data :
1698+ hovermode === 'x unified' ? [ { xa : item0 . xa , x : item0 . xVal } ] : [ { ya : item0 . ya , y : item0 . yVal } ] ,
1699+ fallback : item0 . trace . hovertemplatefallback ,
1700+ locale : fullLayout . _d3locale ,
1701+ template : unifiedhovertitleText
1702+ } ) ;
16881703
16891704 var mockLayoutIn = {
16901705 showlegend : true ,
@@ -2694,12 +2709,12 @@ function cleanPoint(d, hovermode) {
26942709
26952710 var getVal = Array . isArray ( index )
26962711 ? function ( calcKey , traceKey ) {
2697- var v = Lib . castOption ( cd0 , index , calcKey ) ;
2698- return pass ( v ) ? v : Lib . extractOption ( { } , trace , '' , traceKey ) ;
2699- }
2712+ var v = Lib . castOption ( cd0 , index , calcKey ) ;
2713+ return pass ( v ) ? v : Lib . extractOption ( { } , trace , '' , traceKey ) ;
2714+ }
27002715 : function ( calcKey , traceKey ) {
2701- return Lib . extractOption ( cd , trace , calcKey , traceKey ) ;
2702- } ;
2716+ return Lib . extractOption ( cd , trace , calcKey , traceKey ) ;
2717+ } ;
27032718
27042719 function fill ( key , calcKey , traceKey ) {
27052720 var val = getVal ( calcKey , traceKey ) ;
0 commit comments