@@ -63,6 +63,7 @@ export type RenderFlamegraphOptions = {
6363 reverse : boolean ;
6464 keepOnlyFound : boolean ;
6565 disableHighlightRender : boolean ;
66+ scrollParent : HTMLElement ;
6667}
6768
6869function makeByH ( coords : Coordinate [ ] ) : Record < H , Set < I > > {
@@ -110,11 +111,13 @@ export class FlamegraphOffseter {
110111 private reverse : boolean ;
111112 levelHeight : number ;
112113 private shouldReverseDiff = false ;
114+ private maxVerticalRow : number | undefined ;
113115
114116 constructor ( rows : ProfileData [ 'rows' ] , options : { reverse : boolean ; levelHeight : number } ) {
115117 this . rows = rows ;
116118 this . reverse = options . reverse ;
117119 this . levelHeight = options . levelHeight ;
120+ this . maxVerticalRow = this . rows . length ;
118121 }
119122 fillFramesWindow ( [ hmax , imax ] : Coordinate ) : FramesWindow {
120123 const res : Record < number , Interval > = [ ] ;
@@ -154,7 +157,7 @@ export class FlamegraphOffseter {
154157 }
155158
156159 calcTopOffset ( h : number ) {
157- return this . reverse ? h * this . levelHeight : ( this . rows . length * this . levelHeight ) - ( h + 1 ) * this . levelHeight ;
160+ return this . reverse ? h * this . levelHeight : ( this . maxVerticalRow * this . levelHeight ) - ( h + 1 ) * this . levelHeight ;
158161 }
159162
160163 createShouldDrawFrame ( h : number ) {
@@ -325,10 +328,13 @@ export class FlamegraphOffseter {
325328 this . widthRatio = ( this . getEvents ( root ) - ( root . omittedEventCount ?? 0 ) ) / canvasWidth ! ;
326329 this . minVisibleEv = minVisibleWidth * this . widthRatio ;
327330
331+ let maxDrawableLayerDepth = 0 ;
332+
328333 for ( let h = 0 ; h < this . rows . length ; h ++ ) {
329334 const shouldDrawFrame = this . createShouldDrawFrame ( h ) ;
330335 const updateOffsets = this . createOffsetKeeper ( h ) ;
331336 const updateFrameWindows = this . createUpdateWindow ( h ) ;
337+ let shouldDrawLayer = false ;
332338 for ( let i = 0 ; i < this . rows [ h ] . length ; i ++ ) {
333339 if ( ! shouldDrawFrame ( i ) ) {
334340 continue ;
@@ -340,13 +346,21 @@ export class FlamegraphOffseter {
340346 }
341347 const isVisible = this . visibleNode ( this . rows [ h ] [ i ] ) ;
342348 updateOffsets ( i , isVisible ) ;
349+ if ( isVisible ) {
350+ shouldDrawLayer = true ;
351+ }
352+ }
343353
354+ if ( shouldDrawLayer ) {
355+ maxDrawableLayerDepth = h ;
344356 }
345357
346358 if ( ! this . framesWindow ?. [ h ] ) {
347359 break ;
348360 }
349361 }
362+ this . maxVerticalRow = maxDrawableLayerDepth ;
363+ return maxDrawableLayerDepth ;
350364 }
351365
352366 findFrame ( frames : FormatNode [ ] , x : number , left = 0 , right = frames . length - 1 ) {
@@ -406,7 +420,7 @@ export class FlamegraphOffseter {
406420
407421
408422 getTopOffset ( offset : number ) {
409- return this . reverse ? offset : ( ( this . rows . length * this . levelHeight ) - offset ) ;
423+ return this . reverse ? offset : ( ( this . maxVerticalRow * this . levelHeight ) - offset ) ;
410424 }
411425
412426 getCoordsByPosition : ( x : number , y : number ) => null | { h : number ; i : number } = ( x , y ) => {
@@ -467,7 +481,7 @@ export const renderFlamegraph: RenderFlamegraphType = (
467481 flamegraphContainer ,
468482 profileData ,
469483 fg ,
470- { getState, setState, theme, isDiff, onFinishRendering, shortenFrameTexts, excludeSearchPattern, searchPattern, reverse, keepOnlyFound, disableHighlightRender } ,
484+ { getState, setState, theme, isDiff, onFinishRendering, shortenFrameTexts, excludeSearchPattern, searchPattern, reverse, keepOnlyFound, disableHighlightRender, scrollParent } ,
471485) => {
472486 const shouldSwapDiff = getState ( 'flameBase' ) === 'diff' ;
473487
@@ -510,14 +524,30 @@ export const renderFlamegraph: RenderFlamegraphType = (
510524 let canvasWidth : number | undefined ;
511525 let canvasHeight : number | undefined ;
512526
527+ function initCanvasVertical ( layersCount : number , shouldPreserveVerticalScroll ?: boolean , scrollableElement : HTMLElement = document . documentElement ) {
528+ // need to keep the same vertical scroll when vertically resizing reversed flamegraph
529+ const prevScroll = scrollableElement . scrollTop ;
530+ const prevHeight = canvas . offsetHeight ;
531+ const prevScrollHeight = getScrollHeight ( scrollableElement ) ;
532+ const prevBottomOffset = prevScrollHeight - prevScroll ;
533+
534+ canvas . style . height = ( layersCount ? layersCount + 1 : profileData . rows . length ) * LEVEL_HEIGHT + 'px' ;
535+ canvasHeight = canvas . offsetHeight ;
536+ canvas . height = canvasHeight * ( devicePixelRatio || 1 ) ;
537+ if ( devicePixelRatio ) { c . scale ( devicePixelRatio , devicePixelRatio ) ; }
538+ if ( shouldPreserveVerticalScroll && prevHeight !== 0 ) {
539+ const scrollHeight = getScrollHeight ( scrollableElement ) ;
540+ const scroll = scrollableElement . scrollTop ;
541+ const newBottomOffset = scrollHeight - scroll ;
542+ const diff = prevBottomOffset - newBottomOffset ;
543+
544+ scrollableElement . scrollBy ( 0 , - diff ) ;
545+ }
546+ }
513547 function initCanvas ( ) {
514- canvas . style . height = profileData . rows . length * LEVEL_HEIGHT + 'px' ;
515548 canvasWidth = canvas . offsetWidth ;
516- canvasHeight = canvas . offsetHeight ;
517549 canvas . style . width = canvasWidth + 'px' ;
518550 canvas . width = canvasWidth * ( devicePixelRatio || 1 ) ;
519- canvas . height = canvasHeight * ( devicePixelRatio || 1 ) ;
520- if ( devicePixelRatio ) { c . scale ( devicePixelRatio , devicePixelRatio ) ; }
521551 }
522552
523553 initCanvas ( ) ;
@@ -870,22 +900,28 @@ export const renderFlamegraph: RenderFlamegraphType = (
870900
871901 const foundCoords = maybeSearch ( searchPattern , excludeSearchPattern ) ;
872902
873- fg . prerenderOffsets ( canvasWidth ! , [ h , pos ] , omittedStacks , foundCoords , shouldSwapDiff ) ;
874- render ( { pattern : searchPattern , excludePattern : excludeSearchPattern } ) ;
903+ {
904+ const layerCount = fg . prerenderOffsets ( canvasWidth ! , [ h , pos ] , omittedStacks , foundCoords , shouldSwapDiff ) ;
905+ initCanvasVertical ( layerCount , ! reverse && ! disableHighlightRender , scrollParent ) ;
906+ }
907+ render ( { pattern : searchPattern } ) ;
875908 if ( ! disableHighlightRender ) {
876909 renderHighlightRect ( h , pos ) ;
877910 }
878911
879912
913+ // maybe ignore vertiacal resizes?
880914 const onResize = ( ) => requestAnimationFrame ( ( ) => {
881915
916+ if ( canvasWidth === canvas . offsetWidth ) { return ; }
882917 const initialH = parseInt ( getState ( 'frameDepth' , '0' ) ) ;
883918 const initialI = parseInt ( getState ( 'framePos' , '0' ) ) ;
884919 //@ts -ignore
885920 canvas . style . width = null ;
886921 initCanvas ( ) ;
887- fg . prerenderOffsets ( canvasWidth ! , [ initialH , initialI ] , omittedStacks , foundCoords , shouldSwapDiff ) ;
888- render ( { pattern : searchPattern , excludePattern : excludeSearchPattern } ) ;
922+ const layerCount = fg . prerenderOffsets ( canvasWidth ! , [ initialH , initialI ] , omittedStacks , foundCoords , shouldSwapDiff ) ;
923+ initCanvasVertical ( layerCount , ! reverse , scrollParent ) ;
924+ render ( { pattern : searchPattern } ) ;
889925 } ) ;
890926 window . addEventListener ( 'resize' , onResize ) ;
891927
@@ -903,3 +939,10 @@ export const renderFlamegraph: RenderFlamegraphType = (
903939 canvas . style . cursor = 'pointer' ;
904940 }
905941} ;
942+ function getScrollHeight ( scrollableElement : HTMLElement ) {
943+ return Math . max (
944+ scrollableElement . scrollHeight ,
945+ scrollableElement . offsetHeight ,
946+ scrollableElement . clientHeight ,
947+ ) ;
948+ }
0 commit comments