@@ -1302,28 +1302,47 @@ class Profile {
13021302 '<strong>Progress Speedup:</strong> ' + percentFormat ( d . progress_speedup ) ;
13031303 } ) ;
13041304
1305- // Choose a tooltip direction from the point's actual screen position so it
1306- // is never clipped by the top edge of the viewport (see issue #282). The
1307- // static .direction() callback only sees the datum, so we compute it here
1308- // in the mouseover handler where `this` is the hovered circle.
1309- let tip_direction = function ( node : Element , d : any ) {
1310- let rect = node . getBoundingClientRect ( ) ;
1311- // Roughly the height of the 3-line tooltip plus padding/arrow.
1312- let tip_margin = 80 ;
1313- if ( rect . top < tip_margin ) {
1314- // Near the top: drop the tooltip below the point instead of above.
1315- return 's' ;
1316- } else if ( d . speedup > 0.8 ) {
1317- // Near the right edge: place the tooltip to the left.
1318- return 'w' ;
1319- }
1305+ // Choose a tooltip direction from the point's position so it is not clipped
1306+ // by the edges of the viewport (see issue #282). The static .direction()
1307+ // callback only sees the datum, so we compute it in the mouseover handler
1308+ // where `this` is the hovered circle. This is only an initial guess; after
1309+ // the tooltip is rendered we measure its real box and flip it if it still
1310+ // overflows the top (see show_tip below), which avoids guessing its height.
1311+ let tip_direction = function ( d : any ) {
1312+ // Near the right edge: place the tooltip to the left so it isn't clipped.
1313+ if ( d . speedup > 0.8 ) return 'w' ;
13201314 return 'n' ;
13211315 } ;
13221316 let tip_offset = function ( dir : string ) {
13231317 if ( dir === 's' ) return [ 5 , 0 ] ;
13241318 if ( dir === 'w' ) return [ 0 , - 5 ] ;
13251319 return [ - 5 , 0 ] ;
13261320 } ;
1321+ // The currently-visible tip node (d3-tip appends one div per tip instance;
1322+ // pick the one that is actually shown rather than any stale leftovers).
1323+ let active_tip_node = function ( ) : HTMLElement | null {
1324+ let nodes = document . querySelectorAll ( '.d3-tip' ) ;
1325+ for ( let i = nodes . length - 1 ; i >= 0 ; i -- ) {
1326+ let el = nodes [ i ] as HTMLElement ;
1327+ if ( el . style . opacity === '1' ) return el ;
1328+ }
1329+ return null ;
1330+ } ;
1331+ // Show the tooltip, then correct for any remaining top-edge clipping by
1332+ // measuring the rendered tooltip (its real height) and flipping it below
1333+ // the point when the 'north' placement overflows the top of the viewport.
1334+ let show_tip = function ( d : any , target : Element ) {
1335+ let dir = tip_direction ( d ) ;
1336+ tip . direction ( dir ) . offset ( tip_offset ( dir ) ) ;
1337+ tip . show ( d , target ) ;
1338+ if ( dir === 'n' ) {
1339+ let node = active_tip_node ( ) ;
1340+ if ( node && node . getBoundingClientRect ( ) . top < 0 ) {
1341+ tip . direction ( 's' ) . offset ( tip_offset ( 's' ) ) ;
1342+ tip . show ( d , target ) ;
1343+ }
1344+ }
1345+ } ;
13271346
13281347 /****** Add or update divs to hold each plot ******/
13291348 let plot_div_sel = container . selectAll ( 'div.plot' )
@@ -1715,9 +1734,7 @@ class Profile {
17151734 . attr ( 'cy' , function ( d ) { return yscale ( d . progress_speedup ) ; } )
17161735 . on ( 'mouseover' , function ( d ) {
17171736 d3 . select ( this ) . classed ( 'highlight' , true ) ;
1718- let dir = tip_direction ( this , d ) ;
1719- tip . direction ( dir ) . offset ( tip_offset ( dir ) ) ;
1720- tip . show ( d , this ) ;
1737+ show_tip ( d , this ) ;
17211738 } )
17221739 . on ( 'mouseout' , function ( ) {
17231740 d3 . select ( this ) . classed ( 'highlight' , false ) ;
0 commit comments