From 40255518e619ec7cf4f834613e5cda320bccf800 Mon Sep 17 00:00:00 2001 From: Cesar Ferreyra-Mansilla Date: Fri, 9 Aug 2024 11:08:41 -0400 Subject: [PATCH] feat: updates to the minimal mode version (#146) Co-authored-by: sehilyi --- package.json | 1 + src/App.css | 400 +++++++++++++-- src/App.tsx | 143 ++++-- src/alignment.ts | 2 +- src/main-spec.ts | 5 +- src/mid-spec.ts | 2 + .../genome_view/interactions_1.png | Bin 0 -> 146801 bytes .../genome_view/interactions_2.png | Bin 0 -> 43103 bytes .../genome_view/interactions_3.png | Bin 0 -> 473194 bytes .../genome_view/interactions_4.png | Bin 0 -> 86755 bytes .../variant_view/interactions_1.png | Bin 0 -> 23996 bytes .../variant_view/interactions_2.png | Bin 0 -> 104515 bytes .../variant_view/interactions_3.png | Bin 0 -> 7541 bytes .../variant_view/interactions_4.png | Bin 0 -> 7460 bytes .../variant_view/interactions_5.png | Bin 0 -> 10476 bytes .../variant_view/interpretation_1.png | Bin 0 -> 36228 bytes .../alignment/interactions_1.png | Bin 0 -> 64642 bytes .../alignment/interpretation_1.png | Bin 0 -> 25058 bytes .../alignment/interpretation_2.png | Bin 0 -> 17031 bytes .../alignment/interpretation_3.png | Bin 0 -> 11013 bytes .../copy_number_variants/interactions_1.png | Bin 0 -> 97985 bytes .../copy_number_variants/interpretation_1.png | Bin 0 -> 10996 bytes .../copy_number_variants/interpretation_2.png | Bin 0 -> 54038 bytes .../coverage/interpretation_1.png | Bin 0 -> 4447 bytes .../coverage/interpretation_2.png | Bin 0 -> 51090 bytes .../popover-images/driver/interactions_1.png | Bin 0 -> 350524 bytes .../driver/interpretation_1.png | Bin 0 -> 87904 bytes .../driver/interpretation_2.png | Bin 0 -> 322215 bytes .../popover-images/gains/interpretation_1.png | Bin 0 -> 13590 bytes .../popover-images/gains/interpretation_2.png | Bin 0 -> 63191 bytes .../popover-images/genes/interactions_1.png | Bin 0 -> 37435 bytes .../popover-images/genes/interactions_2.png | Bin 0 -> 9417 bytes .../popover-images/genes/interpretation_1.png | Bin 0 -> 7918 bytes .../popover-images/genes/interpretation_2.png | Bin 0 -> 1372 bytes .../ideogram/interpretation_1.png | Bin 0 -> 3443 bytes .../ideogram/interpretation_2.png | Bin 0 -> 12741 bytes .../popover-images/indel/interactions_1.png | Bin 0 -> 136672 bytes .../popover-images/indel/interpretation_1.png | Bin 0 -> 6881 bytes .../popover-images/indel/interpretation_2.png | Bin 0 -> 2027 bytes .../popover-images/loh/interpretation_1.png | Bin 0 -> 12695 bytes .../popover-images/loh/interpretation_2.png | Bin 0 -> 121340 bytes .../mutations/interactions_1.png | Bin 0 -> 3386 bytes .../mutations/interactions_2.png | Bin 0 -> 17955 bytes .../mutations/interpretation_1.png | Bin 0 -> 9644 bytes .../sequence/interpretation_1.png | Bin 0 -> 3344 bytes .../structural_variants/interactions_1.png | Bin 0 -> 6183 bytes .../structural_variants/interactions_2.png | Bin 0 -> 25436 bytes .../structural_variants/interpretation_1.png | Bin 0 -> 47741 bytes src/track/cnv.ts | 2 +- src/track/coverage.ts | 2 +- src/track/driver.ts | 2 +- src/track/gain.ts | 2 +- src/track/indel.ts | 7 +- src/track/loh.ts | 2 +- src/track/mutation.ts | 4 +- src/track/sv.ts | 1 + src/ui/ExportDropdown.tsx | 4 +- src/ui/GenomeViewModal.tsx | 87 ++++ src/ui/NavigationButtons.tsx | 67 +++ src/ui/VariantViewModal.tsx | 129 +++++ src/ui/getTrackDocData.ts | 475 ++++++++++++++++++ yarn.lock | 5 + 62 files changed, 1257 insertions(+), 85 deletions(-) create mode 100644 src/script/img/modal_images/genome_view/interactions_1.png create mode 100644 src/script/img/modal_images/genome_view/interactions_2.png create mode 100644 src/script/img/modal_images/genome_view/interactions_3.png create mode 100644 src/script/img/modal_images/genome_view/interactions_4.png create mode 100644 src/script/img/modal_images/variant_view/interactions_1.png create mode 100644 src/script/img/modal_images/variant_view/interactions_2.png create mode 100644 src/script/img/modal_images/variant_view/interactions_3.png create mode 100644 src/script/img/modal_images/variant_view/interactions_4.png create mode 100644 src/script/img/modal_images/variant_view/interactions_5.png create mode 100644 src/script/img/modal_images/variant_view/interpretation_1.png create mode 100644 src/script/img/popover-images/alignment/interactions_1.png create mode 100644 src/script/img/popover-images/alignment/interpretation_1.png create mode 100644 src/script/img/popover-images/alignment/interpretation_2.png create mode 100644 src/script/img/popover-images/alignment/interpretation_3.png create mode 100644 src/script/img/popover-images/copy_number_variants/interactions_1.png create mode 100644 src/script/img/popover-images/copy_number_variants/interpretation_1.png create mode 100644 src/script/img/popover-images/copy_number_variants/interpretation_2.png create mode 100644 src/script/img/popover-images/coverage/interpretation_1.png create mode 100644 src/script/img/popover-images/coverage/interpretation_2.png create mode 100644 src/script/img/popover-images/driver/interactions_1.png create mode 100644 src/script/img/popover-images/driver/interpretation_1.png create mode 100644 src/script/img/popover-images/driver/interpretation_2.png create mode 100644 src/script/img/popover-images/gains/interpretation_1.png create mode 100644 src/script/img/popover-images/gains/interpretation_2.png create mode 100644 src/script/img/popover-images/genes/interactions_1.png create mode 100644 src/script/img/popover-images/genes/interactions_2.png create mode 100644 src/script/img/popover-images/genes/interpretation_1.png create mode 100644 src/script/img/popover-images/genes/interpretation_2.png create mode 100644 src/script/img/popover-images/ideogram/interpretation_1.png create mode 100644 src/script/img/popover-images/ideogram/interpretation_2.png create mode 100644 src/script/img/popover-images/indel/interactions_1.png create mode 100644 src/script/img/popover-images/indel/interpretation_1.png create mode 100644 src/script/img/popover-images/indel/interpretation_2.png create mode 100644 src/script/img/popover-images/loh/interpretation_1.png create mode 100644 src/script/img/popover-images/loh/interpretation_2.png create mode 100644 src/script/img/popover-images/mutations/interactions_1.png create mode 100644 src/script/img/popover-images/mutations/interactions_2.png create mode 100644 src/script/img/popover-images/mutations/interpretation_1.png create mode 100644 src/script/img/popover-images/sequence/interpretation_1.png create mode 100644 src/script/img/popover-images/structural_variants/interactions_1.png create mode 100644 src/script/img/popover-images/structural_variants/interactions_2.png create mode 100644 src/script/img/popover-images/structural_variants/interpretation_1.png create mode 100644 src/ui/GenomeViewModal.tsx create mode 100644 src/ui/NavigationButtons.tsx create mode 100644 src/ui/VariantViewModal.tsx create mode 100644 src/ui/getTrackDocData.ts diff --git a/package.json b/package.json index 669609bd..135a4817 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@types/react": "^17.0.37", "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.2.0", + "bootstrap": "^5.3.3", "buffer": "^6.0.3", "gosling.js": "^0.17.0", "idb": "^7.0.2", diff --git a/src/App.css b/src/App.css index 03dd2148..ea02fe9c 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,9 @@ +*, +*::before, +*::after { + box-sizing: content-box !important; +} + body { margin: 0; /* font-family: 'Roboto Condensed', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', @@ -129,19 +135,23 @@ a:hover { display: inline-block; position: fixed; right: 200px; + text-decoration: none; } .title-doc-link { + color: black; display: inline-block; position: fixed; right: 20px; + text-decoration: none; } .title-about-link { - margin-left: 12px; - margin-right: 12px; + margin: auto 12px; font-size: 12px; cursor: pointer; + color: black; + text-decoration: none; } .title-github-link svg, @@ -224,7 +234,7 @@ a:hover { border: 1px solid grey; position: absolute; left: 3px; - scroll-margin-top: 50px; + scroll-margin-top: 155px; } .nav-dropdown:focus { @@ -238,6 +248,7 @@ a:hover { cursor: pointer; position: absolute; font-size: 14px; + font-family: Inter; height: 30px; width: 30px; margin-left: 20px; @@ -345,6 +356,10 @@ a:hover { cursor: pointer; z-index: 999; color: #333333; + + svg { + vertical-align: inherit; + } } .tag-parent { @@ -478,12 +493,20 @@ a:hover { cursor: pointer; z-index: 998; opacity: 0.5; + border: none; + border-radius: 4px; + background: none; + padding: 0px; } .move-to-top-btn:hover { opacity: 1; } +.move-to-top-btn:focus-visible { + outline-offset: 2px; +} + .interaction-toggle-button { z-index: 999; cursor: pointer; @@ -508,6 +531,7 @@ a:hover { text-shadow: 0px 0px 6px white; font-size: 18px; z-index: 999; + line-height: normal; /* background: #ffffff99; */ } @@ -558,7 +582,6 @@ a:hover { } .vis-overview-panel .title { - height: 30px; padding: 10px; font-size: 18px; border-bottom: 1px solid lightgray; @@ -621,7 +644,7 @@ a:hover { .overview-status { background: rgba(210, 210, 210, 0.1); width: calc(100% - 400px); - height: 20px; + height: 25px; float: left; border-bottom: 1px solid lightgrey; padding: 0px; @@ -799,6 +822,7 @@ a:hover { .control-group { display: flex; .control { + box-sizing: border-box !important; position: relative; left: 0px; margin-left: 0px; @@ -808,6 +832,92 @@ a:hover { } } +.track-tooltips-container { + top: 100px; + width: 3%; + height: min-content; + position: relative; + z-index: 997; +} + +.track-tooltip { + padding: 0px; + border: none; + + .button.question-mark { + width: 12px; + height: 12px; + fill: black; + } +} +.track-tooltip:hover { + cursor: pointer; +} + +.navigation-buttons { + box-sizing: border-box; + position: fixed; + z-index: 998; + display: flex; + flex-direction: column; + top: 63px; + left: 63px; +} + +.navigation-button-container { + display: flex; + height: auto; + padding: 0px; +} +.navigation-button-container.split { + display: flex; + height: auto; + padding: 0px; + + .split-left { + border-radius: 8px 0px 0px 8px; + border-right: none; + } + .split-right { + width: 40px; + border-radius: 0px 8px 8px 0px; + border-left: none; + + .button.question-mark { + width: 15px; + margin: auto; + color: black; + } + } +} +.navigation-button { + box-sizing: border-box !important; + background-color: #f6f6f6; + cursor: pointer; + font-size: 1rem; + font-family: Inter; + height: 40px; + width: 160px; + padding: 2px 10px; + border: 1px solid #d3d3d3; +} + +.navigation-button-variant, +.navigation-button-read { + margin-top: 4px; +} + +.navigation-button:focus-visible { + outline-offset: -1px; +} + +.navigation-button:hover:not(:disabled) { + background-color: #ebebeb; +} +.navigation-button:active:not(:disabled) { + background-color: #e6e4e4; +} + /* Minimal Mode styles */ .minimal_mode { .gosling-panel { @@ -820,39 +930,15 @@ a:hover { top: 8px; } + .nav-dropdown { + scroll-margin-top: 50px; + } + .navigation-buttons { - position: fixed; - z-index: 998; - display: flex; - flex-direction: column; top: 3px; left: 3px; } - .navigation-button { - background-color: #f6f6f6; - cursor: pointer; - font-size: 1rem; - font-family: Inter; - height: 40px; - width: 210px; - padding: 2px 10px; - border: 1px solid #d3d3d3; - } - - .navigation-button:hover:not(:disabled) { - background-color: #ebebeb; - } - .navigation-button:active:not(:disabled) { - background-color: #e6e4e4; - } - .navigation-button:first-of-type { - border-radius: 8px 8px 0px 0px; - } - .navigation-button:last-of-type { - border-radius: 0px 0px 8px 8px; - } - /* Force scrollbar to show */ ::-webkit-scrollbar { -webkit-appearance: none; @@ -930,6 +1016,7 @@ a:hover { overflow: hidden; .export-button { + box-sizing: border-box !important; width: 210px; height: 35px; border-radius: inherit; @@ -1006,5 +1093,252 @@ a:hover { .variant-view-controls { left: 50%; transform: translate(-50%, 0px); + + .gene-search { + width: 210px; + } + } +} + +.instructions-modals-container { + .modal { + .modal-dialog { + width: 100%; + } + .modal-body { + box-sizing: border-box !important; + max-height: 80vh; + overflow-y: scroll; + display: flex; + justify-content: center; + padding: 24px 36px; + width: 100%; + + .modal-body-content { + display: flex; + flex-direction: column; + width: 100%; + } + .section { + display: flex; + flex-direction: column; + /* margin-bottom: 20px; */ + + .section-content { + padding: 0px 55px; + } + + h3 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 4px; + } + + hr { + color: #cdcdcd; + border-width: 1px; + margin: 0px; + } + hr.header { + color: #b0b0b0; + border-width: 2px; + } + + .block { + flex: 1; + display: flex; + margin: 32px 0px; + min-height: 120px; + } + .block.with-image { + justify-content: center; + height: auto; + + .image-container.two-image { + display: flex; + flex-direction: column; + } + + img { + width: 250px; + height: fit-content; + margin: auto 0px; + border: 2px solid #ebebeb; + object-fit: contain; + } + + .text { + display: flex; + width: 300px; + flex-direction: column; + justify-content: space-evenly; + margin-left: 60px; + + p { + margin-bottom: 0px; + + span.text-button-example { + display: inline-flex; + justify-content: center; + background-color: #efefef; + width: 25px; + height: 25px; + border-radius: 10px 0px 0px 10px; + border: 2px solid lightgray; + + b { + line-height: 20px; + margin: auto auto 5px; + } + } + span.text-button-example.right { + border-radius: 0px 10px 10px 0px; + } + } + } + } + } + } + } +} + +.popover.track-tooltip-popover { + max-width: none; + box-shadow: 0px 0px 10px 0px #22252954; + z-index: 998; + + .popover-header { + font-size: 1.25rem; + font-family: 'Inter'; + font-weight: 600; + background-color: #f7f7f7; + color: #222529; + padding-left: 16px; + } + .popover-body { + display: flex; + background-color: #ffffff; + border-radius: 10px; + padding: 16px 32px; + font-family: 'Inter'; + font-weight: 400; + color: #222529; + + .popover-content { + display: flex; + justify-content: space-between; + gap: 24px; + h3 { + font-size: 1rem; + font-weight: 600; + margin-bottom: 4px; + } + hr { + color: #cdcdcd; + border-width: 1px; + margin: 0px; + } + hr.header { + color: #b0b0b0; + border-width: 2px; + } + .section { + margin-bottom: 20px; + + .block { + margin: 16px 0px; + display: flex; + min-height: 120px; + + p { + span.text-orange { + font-weight: 500; + color: #e1aa4c; + } + span.text-green { + font-weight: 500; + color: #469c77; + } + span.text-green-alignment { + color: #5a9c7c; + } + span.text-gray { + color: #757575; + } + span.text-blue { + color: #71b5f5; + } + span.text-coral { + color: #c96a33; + } + span.text-red { + color: #d73c3a; + } + } + } + .block:last-of-type { + margin-bottom: 0px; + } + .block.text-only { + min-height: min-content; + p { + width: 360px; + padding: 0px 10px; + margin-bottom: 0px; + } + } + .block.text-only.multi-paragraph { + display: flex; + flex-direction: column; + gap: 15px; + } + .block.with-image { + justify-content: center; + height: auto; + + .image-container.two-image { + display: flex; + flex-direction: column; + } + + img { + width: 130px; + height: fit-content; + margin: auto; + border: 2px solid #ebebeb; + object-fit: contain; + } + + .text { + display: flex; + width: 200px; + flex-direction: column; + justify-content: space-evenly; + margin-left: 20px; + + p { + margin-bottom: 0px; + } + } + } + .block.with-image.column { + flex-direction: column; + align-items: center; + width: 100%; + padding-top: 20px; + + img { + width: 180px; + margin-bottom: 20px; + } + + .text { + margin-top: 10px; + width: 250px; + padding: 0px 16px; + } + } + } + } } } diff --git a/src/App.tsx b/src/App.tsx index 431fb7c8..da2256be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,6 +23,14 @@ import SampleConfigForm from './ui/sample-config-form'; import { BrowserDatabase } from './browser-log'; import legend from './legend.png'; import { ExportDropdown } from './ui/ExportDropdown'; +import { GenomeViewModal } from './ui/GenomeViewModal'; +import { VariantViewModal } from './ui/VariantViewModal'; +import { NavigationButtons } from './ui/NavigationButtons'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import * as bootstrap from 'bootstrap/dist/js/bootstrap.bundle.min'; + +import { Track, getTrackDocData } from './ui/getTrackDocData.js'; const db = new Database(); const log = new BrowserDatabase(); @@ -312,7 +320,7 @@ function App(props: RouteComponentProps) { window.addEventListener( 'resize', debounce(() => { - setVisPanelWidth(window.innerWidth - VIS_PADDING.left * 2); + setVisPanelWidth(window.innerWidth - (isMinimalMode ? 10 : VIS_PADDING.left * 2)); }, 500) ); @@ -334,6 +342,12 @@ function App(props: RouteComponentProps) { } }, []); + // Enable Bootstrap popovers for track tooltips, update for selected SV tracks + useEffect(() => { + const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]'); + const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)); + }, [selectedSvId]); + const getThumbnail = (d: SampleType) => { return ( d.thumbnail || @@ -533,6 +547,80 @@ 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]); + const trackTooltips = useMemo(() => { + // calculate the offset by the Genome View + const genomeViewHeight = Math.min(600, visPanelWidth); + const TRACK_DATA = getTrackDocData(isMinimalMode); + const offset = genomeViewHeight + (isMinimalMode ? 100 : 40) - 2; + + // Infer the tracks shown + const tracksShown: Track[] = ['ideogram', 'driver', 'gene']; + if (demo.vcf && demo.vcfIndex) tracksShown.push('mutation'); + if (demo.vcf2 && demo.vcf2Index) tracksShown.push('indel'); + if (demo.cnv) tracksShown.push('cnv', 'gain', 'loh'); + // Pushing this after the others to match order of tracks in UI + tracksShown.push('sv'); + if (selectedSvId !== '') tracksShown.push('sequence'); + if (demo.bam && demo.bai && selectedSvId !== '') tracksShown.push('coverage', 'alignment'); + const HEIGHTS_OF_TRACKS_SHOWN = TRACK_DATA.filter(d => tracksShown.includes(d.type)); + + // Calculate the positions of the tracks + const trackPositions = tracksShown.map((t, i) => { + const indexOfTrack = HEIGHTS_OF_TRACKS_SHOWN.findIndex(d => d.type === t); + const cumHeight = HEIGHTS_OF_TRACKS_SHOWN.slice(0, indexOfTrack).reduce((acc, d) => acc + d.height, 0); + const position = { + y: offset + cumHeight - 100, + type: t, + title: HEIGHTS_OF_TRACKS_SHOWN[indexOfTrack].title, + popover_content: HEIGHTS_OF_TRACKS_SHOWN[indexOfTrack].popover_content + }; + return position; + }); + + return ( +
+ {trackPositions?.map((d, i) => { + return ( + +
+
+

+

+
+
+
+ `} + data-bs-title={d.title} + data-bs-custom-class={'track-tooltip-popover popover-for-' + d.type} + data-bs-html="true" + data-bs-content={d.popover_content} + style={{ + position: 'absolute', + top: d.y + (d.type === 'ideogram' ? 32 : 0) - 1, + left: 10 + }} + > + + Question Mark + {ICONS.QUESTION_CIRCLE_FILL.path.map(p => ( + + ))} + + + ); + })} + + ); + }, [demo, visPanelWidth, selectedSvId]); + useLayoutEffect(() => { if (!gosRef.current) return; @@ -1009,6 +1097,7 @@ function App(props: RouteComponentProps) { }} > {goslingComponent} + {trackTooltips} {jumpButtonInfo ? ( - - - ) : null} + { // External links and export buttons isMinimalMode ? ( @@ -1086,7 +1143,7 @@ function App(props: RouteComponentProps) {