From 45946ee14d3811ebd618d4febb12a35a3ab51499 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 16 Jan 2025 17:08:35 -0500 Subject: [PATCH 1/6] -fix: prevent breaking from type number label -style: move clinical panel to new css file -chore: specify better types -fix: recalculate visPanelWidth on demo change -feat: always load clinical info if available -style: update padding to account for 3px padding on visualization -chore: typecast clinicalInfo into boolean for consistency --- src/App.tsx | 26 +- src/css/App.css | 485 +------------------------------- src/css/ClinicalPanel.css | 486 +++++++++++++++++++++++++++++++++ src/data/samples.ts | 4 +- src/ui/ClinicalPanel/index.tsx | 48 +++- 5 files changed, 544 insertions(+), 505 deletions(-) create mode 100644 src/css/ClinicalPanel.css diff --git a/src/App.tsx b/src/App.tsx index 8148a9dd..22b82ac3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -104,7 +104,8 @@ function App(props: RouteComponentProps) { const currentSpec = useRef(); - const [isClinicalPanelOpen, setIsClinicalPanelOpen] = useState(false); + // Clinical Panel will only render in non-minimal mode and if the demo has clinical info + const [isClinicalPanelOpen, setIsClinicalPanelOpen] = useState(true); const CLINICAL_PANEL_WIDTH = isMinimalMode || !demo?.clinicalInfo ? 0 : isClinicalPanelOpen ? 250 : 45; // interactions @@ -145,8 +146,8 @@ function App(props: RouteComponentProps) { } useEffect(() => { - setVisPanelWidth(INIT_VIS_PANEL_WIDTH - (VIS_PADDING.left + VIS_PADDING.right + CLINICAL_PANEL_WIDTH)); - }, [isClinicalPanelOpen]); + setVisPanelWidth(INIT_VIS_PANEL_WIDTH - (VIS_PADDING.left + VIS_PADDING.right + CLINICAL_PANEL_WIDTH + 6)); + }, [demo, isClinicalPanelOpen]); // update demo useEffect(() => { @@ -196,7 +197,7 @@ function App(props: RouteComponentProps) { rightReads.current = []; // Update the appearance of the clinical panel - setIsClinicalPanelOpen(!!demo?.clinicalInfo); + setIsClinicalPanelOpen(!!demo?.clinicalInfo && isClinicalPanelOpen); }, [demo]); useEffect(() => { @@ -564,7 +565,18 @@ function App(props: RouteComponentProps) { /> ); // !! Removed `demo` not to update twice since `drivers` are updated right after a demo update. - }, [ready, xDomain, visPanelWidth, drivers, showOverview, showPutativeDriver, selectedSvId, breakpoints, svReads]); + }, [ + ready, + xDomain, + visPanelWidth, + drivers, + showOverview, + showPutativeDriver, + selectedSvId, + breakpoints, + svReads, + isClinicalPanelOpen + ]); const trackTooltips = useMemo(() => { // calculate the offset by the Genome View @@ -981,7 +993,7 @@ function App(props: RouteComponentProps) { {!isMinimalMode && (
- {!isMinimalMode && demo?.clinicalInfo && ( + {!isMinimalMode && !!demo?.clinicalInfo && ( { return { group: 'default', ...d }}); diff --git a/src/ui/ClinicalPanel/index.tsx b/src/ui/ClinicalPanel/index.tsx index 74f385e4..5a1daf6c 100644 --- a/src/ui/ClinicalPanel/index.tsx +++ b/src/ui/ClinicalPanel/index.tsx @@ -15,12 +15,42 @@ export type DataRowProps = { handleClick?: () => void; }; +export type ClinicalInfo = { + summary: SummaryItem[]; + variants: VariantItem[]; + signatures: SignatureItem[]; +}; + +export type SummaryItem = { + label: string; + value: string; +}; + +export type VariantItem = { + gene: string; + type?: string; // if we know all the possible types, we can narrow down further, i.e., type: "Germline" | "homozygous loss" | ...; + cDNA?: string; + chr: string; + start: string | number; + end: string | number; + position: string | number; + handleClick?: () => void; +}; + +export type SignatureItem = { + type: string; // if we know all the possible types, we can narrow down further, i.e., type: "indels" | "point_mutations" | ...; + count: string | number; + label: string; + hrDetect: boolean; +}; + // Data row with label and value const DataRow = ({ handleClick, label, value }: DataRowProps) => { - // Format label to be capitalized - let capitalizedLabel: string; - if (label) { - capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1); + let formattedLabel = label; + + // Format label to be capitalized if string + if (label && typeof label === 'string') { + formattedLabel = label.charAt(0).toUpperCase() + label.slice(1); } return ( @@ -29,7 +59,7 @@ const DataRow = ({ handleClick, label, value }: DataRowProps) => { onClick={handleClick ? () => handleClick() : null} role={handleClick ? 'button' : ''} > - {label ? {capitalizedLabel} : null} + {label ? {formattedLabel} : null} {value} ); @@ -240,7 +270,7 @@ export const ClinicalPanel = ({ setInteractiveMode, setIsClinicalPanelOpen }: ClinicalPanelProps) => { - const [clinicalInformation, setClinicalInformation] = useState(null); + const { clinicalInfo: clinicalInformation } = demo; const handleZoomToGene = (gene: string) => { setInteractiveMode(true); @@ -256,12 +286,6 @@ export const ClinicalPanel = ({ gosRef.current.api.zoomToGene(`${demo.id}-mid-ideogram`, `${gene}`, 1000); }; - useEffect(() => { - if (hasClinicalInfo && demo?.clinicalInfo) { - setClinicalInformation(demo.clinicalInfo); - } - }, [demo]); - return (
Date: Thu, 16 Jan 2025 17:16:17 -0500 Subject: [PATCH 2/6] chore: collapse clinical panel by default --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 22b82ac3..aec06f3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -105,7 +105,7 @@ function App(props: RouteComponentProps) { const currentSpec = useRef(); // Clinical Panel will only render in non-minimal mode and if the demo has clinical info - const [isClinicalPanelOpen, setIsClinicalPanelOpen] = useState(true); + const [isClinicalPanelOpen, setIsClinicalPanelOpen] = useState(false); const CLINICAL_PANEL_WIDTH = isMinimalMode || !demo?.clinicalInfo ? 0 : isClinicalPanelOpen ? 250 : 45; // interactions From 0489da661c4a3cee15c1b13fdcc9b97ec966eadc Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 23 Jan 2025 15:43:06 -0500 Subject: [PATCH 3/6] feat: show cancer field in summary --- src/ui/ClinicalPanel/index.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ui/ClinicalPanel/index.tsx b/src/ui/ClinicalPanel/index.tsx index 5a1daf6c..e5c6c14d 100644 --- a/src/ui/ClinicalPanel/index.tsx +++ b/src/ui/ClinicalPanel/index.tsx @@ -112,11 +112,12 @@ const ToggleRowGroup = ({ callout = null, header, data }: ToggleRowGroupProps) = type PanelSectionProps = { data: DataRowProps[]; + callout?: string; handleZoomToGene?: (gene: string) => void; }; // Panel section for Clinical Summary data -const ClinicalSummary = ({ data }: PanelSectionProps) => { +const ClinicalSummary = ({ data, callout = null }: PanelSectionProps) => { const [isExpanded, setIsExpanded] = useState(true); return ( @@ -131,11 +132,13 @@ const ClinicalSummary = ({ data }: PanelSectionProps) => {
-
-
- Invasive Ductal Carcinoma + {callout && ( +
+
+ {callout} +
-
+ )}
    {data.map((row: DataRowProps, i: number) => { return ; @@ -270,7 +273,15 @@ export const ClinicalPanel = ({ setInteractiveMode, setIsClinicalPanelOpen }: ClinicalPanelProps) => { - const { clinicalInfo: clinicalInformation } = demo; + const { clinicalInfo: clinicalInformation, cancer } = demo; + + // Format cancer label to add as callout + const formattedCancerLabel = cancer + ? cancer + .split(' ') + .map((s: string) => s.charAt(0).toUpperCase() + s.slice(1)) + .join(' ') + : null; const handleZoomToGene = (gene: string) => { setInteractiveMode(true); @@ -309,7 +320,7 @@ export const ClinicalPanel = ({ {hasClinicalInfo && clinicalInformation ? (
    - + Date: Thu, 23 Jan 2025 16:12:42 -0500 Subject: [PATCH 4/6] fix: prevent word breaking --- src/css/ClinicalPanel.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/ClinicalPanel.css b/src/css/ClinicalPanel.css index a103efc5..a0df5b4b 100644 --- a/src/css/ClinicalPanel.css +++ b/src/css/ClinicalPanel.css @@ -170,6 +170,7 @@ .data-value { flex: 2; color: #000000; + word-break: break-all; } } From ff9696733ce5fecc14d03121db5ac158630eb163 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 23 Jan 2025 16:13:51 -0500 Subject: [PATCH 5/6] fix: break label words as well --- src/css/ClinicalPanel.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/ClinicalPanel.css b/src/css/ClinicalPanel.css index a0df5b4b..06a18f5b 100644 --- a/src/css/ClinicalPanel.css +++ b/src/css/ClinicalPanel.css @@ -165,6 +165,7 @@ flex: 3; color: #565961; font-weight: 600; + word-break: break-all; } .data-value { From b6bca80ad3e4ccf95a7039362017f38f612a0848 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 23 Jan 2025 16:16:16 -0500 Subject: [PATCH 6/6] fix: prevent collision in data row --- src/css/ClinicalPanel.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/ClinicalPanel.css b/src/css/ClinicalPanel.css index 06a18f5b..743975ad 100644 --- a/src/css/ClinicalPanel.css +++ b/src/css/ClinicalPanel.css @@ -157,6 +157,7 @@ .data-row { box-sizing: border-box !important; display: flex; + gap: 8px; justify-content: flex-start; padding: 8px 0px; border-bottom: 1px solid #e0e6eb;