diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 923c20a5..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -gosling-screenshot.js \ No newline at end of file diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index ff3e3498..d8b351ec 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build and Deploy on: push: - branches: [master] + branches: [main] pull_request: jobs: @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v2 diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz new file mode 100644 index 00000000..8b4ae7cf Binary files /dev/null and b/.yarn/install-state.gz differ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 00000000..3186f3f0 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md index 4f98c17a..35844f25 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,6 @@ http://localhost:3000/docs Please cite the [following publication](10.31219/osf.io/pyqrx): > Sehi L’Yi, Dominika Maziec, Victoria Stevens, Trevor Manz, Alexander Veit, Michele Berselli, Peter J. Park, Dominik Głodzik, and Nils Gehlenborg. Chromoscope: interactive multiscale visualization for structural variation in human genomes. Nat Methods 20, 1834–1835 (2023). https://doi.org/10.1038/s41592-023-02056-x + +## Funding +Chromoscope is funded in part through a grant awarded by [Innovation in Cancer Informatics.](https://www.the-ici-fund.org/) diff --git a/docs/docs/loading-data/data-formats.md b/docs/docs/loading-data/data-formats.md index 908a8159..1f847d18 100644 --- a/docs/docs/loading-data/data-formats.md +++ b/docs/docs/loading-data/data-formats.md @@ -9,7 +9,7 @@ This page describes file formats used in Chromoscope. To find a list of required ## Structural Variants (BEDPE) -The structural variants are stored in a BEDPE file. The following columns are used in the browser: +The structural variants are stored in a headed BEDPE file. The order of the columns does not need to be in the exact same order. This is a The following columns are used in the browser: | Property | Type | Note | |---|---|---| @@ -43,7 +43,7 @@ In Chromosope, strands are mapped with the following types of SVs. ## CNV (TSV) -The CNV is stored in a tab-delimited file that is visualized as three tracks: CNV, Gain, and LOH. +The CNV is stored in a headed tab-delimited file that is visualized as three tracks: CNV, Gain, and LOH. The order of the columns does not need to be in the exact same order. | Property | Type | Note | |---|---|---| @@ -63,7 +63,7 @@ https://s3.amazonaws.com/gosling-lang.org/data/SV/7a921087-8e62-4a93-a757-fd8cdb ## Drivers (TSV or JSON) -The drivers are stored in a tab-delimited file. When this file is present, the browser will show drivers that are included in the file only. +The drivers are stored in a headed tab-delimited file. When this file is present, the browser will show drivers that are included in the file only. The order of the columns does not need to be in the exact same order. diff --git a/docs/docs/loading-data/through-data-config.md b/docs/docs/loading-data/through-data-config.md index d4a0eace..fbbe6a18 100644 --- a/docs/docs/loading-data/through-data-config.md +++ b/docs/docs/loading-data/through-data-config.md @@ -23,7 +23,7 @@ For each sample, you need to prepare the following information in a JSON object. | `cancer` | `string` | Required. Type of a cancer. | | `assembly` | `'hg38'` or `'hg19'` | Required. Assembly. | | `sv` | `string` | Required. An URL of the SV bedpe file (`.bedpe`). | -| `cnv` | `string` | Required. An URL of the CNV text file (`.tsv`). | +| `cnv` | `string` | Optional. An URL of the CNV text file (`.tsv`). | | `drivers` | `string` | Optional. An URL of a file that contains drivers (`.tsv` or `.json`). | | `vcf` | `string` | Optional. An URL of the point mutation file (`.vcf`). | | `vcfIndex` | `string` | Optional. An URL of the point mutation index file (`.tbi`). | diff --git a/docs/docs/visualizations/genome-view.md b/docs/docs/visualizations/genome-view.md index b848d6df..9f6bea84 100644 --- a/docs/docs/visualizations/genome-view.md +++ b/docs/docs/visualizations/genome-view.md @@ -11,5 +11,5 @@ The genome view shows the selected sample in a circular visualization. This uses ## Interactions -- You can move or resize an interactive brush (light blue) using the mouse. This is linked with a [variant view](./cohort-view) that is shown on the bottom of the genome view. +- You can move or resize an interactive brush (light blue) using the mouse. This is linked with a [variant view](./variant-view) that is shown on the bottom of the genome view. - You can move your mouse on top of a structural variant to see detailed information on a tooltip. diff --git a/docs/src/pages/about/index.md b/docs/src/pages/about/index.md index 2bfbc51e..c3df522a 100644 --- a/docs/src/pages/about/index.md +++ b/docs/src/pages/about/index.md @@ -18,3 +18,7 @@ Chromoscope is developed and maintained by the members of [Department of Biomedi When citing Chromoscope in your paper, please cite the [following publication](https://10.31219/osf.io/pyqrx). > Sehi L’Yi, Dominika Maziec, Victoria Stevens, Trevor Manz, Alexander Veit, Michele Berselli, Peter J. Park, Dominik Głodzik, and Nils Gehlenborg. Chromoscope: interactive multiscale visualization for structural variation in human genomes. _Nat Methods_ 20, 1834–1835 (2023). https://doi.org/10.1038/s41592-023-02056-x + +## Funding + +Chromoscope is funded in part through a grant awarded by [Innovation in Cancer Informatics](https://www.the-ici-fund.org/) diff --git a/package.json b/package.json index cb297822..0e97d9f0 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,9 @@ "@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.11.0", + "gosling.js": "^0.17.0", "idb": "^7.0.2", "lodash": "^4.17.21", "path": "^0.12.7", @@ -54,5 +55,8 @@ "hooks": { "pre-commit": "yarn format && git add ." } + }, + "peerDependencies": { + "@popperjs/core": "*" } } diff --git a/scripts/README.md b/scripts/README.md index fc6efbad..cdb89399 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,3 +1,15 @@ # Chromoscope +## presigned_url_scripts This folder contains scripts that are used to process datasets and for generating AWS presigned URLS for a cohort. + + +## S3_Bucket_Query_to_chromoscope + +This folder contains scripts for querying a locally hosted S3 bucket, selecting files of interest, and either generating a `config.json` file or visualizing the data directly in Chromoscope using the external URL parameter. + +**Important:** Before running the scripts, ensure that the S3 bucket name and credentials are properly configured in `app.py` and `scripts.js`. + +## file_creation_for_higlassserver + +This folder contains scripts that are used to create different filetypes used in Higlass, such as multivec files and bed files. The `beddb_file_creation_from_tsv.sh` bash script is used to run both the `tsv_to_bed_bash.py` file which will create a bed file and then encode and upload it to a local higlass server. diff --git a/scripts/S3_Bucket_Query_to_Chromoscope.zip b/scripts/S3_Bucket_Query_to_Chromoscope.zip new file mode 100644 index 00000000..e070c041 Binary files /dev/null and b/scripts/S3_Bucket_Query_to_Chromoscope.zip differ diff --git a/scripts/clustering/run_clustering.sh b/scripts/clustering/run_clustering.sh old mode 100755 new mode 100644 diff --git a/scripts/file_creation_for_higlassserver/beddb_file_creation_from_tsv.sh b/scripts/file_creation_for_higlassserver/beddb_file_creation_from_tsv.sh new file mode 100755 index 00000000..04f3a823 --- /dev/null +++ b/scripts/file_creation_for_higlassserver/beddb_file_creation_from_tsv.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +#check if length of the arguments gives all needed info +if [[ "$#" -ne 2 ]]; then + echo "incorrect number of arguments: eg: script filepath filetype (eg. baf)" + exit 1 +fi + +filepath="$1" +filepath_array=($(echo $filepath| cut -d. -f1)) +for i in "${filepath_array[@]}" +do +echo $i +done +new_bed_file_name=${filepath_array[0]} +new_bed_file_name+=".bed" +uploaded_file_name=$new_bed_file_name +uploaded_file_name+=".beddb" +#python tsv_to_bed_bash.py $1 $2 + +clodius aggregate bedfile --chromsizes-filename hg19.chrom.sizes $new_bed_file_name +higlass-manage ingest $uploaded_file_name --filetype bedfile --datatype bedlike --assembly hg19 diff --git a/scripts/file_creation_for_higlassserver/create_all_files_for_multivec_formatting.py b/scripts/file_creation_for_higlassserver/create_all_files_for_multivec_formatting.py new file mode 100644 index 00000000..ccf5b296 --- /dev/null +++ b/scripts/file_creation_for_higlassserver/create_all_files_for_multivec_formatting.py @@ -0,0 +1,60 @@ +import pandas as pd +import os +import h5py +os.chdir("../..") +os.chdir("documents2/stage/test_data") + +# changes the chromosome column to always have chr. as a row instead of just the numbers as it is outputted from hopla +df = pd.read_csv("combined_filtered_CNV_data.tsv", sep="\t") +df.rename(columns={'seqnames':'chromosome'}, inplace=True) +grouped = df.groupby("chromosome") +df['chromosome'] = df['chromosome'].apply(lambda x: f'chr{x}') + +chromosome_info = {} + +# change the postition of the sample column for my convenience and drop the columns that are giving problems to +# the clodius aggregate function +temp_cols = df.columns.tolist() +new_cols = temp_cols[-1:] + temp_cols[:-1] +df = df[new_cols] +df = df.drop(columns=['range', 'mask', 'threshold', 'seg_threshold']) +unique_values = df['sample'].unique() +df.set_index('sample', inplace=True) + +# make a file containing all sampletypes -> needed for clodius +with open('sample_types.txt', 'w') as f: + for value in unique_values: + f.write(str(value) + '\n') +#for column in df.columns: + + #df[column] = df[column].astype('S') + +#df.to_hdf("combined_csv_test.hdf5",key="df", mode='w', complevel=5) +# create a hdf5 file from the input tsv file in order to use in the aggregate function +with h5py.File('blah.h5', 'w') as f: + # Group by the chromosome column + for chromosome, group in df.groupby('chromosome'): + # Convert the group (DataFrame) to a numpy array, excluding the chromosome column + data = group.drop(columns=['chromosome']) + + # Create a dataset for each chromosome + dset = f.create_dataset(chromosome, data.shape, data=data, compression='gzip') + +# create a matching chromsizes file by looking at the length +for chrom_name, group in grouped: + # Determine the number of rows for this chromosome + num_rows = len(group) + # Assuming all columns from 'start' onward are data columns (adjust as needed) + num_columns = group.shape[1] - 3 # Adjust based on actual column positions + + # Store the size of this chromosome for the chromsizes file + chromosome_info[chrom_name] = num_rows + +with open("chromsizes.txt", 'w') as f: + for chrom_name, size in chromosome_info.items(): + f.write(f"chr{chrom_name}\t{size}\n") + +# checking sizes in order to make sure that no errors occur during the whole aggregate process. +with h5py.File('blah.h5', 'r') as f: + for key in f.keys(): + print(f"Dataset {key}: shape {f[key].shape}, dtype {f[key].dtype}") diff --git a/scripts/file_creation_for_higlassserver/hg19.chrom.sizes b/scripts/file_creation_for_higlassserver/hg19.chrom.sizes new file mode 100644 index 00000000..050d5c66 --- /dev/null +++ b/scripts/file_creation_for_higlassserver/hg19.chrom.sizes @@ -0,0 +1,93 @@ +chr1 249250621 +chr2 243199373 +chr3 198022430 +chr4 191154276 +chr5 180915260 +chr6 171115067 +chr7 159138663 +chrX 155270560 +chr8 146364022 +chr9 141213431 +chr10 135534747 +chr11 135006516 +chr12 133851895 +chr13 115169878 +chr14 107349540 +chr15 102531392 +chr16 90354753 +chr17 81195210 +chr18 78077248 +chr20 63025520 +chrY 59373566 +chr19 59128983 +chr22 51304566 +chr21 48129895 +chr6_ssto_hap7 4928567 +chr6_mcf_hap5 4833398 +chr6_cox_hap2 4795371 +chr6_mann_hap4 4683263 +chr6_apd_hap1 4622290 +chr6_qbl_hap6 4611984 +chr6_dbb_hap3 4610396 +chr17_ctg5_hap1 1680828 +chr4_ctg9_hap1 590426 +chr1_gl000192_random 547496 +chrUn_gl000225 211173 +chr4_gl000194_random 191469 +chr4_gl000193_random 189789 +chr9_gl000200_random 187035 +chrUn_gl000222 186861 +chrUn_gl000212 186858 +chr7_gl000195_random 182896 +chrUn_gl000223 180455 +chrUn_gl000224 179693 +chrUn_gl000219 179198 +chr17_gl000205_random 174588 +chrUn_gl000215 172545 +chrUn_gl000216 172294 +chrUn_gl000217 172149 +chr9_gl000199_random 169874 +chrUn_gl000211 166566 +chrUn_gl000213 164239 +chrUn_gl000220 161802 +chrUn_gl000218 161147 +chr19_gl000209_random 159169 +chrUn_gl000221 155397 +chrUn_gl000214 137718 +chrUn_gl000228 129120 +chrUn_gl000227 128374 +chr1_gl000191_random 106433 +chr19_gl000208_random 92689 +chr9_gl000198_random 90085 +chr17_gl000204_random 81310 +chrUn_gl000233 45941 +chrUn_gl000237 45867 +chrUn_gl000230 43691 +chrUn_gl000242 43523 +chrUn_gl000243 43341 +chrUn_gl000241 42152 +chrUn_gl000236 41934 +chrUn_gl000240 41933 +chr17_gl000206_random 41001 +chrUn_gl000232 40652 +chrUn_gl000234 40531 +chr11_gl000202_random 40103 +chrUn_gl000238 39939 +chrUn_gl000244 39929 +chrUn_gl000248 39786 +chr8_gl000196_random 38914 +chrUn_gl000249 38502 +chrUn_gl000246 38154 +chr17_gl000203_random 37498 +chr8_gl000197_random 37175 +chrUn_gl000245 36651 +chrUn_gl000247 36422 +chr9_gl000201_random 36148 +chrUn_gl000235 34474 +chrUn_gl000239 33824 +chr21_gl000210_random 27682 +chrUn_gl000231 27386 +chrUn_gl000229 19913 +chrM 16571 +chrUn_gl000226 15008 +chr18_gl000207_random 4262 diff --git a/scripts/file_creation_for_higlassserver/tsv_to_bed.py b/scripts/file_creation_for_higlassserver/tsv_to_bed.py new file mode 100644 index 00000000..b9fbe21a --- /dev/null +++ b/scripts/file_creation_for_higlassserver/tsv_to_bed.py @@ -0,0 +1,12 @@ +import pandas as pd +import os +import fuc +import re +os.chdir("../..") +os.chdir("documents2/stage/test_data") +df = pd.read_csv("D2201410_new.tsv", delimiter="\t") +df = df.drop(columns=['Sample']) +df.columns = ['Chromosome', 'Start', 'End','REF', 'ALT','BAF'] +df['Chromosome'] = 'chr' + df['Chromosome'].astype(str) +bf = fuc.pybed.BedFrame.from_frame(meta=[], data=df) +bf.to_file('D2201410_new.bed') diff --git a/scripts/file_creation_for_higlassserver/tsv_to_bed_bash.py b/scripts/file_creation_for_higlassserver/tsv_to_bed_bash.py new file mode 100644 index 00000000..a48eef62 --- /dev/null +++ b/scripts/file_creation_for_higlassserver/tsv_to_bed_bash.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +import pandas as pd +import sys +import fuc + +filepath = sys.argv[1] +type_file = sys.argv[2] +if len(sys.argv) < 3: + print("2 addition arguments are needed: format: tsv_to_bed_bash.py filepath filetype") + sys.exit(1) +filename = filepath.split(".") +df = pd.read_csv(filepath, delimiter="\t") +if type_file.upper() == "BAF": + df = df.drop(columns=['Sample']) + df.columns = ['Chromosome', 'Start', 'End','REF', 'ALT','BAF'] + df['Chromosome'] = df['Chromosome'].apply(lambda x: f'chr{x}') +bf = fuc.pybed.BedFrame.from_frame(meta=[], data=df) +bf.to_file(f'{filename}.bed') diff --git a/src/App.css b/src/App.css index 533e4501..aa51d67c 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,6 +234,7 @@ a:hover { border: 1px solid grey; position: absolute; left: 3px; + scroll-margin-top: 155px; } .nav-dropdown:focus { @@ -237,6 +248,7 @@ a:hover { cursor: pointer; position: absolute; font-size: 14px; + font-family: Inter; height: 30px; width: 30px; margin-left: 20px; @@ -304,6 +316,34 @@ a:hover { fill: grey; } +.region-search { + position: absolute; + font-size: 14px; + left: 158px; + height: 28px; + width: 350px; + /* border-radius: 10px; */ + margin-left: 20px; + padding-left: 29px; + padding-right: 10px; + padding-top: 0px; + padding-bottom: 0px; + line-height: 30px; + border: 1px solid grey; +} + +.region-search-icon { + margin-top: 1px; + margin-left: 22px; + width: 16px; + height: 16px; + position: absolute; + left: 545px; + cursor: pointer; + z-index: 999; + fill: grey; +} + .config-panel-search-box { font-size: 14px; height: calc(30px - 6px); @@ -344,6 +384,10 @@ a:hover { cursor: pointer; z-index: 999; color: #333333; + + svg { + vertical-align: inherit; + } } .tag-parent { @@ -477,12 +521,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; @@ -507,6 +559,7 @@ a:hover { text-shadow: 0px 0px 6px white; font-size: 18px; z-index: 999; + line-height: normal; /* background: #ffffff99; */ } @@ -557,7 +610,6 @@ a:hover { } .vis-overview-panel .title { - height: 30px; padding: 10px; font-size: 18px; border-bottom: 1px solid lightgray; @@ -620,7 +672,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; @@ -747,3 +799,614 @@ a:hover { opacity: 1; background-color: #7aaded; } + +.track-mouseover-menu { + z-index: 999; +} + +/* Styles for the variant view controls */ +.variant-view-controls { + position: absolute; + display: flex; + flex-direction: row; + height: 30px; + justify-content: center; + left: 3px; + + .chromosome-select { + position: relative; + height: auto; + left: 0px; + } + .gene-search { + position: relative; + left: 0px; + padding: 0px; + width: auto; + height: auto; + display: flex; + + svg { + position: absolute; + top: 50%; + transform: translate(0px, -50%); + left: 8px; + margin: auto; + margin-left: 0px; + } + input { + position: relative; + left: 0; + margin-left: 0; + border: none; + width: 180px; + padding-left: 35px; + } + } + .directional-controls { + display: flex; + margin: auto 0px auto 16px; + gap: 16px; + .control-group { + display: flex; + .control { + box-sizing: border-box !important; + position: relative; + left: 0px; + margin-left: 0px; + line-height: 28px; + } + } + } + .region-search { + position: relative; + left: 0px; + padding: 0px; + width: auto; + height: auto; + display: flex; + + svg { + position: absolute; + top: 50%; + transform: translate(0px, -50%); + left: 8px; + margin: auto; + margin-left: 0px; + } + input { + position: relative; + left: 0; + margin-left: 0; + border: none; + width: 325px; + padding-left: 35px; + } + } +} + +.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; + right: 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 { + overflow-y: scroll; + overflow-x: hidden; + } + + .sample-label { + left: 300px; + top: 8px; + } + + .nav-dropdown { + scroll-margin-top: 50px; + } + + .navigation-buttons { + top: 3px; + left: 3px; + } + + /* Force scrollbar to show */ + ::-webkit-scrollbar { + -webkit-appearance: none; + width: 10px; + } + + ::-webkit-scrollbar-thumb { + width: 10px; + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } + ::-webkit-scrollbar:hover { + cursor: pointer; + } + + /* Styles for the navigation on the right side of visualization */ + .external-links { + position: absolute; + z-index: 998; + height: auto; + width: auto; + top: 3px; + right: 12px; + + .external-links-nav { + display: flex; + flex-direction: column; + justify-content: space-between; + + .open-in-chromoscope-link { + background-color: #f6f6f6; + font-size: 0.9rem; + font-family: Inter; + font-weight: 400; + display: flex; + height: 35px; + justify-content: center; + border: 1px solid #d3d3d3; + border-radius: 8px; + + .link-group { + margin: auto; + + .external-link-icon { + margin: auto; + margin-left: 8px; + fill: black; + stroke: black; + } + } + } + + .open-in-chromoscope-link:hover { + text-decoration: none; + cursor: pointer; + background-color: #ebebeb; + } + + .open-in-chromoscope-link:active { + background-color: #e6e4e4; + } + + .export-links { + border-radius: 4px; + margin-top: 4px; + + .export-dropdown { + height: auto; + background-color: #f6f6f6; + right: 0px; + border-radius: 8px; + border: 1px solid #d3d3d3; + transition: all 100ms; + overflow: hidden; + + .export-button { + box-sizing: border-box !important; + width: 210px; + height: 35px; + border-radius: inherit; + border: 0px solid; + font-weight: 400; + background-color: transparent; + + .export-title { + font-size: 0.9rem; + font-family: Inter; + } + + .button.triangle-down { + width: 11px; + height: 7px; + margin-left: 8px; + } + } + + .export-button:hover { + cursor: pointer; + background-color: #ebebeb; + } + + .export-button:active { + background-color: #e6e4e4; + } + + .nav-list { + list-style-type: none; + padding: 0px 10px; + display: flex; + flex-direction: row; + height: 50px; + background-color: white; + margin: 0px 8px 8px 8px; + border-radius: 3px; + + .nav-list-item { + display: flex; + margin: auto; + } + + .title-btn { + display: flex; + position: relative; + width: 25px; + height: 25px; + margin-left: 0px; + } + + .title-btn.png { + padding: 0px; + border: none; + background-color: transparent; + } + } + } + + .export-dropdown.open { + background-color: #ebebeb; + border-radius: 8px; + border: 1px solid #c3c3c3; + transition: all 100ms; + + .export-button { + border: none; + } + } + } + } + } + + .variant-view-controls { + left: 50%; + transform: translate(-50%, 0px); + + .gene-search { + width: 210px; + } + .region-search { + width: 350px; + } + } +} + +.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-skyblue { + color: #56B4E9; + } + span.text-darkblue { + color: #0072B2; + } + span.text-coral { + color: #c96a33; + } + span.text-vermillion { + color: #D55E00; + } + span.text-yellow { + color: #F0E442; + } + 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 e307c00a..65fa7e39 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,6 @@ import { debounce, sample } from 'lodash'; import type { RouteComponentProps } from 'react-router-dom'; import generateSpec from './main-spec'; import ErrorBoundary from './error'; -import _allDrivers from './data/driver.json'; -import _customDrivers from './data/driver.custom.json'; import samples, { SampleType } from './data/samples'; import getOneOfSmallMultiplesSpec from './small-multiples-spec'; import { CHROMOSOMES, THEME, WHOLE_CHROMOSOME_STR } from './constants'; @@ -21,7 +19,16 @@ import CancerSelector from './ui/cancer-selector'; import HorizontalLine from './ui/horizontal-line'; import SampleConfigForm from './ui/sample-config-form'; import { BrowserDatabase } from './browser-log'; -import legend from './legend.png'; +import UrlsafeCodec from './lib/urlsafe-codec'; +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(); @@ -31,23 +38,21 @@ const DATABSE_THUMBNAILS = await db.get(); const GENERATED_THUMBNAILS = {}; const INIT_VIS_PANEL_WIDTH = window.innerWidth; -const VIS_PADDING = 60; const ZOOM_PADDING = 200; const ZOOM_DURATION = 500; -const allDrivers = [ - ...(_allDrivers as any), - ..._customDrivers.map(d => { - return { ...d, sample_id: 'SRR7890905' }; - }), - ..._customDrivers.map(d => { - return { ...d, sample_id: 'SRR7890905_Hartwig' }; - }) -]; function App(props: RouteComponentProps) { // URL parameters const urlParams = new URLSearchParams(props.location.search); + const isMinimalMode = urlParams.get('minimal_mode') === 'true'; + const VIS_PADDING = { + top: isMinimalMode ? 0 : 60, + right: isMinimalMode ? 0 : 60, + bottom: isMinimalMode ? 0 : 60, + left: isMinimalMode ? 0 : 60 + }; + // !! instead of using `urlParams.get('external')`, we directly parse the external URL in order to include // any inlined parameters of the external link (e.g., private AWS link with authentication info.) let externalUrl = null; @@ -61,9 +66,9 @@ function App(props: RouteComponentProps) { const exampleId = urlParams.get('example'); const xDomain = urlParams.get('domain') ? urlParams - .get('domain') - .split('-') - .map(d => +d) + .get('domain') + .split('-') + .map(d => +d) : null; const demoIndex = useRef(+urlParams.get('demoIndex') ?? 0); const [showSmallMultiples, setShowSmallMultiples] = useState(externalUrl === null); @@ -94,17 +99,16 @@ function App(props: RouteComponentProps) { const [filteredSamples, setFilteredSamples] = useState(selectedSamples); const [showOverview, setShowOverview] = useState(true); const [showPutativeDriver, setShowPutativeDriver] = useState(true); - const [interactiveMode, setInteractiveMode] = useState(false); - const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - VIS_PADDING * 2); + const [interactiveMode, setInteractiveMode] = useState(isMinimalMode ?? false); + const [visPanelWidth, setVisPanelWidth] = useState( + INIT_VIS_PANEL_WIDTH - (isMinimalMode ? 10 : VIS_PADDING.left * 2) + ); const [overviewChr, setOverviewChr] = useState(''); const [genomeViewChr, setGenomeViewChr] = useState(''); - const [drivers, setDrivers] = useState( - typeof demo.drivers === 'string' && demo.drivers.split('.').pop() === 'json' ? [] : getFilteredDrivers(demo.id) - ); const [selectedSvId, setSelectedSvId] = useState(''); const [breakpoints, setBreakpoints] = useState<[number, number, number, number]>([1, 100, 1, 100]); const [bpIntervals, setBpIntervals] = useState<[number, number, number, number] | undefined>(); - const [mouseOnVis, setMouseOnVis] = useState(false); + const [mouseOnVis, setMouseOnVis] = useState(isMinimalMode ?? false); const [jumpButtonInfo, setJumpButtonInfo] = useState<{ id: string; x: number; y: number; direction: 'leftward' | 'rightward'; zoomTo: () => void }>(); const mousePos = useRef({ x: -100, y: -100 }); @@ -115,51 +119,8 @@ function App(props: RouteComponentProps) { const rightReads = useRef<{ [k: string]: number | string }[]>([]); const [svReads, setSvReads] = useState<{ name: string; type: string }[]>([]); - function getFilteredDrivers(demoId: string) { - return (allDrivers as any).filter((d: any) => d.sample_id === demoId && +d.pos); - } - // update demo useEffect(() => { - if (typeof demo.drivers === 'string' && demo.drivers.split('.').pop() === 'json') { - // we want to change this json file to json value - fetch(demo.drivers).then(response => - response.text().then(d => { - const customDrivers = JSON.parse(d); - // TODO: these need to be supported in other types of data - customDrivers.forEach(d => { - const optionalFields = [ - 'ref', - 'alt', - 'category', - 'top_category', - 'transcript_consequence', - 'protein-mutation', - 'allele_fraction', - 'mutation_type', - 'biallelic' - ]; - optionalFields.forEach(f => { - if (!d[f]) { - d[f] = ''; - } - }); - if (typeof d['biallelic'] === 'string' && d['biallelic'].toUpperCase() === 'YES') { - d['biallelic'] = 'yes'; - } - if (typeof d['biallelic'] === 'string' && d['biallelic'].toUpperCase() === 'NO') { - d['biallelic'] = 'no'; - } - }); - - setDrivers(customDrivers); - }) - ); - } else { - const filteredDrivers = getFilteredDrivers(demo.id); - setDrivers(filteredDrivers); - } - setOverviewChr(''); setGenomeViewChr(''); setSelectedSvId(''); @@ -167,24 +128,40 @@ function App(props: RouteComponentProps) { rightReads.current = []; }, [demo]); + function isWebAddress(url) { + return url.startsWith('http://') || url.startsWith('https://'); + } + useEffect(() => { + const fetchData = async (url) => { + let responseText; + let externalDemo; + if (isWebAddress(url)) { + responseText = await fetch(url).then(response => response.text()); + externalDemo = JSON.parse(responseText); + } else { + externalDemo = await UrlsafeCodec.decode(url); + console.log(externalDemo) + } + processDemoData(externalDemo); + }; + + function processDemoData(demoData){ + if (Array.isArray(demoData) && demoData.length >= 0) { + setFilteredSamples(demoData); + demoData = demoData[demoIndex.current < demoData.length ? demoIndex.current : 0]; + } else { + setFilteredSamples([demoData]); + } + if (demoData) { + setDemo(demoData); + } + setShowSmallMultiples(true); + setReady(true); + }; + if (externalUrl) { - fetch(externalUrl).then(response => - response.text().then(d => { - let externalDemo = JSON.parse(d); - if (Array.isArray(externalDemo) && externalDemo.length >= 0) { - setFilteredSamples(externalDemo); - externalDemo = externalDemo[demoIndex.current < externalDemo.length ? demoIndex.current : 0]; - } else { - setFilteredSamples([externalDemo]); - } - if (externalDemo) { - setDemo(externalDemo); - } - setShowSmallMultiples(true); - setReady(true); - }) - ); + fetchData(externalUrl); } }, []); @@ -198,92 +175,20 @@ function App(props: RouteComponentProps) { ); }, [filterSampleBy]); - useEffect(() => { - if (!gosRef.current || !demo.bai || !demo.bam) return; - - gosRef.current.api.subscribe('rawData', (type: string, e: any) => { - if (e.id.includes('bam') && (leftReads.current.length === 0 || rightReads.current.length === 0)) { - const isThisPotentiallyJsonRuleData = typeof e.data[0]?.name === 'undefined'; - if (isThisPotentiallyJsonRuleData) { - return; - } - - /// DEBUG - // console.log(e.id, e.data); - /// - - // This means we just received a BAM data that is just rendered - if (e.id.includes('left') && leftReads.current.length === 0) { - leftReads.current = e.data; - } else if (e.id.includes('right') && rightReads.current.length === 0) { - rightReads.current = e.data; - } - - // !! This is to drop duplicated data records. - // Multiple tracks overlaid on alignment tracks makes duplicated data records. - leftReads.current = Array.from(new Set(leftReads.current.map(d => JSON.stringify(d)))).map(d => - JSON.parse(d) - ); - rightReads.current = Array.from(new Set(rightReads.current.map(d => JSON.stringify(d)))).map(d => - JSON.parse(d) - ); - - // Reads on both views prepared? - if (leftReads.current.length !== 0 && rightReads.current.length !== 0) { - const mates = leftReads.current - .filter(l => rightReads.current.filter(r => r.name === l.name && r.id !== l.id).length !== 0) - .map(d => d.name as string); - - const matesWithSv = mates.map(name => { - const matesOnLeft = leftReads.current.filter(d => d.name === name); - const matesOnRight = rightReads.current.filter(d => d.name === name); - - if (matesOnLeft.length !== 1 || matesOnRight.length !== 1) { - // We do not do anything for this case for now. - return { name, type: 'unknown' }; - } - - // console.log(matesOnLeft[0], matesOnRight[0]); - const ld = matesOnLeft[0].strand; - const rd = matesOnRight[0].strand; - - if (matesOnLeft[0].chrName !== matesOnRight[0].chrName) return { name, type: 'Translocation' }; - if (ld === '+' && rd === '-') return { name, type: 'Deletion' }; - if (ld === '-' && rd === '-') return { name, type: 'Inversion (TtT)' }; - if (ld === '+' && rd === '+') return { name, type: 'Inversion (HtH)' }; - if (ld === '-' && rd === '+') return { name, type: 'Duplication' }; - return { name, type: 'unknown' }; - }); - - if ( - matesWithSv - .map(d => d.name) - .sort() - .join() !== - svReads - .map(d => d.name) - .sort() - .join() - ) { - setSvReads(matesWithSv); - } - } - } - }); - - return () => { - gosRef.current.api.unsubscribe('rawData'); - }; - }, [gosRef, svReads, demo]); - useEffect(() => { if (!overviewChr) return; if (overviewChr.includes('chr')) { gosRef.current?.api.zoomTo(`${demo.id}-top-ideogram`, overviewChr, 0, 0); setTimeout(() => setGenomeViewChr(overviewChr), 0); + gosRef.current?.api.zoomTo(`${demo.id}-top-GQbin`, overviewChr, 0, 0); + gosRef.current?.api.zoomTo(`${demo.id}-top-AFbin`, overviewChr, 0, 0); + gosRef.current?.api.zoomTo(`${demo.id}-top-DPbin`, overviewChr, 0, 0); } else { gosRef.current?.api.zoomToExtent(`${demo.id}-top-ideogram`, ZOOM_DURATION); + gosRef.current?.api.zoomToExtent(`${demo.id}-top-GQbin`, ZOOM_DURATION); + gosRef.current?.api.zoomToExtent(`${demo.id}-top-AFbin`, ZOOM_DURATION); + gosRef.current?.api.zoomToExtent(`${demo.id}-top-DPbin`, ZOOM_DURATION); } }, [overviewChr]); @@ -297,16 +202,39 @@ function App(props: RouteComponentProps) { } }, [genomeViewChr]); - // change the width of the visualization panel + // change the width of the visualization panel and register intersection observer useEffect(() => { window.addEventListener( 'resize', debounce(() => { - setVisPanelWidth(window.innerWidth - VIS_PADDING * 2); + setVisPanelWidth(window.innerWidth - (isMinimalMode ? 10 : VIS_PADDING.left * 2)); }, 500) ); + + // Lower opacity of legend image as it leaves viewport + if (isMinimalMode) { + const legendElement = document.querySelector('.genome-view-legend'); + const options = { + root: document.querySelector('.minimal_mode'), + rootMargin: '-250px 0px 0px 0px', + threshold: [0.9, 0.75, 0.5, 0.25, 0] + }; + + const observer = new IntersectionObserver(entry => { + // Set intersection ratio as opacity (round up to one decimal place) + legendElement.style.opacity = '' + Math.ceil(10 * entry[0].intersectionRatio) / 10; + }, options); + + observer.observe(legendElement); + } }, []); + // 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 || @@ -425,12 +353,6 @@ function App(props: RouteComponentProps) {
{AvailabilityIcon(!!d.vcf && !!d.vcfIndex)}Point Mutation
-
- {AvailabilityIcon(!!d.vcf2 && !!d.vcf2Index)}Indel -
-
- {AvailabilityIcon(!!d.bam && !!d.bai)}Read Alignment -
{d.note ?
{d.note}
: null} @@ -471,28 +393,21 @@ function App(props: RouteComponentProps) { ); const goslingComponent = useMemo(() => { - const loadingCustomJSONDrivers = typeof demo.drivers === 'string' && demo.drivers.split('.').pop() === 'json'; - const isStillLoadingDrivers = loadingCustomJSONDrivers && drivers.length == 0; - if (!ready || isStillLoadingDrivers) return null; - - const useCustomDrivers = loadingCustomJSONDrivers || !demo.drivers; const spec = generateSpec({ ...demo, showOverview, xDomain: xDomain as [number, number], xOffset: 0, - showPutativeDriver, width: visPanelWidth, - drivers: useCustomDrivers ? drivers : demo.drivers, - selectedSvId, breakpoints: breakpoints, crossChr: false, bpIntervals, - svReads + spacing: isMinimalMode ? 100 : 40 }); currentSpec.current = JSON.stringify(spec); // console.log('spec', spec); return ( +
+
); // !! 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, showOverview, breakpoints]); + + 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']; + if (demo.baf) tracksShown.push('baf'); + if (demo.baf_server) tracksShown.push('baf'); + if (demo.haplo) tracksShown.push('haplo'); + if (demo.vcf && demo.vcfIndex) tracksShown.push('mutation'); + if (demo.cnv) tracksShown.push('cnv'); + if (demo.me) tracksShown.push('mendelian-errors'); + if (demo.pm) tracksShown.push('parent-mapping'); + // Pushing this after the others to match order of tracks in UI + 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; @@ -543,7 +534,6 @@ function App(props: RouteComponentProps) { // we will show the bam files, so set the initial positions setBreakpoints([+x - ZOOM_PADDING, +xe + ZOOM_PADDING, +x1 - ZOOM_PADDING, +x1e + ZOOM_PADDING]); setBpIntervals([x, xe, x1, x1e]); - setSelectedSvId(e.data[0].sv_id + ''); // move to the bottom setTimeout( @@ -612,21 +602,24 @@ function App(props: RouteComponentProps) { return (
{ const top = e.clientY; const left = e.clientX; const width = window.innerWidth; const height = window.innerHeight; - if ( - VIS_PADDING < top && - top < height - VIS_PADDING && - VIS_PADDING < left && - left < width - VIS_PADDING - ) { - setMouseOnVis(true); - } else { - setMouseOnVis(false); + if (!isMinimalMode) { + if ( + VIS_PADDING.top < top && + top < height - VIS_PADDING.top && + VIS_PADDING.left < left && + left < width - VIS_PADDING.left + ) { + setMouseOnVis(true); + } else { + setMouseOnVis(false); + } } mousePos.current = { x: left, y: top }; }} @@ -637,139 +630,153 @@ function App(props: RouteComponentProps) { setJumpButtonInfo(undefined); }} > - - { - setShowSamples(true); - }} - > - Menu - - -
- - CHROMOSCOPE - - { - setShowAbout(true); - }} - > - - - - About - - {' | '} - {/* {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1) + ' • ' + demo.id} */} - {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1)} - {demo.id} - gosRef.current?.api.exportPng()}> - - Export Image - {ICONS.PNG.path.map(p => ( - - ))} - - + {!isMinimalMode && ( { - const a = document.createElement('a'); - a.setAttribute( - 'href', - `data:text/plain;charset=utf-8,${encodeURIComponent( - getHtmlTemplate(currentSpec.current) - )}` - ); - a.download = 'visualization.html'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + style={{ + height: '50px', + width: '100%', + background: 'white', + position: 'absolute', + zIndex: 999, + opacity: 0.8 }} - style={{ marginLeft: 40 }} - > - - Export HTML - {ICONS.HTML.path.map(p => ( - - ))} - - - + )} + {!isMinimalMode && ( + { - const a = document.createElement('a'); - a.setAttribute( - 'href', - `data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}` - ); - a.download = 'visualization.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + setShowSamples(true); }} - style={{ marginLeft: 70 }} > - - Export Gosling Spec (JSON) - {ICONS.JSON.path.map(p => ( - - ))} - - - { - const { xDomain } = gosRef.current.hgApi.api.getLocation(`${demo.id}-mid-ideogram`); - if (xDomain) { - // urlParams.set('demoIndex', demoIndex.current + ''); - // urlParams.set('domain', xDomain.join('-')); - let newUrl = window.location.origin + window.location.pathname + '?'; - newUrl += `demoIndex=${demoIndex.current}`; - newUrl += `&domain=${xDomain.join('-')}`; - if (externalDemoUrl.current) { - newUrl += `&external=${externalDemoUrl.current}`; - } else if (externalUrl) { - newUrl += `&external=${externalUrl}`; - } - navigator.clipboard - .writeText(newUrl) - .then(() => - alert('The URL of the current session has been copied to your clipboard.') + Menu + + + )} +
+ {!isMinimalMode && ( + <> + + CHROMOSCOPE + + { + setShowAbout(true); + }} + > + + + + About + + {' | '} + {/* {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1) + ' • ' + demo.id} */} + {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1)} + {demo.id} + + )} + {!isMinimalMode && ( + <> + gosRef.current?.api.exportPng()}> + + Export Image + {ICONS.PNG.path.map(p => ( + + ))} + + + { + const a = document.createElement('a'); + a.setAttribute( + 'href', + `data:text/plain;charset=utf-8,${encodeURIComponent( + getHtmlTemplate(currentSpec.current) + )}` ); - } - }} - style={{ marginLeft: 100 }} - > - - Export Link - - - - + a.download = 'visualization.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }} + style={{ marginLeft: 40 }} + > + + Export HTML + {ICONS.HTML.path.map(p => ( + + ))} + + + { + const a = document.createElement('a'); + a.setAttribute( + 'href', + `data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}` + ); + a.download = 'visualization.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }} + style={{ marginLeft: 70 }} + > + + Export Gosling Spec (JSON) + {ICONS.JSON.path.map(p => ( + + ))} + + + { + const { xDomain } = gosRef.current.hgApi.api.getLocation(`${demo.id}-mid-ideogram`); + if (xDomain) { + // urlParams.set('demoIndex', demoIndex.current + ''); + // urlParams.set('domain', xDomain.join('-')); + let newUrl = window.location.origin + window.location.pathname + '?'; + newUrl += `demoIndex=${demoIndex.current}`; + newUrl += `&domain=${xDomain.join('-')}`; + if (externalDemoUrl.current) { + newUrl += `&external=${externalDemoUrl.current}`; + } else if (externalUrl) { + newUrl += `&external=${externalUrl}`; + } + navigator.clipboard + .writeText(newUrl) + .then(() => + alert( + 'The URL of the current session has been copied to your clipboard.' + ) + ); + } + }} + style={{ marginLeft: 100 }} + > + + Export Link + + + + + + )} {!isChrome() ? ( ) : null} - - - GitHub - - - GitHub - - - - - - Documentation - -
-
-
-
{ - if (e.target === e.currentTarget) setShowSamples(false); - }} - > - { - setShowSamples(false); - }} - > - Close - - -
- CHROMOSCOPE - { - setShowAbout(true); - }} - > - - - - About - - {' | '} Samples - setFilterSampleBy(e.target.value)} - hidden - /> -
+ {!isMinimalMode && ( + <> Documentation -
+
+ {!isMinimalMode && ( +
+
{ + if (e.target === e.currentTarget) setShowSamples(false); + }} > - {generateThumbnails ? 'Stop Generating Thumbnails' : 'Generate Missing Thumbnails'} - -
-
-
- { - fetch(url).then(response => - response.text().then(d => { - let externalDemo = JSON.parse(d); - if (Array.isArray(externalDemo) && externalDemo.length >= 0) { - setFilteredSamples(externalDemo); - externalDemo = - externalDemo[ + { + setShowSamples(false); + }} + > + Close + + +
+ CHROMOSCOPE + { + setShowAbout(true); + }} + > + + + + About + + {' | '} Samples + setFilterSampleBy(e.target.value)} + hidden + /> +
+ + + GitHub + + + GitHub + + + + + + Documentation + + +
+
+
+ { + fetch(url).then(response => + response.text().then(d => { + let externalDemo = JSON.parse(d); + if (Array.isArray(externalDemo) && externalDemo.length >= 0) { + setFilteredSamples(externalDemo); + externalDemo = + externalDemo[ demoIndex.current < externalDemo.length ? demoIndex.current : 0 - ]; - } - if (externalDemo) { - externalDemoUrl.current = url; - setDemo(externalDemo); - } - }) - ); - }} - /> - - { - setFilteredSamples([ - { - ...config, - group: 'default' - }, - ...filteredSamples - ]); - }} - /> + ]; + } + if (externalDemo) { + externalDemoUrl.current = url; + setDemo(externalDemo); + } + }) + ); + }} + /> + + { + setFilteredSamples([ + { + ...config, + group: 'default' + }, + ...filteredSamples + ]); + }} + /> +
+
{`Total of ${filteredSamples.length} samples loaded`}
+
{smallOverviewWrapper}
-
{`Total of ${filteredSamples.length} samples loaded`}
-
{smallOverviewWrapper}
-
+ )}
{goslingComponent} + {trackTooltips} {jumpButtonInfo ? ( ) : null} -
+ ? 'lightgray' + : !interactiveMode && !mouseOnVis + ? '#00000000' + : 'lightgray' + }`, + top: VIS_PADDING.top, + left: VIS_PADDING.left, + opacity: 0.9, + zIndex: 2, + pointerEvents: interactiveMode ? 'none' : 'auto' + }} + /> + )} + + { + // External links and export buttons + isMinimalMode ? ( +
+ +
+ ) : null + }
{ - setShowSamples(false); - const chr = e.currentTarget.value; - setTimeout(() => setGenomeViewChr(chr), 300); - }} - value={genomeViewChr} - disabled={!showOverview} - > - {CHROMOSOMES.map(chr => { - return ( - - ); - })} - - - - - { - // const keyword = e.target.value; - // if(keyword !== "" && !keyword.startsWith("c")) { - // gosRef.current.api.suggestGene(keyword, (suggestions) => { - // setGeneSuggestions(suggestions); - // }); - // setSuggestionPosition({ - // left: searchBoxRef.current.getBoundingClientRect().left, - // top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height, - // }); - // } else { - // setGeneSuggestions([]); - // } - // setSearchKeyword(keyword); - // }} - onKeyDown={e => { - const keyword = (e.target as HTMLTextAreaElement).value; - switch (e.key) { - case 'ArrowUp': - break; - case 'ArrowDown': - break; - case 'Enter': - // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156 - gosRef.current.hgApi.api.zoomToGene( - `${demo.id}-mid-ideogram`, - keyword, - 10000, - 1000 - ); - break; - case 'Esc': - case 'Escape': - break; - } - }} - /> - - - - + +
+ + + + { + // const keyword = e.target.value; + // if(keyword !== "" && !keyword.startsWith("c")) { + // gosRef.current.api.suggestGene(keyword, (suggestions) => { + // setGeneSuggestions(suggestions); + // }); + // setSuggestionPosition({ + // left: searchBoxRef.current.getBoundingClientRect().left, + // top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height, + // }); + // } else { + // setGeneSuggestions([]); + // } + // setSearchKeyword(keyword); + // }} + onKeyDown={e => { + const keyword = (e.target as HTMLTextAreaElement).value; + switch (e.key) { + case 'ArrowUp': + break; + case 'ArrowDown': + break; + case 'Enter': + // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156 + gosRef.current.hgApi.api.zoomToGene( + `${demo.id}-mid-ideogram`, + keyword, + 10000, + 1000 + ); + break; + case 'Esc': + case 'Escape': + break; + } + }} + /> +
+
+
+ + +
+
+ + +
+
+
+ + + + { + // const keyword = e.target.value; + // if(keyword !== "" && !keyword.startsWith("c")) { + // gosRef.current.api.suggestGene(keyword, (suggestions) => { + // setGeneSuggestions(suggestions); + // }); + // setSuggestionPosition({ + // left: searchBoxRef.current.getBoundingClientRect().left, + // top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height, + // }); + // } else { + // setGeneSuggestions([]); + // } + // setSearchKeyword(keyword); + // }} + onKeyDown={e => { + const trackId = `${demo.id}-mid-ideogram`; + const keyword = (e.target as HTMLTextAreaElement).value; + switch (e.key) { + case 'ArrowUp': + break; + case 'ArrowDown': + break; + case 'Enter': + // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156 + gosRef.current?.api.zoomTo(trackId, keyword, 0, ZOOM_DURATION); + break; + case 'Esc': + case 'Escape': + break; + } + }} + /> +
+
-
- {!interactiveMode - ? 'Click inside to use interactions on visualizations' - : 'Click outside to deactivate interactions and scroll the page'} -
-
+ + {!isMinimalMode && ( +
+ )}
{'ⓘ No read alignment data available for this sample.'} @@ -1249,116 +1388,120 @@ function App(props: RouteComponentProps) { className={showAbout ? 'about-modal-container' : 'about-modal-container-hidden'} onClick={() => setShowAbout(false)} /> -
- -

- Chromoscope - {' | '}About -

+ {!isMinimalMode && ( +
+ +

+ Chromoscope + {' | '}About +

-

- Whole genome sequencing is now routinely used to profile mutations in DNA in the soma and in the - germline, informing molecular diagnoses of disease and therapeutic decisions. Structural - variants (SVs) are the main new type of alterations we see more of, and they are often - diagnostic, prognostic, or therapy-informing. However, the size and complexity of SV data, - combined with the difficulty of obtaining accurate SV calls, pose challenges in the - interpretation of SVs, requiring tedious visual inspection of potentially pathogenic variants - with multiple visualization tools. -

+

+ Whole genome sequencing is now routinely used to profile mutations in DNA in the soma and in + the germline, informing molecular diagnoses of disease and therapeutic decisions. Structural + variants (SVs) are the main new type of alterations we see more of, and they are often + diagnostic, prognostic, or therapy-informing. However, the size and complexity of SV data, + combined with the difficulty of obtaining accurate SV calls, pose challenges in the + interpretation of SVs, requiring tedious visual inspection of potentially pathogenic + variants with multiple visualization tools. +

-

- To overcome the problems with the interpretation of SVs, we developed Chromoscope, an - open-source web-based application for the interactive visualization of structural variants. - Chromoscope has several innovative features which unlock the insights from whole genome - sequencing: visualization at multiple scale levels simultaneously, effective navigation across - scales, easy setup for loading users' large datasets, and a feature to export, share, and - further customize visualizations. We anticipate that Chromoscope will accelerate the exploration - and interpretation of SVs by a broad range of scientists and clinicians, leading to new insights - into genomic biomarkers. -

-

Learn more about Chromoscope

- -

The Team

-
    -
  • - Sehi L'Yi - {hidiveLabRef} -
  • -
  • - Dominika Maziec - {parkLabRef} -
  • -
  • - Victoria Stevens - {parkLabRef} -
  • -
  • - Trevor Manz - {hidiveLabRef} -
  • -
  • - Alexander Veit - {parkLabRef} -
  • -
  • - Michele Berselli - {parkLabRef} -
  • -
  • - Peter J Park - {parkLabRef} -
  • -
  • - Dominik Glodzik - {parkLabRef} -
  • -
  • - Nils Gehlenborg - {hidiveLabRef} -
  • -
-
-
-
{ setTimeout( () => document.getElementById('gosling-panel')?.scrollTo({ top: 0, behavior: 'smooth' }), @@ -1370,8 +1513,12 @@ function App(props: RouteComponentProps) { Scroll To Top -
+
+
+ + +
); diff --git a/src/alignment.ts b/src/alignment.ts index 70554dd6..189f242d 100644 --- a/src/alignment.ts +++ b/src/alignment.ts @@ -98,7 +98,7 @@ export function alignment(option: SpecOption, isLeft: boolean): GoslingSpec { return { id: `${id}-bottom-${isLeft ? 'left' : 'right'}-bam`, alignment: 'overlay', - title: 'Alignment', + title: isLeft ? '  Alignment' : ' Alignment', data: { type: 'bam', url: bam, indexUrl: bai, loadMates: false }, mark: 'rect', experimental: { diff --git a/src/data/driver.custom.json b/src/data/driver.custom.json index 6e59c89d..66ee7b96 100644 --- a/src/data/driver.custom.json +++ b/src/data/driver.custom.json @@ -1,7 +1,7 @@ [ { "chr": "chr2", - "pos": 47806320, + "pos": 47795017, "ref": "G", "alt": "A", "gene": "MSH6", @@ -13,7 +13,7 @@ }, { "chr": "chr6", - "pos": 157133136, + "pos": 156993402, "ref": "G", "alt": "C", "gene": "ARID1B", @@ -26,7 +26,7 @@ }, { "chr": "chr8", - "pos": 38428420, + "pos": 38439986, "ref": "G", "alt": "A", "gene": "FGFR1", @@ -38,7 +38,7 @@ }, { "chr": "chr13", - "pos": 32339132, + "pos": 32357888, "ref": "G", "alt": "T", "gene": "BRCA2", @@ -51,7 +51,7 @@ }, { "chr": "chr17", - "pos": 7675088, + "pos": 7677976, "ref": "C", "alt": "T", "gene": "TP53", @@ -64,7 +64,7 @@ }, { "chr": "chrX", - "pos": 77681733, + "pos": 77645546, "ref": "T", "alt": "C", "gene": "ATRX", @@ -78,19 +78,19 @@ { "gene": "CDKN2A", "chr": "chr9", - "pos": 21981527, + "pos": 21981538, "category": "deletion" }, { "gene": "MET", "chr": "chr7", - "pos": 116735291, + "pos": 116735286, "category": "amplification" }, { "gene": "PTEN", "chr": "chr10", - "pos": 89677278, + "pos": 87917777, "category": "deletion", "biallelic": "yes" } diff --git a/src/data/samples.ts b/src/data/samples.ts index a152bf6a..7c73b18e 100644 --- a/src/data/samples.ts +++ b/src/data/samples.ts @@ -1,4 +1,4 @@ -import { Assembly } from 'gosling.js/dist/src/core/gosling.schema'; +import { Assembly } from 'gosling.js/dist/src/gosling-schema'; 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'; @@ -19,11 +19,14 @@ export type SampleType = { id: string; // "aliquot ID" cancer: string; // cancer type assembly: Assembly; // hg19 or 38 - sv: string; // URL of bedpe - cnv: string; // URL of txt - drivers?: { [k: string]: string | number }[] | string; - bam?: string; - bai?: string; + cnv?: string; // URL of txt + me?: string; + me2?: string; + pm?: string; + haplo?: string; + roi ?: string; + baf?: string; + baf_server?: string; vcf?: string; vcfIndex?: string; vcf2?: string; @@ -31,6 +34,9 @@ export type SampleType = { cnFields?: [string, string, string]; thumbnail?: string; note?: string; + binStats?: string; + sample?: string; + summary?: string; }; // const samples: SampleType[] = (pcawg as SampleType[]).map(d => { return { group: 'default', ...d }}); @@ -39,212 +45,26 @@ export type SampleType = { const samples: SampleType[] = [ { group: 'default', - id: 'SRR7890905', - cancer: 'breast', + id: 'Proband_22_10969', + cancer: 'Albinism', assembly: 'hg38', vcf: 'https://somatic-browser-test.s3.amazonaws.com/SNV_test_tumor_normal_with_panel.vcf.gz', vcfIndex: 'https://somatic-browser-test.s3.amazonaws.com/SNV_test_tumor_normal_with_panel.vcf.gz.tbi', vcf2: 'https://somatic-browser-test.s3.amazonaws.com/INDEL_test_tumor_normal_with_panel.vcf.gz', vcf2Index: 'https://somatic-browser-test.s3.amazonaws.com/INDEL_test_tumor_normal_with_panel.vcf.gz.tbi', - sv: 'https://somatic-browser-test.s3.amazonaws.com/SVTYPE_SV_test_tumor_normal_with_panel.bedpe', - cnv: 'https://gist.githubusercontent.com/sehilyi/6fbceae35756b13472332d6b81b10803/raw/596428a8b0ebc00e7f8cbc52b050db0fbd6e19a5/SRR7890943.ascat.v3.cnv.tsv', - bam: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam', - bai: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam.bai', + //baf: 'https://gist.githubusercontent.com/nicolasdebusschere/e3f768aabdd46d75ab9fcada81f8fbf2/raw/5ac38b33e6acdeea1ba87753a7f448d927aa84b9/baf.tsv', + baf_server: 'http://localhost:8889/api/v1/tileset_info/?d=KXdCqhUlR0WQsNAi9BhPxg', + cnv: 'https://gist.githubusercontent.com/nicolasdebusschere/86e826877b79ec0ddb20966c78b71da7/raw/d841a32a2b40dc8a0b9df8c5cb6042b79d24fa0f/CNV_sample_2.tsv', + me: 'https://gist.githubusercontent.com/Maximvan/60a5f23de4b9a1fb8edc2a25ef079075/raw/870af34036718a64b78f058a0e53941418820afc/me_1mbp.csv', + me2: 'https://gist.githubusercontent.com/Maximvan/3daf900566eeaf198256f42e4a1f73f0/raw/394de4ff86b7b0b0319f7e94c12c42f66e2dad3f/me_detail.csv', + pm: 'https://gist.githubusercontent.com/Maximvan/8c30172d4852ae16ad346a728073546b/raw/edb67b4144840387fb3acf32d01719548e4e6e2a/parent_mapping.csv', + haplo: 'https://gist.githubusercontent.com/nicolasdebusschere/368bab39fb2704e2fd7c314b0fb438b3/raw/ac5c7f86235e4c2bcc051ef802c090f5455bcb7d/haplo.tsv', + roi: 'https://gist.githubusercontent.com/Maximvan/6d47f196bcac0f28c64b2e491c231a7f/raw/d27d8d1da4531b7740457afba6471de9d74a59fb/ROI.csv', note: 'CNV profile - ASCAT. SVs - Sentieon. Mutations and indels - Sentieon', + binStats: 'https://gist.githubusercontent.com/Maximvan/2095620fd812dc3a708fae3905d16573/raw/e25e46b348da485c8ea0ae10303a0bedac854ebe/binned_stats.tsv', + summary: 'https://gist.githubusercontent.com/Maximvan/e2ad6bf0bb0ad707c9ec8a1d19684ee2/raw/24e9834c6fce7a79e7f417b69107e578a15d1ef8/chromozome1.csv', + sample: 'D2409912,D2409911,D2320995', thumbnail: SRR7890905 - }, - { - group: 'default', - id: 'SRR7890905_Hartwig', - cancer: 'breast', - assembly: 'hg38', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/SNV_test_tumor_normal_with_panel.vcf.gz', - vcfIndex: 'https://somatic-browser-test.s3.amazonaws.com/SNV_test_tumor_normal_with_panel.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/INDEL_test_tumor_normal_with_panel.vcf.gz', - vcf2Index: 'https://somatic-browser-test.s3.amazonaws.com/INDEL_test_tumor_normal_with_panel.vcf.gz.tbi', - sv: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905/SRR7890905.gripss.filtered.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905/SRR7890905.purple.cnv.somatic.reformatted.tsv', - bam: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam', - bai: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam.bai', - note: 'CNV profile - Purple. SVs - Gridss. Mutations and indels - Sentieon', - thumbnail: SRR7890905_Hartwig - }, - { - group: 'default', - id: '7a921087-8e62-4a93-a757-fd8cdbe1eb8f', - cancer: 'ovarian', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7a921087-8e62-4a93-a757-fd8cdbe1eb8f.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - // drivers: 'https://gist.githubusercontent.com/sehilyi/01096d32326da8e7f9acc48dd9790332/raw/4f2bbd96ce3d2b6637bd733674f60e197e4d4da9/json-driver.json', // json example - thumbnail: _7a921087 - }, - { - group: 'default', - id: '7d332cb1-ba25-47e4-8bf8-d25e14f40d59', - cancer: 'sarcoma', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/7d332cb1-ba25-47e4-8bf8-d25e14f40d59.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _7d332cb1 - }, - { - group: 'default', - id: '9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa', - cancer: 'kidney', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/9ae0744a-9bc1-4cd7-b7cf-c6569ed9e4aa.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _9ae0744a - }, - { - group: 'default', - id: 'b27d75ba-5989-4200-bfe9-f1b7d7cf8008', - cancer: 'breast', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/b27d75ba-5989-4200-bfe9-f1b7d7cf8008.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _b27d75ba - }, - { - group: 'default', - id: 'fc8edf46-2005-1af4-e040-11ac0d481414', - cancer: 'breast', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/fc8edf46-2005-1af4-e040-11ac0d481414.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/fc8edf46-2005-1af4-e040-11ac0d481414.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/fc8edf46-2005-1af4-e040-11ac0d481414.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/fc8edf46-2005-1af4-e040-11ac0d481414.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/fc8edf46-2005-1af4-e040-11ac0d481414.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/browserExamples/fc8edf46-2005-1af4-e040-11ac0d481414.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _fc8edf46 - }, - // { - // group: 'default', - // id: 'SRR7890905', - // cancer: 'breast', - // assembly: 'hg38', - // sv: 'https://somatic-browser-test.s3.amazonaws.com/SVTYPE_SV_test_tumor_normal_with_panel.bedpe', - // cnv: 'https://gist.githubusercontent.com/sehilyi/6fbceae35756b13472332d6b81b10803/raw/596428a8b0ebc00e7f8cbc52b050db0fbd6e19a5/SRR7890943.ascat.v3.cnv.tsv', - // bam: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam', - // bai: 'https://somatic-browser-test.s3.amazonaws.com/SRR7890905_GAPFI2USVS21.bam.bai', - // thumbnail: _SRR7890905 - // }, - { - group: 'default', - id: 'bc0dee07-de20-44d6-be65-05af7e63ac96', // GACA-CN- - cancer: 'gastric', - assembly: 'hg19', - sv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.pcawg_consensus_1.6.161116.somatic.sv.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.consensus.20170119.somatic.cna.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/bc0dee07-de20-44d6-be65-05af7e63ac96.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _bc0dee07 - }, - { - group: 'default', - id: 'f1504811-8363-41e6-b43c-62452b1262d3', // OV-AU- - cancer: 'ovarian', - assembly: 'hg19', - sv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.pcawg_consensus_1.6.161116.somatic.sv.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.consensus.20170119.somatic.cna.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/f1504811-8363-41e6-b43c-62452b1262d3.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _f1504811 - }, - { - group: 'default', - id: '89dad92e-5b3f-479a-a6da-a94ee7df7f8a', // OV-AU- - cancer: 'ovarian', - assembly: 'hg19', - sv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.pcawg_consensus_1.6.161116.somatic.sv.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.consensus.20170119.somatic.cna.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/89dad92e-5b3f-479a-a6da-a94ee7df7f8a.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _89dad92e - }, - { - group: 'default', - id: 'b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be', // OV-US- - cancer: 'ovarian serous cystadenocarcinoma', - assembly: 'hg19', - sv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.pcawg_consensus_1.6.161116.somatic.sv.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.consensus.20170119.somatic.cna.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/b243adb4-b3e7-4e0e-bc0d-625aa8dbb1be.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _b243adb4 - }, - { - group: 'default', - id: '84ca6ab0-9edc-4636-9d27-55cdba334d7d', - cancer: 'ovarian', - assembly: 'hg19', - sv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/84ca6ab0-9edc-4636-9d27-55cdba334d7d.pcawg_consensus_1.6.161022.somatic.sv.bedpe', - cnv: 'https://s3.amazonaws.com/gosling-lang.org/data/SV/84ca6ab0-9edc-4636-9d27-55cdba334d7d.consensus.20170119.somatic.cna.annotated.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/84ca6ab0-9edc-4636-9d27-55cdba334d7d.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/84ca6ab0-9edc-4636-9d27-55cdba334d7d.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/84ca6ab0-9edc-4636-9d27-55cdba334d7d.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/84ca6ab0-9edc-4636-9d27-55cdba334d7d.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _84ca6ab0 - }, - { - group: 'default', - id: '0bfd1043-816e-e3e4-e050-11ac0c4860c5', // OV-US- - cancer: 'prostate adenocarcinoma', - assembly: 'hg19', - sv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.pcawg_consensus_1.6.161116.somatic.sv.bedpe', - cnv: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.consensus.20170119.somatic.cna.txt', - vcf: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz', - vcfIndex: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.consensus.20160830.somatic.snv_mnv.sorted.vcf.gz.tbi', - vcf2: 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.consensus.20161006.somatic.indel.sorted.vcf.gz', - vcf2Index: - 'https://somatic-browser-test.s3.amazonaws.com/cdk12cancers/0bfd1043-816e-e3e4-e050-11ac0c4860c5.consensus.20161006.somatic.indel.sorted.vcf.gz.tbi', - thumbnail: _0bfd1043 } ]; diff --git a/src/icon.ts b/src/icon.ts index 32d46f52..76a7a041 100644 --- a/src/icon.ts +++ b/src/icon.ts @@ -333,5 +333,13 @@ export const ICONS: Record = { ], stroke: 'currentColor', fill: 'none' + }, + TRIANGLE_DOWN: { + width: 11, + height: 7, + viewBox: '0 0 11 7', + path: ['M0.5 1H10.5L5.5 6L0.5 1Z', 'M5.5 6L0.5 1H10.5L5.5 6ZM5.5 6V5.28571'], + stroke: 'currentColor', + fill: 'none' } }; diff --git a/src/lib/urlsafe-codec.ts b/src/lib/urlsafe-codec.ts new file mode 100644 index 00000000..ac24f09c --- /dev/null +++ b/src/lib/urlsafe-codec.ts @@ -0,0 +1,72 @@ +import pako from 'pako'; +import base64Js from 'base64-js'; + +/** + * UrlsafeCodec provides static methods to encode and decode samples + * to and from a URL-safe base64 encoded string. It uses JSON for serialization, + * pako for compression, and base64-js for handling base64 encoding. + */ +class UrlsafeCodec { + /** + * Encodes a sample object into a URL-safe base64 string. + * + * The method serializes the sample to a JSON string, compresses it using pako, + * By default, the header check is turned off. If headerCheck is set to true, the method + * will perform zlib/gzip header and checksum verification during decompression using pako. + * converts the compressed data to a base64 string, and then modifies the base64 string + * to make it URL-safe by replacing '+' with '.', '/' with '_', and '=' with '-'. + * + * @param {Object} sample - The sample object to encode. + * @param {boolean} headerCheck - Optional parameter to enable header and checksum verification. + * @returns {string} A URL-safe base64 encoded string representing the sample. + */ + static encode(sample, headerCheck = false) { + try { + const string = JSON.stringify(sample); + const encoder = new TextEncoder(); + const stringAsUint8Array = encoder.encode(string); + // Set 'raw' to true if headerCheck is false + const compressedUint8Array = pako.deflate(stringAsUint8Array, { raw: !headerCheck }); + const base64Bytes = base64Js.fromByteArray(compressedUint8Array); + const base64Blob = base64Bytes.toString(); + const base64UrlsafeBlob = base64Blob.replace(/\+/g, '.').replace(/\//g, '_').replace(/=/g, '-'); + return base64UrlsafeBlob; + } catch (error) { + console.error('Error encoding sample:', error); + // Handle the error or rethrow, depending on your needs + throw error; + } + } + + /** + * Decodes a URL-safe base64 string back into a sample object. + * + * The method reverses the URL-safe transformation by replacing '.', '_', and '-' + * with '+', '/', and '=' respectively. It then converts the base64 string back to bytes. + * By default, the header check is turned off. If headerCheck is set to true, the method + * will perform zlib/gzip header and checksum verification during decompression using pako. + * Finally, it parses the JSON string to reconstruct the original sample object. + * + * @param {string} encodedString - The URL-safe base64 encoded string to decode. + * @param {boolean} headerCheck - Optional parameter to enable header and checksum verification. + * @returns {Object} The original sample object. + */ + static decode(encodedString, headerCheck = false) { + try { + const base64Blob = encodedString.replace(/\./g, '+').replace(/_/g, '/').replace(/-/g, '='); + const compressedUint8Array = base64Js.toByteArray(base64Blob); + // Set 'raw' to true if headerCheck is false + const bytes = pako.inflate(compressedUint8Array, { raw: !headerCheck }); + const decoder = new TextDecoder(); + const string = decoder.decode(bytes); + const sample = JSON.parse(string); + return sample; + } catch (error) { + console.error('Error decoding string:', error); + // Handle the error or rethrow, depending on your needs + throw error; + } + } +} + +export default UrlsafeCodec; diff --git a/src/main-spec.ts b/src/main-spec.ts index e4c97a40..216ee777 100644 --- a/src/main-spec.ts +++ b/src/main-spec.ts @@ -1,34 +1,26 @@ import { GoslingSpec } from 'gosling.js'; -import { Assembly, MultipleViews, SingleTrack, SingleView, View } from 'gosling.js/dist/src/core/gosling.schema'; +import { View } from 'gosling.js/dist/src/gosling-schema'; import getMidView from './mid-spec'; -import { alignment } from './alignment'; -import { verticalGuide } from './vertical-guide'; import tracks from './track'; import { SampleType } from './data/samples'; -import { driversToTsvUrl } from './utils'; export interface SpecOption extends SampleType { showOverview: boolean; - showPutativeDriver: boolean; xDomain?: [number, number]; xOffset: number; width: number; - drivers: { [k: string]: string | number }[] | string; - selectedSvId: string; breakpoints: [number, number, number, number]; - svReads: { name: string; type: string }[]; crossChr: boolean; bpIntervals: [number, number, number, number] | undefined; + spacing: number; } function generateSpec(opt: SpecOption): GoslingSpec { - const { assembly, id, bam, bai, width, selectedSvId, breakpoints, bpIntervals } = opt; + const { assembly, width, spacing } = opt; - const topViewWidth = Math.min(width, 600); + const topViewWidth = Math.min(width, 600);; const midViewWidth = width; const bottomViewGap = 19; - const bottomViewWidth = width / 2.0 - bottomViewGap / 2.0; - const topViewXOffset = (width - topViewWidth) / 2.0; // console.log(getOverviewSpec({ // ...option, // width: topViewWidth, @@ -37,9 +29,9 @@ function generateSpec(opt: SpecOption): GoslingSpec { return { layout: 'linear', arrangement: 'vertical', - centerRadius: 0.5, + centerRadius: 0.2, assembly, - spacing: 40, + spacing, style: { outlineWidth: 1, outline: 'lightgray', @@ -47,229 +39,37 @@ function generateSpec(opt: SpecOption): GoslingSpec { }, views: [ { - arrangement: 'vertical', + arrangement: 'horizontal', // Horizontal arrangement of overview views views: [ ...getOverviewSpec({ ...opt, width: topViewWidth, - xOffset: topViewXOffset + xOffset: 0 }), - ...getMidView({ + ...getOverviewLin({ ...opt, - width: midViewWidth + width: topViewWidth - 40 }) ] }, - ...(selectedSvId === '' - ? [] - : ([ - { - arrangement: 'horizontal', - spacing: bottomViewGap, - views: [ - { - static: false, - zoomLimits: [50, 1000], - layout: 'linear', - centerRadius: 0.05, - xDomain: { interval: [breakpoints[0], breakpoints[1]] }, - spacing: 0.01, - linkingId: 'detail-scale-1', - tracks: [ - ...(opt.bam && opt.bai - ? [ - { - ...tracks.coverage({ ...opt, width: bottomViewWidth }, true) - }, - ...(bpIntervals ? [verticalGuide(bpIntervals[0], bpIntervals[1])] : []) - ] - : []), - { - id: `${id}-bottom-left-sequence`, - title: 'Sequence', - alignment: 'overlay', - data: { - url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=sequence-multivec', - type: 'multivec', - row: 'base', - column: 'position', - value: 'count', - categories: ['A', 'T', 'G', 'C'], - start: 'start', - end: 'end' - }, - tracks: [ - { - mark: 'bar', - y: { - field: 'count', - type: 'quantitative', - axis: 'none' - } - }, - { - dataTransform: [ - { - type: 'filter', - field: 'count', - oneOf: [0], - not: true - } - ], - mark: 'text', - x: { field: 'start', type: 'genomic' }, - xe: { field: 'end', type: 'genomic' }, - size: { value: 24 }, - color: { value: 'white' }, - visibility: [ - { - operation: 'less-than', - measure: 'width', - threshold: '|xe-x|', - transitionPadding: 30, - target: 'mark' - }, - { - operation: 'LT', - measure: 'zoomLevel', - threshold: 10, - target: 'track' - } - ] - } - ], - x: { field: 'position', type: 'genomic' }, - color: { - field: 'base', - type: 'nominal', - domain: ['A', 'T', 'G', 'C'], - legend: true - }, - text: { field: 'base', type: 'nominal' }, - style: { inlineLegend: true }, - width: bottomViewWidth, - height: 40 - }, - ...(opt.bam && opt.bai - ? [ - { - ...alignment({ ...opt, width: bottomViewWidth }, true) - } - ] - : []), - ...(bpIntervals ? [verticalGuide(bpIntervals[0], bpIntervals[1])] : []) - ] - }, - { - static: false, - zoomLimits: [50, 1000], - layout: 'linear', - centerRadius: 0.05, - xDomain: { interval: [breakpoints[2], breakpoints[3]] }, - spacing: 0.01, - linkingId: 'detail-scale-2', - tracks: [ - ...(opt.bam && opt.bai - ? [ - { - ...tracks.coverage({ ...opt, width: bottomViewWidth }, false) - }, - ...(bpIntervals ? [verticalGuide(bpIntervals[2], bpIntervals[3])] : []) - ] - : []), - { - id: `${id}-bottom-right-sequence`, - title: 'Sequence', - alignment: 'overlay', - data: { - url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=sequence-multivec', - type: 'multivec', - row: 'base', - column: 'position', - value: 'count', - categories: ['A', 'T', 'G', 'C'], - start: 'start', - end: 'end' - }, - tracks: [ - { - mark: 'bar', - y: { - field: 'count', - type: 'quantitative', - axis: 'none' - } - }, - { - dataTransform: [ - { - type: 'filter', - field: 'count', - oneOf: [0], - not: true - } - ], - mark: 'text', - x: { field: 'start', type: 'genomic' }, - xe: { field: 'end', type: 'genomic' }, - size: { value: 24 }, - color: { value: 'white' }, - visibility: [ - { - operation: 'less-than', - measure: 'width', - threshold: '|xe-x|', - transitionPadding: 30, - target: 'mark' - }, - { - operation: 'LT', - measure: 'zoomLevel', - threshold: 10, - target: 'track' - } - ] - } - ], - x: { field: 'position', type: 'genomic' }, - color: { - field: 'base', - type: 'nominal', - domain: ['A', 'T', 'G', 'C'], - legend: true - }, - text: { field: 'base', type: 'nominal' }, - style: { inlineLegend: true }, - width: bottomViewWidth, - height: 40 - }, - ...(opt.bam && opt.bai - ? [ - { - ...alignment({ ...opt, width: bottomViewWidth }, false) - } - ] - : []), - ...(bpIntervals ? [verticalGuide(bpIntervals[2], bpIntervals[3])] : []) - ] - } - ] - } - ] as (SingleView | MultipleViews)[])) + ...getMidView({ + ...opt, + width: midViewWidth + }) ] }; } function getOverviewSpec(option: SpecOption): View[] { - const { assembly, id, cnv, sv, width, showPutativeDriver, showOverview, selectedSvId, xOffset, drivers, cnFields } = + const { assembly, id, summary, roi, width, showOverview, xOffset } = option; if (!showOverview) return []; return [ + { xOffset, - static: true, layout: 'circular', spacing: 1, style: { @@ -278,6 +78,7 @@ function getOverviewSpec(option: SpecOption): View[] { }, tracks: [ { + title: 'Ideogram', id: `${id}-top-ideogram`, alignment: 'overlay', data: { @@ -311,15 +112,54 @@ function getOverviewSpec(option: SpecOption): View[] { xe: { field: 'chromEnd', type: 'genomic' }, strokeWidth: { value: 0 }, width, - height: 100 + height: 18 }, - tracks.driver(id, driversToTsvUrl(drivers), width, 40, 'top'), - tracks.boundary('driver', 'top'), - tracks.gain(id, cnv, width, 40, 'top', cnFields), - tracks.boundary('gain', 'top'), - tracks.loh(id, cnv, width, 40, 'top', cnFields), - tracks.boundary('loh', 'top'), - tracks.sv(id, sv, width, 80, 'top', selectedSvId) + tracks.roi(`${id}-mid-ideogram`, roi, 'top'), + ...(!summary + ? [] + : [tracks.GQdetail(id, summary, width, 40, 'top'), + tracks.boundary('GQdetail', 'top'), + tracks.roi('GQdetail', roi, 'top'), + tracks.AFdetail(id, summary, width, 40, 'top'), + tracks.boundary('AFdetail', 'top'), + tracks.roi('AFdetail', roi, 'top'), + tracks.DPdetail(id, summary, width, 40, 'top'), + tracks.boundary('DPdetail', 'top'), + tracks.roi('DPdetail', roi, 'top'), + tracks.PLdetail(id, summary, width, 40, 'top'), + tracks.boundary('PLdetail', 'top'), + tracks.roi('PLdetail', roi, 'top') + ]) + ] + } + ]; +} + +function getOverviewLin(option: SpecOption): View[] { + const { id, binStats, width, showOverview, xOffset } = + option; + + if (!showOverview) return []; + + return [ + + { + xOffset, + static: true, + layout: 'linear', + spacing: 5, + style: { + outlineWidth: 1, + outline: 'lightgray' + }, + tracks: [ + ...(!binStats + ? [] + : [tracks.GQbin(id, binStats, width, 120, 'top'), + tracks.AFbin(id, binStats, width, 120, 'top'), + tracks.DPbin(id, binStats, width, 120, 'top'), + //tracks.PLbin(id, af, width, 120, 'top') + ]), ] } ]; diff --git a/src/main.tsx b/src/main.tsx index 687d69a6..0d8c7dc7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,10 +2,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; import App from './App'; +import JsonBase64Converter from './ui/json-base64-converter'; ReactDOM.render( - + + , document.getElementById('root') ); diff --git a/src/mid-spec.ts b/src/mid-spec.ts index cfaad5c0..bcd1d892 100644 --- a/src/mid-spec.ts +++ b/src/mid-spec.ts @@ -1,5 +1,5 @@ import { SpecOption } from './main-spec'; -import { SingleTrack, View } from 'gosling.js/dist/src/core/gosling.schema'; +import { SingleTrack, View } from 'gosling.js/dist/src/gosling-schema'; import tracks from './track'; import { driversToTsvUrl } from './utils'; @@ -10,17 +10,17 @@ export default function getMidView(option: SpecOption): View[] { xDomain, vcf, vcfIndex, - vcf2, - vcf2Index, cnv, - sv, + baf, + baf_server, + me, + me2, + pm, + haplo, + roi, width, - showPutativeDriver, - showOverview, - xOffset, - selectedSvId, - drivers, - cnFields + cnFields, + yOffset } = option; return [ { @@ -29,6 +29,7 @@ export default function getMidView(option: SpecOption): View[] { layout: 'linear', tracks: [ { + title: '  Ideogram', id: `${id}-mid-ideogram`, alignment: 'overlay', data: { @@ -80,87 +81,97 @@ export default function getMidView(option: SpecOption): View[] { width, height: 18 }, - tracks.driver(id, driversToTsvUrl(drivers), width, 40, 'mid'), - tracks.boundary('driver', 'mid'), + tracks.roi(`${id}-mid-ideogram`, roi, 'mid'), + ...(!baf + ? [] + : [tracks.baf(id, baf, width, 240, 'mid')]), + ...(!baf_server + ? [] + :[tracks.biAlleleFrequency(id, baf_server, width, 240, 'mid')]), + ...(!haplo + ? [] + : [tracks.haplo(id, haplo, width, 300, 'mid'), + tracks.boundary('haplo', 'mid'), + tracks.roi('haplo', roi, 'mid') + ]), + ...(!vcf + ? [] + : [tracks.mutation(id, vcf, vcfIndex, width, 60, 'mid'), tracks.boundary('mutation', 'mid'), + tracks.boundary('mutation', 'mid'), + tracks.roi('mutation', roi, 'mid') + ]), + ...(!cnv + ? [] + : [tracks.cnv(id, cnv, width, 200, 'mid'), + tracks.boundary('cnv', 'mid'), + tracks.roi('cnv', roi, 'mid'), + ]), + ...(!me + ? [] + : [tracks.mendelianErrors(id, me, width, 60, 'mid', cnFields), + tracks.mendelianErrors2('me', me2, 'mid', cnFields), + tracks.boundary('me', 'mid'), + tracks.roi('me', roi, 'mid') + ]), + ...(!pm + ? [] + : [tracks.parentMapping(id, pm, width, 40, 'mid'), + tracks.boundary('pm', 'mid'), + tracks.roi('pm', roi, 'mid') + ]), { - id: `${id}-mid-gene`, - template: 'gene', + id: `${id}-mid-ideogram-bottom`, + alignment: 'overlay', data: { url: - assembly === 'hg19' - ? // TODO: change to gosling's one - 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation-hg19' - : 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', - type: 'beddb', - genomicFields: [ - { index: 1, name: 'start' }, - { index: 2, name: 'end' } - ], - valueFields: [ - { index: 5, name: 'strand', type: 'nominal' }, - { index: 3, name: 'name', type: 'nominal' } - ], - exonIntervalFields: [ - { index: 12, name: 'start' }, - { index: 13, name: 'end' } - ] - }, - encoding: { - startPosition: { field: 'start' }, - endPosition: { field: 'end' }, - strandColor: { field: 'strand', range: ['gray'] }, - strandRow: { field: 'strand' }, - opacity: { value: 0.4 }, - geneHeight: { value: 60 / 3.0 }, - geneLabel: { field: 'name' }, - geneLabelFontSize: { value: 60 / 3.0 }, - geneLabelColor: { field: 'strand', range: ['black'] }, - geneLabelStroke: { value: 'white' }, - geneLabelStrokeThickness: { value: 4 }, - geneLabelOpacity: { value: 1 }, - type: { field: 'type' } + assembly === 'hg38' + ? 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv' + : 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG19.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'] }, - tooltip: [ - { field: 'name', type: 'nominal' }, - { field: 'strand', type: 'nominal' } + tracks: [ + { + mark: 'rect', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'], + not: true + } + ] + }, + { + mark: 'triangleRight', + dataTransform: [ + { type: 'filter', field: 'Stain', oneOf: ['acen'] }, + { type: 'filter', field: 'Name', include: 'q' } + ] + }, + { + mark: 'triangleLeft', + dataTransform: [ + { type: 'filter', field: 'Stain', oneOf: ['acen'] }, + { type: 'filter', field: 'Name', include: 'p' } + ] + } ], + color: { + field: 'Stain', + type: 'nominal', + domain: ['gneg', 'gpos25', 'gpos50', 'gpos75', 'gpos100', 'gvar', 'acen'], + range: ['white', 'lightgray', 'gray', 'gray', 'black', '#7B9CC8', '#DC4542'] + }, + size: { value: 18 }, + x: { field: 'chromStart', type: 'genomic', axis: 'bottom' }, + xe: { field: 'chromEnd', type: 'genomic' }, + strokeWidth: { value: 0 }, width, - height: 60 + height: 18 }, - ...(!vcf - ? [] - : [tracks.mutation(id, vcf, vcfIndex, width, 60, 'mid'), tracks.boundary('mutation', 'mid')]), - ...(!vcf2 - ? [] - : [tracks.indel(id, vcf2, vcf2Index, width, 40, 'mid'), tracks.boundary('indel', 'mid')]), - tracks.cnv(id, cnv, width, 60, 'mid', cnFields), - tracks.boundary('cnv', 'mid'), - tracks.gain(id, cnv, width, 20, 'mid', cnFields), - tracks.boundary('gain', 'mid'), - tracks.loh(id, cnv, width, 20, 'mid', cnFields), - tracks.boundary('loh', 'mid'), - tracks.sv(id, sv, width, 250, 'mid', selectedSvId) - // { - // id: `${id}-${'mid'}-sv`, - // data: { - // type: 'json', - // values: [ - // { c: 'chr1', p1: 1, p2: 4000000000, v: 250 / 4 }, - // { c: 'chr1', p1: 1, p2: 4000000000, v: (250 / 4) * 2 }, - // { c: 'chr1', p1: 1, p2: 4000000000, v: (250 / 4) * 3 } - // ], - // chromosomeField: 'c', - // genomicFields: ['p1', 'p2'] - // }, - // mark: 'rule', - // x: { field: 'p1', type: 'genomic' }, - // xe: { field: 'p2', type: 'genomic' }, - // y: { field: 'v', type: 'quantitative', domain: [0, 250], axis: 'none' }, - // strokeWidth: { value: 0.5 }, - // color: { value: '#E3E3E3' }, - // width, - // height: 250 - // }, + tracks.roi(`${id}-mid-ideogram-bottom`, roi, 'mid'), ] } ]; diff --git a/src/script/img/modal_images/genome_view/interactions_1.png b/src/script/img/modal_images/genome_view/interactions_1.png new file mode 100644 index 00000000..85029707 Binary files /dev/null and b/src/script/img/modal_images/genome_view/interactions_1.png differ diff --git a/src/script/img/modal_images/genome_view/interactions_2.png b/src/script/img/modal_images/genome_view/interactions_2.png new file mode 100644 index 00000000..96629a42 Binary files /dev/null and b/src/script/img/modal_images/genome_view/interactions_2.png differ diff --git a/src/script/img/modal_images/genome_view/interactions_3.png b/src/script/img/modal_images/genome_view/interactions_3.png new file mode 100644 index 00000000..b2dcc7fc Binary files /dev/null and b/src/script/img/modal_images/genome_view/interactions_3.png differ diff --git a/src/script/img/modal_images/genome_view/interactions_4.png b/src/script/img/modal_images/genome_view/interactions_4.png new file mode 100644 index 00000000..58e46f1d Binary files /dev/null and b/src/script/img/modal_images/genome_view/interactions_4.png differ diff --git a/src/script/img/modal_images/genome_view/interactions_5.png b/src/script/img/modal_images/genome_view/interactions_5.png new file mode 100644 index 00000000..fcf94f47 Binary files /dev/null and b/src/script/img/modal_images/genome_view/interactions_5.png differ diff --git a/src/script/img/modal_images/genome_view/interpretation_1.png b/src/script/img/modal_images/genome_view/interpretation_1.png new file mode 100644 index 00000000..d07c7d56 Binary files /dev/null and b/src/script/img/modal_images/genome_view/interpretation_1.png differ diff --git a/src/script/img/modal_images/variant_view/interactions_1.png b/src/script/img/modal_images/variant_view/interactions_1.png new file mode 100644 index 00000000..dd8d35ee Binary files /dev/null and b/src/script/img/modal_images/variant_view/interactions_1.png differ diff --git a/src/script/img/modal_images/variant_view/interactions_2.png b/src/script/img/modal_images/variant_view/interactions_2.png new file mode 100644 index 00000000..2aa9c43e Binary files /dev/null and b/src/script/img/modal_images/variant_view/interactions_2.png differ diff --git a/src/script/img/modal_images/variant_view/interactions_3.png b/src/script/img/modal_images/variant_view/interactions_3.png new file mode 100644 index 00000000..a6aaa830 Binary files /dev/null and b/src/script/img/modal_images/variant_view/interactions_3.png differ diff --git a/src/script/img/modal_images/variant_view/interactions_4.png b/src/script/img/modal_images/variant_view/interactions_4.png new file mode 100644 index 00000000..1bcc23f4 Binary files /dev/null and b/src/script/img/modal_images/variant_view/interactions_4.png differ diff --git a/src/script/img/modal_images/variant_view/interactions_5.png b/src/script/img/modal_images/variant_view/interactions_5.png new file mode 100644 index 00000000..deebcaae Binary files /dev/null and b/src/script/img/modal_images/variant_view/interactions_5.png differ diff --git a/src/script/img/modal_images/variant_view/interpretation_1.png b/src/script/img/modal_images/variant_view/interpretation_1.png new file mode 100644 index 00000000..e2ccaf77 Binary files /dev/null and b/src/script/img/modal_images/variant_view/interpretation_1.png differ diff --git a/src/script/img/popover-images/alignment/interactions_1.png b/src/script/img/popover-images/alignment/interactions_1.png new file mode 100644 index 00000000..cd79eac4 Binary files /dev/null and b/src/script/img/popover-images/alignment/interactions_1.png differ diff --git a/src/script/img/popover-images/alignment/interpretation_1.png b/src/script/img/popover-images/alignment/interpretation_1.png new file mode 100644 index 00000000..6ba70da4 Binary files /dev/null and b/src/script/img/popover-images/alignment/interpretation_1.png differ diff --git a/src/script/img/popover-images/alignment/interpretation_2.png b/src/script/img/popover-images/alignment/interpretation_2.png new file mode 100644 index 00000000..a54b38ad Binary files /dev/null and b/src/script/img/popover-images/alignment/interpretation_2.png differ diff --git a/src/script/img/popover-images/alignment/interpretation_3.png b/src/script/img/popover-images/alignment/interpretation_3.png new file mode 100644 index 00000000..287b2f65 Binary files /dev/null and b/src/script/img/popover-images/alignment/interpretation_3.png differ diff --git a/src/script/img/popover-images/baf/interaction_1.png b/src/script/img/popover-images/baf/interaction_1.png new file mode 100644 index 00000000..565d8d5d Binary files /dev/null and b/src/script/img/popover-images/baf/interaction_1.png differ diff --git a/src/script/img/popover-images/baf/interpretation_1.png b/src/script/img/popover-images/baf/interpretation_1.png new file mode 100644 index 00000000..5c2df654 Binary files /dev/null and b/src/script/img/popover-images/baf/interpretation_1.png differ diff --git a/src/script/img/popover-images/copy_number_variants/interactions_1.png b/src/script/img/popover-images/copy_number_variants/interactions_1.png new file mode 100644 index 00000000..157d56e2 Binary files /dev/null and b/src/script/img/popover-images/copy_number_variants/interactions_1.png differ diff --git a/src/script/img/popover-images/copy_number_variants/interpretation_1.png b/src/script/img/popover-images/copy_number_variants/interpretation_1.png new file mode 100644 index 00000000..5d5f8f62 Binary files /dev/null and b/src/script/img/popover-images/copy_number_variants/interpretation_1.png differ diff --git a/src/script/img/popover-images/copy_number_variants/interpretation_2.png b/src/script/img/popover-images/copy_number_variants/interpretation_2.png new file mode 100644 index 00000000..a4e0dd27 Binary files /dev/null and b/src/script/img/popover-images/copy_number_variants/interpretation_2.png differ diff --git a/src/script/img/popover-images/haplo/interaction_1.png b/src/script/img/popover-images/haplo/interaction_1.png new file mode 100644 index 00000000..dd5100ea Binary files /dev/null and b/src/script/img/popover-images/haplo/interaction_1.png differ diff --git a/src/script/img/popover-images/haplo/interpretation_1.png b/src/script/img/popover-images/haplo/interpretation_1.png new file mode 100644 index 00000000..9f1391b6 Binary files /dev/null and b/src/script/img/popover-images/haplo/interpretation_1.png differ diff --git a/src/script/img/popover-images/ideogram/interpretation_1.png b/src/script/img/popover-images/ideogram/interpretation_1.png new file mode 100644 index 00000000..088620e4 Binary files /dev/null and b/src/script/img/popover-images/ideogram/interpretation_1.png differ diff --git a/src/script/img/popover-images/ideogram/interpretation_2.png b/src/script/img/popover-images/ideogram/interpretation_2.png new file mode 100644 index 00000000..e66c1bbc Binary files /dev/null and b/src/script/img/popover-images/ideogram/interpretation_2.png differ diff --git a/src/script/img/popover-images/mendelian-errors/interactions_1.png b/src/script/img/popover-images/mendelian-errors/interactions_1.png new file mode 100644 index 00000000..e4f26933 Binary files /dev/null and b/src/script/img/popover-images/mendelian-errors/interactions_1.png differ diff --git a/src/script/img/popover-images/mendelian-errors/interactions_2.png b/src/script/img/popover-images/mendelian-errors/interactions_2.png new file mode 100644 index 00000000..3d16cf07 Binary files /dev/null and b/src/script/img/popover-images/mendelian-errors/interactions_2.png differ diff --git a/src/script/img/popover-images/mendelian-errors/interpretation_1.png b/src/script/img/popover-images/mendelian-errors/interpretation_1.png new file mode 100644 index 00000000..e2315c0d Binary files /dev/null and b/src/script/img/popover-images/mendelian-errors/interpretation_1.png differ diff --git a/src/script/img/popover-images/mendelian-errors/interpretation_2.png b/src/script/img/popover-images/mendelian-errors/interpretation_2.png new file mode 100644 index 00000000..43438470 Binary files /dev/null and b/src/script/img/popover-images/mendelian-errors/interpretation_2.png differ diff --git a/src/script/img/popover-images/mutations/interactions_1.png b/src/script/img/popover-images/mutations/interactions_1.png new file mode 100644 index 00000000..90e754b4 Binary files /dev/null and b/src/script/img/popover-images/mutations/interactions_1.png differ diff --git a/src/script/img/popover-images/mutations/interactions_2.png b/src/script/img/popover-images/mutations/interactions_2.png new file mode 100644 index 00000000..f5cf3c05 Binary files /dev/null and b/src/script/img/popover-images/mutations/interactions_2.png differ diff --git a/src/script/img/popover-images/mutations/interpretation_1.png b/src/script/img/popover-images/mutations/interpretation_1.png new file mode 100644 index 00000000..a8f1a917 Binary files /dev/null and b/src/script/img/popover-images/mutations/interpretation_1.png differ diff --git a/src/script/img/popover-images/parent-mapping/interactions_1.png b/src/script/img/popover-images/parent-mapping/interactions_1.png new file mode 100644 index 00000000..870a1ca1 Binary files /dev/null and b/src/script/img/popover-images/parent-mapping/interactions_1.png differ diff --git a/src/script/img/popover-images/parent-mapping/interpretation_1.png b/src/script/img/popover-images/parent-mapping/interpretation_1.png new file mode 100644 index 00000000..de6c0749 Binary files /dev/null and b/src/script/img/popover-images/parent-mapping/interpretation_1.png differ diff --git a/src/script/img/popover-images/sequence/interpretation_1.png b/src/script/img/popover-images/sequence/interpretation_1.png new file mode 100644 index 00000000..ce8ceb62 Binary files /dev/null and b/src/script/img/popover-images/sequence/interpretation_1.png differ diff --git a/src/script/overview-specs-to-imgs.sh b/src/script/overview-specs-to-imgs.sh old mode 100755 new mode 100644 diff --git a/src/track/AFbin.ts b/src/track/AFbin.ts new file mode 100644 index 00000000..f410ebe5 --- /dev/null +++ b/src/track/AFbin.ts @@ -0,0 +1,74 @@ +import { StackedTracks, OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function AFbin( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + const sampleId2 = 'D2409912'; + return { + id: `${sampleId}-${mode}-AFbin`, + title: 'Allele Frequency Spectrum', + style: { background: '#F6F6F6', inlineLegend: true }, + alignment: 'overlay', + data: { + separator: '\t', + url: url, + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['Midpoint'], + sampleLength: 1500 + }, + tracks: [ + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['genome'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic', axis: 'none' }, + y: { field: 'Amount_log_AF', type: 'quantitative', axis: 'left', range: [0, height - 20]}, + size: { value: width / 50 }, + color: { value: '#56B4E9' }, + stroke: {value: 'white'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'GT', + measure: 'zoomLevel' + }], + }, + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['chrom'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic', axis: 'none' }, + y: { field: 'Amount_log_AF', type: 'quantitative', axis: 'left', range: [0, height - 20] }, + size: { value: width / 65 }, + color: { value: '#56B4E9' }, + stroke: {value: '#F6F6F6'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + } + ], + width, + height, + tooltip: [ + { field: 'AF_bin', type: 'nominal', alt: 'Allele Frequency' }, + { field: 'Amount_AF', type: 'nominal', alt: 'Amount' } + ] + }; +} diff --git a/src/track/AFdetail.ts b/src/track/AFdetail.ts new file mode 100644 index 00000000..d2647235 --- /dev/null +++ b/src/track/AFdetail.ts @@ -0,0 +1,37 @@ +import { SingleTrack } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function AFdetail( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): SingleTrack { + const sampleId2 = 'D2323937'; + return { + id: `${sampleId}-${sampleId2}-${mode}-AFdetail`, + title: 'Allele Frequency', + style: { background: '#FFFFFF', inlineLegend: true }, + data: { + separator: ',', + url: url, + type: 'csv', + chromosomeField: `${sampleId2}.CHROM`, + genomicFields: [`${sampleId2}.POS`] + }, + mark: 'bar', + x: { field: `${sampleId2}.POS`, type: 'genomic' }, + y: { field: `${sampleId2}.AF`, type: 'quantitative', axis: 'left' }, + color: { value: '#56B4E9' }, + tooltip: [ + { field: `${sampleId2}.POS`, type: 'genomic', alt: 'Position'}, + { field: `${sampleId2}.REF`, type: 'nominal', alt: 'Reference Allele'}, + { field: `${sampleId2}.ALT`, type: 'nominal', alt: 'Alternative Allele'}, + { field: `${sampleId2}.GT`, type: 'nominal', alt: 'Genotype'}, + { field: `${sampleId2}.AF`, type: 'nominal', alt: 'Allele Frequency' } + ], + width, + height + }; +} diff --git a/src/track/Bi_allel_via_higlass.ts b/src/track/Bi_allel_via_higlass.ts new file mode 100644 index 00000000..2e389325 --- /dev/null +++ b/src/track/Bi_allel_via_higlass.ts @@ -0,0 +1,59 @@ +import { OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function biAlleleFrequency( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + return { + id: `${sampleId}-${mode}-bi-allele-frequency`, + title: '  Bi Allele Frequency', + style: { background: '#FFFFFF' }, + data: { + type: 'beddb', + url: url, + genomicFields: [ + {index: 1, name: "start"}, + {index: 2, name: "end"} + ], + valueFields: [ + {index: 0, name: "chromosome", type: "nominal"}, + {index: 5, name: "BAF", type: "quantitative"}, + {index: 3, name: "ref", type:"nominal"}, + {index: 4, name: "alt", type:"nominal"} + ] + }, + mark: "point", + x: { field: 'start', type: 'genomic' }, + alignment: 'overlay', + tracks: [ + { + y:{ + field: "BAF", + type: "quantitative", + axis: 'right', + grid: true, + domain: [0.1,1.1], + }, + color: { value: '#3ebecf' } + }, + ], + tooltip: [ + { field: "BAF", type: 'quantitative' }, + { field: "chromosome", type: 'quantitative' }, + { field: "start", type: 'quantitative' }, + { field: "end", type: 'quantitative' }, + {field: "ref", type: 'nominal'}, + {field: "alt", type: 'nominal'} + ], + size: { value: 3 }, + stroke: { value: '#808080' }, + strokeWidth: { value: 1 }, + opacity: { value: 0.7 }, + width, + height, + }; +} diff --git a/src/track/DPbin.ts b/src/track/DPbin.ts new file mode 100644 index 00000000..3fda0c17 --- /dev/null +++ b/src/track/DPbin.ts @@ -0,0 +1,74 @@ +import { StackedTracks, OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function DPbin( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + const sampleId2 = 'D2409912'; + return { + id: `${sampleId}-${mode}-DPbin`, + title: 'Depth of Coverage Spectrum', + style: { background: '#F6F6F6', inlineLegend: true }, + alignment: 'overlay', + data: { + separator: '\t', + url: url, + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['Midpoint'], + sampleLength: 1500 + }, + tracks: [ + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['genome'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic', axis: 'none' }, + y: { field: 'Amount_log_DP', type: 'quantitative', axis: 'left', range: [0, height - 20]}, + size: { value: width / 50 }, + color: { value: '#D55E00' }, + stroke: {value: 'white'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'GT', + measure: 'zoomLevel' + }], + }, + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['chrom'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic' }, + y: { field: 'Amount_log_DP', type: 'quantitative', axis: 'left', range: [0, height - 20] }, + size: { value: width / 65 }, + color: { value: '#D55E00' }, + stroke: {value: '#F6F6F6'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + } + ], + width, + height, + tooltip: [ + { field: 'DP_bin', type: 'nominal', alt: 'Depth of Coverage' }, + { field: 'Amount_DP', type: 'nominal', alt: 'Amount' } + ] + }; +} diff --git a/src/track/DPdetail.ts b/src/track/DPdetail.ts new file mode 100644 index 00000000..0a6a77f1 --- /dev/null +++ b/src/track/DPdetail.ts @@ -0,0 +1,37 @@ +import { SingleTrack } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function DPdetail( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): SingleTrack { + const sampleId2 = 'D2323937'; + return { + id: `${sampleId}-${sampleId2}-${mode}-DPdetail`, + title: 'Depth of Coverage', + style: { background: '#FFFFFF', inlineLegend: true }, + data: { + separator: ',', + url: url, + type: 'csv', + chromosomeField: `${sampleId2}.CHROM`, + genomicFields: [`${sampleId2}.POS`] + }, + mark: 'bar', + x: { field: `${sampleId2}.POS`, type: 'genomic' }, + y: { field: `${sampleId2}.DP`, type: 'quantitative', axis: 'left' }, + color: { value: '#D55E00' }, + tooltip: [ + { field: `${sampleId2}.POS`, type: 'genomic', alt: 'Position'}, + { field: `${sampleId2}.REF`, type: 'nominal', alt: 'Reference Allele'}, + { field: `${sampleId2}.ALT`, type: 'nominal', alt: 'Alternative Allele'}, + { field: `${sampleId2}.GT`, type: 'nominal', alt: 'Genotype'}, + { field: `${sampleId2}.DP`, type: 'nominal', alt: 'Depth of Coverage' } + ], + width, + height + }; +} diff --git a/src/track/GQbin.ts b/src/track/GQbin.ts new file mode 100644 index 00000000..368c4567 --- /dev/null +++ b/src/track/GQbin.ts @@ -0,0 +1,74 @@ +import { StackedTracks, OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function GQbin( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + const sampleId2 = 'D2409912'; + return { + id: `${sampleId}-${mode}-GQbin`, + title: 'Genotype Quality Spectrum', + style: { background: '#F6F6F6', inlineLegend: true }, + alignment: 'overlay', + data: { + separator: '\t', + url: url, + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['Midpoint'], + sampleLength: 1500 + }, + tracks: [ + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['genome'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic', axis: 'none' }, + y: { field: 'Amount_log_GQ', type: 'quantitative', axis: 'left', range: [0, height - 20]}, + size: { value: width / 50 }, + color: { value: '#E69F00' }, + stroke: {value: 'white'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'GT', + measure: 'zoomLevel' + }], + }, + { + dataTransform: [{ + type: 'filter', + field: 'Part', + oneOf: ['chrom'] + }], + mark: 'bar', + x: { field: 'Midpoint', type: 'genomic' }, + y: { field: 'Amount_log_GQ', type: 'quantitative', axis: 'left', range: [0, height - 20] }, + size: { value: width / 65 }, + color: { value: '#E69F00' }, + stroke: {value: '#F6F6F6'}, + strokeWidth: { value: 0.5 }, + visibility: [{ + threshold: 250000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + } + ], + width, + height, + tooltip: [ + { field: 'GQ_bin', type: 'nominal', alt: 'Genotype Quality' }, + { field: 'Amount_GQ', type: 'nominal', alt: 'Amount' } + ] + }; +} diff --git a/src/track/GQdetail.ts b/src/track/GQdetail.ts new file mode 100644 index 00000000..cc8d3880 --- /dev/null +++ b/src/track/GQdetail.ts @@ -0,0 +1,37 @@ +import { SingleTrack } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function GQdetail( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): SingleTrack { + const sampleId2 = 'D2323937'; + return { + id: `${sampleId}-${sampleId2}-${mode}-GQdetail`, + title: 'Genotype Quality', + style: { background: '#FFFFFF', inlineLegend: true }, + data: { + separator: ',', + url: url, + type: 'csv', + chromosomeField: `${sampleId2}.CHROM`, + genomicFields: [`${sampleId2}.POS`] + }, + mark: 'bar', + x: { field: `${sampleId2}.POS`, type: 'genomic' }, + y: { field: `${sampleId2}.GQ`, type: 'quantitative', axis: 'left' }, + color: { value: '#E69F00' }, + tooltip: [ + { field: `${sampleId2}.POS`, type: 'genomic', alt: 'Position'}, + { field: `${sampleId2}.REF`, type: 'nominal', alt: 'Reference Allele'}, + { field: `${sampleId2}.ALT`, type: 'nominal', alt: 'Alternative Allele'}, + { field: `${sampleId2}.GT`, type: 'nominal', alt: 'Genotype'}, + { field: `${sampleId2}.GQ`, type: 'nominal', alt: 'Genotype Quality'}, + ], + width, + height + }; +} diff --git a/src/track/PLdetail.ts b/src/track/PLdetail.ts new file mode 100644 index 00000000..4b103a9d --- /dev/null +++ b/src/track/PLdetail.ts @@ -0,0 +1,38 @@ +import { SingleTrack } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function PLdetail( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): SingleTrack { + const sampleId2 = 'D2323937'; + return { + id: `${sampleId}-${sampleId2}-${mode}-PLdetail`, + title: 'Phred-scaled genotype likelihoods', + style: { background: '#FFFFFF', inlineLegend: true }, + data: { + separator: ',', + url: url, + type: 'csv', + chromosomeField: `${sampleId2}.CHROM`, + genomicFields: [`${sampleId2}.POS`] + }, + mark: 'bar', + x: { field: `${sampleId2}.POS`, type: 'genomic' }, + //y: { field: `${sampleId2}.PL`, type: 'quantitative', axis: 'left' }, + color: { value: '#009E73' }, + opacity: { value: 0.9 }, + tooltip: [ + { field: `${sampleId2}.POS`, type: 'genomic', alt: 'Position'}, + { field: `${sampleId2}.REF`, type: 'nominal', alt: 'Reference Allele'}, + { field: `${sampleId2}.ALT`, type: 'nominal', alt: 'Alternative Allele'}, + { field: `${sampleId2}.GT`, type: 'nominal', alt: 'Genotype'}, + { field: `${sampleId2}.PL`, type: 'nominal', alt: 'Phred-scaled genotype likelihoods'} + ], + width, + height + }; +} diff --git a/src/track/baf.ts b/src/track/baf.ts new file mode 100644 index 00000000..eb85f27c --- /dev/null +++ b/src/track/baf.ts @@ -0,0 +1,48 @@ +import { SingleTrack, OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; +export default function baf( + sampleId: string, + url: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + return { + id: `${sampleId}-${mode}-baf`, + title: '  B-allele Frequency', + style: { background: '#FFFFFF' }, + data: { + separator: '\t', + url, + type: 'csv', + chromosomeField: 'chromosome', + genomicFields: ['start', 'end'] + }, + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'none' }, + xe: { field: 'end', type: 'genomic' }, + //x1: {field: 'chromosome', type: 'genomic', "axis": "bottom"}, + alignment: 'overlay', + tracks: [ + { + mark: 'point', + size: { value: 3 }, + y: { field: 'BAF', type: 'quantitative', axis: 'right', grid: true, domain: [-0.1, 1.1] }, + color: { value: '#3ebecf' } + } + ], + tooltip: [ + { field: 'BAF', type: 'quantitative' }, + { field: 'REF', type: 'nominal' }, + { field: 'ALT', type: 'nominal' }, + { field: 'start', type: 'quantitative', alt: 'Location' }, + { field: 'chromosome', type: 'nominal' } + ], + size: { value: 5 }, + stroke: { value: '#808080' }, + strokeWidth: { value: 0 }, + opacity: { value: 0.7 }, + width, + height + }; +} diff --git a/src/track/cnv.ts b/src/track/cnv.ts index a7e87c35..50d0c942 100644 --- a/src/track/cnv.ts +++ b/src/track/cnv.ts @@ -4,21 +4,19 @@ import { TrackMode } from './index'; export default function cnv( sampleId: string, cnvUrl: string, - width: number, + width: number, height: number, mode: TrackMode, - cnFields: [string, string, string] = ['total_cn', 'major_cn', 'minor_cn'] ): OverlaidTracks { - const [total_cn, major_cn, minor_cn] = cnFields; return { id: `${sampleId}-${mode}-cnv`, - title: mode === 'small' ? '' : 'Copy Number Variants', - style: { background: '#FFFFFF' }, + title: '  Copy Number Variants', + style: { background: '#FFFFFF', inlineLegend: true }, data: { separator: '\t', url: cnvUrl, type: 'csv', - chromosomeField: 'chromosome', + chromosomeField: 'seqnames', genomicFields: ['start', 'end'] }, mark: 'rect', @@ -27,24 +25,51 @@ export default function cnv( alignment: 'overlay', tracks: [ { - y: { field: total_cn, type: 'quantitative', axis: 'right', grid: true, range: [0 + 10, height - 10] }, - color: { value: '#808080' } + mark: 'point', + y: { field: 'ratio', type: 'quantitative', axis: 'left', grid: true, range: [0 + 10, height - 10], domain: [-2.4, 2.4]}, + color: { + field: 'threshold', type: 'nominal', + range: ['#FF0000', '#800080', '#ADD8E6', '#FFFF00','#FE66E7', '#008000' ], + domain: [ 'ratio<-1.0 (loss of 1 copy or more)', '-1.0< ratio<-0.3 (sign. loss)', + '-0.3<=ratio<=0.3 (+/- normal)', '0.3= 1.0 (copies at least doubled)' + ], + legend: true + }, + stroke: { value: '#808080' }, + strokeWidth: { value: 1 }, + opacity: { value: 0.6 } + }, + { + mark: 'line', + y: { field: 'seg.mean', type: 'quantitative', axis: 'left', grid: true, range: [0 + 10, height - 10], domain: [-2.4, 2.4] }, + color: { + field: 'seg_threshold', type: 'nominal', + range: ['#FF0000', '#800080', '#ADD8E6', '#FFFF00','#FE66E7', '#008000' ], + domain: [ 'ratio<-1.0 (loss of 1 copy or more)', '-1.0< ratio<-0.3 (sign. loss)', + '-0.3<=ratio<=0.3 (+/- normal)', '0.3= 1.0 (copies at least doubled)' + ] + }, + size: { value: 4 }, + stroke: { value: '#000000' }, + strokeWidth: { value: 2 }, + opacity: { value: 1 } } - // { - // y: { field: 'minor_cn', type: 'quantitative', axis: 'right', grid: true }, - // color: { value: 'red' }, - // } ], tooltip: [ - { field: total_cn, type: 'quantitative' }, - { field: major_cn, type: 'quantitative' }, - { field: minor_cn, type: 'quantitative' } + + { field: 'seqnames', type: 'nominal', alt: 'Chromosome' }, + { field: 'start', type: 'genomic', alt: 'Start' }, + { field: 'end', type: 'genomic', alt: 'End' }, + { field: 'ratio', type: 'quantitative', format: '.5f' }, + { field: 'seg.start', type: 'quantitative', alt: 'Segment Start' }, + { field: 'seg.end', type: 'quantitative', alt: 'Segment End' }, + { field: 'seg.mean', type: 'quantitative' } ], - size: { value: 5 }, - stroke: { value: '#808080' }, - strokeWidth: { value: 1 }, - opacity: { value: 0.7 }, width, height }; -} +} \ No newline at end of file diff --git a/src/track/coverage.ts b/src/track/coverage.ts index 5cb0dbaf..ce38415a 100644 --- a/src/track/coverage.ts +++ b/src/track/coverage.ts @@ -5,7 +5,7 @@ export default function coverage(option: SpecOption, isLeft: boolean): Partial[] => [ + { + mark: 'area', + data: { + separator: ',', + url: meUrl1, + type: 'csv', + chromosomeField: 'seqnames', + genomicFields: ['start'], + sampleLength: 5000 + }, + x: { field: 'start', type: 'genomic' }, + size: { value: 1 }, + y: { field: field, type: 'quantitative', axis: 'right', grid: true, range: [0, height - 10] }, + color: { value: color }, + stroke: { field: field, type: 'quantitative' }, + strokeWidth: { value: 1 }, + opacity: { value: 0.7 }, + visibility: [{ + threshold: 1000000, + target: 'track', + operation: 'GT', + measure: 'zoomLevel' + }] + }, + ]; + + + return { + id: `${sampleId}-${mode}-mendelian-errors`, + title: '  Mendelian Errors', + style: { background: '#FFFFFF', inlineLegend: true }, + alignment: 'overlay', + tracks: [ + { + mark: 'point', + x: { value: 0 }, + y: { value: 0 }, + color: { + type: 'nominal', + domain: ['Trio Error', 'Father Error', 'Mother Error'], + range: ['#F0E442', '#56B4E9', '#D55E00'], // Colors representing each category + legend: true + } + }, + ...(trioError ? createTrack(trioError, '#F0E442') : []), + ...(fatherError ? createTrack(fatherError, '#56B4E9') : []), + ...(motherError ? createTrack(motherError, '#D55E00') : []) + ], + tooltip: [ + { field: 'start', type: 'genomic', alt: 'Start Position' }, + { field: 'end', type: 'genomic', alt: 'End Position' }, + { field: trioError, type: 'quantitative', alt: 'Trio Errors' }, + { field: fatherError, type: 'quantitative', alt: 'Father Errors' }, + { field: motherError, type: 'quantitative', alt: 'Mother Errors' } + ], + width, + height + }; +} diff --git a/src/track/mendelianErros2.ts b/src/track/mendelianErros2.ts new file mode 100644 index 00000000..33937062 --- /dev/null +++ b/src/track/mendelianErros2.ts @@ -0,0 +1,88 @@ +import { OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function mendelianErrors2( + parent: string, + meUrl2: string, + mode: TrackMode, + cnFields: [string, string, string] = ['trio', 'fat', 'mot'] +): Partial { + const [trioError, fatherError, motherError] = cnFields; + + return { + id: `${parent}-${mode}-mendelian-errors2`, + title: '  Mendelian Errors', + style: { inlineLegend: true }, + alignment: 'overlay', + data: { + separator: ',', + url: meUrl2, + type: 'csv', + chromosomeField: 'CHROM', + genomicFields: ['POS'] + }, + tracks: [ + { + mark: 'point', + x: { value: 0 }, + y: { value: 0 }, + color: { + type: 'nominal', + domain: ['Trio Error', 'Father Error', 'Mother Error'], + range: ['#F0E442', '#56B4E9', '#D55E00'], // Colors representing each category + legend: true + }, + overlayOnPreviousTrack: true + }, + { + mark: 'point', + x: { field: 'POS', type: 'genomic' }, + y: { value: 45 }, + size: { field: trioError, type: 'quantitative' }, + color: { value: '#F0E442' }, + visibility: [{ + threshold: 1000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + overlayOnPreviousTrack: true + }, + { + mark: 'point', + x: { field: 'POS', type: 'genomic' }, + y: { value: 30 }, + size: { field: fatherError, type: 'quantitative' }, + color: { value: '#56B4E9' }, + visibility: [{ + threshold: 1000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + overlayOnPreviousTrack: true + }, + { + mark: 'point', + x: { field: 'POS', type: 'genomic' }, + y: { value: 15 }, + size: { field: motherError, type: 'quantitative' }, + color: { value: '#D55E00' }, + visibility: [{ + threshold: 1000000, + target: 'track', + operation: 'LT', + measure: 'zoomLevel' + }], + overlayOnPreviousTrack: true + } + ], + tooltip: [ + { field: 'POS', type: 'genomic', alt: 'Start Position' }, + { field: trioError, type: 'quantitative', alt: 'Trio Errors' }, + { field: fatherError, type: 'quantitative', alt: 'Father Errors' }, + { field: motherError, type: 'quantitative', alt: 'Mother Errors' }, + ], + height: 0.01, + }; +} diff --git a/src/track/mutation.ts b/src/track/mutation.ts index 5f753776..f4661049 100644 --- a/src/track/mutation.ts +++ b/src/track/mutation.ts @@ -11,7 +11,7 @@ export default function mutation( ): SingleTrack { return { id: `${sampleId}-${mode}-mutation`, - title: 'Point Mutation', + title: '  Point Mutation', style: { background: '#FFFFFF', inlineLegend: true }, data: { type: 'vcf', @@ -23,8 +23,16 @@ export default function mutation( mark: 'point', x: { field: 'POS', type: 'genomic' }, color: { field: 'SUBTYPE', type: 'nominal', legend: true, domain: ['C>A', 'C>G', 'C>T', 'T>A', 'T>C', 'T>G'] }, - y: { field: 'DISTPREVLOGE', type: 'quantitative', axis: 'left', range: [10, height - 10] }, + y: { field: 'DISTPREVLOGE', type: 'quantitative', axis: 'right', range: [10, height - 10] }, opacity: { value: 0.9 }, + visibility: [ + { + operation: 'LT', + measure: 'zoomLevel', + threshold: 10000000, + target: 'track' + } + ], tooltip: [ { field: 'DISTPREV', type: 'nominal', format: 's1', alt: 'Distance To Previous Mutation (BP)' }, { field: 'POS', type: 'genomic' }, diff --git a/src/track/parentMapping.ts b/src/track/parentMapping.ts new file mode 100644 index 00000000..4c30816f --- /dev/null +++ b/src/track/parentMapping.ts @@ -0,0 +1,92 @@ +import { OverlaidTracks } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from './index'; + +export default function parentMapping( + sampleId: string, + pmUrl: string, + width: number, + height: number, + mode: TrackMode +): OverlaidTracks { + return { + id: `${sampleId}-${mode}-parent-mapping`, + title: '  Parent Mapping', + style: { background: '#F6F6F6', inlineLegend: true }, + data: { + separator: ',', + url: pmUrl, + type: 'csv', + chromosomeField: 'chromosome', + genomicFields: ['POS'], + sampleLength: 5000 + }, + dataTransform: [ + { + type: 'replace', + field: 'tracks', + replace: [ + { from: '1', to: 'Mother | Homozygous' }, + { from: '2', to: 'Mother | Heterozygous' }, + { from: '4', to: 'Father | Homozygous' }, + { from: '5', to: 'Father | Heterozygous' } + ], + newField: 'parental_origin' + }, + { + type: 'replace', + field: 'tracks', + replace: [ + { from: '1', to: 'Mother' }, + { from: '2', to: 'Mother' }, + { from: '4', to: 'Father' }, + { from: '5', to: 'Father' } + ], + newField: 'Parental Origin' + }, + { + type: 'replace', + field: 'tracks', + replace: [ + { from: '1', to: 'Homozygous' }, + { from: '2', to: 'Heterozygous' }, + { from: '4', to: 'Homozygous' }, + { from: '5', to: 'Heterozygous' } + ], + newField: 'Variant' + } + ], + alignment: 'overlay', + tracks: [ + { + mark: 'rect', + x: { field: 'POS', type: 'genomic' }, + y: { value: 0 }, + color: { + field: 'parental_origin', + type: 'nominal', + domain: ['Mother | Homozygous', 'Mother | Heterozygous', 'Father | Homozygous', 'Father | Heterozygous'], + range: ['#D55E00', '#E69F00', '#0072B2', '#56B4E9'], // Colors representing each category + legend: true + }, + stroke : { + field: 'parental_origin', + type: 'nominal', + domain: ['Mother | Homozygous', 'Mother | Heterozygous', 'Father | Homozygous', 'Father | Heterozygous'], + range: ['#D55E00', '#E69F00', '#0072B2', '#56B4E9'] + }, + strokeWidth : { value: 3 }, + opacity : { value: 0.9 }, + size: { value: height - 5 }, + } + ], + tooltip: [ + { field: 'chromosome', type: 'nominal', alt: 'Chromosome'}, + { field: 'POS', type: 'genomic', alt: 'Position' }, + { field: 'GT', type: 'nominal', alt: 'Genotype' }, + { field: 'Parental Origin', type: 'nominal', alt: 'Parental Origin' }, + { field: 'Variant', type: 'nominal', alt: 'Variant' } + ], + width, + height + }; +} diff --git a/src/track/roi.ts b/src/track/roi.ts new file mode 100644 index 00000000..5000641c --- /dev/null +++ b/src/track/roi.ts @@ -0,0 +1,23 @@ +import { SingleTrack } from 'gosling.js/dist/src/gosling-schema'; +import { TrackMode } from '.'; + +const hex = 'red'; + +export default function roi(parent: string, roiUrl: string, mode: TrackMode): Partial { + return { + id: `${parent}-${mode}-roi`, + data: { + separator: ',', + url: roiUrl, + type: 'csv', + chromosomeField: 'CHROM', + genomicFields: ['POS'], + sampleLength: 5000 + }, + mark: mode === 'mid' ? 'rule' : 'rect', + x: { field: 'POS', type: 'genomic' }, + color: { value: hex }, + opacity: { value: 0.5 }, + overlayOnPreviousTrack: true + }; +} diff --git a/src/track/sv.ts b/src/track/sv.ts index 93e71ff5..02993b72 100644 --- a/src/track/sv.ts +++ b/src/track/sv.ts @@ -89,6 +89,7 @@ export default function sv( const svs = [...defaults.color.svclass.domain]; return { id: `${sampleId}-${mode}-sv`, + title: mode === 'mid' ? '  Structural Variants' : '', alignment: 'overlay', experimental: { mouseEvents: { @@ -102,18 +103,6 @@ export default function sv( url, type: 'csv', separator: '\t', - headerNames: [ - 'chrom1', - 'start1', - 'end1', - 'chrom2', - 'start2', - 'end2', - 'sv_id', - 'pe_support', - 'strand1', - 'strand2' - ], genomicFieldsToConvert: [ { chromosomeField: 'chrom1', @@ -226,4 +215,4 @@ export default function sv( width, height }; -} +} \ No newline at end of file diff --git a/src/ui/ExportDropdown.tsx b/src/ui/ExportDropdown.tsx new file mode 100644 index 00000000..27aedde4 --- /dev/null +++ b/src/ui/ExportDropdown.tsx @@ -0,0 +1,90 @@ +import React, { useState, useEffect } from 'react'; +import { ICONS } from '../icon'; +import { getHtmlTemplate } from '../html-template'; + +type ExportButtonProps = { + title: string; + icon: string; +}; + +const ExportButton = ({ title, icon }: ExportButtonProps) => { + return ( + + {title} + {ICONS[icon].path.map(p => ( + + ))} + + ); +}; + +type ExportDropdownProps = { + gosRef: React.RefObject; + currentSpec: React.MutableRefObject; +}; + +export const ExportDropdown = ({ gosRef, currentSpec }: ExportDropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
setIsOpen(!isOpen)} + aria-expanded={isOpen} + > + + {isOpen ? ( + + ) : null} +
+ ); +}; diff --git a/src/ui/GenomeViewModal.tsx b/src/ui/GenomeViewModal.tsx new file mode 100644 index 00000000..9277c444 --- /dev/null +++ b/src/ui/GenomeViewModal.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import genome_interactions_1 from '../script/img/modal_images/genome_view/interactions_1.png'; +import genome_interactions_2 from '../script/img/modal_images/genome_view/interactions_2.png'; +import genome_interactions_3 from '../script/img/modal_images/genome_view/interactions_3.png'; +import genome_interactions_4 from '../script/img/modal_images/genome_view/interactions_4.png'; +import genome_interactions_5 from '../script/img/modal_images/genome_view/interactions_5.png'; +import summary_interpretation_1 from '../script/img/modal_images/genome_view/interpretation_1.png'; + + +export const GenomeViewModal = () => { + return ( + + ); +}; diff --git a/src/ui/NavigationButtons.tsx b/src/ui/NavigationButtons.tsx new file mode 100644 index 00000000..a69a5b0d --- /dev/null +++ b/src/ui/NavigationButtons.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { ICONS } from '../icon'; + +export const NavigationButtons = () => { + return ( +
+
+ + +
+
+ + +
+
+ ); +}; diff --git a/src/ui/VariantViewModal.tsx b/src/ui/VariantViewModal.tsx new file mode 100644 index 00000000..ad01c535 --- /dev/null +++ b/src/ui/VariantViewModal.tsx @@ -0,0 +1,124 @@ +import React from 'react'; + +import variant_interpretation_1 from '../script/img/modal_images/variant_view/interpretation_1.png'; +import variant_interactions_1 from '../script/img/modal_images/variant_view/interactions_1.png'; +import variant_interactions_2 from '../script/img/modal_images/variant_view/interactions_2.png'; +import variant_interactions_3 from '../script/img/modal_images/variant_view/interactions_3.png'; +import variant_interactions_4 from '../script/img/modal_images/variant_view/interactions_4.png'; +import variant_interactions_5 from '../script/img/modal_images/variant_view/interactions_5.png'; + +export const VariantViewModal = () => { + return ( + + ); +}; diff --git a/src/ui/getTrackDocData.ts b/src/ui/getTrackDocData.ts new file mode 100644 index 00000000..592584b6 --- /dev/null +++ b/src/ui/getTrackDocData.ts @@ -0,0 +1,301 @@ +import ideogram_interpretation_1 from '../script/img/popover-images/ideogram/interpretation_1.png'; +import ideogram_interpretation_2 from '../script/img/popover-images/ideogram/interpretation_2.png'; +import haplo_interpretation_1 from '../script/img/popover-images/haplo/interpretation_1.png'; +import haplo_interaction_1 from '../script/img/popover-images/haplo/interaction_1.png'; +import baf_interaction_1 from '../script/img/popover-images/baf/interaction_1.png'; +import baf_interpretation_1 from '../script/img/popover-images/baf/interpretation_1.png'; +import mutations_interpretation_1 from '../script/img/popover-images/mutations/interpretation_1.png'; +import mutations_interactions_1 from '../script/img/popover-images/mutations/interactions_1.png'; +import mutations_interactions_2 from '../script/img/popover-images/mutations/interactions_2.png'; +import copy_number_variants_interpretation_1 from '../script/img/popover-images/copy_number_variants/interpretation_1.png'; +import copy_number_variants_interpretation_2 from '../script/img/popover-images/copy_number_variants/interpretation_2.png'; +import copy_number_variants_interactions_1 from '../script/img/popover-images/copy_number_variants/interactions_1.png'; + +import mendelian_errors_interpretation_1 from '../script/img/popover-images/mendelian-errors/interpretation_1.png' +import mendelian_errors_interpretation_2 from '../script/img/popover-images/mendelian-errors/interpretation_2.png' +import mendelian_errors_interactions_1 from '../script/img/popover-images/mendelian-errors/interactions_1.png' +import mendelian_errors_interactions_2 from '../script/img/popover-images/mendelian-errors/interactions_2.png' +import parent_mapping_interpretation_1 from '../script/img/popover-images/parent-mapping/interpretation_1.png' +import parent_mapping_interactions_1 from '../script/img/popover-images/parent-mapping/interactions_1.png' + + +export type Track = + | 'ideogram' + | 'baf' + | 'haplo' + | 'mutation' + | 'cnv' + | 'mendelian-errors' + | 'parent-mapping'; + +// TODO: Not ideal to hard coded! +// The heights of each track +export const getTrackDocData = ( + isMinimalMode: boolean +): { height: number; type: Track; title: string; popover_content?: string }[] => { + return [ + { + height: 50, + type: 'ideogram', + title: 'Ideogram', + popover_content: ` +
+
+

