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
+ }}
+ >
+
+
+ );
+ })}
+
+ );
+ }, [demo, visPanelWidth, selectedSvId]);
+
useLayoutEffect(() => {
if (!gosRef.current) return;
@@ -1009,6 +1097,7 @@ function App(props: RouteComponentProps) {
}}
>
{goslingComponent}
+ {trackTooltips}
{jumpButtonInfo ? (
)}
- {isMinimalMode ? (
-
-
-
-
- ) : null}
+
{
// External links and export buttons
isMinimalMode ? (
@@ -1086,7 +1143,7 @@ function App(props: RouteComponentProps) {