From a87b282e205f7faec22fe6285a3ec4b386ee9e63 Mon Sep 17 00:00:00 2001 From: Jair Meza Date: Mon, 13 Jan 2025 10:45:19 -0500 Subject: [PATCH] Keep tabs mounted (#544) * Keep tabs mounted Keep tabs mounted so that their cached data does not get removed when switching between tabs. This was don by wrapping components in a div and adding a style which changes the display type to block or non depoending on if it is open. * Created TabPanel function * Fix bar plot issues * Fixed cCRE tabs not opening Fixed an issue where clicking a cCRE in the browser would not open a new tab, but overwrite a previous one. * rm unused variable --------- Co-authored-by: jpfisher72 --- screen2.0/package.json | 2 +- screen2.0/src/app/_barPlot/BarPlot.tsx | 31 ++- .../gene-expression/geneexpression.tsx | 16 +- .../src/app/search/_newgbview/browser.tsx | 49 ++--- screen2.0/src/app/search/page.tsx | 205 +++++++++++------- screen2.0/yarn.lock | 8 +- 6 files changed, 182 insertions(+), 129 deletions(-) diff --git a/screen2.0/package.json b/screen2.0/package.json index 3f2cb0dc..3b86a72d 100644 --- a/screen2.0/package.json +++ b/screen2.0/package.json @@ -62,4 +62,4 @@ "typescript-eslint": "^8.18.0" }, "packageManager": "yarn@4.5.3" -} \ No newline at end of file +} diff --git a/screen2.0/src/app/_barPlot/BarPlot.tsx b/screen2.0/src/app/_barPlot/BarPlot.tsx index b591cb21..2335bf68 100644 --- a/screen2.0/src/app/_barPlot/BarPlot.tsx +++ b/screen2.0/src/app/_barPlot/BarPlot.tsx @@ -35,8 +35,10 @@ const VerticalBarPlot = ({ onBarClicked, TooltipContents }: BarPlotProps) => { - const [spaceForLabel, setSpaceForLabel] = useState(200) //this needs to be initialized with zero. Will break useEffect if changed + const [spaceForLabel, setSpaceForLabel] = useState(200) const [labelSpaceDecided, setLabelSpaceDecided] = useState(false) + // Unique ID needed to not mix up getElementByID calls if multiple charts are in DOM + const [uniqueID] = useState(topAxisLabel + String(Math.random())) const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip>({}); const requestRef = useRef(null); const tooltipDataRef = useRef<{ top: number; left: number; data: BarData } | null>(null); @@ -48,6 +50,8 @@ const VerticalBarPlot = ({ const Portal = VisxPortal as unknown as React.FC; const TooltipWithBounds = VisxTooltipWithBounds as unknown as React.FC; + const outerSvgRef = useRef(null) + const handleMouseMove = useCallback((event: React.MouseEvent, barData: BarData) => { tooltipDataRef.current = { top: event.pageY, @@ -91,19 +95,21 @@ const VerticalBarPlot = ({ range: [0, Math.max(width - spaceForCategory - spaceForLabel, 0)], }), [data, spaceForLabel, width]) + + //use prop ref or fallback if ref not passed + const containerWidth = SVGref ? SVGref.current?.clientWidth : outerSvgRef.current?.clientWidth + //This feels really dumb but I couldn't figure out a better way to have the labels not overflow sometimes - JF 11/8/24 //Whenever xScale is adjusted, it checks to see if any of the labels overflow the container, and if so //it sets the spaceForLabel to be the amount overflowed. useEffect(() => { - const containerWidth = document.getElementById('outerSVG')?.clientWidth if (!containerWidth) { return } let maxOverflow = 0 let minUnderflow: number = null - // let maxOverflowingPoint: [BarData, { textWidth: number, barWidth: number, totalWidth: number, overflow: number }] data.forEach((d, i) => { - const textElement = document.getElementById(`label-${i}`) as unknown as SVGSVGElement; + const textElement = document.getElementById(`label-${i}-${uniqueID}`) as unknown as SVGSVGElement; if (textElement) { const textWidth = textElement.getBBox().width; @@ -137,14 +143,25 @@ const VerticalBarPlot = ({ setLabelSpaceDecided(true) } - }, [data, xScale]); + }, [data, xScale, spaceForLabel, labelSpaceDecided, SVGref, containerWidth, topAxisLabel, uniqueID]); return (
{data.length === 0 ?

No Data To Display

: - 0) ? 1 : 0.3} id={'outerSVG'}> + { + if (SVGref) { + SVGref.current = node; + } + outerSvgRef.current = node; + }} + width={width} + height={totalHeight} + opacity={(labelSpaceDecided && ParentWidth > 0) ? 1 : 0.3} + > {/* Top Axis with Label */} @@ -185,7 +202,7 @@ const VerticalBarPlot = ({ /> {/* Value label next to the bar */} : dataExperiments ? - // - window.open("https://www.encodeproject.org/experiments/" + x.metadata.accession, "_blank", "noopener,noreferrer")} - TooltipContents={(bar) => } - /> - // + window.open("https://www.encodeproject.org/experiments/" + x.metadata.accession, "_blank", "noopener,noreferrer")} + TooltipContents={(bar) => } + /> : Please Select a Gene diff --git a/screen2.0/src/app/search/_newgbview/browser.tsx b/screen2.0/src/app/search/_newgbview/browser.tsx index ff4cbcf3..20a08d4e 100644 --- a/screen2.0/src/app/search/_newgbview/browser.tsx +++ b/screen2.0/src/app/search/_newgbview/browser.tsx @@ -75,39 +75,36 @@ export const Browser = ({ cCREClick, state, dispatch, coordinates, gene, biosamp const bedMouseOut = () => { dispatch({ type: BrowserActionType.REMOVE_LAST_HIGHLIGHT }) } - const bedClick = (item: Rect) => { + const bedClick = useMemo(() => (item: Rect) => { dispatch({ type: BrowserActionType.REMOVE_LAST_HIGHLIGHT }) const { left, right } = linearScale(state.domain, item.start, item.end) const ccre = { start: left, end: right, color: item.color, name: item.name, score: item.score, chromosome: state.domain.chromosome } cCREClick(ccre) - } - const initialLoad = useRef(true) + }, [state.domain, state.highlights, cCREClick]) + useEffect(() => { - if (initialLoad.current) { - const bigbeds = state.tracks.filter(track => track.trackType === TrackType.BIGBED) - bigbeds.forEach(track => { - dispatch({ - type: BrowserActionType.UPDATE_TRACK, id: track.id, track: { - ...track, - onMouseOut: bedMouseOut, - onMouseOver: bedMouseOver, - onClick: bedClick, - tooltipContent: (item: Rect) => CCRETooltip({ biosample, assembly: coordinates.assembly, name: item.name }) - } - }) + const bigbeds = state.tracks.filter(track => track.trackType === TrackType.BIGBED) + bigbeds.forEach(track => { + dispatch({ + type: BrowserActionType.UPDATE_TRACK, id: track.id, track: { + ...track, + onMouseOut: bedMouseOut, + onMouseOver: bedMouseOver, + onClick: bedClick, + tooltipContent: (item: Rect) => CCRETooltip({ biosample, assembly: coordinates.assembly, name: item.name }) + } }) - const transcripts = state.tracks.filter(track => track.trackType === TrackType.TRANSCRIPT) - transcripts.forEach(track => { - dispatch({ - type: BrowserActionType.UPDATE_PROPS, id: track.id, - props: { - geneName: gene - } - }) + }) + const transcripts = state.tracks.filter(track => track.trackType === TrackType.TRANSCRIPT) + transcripts.forEach(track => { + dispatch({ + type: BrowserActionType.UPDATE_PROPS, id: track.id, + props: { + geneName: gene + } }) - initialLoad.current = false - } - }, []) + }) + }, [cCREClick]) const [loadBiosample, { loading: bloading, data: bdata }] = useLazyQuery(BIOSAMPLE_QUERY, { fetchPolicy: "cache-and-network", diff --git a/screen2.0/src/app/search/page.tsx b/screen2.0/src/app/search/page.tsx index 9cbe57c8..aa3fd296 100644 --- a/screen2.0/src/app/search/page.tsx +++ b/screen2.0/src/app/search/page.tsx @@ -3,7 +3,7 @@ import { BIOSAMPLE_Data, biosampleQuery } from "../../common/lib/queries" import { FilterCriteria, MainQueryData, MainQueryParams, RegistryBiosample } from "./types" import { constructFilterCriteriaFromURL, constructMainQueryParamsFromURL, constructSearchURL, downloadBED, fetchcCREData } from "./searchhelpers" -import React, { useEffect, useMemo, useRef, useState } from "react" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import { styled } from '@mui/material/styles'; import { Divider, IconButton, Tab, Tabs, Typography, Box, Button, CircularProgressProps, CircularProgress, Stack } from "@mui/material" import { MainResultsTable } from "./mainresultstable" @@ -54,6 +54,14 @@ const StyledVerticalTab = styled(Tab)(() => ({ minWidth: 'auto' })) +function TabPanel({ page, value, children }: { page: number, value: number, children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} + //Wrapper for the table const Main = styled('div', { shouldForwardProp: (prop) => prop !== 'open' })<{ open?: boolean; @@ -256,6 +264,7 @@ export default function Search({ searchParams }: { searchParams: { [key: string] /** * Backwards compatibility for ENCODE * This is for sure going to be buggy, need to test many edge cases + * @todo move this to it's own file. Taking up way too much space */ useEffect(() => { const fetchCoordinates = async () => { @@ -503,8 +512,12 @@ export default function Search({ searchParams }: { searchParams: { [key: string] const handleDrawerOpen = () => { setOpen(true) } const handleDrawerClose = () => { setOpen(false) } + const findTabByID = useCallback((id: string, numberOfTable: number = 2) => { + return (opencCREs.findIndex((x) => x.ID === id) + numberOfTable) + }, [opencCREs]) + //Handle opening a cCRE or navigating to its open tab - const handlecCREClick = (item) => { + const handlecCREClick = useCallback((item) => { const newcCRE = { ID: item.name || item.accession, region: { start: item.start, end: item.end, chrom: item.chromosome } } const color = item.color || GROUP_COLOR_MAP.get(item.class).split(":")[1] || "#8c8c8c" browserDispatch({ type: BrowserActionType.ADD_HIGHLIGHT, highlight: { domain: { chromosome: item.chromosome, start: item.start, end: item.end }, color, id: item.name || item.accession } }) @@ -515,7 +528,8 @@ export default function Search({ searchParams }: { searchParams: { [key: string] } else { setPage(findTabByID(newcCRE.ID, numberOfDefaultTabs)) } - } + }, [browserDispatch, opencCREs, numberOfDefaultTabs, findTabByID]) + //Handle closing cCRE, and changing page if needed const handleClosecCRE = (closedID: string) => { browserDispatch({ type: BrowserActionType.REMOVE_HIGHLIGHT, id: closedID }) @@ -717,10 +731,6 @@ export default function Search({ searchParams }: { searchParams: { [key: string] } }, [mainQueryData, dataLinkedGenes, filterCriteria, mainQueryParams.gene.nearTSS, TSSranges]) - const findTabByID = (id: string, numberOfTable: number = 2) => { - return (opencCREs.findIndex((x) => x.ID === id) + numberOfTable) - } - /** * @todo Make this (and other download tool) properly download new linked genes */ @@ -890,7 +900,6 @@ export default function Search({ searchParams }: { searchParams: { [key: string] setTSSranges={setTSSranges} genomeBrowserView={page === 1} useLinkedGenes={useLinkedGenes} - /> : {/* Bumps content below app bar */} - {page === 0 && ( - - {loadingTable || loadingFetch || !haveCoordinates ? - - : - <> + {/* Table View */} + + + {loadingTable || loadingFetch || !haveCoordinates ? ( + + ) : ( + <> + + + + {bedLoadingPercent !== null && } + + + )} + + + + {/* Genome Browser View */} + + {haveCoordinates && ( + + )} + + + {/* Gene Expression */} + + {mainQueryParams.gene.name && haveCoordinates && ( + - - - {bedLoadingPercent !== null && } - - - - } - - )} - {page === 1 && haveCoordinates && ( - - )} - {page === 2 && mainQueryParams.gene.name && haveCoordinates && - - } - {page === 3 && mainQueryParams.gene.name && haveCoordinates && mainQueryParams.coordinates.assembly.toLowerCase() !== "mm10" && ( - - )} - {page >= numberOfDefaultTabs && opencCREs.length > 0 && ( - opencCREs[page - numberOfDefaultTabs] ? - - : - - )} + )} + + + {/* RAMPAGE */} + + {mainQueryParams.gene.name && haveCoordinates && mainQueryParams.coordinates.assembly.toLowerCase() !== "mm10" && ( + + )} + + + {/* cCRE Details */} + {opencCREs.map((ccre, index) => { + return ( + + + + ) + })} +
) -} +} \ No newline at end of file diff --git a/screen2.0/yarn.lock b/screen2.0/yarn.lock index d55d04c0..d7ebcc9b 100644 --- a/screen2.0/yarn.lock +++ b/screen2.0/yarn.lock @@ -2367,10 +2367,10 @@ "@visx/event" "3.12.0" prop-types "^15.6.2" -"@weng-lab/genomebrowser@^0.0.16": - version "0.0.16" - resolved "https://registry.yarnpkg.com/@weng-lab/genomebrowser/-/genomebrowser-0.0.16.tgz#98c9fc26ae1e03431298bf4e7062db3d25b04e69" - integrity sha512-hpBbY0aoYzTrD5uySsz70GDHN2sEcsmRWeTbP+sUNLSCNc8GOAasN8jql/lVD0u3Co4MA36KhfOo+zxmKXWAXQ== +"@weng-lab/genomebrowser@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@weng-lab/genomebrowser/-/genomebrowser-0.0.17.tgz#25ced50736d77a516f9828bb1771187669c8bbd8" + integrity sha512-y78btarkivq9fa5Ex+Wd//LWTWTzx29S9rLogivzBqBR9kVKUGBVc7jiqiLEoj24OBStwCnqxvU2GGxuX/9SQg== dependencies: "@apollo/client" "^3.11.10" bigwig-reader "^1.3.1"