Interpretation

+
+
+ Chromosome bands on Ideogram track. +
+

Black stripes - indicate chromosome bands (cytobands) obtained from chromosome staining viewing under microscope.

+
+
+
+
+ Chromosome centromeres on Ideogram track. +
+

Red Triangles - represent chromosome centromeres.

+
+
+
+
+ ` + }, + { + height: 240, + type: 'baf', + title: 'B Allele Frequency', + popover_content: ` +
+
+

Interpretation

+
+
+
+ B-Allele frequency interpretation. +
+

Y-axis - shows the B-Allele frequency ranging from 0 to 1

+

Points - shows the location of BAF

+ + +
+
+
+
+
+
+

Interactions

+
+
+ BAF details pop up. +
+

Hover - over BAF points to see annotation details, shows chromosome your on, the location, the reference base, alternate base and the B-allele frequency .

+
+
+
+
` + }, + { + height: 300, + type: 'haplo', + title: 'Haplotyping by Merlin', + popover_content: ` +
+
+

Interpretation

+
+
+
+ Haplotype strand information +
+

Strands
Denotes the forward and backward strands per sample.

+

Color changes
Indicates a new breakpoint. The colors relate to the parts of the strands from the parents.

+

Y-axis
Displays the different samples. The first two samples (four strands) represent the parents, if specified.

