From 5c967b38e480df190094b62fcc4878edb1287f9a Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 19 Dec 2024 17:28:11 -0500 Subject: [PATCH 1/6] feat: clinical panel feat: zoom to gene functionality style: add clinical panel styles --- src/App.tsx | 58 +++- src/css/App.css | 501 ++++++++++++++++++++++++++++++++- src/css/NavigationBar.css | 9 +- src/data/samples.ts | 3 + src/icon.ts | 28 ++ src/ui/ClinicalPanel/index.tsx | 300 ++++++++++++++++++++ src/ui/NavigationBar.tsx | 20 +- src/ui/NavigationButtons.tsx | 5 +- 8 files changed, 885 insertions(+), 39 deletions(-) create mode 100644 src/ui/ClinicalPanel/index.tsx diff --git a/src/App.tsx b/src/App.tsx index 033403a5..8148a9dd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,6 +29,7 @@ import { VariantViewModal } from './ui/VariantViewModal'; import { NavigationButtons } from './ui/NavigationButtons'; import { Track, getTrackDocData } from './ui/getTrackDocData.js'; import { NavigationBar } from './ui/NavigationBar'; +import { ClinicalPanel } from './ui/ClinicalPanel'; import 'bootstrap/dist/css/bootstrap.min.css'; import './css/App.css'; @@ -103,6 +104,9 @@ function App(props: RouteComponentProps) { const currentSpec = useRef(); + const [isClinicalPanelOpen, setIsClinicalPanelOpen] = useState(false); + const CLINICAL_PANEL_WIDTH = isMinimalMode || !demo?.clinicalInfo ? 0 : isClinicalPanelOpen ? 250 : 45; + // interactions const [showSamples, setShowSamples] = useState(urlParams.get('showSamples') !== 'false' && !xDomain); const [showAbout, setShowAbout] = useState(false); @@ -115,7 +119,7 @@ function App(props: RouteComponentProps) { const [showPutativeDriver, setShowPutativeDriver] = useState(true); const [interactiveMode, setInteractiveMode] = useState(isMinimalMode ?? false); const [visPanelWidth, setVisPanelWidth] = useState( - INIT_VIS_PANEL_WIDTH - (isMinimalMode ? 10 : VIS_PADDING.left * 2) + INIT_VIS_PANEL_WIDTH - (isMinimalMode ? 10 : VIS_PADDING.left + VIS_PADDING.right + CLINICAL_PANEL_WIDTH) ); const [overviewChr, setOverviewChr] = useState(''); const [genomeViewChr, setGenomeViewChr] = useState(''); @@ -140,6 +144,10 @@ function App(props: RouteComponentProps) { return (allDrivers as any).filter((d: any) => d.sample_id === demoId && +d.pos); } + useEffect(() => { + setVisPanelWidth(INIT_VIS_PANEL_WIDTH - (VIS_PADDING.left + VIS_PADDING.right + CLINICAL_PANEL_WIDTH)); + }, [isClinicalPanelOpen]); + // update demo useEffect(() => { if (typeof demo.drivers === 'string' && demo.drivers.split('.').pop() === 'json') { @@ -186,6 +194,9 @@ function App(props: RouteComponentProps) { setSelectedSvId(''); leftReads.current = []; rightReads.current = []; + + // Update the appearance of the clinical panel + setIsClinicalPanelOpen(!!demo?.clinicalInfo); }, [demo]); useEffect(() => { @@ -323,13 +334,14 @@ function App(props: RouteComponentProps) { window.addEventListener( 'resize', debounce(() => { - setVisPanelWidth(window.innerWidth - (isMinimalMode ? 10 : VIS_PADDING.left * 2)); + setVisPanelWidth(window.innerWidth - (isMinimalMode ? 10 : VIS_PADDING.left + VIS_PADDING.right)); }, 500) ); - // Lower opacity of legend image as it leaves viewport + // Lower opacity of legend image as it leaves viewport in minimal mode if (isMinimalMode) { const legendElement = document.querySelector('.genome-view-legend'); + const options = { root: document.querySelector('.minimal_mode'), rootMargin: '-250px 0px 0px 0px', @@ -342,6 +354,10 @@ function App(props: RouteComponentProps) { }, options); observer.observe(legendElement); + + return () => { + observer.unobserve(legendElement); + }; } }, []); @@ -740,10 +756,10 @@ function App(props: RouteComponentProps) { const height = window.innerHeight; if (!isMinimalMode) { if ( - VIS_PADDING.top < top && - top < height - VIS_PADDING.top && - VIS_PADDING.left < left && - left < width - VIS_PADDING.left + VIS_PADDING.top < top && // past top margin + top < height - VIS_PADDING.top && // before bottom margin + VIS_PADDING.left < left && // past left margin + left < width - (VIS_PADDING.right + CLINICAL_PANEL_WIDTH) // before right margin ) { setMouseOnVis(true); } else { @@ -939,7 +955,9 @@ function App(props: RouteComponentProps) { id="gosling-panel" className="gosling-panel" style={{ - width: `calc(100% - ${VIS_PADDING.left * 2}px)`, + width: isMinimalMode + ? `calc(100% - ${VIS_PADDING.left + VIS_PADDING.right}px)` + : `calc(100% - ${VIS_PADDING.left + VIS_PADDING.right + CLINICAL_PANEL_WIDTH}px)`, height: `calc(100% - ${VIS_PADDING.top * 2}px)`, padding: `${VIS_PADDING.top}px ${VIS_PADDING.right}px ${VIS_PADDING.bottom}px ${VIS_PADDING.left}px` }} @@ -963,7 +981,7 @@ function App(props: RouteComponentProps) { {!isMinimalMode && (
@@ -1280,13 +1300,13 @@ function App(props: RouteComponentProps) { ? 'visible' : 'collapse', position: 'absolute', - right: `${VIS_PADDING.right}px`, + right: `${VIS_PADDING.right + CLINICAL_PANEL_WIDTH}px`, top: '60px', background: 'lightgray', color: 'black', padding: '6px', pointerEvents: 'none', - zIndex: 9999, + zIndex: 999, boxShadow: '0 0 20px 2px rgba(0, 0, 0, 0.2)' }} > @@ -1302,7 +1322,7 @@ function App(props: RouteComponentProps) { height: '100%', visibility: 'collapse', boxShadow: interactiveMode ? 'inset 0 0 4px 2px #2399DB' : 'none', - zIndex: 9999, + zIndex: 999, background: 'none', position: 'absolute', top: 0, @@ -1441,6 +1461,9 @@ function App(props: RouteComponentProps) { className="move-to-top-btn" tabIndex={showSamples ? -1 : 0} aria-label="Scroll to top." + style={{ + right: isMinimalMode ? '10px' : `${VIS_PADDING.right + CLINICAL_PANEL_WIDTH}px` + }} onClick={() => { setTimeout( () => document.getElementById('gosling-panel')?.scrollTo({ top: 0, behavior: 'smooth' }), @@ -1458,6 +1481,17 @@ function App(props: RouteComponentProps) {
+ {!isMinimalMode && demo?.clinicalInfo && ( + + )}
); diff --git a/src/css/App.css b/src/css/App.css index 254cab7f..c537f7ec 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -44,7 +44,6 @@ a:hover { text-decoration: 3px underline #5bb6ea; } - .vis-panel { .vis-overview-panel { position: absolute; @@ -68,7 +67,6 @@ a:hover { border-bottom: 1px solid lightgray; background: #f6f6f6; - .config-button { svg { vertical-align: unset; @@ -86,7 +84,7 @@ a:hover { .feedback a:hover { background-color: #eaeaea; } - + .title-github-link:active, .title-doc-link:active, .feedback a:active { @@ -129,7 +127,6 @@ a:hover { top: 12px; } - .vis-overview-panel .overview-container { display: flex; flex-wrap: wrap; @@ -234,7 +231,8 @@ a:hover { cursor: pointer; } -.chromoscope-title:focus, .chromoscope-title:focus-visible { +.chromoscope-title:focus, +.chromoscope-title:focus-visible { /* padding: 5px; */ } @@ -638,7 +636,6 @@ a:hover { position: absolute; box-shadow: 0 0 10px 0px #00000032; bottom: 20px; - right: 20px; cursor: pointer; z-index: 998; opacity: 0.5; @@ -830,7 +827,8 @@ a:hover { .track-tooltip:hover { cursor: pointer; } -.track-tooltip:focus, .track-tooltip:focus-visible { +.track-tooltip:focus, +.track-tooltip:focus-visible { outline-offset: 2px; } @@ -899,11 +897,495 @@ a:hover { background-color: #e6e4e4; } +.clinical-panel-container { + box-sizing: border-box !important; + position: fixed; + top: 50px; + right: 0px; + width: 250px; + height: calc(100% - 50px); + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc1JyBoZWlnaHQ9JzUnPgo8cmVjdCB3aWR0aD0nNScgaGVpZ2h0PSc1JyBmaWxsPScjZmZmJy8+CjxyZWN0IHdpZHRoPScxJyBoZWlnaHQ9JzEnIGZpbGw9JyNjY2MnLz4KPC9zdmc+); + background-position-x: -1px; + + .clinical-panel { + box-sizing: border-box !important; + display: flex; + flex-direction: column; + margin-top: 10px; + padding: 10px 10px 20px; + border-radius: 10px 0px 0px 10px; + height: 100%; + background-color: #eef2f6; + border: 1px solid #d7dfe4; + } + + .panel-header { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0px 0px 6px 0px; + border-bottom: 1px solid #cad3da; + margin-bottom: 10px; + + .panel-icon { + color: #434d5e; + height: 20px; + margin: 0px; + } + + h2 { + font-size: 1rem; + font-weight: 600; + color: #2c3648; + margin-bottom: 0px; + } + + .collapse-panel { + box-sizing: border-box !important; + display: flex; + justify-content: center; + width: 20px; + height: 20px; + background-color: #f5f5f5; + border: 1px solid #cad3da; + border-radius: 100%; + padding: 3px; + svg { + margin: auto; + color: #4d565c; + } + } + } + + .content { + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + padding: 0px; + overflow-y: auto; + + .panel-section { + box-sizing: border-box !important; + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + background-color: #ffffff; + /* border: 1px solid #CAD3DA; */ + border-radius: 8px; + overflow: hidden; + margin-bottom: 16px; + + .section-header { + display: flex; + justify-content: space-between; + background-color: #202f48; + padding: 10px; + border: none; + + h3 { + font-weight: 600; + color: white; + font-size: 0.875rem; + margin: 0px; + } + + .section-toggle { + display: flex; + justify-content: center; + padding: 0px; + height: 12px; + width: 12px; + border: none; + background-color: transparent; + margin: auto 0px; + + svg { + margin: auto; + color: white; + } + } + } + .callout { + padding: 4px; + border-bottom: 1px solid #e0e6eb; + + .content { + display: flex; + flex-direction: row; + justify-content: center; + background-color: #e9edf2; + padding: 5px 8px; + border-radius: 5px; + + svg { + margin: auto 8px auto 0px; + color: #464e55; + height: 10px; + width: 10px; + } + span { + color: #464e55; + font-size: 0.625rem; + } + } + } + + .section:focus-visible, + .section:focus { + outline-offset: -1px; + } + .section-body { + display: flex; + flex-direction: column; + flex: 1; + box-sizing: border-box !important; + overflow-y: scroll; + padding: 0px 8px; + font-size: 0.75rem; + border: solid #cad3da; + border-width: 0px 1px 1px 1px; + border-radius: 0px 0px 8px 8px; + + .data-list { + padding: 0px; + margin-bottom: 0px; + + .data-row { + box-sizing: border-box !important; + display: flex; + justify-content: flex-start; + padding: 8px 0px; + border-bottom: 1px solid #e0e6eb; + + .data-label { + flex: 3; + color: #565961; + font-weight: 600; + } + + .data-value { + flex: 2; + color: #000000; + } + } + + .data-row:last-of-type { + border-bottom: none; + } + + .data-row.clickable:hover { + background-color: #d5dce2 !important; + cursor: pointer; + } + } + } + } + + /* Style individual sections */ + .panel-section.summary { + flex: 3; + + .section-body { + .callout { + padding: 5px 0px; + .content { + padding: 4px 12px; + display: flex; + background-color: #ffefdb; + border: 1px solid #f6e4cd; + border-radius: 10px; + + span { + font-size: 0.75rem; + color: #502d07; + margin: auto; + } + } + } + } + } + .panel-section.variants { + flex: 2; + .section-body { + padding: 0px; + + .data-list { + overflow: scroll; + .data-row { + padding: 8px; + } + .data-row:nth-of-type(even) { + background-color: #f5f5f5; + } + } + } + } + .panel-section.signatures { + flex: 4; + + .callout.hrdetect { + display: flex; + justify-content: space-between; + padding: 10px; + border: solid #cad3da; + border-width: 0px 1px; + + .label { + flex: 1; + color: #565961; + font-size: 0.75rem; + font-weight: 500; + } + + .value { + flex: 1; + color: #000000; + font-size: 0.75rem; + font-weight: 500; + + .circle-icon { + width: 8px; + height: 8px; + } + .circle-icon.positive { + color: #1d9819; + } + .circle-icon.negative { + color: lightgray; + } + + span { + margin-left: 5px; + } + } + } + + .section-body { + padding: 0px; + overflow: hidden; + + .data-list { + .data-row:nth-of-type(even) { + background-color: #f5f5f5; + } + + .data-row { + display: inline-block; + width: 100%; + padding: 10px 8px; + + .data-label { + display: inline; + flex: none; + color: #000000; + font-weight: 600; + margin-right: 0.25rem; + } + .data-value { + } + } + } + } + } + + /* Expanded and collapsed */ + .panel-section.expanded { + max-height: fit-content; + .section-header { + .section-toggle { + svg { + transform: rotate(180deg); + } + } + } + } + .panel-section.collapsed { + flex: none; + max-height: none; + .section-body { + flex: none; + padding: 0px; + height: 0px; + border: none; + } + + .callout.hrdetect { + display: none; + } + + .section-header { + .section-toggle { + svg { + transform: none; + } + } + } + } + + /* Styles for toggle-able rows */ + .dropdown-row { + display: flex; + flex-direction: column; + overflow: scroll; + + .header { + box-sizing: border-box !important; + display: flex; + width: 100%; + justify-content: space-between; + background-color: #e0e6eb; + padding: 10px; + border: 1px solid #cad3da; + border-width: 1px 0px; + + h4 { + font-weight: 600; + color: #2c3648; + font-size: 0.75rem; + margin: 0px; + } + + .toggle { + display: flex; + justify-content: center; + padding: 0px; + height: 12px; + width: 12px; + border: none; + background-color: transparent; + margin: auto 0px; + + svg { + margin: auto; + color: #55606b; + } + } + } + .header:hover { + background-color: #d5dce2; + } + + .body { + overflow: scroll; + } + } + .dropdown-row.expanded { + flex: 1; + max-height: fit-content; + + .header { + .toggle { + svg { + transform: rotate(180deg); + } + } + } + .body { + flex: 1; + } + } + .dropdown-row.collapsed { + flex: none; + max-height: none; + + .header { + .toggle { + border-bottom: 0px; + svg { + transform: none; + } + } + } + .callout { + display: none; + } + .body { + height: 0px; + } + } + .dropdown-row.disabled { + .header { + background-color: #f5f5f5; + pointer-events: none; + border-bottom: none; + h4 { + color: gray; + } + .toggle { + svg { + transform: none; + color: gray; + } + } + } + } + } +} + +.clinical-panel-container.open { + box-sizing: border-box !important; + position: fixed; + top: 50px; + right: 0px; + width: 250px; + height: calc(100% - 50px); + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc1JyBoZWlnaHQ9JzUnPgo8cmVjdCB3aWR0aD0nNScgaGVpZ2h0PSc1JyBmaWxsPScjZmZmJy8+CjxyZWN0IHdpZHRoPScxJyBoZWlnaHQ9JzEnIGZpbGw9JyNjY2MnLz4KPC9zdmc+); + background-position-x: -1px; +} +.clinical-panel-container.closed { + width: 45px; + + .clinical-panel { + width: 100%; + } + + .panel-header { + flex-direction: column-reverse; + justify-content: center; + align-items: center; + gap: 8px; + border: none; + + h2 { + display: none; + } + + .collapse-panel { + svg { + transform: rotate(180deg); + } + } + } + + .panel-section { + display: none; + } +} + +.clinical-panel-container.closed.disabled { + .clinical-panel { + background-color: #f5f5f5; + + .panel-header { + .panel-icon { + color: gray; + } + + .collapse-panel { + display: none; + } + } + } +} + /* Minimal Mode styles */ .minimal_mode { .gosling-panel { overflow-y: scroll; overflow-x: hidden; + padding: 60px 300px 60px 60px; } .sample-label { @@ -1149,6 +1631,7 @@ a:hover { .instructions-modals-container { .modal { .modal-dialog { + display: flex; width: 100%; } .modal-body { @@ -1156,8 +1639,8 @@ a:hover { max-height: 80vh; overflow-y: scroll; display: flex; - justify-content: center; - padding: 24px 36px; + justify-content: start; + padding: 0px; width: 100%; .modal-body-content { diff --git a/src/css/NavigationBar.css b/src/css/NavigationBar.css index 839656b8..15075550 100644 --- a/src/css/NavigationBar.css +++ b/src/css/NavigationBar.css @@ -117,7 +117,7 @@ border-radius: 4px; background-color: transparent; } - + .config-button:hover { background-color: #f6f6f6; } @@ -125,7 +125,8 @@ background-color: #e6e4e4; } - .config-button:focus-visible, .config-button:focus { + .config-button:focus-visible, + .config-button:focus { outline-offset: 0px; } @@ -198,7 +199,7 @@ border: none; background-color: transparent; } - + .title-btn.clipboard { border: none; background-color: transparent; @@ -217,4 +218,4 @@ justify-content: end; align-items: center; } -} \ No newline at end of file +} diff --git a/src/data/samples.ts b/src/data/samples.ts index 53bea3f1..3b154694 100644 --- a/src/data/samples.ts +++ b/src/data/samples.ts @@ -1,4 +1,6 @@ import { Assembly } from 'gosling.js/dist/src/gosling-schema'; +import { DataRowProps } from '../ui/ClinicalPanel'; + import _7a921087 from '../script/img/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.jpeg'; import _84ca6ab0 from '../script/img/84ca6ab0-9edc-4636-9d27-55cdba334d7d.jpeg'; import _7d332cb1 from '../script/img/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.jpeg'; @@ -31,6 +33,7 @@ export type SampleType = { cnFields?: [string, string, string]; thumbnail?: string; note?: string; + clinicalInfo?: { [key: string]: DataRowProps[] }; }; // const samples: SampleType[] = (pcawg as SampleType[]).map(d => { return { group: 'default', ...d }}); diff --git a/src/icon.ts b/src/icon.ts index 29bd8e16..cb69f2c6 100644 --- a/src/icon.ts +++ b/src/icon.ts @@ -361,5 +361,33 @@ export const ICONS: Record = { ], stroke: 'currentColor', fill: 'none' + }, + CLIPBOARD: { + width: 11, + height: 14, + viewBox: '0 0 11 14', + path: [ + 'M1.375 1.75H3.66667C3.66667 0.784766 4.4888 0 5.5 0C6.5112 0 7.33333 0.784766 7.33333 1.75H9.625C10.3841 1.75 11 2.33789 11 3.0625V12.6875C11 13.4121 10.3841 14 9.625 14H1.375C0.615885 14 0 13.4121 0 12.6875V3.0625C0 2.33789 0.615885 1.75 1.375 1.75ZM5.5 1.09375C5.11901 1.09375 4.8125 1.38633 4.8125 1.75C4.8125 2.11367 5.11901 2.40625 5.5 2.40625C5.88099 2.40625 6.1875 2.11367 6.1875 1.75C6.1875 1.38633 5.88099 1.09375 5.5 1.09375ZM2.75 9.40625C2.75 9.52656 2.85312 9.625 2.97917 9.625H4.58333V11.1562C4.58333 11.2766 4.68646 11.375 4.8125 11.375H6.1875C6.31354 11.375 6.41667 11.2766 6.41667 11.1562V9.625H8.02083C8.14687 9.625 8.25 9.52656 8.25 9.40625V8.09375C8.25 7.97344 8.14687 7.875 8.02083 7.875H6.41667V6.34375C6.41667 6.22344 6.31354 6.125 6.1875 6.125H4.8125C4.68646 6.125 4.58333 6.22344 4.58333 6.34375V7.875H2.97917C2.85312 7.875 2.75 7.97344 2.75 8.09375V9.40625ZM2.75 4.15625C2.75 4.27656 2.85312 4.375 2.97917 4.375H8.02083C8.14687 4.375 8.25 4.27656 8.25 4.15625V3.71875C8.25 3.59844 8.14687 3.5 8.02083 3.5H2.97917C2.85312 3.5 2.75 3.59844 2.75 3.71875V4.15625Z' + ], + stroke: 'currentColor', + fill: 'none' + }, + ARROW_RIGHT: { + width: 7, + height: 6, + viewBox: '0 0 7 6', + path: [ + 'M6.85351 2.64701C7.04883 2.84225 7.04883 3.15931 6.85351 3.35455L4.35337 5.85357C4.15804 6.04881 3.84084 6.04881 3.64552 5.85357C3.45019 5.65834 3.45019 5.34127 3.64552 5.14604L5.29404 3.49981L0.500028 3.4998C0.22345 3.4998 4.762e-07 3.27645 5.24537e-07 3C5.72874e-07 2.72354 0.223451 2.50019 0.500028 2.50019L5.29248 2.5002L3.64708 0.853964C3.45176 0.658727 3.45176 0.341663 3.64708 0.146426C3.8424 -0.0488099 4.15961 -0.0488098 4.35493 0.146426L6.85507 2.64545L6.85351 2.64701Z' + ], + stroke: 'currentColor', + fill: 'none' + }, + CIRCLE: { + width: 10, + height: 10, + viewBox: '0 0 512 512', + path: ['M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z'], + stroke: 'currentColor', + fill: 'none' } }; diff --git a/src/ui/ClinicalPanel/index.tsx b/src/ui/ClinicalPanel/index.tsx new file mode 100644 index 00000000..2afe3ab2 --- /dev/null +++ b/src/ui/ClinicalPanel/index.tsx @@ -0,0 +1,300 @@ +import React, { useState, useEffect } from 'react'; + +import { ICONS } from '../../icon'; + +export type DataRowProps = { + label?: string; + value?: string; + cDNA?: string; + chr?: string; + gene?: string; + position?: string; + type?: string; + count?: string; + hrDetect?: boolean; + handleClick?: () => void; +}; + +// 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); + } + + return ( +
  • handleClick() : null} + role={handleClick ? 'button' : ''} + > + {label ? {capitalizedLabel} : null} + {value} +
  • + ); +}; + +type ToggleRowGroupProps = { + header: string; + callout?: string; + data: DataRowProps[]; +}; + +// Group of rows with a toggle-able header +const ToggleRowGroup = ({ callout = null, header, data }: ToggleRowGroupProps) => { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
    + + {callout && ( +
    +
    + + INFO CIRCLE + {ICONS.INFO_CIRCLE.path.map(p => ( + + ))} + + {callout} +
    +
    + )} +
    +
      + {data.map((row: DataRowProps, i: number) => { + return ; + })} +
    +
    +
    + ); +}; + +type PanelSectionProps = { + data: DataRowProps[]; + handleZoomToGene?: (gene: string) => void; +}; + +// Panel section for Clinical Summary data +const ClinicalSummary = ({ data }: PanelSectionProps) => { + const [isExpanded, setIsExpanded] = useState(true); + + return ( +
    + +
    +
    +
    + Invasive Ductal Carcinoma +
    +
    +
      + {data.map((row: DataRowProps, i: number) => { + return ; + })} +
    +
    +
    + ); +}; + +// Panel section for Clinically Relevant Variants data +const ClinicallyRelevantVariants = ({ handleZoomToGene, data }: PanelSectionProps) => { + const [isExpanded, setIsExpanded] = useState(true); + + return ( +
    + +
    +
    +
    + + INFO CIRCLE + {ICONS.INFO_CIRCLE.path.map(p => ( + + ))} + + Click to show in the visualization +
    +
    +
      + {data.map((row: DataRowProps, i: number) => { + const gene = row?.gene ?? ''; + const type = row?.type ?? ''; + const cDNA = row?.cDNA ?? ''; + const variantString = gene + ' ' + type + ' ' + cDNA; + + const handleClick = () => { + handleZoomToGene(gene); + }; + + return ; + })} +
    +
    +
    + ); +}; + +// Panel section for Mutational Signatures data +const MutationalSignatures = ({ data }: PanelSectionProps) => { + const [isExpanded, setIsExpanded] = useState(true); + + // Check if HRDetect is positive + const isHrDetect = data.some((row: DataRowProps) => row.hrDetect); + + // Split data into HRDetect and other data + const hrDetectData = []; + const otherData = []; + + data.forEach((row: DataRowProps) => { + const formattedData = { + label: row.count, + value: row.label + }; + + if (row.hrDetect) { + hrDetectData.push(formattedData); + } else { + otherData.push(formattedData); + } + }); + + return ( +
    + +
    + HRDetect +
    + + CIRCLE + + + {isHrDetect ? 'Positive' : 'Negative'} +
    +
    +
    + + +
    +
    + ); +}; + +type ClinicalPanelProps = { + demo: any; + gosRef: any; + filteredSamples: any; + isClinicalPanelOpen: boolean; + hasClinicalInfo: boolean; + setInteractiveMode: (interactiveMode: boolean) => void; + setIsClinicalPanelOpen: (isClinicalPanelOpen: boolean) => void; +}; + +export const ClinicalPanel = ({ + hasClinicalInfo, + demo, + gosRef, + isClinicalPanelOpen, + setInteractiveMode, + setIsClinicalPanelOpen +}: ClinicalPanelProps) => { + const [clinicalInformation, setClinicalInformation] = useState(null); + + const handleZoomToGene = (gene: string) => { + setInteractiveMode(true); + setTimeout(() => { + document.getElementById('variant-view')?.scrollIntoView({ + block: 'start', + inline: 'nearest', + behavior: 'smooth' + }), + 0; + }); + + gosRef.current.api.zoomToGene(`${demo.id}-mid-ideogram`, `${gene}`, 1000); + }; + + useEffect(() => { + if (hasClinicalInfo && demo?.clinicalInfo) { + console.log('demo.clinicalInfo', demo.clinicalInfo); + setClinicalInformation(demo.clinicalInfo); + } + }, [demo]); + + return ( +
    +
    +
    + + Clipboard + + +

    Genome Interpretation

    + +
    + + {hasClinicalInfo && clinicalInformation ? ( +
    + + + +
    + ) : null} +
    +
    + ); +}; diff --git a/src/ui/NavigationBar.tsx b/src/ui/NavigationBar.tsx index 37e49d0a..ccadf569 100644 --- a/src/ui/NavigationBar.tsx +++ b/src/ui/NavigationBar.tsx @@ -37,17 +37,14 @@ export const NavigationBar = ({ return (
    - -
    {!isChrome() ? ( GitHub - diff --git a/src/ui/NavigationButtons.tsx b/src/ui/NavigationButtons.tsx index 487330c8..65890569 100644 --- a/src/ui/NavigationButtons.tsx +++ b/src/ui/NavigationButtons.tsx @@ -4,10 +4,9 @@ import { ICONS } from '../icon'; type NavigationButtonsProps = { showSamples: boolean; isMinimalMode: boolean; -} - -export const NavigationButtons = ({ showSamples, isMinimalMode } : NavigationButtonsProps) => { +}; +export const NavigationButtons = ({ showSamples, isMinimalMode }: NavigationButtonsProps) => { return (
    From 1ce6af1da8b2fc886c67bc4fce14f8b6946ad3ed Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 2 Jan 2025 15:45:27 -0500 Subject: [PATCH 2/6] style: revert padding change style: center callout text --- src/css/App.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/css/App.css b/src/css/App.css index c537f7ec..922b0d70 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -1101,6 +1101,7 @@ a:hover { font-size: 0.75rem; color: #502d07; margin: auto; + text-align: center; } } } @@ -1640,7 +1641,7 @@ a:hover { overflow-y: scroll; display: flex; justify-content: start; - padding: 0px; + padding: 24px 36px; width: 100%; .modal-body-content { From e36d3f12227e48a7feff052f4072f4c77a5e6c3f Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 2 Jan 2025 15:59:50 -0500 Subject: [PATCH 3/6] style: revert display change for modal dialog style: revert padding for gosling panel --- src/css/App.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/css/App.css b/src/css/App.css index 922b0d70..d455479a 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -1386,7 +1386,6 @@ a:hover { .gosling-panel { overflow-y: scroll; overflow-x: hidden; - padding: 60px 300px 60px 60px; } .sample-label { @@ -1632,7 +1631,6 @@ a:hover { .instructions-modals-container { .modal { .modal-dialog { - display: flex; width: 100%; } .modal-body { From 4666412ebcd35eda617685c929728d8ddf3800c9 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 2 Jan 2025 16:20:02 -0500 Subject: [PATCH 4/6] chore: remove log statement --- src/ui/ClinicalPanel/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/ClinicalPanel/index.tsx b/src/ui/ClinicalPanel/index.tsx index 2afe3ab2..dcd6aca9 100644 --- a/src/ui/ClinicalPanel/index.tsx +++ b/src/ui/ClinicalPanel/index.tsx @@ -258,7 +258,6 @@ export const ClinicalPanel = ({ useEffect(() => { if (hasClinicalInfo && demo?.clinicalInfo) { - console.log('demo.clinicalInfo', demo.clinicalInfo); setClinicalInformation(demo.clinicalInfo); } }, [demo]); From 61dcd20f72cedf0baaf8a563e776394b0b65af93 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 2 Jan 2025 16:24:11 -0500 Subject: [PATCH 5/6] style: update font weight on hover instead of background color --- src/css/App.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/css/App.css b/src/css/App.css index d455479a..b4197307 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -1076,8 +1076,8 @@ a:hover { } .data-row.clickable:hover { - background-color: #d5dce2 !important; cursor: pointer; + font-weight: 600; } } } From 859dc5fa3606047ac32e42b964446979e7e4277e Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Thu, 2 Jan 2025 16:26:58 -0500 Subject: [PATCH 6/6] chore: remove click notice --- src/ui/ClinicalPanel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/ClinicalPanel/index.tsx b/src/ui/ClinicalPanel/index.tsx index dcd6aca9..74f385e4 100644 --- a/src/ui/ClinicalPanel/index.tsx +++ b/src/ui/ClinicalPanel/index.tsx @@ -212,7 +212,7 @@ const MutationalSignatures = ({ data }: PanelSectionProps) => {