From 3aa894d4579d0dce5259944c57777bf3a4e144cf Mon Sep 17 00:00:00 2001 From: Eliot Ragueneau Date: Fri, 27 Mar 2026 12:33:33 +0000 Subject: [PATCH 1/2] :zap: Improve performance and rendering of Reacfoam by removing relaxation visibility and moving to last version --- package.json | 2 +- .../src/app/reacfoam/reacfoam.component.ts | 61 ++++++++----------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index beb98df..49e34c2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "dependencies": { "@angular/animations": "^19.2.20", "@angular/cdk": "^19.2.19", - "@carrotsearch/foamtree": "^3.5.7", "@angular/common": "^19.2.20", "@angular/compiler": "^19.2.20", "@angular/core": "^19.2.20", @@ -35,6 +34,7 @@ "@angular/platform-browser-dynamic": "^19.2.20", "@angular/router": "^19.2.20", "@angular/ssr": "^19.2.20", + "@carrotsearch/foamtree": "^3.6.0-rc1", "@hakimio/ngx-google-analytics": "^15.0.0", "@lottiefiles/dotlottie-web": "^0.49.0", "@ngneat/until-destroy": "^10.0.0", diff --git a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts index 77813bd..57575da 100644 --- a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts +++ b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts @@ -46,7 +46,7 @@ export class ReacfoamComponent implements OnDestroy { stacking: "flattened", relaxationInitializer: "ordered", // Impactful on the sub-groups of TLPs layoutByWeightOrder: false, - relaxationVisible: true, // TODO evaluate if we wanna keep this + relaxationVisible: false, pixelRatio: window.devicePixelRatio || 1, wireframePixelRatio: window.devicePixelRatio || 1, exposeDuration: 500, @@ -93,7 +93,7 @@ export class ReacfoamComponent implements OnDestroy { wireframeToFinalFadeDuration: 0, groupLabelColorThreshold: 0.8, relaxationMaxDuration: 4000, - relaxationQualityThreshold: 0.5, + relaxationQualityThreshold: 10, // Labels groupLabelFontFamily: 'Roboto', @@ -106,12 +106,12 @@ export class ReacfoamComponent implements OnDestroy { // Roll out in groups rolloutMethod: "groups", - onGroupDoubleClick: (event: any) => { + onGroupDoubleClick: (event) => { event.preventDefault(); - this.state.navigateTo(event.group.stId, {queryParamsHandling: 'preserve', preserveFragment: true}) + this.router.navigate([event.group.stId], {queryParamsHandling: 'preserve', preserveFragment: true}) }, - onGroupClick: (event: any) => { + onGroupClick: (event) => { event.preventDefault(); if (!event.secondary) { this.state.select.set(event.group.stId) @@ -124,22 +124,6 @@ export class ReacfoamComponent implements OnDestroy { } }, - // For now, add exposure at end of relaxation, useful upon resizing reset. to be removed when alternative solution found for stable layout - onRelaxationStep: (relaxationProgress: any, relaxationComplete: any, relaxationTimeout: any) => { - this.relaxing.set(true) - this.foamTree().set("groupLabelMinFontSize", 20); - if ((relaxationTimeout || relaxationComplete)) { - this.relaxing.set(false) - this.foamTree().set("groupLabelMinFontSize", 3); - this.foamTree().redraw() - if (this.correctedSelectedId()) { - setTimeout(() => { - this.foamTree().expose({groups: this.correctedSelectedId(), keepPrevious: false}) - }) - } - } - }, - onViewReset: () => { // Reset selection on esc pressed this.state.select.set(null) this.state.path.set([]) @@ -160,18 +144,25 @@ export class ReacfoamComponent implements OnDestroy { relaxing = signal(false) - sizeObserver = new ResizeObserver(() => { - setTimeout(() => { // Avoid white flickering - this.foamTree().set('exposeDuration', 0) // Make removal of exposure instant - this.foamTree().expose({ - groups: undefined, - keepPrevious: false - }).then(() => { - this.foamTree().set('exposeDuration', this.options()['exposeDuration']!) // Put back initial exposure time - this.foamTree().resize() + sizeObserver = new ResizeObserver(throttle(50, () => { + setTimeout(() => { // Avoid white flickering + this.foamTree().set('exposeDuration', 0) // Make removal of exposure instant + this.foamTree().expose({ + groups: undefined, + keepPrevious: false + }).then(() => { + this.foamTree().resize() + if (this.correctedSelectedId()) { + this.foamTree().expose({ + groups: this.correctedSelectedId(), + keepPrevious: false + }) + } + this.foamTree().set('exposeDuration', this.options().exposeDuration!) // Put back initial exposure time + }) }) }) - }); + ); cleanFlagIdentifiers = computed(() => new Set(this.data.flagIdentifiers().filter(id => id.startsWith('R-')))) flagging = computed(() => this.cleanFlagIdentifiers().size !== 0) @@ -236,7 +227,7 @@ export class ReacfoamComponent implements OnDestroy { this.foamTree().set({ groupStrokePlainLightnessShift: this.dark.isDark() ? 70 : -70, groupStrokePlainSaturationShift: 0, - groupColorDecorator: (options: any, props: any, values: any) => { + groupColorDecorator: (options, props, values) => { const depth = props.group.depth; // If child groups of some group doesn't have enough space to // render, draw the parent group in red. @@ -261,10 +252,10 @@ export class ReacfoamComponent implements OnDestroy { values.groupColor = notFoundColor; } else { if (this.analysis.type() === 'OVERREPRESENTATION' || this.analysis.type() === 'SPECIES_COMPARISON') { // FDR ~ color - values.groupColor = (this.analysis.palette().scale(props.group.fdr) as any).hex() + values.groupColor = this.analysis.palette().scale(props.group.fdr).hex() } else { // expression ~ color if (props.group.expressions) { - values.groupColor = (this.analysis.palette().scale(props.group.expressions[this.analysis.sampleIndex()]) as any).hex() + values.groupColor = this.analysis.palette().scale(props.group.expressions[this.analysis.sampleIndex()]).hex() } else { values.groupColor = notFoundColor; } @@ -330,7 +321,7 @@ export class ReacfoamComponent implements OnDestroy { } -function throttle(func: (...args: Args) => void, delay: number): (...args: Args) => void { +function throttle(delay: number, func: (...args: Args) => void): (...args: Args) => void { let lastCall = 0; return (...args: Args) => { const now = new Date().getTime(); From 767c751cc6bfe3b28fe88a61c746d75c01d000e2 Mon Sep 17 00:00:00 2001 From: el-rabies Date: Fri, 27 Mar 2026 11:04:56 -0400 Subject: [PATCH 2/2] fix: state based navigation --- projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts index 57575da..e28aa6f 100644 --- a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts +++ b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts @@ -108,7 +108,7 @@ export class ReacfoamComponent implements OnDestroy { onGroupDoubleClick: (event) => { event.preventDefault(); - this.router.navigate([event.group.stId], {queryParamsHandling: 'preserve', preserveFragment: true}) + this.state.navigateTo(event.group.stId, {queryParamsHandling: 'preserve', preserveFragment: true}) }, onGroupClick: (event) => {