+
+
+
+
+
+
+

Interactions

+
+
+ Haplotyping details pop up. +
+

Hover
Hover over haplotype strands to see annotation details. It shows the chromosome you are on, the breakpoint start and end, and the sample you are looking at.

+
+
+
+
` + }, + + { + height: 60, + type: 'mutation', + title: 'Point Mutation', + popover_content: ` +
+
+

Interpretation

+
+
+ Point Mutation track y-axis. +
+

Y-axis shows the distance (in kb) between adjacent point mutations, on a logarithmic scale.

+
+
+
+
+

At low magnification, only selected mutations are visible.

+
+
+
+

Interactions

+
+
+ Point Mutation track at high magnification. +
+

+ Zoom in - to reveal more point mutations. +

+
+
+
+
+ Point Mutation track details pop up. +
+

Hover - over a point mutation to see details.

+
+
+
+
` + }, + { + height: 200, + type: 'cnv', + title: 'Copy Number Variants', + popover_content: ` +
+
+

Interpretation

+
+
+ Copy Number Variants track. +
+

Points - represent copy number profiles.

+

Lines - represent the mean per segment.

+

Y-axis - represents the log2 ratio.

+
+
+
+
+ Copy Number Variants track. +
+

colors - show the different thresholds the points or segments are part of.

+
+
+
+
+

Interactions

+
+
+ Copy number segment details pop up. +
+

+ Hover - over the copy number segment to see the annotation details, chromosome, start and end position of the variant, the log2 ratio, segment start and end and the segment mean. +

+
+
+
+
+ ` + }, + { + height: 60, + type: 'mendelian-errors', + title: 'Mendelian Errors', + popover_content: ` +
+
+

Interpretation

+
+
+

Trio errors involve inconsistencies in the child's genotype when considering the combined genotypes of both parents. + Father errors arise when the child's genotype does not match the expected inheritance from the father. + Mother errors occur when the child's genotype is inconsistent with the mother's genotype.

+
+
+ Mendelian Errors track. +
+

When zoomed out to a scale greater than 1 million base pairs, Mendelian errors are aggregated every 1 million base pairs in the line chart.

+
+
+
+
+ Zoomed In Mendelian Errors track. +
+

When zoomed in to less than 1 million base pairs, individual Mendelian errors can be seen for each variant.

+
+
+
+
+

Interactions

+
+
+ Mendelian Errors line chart pop up. +
+

+ Hover - over a point on the chart reveals the start and stop positions of the bin, as well as the total number of trio, father, and mother errors within that bin. +

+
+
+
+
+ Zoomed In Mendelian Errors pop up. +
+

+ Hover - over the individual mendelian errors to see the position and the amount of trio, father and mother errors. +

+
+
+
+
+ ` + }, + { + height: 40, + type: 'parent-mapping', + title: 'Parent Mapping', + popover_content: ` +
+
+

Interpretation

+
+
+

'Parent mapping' is executed for every sample when at least one parent is available. Useful for hetero/iso-uniparental disomy (UPD) detection and to analyze whether aberrations are meiotic or mitotic. Variants of the child/embryo are distributed per parental origin.

+
+
+ Parent Mapping Track. +
+

Dark blue boxes - father-derived, homozygous variants.

+

Light blue boxes - father-derived, heterozygous variants.

+

Vermillion boxes - mother-derived, homozygous variants.

+

Orange boxes - mother-derived, heterozygous variants.

+
+
+
+
+

Interactions

+
+
+ Parent Mapping segment details pop up. +
+

+ Hover - over the parent mapping segment to see the chromosome, position, genotype, parental origin and variant. +

+
+
+
+
` + }, + ]; +}; diff --git a/src/ui/json-base64-converter.tsx b/src/ui/json-base64-converter.tsx new file mode 100644 index 00000000..3d3923bc --- /dev/null +++ b/src/ui/json-base64-converter.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import UrlsafeCodec from '../lib/urlsafe-codec'; + +const JsonBase64Converter = () => { + const [jsonText, setJsonText] = useState(''); + const [base64Text, setBase64Text] = useState(''); + const [error, setError] = useState(''); + + const handleEncode = () => { + try { + const jsonObject = JSON.parse(jsonText); + const encoded = UrlsafeCodec.encode(jsonObject); + setBase64Text(encoded); + setError(''); + } catch (error) { + setError('Error parsing JSON. Please enter valid JSON.'); + } + }; + + const handleDecode = () => { + try { + const decoded = UrlsafeCodec.decode(base64Text); + const jsonString = JSON.stringify(decoded, null, 4); + setJsonText(jsonString); + setError(''); + } catch (error) { + setError('Error decoding base64 string. Please enter a valid base64-encoded string.'); + } + }; + + return ( +
+
+

JSON Text

+