From ac0ea38ff2847ef3dbf63e3b017314c79fbef23d Mon Sep 17 00:00:00 2001 From: Karthik Kalletla Date: Tue, 20 Apr 2021 09:02:28 -0400 Subject: [PATCH] Fusion sv migration (#3358) Frontend changes to use structure_variant able over mutations table for fusions ### Impacted pages #### Query page - Structural variant/Fusion profile is shown if the study has it - For multi-study selection: new molecular profile checkbox for structural variants #### Study view page - Rename fusion table to structural variant table #### Results page - Oncoprint tab - Rename fusion to structural variant - New track for fusion/structural variant profiled cases - Plots tab - Structural variants are shown as a new Data Type/selector - Comparison tab/alterations - no visual changes - Downloads tab #### Patient view page - All fusions in mutation table should be in new structural variants table Commits: * Migrate from fusions to sv in study-view * Add structural variant table in patient page * Migrate fusion to structural variant in oncoprint * Support structural variants in cancer summary and download tabs * Support structural variant profile in result page plots tab * Add structural variant enrichments analysis tab * Structural variant coloring in plots tab * update expression tab to use new code * plots tab refactoring * Fix sort order - structural variant goes on top of CNA, and show message indicating that * Update molecular profile filter parameter in query and results page * Fix error from merging master and failing e2e * Fix how unprofiled cna and unprofiled sv are handled in plots tab scatter plot annotation * Fix plots bug causing too-thick stroke width * Fix plots bug with cna and sv in legend when there is no cna or sv data * Fix issues in plots tab relating to structural variant migration * Fix bug in plots tab and fix e2e tests * SV: do not invoke api's if all samples are filtered out * Fix issues arising from mobx upgrade * Fix plots tab opacity issue * Use same color for stroke and fill in waterfall plot * Fix error selecting geneset profile and other e2e local tests * Alteration Enrichments: avoid sending duplicate sample during api query * SV table: show profiled samples count * Incude structural variants in VUS * Study page: fix SV table column width * Add tumor type to sv objects while invoking oncokb api * Update failing e2e tests * Regenerate api client files * Fix appearance of not-profiled-for-cna-and-sv * Fix bug in plots relating to not-profiled-for-cna-and-sv * Update failing e2e tests * Change color for not-profiled-for-cna-and-sv to match current production * Update localdb e2e tests * Fix condition for invoking alteration enrichment api * Set mutationType to fusion for structural variants * Fix group size in mini-oncoprint in alterations enrichment tab * Fix oncokb lookup issue for structural variants * Support old FUSION_GENES_TABLE from user settings * Update failing e2e test image * Update protein display name in mutation-mapper Co-authored-by: Adam Abeshouse Co-authored-by: Adam Abeshouse Former-commit-id: eefd23507ca721c863409dd8e8d43dedbec55ca6 --- .prettierignore | 3 +- .../local/specs/core/plotstab.spec.js | 10 +- end-to-end-test/local/specs/treatment.spec.js | 2 + .../remote/specs/core/home.spec.js | 71 +- .../remote/specs/core/oncoprint.spec.js | 4 +- .../specs/core/plots.screenshot.spec.js | 6 + .../remote/specs/core/results.logic.spec.js | 24 +- .../remote/specs/core/studyview.spec.js | 22 +- env/custom.sh | 2 +- env/fusion-sv-migration.sh | 3 + env/master.sh | 2 +- .../src/lib/AlterationColors.ts | 2 +- .../src/generated/CBioPortalAPI-docs.json | 20 +- .../src/generated/CBioPortalAPI.ts | 8 +- .../generated/CBioPortalAPIInternal-docs.json | 79 +- .../src/generated/CBioPortalAPIInternal.ts | 156 +-- .../src/oncokb/OncoKbUtils.ts | 52 + .../filter/ProteinImpactTypeHelper.tsx | 2 +- .../DefaultLollipopPlotLegend.tsx | 4 +- .../store/DefaultMutationMapperDataFetcher.ts | 5 +- .../src/util/MutationTypeUtils.ts | 4 +- src/config/serverConfigDefaults.ts | 2 +- .../groupComparison/AlterationEnrichments.tsx | 1 + .../groupComparison/GroupComparisonPage.tsx | 6 +- .../groupComparison/GroupComparisonStore.ts | 8 +- src/pages/patientView/PatientViewPage.tsx | 61 +- .../PatientViewPageStore.ts | 385 +++++-- .../PatientViewStructuralVariantTable.tsx | 426 +++++++ .../column/AnnotationColumnFormatter.tsx | 215 ++++ .../resultsView/ResultsViewPageStore.spec.ts | 22 +- src/pages/resultsView/ResultsViewPageStore.ts | 645 +++++++++-- .../resultsView/ResultsViewPageStoreUtils.ts | 112 +- .../resultsView/ResultsViewURLWrapper.ts | 11 +- .../cancerSummary/CancerSummaryChart.tsx | 5 +- .../CancerSummaryContent.spec.tsx | 12 +- .../cancerSummary/CancerSummaryContent.tsx | 9 +- .../resultsView/comparison/ComparisonTab.tsx | 19 +- .../comparison/ResultsViewComparisonStore.ts | 20 +- .../download/CaseAlterationTable.tsx | 9 +- .../resultsView/download/DownloadTab.tsx | 68 ++ .../download/DownloadUtils.spec.ts | 183 +-- .../resultsView/download/DownloadUtils.ts | 88 +- .../AlterationEnrichmentsContainer.tsx | 14 +- .../expression/ExpressionWrapper.tsx | 42 +- .../expression/expressionHelpers.tsx | 8 +- .../LastPlotsTabSelectionForDatatype.spec.ts | 7 +- src/pages/resultsView/plots/PlotsTab.tsx | 704 ++++++++---- .../resultsView/plots/PlotsTabUtils.spec.ts | 14 +- src/pages/resultsView/plots/PlotsTabUtils.tsx | 1020 ++++++++++------- .../settings/ResultsPageSettings.tsx | 4 +- .../OncoprinterGeneticUtils.spec.ts | 4 +- .../oncoprinter/OncoprinterGeneticUtils.ts | 26 +- .../OncoprinterImportUtils.spec.ts | 15 +- .../oncoprinter/OncoprinterImportUtils.ts | 10 +- src/pages/studyView/StudyViewConfig.ts | 8 +- src/pages/studyView/StudyViewPageStore.ts | 324 ++++-- src/pages/studyView/StudyViewUtils.spec.ts | 9 +- src/pages/studyView/StudyViewUtils.tsx | 85 +- src/pages/studyView/TableUtils.tsx | 2 +- src/pages/studyView/UserSelections.tsx | 6 +- src/pages/studyView/charts/ChartContainer.tsx | 6 +- .../studyView/table/MultiSelectionTable.tsx | 26 +- src/pages/studyView/tabs/SummaryTab.tsx | 13 +- .../cache/MolecularProfilesInStudyCache.ts | 20 +- .../banners/AlterationFilterWarning.tsx | 15 +- .../column/ChromosomeColumnFormatter.tsx | 8 +- .../components/oncoprint/DataUtils.spec.ts | 33 +- src/shared/components/oncoprint/DataUtils.ts | 41 +- src/shared/components/oncoprint/Oncoprint.tsx | 5 +- .../oncoprint/ResultsViewOncoprint.tsx | 1 + .../oncoprint/ResultsViewOncoprintUtils.tsx | 1 + src/shared/components/oncoprint/SortUtils.ts | 4 +- .../components/oncoprint/TooltipUtils.spec.ts | 178 ++- .../components/oncoprint/TooltipUtils.ts | 121 +- .../components/oncoprint/geneticrules.ts | 22 +- .../components/oncoprint/tabularDownload.ts | 26 +- .../query/DataTypePrioritySelector.spec.tsx | 46 - .../query/DataTypePrioritySelector.tsx | 134 +-- .../query/MolecularProfileSelector.tsx | 41 +- src/shared/components/query/QueryStore.ts | 500 ++++---- .../components/query/QueryStoreUtils.spec.ts | 158 +-- .../components/query/QueryStoreUtils.ts | 220 ++-- src/shared/lib/Colors.ts | 4 +- src/shared/lib/MutationUtils.ts | 4 +- src/shared/lib/StoreUtils.ts | 87 +- src/shared/lib/URLWrapper.spec.ts | 1 + src/shared/lib/alterationCountHelpers.spec.ts | 17 +- src/shared/lib/alterationCountHelpers.ts | 14 +- .../AlterationEnrichmentTypeSelector.spec.tsx | 74 +- .../AlterationEnrichmentTypeSelector.tsx | 31 +- src/shared/lib/comparison/ComparisonStore.ts | 277 +++-- .../lib/comparison/ComparisonStoreUtils.ts | 51 +- src/shared/lib/getDefaultMolecularProfiles.ts | 91 +- src/shared/lib/joinJsx.tsx | 12 + src/shared/lib/oql/AccessorsForOqlFilter.ts | 24 +- src/shared/lib/oql/annotateAlterationTypes.ts | 29 +- src/shared/lib/oql/oqlfilter.spec.ts | 47 +- src/shared/lib/oql/oqlfilter.ts | 43 +- 98 files changed, 5074 insertions(+), 2441 deletions(-) create mode 100644 env/fusion-sv-migration.sh create mode 100644 src/pages/patientView/structuralVariant/PatientViewStructuralVariantTable.tsx create mode 100644 src/pages/patientView/structuralVariant/column/AnnotationColumnFormatter.tsx delete mode 100644 src/shared/components/query/DataTypePrioritySelector.spec.tsx create mode 100644 src/shared/lib/joinJsx.tsx diff --git a/.prettierignore b/.prettierignore index 1a641a7d569..dc5855740c8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -29,4 +29,5 @@ yarn.lock *properties *.seg end-to-end-test/local/studies/** -*.screenshottest \ No newline at end of file +*.screenshottest +*.jks \ No newline at end of file diff --git a/end-to-end-test/local/specs/core/plotstab.spec.js b/end-to-end-test/local/specs/core/plotstab.spec.js index e2f07f3e6a8..4f21e361bba 100644 --- a/end-to-end-test/local/specs/core/plotstab.spec.js +++ b/end-to-end-test/local/specs/core/plotstab.spec.js @@ -38,13 +38,13 @@ describe('plots tab', function() { assert($('div.coloring-menu').isExisting()); assert( $('div.coloring-menu').$$('input[type="checkbox"]') - .length === 2 + .length === 3 ); }); it('shows mutation and copy number by default', () => { loadPlotsTab( - `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib` + `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib&plots_horz_selection=%7B"dataType"%3A"clinical_attribute"%7D&plots_vert_selection=%7B"selectedGeneOption"%3A6205%2C"dataType"%3A"TREATMENT_RESPONSE"%7D&plots_coloring_selection=%7B%7D&profileFilter=0` ); selectTreatmentProfile(); const res = browser.checkElement('[id=plots-tab-plot-svg]'); @@ -53,7 +53,7 @@ describe('plots tab', function() { it('shows only mutation types when copy number is de-selected', () => { loadPlotsTab( - `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib` + `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib&plots_horz_selection=%7B"dataType"%3A"clinical_attribute"%7D&plots_vert_selection=%7B"selectedGeneOption"%3A6205%2C"dataType"%3A"TREATMENT_RESPONSE"%7D&plots_coloring_selection=%7B"colorByCopyNumber"%3A"false"%2C"colorBySv"%3A"false"%7D&profileFilter=0` ); selectTreatmentProfile(); $('input[data-test="ViewCopyNumber"]').click(); @@ -63,7 +63,7 @@ describe('plots tab', function() { it('shows only CNA types when mutation checkbox is deselected', () => { loadPlotsTab( - `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib` + `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib&plots_horz_selection=%7B"dataType"%3A"clinical_attribute"%7D&plots_vert_selection=%7B"selectedGeneOption"%3A6205%2C"dataType"%3A"TREATMENT_RESPONSE"%7D&plots_coloring_selection=%7B"colorByMutationType"%3A"false"%2C"colorBySv"%3A"false"%7D&profileFilter=0` ); selectTreatmentProfile(); $('input[data-test="ViewMutationType"]').click(); @@ -73,7 +73,7 @@ describe('plots tab', function() { it('removes sample stylings when selecting None in gene selection box', () => { loadPlotsTab( - `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib` + `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=study_es_0&case_set_id=study_es_0_all&clinicallist=NUM_SAMPLES_PER_PATIENT%2CPROFILED_IN_study_es_0_mutations%2CPROFILED_IN_study_es_0_gistic%2CPROFILED_IN_study_es_0_mrna_median_Zscores&data_priority=0&gene_list=RPS11&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=study_es_0_gistic&genetic_profile_ids_PROFILE_MRNA_EXPRESSION=study_es_0_mrna_median_Zscores&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=study_es_0_mutations&heatmap_track_groups=study_es_0_treatment_ic50%2CErlotinib&show_samples=false&tab_index=tab_visualize&treatment_list=Erlotinib&plots_horz_selection=%7B"dataType"%3A"clinical_attribute"%7D&plots_vert_selection=%7B"dataType"%3A"TREATMENT_RESPONSE"%7D&plots_coloring_selection=%7B"selectedOption"%3A"6205_undefined"%7D&profileFilter=0` ); selectTreatmentProfile(); $('.gene-select').click(); diff --git a/end-to-end-test/local/specs/treatment.spec.js b/end-to-end-test/local/specs/treatment.spec.js index fff7255013f..e40e57228f3 100644 --- a/end-to-end-test/local/specs/treatment.spec.js +++ b/end-to-end-test/local/specs/treatment.spec.js @@ -410,6 +410,7 @@ describe('treatment feature', function() { }) ); + $('[data-test=ViewLimitValues]').waitForExist(); assert($('[data-test=ViewLimitValues]').isVisible()); }); @@ -436,6 +437,7 @@ describe('treatment feature', function() { }) ); + $('[data-test=ViewLimitValues]').waitForExist(); assert($('[data-test=ViewLimitValues]').isVisible()); }); diff --git a/end-to-end-test/remote/specs/core/home.spec.js b/end-to-end-test/remote/specs/core/home.spec.js index f884920d1de..840577e76df 100644 --- a/end-to-end-test/remote/specs/core/home.spec.js +++ b/end-to-end-test/remote/specs/core/home.spec.js @@ -290,7 +290,7 @@ describe('case set selection in front page query form', function() { clickQueryByGeneButton(); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); @@ -356,11 +356,11 @@ describe('case set selection in front page query form', function() { clickQueryByGeneButton(); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="cna-gistic-cna_rae-cna_consensus"]', 10000 ); browser.waitForExist(selectedCaseSet_sel, 10000); @@ -388,11 +388,11 @@ describe('case set selection in front page query form', function() { clickQueryByGeneButton(); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); browser.waitForExist(selectedCaseSet_sel, 10000); @@ -485,22 +485,22 @@ describe('genetic profile selection in front page query form', () => { // wait for data type priority selector to load browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]' ), "'Mutation' should be selected" ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]' ), "'Copy number alterations' should be selected" ); @@ -551,22 +551,22 @@ describe('genetic profile selection in front page query form', () => { // wait for data type priority selector to load browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]' ), "'Mutation' should be selected" ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]' ), "'Copy number alterations' should be selected" ); @@ -669,21 +669,17 @@ describe('auto-selecting needed profiles for oql in query form', () => { // wait for query to load waitForOncoprint(20000); - const query = browser.execute(function() { - return urlWrapper.query; - }).value; + const profileFilter = ( + browser.execute(function() { + return urlWrapper.query; + }).value.profileFilter || '' + ).split(','); // mutation, cna, mrna profiles are there + assert.equal(profileFilter.includes('mutations'), true); + assert.equal(profileFilter.includes('gistic'), true); assert.equal( - query.genetic_profile_ids_PROFILE_MUTATION_EXTENDED, - 'prad_tcga_pub_mutations' - ); - assert.equal( - query.genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION, - 'prad_tcga_pub_gistic' - ); - assert.equal( - query.genetic_profile_ids_PROFILE_MRNA_EXPRESSION, - 'prad_tcga_pub_rna_seq_v2_mrna_median_Zscores' + profileFilter.includes('rna_seq_v2_mrna_median_Zscores'), + true ); }); }); @@ -760,20 +756,17 @@ describe('results page quick oql edit', () => { waitForOncoprint(20000); // mutation, cna, mrna profiles are there - query = browser.execute(function() { - return urlWrapper.query; - }).value; - assert.equal( - query.genetic_profile_ids_PROFILE_MUTATION_EXTENDED, - 'prad_tcga_pub_mutations' - ); - assert.equal( - query.genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION, - 'prad_tcga_pub_gistic' - ); + let profileFilter = ( + browser.execute(function() { + return urlWrapper.query; + }).value.profileFilter || '' + ).split(','); + // mutation, cna, mrna profiles are there + assert.equal(profileFilter.includes('mutations'), true); + assert.equal(profileFilter.includes('gistic'), true); assert.equal( - query.genetic_profile_ids_PROFILE_MRNA_EXPRESSION, - 'prad_tcga_pub_rna_seq_v2_mrna_median_Zscores' + profileFilter.includes('rna_seq_v2_mrna_median_Zscores'), + true ); }); }); diff --git a/end-to-end-test/remote/specs/core/oncoprint.spec.js b/end-to-end-test/remote/specs/core/oncoprint.spec.js index 96fd41f46b5..504622438e8 100644 --- a/end-to-end-test/remote/specs/core/oncoprint.spec.js +++ b/end-to-end-test/remote/specs/core/oncoprint.spec.js @@ -556,11 +556,11 @@ describe('oncoprint', function() { clickQueryByGeneButton(); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); diff --git a/end-to-end-test/remote/specs/core/plots.screenshot.spec.js b/end-to-end-test/remote/specs/core/plots.screenshot.spec.js index 5d9c03224fb..4a83612d7bb 100644 --- a/end-to-end-test/remote/specs/core/plots.screenshot.spec.js +++ b/end-to-end-test/remote/specs/core/plots.screenshot.spec.js @@ -457,6 +457,12 @@ describe('plots tab screenshot tests', function() { browser.click('.coloringLogScale'); waitForAndCheckPlotsTab(); }); + it('plots tab with structural variant coloring', () => { + goToUrlAndSetLocalStorage( + `${CBIOPORTAL_URL}/results/plots?Action=Submit&RPPA_SCORE_THRESHOLD=2.0&Z_SCORE_THRESHOLD=2.0&cancer_study_list=prad_mich&case_set_id=prad_mich_cna_seq&data_priority=0&gene_list=ERG&geneset_list=%20&genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION=prad_mich_cna&genetic_profile_ids_PROFILE_MUTATION_EXTENDED=prad_mich_mutations&genetic_profile_ids_PROFILE_STRUCTURAL_VARIANT=prad_mich_fusion&plots_coloring_selection=%7B"colorByCopyNumber"%3A"true"%2C"colorBySv"%3A"true"%7D&plots_horz_selection=%7B"dataType"%3A"clinical_attribute"%7D&plots_vert_selection=%7B"selectedGeneOption"%3A2078%2C"dataType"%3A"COPY_NUMBER_ALTERATION"%7D&profileFilter=0&tab_index=tab_visualize` + ); + waitForAndCheckPlotsTab(); + }); }); describe('plots tab multiple studies screenshot tests', function() { diff --git a/end-to-end-test/remote/specs/core/results.logic.spec.js b/end-to-end-test/remote/specs/core/results.logic.spec.js index 766f2382964..af247d47243 100644 --- a/end-to-end-test/remote/specs/core/results.logic.spec.js +++ b/end-to-end-test/remote/specs/core/results.logic.spec.js @@ -217,11 +217,11 @@ describe('case set selection in modify query form', function() { browser.pause(100); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); browser.waitForExist(selectedCaseSet_sel, 10000); @@ -264,11 +264,11 @@ describe('case set selection in modify query form', function() { ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="cna-gistic-cna_rae-cna_consensus"]', 10000 ); browser.waitForExist(selectedCaseSet_sel, 10000); @@ -348,22 +348,22 @@ describe('genetic profile selection in modify query form', function() { // wait for data type priority selector to load browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]', 10000 ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]' ), "'Mutation' should be selected" ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="gistic"]' ), "'Copy number alterations' should be selected" ); @@ -449,22 +449,22 @@ describe('genetic profile selection in modify query form', function() { // wait for data type priority selector to load browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]', 10000 ); browser.waitForExist( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]', + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="cna-gistic-cna_rae-cna_consensus"]', 10000 ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="M"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="mutations"]' ), "'Mutation' should be selected" ); assert( browser.isSelected( - '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="C"]' + '[data-test="dataTypePrioritySelector"] input[type="checkbox"][data-test="cna-gistic-cna_rae-cna_consensus"]' ), "'Copy number alterations' should be selected" ); diff --git a/end-to-end-test/remote/specs/core/studyview.spec.js b/end-to-end-test/remote/specs/core/studyview.spec.js index 6d5b0e07f93..b379c81486c 100644 --- a/end-to-end-test/remote/specs/core/studyview.spec.js +++ b/end-to-end-test/remote/specs/core/studyview.spec.js @@ -689,20 +689,16 @@ describe('submit genes to results view query', () => { waitForOncoprint(20000); // only mrna profile is there - const query = browser.execute(function() { - return urlWrapper.query; - }).value; + const profileFilter = ( + browser.execute(function() { + return urlWrapper.query; + }).value.profileFilter || '' + ).split(','); + assert.equal(profileFilter.includes('mutations'), false); + assert.equal(profileFilter.includes('gistic'), false); assert.equal( - query.genetic_profile_ids_PROFILE_MUTATION_EXTENDED, - undefined - ); - assert.equal( - query.genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION, - undefined - ); - assert.equal( - query.genetic_profile_ids_PROFILE_MRNA_EXPRESSION, - 'acc_tcga_pan_can_atlas_2018_rna_seq_v2_mrna_median_Zscores' + profileFilter.includes('rna_seq_v2_mrna_median_Zscores'), + true ); }); diff --git a/env/custom.sh b/env/custom.sh index fabbea1d01e..c5e767b5812 100644 --- a/env/custom.sh +++ b/env/custom.sh @@ -1,2 +1,2 @@ -export BRANCH_ENV="http://localhost:8080" +export BACKEND=cbioportal:fusion-sv-migration export GENOME_NEXUS_URL="https://www.genomenexus.org" diff --git a/env/fusion-sv-migration.sh b/env/fusion-sv-migration.sh new file mode 100644 index 00000000000..497aef27103 --- /dev/null +++ b/env/fusion-sv-migration.sh @@ -0,0 +1,3 @@ +export CBIOPORTAL_URL="https://beta.cbioportal.org" +export GENOME_NEXUS_URL="https://www.genomenexus.org" +export BACKEND=cbioportal:fusion-sv-migration \ No newline at end of file diff --git a/env/master.sh b/env/master.sh index 44e01344dcc..1ac0a33a759 100644 --- a/env/master.sh +++ b/env/master.sh @@ -1,2 +1,2 @@ -export CBIOPORTAL_URL="https://master.cbioportal.org" +export CBIOPORTAL_URL="https://beta.cbioportal.org" export GENOME_NEXUS_URL="https://www.genomenexus.org" diff --git a/packages/cbioportal-frontend-commons/src/lib/AlterationColors.ts b/packages/cbioportal-frontend-commons/src/lib/AlterationColors.ts index 5372d7cc1c2..bf84bea613f 100644 --- a/packages/cbioportal-frontend-commons/src/lib/AlterationColors.ts +++ b/packages/cbioportal-frontend-commons/src/lib/AlterationColors.ts @@ -7,7 +7,7 @@ export const MUT_COLOR_TRUNC = '#000000'; export const MUT_COLOR_TRUNC_PASSENGER = '#708090'; export const MUT_COLOR_SPLICE = '#e5802b'; export const MUT_COLOR_SPLICE_PASSENGER = '#f0b87b'; -export const MUT_COLOR_FUSION = '#8B00C9'; +export const STRUCTURAL_VARIANT_COLOR = '#8B00C9'; export const MUT_COLOR_PROMOTER = '#00B7CE'; export const MUT_COLOR_OTHER = '#cf58bc'; //'#cfb537'; export const MRNA_COLOR_HIGH = '#ff9999'; diff --git a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI-docs.json b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI-docs.json index 13a4f2ec775..686de060642 100644 --- a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI-docs.json +++ b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI-docs.json @@ -2567,7 +2567,7 @@ "deprecated": false } }, - "/structuralvariant/fetch": { + "/structural-variant/fetch": { "post": { "tags": [ "Structural Variants" @@ -2590,7 +2590,9 @@ "type": "integer", "format": "int32" }, - "collectionFormat": "multi" + "collectionFormat": "multi", + "maximum": 10000000, + "minimum": 0 }, { "name": "molecularProfileIds", @@ -2602,7 +2604,7 @@ }, "collectionFormat": "multi", "maximum": 10000000, - "minimum": 1 + "minimum": 0 }, { "name": "sampleMolecularIdentifiers[0].molecularProfileId", @@ -5787,10 +5789,6 @@ "sampleId": { "type": "string" }, - "sampleIdInternal": { - "type": "integer", - "format": "int32" - }, "site1Chromosome": { "type": "string" }, @@ -5842,10 +5840,6 @@ "type": "integer", "format": "int32" }, - "structuralVariantId": { - "type": "integer", - "format": "int64" - }, "studyId": { "type": "string" }, @@ -5884,7 +5878,9 @@ "type": "array", "items": { "type": "integer", - "format": "int32" + "format": "int32", + "minimum": 0, + "maximum": 10000000 } }, "molecularProfileIds": { diff --git a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI.ts b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI.ts index fa1d91398c9..abb58c25be1 100644 --- a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI.ts +++ b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPI.ts @@ -659,8 +659,6 @@ export type StructuralVariant = { 'sampleId': string - 'sampleIdInternal': number - 'site1Chromosome': string 'site1Description': string @@ -691,8 +689,6 @@ export type StructuralVariant = { 'site2Position': number - 'structuralVariantId': number - 'studyId': string 'tumorPairedEndReadCount': number @@ -4712,7 +4708,7 @@ export default class CBioPortalAPI { $queryParameters ? : any }): string { let queryParameters: any = {}; - let path = '/structuralvariant/fetch'; + let path = '/structural-variant/fetch'; if (parameters['entrezGeneIds'] !== undefined) { queryParameters['entrezGeneIds'] = parameters['entrezGeneIds']; } @@ -4761,7 +4757,7 @@ export default class CBioPortalAPI { const domain = parameters.$domain ? parameters.$domain : this.domain; const errorHandlers = this.errorHandlers; const request = this.request; - let path = '/structuralvariant/fetch'; + let path = '/structural-variant/fetch'; let body: any; let queryParameters: any = {}; let headers: any = {}; diff --git a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal-docs.json b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal-docs.json index 21653b79e3d..492748d733b 100644 --- a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal-docs.json +++ b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal-docs.json @@ -1327,44 +1327,6 @@ "deprecated": false } }, - "/fusion-genes/fetch": { - "post": { - "tags": [ - "Study View" - ], - "summary": "Fetch fusion genes by study view filter", - "operationId": "fetchFusionGenesUsingPOST", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "parameters": [ - { - "in": "body", - "name": "studyViewFilter", - "description": "Study view filter", - "required": true, - "schema": { - "$ref": "#/definitions/StudyViewFilter" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/AlterationCountByGene" - } - } - } - }, - "deprecated": false - } - }, "/generic-assay-data-bin-counts/fetch": { "post": { "tags": [ @@ -2534,6 +2496,44 @@ "deprecated": false } }, + "/structuralvariant-genes/fetch": { + "post": { + "tags": [ + "Study View" + ], + "summary": "Fetch structural variant genes by study view filter", + "operationId": "fetchStructuralVariantGenesUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "studyViewFilter", + "description": "Study view filter", + "required": true, + "schema": { + "$ref": "#/definitions/StudyViewFilter" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/AlterationCountByGene" + } + } + } + }, + "deprecated": false + } + }, "/studies/{studyId}/clinical-events": { "get": { "tags": [ @@ -3527,6 +3527,9 @@ "additionalProperties": { "type": "boolean" } + }, + "structuralVariants": { + "type": "boolean" } }, "title": "AlterationEventTypeFilter" diff --git a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts index 4db4dfebf84..2275b1c582f 100644 --- a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts +++ b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts @@ -34,6 +34,8 @@ export type AlterationEventTypeFilter = { 'mutationEventTypes': {} + 'structuralVariants': boolean + }; export type AndedPatientTreatmentFilters = { 'filters': Array < OredPatientTreatmentFilters > @@ -3360,83 +3362,6 @@ export default class CBioPortalAPIInternal { return response.body; }); }; - fetchFusionGenesUsingPOSTURL(parameters: { - 'studyViewFilter': StudyViewFilter, - $queryParameters ? : any - }): string { - let queryParameters: any = {}; - let path = '/fusion-genes/fetch'; - - if (parameters.$queryParameters) { - Object.keys(parameters.$queryParameters).forEach(function(parameterName) { - var parameter = parameters.$queryParameters[parameterName]; - queryParameters[parameterName] = parameter; - }); - } - let keys = Object.keys(queryParameters); - return this.domain + path + (keys.length > 0 ? '?' + (keys.map(key => key + '=' + encodeURIComponent(queryParameters[key])).join('&')) : ''); - }; - - /** - * Fetch fusion genes by study view filter - * @method - * @name CBioPortalAPIInternal#fetchFusionGenesUsingPOST - * @param {} studyViewFilter - Study view filter - */ - fetchFusionGenesUsingPOSTWithHttpInfo(parameters: { - 'studyViewFilter': StudyViewFilter, - $queryParameters ? : any, - $domain ? : string - }): Promise < request.Response > { - const domain = parameters.$domain ? parameters.$domain : this.domain; - const errorHandlers = this.errorHandlers; - const request = this.request; - let path = '/fusion-genes/fetch'; - let body: any; - let queryParameters: any = {}; - let headers: any = {}; - let form: any = {}; - return new Promise(function(resolve, reject) { - headers['Accept'] = 'application/json'; - headers['Content-Type'] = 'application/json'; - - if (parameters['studyViewFilter'] !== undefined) { - body = parameters['studyViewFilter']; - } - - if (parameters['studyViewFilter'] === undefined) { - reject(new Error('Missing required parameter: studyViewFilter')); - return; - } - - if (parameters.$queryParameters) { - Object.keys(parameters.$queryParameters).forEach(function(parameterName) { - var parameter = parameters.$queryParameters[parameterName]; - queryParameters[parameterName] = parameter; - }); - } - - request('POST', domain + path, body, headers, queryParameters, form, reject, resolve, errorHandlers); - - }); - }; - - /** - * Fetch fusion genes by study view filter - * @method - * @name CBioPortalAPIInternal#fetchFusionGenesUsingPOST - * @param {} studyViewFilter - Study view filter - */ - fetchFusionGenesUsingPOST(parameters: { - 'studyViewFilter': StudyViewFilter, - $queryParameters ? : any, - $domain ? : string - }): Promise < Array < AlterationCountByGene > - > { - return this.fetchFusionGenesUsingPOSTWithHttpInfo(parameters).then(function(response: request.Response) { - return response.body; - }); - }; fetchGenericAssayDataBinCountsUsingPOSTURL(parameters: { 'dataBinMethod' ? : "STATIC" | "DYNAMIC", 'genericAssayDataBinCountFilter': GenericAssayDataBinCountFilter, @@ -5659,6 +5584,83 @@ export default class CBioPortalAPIInternal { return response.body; }); }; + fetchStructuralVariantGenesUsingPOSTURL(parameters: { + 'studyViewFilter': StudyViewFilter, + $queryParameters ? : any + }): string { + let queryParameters: any = {}; + let path = '/structuralvariant-genes/fetch'; + + if (parameters.$queryParameters) { + Object.keys(parameters.$queryParameters).forEach(function(parameterName) { + var parameter = parameters.$queryParameters[parameterName]; + queryParameters[parameterName] = parameter; + }); + } + let keys = Object.keys(queryParameters); + return this.domain + path + (keys.length > 0 ? '?' + (keys.map(key => key + '=' + encodeURIComponent(queryParameters[key])).join('&')) : ''); + }; + + /** + * Fetch structural variant genes by study view filter + * @method + * @name CBioPortalAPIInternal#fetchStructuralVariantGenesUsingPOST + * @param {} studyViewFilter - Study view filter + */ + fetchStructuralVariantGenesUsingPOSTWithHttpInfo(parameters: { + 'studyViewFilter': StudyViewFilter, + $queryParameters ? : any, + $domain ? : string + }): Promise < request.Response > { + const domain = parameters.$domain ? parameters.$domain : this.domain; + const errorHandlers = this.errorHandlers; + const request = this.request; + let path = '/structuralvariant-genes/fetch'; + let body: any; + let queryParameters: any = {}; + let headers: any = {}; + let form: any = {}; + return new Promise(function(resolve, reject) { + headers['Accept'] = 'application/json'; + headers['Content-Type'] = 'application/json'; + + if (parameters['studyViewFilter'] !== undefined) { + body = parameters['studyViewFilter']; + } + + if (parameters['studyViewFilter'] === undefined) { + reject(new Error('Missing required parameter: studyViewFilter')); + return; + } + + if (parameters.$queryParameters) { + Object.keys(parameters.$queryParameters).forEach(function(parameterName) { + var parameter = parameters.$queryParameters[parameterName]; + queryParameters[parameterName] = parameter; + }); + } + + request('POST', domain + path, body, headers, queryParameters, form, reject, resolve, errorHandlers); + + }); + }; + + /** + * Fetch structural variant genes by study view filter + * @method + * @name CBioPortalAPIInternal#fetchStructuralVariantGenesUsingPOST + * @param {} studyViewFilter - Study view filter + */ + fetchStructuralVariantGenesUsingPOST(parameters: { + 'studyViewFilter': StudyViewFilter, + $queryParameters ? : any, + $domain ? : string + }): Promise < Array < AlterationCountByGene > + > { + return this.fetchStructuralVariantGenesUsingPOSTWithHttpInfo(parameters).then(function(response: request.Response) { + return response.body; + }); + }; getAllClinicalEventsInStudyUsingGETURL(parameters: { 'direction' ? : "ASC" | "DESC", 'pageNumber' ? : number, diff --git a/packages/cbioportal-utils/src/oncokb/OncoKbUtils.ts b/packages/cbioportal-utils/src/oncokb/OncoKbUtils.ts index d6d5a25333e..8784ee7ff14 100644 --- a/packages/cbioportal-utils/src/oncokb/OncoKbUtils.ts +++ b/packages/cbioportal-utils/src/oncokb/OncoKbUtils.ts @@ -76,6 +76,19 @@ export function generateQueryVariant( return query; } +export function generateQueryStructuralVariantId( + site1EntrezGeneId: number, + site2EntrezGeneId: number, + tumorType: string | null +): string { + let id = `${site1EntrezGeneId}_${site2EntrezGeneId}`; + if (tumorType) { + id = `${id}_${tumorType}`; + } + + return id.trim().replace(/\s/g, '_'); +} + export function generateQueryVariantId( entrezGeneId: number, tumorType: string | null, @@ -225,6 +238,45 @@ export function generateAnnotateStructuralVariantQuery( } as AnnotateStructuralVariantQuery; } +export enum StructuralVariantType { + DELETION = 'DELETION', + TRANSLOCATION = 'TRANSLOCATION', + DUPLICATION = 'DUPLICATION', + INSERTION = 'INSERTION', + INVERSION = 'INVERSION', + FUSION = 'FUSION', + UNKNOWN = 'UNKNOWN', +} + +export function generateAnnotateStructuralVariantQueryFromGenes( + site1EntrezGeneId: number, + site2EntrezGeneId: number, + tumorType: string | null, + structuralVariantType: keyof typeof StructuralVariantType, + evidenceTypes?: EvidenceType[] +): AnnotateStructuralVariantQuery { + return { + id: generateQueryStructuralVariantId( + site1EntrezGeneId, + site2EntrezGeneId, + tumorType + ), + geneA: { + entrezGeneId: site1EntrezGeneId, + }, + geneB: { + entrezGeneId: site2EntrezGeneId, + }, + structuralVariantType, + functionalFusion: !( + site1EntrezGeneId === site2EntrezGeneId || + site2EntrezGeneId === null + ), + tumorType, + evidenceTypes: evidenceTypes, + } as AnnotateStructuralVariantQuery; +} + export function calculateOncoKbAvailableDataType( annotations: IndicatorQueryResp[] ) { diff --git a/packages/react-mutation-mapper/src/component/filter/ProteinImpactTypeHelper.tsx b/packages/react-mutation-mapper/src/component/filter/ProteinImpactTypeHelper.tsx index b6179e77762..31ad50fb11d 100644 --- a/packages/react-mutation-mapper/src/component/filter/ProteinImpactTypeHelper.tsx +++ b/packages/react-mutation-mapper/src/component/filter/ProteinImpactTypeHelper.tsx @@ -42,7 +42,7 @@ export function getProteinImpactTypeOptionDisplayValueMap(proteinImpactTypeColor color: proteinImpactTypeColorMap[ProteinImpactType.FUSION], }} > - Fusion + SV/Fusion ), [ProteinImpactType.OTHER]: ( diff --git a/packages/react-mutation-mapper/src/component/lollipopMutationPlot/DefaultLollipopPlotLegend.tsx b/packages/react-mutation-mapper/src/component/lollipopMutationPlot/DefaultLollipopPlotLegend.tsx index d1bc3849716..3f89a969d35 100644 --- a/packages/react-mutation-mapper/src/component/lollipopMutationPlot/DefaultLollipopPlotLegend.tsx +++ b/packages/react-mutation-mapper/src/component/lollipopMutationPlot/DefaultLollipopPlotLegend.tsx @@ -3,7 +3,7 @@ import { MUT_COLOR_MISSENSE, MUT_COLOR_TRUNC, MUT_COLOR_INFRAME, - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_SPLICE, MUT_COLOR_OTHER, } from 'cbioportal-frontend-commons'; @@ -25,7 +25,7 @@ export default class DefaultLollipopPlotLegend extends React.Component< truncatingColor: MUT_COLOR_TRUNC, inframeColor: MUT_COLOR_INFRAME, spliceColor: MUT_COLOR_SPLICE, - fusionColor: MUT_COLOR_FUSION, + fusionColor: STRUCTURAL_VARIANT_COLOR, otherColor: MUT_COLOR_OTHER, }; diff --git a/packages/react-mutation-mapper/src/store/DefaultMutationMapperDataFetcher.ts b/packages/react-mutation-mapper/src/store/DefaultMutationMapperDataFetcher.ts index 07aa11f70c1..554a21c0c2a 100644 --- a/packages/react-mutation-mapper/src/store/DefaultMutationMapperDataFetcher.ts +++ b/packages/react-mutation-mapper/src/store/DefaultMutationMapperDataFetcher.ts @@ -11,6 +11,7 @@ import { IOncoKbData, Mutation, uniqueGenomicLocations, + StructuralVariantType, } from 'cbioportal-utils'; import { AnnotateMutationByProteinChangeQuery, @@ -319,7 +320,9 @@ export class DefaultMutationMapperDataFetcher const structuralQueryVariants: AnnotateStructuralVariantQuery[] = _.uniqBy( _.map( queryVariants.filter( - mutation => mutation.mutationType === 'Fusion' + mutation => + mutation.mutationType?.toUpperCase() === + StructuralVariantType.FUSION ), (mutation: Mutation) => { return generateAnnotateStructuralVariantQuery( diff --git a/packages/react-mutation-mapper/src/util/MutationTypeUtils.ts b/packages/react-mutation-mapper/src/util/MutationTypeUtils.ts index 682c6b5dd3f..166e93f4837 100644 --- a/packages/react-mutation-mapper/src/util/MutationTypeUtils.ts +++ b/packages/react-mutation-mapper/src/util/MutationTypeUtils.ts @@ -8,7 +8,7 @@ import { MUT_COLOR_INFRAME, MUT_COLOR_TRUNC, MUT_COLOR_SPLICE, - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_OTHER, MUT_COLOR_MISSENSE_PASSENGER, MUT_COLOR_INFRAME_PASSENGER, @@ -29,7 +29,7 @@ export const DEFAULT_PROTEIN_IMPACT_TYPE_COLORS: IProteinImpactTypeColors = { truncatingVusColor: MUT_COLOR_TRUNC_PASSENGER, spliceColor: MUT_COLOR_SPLICE, spliceVusColor: MUT_COLOR_SPLICE_PASSENGER, - fusionColor: MUT_COLOR_FUSION, + fusionColor: STRUCTURAL_VARIANT_COLOR, otherColor: MUT_COLOR_OTHER, }; diff --git a/src/config/serverConfigDefaults.ts b/src/config/serverConfigDefaults.ts index ebd6c0e4afd..b7c1bb01b1b 100644 --- a/src/config/serverConfigDefaults.ts +++ b/src/config/serverConfigDefaults.ts @@ -133,7 +133,7 @@ const ServerConfigDefaults: Partial = { PFS_SURVIVAL: 250, MUTATION_COUNT_CNA_FRACTION: 200, MUTATED_GENES_TABLE: 90, - FUSION_GENES_TABLE: 85, + STRUCTURAL_VARIANT_GENES_TABLE: 85, CNA_GENES_TABLE: 80, PATIENT_TREATMENTS_TABLE: 75, SAMPLE_TREATMENTS_TABLE: 75, diff --git a/src/pages/groupComparison/AlterationEnrichments.tsx b/src/pages/groupComparison/AlterationEnrichments.tsx index 8eb62b492c0..0c827445868 100644 --- a/src/pages/groupComparison/AlterationEnrichments.tsx +++ b/src/pages/groupComparison/AlterationEnrichments.tsx @@ -40,6 +40,7 @@ export default class AlterationEnrichments extends React.Component< this.props.store.alterationsEnrichmentAnalysisGroups, this.props.store.selectedStudyMutationEnrichmentProfileMap, this.props.store.selectedStudyCopyNumberEnrichmentProfileMap, + this.props.store.selectedStudyStructuralVariantEnrichmentProfileMap, this.props.store.studies, ], render: () => { diff --git a/src/pages/groupComparison/GroupComparisonPage.tsx b/src/pages/groupComparison/GroupComparisonPage.tsx index 236108b9f17..efa63a0f713 100644 --- a/src/pages/groupComparison/GroupComparisonPage.tsx +++ b/src/pages/groupComparison/GroupComparisonPage.tsx @@ -119,7 +119,7 @@ export default class GroupComparisonPage extends React.Component< this.store._activeGroupsNotOverlapRemoved, this.store.activeGroups, this.store.mutationEnrichmentProfiles, - this.store.structuralVariantProfiles, + this.store.structuralVariantEnrichmentProfiles, this.store.copyNumberEnrichmentProfiles, this.store.mRNAEnrichmentProfiles, this.store.proteinEnrichmentProfiles, @@ -184,7 +184,9 @@ export default class GroupComparisonPage extends React.Component< this.store.hasMutationEnrichmentData } showCnas={this.store.hasCnaEnrichmentData} - showFusions={this.store.hasFusionEnrichmentData} + showStructuralVariants={ + this.store.hasStructuralVariantData + } /> diff --git a/src/pages/groupComparison/GroupComparisonStore.ts b/src/pages/groupComparison/GroupComparisonStore.ts index bcea32d6d8d..fbc83dd8d81 100644 --- a/src/pages/groupComparison/GroupComparisonStore.ts +++ b/src/pages/groupComparison/GroupComparisonStore.ts @@ -5,7 +5,6 @@ import { getOrdinals, getStudyIds, } from './GroupComparisonUtils'; -import { GroupComparisonTab } from './GroupComparisonTabs'; import { remoteData, stringListToIndexSet } from 'cbioportal-frontend-commons'; import { SampleFilter, CancerStudy } from 'cbioportal-ts-api-client'; import { action, computed, observable, makeObservable } from 'mobx'; @@ -21,9 +20,7 @@ import { import { AppStore } from '../../AppStore'; import { GACustomFieldsEnum, trackEvent } from 'shared/lib/tracking'; import ifNotDefined from '../../shared/lib/ifNotDefined'; -import GroupComparisonURLWrapper, { - GroupComparisonURLQuery, -} from './GroupComparisonURLWrapper'; +import GroupComparisonURLWrapper from './GroupComparisonURLWrapper'; import ComparisonStore, { OverlapStrategy, } from '../../shared/lib/comparison/ComparisonStore'; @@ -32,9 +29,6 @@ import sessionServiceClient from 'shared/api//sessionServiceInstance'; import { COLORS } from '../studyView/StudyViewUtils'; export default class GroupComparisonStore extends ComparisonStore { - @observable private _currentTabId: - | GroupComparisonTab - | undefined = undefined; @observable.ref private sessionId: string; constructor( diff --git a/src/pages/patientView/PatientViewPage.tsx b/src/pages/patientView/PatientViewPage.tsx index b14cba515bb..6c1eef71d7c 100644 --- a/src/pages/patientView/PatientViewPage.tsx +++ b/src/pages/patientView/PatientViewPage.tsx @@ -78,6 +78,7 @@ import PatientViewPathwayMapper from './pathwayMapper/PatientViewPathwayMapper'; import ResourcesTab, { RESOURCES_TAB_NAME } from './resources/ResourcesTab'; import { MakeMobxView } from '../../shared/components/MobxView'; import ResourceTab from '../../shared/components/resources/ResourceTab'; +import PatientViewStructuralVariantTable from './structuralVariant/PatientViewStructuralVariantTable'; import TimelineWrapper from './timeline2/TimelineWrapper'; import { isFusion } from '../../shared/lib/MutationUtils'; import { Mutation } from 'cbioportal-ts-api-client'; @@ -205,13 +206,6 @@ export default class PatientViewPage extends React.Component< }, { fireImmediately: true } ); - - this.onMutationTableColumnVisibilityToggled = this.onMutationTableColumnVisibilityToggled.bind( - this - ); - this.onCnaTableColumnVisibilityToggled = this.onCnaTableColumnVisibilityToggled.bind( - this - ); } private dataStore: PatientViewMutationsDataStore; @@ -310,7 +304,8 @@ export default class PatientViewPage extends React.Component< ); } - @action private onCnaTableColumnVisibilityToggled( + @action.bound + private onCnaTableColumnVisibilityToggled( columnId: string, columnVisibility?: IColumnVisibilityDef[] ) { @@ -321,7 +316,8 @@ export default class PatientViewPage extends React.Component< ); } - @action private onMutationTableColumnVisibilityToggled( + @action.bound + private onMutationTableColumnVisibilityToggled( columnId: string, columnVisibility?: IColumnVisibilityDef[] ) { @@ -465,35 +461,6 @@ export default class PatientViewPage extends React.Component< this.genePanelModal.genePanelId ]; } - @computed get sampleManager() { - if ( - this.patientViewPageStore.patientViewDataForAllSamplesForPatient - .isComplete && - this.patientViewPageStore.studyMetaData.isComplete - ) { - const patientData = this.patientViewPageStore - .patientViewDataForAllSamplesForPatient.result; - - if ( - this.patientViewPageStore.clinicalEvents.isComplete && - this.patientViewPageStore.clinicalEvents.result!.length > 0 - ) { - return new SampleManager( - patientData.samples!, - this.patientViewPageStore.clinicalEvents.result, - this.patientViewPageStore.sampleIds - ); - } else { - return new SampleManager( - patientData.samples!, - undefined, - this.patientViewPageStore.sampleIds - ); - } - } else { - return null; - } - } @autobind private onMutationTableRowClick(d: Mutation[]) { @@ -584,7 +551,10 @@ export default class PatientViewPage extends React.Component< } public render() { - const sampleManager = this.sampleManager; + let sampleManager: SampleManager | null = null; + if (this.patientViewPageStore.sampleManager.isComplete) { + sampleManager = this.patientViewPageStore.sampleManager.result!; + } let cohortNav: JSX.Element | null = null; let studyName: JSX.Element | null = null; @@ -1423,6 +1393,14 @@ export default class PatientViewPage extends React.Component< /> )} + +
+ {!!sampleManager && this.patientViewPageStore.sampleIds.length > @@ -1531,7 +1509,10 @@ export default class PatientViewPage extends React.Component<
diff --git a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts index c3b83f5eca9..c75e3a13848 100644 --- a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts +++ b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts @@ -16,6 +16,7 @@ import { ResourceData, Sample, SampleMolecularIdentifier, + StructuralVariantFilter, GenericAssayData, GenericAssayMeta, GenericAssayDataMultipleStudyFilter, @@ -77,8 +78,6 @@ import { fetchCopyNumberSegments, fetchCosmicData, fetchDiscreteCNAData, - fetchGenePanel, - fetchGenePanelData, fetchGisticData, fetchMutationData, fetchMutSigData, @@ -113,14 +112,15 @@ import { mergeDiscreteCNAData, mergeMutations, mergeMutationsIncludingUncalled, - noGenePanelUsed, ONCOKB_DEFAULT, + generateStructuralVariantId, + fetchStructuralVariantOncoKbData, parseOtherBiomarkerQueryId, tumorTypeResolver, } from 'shared/lib/StoreUtils'; import { + computeGenePanelInformation, CoverageInformation, - getCoverageInformation, } from 'shared/lib/GenePanelUtils'; import { fetchCivicGenes, @@ -154,6 +154,8 @@ import { getGeneFilterDefault } from './PatientViewPageStoreUtil'; import { checkNonProfiledGenesExist } from '../PatientViewPageUtils'; import autobind from 'autobind-decorator'; import { createVariantAnnotationsByMutationFetcher } from 'shared/components/mutationMapper/MutationMapperUtils'; +import SampleManager from '../SampleManager'; +import { getFilteredMolecularProfilesByAlterationType } from 'pages/studyView/StudyViewUtils'; import { getMyCancerGenomeData, getMyVariantInfoAnnotationsFromIndexedVariantAnnotations, @@ -170,6 +172,7 @@ import { GeneticTrackDatum } from 'shared/components/oncoprint/Oncoprint'; import { AlterationTypeConstants, AnnotatedExtendedAlteration, + DataTypeConstants, CustomDriverNumericGeneMolecularData, } from 'pages/resultsView/ResultsViewPageStore'; import { @@ -337,6 +340,11 @@ export class PatientViewPageStore { this.openResourceTabMap.set(resourceId, open); } + @action.bound + public onFilterGenesStructuralVariantTable(option: GeneFilterOption): void { + this.structuralVariantTableGeneFilterOption = option; + } + @observable public mutationTableGeneFilterOption: GeneFilterOption = getGeneFilterDefault( getBrowserWindow().frontendConfig @@ -345,6 +353,10 @@ export class PatientViewPageStore { public copyNumberTableGeneFilterOption: GeneFilterOption = getGeneFilterDefault( getBrowserWindow().frontendConfig ); + @observable + public structuralVariantTableGeneFilterOption: GeneFilterOption = getGeneFilterDefault( + getBrowserWindow().frontendConfig + ); @computed get sampleId() { return this._sampleId; @@ -387,20 +399,31 @@ export class PatientViewPageStore { }); readonly mutationMolecularProfileId = remoteData({ - await: () => [this.molecularProfilesInStudy], + await: () => [this.mutationMolecularProfile], invoke: async () => { - const profile = findMutationMolecularProfile( - this.molecularProfilesInStudy, - this.studyId - ); - if (profile) { - return profile.molecularProfileId; + if (this.mutationMolecularProfile.result) { + return this.mutationMolecularProfile.result.molecularProfileId; } else { return undefined; } }, }); + readonly structuralVariantProfile = remoteData({ + await: () => [this.studyIdToMolecularProfiles], + invoke: async () => { + const structuralVariantProfiles = getFilteredMolecularProfilesByAlterationType( + this.studyIdToMolecularProfiles.result, + AlterationTypeConstants.STRUCTURAL_VARIANT, + [DataTypeConstants.FUSION, DataTypeConstants.SV] + ); + if (structuralVariantProfiles.length > 0) { + return structuralVariantProfiles[0]; + } + return undefined; + }, + }); + readonly uncalledMutationMolecularProfileId = remoteData({ await: () => [this.molecularProfilesInStudy], invoke: async () => @@ -1143,6 +1166,32 @@ export class PatientViewPageStore { invoke: async () => client.getStudyUsingGET({ studyId: this.studyId }), }); + public sampleManager = remoteData({ + await: () => [ + this.patientViewDataForAllSamplesForPatient, + this.studyMetaData, + this.clinicalEvents, + ], + invoke: async () => { + const patientData = this.patientViewDataForAllSamplesForPatient + .result!; + + if (this.clinicalEvents.result.length > 0) { + return new SampleManager( + patientData.samples!, + this.clinicalEvents.result, + this.sampleIds + ); + } else { + return new SampleManager( + patientData.samples!, + undefined, + this.sampleIds + ); + } + }, + }); + readonly patientViewData = remoteData( { await: () => [ @@ -1201,6 +1250,20 @@ export class PatientViewPageStore { }); }, []); + readonly studyIdToMolecularProfiles = remoteData({ + await: () => [this.molecularProfilesInStudy], + invoke: () => { + return Promise.resolve( + _.groupBy( + this.molecularProfilesInStudy.result!, + molecularProfile => molecularProfile.studyId + ) + ); + }, + onError: error => {}, + default: {}, + }); + readonly molecularProfileIdToMolecularProfile = remoteData<{ [molecularProfileId: string]: MolecularProfile; }>( @@ -1394,29 +1457,123 @@ export class PatientViewPageStore { [] ); + readonly genePanelData = remoteData( + { + await: () => [this.samples, this.molecularProfilesInStudy], + invoke: async () => { + // gather sample molecular identifiers + const sampleMolecularIdentifiers: SampleMolecularIdentifier[] = []; + this.samples.result!.forEach(sample => { + const profiles = this.molecularProfilesInStudy.result!; + if (profiles) { + const sampleId = sample.sampleId; + for (const profile of profiles) { + sampleMolecularIdentifiers.push({ + molecularProfileId: profile.molecularProfileId, + sampleId, + }); + } + } + }); + // query for gene panel data using sample molecular identifiers + let genePanelData: GenePanelData[] = []; + if (sampleMolecularIdentifiers.length) { + genePanelData = await client.fetchGenePanelDataInMultipleMolecularProfilesUsingPOST( + { + sampleMolecularIdentifiers, + } + ); + } + + return genePanelData; + }, + }, + [] + ); + + readonly genePanels = remoteData( + { + await: () => [this.genePanelData], + invoke: async () => { + let genePanelData: GenePanelData[] = this.genePanelData.result; + + // query for gene panel metadata + const genePanelIds = _.uniq( + genePanelData + .map(gpData => gpData.genePanelId) + .filter(id => !!id) + ); + if (genePanelIds.length) { + return client.fetchGenePanelsUsingPOST({ + genePanelIds, + projection: 'DETAILED', + }); + } + return []; + }, + }, + [] + ); + + readonly genePanelIdToPanel = remoteData<{ + [genePanelId: string]: GenePanel; + }>( + { + await: () => [this.genePanels], + invoke: async () => { + return _.keyBy( + this.genePanels.result, + genePanel => genePanel.genePanelId + ); + }, + }, + {} + ); + readonly coverageInformation = remoteData( { await: () => [ this.mutatedGenes, - this.samplesWithUniqueKeys, - this.molecularProfilesInStudy, + this.samples, + this.genePanelData, + this.genePanels, ], - invoke: () => - getCoverageInformation( - this.samplesWithUniqueKeys, - this.mutatedGenes, - () => this.molecularProfilesInStudy.result!, - () => [ + invoke: async () => { + // plug all data into computeGenePanelInformation to generate coverageInformation object + return computeGenePanelInformation( + this.genePanelData.result, + this.genePanels.result, + this.samples.result!, + [ { - uniquePatientKey: this.samplesWithUniqueKeys - .result![0].uniquePatientKey, + uniquePatientKey: this.samples.result![0] + .uniquePatientKey, }, - ] - ), + ], + this.mutatedGenes.result! + ); + }, }, { samples: {}, patients: {} } ); + readonly genePanelDataByMolecularProfileIdAndSampleId = remoteData<{ + [profileId: string]: { [sampleId: string]: GenePanelData }; + }>( + { + await: () => [this.genePanelData], + invoke: async () => { + return _.chain(this.genePanelData.result!) + .groupBy(datum => datum.molecularProfileId) + .mapValues(data => + _.keyBy(data, genePanelData => genePanelData.sampleId) + ) + .value(); + }, + }, + {} + ); + readonly mutationData = remoteData( { await: () => [this.samples, this.mutationMolecularProfileId], @@ -1434,6 +1591,40 @@ export class PatientViewPageStore { [] ); + readonly structuralVariantData = remoteData({ + await: () => [this.samples, this.structuralVariantProfile], + invoke: async () => { + if (this.structuralVariantProfile.result) { + const structuralVariantFilter = { + sampleMolecularIdentifiers: this.sampleIds.map(sampleId => { + return { + molecularProfileId: this.structuralVariantProfile + .result!.molecularProfileId, + sampleId, + }; + }), + } as StructuralVariantFilter; + + return client.fetchStructuralVariantsUsingPOST({ + structuralVariantFilter, + }); + } + return []; + }, + default: [], + }); + + readonly groupedStructuralVariantData = remoteData({ + await: () => [this.structuralVariantData], + invoke: async () => { + return _(this.structuralVariantData.result) + .groupBy(generateStructuralVariantId) + .values() + .value(); + }, + default: [], + }); + readonly mutatedGenes = remoteData({ await: () => [this.mutationData], invoke: () => { @@ -1616,6 +1807,32 @@ export class PatientViewPageStore { ONCOKB_DEFAULT ); + readonly structuralVariantOncoKbData = remoteData( + { + await: () => [ + this.oncoKbAnnotatedGenes, + this.structuralVariantData, + this.clinicalDataForSamples, + this.studies, + ], + invoke: async () => { + if (AppConfig.serverConfig.show_oncokb) { + return fetchStructuralVariantOncoKbData( + this.uniqueSampleKeyToTumorType, + this.oncoKbAnnotatedGenes.result || {}, + this.structuralVariantData + ); + } else { + return ONCOKB_DEFAULT; + } + }, + onError: (err: Error) => { + // fail silently, leave the error handling responsibility to the data consumer + }, + }, + ONCOKB_DEFAULT + ); + readonly cnaCivicGenes = remoteData( { await: () => [this.discreteCNAData, this.clinicalDataForSamples], @@ -1672,16 +1889,21 @@ export class PatientViewPageStore { invoke: () => Promise.resolve(indexHotspotsData(this.hotspotData)), }); - readonly sampleToMutationGenePanelData = remoteData<{ - [sampleId: string]: GenePanelData; + readonly sampleToMutationGenePanelId = remoteData<{ + [sampleId: string]: string; }>( { - await: () => [this.mutationMolecularProfileId, this.samples], + await: () => [ + this.mutationMolecularProfileId, + this.genePanelDataByMolecularProfileIdAndSampleId, + ], invoke: async () => { if (this.mutationMolecularProfileId.result) { - return fetchGenePanelData( - this.mutationMolecularProfileId.result, - this.sampleIds + return _.mapValues( + this.genePanelDataByMolecularProfileIdAndSampleId + .result[this.mutationMolecularProfileId.result] || + {}, + genePanelData => genePanelData.genePanelId ); } return {}; @@ -1690,31 +1912,23 @@ export class PatientViewPageStore { {} ); - readonly sampleToMutationGenePanelId = remoteData<{ + readonly sampleToStructuralVariantGenePanelId = remoteData<{ [sampleId: string]: string; }>( { - await: () => [this.sampleToMutationGenePanelData], - invoke: async () => { - return _.mapValues( - this.sampleToMutationGenePanelData.result, - genePanelData => genePanelData.genePanelId - ); - }, - }, - {} - ); - - readonly sampleToDiscreteGenePanelData = remoteData<{ - [sampleId: string]: GenePanelData; - }>( - { - await: () => [this.molecularProfileIdDiscrete, this.samples], + await: () => [ + this.structuralVariantProfile, + this.genePanelDataByMolecularProfileIdAndSampleId, + ], invoke: async () => { - if (this.molecularProfileIdDiscrete.result) { - return fetchGenePanelData( - this.molecularProfileIdDiscrete.result, - this.sampleIds + if (this.structuralVariantProfile.result) { + return _.mapValues( + this.genePanelDataByMolecularProfileIdAndSampleId + .result[ + this.structuralVariantProfile.result! + .molecularProfileId + ] || {}, + genePanelData => genePanelData.genePanelId ); } return {}; @@ -1725,37 +1939,22 @@ export class PatientViewPageStore { readonly sampleToDiscreteGenePanelId = remoteData<{ [sampleId: string]: string; - }>( - { - await: () => [this.sampleToDiscreteGenePanelData], - invoke: async () => { - return _.mapValues( - this.sampleToDiscreteGenePanelData.result, - genePanelData => genePanelData.genePanelId - ); - }, - }, - {} - ); - - readonly genePanelIdToPanel = remoteData<{ - [genePanelId: string]: GenePanel; }>( { await: () => [ - this.sampleToMutationGenePanelData, - this.sampleToDiscreteGenePanelData, + this.molecularProfileIdDiscrete, + this.genePanelDataByMolecularProfileIdAndSampleId, ], invoke: async () => { - const sampleGenePanelInfo = _.concat( - _.values(this.sampleToMutationGenePanelData.result), - _.values(this.sampleToDiscreteGenePanelData.result) - ); - const panelIds = _(sampleGenePanelInfo) - .map(genePanelData => genePanelData.genePanelId) - .filter(genePanelId => !noGenePanelUsed(genePanelId)) - .value(); - return fetchGenePanel(panelIds); + if (this.molecularProfileIdDiscrete.result) { + return _.mapValues( + this.genePanelDataByMolecularProfileIdAndSampleId + .result[this.molecularProfileIdDiscrete.result] || + {}, + genePanelData => genePanelData.genePanelId + ); + } + return {}; }, }, {} @@ -1881,7 +2080,7 @@ export class PatientViewPageStore { value: 0, }; } - ); + ) as AnnotatedExtendedAlteration[]; return [...annotatedExtendedMutations, ...annotatedExtendedCnaData]; } @@ -2031,6 +2230,38 @@ export class PatientViewPageStore { }, }); + readonly structuralVariantTableShowGeneFilterMenu = remoteData({ + await: () => [ + this.samples, + this.sampleToStructuralVariantGenePanelId, + this.genePanelIdToEntrezGeneIds, + this.groupedStructuralVariantData, + ], + invoke: () => { + const entrezGeneIds: number[] = _.uniq( + _.flatMap(this.groupedStructuralVariantData.result, datum => + datum[0].site2EntrezGeneId + ? [ + datum[0].site1EntrezGeneId, + datum[0].site2EntrezGeneId, + ] + : [datum[0].site1EntrezGeneId] + ) + ); + const sampleIds = this.samples.result!.map(s => s.sampleId); + return Promise.resolve( + sampleIds.length > 1 && + checkNonProfiledGenesExist( + sampleIds, + entrezGeneIds, + this.sampleToStructuralVariantGenePanelId.result, + this.genePanelIdToEntrezGeneIds.result + ) + ); + }, + default: false, + }); + @computed get uniqueSampleKeyToTumorType(): { [sampleId: string]: string } { return generateUniqueSampleKeyToTumorTypeMap( this.clinicalDataForSamples, diff --git a/src/pages/patientView/structuralVariant/PatientViewStructuralVariantTable.tsx b/src/pages/patientView/structuralVariant/PatientViewStructuralVariantTable.tsx new file mode 100644 index 00000000000..7e78ef6ea58 --- /dev/null +++ b/src/pages/patientView/structuralVariant/PatientViewStructuralVariantTable.tsx @@ -0,0 +1,426 @@ +import * as React from 'react'; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { PatientViewPageStore } from '../clinicalInformation/PatientViewPageStore'; +import LazyMobXTable, { + Column, +} from 'shared/components/lazyMobXTable/LazyMobXTable'; +import { StructuralVariant } from 'cbioportal-ts-api-client'; +import TumorColumnFormatter from '../mutation/column/TumorColumnFormatter'; +import HeaderIconMenu from '../mutation/HeaderIconMenu'; +import GeneFilterMenu from '../mutation/GeneFilterMenu'; +import PanelColumnFormatter from 'shared/components/mutationTable/column/PanelColumnFormatter'; +import * as _ from 'lodash'; +import { MakeMobxView } from 'shared/components/MobxView'; +import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicator'; +import ErrorMessage from 'shared/components/ErrorMessage'; +import AnnotationColumnFormatter from './column/AnnotationColumnFormatter'; +import AppConfig from 'appConfig'; +import { ServerConfigHelpers } from 'config/config'; +import ChromosomeColumnFormatter from 'shared/components/mutationTable/column/ChromosomeColumnFormatter'; +import { remoteData } from 'cbioportal-frontend-commons'; + +export interface IPatientViewStructuralVariantTableProps { + store: PatientViewPageStore; + onSelectGenePanel?: (name: string) => void; +} + +type CNATableColumn = Column & { order: number }; + +class StructuralVariantTableComponent extends LazyMobXTable< + StructuralVariant[] +> {} + +@observer +export default class PatientViewStructuralVariantTable extends React.Component< + IPatientViewStructuralVariantTableProps, + {} +> { + constructor(props: IPatientViewStructuralVariantTableProps) { + super(props); + } + + readonly columns = remoteData({ + await: () => [ + this.props.store.sampleManager, + this.props.store.sampleToStructuralVariantGenePanelId, + this.props.store.genePanelIdToEntrezGeneIds, + this.props.store.structuralVariantTableShowGeneFilterMenu, + this.props.store.oncoKbAnnotatedGenes, + this.props.store.studyIdToStudy, + this.props.store.oncoKbCancerGenes, + ], + invoke: async () => { + const columns: CNATableColumn[] = []; + const numSamples = this.props.store.sampleIds.length; + + if (numSamples >= 2) { + columns.push({ + name: 'Samples', + render: (d: StructuralVariant[]) => + TumorColumnFormatter.renderFunction( + d.map(datum => ({ + sampleId: datum.sampleId, + entrezGeneId: datum.site1EntrezGeneId, + })), + this.props.store.sampleManager.result!, + this.props.store + .sampleToStructuralVariantGenePanelId.result!, + this.props.store.genePanelIdToEntrezGeneIds.result!, + this.props.onSelectGenePanel + ), + sortBy: (d: StructuralVariant[]) => + TumorColumnFormatter.getSortValue( + d, + this.props.store.sampleManager.result! + ), + download: (d: StructuralVariant[]) => + TumorColumnFormatter.getSample(d), + order: 20, + resizable: true, + }); + } + + columns.push({ + name: 'Gene 1', + render: (d: StructuralVariant[]) => ( + + {d[0].site1HugoSymbol} + + ), + filter: ( + d: StructuralVariant[], + filterString: string, + filterStringUpper: string + ) => { + return d[0].site1HugoSymbol.indexOf(filterStringUpper) > -1; + }, + download: (d: StructuralVariant[]) => d[0].site1HugoSymbol, + sortBy: (d: StructuralVariant[]) => d[0].site1HugoSymbol, + headerRender: (name: string) => { + return ( + + + + ); + }, + visible: true, + order: 30, + }); + + columns.push({ + name: 'Gene 2', + render: (d: StructuralVariant[]) => ( + + {d[0].site2HugoSymbol} + + ), + filter: ( + d: StructuralVariant[], + filterString: string, + filterStringUpper: string + ) => { + return ( + (d[0].site2HugoSymbol || '').indexOf( + filterStringUpper + ) > -1 + ); + }, + download: (d: StructuralVariant[]) => d[0].site2HugoSymbol, + sortBy: (d: StructuralVariant[]) => d[0].site2HugoSymbol, + headerRender: (name: string) => { + return ( + + + + ); + }, + visible: true, + order: 35, + }); + + const genePanelProps = (d: StructuralVariant[]) => ({ + data: d.map(datum => ({ + sampleId: datum.sampleId, + entrezGeneId: datum.site1EntrezGeneId, + })), + sampleToGenePanelId: this.props.store + .sampleToStructuralVariantGenePanelId.result!, + sampleManager: this.props.store.sampleManager.result!, + genePanelIdToGene: this.props.store.genePanelIdToEntrezGeneIds + .result!, + onSelectGenePanel: this.props.onSelectGenePanel, + }); + + columns.push({ + name: 'Gene panel', + render: (d: StructuralVariant[]) => + PanelColumnFormatter.renderFunction(genePanelProps(d)), + download: (d: StructuralVariant[]) => + PanelColumnFormatter.download(genePanelProps(d)), + sortBy: (d: StructuralVariant[]) => + PanelColumnFormatter.getGenePanelIds(genePanelProps(d)), + visible: false, + order: 40, + }); + + columns.push({ + name: 'Annotation', + render: (d: StructuralVariant[]) => + AnnotationColumnFormatter.renderFunction(d, { + uniqueSampleKeyToTumorType: this.props.store + .uniqueSampleKeyToTumorType, + oncoKbData: this.props.store + .structuralVariantOncoKbData, + oncoKbCancerGenes: this.props.store.oncoKbCancerGenes, + usingPublicOncoKbInstance: this.props.store + .usingPublicOncoKbInstance, + enableOncoKb: AppConfig.serverConfig + .show_oncokb as boolean, + pubMedCache: this.props.store.pubMedCache, + enableCivic: false, + enableMyCancerGenome: false, + enableHotspot: false, + userEmailAddress: ServerConfigHelpers.getUserEmailAddress(), + studyIdToStudy: this.props.store.studyIdToStudy.result, + }), + sortBy: (d: StructuralVariant[]) => { + return AnnotationColumnFormatter.sortValue( + d, + this.props.store.oncoKbCancerGenes, + this.props.store.usingPublicOncoKbInstance, + this.props.store.structuralVariantOncoKbData, + this.props.store.uniqueSampleKeyToTumorType + ); + }, + order: 45, + }); + + columns.push({ + name: 'Variant Class', + render: (d: StructuralVariant[]) => ( + {d[0].variantClass} + ), + filter: ( + d: StructuralVariant[], + filterString: string, + filterStringUpper: string + ) => { + return ( + d[0].variantClass + .toUpperCase() + .indexOf(filterStringUpper) > -1 + ); + }, + download: (d: StructuralVariant[]) => d[0].variantClass, + sortBy: (d: StructuralVariant[]) => d[0].variantClass, + visible: true, + order: 50, + }); + + columns.push({ + name: 'Site1 Chromosome', + render: (d: StructuralVariant[]) => ( + + {ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site1Chromosome })) + )} + + ), + download: (d: StructuralVariant[]) => + ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site1Chromosome })) + ) || '', + sortBy: (d: StructuralVariant[]) => + ChromosomeColumnFormatter.getSortValue( + d.map(datum => ({ chr: datum.site1Chromosome })) + ), + filter: ( + d: StructuralVariant[], + filterString: string, + filterStringUpper: string + ) => + ( + ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site1Chromosome })) + ) + '' + ) + .toUpperCase() + .includes(filterStringUpper), + visible: false, + order: 51, + }); + + columns.push({ + name: 'Site2 Chromosome', + render: (d: StructuralVariant[]) => ( + + {ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site2Chromosome })) + )} + + ), + download: (d: StructuralVariant[]) => + ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site2Chromosome })) + ) || '', + sortBy: (d: StructuralVariant[]) => + ChromosomeColumnFormatter.getSortValue( + d.map(datum => ({ chr: datum.site2Chromosome })) + ), + filter: ( + d: StructuralVariant[], + filterString: string, + filterStringUpper: string + ) => + ( + ChromosomeColumnFormatter.getData( + d.map(datum => ({ chr: datum.site2Chromosome })) + ) + '' + ) + .toUpperCase() + .includes(filterStringUpper), + visible: false, + order: 52, + }); + + columns.push({ + name: 'Site1 Position', + render: (d: StructuralVariant[]) => ( + {d[0].site1Position} + ), + download: (d: StructuralVariant[]) => `${d[0].site1Position}`, + sortBy: (d: StructuralVariant[]) => `${d[0].site1Position}`, + visible: false, + order: 55, + }); + + columns.push({ + name: 'Site2 Position', + render: (d: StructuralVariant[]) => ( + {d[0].site2Position} + ), + download: (d: StructuralVariant[]) => `${d[0].site2Position}`, + sortBy: (d: StructuralVariant[]) => `${d[0].site2Position}`, + visible: false, + order: 65, + }); + + columns.push({ + name: 'Event Info', + render: (d: StructuralVariant[]) => ( + {d[0].eventInfo} + ), + download: (d: StructuralVariant[]) => d[0].eventInfo, + sortBy: (d: StructuralVariant[]) => d[0].eventInfo, + visible: false, + order: 66, + }); + + columns.push({ + name: 'Connection Type', + render: (d: StructuralVariant[]) => ( + {d[0].connectionType} + ), + download: (d: StructuralVariant[]) => d[0].connectionType, + sortBy: (d: StructuralVariant[]) => d[0].connectionType, + visible: true, + order: 70, + }); + + columns.push({ + name: 'Breakpoint Type', + render: (d: StructuralVariant[]) => ( + {d[0].breakpointType} + ), + download: (d: StructuralVariant[]) => d[0].breakpointType, + sortBy: (d: StructuralVariant[]) => d[0].breakpointType, + visible: false, + order: 75, + }); + + columns.push({ + name: 'Additional Annotation', + render: (d: StructuralVariant[]) => ( + {d[0].annotation} + ), + download: (d: StructuralVariant[]) => d[0].annotation, + sortBy: (d: StructuralVariant[]) => d[0].annotation, + visible: false, + order: 80, + }); + + return _.sortBy(columns, (c: CNATableColumn) => c.order); + }, + default: [], + }); + + readonly tableUI = MakeMobxView({ + await: () => [ + this.props.store.structuralVariantProfile, + this.props.store.groupedStructuralVariantData, + this.columns, + ], + render: () => { + if ( + this.props.store.structuralVariantProfile.result === undefined + ) { + return ( +
+ Structural Variants are not available. +
+ ); + } + return ( + + ); + }, + renderPending: () => , + renderError: () => , + }); + + public render() { + return this.tableUI.component; + } +} diff --git a/src/pages/patientView/structuralVariant/column/AnnotationColumnFormatter.tsx b/src/pages/patientView/structuralVariant/column/AnnotationColumnFormatter.tsx new file mode 100644 index 00000000000..3baeeef531a --- /dev/null +++ b/src/pages/patientView/structuralVariant/column/AnnotationColumnFormatter.tsx @@ -0,0 +1,215 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import { + civicSortValue, + DEFAULT_ANNOTATION_DATA, + GenericAnnotation, + IAnnotation, + USE_DEFAULT_PUBLIC_INSTANCE_FOR_ONCOKB, + oncoKbAnnotationSortValue, +} from 'react-mutation-mapper'; +import { CancerStudy, StructuralVariant } from 'cbioportal-ts-api-client'; +import { IAnnotationColumnProps } from 'shared/components/mutationTable/column/AnnotationColumnFormatter'; +import { CancerGene, IndicatorQueryResp } from 'oncokb-ts-api-client'; +import { + RemoteData, + IOncoKbData, + generateQueryStructuralVariantId, + OncoKbCardDataType, +} from 'cbioportal-utils'; + +export default class AnnotationColumnFormatter { + public static getData( + structuralVariantData: StructuralVariant[] | undefined, + oncoKbCancerGenes?: RemoteData, + oncoKbData?: RemoteData, + usingPublicOncoKbInstance?: boolean, + uniqueSampleKeyToTumorType?: { [sampleId: string]: string }, + studyIdToStudy?: { [studyId: string]: CancerStudy } + ) { + let value: IAnnotation; + + if (structuralVariantData) { + let oncoKbIndicator: IndicatorQueryResp | undefined = undefined; + let oncoKbStatus: IAnnotation['oncoKbStatus'] = 'complete'; + + let oncoKbGeneExist = false; + let isOncoKbCancerGene = false; + let isSite1GeneOncoKbAnnotated = false; + let isSite1oncoKbCancerGene = false; + let isSite2GeneOncoKbAnnotated = false; + let isSite2oncoKbCancerGene = false; + if ( + oncoKbCancerGenes && + !(oncoKbCancerGenes instanceof Error) && + !(oncoKbCancerGenes.result instanceof Error) + ) { + const oncoKbCancerGeneSetByEntrezGeneId = _.keyBy( + oncoKbCancerGenes.result, + gene => gene.entrezGeneId + ); + isSite1oncoKbCancerGene = + oncoKbCancerGeneSetByEntrezGeneId[ + structuralVariantData[0].site1EntrezGeneId + ] !== undefined; + isSite2oncoKbCancerGene = + oncoKbCancerGeneSetByEntrezGeneId[ + structuralVariantData[0].site2EntrezGeneId + ] !== undefined; + isSite1GeneOncoKbAnnotated = + isSite1oncoKbCancerGene && + oncoKbCancerGeneSetByEntrezGeneId[ + structuralVariantData[0].site1EntrezGeneId + ].oncokbAnnotated; + isSite2GeneOncoKbAnnotated = + isSite2oncoKbCancerGene && + oncoKbCancerGeneSetByEntrezGeneId[ + structuralVariantData[0].site2EntrezGeneId + ].oncokbAnnotated; + oncoKbGeneExist = + isSite1GeneOncoKbAnnotated || isSite2GeneOncoKbAnnotated; + isOncoKbCancerGene = + isSite1oncoKbCancerGene || isSite2oncoKbCancerGene; + } + + // Always show oncogenicity icon even when the indicatorMapResult is empty. + // We want to show an icon for genes that haven't been annotated by OncoKB + let oncoKbAvailableDataTypes: OncoKbCardDataType[] = [ + OncoKbCardDataType.BIOLOGICAL, + ]; + + // oncoKbData may exist but it might be an instance of Error, in that case we flag the status as error + if (oncoKbData && oncoKbData.result instanceof Error) { + oncoKbStatus = 'error'; + } else if (oncoKbGeneExist) { + // actually, oncoKbData.result shouldn't be an instance of Error in this case (we already check it above), + // but we need to check it again in order to avoid TS errors/warnings + if ( + oncoKbData && + oncoKbData.result && + !(oncoKbData.result instanceof Error) && + oncoKbData.isComplete + ) { + oncoKbIndicator = this.getIndicatorData( + structuralVariantData, + oncoKbData.result, + uniqueSampleKeyToTumorType, + studyIdToStudy + ); + } + oncoKbStatus = oncoKbData ? oncoKbData.status : 'pending'; + } + + const site1HugoSymbol = structuralVariantData[0].site1HugoSymbol; + const site2HugoSymbol = structuralVariantData[0].site2HugoSymbol; + let hugoGeneSymbol = site1HugoSymbol; + if (oncoKbGeneExist) { + hugoGeneSymbol = isSite1GeneOncoKbAnnotated + ? site1HugoSymbol + : site2HugoSymbol; + } else if (isOncoKbCancerGene) { + hugoGeneSymbol = isSite1oncoKbCancerGene + ? site1HugoSymbol + : site2HugoSymbol; + } + + value = { + hugoGeneSymbol, + oncoKbStatus, + oncoKbIndicator, + oncoKbGeneExist, + isOncoKbCancerGene, + usingPublicOncoKbInstance: + usingPublicOncoKbInstance === undefined + ? USE_DEFAULT_PUBLIC_INSTANCE_FOR_ONCOKB + : usingPublicOncoKbInstance, + civicEntry: undefined, + civicStatus: 'complete', + hasCivicVariants: false, + myCancerGenomeLinks: [], + hotspotStatus: 'complete', + isHotspot: false, + is3dHotspot: false, + oncoKbAvailableDataTypes, + }; + } else { + value = DEFAULT_ANNOTATION_DATA; + } + + return value; + } + + public static getIndicatorData( + structuralVariantData: StructuralVariant[], + oncoKbData: IOncoKbData, + uniqueSampleKeyToTumorType?: { [sampleId: string]: string }, + studyIdToStudy?: { [studyId: string]: CancerStudy } + ): IndicatorQueryResp | undefined { + if ( + uniqueSampleKeyToTumorType === null || + oncoKbData.indicatorMap === null + ) { + return undefined; + } + + const id = generateQueryStructuralVariantId( + structuralVariantData[0].site1EntrezGeneId, + structuralVariantData[0].site2EntrezGeneId, + uniqueSampleKeyToTumorType![ + structuralVariantData[0].uniqueSampleKey + ] + ); + + if (oncoKbData.indicatorMap[id]) { + let indicator = oncoKbData.indicatorMap[id]; + if (indicator.query.tumorType === null && studyIdToStudy) { + const studyMetaData = + studyIdToStudy[structuralVariantData[0].studyId]; + if (studyMetaData.cancerTypeId !== 'mixed') { + indicator.query.tumorType = studyMetaData.cancerType.name; + } + } + return indicator; + } else { + return undefined; + } + } + + public static sortValue( + data: StructuralVariant[], + oncoKbCancerGenes?: RemoteData, + usingPublicOncoKbInstance?: boolean, + oncoKbData?: RemoteData, + uniqueSampleKeyToTumorType?: { [sampleId: string]: string } + ): number[] { + const annotationData: IAnnotation = this.getData( + data, + oncoKbCancerGenes, + oncoKbData, + usingPublicOncoKbInstance, + uniqueSampleKeyToTumorType + ); + + return _.flatten([ + oncoKbAnnotationSortValue(annotationData.oncoKbIndicator), + civicSortValue(annotationData.civicEntry), + annotationData.isOncoKbCancerGene ? 1 : 0, + ]); + } + + public static renderFunction( + data: StructuralVariant[], + columnProps: IAnnotationColumnProps + ) { + const annotation: IAnnotation = this.getData( + data, + columnProps.oncoKbCancerGenes, + columnProps.oncoKbData, + columnProps.usingPublicOncoKbInstance, + columnProps.uniqueSampleKeyToTumorType, + columnProps.studyIdToStudy + ); + + return ; + } +} diff --git a/src/pages/resultsView/ResultsViewPageStore.spec.ts b/src/pages/resultsView/ResultsViewPageStore.spec.ts index c8394d040ba..e9c437014cd 100644 --- a/src/pages/resultsView/ResultsViewPageStore.spec.ts +++ b/src/pages/resultsView/ResultsViewPageStore.spec.ts @@ -13,7 +13,11 @@ describe('buildDefaultOQLProfile', () => { ); assert.equal( buildDefaultOQLProfile(['MUTATION_EXTENDED'], 2, 2), - 'MUT FUSION' + 'MUT' + ); + assert.equal( + buildDefaultOQLProfile(['STRUCTURAL_VARIANT'], 2, 2), + 'FUSION' ); assert.equal( buildDefaultOQLProfile( @@ -21,6 +25,18 @@ describe('buildDefaultOQLProfile', () => { 2, 2 ), + 'MUT AMP HOMDEL' + ); + assert.equal( + buildDefaultOQLProfile( + [ + 'MUTATION_EXTENDED', + 'STRUCTURAL_VARIANT', + 'COPY_NUMBER_ALTERATION', + ], + 2, + 2 + ), 'MUT FUSION AMP HOMDEL' ); assert.equal( @@ -29,7 +45,7 @@ describe('buildDefaultOQLProfile', () => { 2, 2 ), - 'MUT FUSION EXP>=2 EXP<=-2' + 'MUT EXP>=2 EXP<=-2' ); assert.equal( buildDefaultOQLProfile( @@ -37,7 +53,7 @@ describe('buildDefaultOQLProfile', () => { 2, 2 ), - 'MUT FUSION PROT>=2 PROT<=-2' + 'MUT PROT>=2 PROT<=-2' ); }); }); diff --git a/src/pages/resultsView/ResultsViewPageStore.ts b/src/pages/resultsView/ResultsViewPageStore.ts index 042125945f1..72eb975a404 100644 --- a/src/pages/resultsView/ResultsViewPageStore.ts +++ b/src/pages/resultsView/ResultsViewPageStore.ts @@ -32,9 +32,15 @@ import { SampleIdentifier, SampleList, SampleMolecularIdentifier, + StructuralVariant, + StructuralVariantFilter, } from 'cbioportal-ts-api-client'; import client from 'shared/api/cbioportalClientInstance'; -import { remoteData, stringListToSet } from 'cbioportal-frontend-commons'; +import { + CanonicalMutationType, + remoteData, + stringListToSet, +} from 'cbioportal-frontend-commons'; import { action, computed, @@ -48,6 +54,8 @@ import { IHotspotIndex, indexHotspotsData, IOncoKbData, + generateQueryStructuralVariantId, + isLinearClusterHotspot, } from 'cbioportal-utils'; import { GenomeNexusAPI, @@ -86,12 +94,14 @@ import { groupBy, groupBySampleId, IDataQueryFilter, - isMutationProfile, makeGetOncoKbCnaAnnotationForOncoprint, makeGetOncoKbMutationAnnotationForOncoprint, makeIsHotspotForOncoprint, mapSampleIdToClinicalData, ONCOKB_DEFAULT, + fetchStructuralVariantOncoKbData, + cancerTypeForOncoKb, + getOncoKbOncogenic, } from 'shared/lib/StoreUtils'; import { CoverageInformation, @@ -105,6 +115,7 @@ import { toSampleUuid } from '../../shared/lib/UuidUtils'; import MutationDataCache from '../../shared/cache/MutationDataCache'; import AccessorsForOqlFilter, { SimplifiedMutationType, + getSimplifiedMutationType, } from '../../shared/lib/oql/AccessorsForOqlFilter'; import { doesQueryContainMutationOQL, @@ -149,6 +160,9 @@ import { isRNASeqProfile, OncoprintAnalysisCaseType, parseGenericAssayGroups, + filterAndAnnotateStructuralVariants, + compileStructuralVariants, + FilteredAndAnnotatedStructuralVariantsReport, } from './ResultsViewPageStoreUtils'; import MobxPromiseCache from '../../shared/lib/MobxPromiseCache'; import { isSampleProfiledInMultiple } from '../../shared/lib/isSampleProfiled'; @@ -273,6 +287,8 @@ export const DataTypeConstants = { LOGVALUE: 'LOG-VALUE', LOG2VALUE: 'LOG2-VALUE', LIMITVALUE: 'LIMIT-VALUE', + FUSION: 'FUSION', + SV: 'SV', }; export enum SampleListCategoryType { @@ -298,7 +314,10 @@ export type SamplesSpecificationElement = | { studyId: string; sampleId: string; sampleListId: undefined } | { studyId: string; sampleId: undefined; sampleListId: string }; -export interface ExtendedAlteration extends Mutation, NumericGeneMolecularData { +export interface ExtendedAlteration + extends Mutation, + NumericGeneMolecularData, + StructuralVariant { hugoGeneSymbol: string; molecularProfileAlterationType: MolecularProfile['molecularAlterationType']; // TODO: what is difference molecularProfileAlterationType and @@ -315,6 +334,14 @@ export interface AnnotatedMutation extends Mutation { simplifiedMutationType: SimplifiedMutationType; } +export interface AnnotatedStructuralVariant extends StructuralVariant { + putativeDriver: boolean; + oncoKbOncogenic: string; + isHotspot: boolean; + entrezGeneId: number; + hugoGeneSymbol: string; +} + export interface CustomDriverNumericGeneMolecularData extends NumericGeneMolecularData { driverFilter: string; @@ -333,6 +360,7 @@ export interface AnnotatedNumericGeneMolecularData export interface AnnotatedExtendedAlteration extends ExtendedAlteration, AnnotatedMutation, + AnnotatedStructuralVariant, AnnotatedNumericGeneMolecularData {} export interface ExtendedSample extends Sample { @@ -384,7 +412,6 @@ export function buildDefaultOQLProfile( switch (type) { case AlterationTypeConstants.MUTATION_EXTENDED: default_oql_uniq['MUT'] = true; - default_oql_uniq['FUSION'] = true; break; case AlterationTypeConstants.COPY_NUMBER_ALTERATION: default_oql_uniq['AMP'] = true; @@ -398,6 +425,9 @@ export function buildDefaultOQLProfile( default_oql_uniq['PROT>=' + rppaScoreThreshold] = true; default_oql_uniq['PROT<=-' + rppaScoreThreshold] = true; break; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + default_oql_uniq['FUSION'] = true; + break; } } return Object.keys(default_oql_uniq).join(' '); @@ -627,6 +657,10 @@ export class ResultsViewPageStore { @computed get selectedMolecularProfileIds() { + //use profileFilter when both profileFilter and MolecularProfileIds are present in query + if (isNaN(parseInt(this.urlWrapper.query.profileFilter, 10))) { + return []; + } return getMolecularProfiles(this.urlWrapper.query); } @@ -645,11 +679,7 @@ export class ResultsViewPageStore { @observable public urlValidationError: string | null = null; @computed get profileFilter() { - if (this.urlWrapper.query.profileFilter) { - return parseInt(this.urlWrapper.query.profileFilter, 10); - } else { - return 0; - } + return this.urlWrapper.query.profileFilter || '0'; } @observable ajaxErrors: Error[] = []; @@ -803,7 +833,11 @@ export class ResultsViewPageStore { } readonly selectedMolecularProfiles = remoteData({ - await: () => [this.studyToMolecularProfiles, this.studies], + await: () => [ + this.studyToMolecularProfiles, + this.studies, + this.molecularProfileIdToMolecularProfile, + ], invoke: () => { // if there are multiple studies or if there are no selected molecular profiles in query // derive default profiles based on profileFilter (refers to old data priority) @@ -824,6 +858,38 @@ export class ResultsViewPageStore { this.selectedMolecularProfileIds, (id: string) => id ); // optimization + + const hasMutationProfileInQuery = _.some( + this.selectedMolecularProfileIds, + molecularProfileId => { + const molecularProfile = this + .molecularProfileIdToMolecularProfile.result[ + molecularProfileId + ]; + return ( + molecularProfile !== undefined && + molecularProfile.molecularAlterationType === + AlterationTypeConstants.MUTATION_EXTENDED + ); + } + ); + + if (hasMutationProfileInQuery) { + const structuralVariantProfile = _.find( + this.molecularProfilesInStudies.result!, + molecularProfile => { + return ( + molecularProfile.molecularAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT + ); + } + ); + if (structuralVariantProfile) { + idLookupMap[ + structuralVariantProfile.molecularProfileId + ] = structuralVariantProfile.molecularProfileId; + } + } return Promise.resolve( this.molecularProfilesInStudies.result!.filter( (profile: MolecularProfile) => @@ -1588,7 +1654,9 @@ export class ResultsViewPageStore { profile.molecularAlterationType === AlterationTypeConstants.GENESET_SCORE || profile.molecularAlterationType === - AlterationTypeConstants.GENERIC_ASSAY + AlterationTypeConstants.GENERIC_ASSAY || + profile.molecularAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT ) { // geneset profile, we dont have the META projection for geneset data, so just add it /*promises.push(internalClient.fetchGeneticDataItemsUsingPOST({ @@ -1639,6 +1707,7 @@ export class ResultsViewPageStore { await: () => [ this.filteredAndAnnotatedMutations, this.filteredAndAnnotatedMolecularData, + this.filteredAndAnnotatedStructuralVariants, this.selectedMolecularProfiles, this.entrezGeneIdToGene, ], @@ -1647,14 +1716,11 @@ export class ResultsViewPageStore { this.selectedMolecularProfiles.result! ); const entrezGeneIdToGene = this.entrezGeneIdToGene.result!; - let result: ( - | AnnotatedMutation - | AnnotatedNumericGeneMolecularData - )[] = []; - result = result.concat(this.filteredAndAnnotatedMutations.result!); - result = result.concat( - this.filteredAndAnnotatedMolecularData.result! - ); + let result = [ + ...this.filteredAndAnnotatedMutations.result!, + ...this.filteredAndAnnotatedMolecularData.result!, + ...this.filteredAndAnnotatedStructuralVariants.result!, + ]; return Promise.resolve( result.map(d => { const extendedD: ExtendedAlteration = annotateAlterationTypes( @@ -1696,6 +1762,30 @@ export class ResultsViewPageStore { }, }); + readonly oqlFilteredStructuralVariantsReport = remoteData({ + await: () => [ + this._filteredAndAnnotatedStructuralVariantsReport, + this.selectedMolecularProfiles, + this.defaultOQLQuery, + ], + invoke: () => { + return Promise.resolve( + _.mapValues( + this._filteredAndAnnotatedStructuralVariantsReport.result!, + data => + filterCBioPortalWebServiceData( + this.oqlText, + data, + new AccessorsForOqlFilter( + this.selectedMolecularProfiles.result! + ), + this.defaultOQLQuery.result! + ) + ) + ); + }, + }); + readonly oqlFilteredMolecularDataReport = remoteData({ await: () => [ this._filteredAndAnnotatedMolecularDataReport, @@ -1724,6 +1814,7 @@ export class ResultsViewPageStore { await: () => [ this.filteredAndAnnotatedMutations, this.filteredAndAnnotatedMolecularData, + this.filteredAndAnnotatedStructuralVariants, this.selectedMolecularProfiles, this.defaultOQLQuery, ], @@ -1740,7 +1831,12 @@ export class ResultsViewPageStore { return Promise.resolve( filterCBioPortalWebServiceData( this.oqlText, - data, + [ + ...this.filteredAndAnnotatedMutations.result!, + ...this.filteredAndAnnotatedMolecularData.result!, + ...this.filteredAndAnnotatedStructuralVariants + .result!, + ], new AccessorsForOqlFilter( this.selectedMolecularProfiles.result! ), @@ -1803,6 +1899,7 @@ export class ResultsViewPageStore { await: () => [ this.filteredAndAnnotatedMutations, this.filteredAndAnnotatedMolecularData, + this.filteredAndAnnotatedStructuralVariants, this.selectedMolecularProfiles, this.defaultOQLQuery, this.samples, @@ -1812,6 +1909,7 @@ export class ResultsViewPageStore { const data = [ ...this.filteredAndAnnotatedMutations.result!, ...this.filteredAndAnnotatedMolecularData.result!, + ...this.filteredAndAnnotatedStructuralVariants.result!, ]; const accessorsInstance = new AccessorsForOqlFilter( this.selectedMolecularProfiles.result! @@ -1880,6 +1978,7 @@ export class ResultsViewPageStore { await: () => [ this.filteredAndAnnotatedMutations, this.filteredAndAnnotatedMolecularData, + this.filteredAndAnnotatedStructuralVariants, this.selectedMolecularProfiles, this.defaultOQLQuery, this.samples, @@ -1896,6 +1995,7 @@ export class ResultsViewPageStore { [ ...this.filteredAndAnnotatedMutations.result!, ...this.filteredAndAnnotatedMolecularData.result!, + ...this.filteredAndAnnotatedStructuralVariants.result!, ], new AccessorsForOqlFilter( this.selectedMolecularProfiles.result! @@ -2130,7 +2230,7 @@ export class ResultsViewPageStore { // first group them by gene symbol const groupedGenesMap = _.groupBy( this.oqlFilteredAlterations.result!, - alteration => alteration.gene.hugoGeneSymbol + alteration => alteration.hugoGeneSymbol ); // kind of ugly but this fixes a bug where sort order of genes not respected // yes we are relying on add order of js map. in theory not guaranteed, in practice guaranteed @@ -2386,6 +2486,19 @@ export class ResultsViewPageStore { default: [], }); + readonly structuralVariantProfiles = remoteData({ + await: () => [this.selectedMolecularProfiles], + invoke: async () => { + return this.selectedMolecularProfiles.result!.filter( + profile => + profile.molecularAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT + ); + }, + onError: error => {}, + default: [], + }); + readonly clinicalAttributes_customCharts = remoteData({ await: () => [this.sampleMap], invoke: async () => { @@ -2736,16 +2849,14 @@ export class ResultsViewPageStore { [studyId: string]: MolecularProfile; }>( { - await: () => [this.molecularProfilesInStudies], + await: () => [this.mutationProfiles], invoke: () => { - const ret: { [studyId: string]: MolecularProfile } = {}; - for (const profile of this.molecularProfilesInStudies.result) { - const studyId = profile.studyId; - if (!ret[studyId] && isMutationProfile(profile)) { - ret[studyId] = profile; - } - } - return Promise.resolve(ret); + return Promise.resolve( + _.keyBy( + this.mutationProfiles.result, + (profile: MolecularProfile) => profile.studyId + ) + ); }, }, {} @@ -2956,28 +3067,15 @@ export class ResultsViewPageStore { readonly mutations = remoteData({ await: () => [ this.genes, - this.selectedMolecularProfiles, this.samples, - this.studyIdToStudy, + this.studyToMutationMolecularProfile, ], invoke: async () => { - const mutationProfiles = _.filter( - this.selectedMolecularProfiles.result, - (profile: MolecularProfile) => - profile.molecularAlterationType === - AlterationTypeConstants.MUTATION_EXTENDED - ); - - if (mutationProfiles.length === 0) { + if (_.isEmpty(this.studyToMutationMolecularProfile.result)) { return []; } - - const studyIdToProfileMap: { - [studyId: string]: MolecularProfile; - } = _.keyBy( - mutationProfiles, - (profile: MolecularProfile) => profile.studyId - ); + const studyIdToProfileMap = this.studyToMutationMolecularProfile + .result; const filters = this.samples.result.reduce( (memo, sample: Sample) => { @@ -3011,6 +3109,67 @@ export class ResultsViewPageStore { }, }); + readonly studyToStructuralVariantMolecularProfile = remoteData<{ + [studyId: string]: MolecularProfile; + }>( + { + await: () => [this.structuralVariantProfiles], + invoke: () => { + return Promise.resolve( + _.keyBy( + this.structuralVariantProfiles.result, + (profile: MolecularProfile) => profile.studyId + ) + ); + }, + }, + {} + ); + + readonly structuralVariants = remoteData({ + await: () => [ + this.genes, + this.samples, + this.studyToStructuralVariantMolecularProfile, + ], + invoke: async () => { + if ( + _.isEmpty(this.studyToStructuralVariantMolecularProfile.result) + ) { + return []; + } + const studyIdToProfileMap = this + .studyToStructuralVariantMolecularProfile.result; + + const filters = this.samples.result.reduce( + (memo, sample: Sample) => { + if (sample.studyId in studyIdToProfileMap) { + memo.push({ + molecularProfileId: + studyIdToProfileMap[sample.studyId] + .molecularProfileId, + sampleId: sample.sampleId, + }); + } + return memo; + }, + [] as StructuralVariantFilter['sampleMolecularIdentifiers'] + ); + + const data = { + entrezGeneIds: _.map( + this.genes.result, + (gene: Gene) => gene.entrezGeneId + ), + sampleMolecularIdentifiers: filters, + } as StructuralVariantFilter; + + return await client.fetchStructuralVariantsUsingPOST({ + structuralVariantFilter: data, + }); + }, + }); + @computed get existsSomeMutationWithAscnProperty(): { [property: string]: boolean; } { @@ -3061,6 +3220,51 @@ export class ResultsViewPageStore { }, }); + readonly structuralVariantsReportByGene = remoteData<{ + [hugeGeneSymbol: string]: FilteredAndAnnotatedStructuralVariantsReport; + }>({ + await: () => [ + this._filteredAndAnnotatedStructuralVariantsReport, + this.genes, + ], + invoke: () => { + let structuralVariantsGroups = this + ._filteredAndAnnotatedStructuralVariantsReport.result!; + const ret: { + [hugoGeneSymbol: string]: FilteredAndAnnotatedStructuralVariantsReport; + } = {}; + for (const gene of this.genes.result!) { + ret[gene.hugoGeneSymbol] = { + data: [], + vus: [], + germline: [], + vusAndGermline: [], + }; + } + for (const structuralVariant of structuralVariantsGroups.data) { + ret[structuralVariant.site1HugoSymbol].data.push( + structuralVariant + ); + } + for (const structuralVariant of structuralVariantsGroups.vus) { + ret[structuralVariant.site1HugoSymbol].vus.push( + structuralVariant + ); + } + for (const structuralVariant of structuralVariantsGroups.germline) { + ret[structuralVariant.site1HugoSymbol].germline.push( + structuralVariant + ); + } + for (const structuralVariant of structuralVariantsGroups.vusAndGermline) { + ret[structuralVariant.site1HugoSymbol].vusAndGermline.push( + structuralVariant + ); + } + return Promise.resolve(ret); + }, + }); + readonly mutationsByGene = remoteData<{ [hugoGeneSymbol: string]: Mutation[]; }>({ @@ -3069,48 +3273,128 @@ export class ResultsViewPageStore { this.defaultOQLQuery, this.mutationsReportByGene, this.filteredSampleKeyToSample, + this.structuralVariantsReportByGene, ], invoke: () => { - return Promise.resolve( - _.mapValues( - this.mutationsReportByGene.result!, - (mutationGroups: FilteredAndAnnotatedMutationsReport) => { - if ( - this.mutationsTabFilteringSettings.useOql && - this.queryContainsMutationOql - ) { - // use oql filtering in mutations tab only if query contains mutation oql - mutationGroups = _.mapValues( - mutationGroups, - mutations => - filterCBioPortalWebServiceData( - this.oqlText, - mutations, - new AccessorsForOqlFilter( - this.selectedMolecularProfiles.result! - ), - this.defaultOQLQuery.result! - ) - ); - } - const filteredMutations = compileMutations( + const mutationsByGene = _.mapValues( + this.mutationsReportByGene.result!, + (mutationGroups: FilteredAndAnnotatedMutationsReport) => { + if ( + this.mutationsTabFilteringSettings.useOql && + this.queryContainsMutationOql + ) { + // use oql filtering in mutations tab only if query contains mutation oql + mutationGroups = _.mapValues( mutationGroups, - this.mutationsTabFilteringSettings.excludeVus, - this.mutationsTabFilteringSettings.excludeGermline + mutations => + filterCBioPortalWebServiceData( + this.oqlText, + mutations, + new AccessorsForOqlFilter( + this.selectedMolecularProfiles.result! + ), + this.defaultOQLQuery.result! + ) ); - if (this.hideUnprofiledSamples) { - // filter unprofiled samples - const sampleMap = this.filteredSampleKeyToSample - .result!; - return filteredMutations.filter( - m => m.uniqueSampleKey in sampleMap - ); - } else { - return filteredMutations; - } } - ) + const filteredMutations = compileMutations( + mutationGroups, + this.mutationsTabFilteringSettings.excludeVus, + this.mutationsTabFilteringSettings.excludeGermline + ); + if (this.hideUnprofiledSamples) { + // filter unprofiled samples + const sampleMap = this.filteredSampleKeyToSample + .result!; + return filteredMutations.filter( + m => m.uniqueSampleKey in sampleMap + ); + } else { + return filteredMutations; + } + } ); + + //TODO: remove once SV/Fusion tab is merged + _.forEach( + this.structuralVariantsReportByGene.result, + (structuralVariantsGroups, hugoGeneSymbol) => { + if (mutationsByGene[hugoGeneSymbol] === undefined) { + mutationsByGene[hugoGeneSymbol] = []; + } + + if ( + this.mutationsTabFilteringSettings.useOql && + this.queryContainsMutationOql + ) { + // use oql filtering in mutations tab only if query contains mutation oql + structuralVariantsGroups = _.mapValues( + structuralVariantsGroups, + structuralVariants => + filterCBioPortalWebServiceData( + this.oqlText, + structuralVariants, + new AccessorsForOqlFilter( + this.selectedMolecularProfiles.result! + ), + this.defaultOQLQuery.result! + ) + ); + } + let filteredStructuralVariants = compileStructuralVariants( + structuralVariantsGroups, + this.mutationsTabFilteringSettings.excludeVus, + this.mutationsTabFilteringSettings.excludeGermline + ); + if (this.hideUnprofiledSamples) { + // filter unprofiled samples + const sampleMap = this.filteredSampleKeyToSample + .result!; + filteredStructuralVariants = filteredStructuralVariants.filter( + m => m.uniqueSampleKey in sampleMap + ); + } + + filteredStructuralVariants.forEach(structuralVariant => { + const mutation = { + center: structuralVariant.center, + chr: structuralVariant.site1Chromosome, + entrezGeneId: structuralVariant.site1EntrezGeneId, + keyword: structuralVariant.comments, + molecularProfileId: + structuralVariant.molecularProfileId, + mutationType: CanonicalMutationType.FUSION, + ncbiBuild: structuralVariant.ncbiBuild, + patientId: structuralVariant.patientId, + proteinChange: structuralVariant.eventInfo, + sampleId: structuralVariant.sampleId, + startPosition: structuralVariant.site1Position, + studyId: structuralVariant.studyId, + uniquePatientKey: + structuralVariant.uniquePatientKey, + uniqueSampleKey: structuralVariant.uniqueSampleKey, + variantType: structuralVariant.variantClass, + gene: { + entrezGeneId: + structuralVariant.site1EntrezGeneId, + hugoGeneSymbol: + structuralVariant.site1HugoSymbol, + }, + hugoGeneSymbol: structuralVariant.site1HugoSymbol, + putativeDriver: structuralVariant.putativeDriver, + oncoKbOncogenic: structuralVariant.oncoKbOncogenic, + isHotspot: structuralVariant.isHotspot, + simplifiedMutationType: + CanonicalMutationType.FUSION, + } as AnnotatedMutation; + + mutationsByGene[hugoGeneSymbol].push(mutation); + }); + } + ); + //TODO: remove once SV/Fusion tab is merged + + return Promise.resolve(mutationsByGene); }, }); @@ -4289,6 +4573,21 @@ export class ResultsViewPageStore { }, }); + readonly _filteredAndAnnotatedStructuralVariantsReport = remoteData({ + await: () => [ + this.structuralVariants, + this.getStructuralVariantPutativeDriverInfo, + ], + invoke: () => { + return Promise.resolve( + filterAndAnnotateStructuralVariants( + this.structuralVariants.result!, + this.getStructuralVariantPutativeDriverInfo.result! + ) + ); + }, + }); + readonly filteredAndAnnotatedMutations = remoteData({ await: () => [ this._filteredAndAnnotatedMutationsReport, @@ -4310,6 +4609,20 @@ export class ResultsViewPageStore { }, }); + readonly filteredAndAnnotatedStructuralVariants = remoteData< + AnnotatedStructuralVariant[] + >({ + await: () => [this._filteredAndAnnotatedStructuralVariantsReport], + invoke: () => + Promise.resolve( + compileStructuralVariants( + this._filteredAndAnnotatedStructuralVariantsReport.result!, + this.driverAnnotationSettings.excludeVUS, + this.excludeGermlineMutations + ) + ), + }); + public annotatedMutationCache = new MobxPromiseCache< { entrezGeneId: number }, AnnotatedMutation[] @@ -4425,7 +4738,7 @@ export class ResultsViewPageStore { await: () => { const toAwait = []; if (this.driverAnnotationSettings.oncoKb) { - toAwait.push(this.getOncoKbMutationAnnotationForOncoprint); + toAwait.push(this.oncoKbMutationAnnotationForOncoprint); } if (this.driverAnnotationSettings.hotspots) { toAwait.push(this.isHotspotForOncoprint); @@ -4448,7 +4761,7 @@ export class ResultsViewPageStore { customDriverTier?: string; } => { const getOncoKbMutationAnnotationForOncoprint = this - .getOncoKbMutationAnnotationForOncoprint.result!; + .oncoKbMutationAnnotationForOncoprint.result!; const oncoKbDatum: | IndicatorQueryResp | undefined @@ -4490,6 +4803,58 @@ export class ResultsViewPageStore { }, }); + readonly getStructuralVariantPutativeDriverInfo = remoteData({ + await: () => { + const toAwait = []; + if (this.driverAnnotationSettings.oncoKb) { + toAwait.push( + this.oncoKbStructuralVariantAnnotationForOncoprint + ); + } + return toAwait; + }, + invoke: () => { + return Promise.resolve((structualVariant: StructuralVariant): { + oncoKb: string; + hotspots: boolean; + cbioportalCount: boolean; + cosmicCount: boolean; + customDriverBinary: boolean; + customDriverTier?: string; + } => { + const getOncoKbStructuralVariantAnnotationForOncoprint = this + .oncoKbStructuralVariantAnnotationForOncoprint.result!; + const oncoKbDatum: + | IndicatorQueryResp + | undefined + | null + | false = + this.driverAnnotationSettings.oncoKb && + getOncoKbStructuralVariantAnnotationForOncoprint && + !( + getOncoKbStructuralVariantAnnotationForOncoprint instanceof + Error + ) && + getOncoKbStructuralVariantAnnotationForOncoprint( + structualVariant + ); + + let oncoKb: string = ''; + if (oncoKbDatum) { + oncoKb = getOncoKbOncogenic(oncoKbDatum); + } + return { + oncoKb, + hotspots: false, + cbioportalCount: false, + cosmicCount: false, + customDriverBinary: false, + customDriverTier: undefined, + }; + }); + }, + }); + readonly getDiscreteCNAPutativeDriverInfo = remoteData({ await: () => { const toAwait = []; @@ -4620,6 +4985,35 @@ export class ResultsViewPageStore { ONCOKB_DEFAULT ); + readonly structuralVariantOncoKbDataForOncoprint = remoteData< + IOncoKbData | Error + >( + { + await: () => [this.structuralVariants, this.oncoKbAnnotatedGenes], + invoke: async () => { + if (AppConfig.serverConfig.show_oncokb) { + let result; + try { + result = await fetchStructuralVariantOncoKbData( + this.uniqueSampleKeyToTumorType.result || {}, + this.oncoKbAnnotatedGenes.result!, + this.structuralVariants + ); + } catch (e) { + result = new Error(); + } + return result; + } else { + return ONCOKB_DEFAULT; + } + }, + onError: (err: Error) => { + // fail silently, leave the error handling responsibility to the data consumer + }, + }, + ONCOKB_DEFAULT + ); + //we need seperate oncokb data because oncoprint requires onkb queries across cancertype //mutations tab the opposite readonly cnaOncoKbDataForOncoprint = remoteData( @@ -4641,9 +5035,9 @@ export class ResultsViewPageStore { @computed get didOncoKbFailInOncoprint() { // check in this order so that we don't trigger invoke return ( - this.getOncoKbMutationAnnotationForOncoprint.peekStatus === + this.oncoKbMutationAnnotationForOncoprint.peekStatus === 'complete' && - this.getOncoKbMutationAnnotationForOncoprint.result instanceof Error + this.oncoKbMutationAnnotationForOncoprint.result instanceof Error ); } @@ -4655,7 +5049,7 @@ export class ResultsViewPageStore { ); } - readonly getOncoKbMutationAnnotationForOncoprint = remoteData< + readonly oncoKbMutationAnnotationForOncoprint = remoteData< Error | ((mutation: Mutation) => IndicatorQueryResp | undefined) >({ await: () => [this.oncoKbDataForOncoprint], @@ -4665,6 +5059,41 @@ export class ResultsViewPageStore { ), }); + readonly oncoKbStructuralVariantAnnotationForOncoprint = remoteData< + | Error + | (( + structuralVariant: StructuralVariant + ) => IndicatorQueryResp | undefined) + >({ + await: () => [ + this.structuralVariantOncoKbDataForOncoprint, + this.uniqueSampleKeyToTumorType, + ], + invoke: () => { + const structuralVariantOncoKbDataForOncoprint = this + .structuralVariantOncoKbDataForOncoprint.result!; + if (structuralVariantOncoKbDataForOncoprint instanceof Error) { + return Promise.resolve(new Error()); + } else { + return Promise.resolve( + (structuralVariant: StructuralVariant) => { + const id = generateQueryStructuralVariantId( + structuralVariant.site1EntrezGeneId, + structuralVariant.site2EntrezGeneId, + cancerTypeForOncoKb( + structuralVariant.uniqueSampleKey, + this.uniqueSampleKeyToTumorType.result || {} + ) + ); + return structuralVariantOncoKbDataForOncoprint.indicatorMap![ + id + ]; + } + ); + } + }, + }); + readonly getOncoKbCnaAnnotationForOncoprint = remoteData< | Error | ((data: NumericGeneMolecularData) => IndicatorQueryResp | undefined) @@ -5056,6 +5485,46 @@ export class ResultsViewPageStore { }, })); + public structuralVariantCache = new MobxPromiseCache< + { entrezGeneId: number }, + StructuralVariant[] + >(q => ({ + await: () => [ + this.studyToStructuralVariantMolecularProfile, + this.studyToDataQueryFilter, + ], + invoke: async () => { + const studyIdToProfileMap = this + .studyToStructuralVariantMolecularProfile.result!; + + if (_.isEmpty(studyIdToProfileMap)) { + return Promise.resolve([]); + } + + const filters = this.samples.result.reduce( + (memo, sample: Sample) => { + if (sample.studyId in studyIdToProfileMap) { + memo.push({ + molecularProfileId: + studyIdToProfileMap[sample.studyId] + .molecularProfileId, + sampleId: sample.sampleId, + }); + } + return memo; + }, + [] as StructuralVariantFilter['sampleMolecularIdentifiers'] + ); + + return client.fetchStructuralVariantsUsingPOST({ + structuralVariantFilter: { + entrezGeneIds: [q.entrezGeneId], + sampleMolecularIdentifiers: filters, + } as StructuralVariantFilter, + }); + }, + })); + @action clearErrors() { this.ajaxErrors = []; } diff --git a/src/pages/resultsView/ResultsViewPageStoreUtils.ts b/src/pages/resultsView/ResultsViewPageStoreUtils.ts index 537965a160e..cabbb054594 100644 --- a/src/pages/resultsView/ResultsViewPageStoreUtils.ts +++ b/src/pages/resultsView/ResultsViewPageStoreUtils.ts @@ -9,6 +9,7 @@ import { PatientFilter, PatientIdentifier, ReferenceGenomeGene, + StructuralVariant, Sample, } from 'cbioportal-ts-api-client'; import { action, ObservableMap } from 'mobx'; @@ -34,6 +35,7 @@ import { IQueriedCaseData, IQueriedMergedTrackCaseData, ResultsViewPageStore, + AnnotatedStructuralVariant, } from './ResultsViewPageStore'; import { remoteData } from 'cbioportal-frontend-commons'; import { IndicatorQueryResp } from 'oncokb-ts-api-client'; @@ -150,6 +152,33 @@ export function annotateMutationPutativeDriver( ) as AnnotatedMutation; } +export function annotateStructuralVariantPutativeDriver( + structuralVariant: StructuralVariant, + putativeDriverInfo: { + oncoKb: string; + hotspots: boolean; + cbioportalCount: boolean; + cosmicCount: boolean; + customDriverBinary: boolean; + customDriverTier?: string; + } +): AnnotatedStructuralVariant { + const putativeDriver = !!( + putativeDriverInfo.oncoKb || + putativeDriverInfo.hotspots || + putativeDriverInfo.cbioportalCount || + putativeDriverInfo.cosmicCount || + putativeDriverInfo.customDriverBinary || + putativeDriverInfo.customDriverTier + ); + return { + putativeDriver, + isHotspot: putativeDriverInfo.hotspots, + oncoKbOncogenic: putativeDriverInfo.oncoKb, + ...structuralVariant, + } as AnnotatedStructuralVariant; +} + export function annotateMolecularDatum( molecularDatum: NumericGeneMolecularData, putativeDriverInfo: { @@ -195,6 +224,61 @@ export type FilteredAndAnnotatedDiscreteCNAReport< vus: T[]; }; +export type FilteredAndAnnotatedStructuralVariantsReport< + T extends AnnotatedStructuralVariant = AnnotatedStructuralVariant +> = { + data: T[]; + vus: T[]; + germline: T[]; + vusAndGermline: T[]; +}; + +export function filterAndAnnotateStructuralVariants( + structuralVariants: StructuralVariant[], + getPutativeDriverInfo: ( + structuralVariant: StructuralVariant + ) => { + oncoKb: string; + hotspots: boolean; + cbioportalCount: boolean; + cosmicCount: boolean; + customDriverBinary: boolean; + customDriverTier?: string; + } +): FilteredAndAnnotatedStructuralVariantsReport { + const vus: AnnotatedStructuralVariant[] = []; + const germline: AnnotatedStructuralVariant[] = []; + const vusAndGermline: AnnotatedStructuralVariant[] = []; + const filteredAnnotatedMutations = []; + for (const structuralVariant of structuralVariants) { + const annotatedStructuralVariant = annotateStructuralVariantPutativeDriver( + structuralVariant, + getPutativeDriverInfo(structuralVariant) + ); // annotate + annotatedStructuralVariant.entrezGeneId = + structuralVariant.site1EntrezGeneId; + annotatedStructuralVariant.hugoGeneSymbol = + structuralVariant.site1HugoSymbol; + const isGermline = false; + const isVus = !annotatedStructuralVariant.putativeDriver; + if (isGermline && isVus) { + vusAndGermline.push(annotatedStructuralVariant); + } else if (isGermline) { + germline.push(annotatedStructuralVariant); + } else if (isVus) { + vus.push(annotatedStructuralVariant); + } else { + filteredAnnotatedMutations.push(annotatedStructuralVariant); + } + } + return { + data: filteredAnnotatedMutations, + vus, + germline, + vusAndGermline, + }; +} + export function compileMutations< T extends AnnotatedMutation = AnnotatedMutation >( @@ -215,6 +299,26 @@ export function compileMutations< return mutations; } +export function compileStructuralVariants< + T extends AnnotatedStructuralVariant = AnnotatedStructuralVariant +>( + report: FilteredAndAnnotatedStructuralVariantsReport, + excludeVus: boolean, + excludeGermline: boolean +) { + let structuralVariants = report.data; + if (!excludeVus) { + structuralVariants = structuralVariants.concat(report.vus); + } + if (!excludeGermline) { + structuralVariants = structuralVariants.concat(report.germline); + } + if (!excludeVus && !excludeGermline) { + structuralVariants = structuralVariants.concat(report.vusAndGermline); + } + return structuralVariants; +} + export const ONCOKB_ONCOGENIC_LOWERCASE = [ 'likely oncogenic', 'predicted oncogenic', @@ -284,7 +388,11 @@ export function groupDataByCase( export function filterSubQueryData( queryStructure: UnflattenedOQLLineFilterOutput, defaultOQLQuery: string, - data: (AnnotatedMutation | NumericGeneMolecularData)[], + data: ( + | AnnotatedMutation + | NumericGeneMolecularData + | AnnotatedStructuralVariant + )[], accessorsInstance: AccessorsForOqlFilter, samples: { uniqueSampleKey: string }[], patients: { uniquePatientKey: string }[] @@ -366,7 +474,7 @@ export function buildResultsViewPageTitle( export function getMolecularProfiles(query: any) { //if there's only one study, we read profiles from query params and filter out undefined - let molecularProfiles = [ + let molecularProfiles: string[] = [ query.genetic_profile_ids_PROFILE_MUTATION_EXTENDED, query.genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION, query.genetic_profile_ids_PROFILE_MRNA_EXPRESSION, diff --git a/src/pages/resultsView/ResultsViewURLWrapper.ts b/src/pages/resultsView/ResultsViewURLWrapper.ts index 0a3dea7ac64..2acdb55fdc8 100644 --- a/src/pages/resultsView/ResultsViewURLWrapper.ts +++ b/src/pages/resultsView/ResultsViewURLWrapper.ts @@ -19,6 +19,7 @@ import { import IComparisonURLWrapper from 'pages/groupComparison/IComparisonURLWrapper'; import _ from 'lodash'; import { MapValues } from 'shared/lib/TypeScriptUtils'; +import { GroupComparisonTab } from 'pages/groupComparison/GroupComparisonTabs'; export type PlotsSelectionParam = { selectedGeneOption?: string; @@ -27,6 +28,7 @@ export type PlotsSelectionParam = { dataType?: string; selectedDataSourceOption?: string; mutationCountBy?: string; + structuralVariantCountBy?: string; logScale?: string; }; @@ -37,6 +39,7 @@ const PlotsSelectionParamProps: Required = { dataType: '', selectedDataSourceOption: '', mutationCountBy: '', + structuralVariantCountBy: '', logScale: '', }; @@ -45,6 +48,7 @@ export type PlotsColoringParam = { logScale?: string; colorByMutationType?: string; colorByCopyNumber?: string; + colorBySv?: string; }; const PlotsColoringParamProps: Required = { @@ -52,6 +56,7 @@ const PlotsColoringParamProps: Required = { logScale: '', colorByMutationType: '', colorByCopyNumber: '', + colorBySv: '', }; export enum ResultsViewURLQueryEnum { @@ -246,9 +251,7 @@ export default class ResultsViewURLWrapper } @computed public get comparisonSubTabId() { - return ( - this.query.comparison_subtab || ResultsViewComparisonSubTab.OVERLAP - ); + return this.query.comparison_subtab || GroupComparisonTab.OVERLAP; } @autobind @@ -257,7 +260,7 @@ export default class ResultsViewURLWrapper } @autobind - public setComparisonSubTabId(tabId: ResultsViewComparisonSubTab) { + public setComparisonSubTabId(tabId: GroupComparisonTab) { this.updateURL({ comparison_subtab: tabId }); } diff --git a/src/pages/resultsView/cancerSummary/CancerSummaryChart.tsx b/src/pages/resultsView/cancerSummary/CancerSummaryChart.tsx index 88ae75c24e5..559afe761a8 100644 --- a/src/pages/resultsView/cancerSummary/CancerSummaryChart.tsx +++ b/src/pages/resultsView/cancerSummary/CancerSummaryChart.tsx @@ -444,7 +444,10 @@ export class CancerSummaryChart extends React.Component< } private get leftPadding() { - return 50; + return Math.max( + 50, + Math.max(...this.scatterData.map(datum => datum.y.length)) * 4 + ); } private get bottomPadding() { diff --git a/src/pages/resultsView/cancerSummary/CancerSummaryContent.spec.tsx b/src/pages/resultsView/cancerSummary/CancerSummaryContent.spec.tsx index a6e50404e75..e5fed3ddafb 100644 --- a/src/pages/resultsView/cancerSummary/CancerSummaryContent.spec.tsx +++ b/src/pages/resultsView/cancerSummary/CancerSummaryContent.spec.tsx @@ -29,7 +29,7 @@ describe('CancerSummaryContent', () => { homdel: 0, hetloss: 0, gain: 0, - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -43,12 +43,14 @@ describe('CancerSummaryContent', () => { cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 0, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, 'Colorectal Adenocarcinoma': { @@ -60,7 +62,7 @@ describe('CancerSummaryContent', () => { homdel: 0, hetloss: 0, gain: 0, - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -74,12 +76,14 @@ describe('CancerSummaryContent', () => { cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 0, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, 'Rectal Adenocarcinoma': { @@ -91,7 +95,7 @@ describe('CancerSummaryContent', () => { homdel: 0, hetloss: 0, gain: 0, - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -105,12 +109,14 @@ describe('CancerSummaryContent', () => { cna: 1, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 0, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, }; diff --git a/src/pages/resultsView/cancerSummary/CancerSummaryContent.tsx b/src/pages/resultsView/cancerSummary/CancerSummaryContent.tsx index 9b1acdbba56..4fcdb928d3f 100644 --- a/src/pages/resultsView/cancerSummary/CancerSummaryContent.tsx +++ b/src/pages/resultsView/cancerSummary/CancerSummaryContent.tsx @@ -27,7 +27,7 @@ export const OrderedAlterationLabelMap: Record< homdel: 'Deep Deletion', gain: 'Gain', amp: 'Amplification', - fusion: 'Fusion', + structuralVariant: 'Structural Variant', mutated: 'Mutation', }; @@ -36,6 +36,7 @@ export const AlterationTypeToDataTypeLabel: { [id: string]: string } = { expression: 'mRNA data', cna: 'CNA data', mutation: 'Mutation data', + structuralVariant: 'Structural variant data', }; const alterationToColor: Record = { @@ -44,7 +45,7 @@ const alterationToColor: Record = { homdel: '#0000ff', //"#8fd8d8" "rgb(0,0,255)", hetloss: '#8fd8d8', gain: 'rgb(255,182,193)', - fusion: '#8B00C9', + structuralVariant: '#8B00C9', mrnaExpressionHigh: '#FF989A', mrnaExpressionLow: '#529AC8', protExpressionHigh: '#FF989A', @@ -58,7 +59,7 @@ export interface IAlterationCountMap { homdel: number; hetloss: number; gain: number; - fusion: number; + structuralVariant: number; mrnaExpressionHigh: number; mrnaExpressionLow: number; protExpressionHigh: number; @@ -77,12 +78,14 @@ export interface IAlterationData { cna: number; expression: number; protein: number; + structuralVariant: number; }; notProfiledSamplesCounts: { mutation: number; cna: number; expression: number; protein: number; + structuralVariant: number; }; } diff --git a/src/pages/resultsView/comparison/ComparisonTab.tsx b/src/pages/resultsView/comparison/ComparisonTab.tsx index f4fc4e3c374..e406bf6b668 100644 --- a/src/pages/resultsView/comparison/ComparisonTab.tsx +++ b/src/pages/resultsView/comparison/ComparisonTab.tsx @@ -129,7 +129,7 @@ export default class ComparisonTab extends React.Component< this.store._activeGroupsNotOverlapRemoved, this.store.activeGroups, this.store.mutationEnrichmentProfiles, - this.store.structuralVariantProfiles, + this.store.structuralVariantEnrichmentProfiles, this.store.copyNumberEnrichmentProfiles, this.store.mRNAEnrichmentProfiles, this.store.proteinEnrichmentProfiles, @@ -145,10 +145,7 @@ export default class ComparisonTab extends React.Component< onTabClick={this.props.urlWrapper.setComparisonSubTabId} className="secondaryNavigation comparisonTabSubTabs" > - + {this.store.showSurvivalTab && ( @@ -165,7 +162,7 @@ export default class ComparisonTab extends React.Component< )} )} @@ -210,7 +207,7 @@ export default class ComparisonTab extends React.Component< )} {this.store.showMRNATab && ( oqlData.fusion, + getAlterationData: (oqlData: IOqlData) => + oqlData.structuralVariant, isNotProfiled: (oqlData: IOqlData) => - oqlData.isFusionNotProfiled, + oqlData.isStructuralVariantNotProfiled, getValues: stringMapper, }; break; diff --git a/src/pages/resultsView/download/DownloadTab.tsx b/src/pages/resultsView/download/DownloadTab.tsx index a0a807cb5e9..5f1216c0586 100644 --- a/src/pages/resultsView/download/DownloadTab.tsx +++ b/src/pages/resultsView/download/DownloadTab.tsx @@ -41,12 +41,15 @@ import { generateMutationDownloadData, generateProteinData, hasValidData, + hasValidStructuralVariantData, hasValidMutationData, stringify2DArray, generateOtherMolecularProfileData, generateOtherMolecularProfileDownloadData, generateGenericAssayProfileData, generateGenericAssayProfileDownloadData, + generateStructuralVariantData, + generateStructuralDownloadData, makeIsSampleProfiledFunction, } from './DownloadUtils'; @@ -429,6 +432,42 @@ export default class DownloadTab extends React.Component< ), }); + readonly structuralVariantData = remoteData<{ + [key: string]: ExtendedAlteration[]; + }>({ + await: () => [this.props.store.nonOqlFilteredCaseAggregatedData], + invoke: () => + Promise.resolve( + generateStructuralVariantData( + this.props.store.nonOqlFilteredCaseAggregatedData.result! + ) + ), + }); + + readonly structuralVariantDownloadData = remoteData({ + await: () => [ + this.structuralVariantData, + this.props.store.samples, + this.props.store.genes, + this.props.store.coverageInformation, + this.props.store.studyToSelectedMolecularProfilesMap, + ], + invoke: () => + Promise.resolve( + generateStructuralDownloadData( + this.structuralVariantData.result!, + this.props.store.samples.result!, + this.props.store.genes.result!, + makeIsSampleProfiledFunction( + AlterationTypeConstants.STRUCTURAL_VARIANT, + this.props.store.studyToSelectedMolecularProfilesMap + .result!, + this.props.store.coverageInformation.result! + ) + ) + ), + }); + readonly alteredCaseAlterationData = remoteData({ await: () => [this.caseAlterationData], invoke: () => @@ -671,6 +710,7 @@ export default class DownloadTab extends React.Component< this.geneAlterationMap, this.cnaData, this.mutationData, + this.structuralVariantData, this.mrnaData, this.proteinData, this.unalteredCaseAlterationData, @@ -727,6 +767,10 @@ export default class DownloadTab extends React.Component< {hasValidMutationData( this.mutationData.result! ) && this.mutationDownloadControls()} + {hasValidStructuralVariantData( + this.structuralVariantData.result! + ) && + this.structuralVariantDownloadControls()} {hasValidData(this.mrnaData.result!) && this.mrnaExprDownloadControls( this.props.store.selectedMolecularProfiles.result!.find( @@ -845,6 +889,14 @@ export default class DownloadTab extends React.Component< ); } + private structuralVariantDownloadControls(): JSX.Element { + return this.downloadControlsRow( + 'Structural Variants (OQL is not in effect)', + this.handleStructuralVariantDownload, + this.handleTransposedStructuralVariantDownload + ); + } + private mrnaExprDownloadControls(profileName: string): JSX.Element { return this.downloadControlsRow( profileName, @@ -1216,6 +1268,22 @@ export default class DownloadTab extends React.Component< }); } + @autobind + private handleStructuralVariantDownload() { + onMobxPromise(this.structuralVariantDownloadData, data => { + const text = this.downloadDataText(data); + fileDownload(text, 'structural_variants.txt'); + }); + } + + @autobind + private handleTransposedStructuralVariantDownload() { + onMobxPromise(this.structuralVariantDownloadData, data => { + const text = this.downloadDataText(this.unzipDownloadData(data)); + fileDownload(text, 'structural_variants_transposed.txt'); + }); + } + private handleMrnaDownload(profileName: string) { onMobxPromise(this.mrnaDownloadData, data => { const text = this.downloadDataText(data); diff --git a/src/pages/resultsView/download/DownloadUtils.spec.ts b/src/pages/resultsView/download/DownloadUtils.spec.ts index 99cbb34f741..765bb51673b 100644 --- a/src/pages/resultsView/download/DownloadUtils.spec.ts +++ b/src/pages/resultsView/download/DownloadUtils.spec.ts @@ -14,8 +14,13 @@ import { generateOqlData, updateOqlData, decideMolecularProfileSortingOrder, + generateStructuralDownloadData, } from './DownloadUtils'; -import { AnnotatedMutation, ExtendedAlteration } from '../ResultsViewPageStore'; +import { + AnnotatedMutation, + AnnotatedStructuralVariant, + ExtendedAlteration, +} from '../ResultsViewPageStore'; import oql_parser, { SingleGeneQuery } from 'shared/lib/oql/oql-parser'; describe('DownloadUtils', () => { @@ -122,7 +127,7 @@ describe('DownloadUtils', () => { }, }; - const sampleDataWithBothMutationAndFusion = [ + const sampleDataWithMutation = [ { putativeDriver: true, isHotspot: true, @@ -175,52 +180,6 @@ describe('DownloadUtils', () => { alterationType: 'MUTATION_EXTENDED', alterationSubType: 'missense', }, - { - putativeDriver: true, - isHotspot: false, - oncoKbOncogenic: 'likely oncogenic', - simplifiedMutationType: 'fusion', - uniqueSampleKey: 'UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3', - uniquePatientKey: 'UC0wMDAwMzc4Om1za19pbXBhY3RfMjAxNw', - molecularProfileId: 'msk_impact_2017_mutations', - sampleId: 'P-0000378-T01-IM3', - patientId: 'P-0000378', - entrezGeneId: 1956, - gene: { - geneticEntityId: 1575, - entrezGeneId: 1956, - hugoGeneSymbol: 'EGFR', - type: 'protein-coding', - }, - studyId: 'msk_impact_2017', - center: 'MSKCC-DMP', - mutationStatus: 'NA', - validationStatus: 'NA', - tumorAltCount: -1, - tumorRefCount: -1, - normalAltCount: -1, - normalRefCount: -1, - startPosition: -1, - endPosition: -1, - referenceAllele: 'NA', - proteinChange: 'EGFR-intragenic', - mutationType: 'Fusion', - functionalImpactScore: 'NA', - fisValue: -1, - linkXvar: 'NA', - linkPdb: 'NA', - linkMsa: 'NA', - ncbiBuild: 'NA', - variantType: 'NA', - keyword: 'EGFR EGFR-intragenic', - variantAllele: 'NA', - refseqMrnaId: 'NA', - proteinPosStart: -1, - proteinPosEnd: -1, - molecularProfileAlterationType: 'MUTATION_EXTENDED', - alterationType: 'FUSION', - alterationSubType: 'fusion', - }, { putativeDriver: false, isHotspot: false, @@ -273,11 +232,37 @@ describe('DownloadUtils', () => { }, ] as (ExtendedAlteration & AnnotatedMutation)[]; + const sampleDataWithStructuralVariant = [ + { + uniqueSampleKey: 'UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3', + uniquePatientKey: 'UC0wMDAwMzc4Om1za19pbXBhY3RfMjAxNw', + molecularProfileId: 'msk_impact_2017_fusion', + sampleId: 'P-0000378-T01-IM3', + patientId: 'P-0000378', + studyId: 'msk_impact_2017', + site1EntrezGeneId: 1956, + site1HugoSymbol: 'EGFR', + site1Chromosome: 'NA', + site1Position: -1, + ncbiBuild: 'NA', + center: 'MSKCC-DMP', + eventInfo: 'EGFR-intragenic', + variantClass: 'INTRAGENIC', + comments: 'EGFR EGFR-intragenic', + molecularProfileAlterationType: 'STRUCTURAL_VARIANT', + alterationType: 'STRUCTURAL_VARIANT', + alterationSubType: '', + }, + ] as (ExtendedAlteration & AnnotatedStructuralVariant)[]; + const caseAggregatedDataByOQLLine = [ { cases: { samples: { - UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: sampleDataWithBothMutationAndFusion, + UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [ + ...sampleDataWithMutation, + ...sampleDataWithStructuralVariant, + ], VENHQS1FRS1BMjBDLTA2OnNrY21fdGNnYQ: [], }, }, @@ -287,7 +272,10 @@ describe('DownloadUtils', () => { parsed_oql_line: oql_parser.parse( 'EGFR: AMP HOMDEL MUT FUSION;' )![0], - data: sampleDataWithBothMutationAndFusion, + data: [ + ...sampleDataWithMutation, + ...sampleDataWithStructuralVariant, + ], }, }, { @@ -356,9 +344,9 @@ describe('DownloadUtils', () => { 'mutation data is empty for the sample with no alteration' ); assert.equal( - oqlData.fusion.length, + oqlData.structuralVariant.length, 0, - 'fusion data is empty for the sample with no alteration' + 'structural variant data is empty for the sample with no alteration' ); assert.equal( oqlData.mrnaExp.length, @@ -405,9 +393,9 @@ describe('DownloadUtils', () => { 'mutation data is empty for the sample with mrna and protein data only' ); assert.equal( - oqlData.fusion.length, + oqlData.structuralVariant.length, 0, - 'fusion data is empty for the sample with mrna and protein data only' + 'structural variant data is empty for the sample with mrna and protein data only' ); assert.equal( @@ -440,8 +428,11 @@ describe('DownloadUtils', () => { study_id: 'msk_impact_2017', uid: 'UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3', trackLabel: 'EGFR', - data: sampleDataWithBothMutationAndFusion, - disp_fusion: true, + data: [ + ...sampleDataWithMutation, + ...sampleDataWithStructuralVariant, + ], + disp_structuralVariant: true, disp_cna: 'amp', disp_mut: 'missense_rec', }; @@ -451,27 +442,27 @@ describe('DownloadUtils', () => { assert.equal( oqlData.geneSymbol, 'EGFR', - 'gene symbol is correct for the sample with both mutation and fusion data' + 'gene symbol is correct for the sample with both mutation and structural variant data' ); assert.deepEqual( oqlData.mrnaExp, [], - 'mRNA expression data is empty for the sample with mutation and fusion data' + 'mRNA expression data is empty for the sample with mutation and structural variant data' ); assert.deepEqual( oqlData.proteinLevel, [], - 'protein level data is empty for the sample with mutation and fusion data' + 'protein level data is empty for the sample with mutation and structural variant data' ); assert.deepEqual( oqlData.cna, [], - 'CNA data is empty for the sample with mutation and fusion data' + 'CNA data is empty for the sample with mutation and structural variant data' ); assert.deepEqual( - oqlData.fusion, + oqlData.structuralVariant, ['EGFR-intragenic'], - 'fusion data is correct for the sample with mutation and fusion data' + 'structural variant data is correct for the sample with mutation and structural variant data' ); assert.deepEqual( oqlData.mutation, @@ -583,7 +574,7 @@ describe('DownloadUtils', () => { it('generates download data for mutated samples', () => { const sampleAlterationDataByGene = { EGFR_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [ - ...sampleDataWithBothMutationAndFusion, + ...sampleDataWithMutation, ], PTEN_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [], TP53_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [], @@ -612,7 +603,7 @@ describe('DownloadUtils', () => { 'P-0000378-T01-IM3', 'WT', 'WT', - 'G598A [germline] EGFR-intragenic G239C', + 'G598A [germline] G239C', ], ['skcm_tcga', 'TCGA-EE-A20C-06', 'NS', 'WT', 'WT'], ]; @@ -625,6 +616,46 @@ describe('DownloadUtils', () => { }); }); + describe('generateStructuralVariantDownloadData', () => { + it('generates download data for structural variant samples', () => { + const sampleAlterationDataByGene = { + EGFR_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [ + ...sampleDataWithStructuralVariant, + ], + PTEN_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [], + TP53_UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3: [], + EGFR_VENHQS1FRS1BMjBDLTA2OnNrY21fdGNnYQ: [], + PTEN_VENHQS1FRS1BMjBDLTA2OnNrY21fdGNnYQ: [], + TP53_VENHQS1FRS1BMjBDLTA2OnNrY21fdGNnYQ: [], + }; + + const downloadData = generateStructuralDownloadData( + sampleAlterationDataByGene, + samples, + genes, + () => true + ); + + const expectedResult = [ + ['STUDY_ID', 'SAMPLE_ID', 'PTEN', 'TP53', 'EGFR'], + [ + 'msk_impact_2017', + 'P-0000378-T01-IM3', + 'NA', + 'NA', + 'EGFR-intragenic', + ], + ['skcm_tcga', 'TCGA-EE-A20C-06', 'NA', 'NA', 'NA'], + ]; + + assert.deepEqual( + downloadData, + expectedResult, + 'structural variant download data is correctly generated' + ); + }); + }); + describe('generateDownloadData', () => { it('generates download data for mRNA expression alterations', () => { const sampleAlterationDataByGene = { @@ -889,9 +920,9 @@ describe('DownloadUtils', () => { 'mutation data is correct for the sample key UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3' ); assert.deepEqual( - caseAlterationData[0].oqlData['EGFR'].fusion, + caseAlterationData[0].oqlData['EGFR'].structuralVariant, ['EGFR-intragenic'], - 'fusion data is correct for the sample key UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3' + 'structural variant data is correct for the sample key UC0wMDAwMzc4LVQwMS1JTTM6bXNrX2ltcGFjdF8yMDE3' ); assert.equal( @@ -927,12 +958,12 @@ describe('DownloadUtils', () => { sequenced: true, geneSymbol: 'EGFR', mutation: [], - fusion: [], + structuralVariant: [], cna: [], mrnaExp: [], proteinLevel: [], isMutationNotProfiled: false, - isFusionNotProfiled: false, + isStructuralVariantNotProfiled: false, isCnaNotProfiled: false, isMrnaExpNotProfiled: false, isProteinLevelNotProfiled: false, @@ -1147,9 +1178,9 @@ describe('DownloadUtils', () => { 'cna is profiled for the zero not profiled GeneticTrackDatum' ); assert.equal( - oqlData0.isFusionNotProfiled, + oqlData0.isStructuralVariantNotProfiled, false, - 'fusion is profiled for the zero not profiled GeneticTrackDatum' + 'structural variant is profiled for the zero not profiled GeneticTrackDatum' ); assert.equal( oqlData0.isMrnaExpNotProfiled, @@ -1179,9 +1210,9 @@ describe('DownloadUtils', () => { 'cna is profiled for the one not profiled GeneticTrackDatum' ); assert.equal( - oqlData1.isFusionNotProfiled, + oqlData1.isStructuralVariantNotProfiled, true, - 'fusion is not profiled for the one not profiled GeneticTrackDatum' + 'structural variant is not profiled for the one not profiled GeneticTrackDatum' ); assert.equal( oqlData1.isMrnaExpNotProfiled, @@ -1211,9 +1242,9 @@ describe('DownloadUtils', () => { 'cna is not profiled for the two not profiled GeneticTrackDatum' ); assert.equal( - oqlData2.isFusionNotProfiled, + oqlData2.isStructuralVariantNotProfiled, true, - 'fusion is not profiled for the two not profiled GeneticTrackDatum' + 'structural variant is not profiled for the two not profiled GeneticTrackDatum' ); assert.equal( oqlData2.isMrnaExpNotProfiled, @@ -1274,9 +1305,9 @@ describe('DownloadUtils', () => { it('should return maximum number for the other types', () => { assert.equal( - decideMolecularProfileSortingOrder('FUSION'), + decideMolecularProfileSortingOrder('STRUCTURAL_VARIANT'), Number.MAX_VALUE, - 'FUSION should be put to the end' + 'STRUCTURAL_VARIANT should be put to the end' ); }); }); diff --git a/src/pages/resultsView/download/DownloadUtils.ts b/src/pages/resultsView/download/DownloadUtils.ts index fb01978c860..a4fc0d0a588 100644 --- a/src/pages/resultsView/download/DownloadUtils.ts +++ b/src/pages/resultsView/download/DownloadUtils.ts @@ -58,7 +58,7 @@ export function generateOqlData( } ): IOqlData { const mutation: IOqlData['mutation'] = []; - const fusions: string[] = []; + const structuralVariant: string[] = []; const cnaAlterations: ISubAlteration[] = []; const proteinLevels: ISubAlteration[] = []; const mrnaExpressions: ISubAlteration[] = []; @@ -99,18 +99,16 @@ export function generateOqlData( } break; case AlterationTypeConstants.MUTATION_EXTENDED: - if (alteration.mutationType.toLowerCase().includes('fusion')) { - fusions.push(alteration.proteinChange); - alterationTypes.push('FUSION'); - } else { - mutation.push({ - proteinChange: alteration.proteinChange, - isGermline: !isNotGermlineMutation(alteration), - putativeDriver: alteration.putativeDriver, - }); - alterationTypes.push('MUT'); - } + mutation.push({ + proteinChange: alteration.proteinChange, + isGermline: !isNotGermlineMutation(alteration), + putativeDriver: alteration.putativeDriver, + }); + alterationTypes.push('MUT'); break; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + structuralVariant.push(alteration.eventInfo); + alterationTypes.push('FUSION'); } } @@ -126,12 +124,12 @@ export function generateOqlData( : true, geneSymbol: datum.trackLabel, mutation, - fusion: fusions, + structuralVariant, cna: cnaAlterations, mrnaExp: mrnaExpressions, proteinLevel: proteinLevels, isMutationNotProfiled: false, - isFusionNotProfiled: false, + isStructuralVariantNotProfiled: false, isCnaNotProfiled: false, isMrnaExpNotProfiled: false, isProteinLevelNotProfiled: false, @@ -147,7 +145,7 @@ export function updateOqlData( } ): IOqlData { let isMutationNotProfiled = true; - let isFusionNotProfiled = true; + let isStructuralVariantNotProfiled = true; let isCnaNotProfiled = true; let isMrnaExpNotProfiled = true; let isProteinLevelNotProfiled = true; @@ -172,15 +170,15 @@ export function updateOqlData( break; case AlterationTypeConstants.MUTATION_EXTENDED: isMutationNotProfiled = false; - case AlterationTypeConstants.FUSION: - isFusionNotProfiled = false; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + isStructuralVariantNotProfiled = false; break; } } } } oql.isMutationNotProfiled = isMutationNotProfiled; - oql.isFusionNotProfiled = isFusionNotProfiled; + oql.isStructuralVariantNotProfiled = isStructuralVariantNotProfiled; oql.isCnaNotProfiled = isCnaNotProfiled; oql.isMrnaExpNotProfiled = isMrnaExpNotProfiled; oql.isProteinLevelNotProfiled = isProteinLevelNotProfiled; @@ -266,6 +264,27 @@ export function generateMutationDownloadData( : []; } +export function generateStructuralDownloadData( + sampleAlterationDataByGene: { [key: string]: ExtendedAlteration[] }, + samples: Sample[] = [], + genes: Gene[] = [], + isSampleProfiledFunc: ( + uniqueSampleKey: string, + studyId: string, + hugoGeneSymbol: string + ) => boolean +): string[][] { + return sampleAlterationDataByGene + ? generateDownloadData( + sampleAlterationDataByGene, + samples, + genes, + isSampleProfiledFunc, + extractStructuralVariantValue + ) + : []; +} + export function generateMrnaData( unfilteredCaseAggregatedData?: CaseAggregatedData ): { [key: string]: ExtendedAlteration[] } { @@ -320,6 +339,24 @@ export function generateCnaData( : {}; } +export function generateStructuralVariantData( + unfilteredCaseAggregatedData?: CaseAggregatedData +): { [key: string]: ExtendedAlteration[] } { + const sampleFilter = (alteration: ExtendedAlteration) => { + return ( + alteration.molecularProfileAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT + ); + }; + + return unfilteredCaseAggregatedData + ? generateSampleAlterationDataByGene( + unfilteredCaseAggregatedData, + sampleFilter + ) + : {}; +} + export function generateOtherMolecularProfileData( molecularProfileId: string[], unfilteredCaseAggregatedData?: CaseAggregatedData @@ -446,7 +483,7 @@ export function generateSampleAlterationDataByGene( _.values(unfilteredCaseAggregatedData.samples).forEach(alterations => { alterations.forEach(alteration => { - const key = `${alteration.gene.hugoGeneSymbol}_${alteration.uniqueSampleKey}`; + const key = `${alteration.hugoGeneSymbol}_${alteration.uniqueSampleKey}`; sampleDataByGene[key] = sampleDataByGene[key] || []; // if no filter function provided nothing is filtered out, @@ -832,6 +869,19 @@ function extractMutationValue(alteration: ExtendedAlteration) { }`; } +export function hasValidStructuralVariantData(sampleAlterationDataByGene: { + [key: string]: ExtendedAlteration[]; +}): boolean { + return hasValidData( + sampleAlterationDataByGene, + extractStructuralVariantValue + ); +} + +function extractStructuralVariantValue(alteration: ExtendedAlteration) { + return alteration.eventInfo; +} + export function decideMolecularProfileSortingOrder( profileType: MolecularProfile['molecularAlterationType'] ) { diff --git a/src/pages/resultsView/enrichments/AlterationEnrichmentsContainer.tsx b/src/pages/resultsView/enrichments/AlterationEnrichmentsContainer.tsx index c8bc6027e83..bc6c5491689 100644 --- a/src/pages/resultsView/enrichments/AlterationEnrichmentsContainer.tsx +++ b/src/pages/resultsView/enrichments/AlterationEnrichmentsContainer.tsx @@ -455,18 +455,10 @@ export default class AlterationEnrichmentContainer extends React.Component< } @computed get isAnyFusionTypeSelected() { - if ( + return ( this.props.comparisonStore && - this.props.comparisonStore!.selectedMutationEnrichmentEventTypes && - MutationEnrichmentEventType.fusion in - this.props.comparisonStore!.selectedMutationEnrichmentEventTypes - ) { - return !!this.props.comparisonStore! - .selectedMutationEnrichmentEventTypes[ - MutationEnrichmentEventType.fusion - ]; - } - return false; + this.props.comparisonStore!.isStructuralVariantEnrichmentSelected + ); } @computed get isAnyCnaTypeSelected() { diff --git a/src/pages/resultsView/expression/ExpressionWrapper.tsx b/src/pages/resultsView/expression/ExpressionWrapper.tsx index bf53a5b51b6..d985c50b719 100644 --- a/src/pages/resultsView/expression/ExpressionWrapper.tsx +++ b/src/pages/resultsView/expression/ExpressionWrapper.tsx @@ -49,7 +49,11 @@ import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicato import BoxScatterPlot, { IBoxScatterPlotData, } from '../../../shared/components/plots/BoxScatterPlot'; -import { ColoringType, PlotType } from '../plots/PlotsTab'; +import { + ColoringType, + PlotType, + SelectedColoringTypes, +} from '../plots/PlotsTab'; import AlterationFilterWarning from '../../../shared/components/banners/AlterationFilterWarning'; import CaseFilterWarning from '../../../shared/components/banners/CaseFilterWarning'; import { getBoxWidth } from 'shared/lib/boxPlotUtils'; @@ -594,23 +598,23 @@ export default class ExpressionWrapper extends React.Component< @computed get scatterPlotAppearance() { return makeScatterPlotPointAppearance( - this.viewType, - this.mutationDataExists, - this.cnaDataExists, + this.coloringTypes, + this.mutationDataExists.result!, + this.cnaDataExists.result!, + false, this.props.store.driverAnnotationSettings.driversAnnotated ); } - @computed get viewType() { - if (this.showMutations && this.showCna) { - return ColoringType.MutationTypeAndCopyNumber; - } else if (this.showMutations) { - return ColoringType.MutationType; - } else if (this.showCna) { - return ColoringType.CopyNumber; - } else { - return ColoringType.None; + @computed get coloringTypes() { + const ret: SelectedColoringTypes = {}; + if (this.showMutations) { + ret[ColoringType.MutationType] = true; + } + if (this.showCna) { + ret[ColoringType.CopyNumber] = true; } + return ret; } private boxCalculationFilter(d: IBoxScatterPlotPoint) { @@ -633,7 +637,7 @@ export default class ExpressionWrapper extends React.Component< } @computed get zIndexSortBy() { - return scatterPlotZIndexSortBy(this.viewType); + return scatterPlotZIndexSortBy(this.coloringTypes); } @computed get axisLogScaleFunction(): IAxisLogScaleParams | undefined { @@ -652,7 +656,11 @@ export default class ExpressionWrapper extends React.Component< @autobind private getChart() { - if (this.boxPlotData.isComplete) { + if ( + this.boxPlotData.isComplete && + this.mutationDataExists.isComplete && + this.cnaDataExists.isComplete + ) { return ( d.data)), - this.viewType, + this.coloringTypes, PlotType.BoxPlot, - this.mutationDataExists, - this.cnaDataExists, this.props.store.driverAnnotationSettings .driversAnnotated, [] diff --git a/src/pages/resultsView/expression/expressionHelpers.tsx b/src/pages/resultsView/expression/expressionHelpers.tsx index e8ea0a000af..63144506ba9 100644 --- a/src/pages/resultsView/expression/expressionHelpers.tsx +++ b/src/pages/resultsView/expression/expressionHelpers.tsx @@ -19,7 +19,7 @@ import { getJitterForCase } from '../../../shared/components/plots/PlotUtils'; import * as React from 'react'; import { getSampleViewUrl, getStudySummaryUrl } from '../../../shared/api/urls'; import { - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_INFRAME, MUT_COLOR_MISSENSE, MUT_COLOR_PROMOTER, @@ -71,7 +71,7 @@ export const ExpressionStyleSheet: { fusion: { typeName: 'Fusion', symbol: VictoryShapeType.circle, - fill: MUT_COLOR_FUSION, + fill: STRUCTURAL_VARIANT_COLOR, stroke: '#000000', legendText: 'Fusion', }, @@ -229,12 +229,12 @@ export function expressionTooltip( let cna = null; if (d.mutations.length > 0) { - mutations = tooltipMutationsSection(d.mutations); + mutations = tooltipMutationsSection(d); } const nonDiploidCna = d.copyNumberAlterations.filter(x => x.value !== 0); if (nonDiploidCna.length > 0) { - cna = tooltipCnaSection(nonDiploidCna); + cna = tooltipCnaSection(d); } return ( diff --git a/src/pages/resultsView/plots/LastPlotsTabSelectionForDatatype.spec.ts b/src/pages/resultsView/plots/LastPlotsTabSelectionForDatatype.spec.ts index f9e63a4372c..097244f6342 100644 --- a/src/pages/resultsView/plots/LastPlotsTabSelectionForDatatype.spec.ts +++ b/src/pages/resultsView/plots/LastPlotsTabSelectionForDatatype.spec.ts @@ -1,6 +1,10 @@ import { assert } from 'chai'; import LastPlotsTabSelectionForDatatype from './LastPlotsTabSelectionForDatatype'; -import { AxisMenuSelection, MutationCountBy } from './PlotsTab'; +import { + AxisMenuSelection, + MutationCountBy, + StructuralVariantCountBy, +} from './PlotsTab'; import sinon from 'sinon'; // couldn't figure out how to do this with sinon @@ -17,6 +21,7 @@ function createFakeCallbacks() { // used in PlotsTabSelectionHistory const untestedSelectionFields = { mutationCountBy: MutationCountBy.MutationType, + structuralVariantCountBy: StructuralVariantCountBy.VariantType, logScale: false, } as AxisMenuSelection; diff --git a/src/pages/resultsView/plots/PlotsTab.tsx b/src/pages/resultsView/plots/PlotsTab.tsx index ef2d01cb7c2..5004bdde23d 100644 --- a/src/pages/resultsView/plots/PlotsTab.tsx +++ b/src/pages/resultsView/plots/PlotsTab.tsx @@ -27,9 +27,7 @@ import { GENESET_DATA_TYPE, getAxisLabel, getBoxPlotDownloadData, - getCnaQueries, getLimitValues, - getMutationQueries, getScatterPlotDownloadData, getWaterfallPlotDownloadData, IAxisLogScaleParams, @@ -63,6 +61,8 @@ import { getColoringMenuOptionValue, basicAppearance, getAxisDataOverlapSampleCount, + isAlterationTypePresent, + getCacheQueries, getCategoryOptions, maybeSetLogScale, logScalePossibleForProfile, @@ -135,28 +135,26 @@ enum EventKey { export enum ColoringType { ClinicalData, MutationType, - MutationTypeAndCopyNumber, CopyNumber, LimitVal, - LimitValMutationType, - LimitValCopyNumber, - LimitValMutationTypeAndCopyNumber, + StructuralVariant, None, } export enum PotentialColoringType { - MutationTypeAndCopyNumber, + GenomicData, None, - LimitValMutationTypeAndCopyNumber, + LimitValGenomicData, LimitVal, } +export type SelectedColoringTypes = Partial<{ [c in ColoringType]: any }>; + export enum PlotType { ScatterPlot, WaterfallPlot, BoxPlot, DiscreteVsDiscrete, - Table, } export enum DiscreteVsDiscretePlotType { @@ -172,6 +170,11 @@ export enum MutationCountBy { DriverVsVUS = 'DriverVsVUS', } +export enum StructuralVariantCountBy { + VariantType = 'VariantType', + MutatedVsWildType = 'MutatedVsWildType', +} + export type AxisMenuSelection = { entrezGeneId?: number; genesetId?: string; @@ -185,6 +188,7 @@ export type AxisMenuSelection = { dataType?: string; dataSourceId?: string; mutationCountBy: MutationCountBy; + structuralVariantCountBy: StructuralVariantCountBy; logScale: boolean; }; @@ -205,8 +209,9 @@ export type ColoringMenuOmnibarGroup = { export type ColoringMenuSelection = { selectedOption: ColoringMenuOmnibarOption | undefined; logScale?: boolean; - colorByMutationType: boolean; - colorByCopyNumber: boolean; + readonly colorByMutationType: boolean; + readonly colorByCopyNumber: boolean; + readonly colorByStructuralVariant: boolean; default: { entrezGeneId?: number; }; @@ -254,6 +259,13 @@ const mutationCountByOptions = [ { value: MutationCountBy.MutatedVsWildType, label: 'Mutated vs Wild-type' }, { value: MutationCountBy.DriverVsVUS, label: 'Driver vs VUS' }, ]; +const structuralVariantCountByOptions = [ + { value: StructuralVariantCountBy.VariantType, label: 'Variant Type' }, + { + value: StructuralVariantCountBy.MutatedVsWildType, + label: 'Variant vs No Variant', + }, +]; const discreteVsDiscretePlotTypeOptions = [ { value: DiscreteVsDiscretePlotType.Bar, label: 'Bar chart' }, @@ -314,83 +326,89 @@ export default class PlotsTab extends React.Component { @action.bound private onClickColorByCopyNumber() { if (this.plotType.result === PlotType.WaterfallPlot) { - // waterfall plot is a radio - cant select both mutation type and copy number - this.coloringMenuSelection.colorByMutationType = false; - this.coloringMenuSelection.colorByCopyNumber = true; + // waterfall plot has radio buttons + this.setColorByMutationType(false); + this.setColorByStructuralVariant(false); + this.setColorByCopyNumber(true); } else { - this.coloringMenuSelection.colorByCopyNumber = !this - .coloringMenuSelection.colorByCopyNumber; + this.setColorByCopyNumber( + !this.coloringMenuSelection.colorByCopyNumber + ); } } @action.bound private onClickColorByMutationType() { if (this.plotType.result === PlotType.WaterfallPlot) { - // waterfall plot is a radio - cant select both mutation type and copy number - this.coloringMenuSelection.colorByCopyNumber = false; - this.coloringMenuSelection.colorByMutationType = true; + // waterfall plot has radio buttons + this.setColorByCopyNumber(false); + this.setColorByStructuralVariant(false); + this.setColorByMutationType(true); } else { - this.coloringMenuSelection.colorByMutationType = !this - .coloringMenuSelection.colorByMutationType; + this.setColorByMutationType( + !this.coloringMenuSelection.colorByMutationType + ); + } + } + + @action.bound + private onClickColorByStructuralVariant() { + if (this.plotType.result === PlotType.WaterfallPlot) { + // waterfall plot has radio buttons + this.setColorByCopyNumber(false); + this.setColorByMutationType(false); + this.setColorByStructuralVariant(true); + } else { + this.setColorByStructuralVariant( + !this.coloringMenuSelection.colorByStructuralVariant + ); } } // determine whether formatting for points in the scatter plot (based on // mutations type, CNA, ...) will actually be shown in the plot (depends // on user choice via check boxes). - @computed get coloringType(): ColoringType { + @computed get coloringTypes(): SelectedColoringTypes { if ( this.coloringMenuSelection.selectedOption && this.coloringMenuSelection.selectedOption.info.clinicalAttribute ) { - return ColoringType.ClinicalData; + return { [ColoringType.ClinicalData]: true }; } - let ret: ColoringType = ColoringType.None; + let ret: SelectedColoringTypes = {}; const colorByMutationType = this.coloringMenuSelection .colorByMutationType; const colorByCopyNumber = this.coloringMenuSelection.colorByCopyNumber; - switch (this.potentialColoringType) { - case PotentialColoringType.MutationTypeAndCopyNumber: - if (colorByMutationType && colorByCopyNumber) { - ret = ColoringType.MutationTypeAndCopyNumber; - } else if (colorByMutationType) { - ret = ColoringType.MutationType; - } else if (colorByCopyNumber) { - ret = ColoringType.CopyNumber; - } else { - ret = ColoringType.None; - } - break; - case PotentialColoringType.LimitValMutationTypeAndCopyNumber: - if ( - colorByMutationType && - colorByCopyNumber && - this.viewLimitValues - ) { - ret = ColoringType.LimitValMutationTypeAndCopyNumber; - } else if (colorByMutationType && colorByCopyNumber) { - ret = ColoringType.MutationTypeAndCopyNumber; - } else if (colorByMutationType && this.viewLimitValues) { - ret = ColoringType.LimitValMutationType; - } else if (colorByCopyNumber && this.viewLimitValues) { - ret = ColoringType.LimitValCopyNumber; - } else if (colorByMutationType) { - ret = ColoringType.MutationType; - } else if (colorByCopyNumber) { - ret = ColoringType.CopyNumber; - } else if (this.viewLimitValues) { - ret = ColoringType.LimitVal; - } else { - ret = ColoringType.None; - } - break; - case PotentialColoringType.LimitVal: - if (this.viewLimitValues) { - ret = ColoringType.LimitVal; - } - break; + const colorByStructuralVariant = this.coloringMenuSelection + .colorByStructuralVariant; + + if ( + this.potentialColoringType === PotentialColoringType.GenomicData || + this.potentialColoringType === + PotentialColoringType.LimitValGenomicData + ) { + if (colorByMutationType && this.canColorByMutationData) { + ret[ColoringType.MutationType] = true; + } + if (colorByCopyNumber && this.canColorByCnaData) { + ret[ColoringType.CopyNumber] = true; + } + if (colorByStructuralVariant && this.canColorBySVData) { + ret[ColoringType.StructuralVariant] = true; + } } + + if ( + this.potentialColoringType === PotentialColoringType.LimitVal || + this.potentialColoringType === + PotentialColoringType.LimitValGenomicData + ) { + if (this.viewLimitValues) { + ret[ColoringType.LimitVal] = true; + } + } + return ret; } @@ -652,10 +670,14 @@ export default class PlotsTab extends React.Component { return PotentialColoringType.None; } - if (this.limitValuesCanBeShown) { - return PotentialColoringType.LimitValMutationTypeAndCopyNumber; + if (this.limitValuesCanBeShown && this.coloringByGene) { + return PotentialColoringType.LimitValGenomicData; + } else if (this.limitValuesCanBeShown) { + return PotentialColoringType.LimitVal; + } else if (this.coloringByGene) { + return PotentialColoringType.GenomicData; } else { - return PotentialColoringType.MutationTypeAndCopyNumber; + return PotentialColoringType.None; } } @@ -760,25 +782,32 @@ export default class PlotsTab extends React.Component { if (this._dataType === undefined && dataTypeOptions.length) { // return computed default if _dataType is undefined and if there are options to select a default value from if ( - vertical && - !!dataTypeOptions.find( - o => - o.value === - AlterationTypeConstants.MRNA_EXPRESSION + isAlterationTypePresent( + dataTypeOptions, + vertical, + AlterationTypeConstants.MRNA_EXPRESSION ) ) { // default for the vertical axis is mrna, if one is available return AlterationTypeConstants.MRNA_EXPRESSION; } else if ( - !vertical && - !!dataTypeOptions.find( - o => - o.value === - AlterationTypeConstants.COPY_NUMBER_ALTERATION + isAlterationTypePresent( + dataTypeOptions, + !vertical, + AlterationTypeConstants.COPY_NUMBER_ALTERATION ) ) { // default for the horizontal axis is CNA, if one is available return AlterationTypeConstants.COPY_NUMBER_ALTERATION; + } else if ( + isAlterationTypePresent( + dataTypeOptions, + !vertical, + AlterationTypeConstants.STRUCTURAL_VARIANT + ) + ) { + // default for the horizontal axis is Structural variant, if one is available + return AlterationTypeConstants.STRUCTURAL_VARIANT; } else { // otherwise, just return the first option return dataTypeOptions[0].value; @@ -855,6 +884,17 @@ export default class PlotsTab extends React.Component { set mutationCountBy(m: MutationCountBy) { this._mutationCountBy = m; }, + get structuralVariantCountBy() { + if (this._structuralVariantCountBy === undefined) { + // default + return StructuralVariantCountBy.VariantType; + } else { + return this._structuralVariantCountBy; + } + }, + set structuralVariantCountBy(s: StructuralVariantCountBy) { + this._structuralVariantCountBy = s; + }, get logScale() { return this._logScale && logScalePossible(this); }, @@ -1159,6 +1199,25 @@ export default class PlotsTab extends React.Component { }); }, + get _structuralVariantCountBy() { + const urlSelection = + (vertical + ? self.props.urlWrapper.query.plots_vert_selection + : self.props.urlWrapper.query.plots_horz_selection) || + {}; + return urlSelection.structuralVariantCountBy as StructuralVariantCountBy; + }, + set _structuralVariantCountBy(c: StructuralVariantCountBy) { + self.props.urlWrapper.updateURL(currentParams => { + if (vertical) { + currentParams.plots_vert_selection.structuralVariantCountBy = c; + } else { + currentParams.plots_horz_selection.structuralVariantCountBy = c; + } + return currentParams; + }); + }, + get _logScale() { const urlSelection = (vertical @@ -1271,46 +1330,76 @@ export default class PlotsTab extends React.Component { .colorByMutationType !== 'false' ); }, - set colorByMutationType(s: boolean) { - runInAction(() => { - self.props.urlWrapper.updateURL(currentQuery => { - currentQuery.plots_coloring_selection.colorByMutationType = s.toString(); - return currentQuery; - }); - // reset highlights - self.highlightedLegendItems.clear(); - }); - }, get colorByCopyNumber() { - // cant have both in waterfall plot + // radio buttons in waterfall plot if (self.plotType.result === PlotType.WaterfallPlot) { - return this._colorByCopyNumber && !this.colorByMutationType; + return ( + this.colorByCopyNumberFromUrl && + !this.colorByMutationType + ); } else { - return this._colorByCopyNumber; + return this.colorByCopyNumberFromUrl; } }, - get _colorByCopyNumber() { + get colorByCopyNumberFromUrl() { // default true return ( self.props.urlWrapper.query.plots_coloring_selection .colorByCopyNumber !== 'false' ); }, - set colorByCopyNumber(s: boolean) { - runInAction(() => { - self.props.urlWrapper.updateURL(currentQuery => { - currentQuery.plots_coloring_selection.colorByCopyNumber = s.toString(); - return currentQuery; - }); - // reset highlights - self.highlightedLegendItems.clear(); - }); + get colorByStructuralVariant() { + // radio buttons in waterfall plot + if (self.plotType.result === PlotType.WaterfallPlot) { + return ( + this.colorByStructuralVariantFromUrl && + !this.colorByCopyNumberFromUrl && + !this.colorByMutationType + ); + } else { + return this.colorByStructuralVariantFromUrl; + } + }, + get colorByStructuralVariantFromUrl() { + // default true + return ( + self.props.urlWrapper.query.plots_coloring_selection + .colorBySv !== 'false' + ); }, default: { entrezGeneId: undefined, }, }); } + + @action + setColorByMutationType(s: boolean) { + this.props.urlWrapper.updateURL(currentQuery => { + currentQuery.plots_coloring_selection.colorByMutationType = s.toString(); + return currentQuery; + }); + // reset highlights + this.highlightedLegendItems.clear(); + } + @action + setColorByCopyNumber(s: boolean) { + this.props.urlWrapper.updateURL(currentQuery => { + currentQuery.plots_coloring_selection.colorByCopyNumber = s.toString(); + return currentQuery; + }); + // reset highlights + this.highlightedLegendItems.clear(); + } + @action + setColorByStructuralVariant(s: boolean) { + this.props.urlWrapper.updateURL(currentQuery => { + currentQuery.plots_coloring_selection.colorBySv = s.toString(); + return currentQuery; + }); + // reset highlights + this.highlightedLegendItems.clear(); + } @action.bound private updateColoringMenuGene(entrezGeneId: number) { this.coloringMenuSelection.selectedOption = undefined; @@ -2325,8 +2414,8 @@ export default class PlotsTab extends React.Component { !this.coloringMenuSelection.colorByCopyNumber && !this.coloringMenuSelection.colorByMutationType ) { - this.coloringMenuSelection.colorByMutationType = true; - this.coloringMenuSelection.colorByCopyNumber = true; + this.setColorByMutationType(true); + this.setColorByCopyNumber(true); } } @@ -2344,6 +2433,20 @@ export default class PlotsTab extends React.Component { this.autoChooseColoringMenuGene(); } + @action.bound + public onVerticalAxisStructuralVariantCountBySelect(option: any) { + this.vertSelection.structuralVariantCountBy = option.value; + this.viewLimitValues = true; + this.autoChooseColoringMenuGene(); + } + + @action.bound + public onHorizontalAxisStructuralVariantCountBySelect(option: any) { + this.horzSelection.structuralVariantCountBy = option.value; + this.viewLimitValues = true; + this.autoChooseColoringMenuGene(); + } + @action.bound private onDiscreteVsDiscretePlotTypeSelect(option: any) { this.discreteVsDiscretePlotType = option.value; @@ -2435,10 +2538,18 @@ export default class PlotsTab extends React.Component { @computed get canColorByCnaData() { return !!( this.cnaDataExists.result && - (this.potentialColoringType === - PotentialColoringType.MutationTypeAndCopyNumber || + (this.potentialColoringType === PotentialColoringType.GenomicData || + this.potentialColoringType === + PotentialColoringType.LimitValGenomicData) + ); + } + + @computed get canColorBySVData() { + return !!( + this.svDataExists.result && + (this.potentialColoringType === PotentialColoringType.GenomicData || this.potentialColoringType === - PotentialColoringType.LimitValMutationTypeAndCopyNumber) + PotentialColoringType.LimitValGenomicData) ); } @@ -2500,24 +2611,13 @@ export default class PlotsTab extends React.Component { }`; } - @computed get isColoringByCnaData() { - return !!( - this.cnaDataExists.result && - (this.coloringType === ColoringType.CopyNumber || - this.coloringType === ColoringType.MutationTypeAndCopyNumber || - this.coloringType === ColoringType.LimitValCopyNumber || - this.coloringType === - ColoringType.LimitValMutationTypeAndCopyNumber) - ); - } - readonly cnaPromiseForColoring = remoteData({ await: () => this.props.store.annotatedCnaCache.getAll( - getCnaQueries(this.coloringMenuSelection) + getCacheQueries(this.coloringMenuSelection) ), invoke: () => { - const queries = getCnaQueries(this.coloringMenuSelection); + const queries = getCacheQueries(this.coloringMenuSelection); if (queries.length > 0) { return Promise.resolve( _.flatten( @@ -2540,24 +2640,32 @@ export default class PlotsTab extends React.Component { ); } - @computed get isColoringByMutationData() { - return !!( - this.mutationDataExists.result && - (this.coloringType === ColoringType.MutationType || - this.coloringType === ColoringType.MutationTypeAndCopyNumber) - ); - } - readonly mutationPromiseForColoring = remoteData({ await: () => this.props.store.annotatedMutationCache.getAll( - getMutationQueries(this.coloringMenuSelection) + getCacheQueries(this.coloringMenuSelection) ), invoke: () => { return Promise.resolve( _.flatten( this.props.store.annotatedMutationCache - .getAll(getMutationQueries(this.coloringMenuSelection)) + .getAll(getCacheQueries(this.coloringMenuSelection)) + .map(p => p.result!) + ).filter(x => !!x) + ); + }, + }); + + readonly structuralVariantPromise = remoteData({ + await: () => + this.props.store.structuralVariantCache.getAll( + getCacheQueries(this.coloringMenuSelection) + ), + invoke: () => { + return Promise.resolve( + _.flatten( + this.props.store.structuralVariantCache + .getAll(getCacheQueries(this.coloringMenuSelection)) .map(p => p.result!) ).filter(x => !!x) ); @@ -2628,6 +2736,7 @@ export default class PlotsTab extends React.Component { this.props.store.entrezGeneIdToGene, this.props.store.clinicalDataCache, this.props.store.annotatedMutationCache, + this.props.store.structuralVariantCache, this.props.store.numericGeneMolecularDataCache, this.props.store.coverageInformation, this.props.store.filteredSamples, @@ -2654,6 +2763,7 @@ export default class PlotsTab extends React.Component { this.props.store.entrezGeneIdToGene, this.props.store.clinicalDataCache, this.props.store.annotatedMutationCache, + this.props.store.structuralVariantCache, this.props.store.numericGeneMolecularDataCache, this.props.store.coverageInformation, this.props.store.filteredSamples, @@ -2695,8 +2805,9 @@ export default class PlotsTab extends React.Component { await: () => [this.props.store.studyToMutationMolecularProfile], invoke: () => { return Promise.resolve( - !!_.values(this.props.store.studyToMutationMolecularProfile) - .length + _.values( + this.props.store.studyToMutationMolecularProfile.result + ).length > 0 ); }, }); @@ -2705,8 +2816,23 @@ export default class PlotsTab extends React.Component { await: () => [this.props.store.studyToMolecularProfileDiscreteCna], invoke: () => { return Promise.resolve( - !!_.values(this.props.store.studyToMolecularProfileDiscreteCna) - .length + _.values( + this.props.store.studyToMolecularProfileDiscreteCna.result + ).length > 0 + ); + }, + }); + + readonly svDataExists = remoteData({ + await: () => [ + this.props.store.studyToStructuralVariantMolecularProfile, + ], + invoke: () => { + return Promise.resolve( + _.values( + this.props.store.studyToStructuralVariantMolecularProfile + .result + ).length > 0 ); }, }); @@ -2814,9 +2940,10 @@ export default class PlotsTab extends React.Component { @computed get scatterPlotAppearance() { return makeScatterPlotPointAppearance( - this.coloringType, - this.mutationDataExists, - this.cnaDataExists, + this.coloringTypes, + this.mutationDataExists.result!, + this.cnaDataExists.result!, + this.svDataExists.result!, this.props.store.driverAnnotationSettings.driversAnnotated, this.coloringMenuSelection.selectedOption, this.props.store.clinicalDataCache, @@ -2825,30 +2952,27 @@ export default class PlotsTab extends React.Component { } @computed get scatterPlotFill() { - switch (this.coloringType) { - case ColoringType.CopyNumber: - return '#000000'; - case ColoringType.ClinicalData: - case ColoringType.MutationTypeAndCopyNumber: - case ColoringType.MutationType: - case ColoringType.LimitVal: - case ColoringType.LimitValMutationType: - case ColoringType.LimitValMutationTypeAndCopyNumber: - return (d: IPlotSampleData) => - this.scatterPlotAppearance(d).fill!; - case ColoringType.None: - return basicAppearance.fill; + if ( + ColoringType.MutationType in this.coloringTypes || + ColoringType.ClinicalData in this.coloringTypes + ) { + return (d: IPlotSampleData) => this.scatterPlotAppearance(d).fill!; + } else if (ColoringType.CopyNumber in this.coloringTypes) { + return '#000'; + } else { + return basicAppearance.fill; } } @computed get scatterPlotFillOpacity() { if ( - this.coloringType === ColoringType.CopyNumber || - this.coloringType === ColoringType.LimitValCopyNumber + ColoringType.MutationType in this.coloringTypes || + ColoringType.ClinicalData in this.coloringTypes || + _.isEmpty(this.coloringTypes) ) { - return 0; - } else { return 1; + } else { + return 0; } } @@ -2859,10 +2983,8 @@ export default class PlotsTab extends React.Component { @computed get scatterPlotStrokeWidth() { if ( - this.coloringType === ColoringType.CopyNumber || - this.coloringType === ColoringType.MutationTypeAndCopyNumber || - this.coloringType === ColoringType.LimitValCopyNumber || - this.coloringType === ColoringType.LimitValMutationTypeAndCopyNumber + ColoringType.CopyNumber in this.coloringTypes || + ColoringType.StructuralVariant in this.coloringTypes ) { return CNA_STROKE_WIDTH; } else { @@ -2882,35 +3004,29 @@ export default class PlotsTab extends React.Component { @autobind private waterfallPlotColor(d: IPlotSampleData) { - // With the waterfall plot coloring for mutation type - // and copy number are mutually exclusive. Therefore, - // combined viewTypes (such as MutationTypeAndCopyNumber) - // do not exist for this plot type and are not evaluated. - switch (this.coloringType) { - case ColoringType.CopyNumber: - case ColoringType.LimitValCopyNumber: - return this.scatterPlotStroke(d); - case ColoringType.MutationType: - case ColoringType.LimitValMutationType: - case ColoringType.ClinicalData: - return this.scatterPlotAppearance(d).fill!; - case ColoringType.LimitVal: - case ColoringType.None: - default: - return basicAppearance.fill; + // With the waterfall plot genomic coloring is mutually exclusive. Therefore, + // combined viewTypes do not exist for this plot type and are not evaluated. + if ( + ColoringType.CopyNumber in this.coloringTypes || + ColoringType.StructuralVariant in this.coloringTypes + ) { + return this.scatterPlotStroke(d); + } else if ( + ColoringType.MutationType in this.coloringTypes || + ColoringType.ClinicalData in this.coloringTypes + ) { + return this.scatterPlotAppearance(d).fill!; + } else { + return basicAppearance.fill; } } @autobind private waterfallPlotLimitValueSymbolVisibility(d: IPlotSampleData) { - switch (this.coloringType) { - case ColoringType.LimitVal: - case ColoringType.LimitValMutationType: - case ColoringType.LimitValCopyNumber: - case ColoringType.LimitValMutationTypeAndCopyNumber: - return dataPointIsLimited(d); - default: - return false; + if (ColoringType.LimitVal in this.coloringTypes) { + return dataPointIsLimited(d); + } else { + return false; } } @@ -3117,6 +3233,14 @@ export default class PlotsTab extends React.Component { ? this.onVerticalAxisMutationCountBySelect : this.onHorizontalAxisMutationCountBySelect; break; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + dataSourceLabel = 'Group Structural variants by'; + dataSourceValue = axisSelection.structuralVariantCountBy; + dataSourceOptions = structuralVariantCountByOptions; + onDataSourceChange = vertical + ? this.onVerticalAxisStructuralVariantCountBySelect + : this.onHorizontalAxisStructuralVariantCountBySelect; + break; case undefined: break; default: @@ -3133,7 +3257,10 @@ export default class PlotsTab extends React.Component { let dataSourceDescription: string = ''; if ( dataSourceValue && - axisSelection.dataType !== AlterationTypeConstants.MUTATION_EXTENDED + axisSelection.dataType !== + AlterationTypeConstants.MUTATION_EXTENDED && + axisSelection.dataType !== + AlterationTypeConstants.STRUCTURAL_VARIANT ) { if (axisSelection.dataType === CLIN_ATTR_DATA_TYPE) { dataSourceDescription = this @@ -3945,10 +4072,15 @@ export default class PlotsTab extends React.Component { this.vertAxisDataPromise, this.props.store.sampleKeyToSample, this.props.store.coverageInformation, - this.mutationPromiseForColoring, + this.mutationDataExists, + this.cnaDataExists, + this.structuralVariantPromise, + this.svDataExists, this.props.store.studyToMutationMolecularProfile, + this.mutationPromiseForColoring, this.cnaPromiseForColoring, this.props.store.studyToMolecularProfileDiscreteCna, + this.props.store.studyToStructuralVariantMolecularProfile, ]; if ( this.coloringMenuSelection.selectedOption && @@ -3988,8 +4120,38 @@ export default class PlotsTab extends React.Component { this.props.store.sampleKeyToSample.result!, this.props.store.coverageInformation.result! .samples, - this.mutationDataForColoring, - this.cnaDataForColoring, + this.mutationDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMutationMolecularProfile + .result! + ).map(p => p.molecularProfileId), + data: this.mutationPromiseForColoring + .result!, + } + : undefined, + this.cnaDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMolecularProfileDiscreteCna + .result! + ).map(p => p.molecularProfileId), + data: this.cnaPromiseForColoring.result!, + } + : undefined, + this.svDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToStructuralVariantMolecularProfile + .result! + ).map(p => p.molecularProfileId), + data: this.structuralVariantPromise + .result!, + } + : undefined, this.selectedGeneForStyling, this.clinicalDataForColoring ) @@ -4008,8 +4170,12 @@ export default class PlotsTab extends React.Component { this.vertAxisDataPromise, this.props.store.sampleKeyToSample, this.props.store.coverageInformation, - this.mutationPromiseForColoring, + this.mutationDataExists, + this.cnaDataExists, + this.structuralVariantPromise, + this.svDataExists, this.props.store.studyToMutationMolecularProfile, + this.mutationPromiseForColoring, this.cnaPromiseForColoring, this.props.store.studyToMolecularProfileDiscreteCna, ]; @@ -4070,9 +4236,43 @@ export default class PlotsTab extends React.Component { this.props.store.coverageInformation.result! .samples, selectedGene, - this.mutationDataForColoring, - this.cnaDataForColoring, - this.clinicalDataForColoring + this.mutationDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMutationMolecularProfile + .result! + ).map(p => p.molecularProfileId), + data: this.mutationPromiseForColoring + .result!, + } + : undefined, + this.cnaDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMolecularProfileDiscreteCna + .result! + ).map(p => p.molecularProfileId), + data: this.cnaPromiseForColoring.result!, + } + : undefined, + this.svDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToStructuralVariantMolecularProfile + .result! + ).map(p => p.molecularProfileId), + data: this.structuralVariantPromise + .result!, + } + : undefined, + clinicalData && { + clinicalAttribute: this.coloringMenuSelection + .selectedOption!.info.clinicalAttribute!, + data: clinicalData, + } ), }); } else { @@ -4146,8 +4346,12 @@ export default class PlotsTab extends React.Component { this.vertAxisDataPromise, this.props.store.sampleKeyToSample, this.props.store.coverageInformation, - this.mutationPromiseForColoring, + this.mutationDataExists, + this.cnaDataExists, + this.structuralVariantPromise, + this.svDataExists, this.props.store.studyToMutationMolecularProfile, + this.mutationPromiseForColoring, this.cnaPromiseForColoring, this.props.store.studyToMolecularProfileDiscreteCna, ]; @@ -4197,15 +4401,59 @@ export default class PlotsTab extends React.Component { return Promise.resolve({ horizontal: false, data: [] }); } + let clinicalData; + if ( + this.coloringMenuSelection.selectedOption && + this.coloringMenuSelection.selectedOption.info + .clinicalAttribute + ) { + const promise = this.props.store.clinicalDataCache.get( + this.coloringMenuSelection.selectedOption.info + .clinicalAttribute + ); + clinicalData = promise.result!.data as ClinicalData[]; + } + let data = makeBoxScatterPlotData( categoryData, numberData, this.props.store.sampleKeyToSample.result!, this.props.store.coverageInformation.result!.samples, - this.mutationDataForColoring, - this.cnaDataForColoring, + this.mutationDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMutationMolecularProfile.result! + ).map(p => p.molecularProfileId), + data: this.mutationPromiseForColoring.result!, + } + : undefined, + this.cnaDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToMolecularProfileDiscreteCna + .result! + ).map(p => p.molecularProfileId), + data: this.cnaPromiseForColoring.result!, + } + : undefined, + this.svDataExists.result + ? { + molecularProfileIds: _.values( + this.props.store + .studyToStructuralVariantMolecularProfile + .result! + ).map(p => p.molecularProfileId), + data: this.structuralVariantPromise.result!, + } + : undefined, this.selectedGeneForStyling, - this.clinicalDataForColoring + clinicalData && { + clinicalAttribute: this.coloringMenuSelection + .selectedOption!.info.clinicalAttribute!, + data: clinicalData, + } ); if (selectedCategories && !_.isEmpty(selectedCategories)) { data = data.filter(d => d.label in selectedCategories); @@ -4241,7 +4489,7 @@ export default class PlotsTab extends React.Component { @computed get zIndexSortBy() { return scatterPlotZIndexSortBy( - this.coloringType, + this.coloringTypes, this.scatterPlotHighlight ); } @@ -4280,7 +4528,11 @@ export default class PlotsTab extends React.Component { } @computed get showUtilitiesMenu() { - return this.canColorByMutationData || this.canColorByCnaData; + return ( + this.plotType.isComplete && + this.plotType.result !== PlotType.DiscreteVsDiscrete && + (this.plotDataExistsForTwoAxes || this.waterfallPlotIsShown) + ); } @computed get noGeneSelectedForStyling(): boolean { @@ -4325,7 +4577,9 @@ export default class PlotsTab extends React.Component { return !!( this.coloringMenuSelection.selectedOption && this.coloringMenuSelection.selectedOption.info.entrezGeneId !== - undefined + undefined && + this.coloringMenuSelection.selectedOption.info.entrezGeneId !== + NONE_SELECTED_OPTION_NUMERICAL_VALUE ); } @@ -4499,10 +4753,8 @@ export default class PlotsTab extends React.Component { } legendData={scatterPlotLegendData( this.scatterPlotData.result, - this.coloringType, + this.coloringTypes, PlotType.ScatterPlot, - this.mutationDataExists, - this.cnaDataExists, this.props.store .driverAnnotationSettings .driversAnnotated, @@ -4571,10 +4823,8 @@ export default class PlotsTab extends React.Component { } legendData={scatterPlotLegendData( this.waterfallPlotData.result.data, - this.coloringType, + this.coloringTypes, PlotType.WaterfallPlot, - this.mutationDataExists, - this.cnaDataExists, this.props.store .driverAnnotationSettings .driversAnnotated, @@ -4645,10 +4895,8 @@ export default class PlotsTab extends React.Component { d => d.data ) ), - this.coloringType, + this.coloringTypes, PlotType.BoxPlot, - this.mutationDataExists, - this.cnaDataExists, this.props.store .driverAnnotationSettings .driversAnnotated, @@ -4804,6 +5052,31 @@ export default class PlotsTab extends React.Component { Mutation Type * )} + {this.coloringByGene && + this.canColorBySVData && ( + + Structural Variant{`\u00B9`} + + )} {this.coloringByGene && this.canColorByCnaData && ( { .colorByCopyNumber && !this .coloringMenuSelection - .colorByMutationType + .colorByMutationType && + !this + .coloringMenuSelection + .colorByStructuralVariant } onChange={action(() => { - this.coloringMenuSelection.colorByMutationType = false; - this.coloringMenuSelection.colorByCopyNumber = false; + this.setColorByMutationType( + false + ); + this.setColorByCopyNumber( + false + ); + this.setColorByStructuralVariant( + false + ); })} inputProps={{ type: 'radio', @@ -4933,6 +5216,13 @@ export default class PlotsTab extends React.Component { of the page. )} + {this.canColorBySVData && ( +
+ {`\u00B9 `}Structural variants are shown instead + of copy number alterations when a sample has + both. +
+ )} {this.limitValuesCanBeShown && this.plotType.result === PlotType.ScatterPlot && (
diff --git a/src/pages/resultsView/plots/PlotsTabUtils.spec.ts b/src/pages/resultsView/plots/PlotsTabUtils.spec.ts index 31ec8e48ebd..e6c9387c3b7 100644 --- a/src/pages/resultsView/plots/PlotsTabUtils.spec.ts +++ b/src/pages/resultsView/plots/PlotsTabUtils.spec.ts @@ -196,25 +196,25 @@ describe('PlotsTabUtils', () => { entrezGeneId: 1234, uniqueSampleKey: 'sample1', oncoKbOncogenic: 'MutationA', - mutationType: 'fusion', + mutationType: 'missense_mutation', }, { entrezGeneId: 1234, uniqueSampleKey: 'sample1', oncoKbOncogenic: 'MutationB', - mutationType: 'fusion', + mutationType: 'missense_mutation', }, { entrezGeneId: 5678, uniqueSampleKey: 'sample2', oncoKbOncogenic: 'MutationC', - mutationType: 'fusion', + mutationType: 'missense_mutation', }, { entrezGeneId: 5678, uniqueSampleKey: 'sample2', oncoKbOncogenic: 'MutationD', - mutationType: 'fusion', + mutationType: 'missense_mutation', }, ] as AnnotatedMutation[], }, @@ -344,7 +344,7 @@ describe('PlotsTabUtils', () => { i.copyNumberAlterations ); assert.equal(data.length, 1); - assert.equal(data[0].dispMutationType!, 'fusion'); + assert.equal(data[0].dispMutationType!, 'missense'); }); it("should return no mutation when user-selected gene has no mutations's", () => { @@ -524,7 +524,7 @@ describe('PlotsTabUtils', () => { { uniqueSampleKey: 'sample3', proteinChange: '', - mutationType: 'fusion', + mutationType: 'missense', putativeDriver: false, }, ]; @@ -707,7 +707,7 @@ describe('PlotsTabUtils', () => { }, { uniqueSampleKey: 'sample3', - value: [mutationTypeToDisplayName.fusion], + value: [mutationTypeToDisplayName.missense], }, { uniqueSampleKey: 'sample4', diff --git a/src/pages/resultsView/plots/PlotsTabUtils.tsx b/src/pages/resultsView/plots/PlotsTabUtils.tsx index 4faee198b22..7b30d518387 100644 --- a/src/pages/resultsView/plots/PlotsTabUtils.tsx +++ b/src/pages/resultsView/plots/PlotsTabUtils.tsx @@ -7,6 +7,9 @@ import { NONE_SELECTED_OPTION_NUMERICAL_VALUE, NONE_SELECTED_OPTION_STRING_VALUE, PlotType, + StructuralVariantCountBy, + PlotsTabOption, + SelectedColoringTypes, } from './PlotsTab'; import { MobxPromise } from 'mobxpromise'; import { @@ -19,6 +22,7 @@ import { Mutation, NumericGeneMolecularData, Sample, + StructuralVariant, } from 'cbioportal-ts-api-client'; import { capitalize, @@ -34,7 +38,7 @@ import { OncoprintMutationType, selectDisplayValue, } from '../../../shared/components/oncoprint/DataUtils'; -import { DEFAULT_GREY, LIGHT_GREY } from 'shared/lib/Colors'; +import { BLACK, DEFAULT_GREY, LIGHT_GREY } from 'shared/lib/Colors'; import { CoverageInformation } from '../../../shared/lib/GenePanelUtils'; import { IBoxScatterPlotData } from '../../../shared/components/plots/BoxScatterPlot'; import { @@ -62,11 +66,12 @@ import AppConfig from 'appConfig'; import { SpecialChartsUniqueKeyEnum } from 'pages/studyView/StudyViewUtils'; import { ObservableMap } from 'mobx'; import { toFixedWithoutTrailingZeros } from '../../../shared/lib/FormatUtils'; +import joinJsx from 'shared/lib/joinJsx'; import jStat from 'jStat'; import { CNA_COLOR_AMP, CNA_COLOR_HOMDEL, - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_INFRAME, MUT_COLOR_INFRAME_PASSENGER, MUT_COLOR_MISSENSE, @@ -83,6 +88,7 @@ export const CLIN_ATTR_DATA_TYPE = 'clinical_attribute'; export const GENESET_DATA_TYPE = 'GENESET_SCORE'; export const dataTypeToDisplayType: { [s: string]: string } = { [AlterationTypeConstants.MUTATION_EXTENDED]: 'Mutation', + [AlterationTypeConstants.STRUCTURAL_VARIANT]: 'Structural Variant', [AlterationTypeConstants.COPY_NUMBER_ALTERATION]: 'Copy Number', [AlterationTypeConstants.MRNA_EXPRESSION]: 'mRNA', [AlterationTypeConstants.PROTEIN_LEVEL]: 'Protein Level', @@ -101,7 +107,6 @@ export const mutationTypeToDisplayName: { } = { missense: 'Missense', inframe: 'Inframe', - fusion: 'Fusion', splice: 'Splice', promoter: 'Promoter', trunc: 'Truncating', @@ -111,6 +116,7 @@ export const mutationTypeToDisplayName: { export const dataTypeDisplayOrder = [ CLIN_ATTR_DATA_TYPE, AlterationTypeConstants.MUTATION_EXTENDED, + AlterationTypeConstants.STRUCTURAL_VARIANT, AlterationTypeConstants.COPY_NUMBER_ALTERATION, AlterationTypeConstants.MRNA_EXPRESSION, GENESET_DATA_TYPE, @@ -207,7 +213,22 @@ export interface INumberAxisData { } const NOT_PROFILED_MUTATION_LEGEND_LABEL = ['Not profiled', 'for mutations']; -const NOT_PROFILED_CNA_LEGEND_LABEL = ['Not profiled', 'for CNA']; +const NOT_PROFILED_CNA_SV_LEGEND_LABEL = ( + coloringTypes: SelectedColoringTypes +) => { + const cna = ColoringType.CopyNumber in coloringTypes; + const sv = ColoringType.StructuralVariant in coloringTypes; + let secondLine; + if (cna && sv) { + secondLine = 'for CNA and Structural Variants'; + } else if (cna) { + secondLine = 'for CNA'; + } else { + //sv + secondLine = 'for Structural Variants'; + } + return ['Not profiled', secondLine]; +}; const NO_DATA_CLINICAL_LEGEND_LABEL = 'No data'; const MUTATION_TYPE_NOT_PROFILED = 'not_profiled_mutation'; const MUTATION_TYPE_NOT_MUTATED = 'not_mutated'; @@ -223,10 +244,13 @@ export interface IPlotSampleData { dispCna?: AnnotatedNumericGeneMolecularData; dispMutationType?: OncoprintMutationType; dispClinicalValue?: string | number; - profiledCna?: boolean; - profiledMutations?: boolean; + dispStructuralVariant?: string; + isProfiledCna?: boolean; + isProfiledMutations?: boolean; + isProfiledStructuralVariants?: boolean; mutations: AnnotatedMutation[]; copyNumberAlterations: AnnotatedNumericGeneMolecularData[]; + structuralVariants: StructuralVariant[]; } export interface IThreshold1D { @@ -304,30 +328,57 @@ function doesPointHaveMutationData(d: IPlotSampleData) { function doesPointHaveCnaData(d: IPlotSampleData) { return !!d.dispCna; } +function doesPointHaveSvData(d: IPlotSampleData) { + return !!d.dispStructuralVariant; +} function isPointProfiledForMutations(d: IPlotSampleData) { - return !!d.profiledMutations; + return !!d.isProfiledMutations; } function isPointProfiledForCna(d: IPlotSampleData) { - return !!d.profiledCna; + return !!d.isProfiledCna; +} +function isPointProfiledForSv(d: IPlotSampleData) { + return !!d.isProfiledStructuralVariants; +} +function isPointNotProfiledForCnaAndSv( + d: IPlotSampleData, + coloringTypes: SelectedColoringTypes +) { + const isUnprofiledCna = !isPointProfiledForCna(d); + const isUnprofiledSv = !isPointProfiledForSv(d); + return ( + (ColoringType.CopyNumber in coloringTypes && + ColoringType.StructuralVariant in coloringTypes && + isUnprofiledCna && + isUnprofiledSv) || + (ColoringType.CopyNumber in coloringTypes && + !(ColoringType.StructuralVariant in coloringTypes) && + isUnprofiledCna) || + (ColoringType.StructuralVariant in coloringTypes && + !(ColoringType.CopyNumber in coloringTypes) && + isUnprofiledSv) + ); } export function scatterPlotZIndexSortBy< D extends Pick< IPlotSampleData, | 'dispMutationType' - | 'profiledMutations' + | 'isProfiledMutations' | 'dispCna' | 'dispClinicalValue' - | 'profiledCna' + | 'isProfiledCna' + | 'isProfiledStructuralVariants' + | 'dispStructuralVariant' > ->(viewType: ColoringType, highlight?: (d: D) => boolean) { +>(viewType: SelectedColoringTypes, highlight?: (d: D) => boolean) { // sort by render priority const sortByHighlight = highlight ? (d: D) => (highlight(d) ? 1 : 0) : (d: D) => 0; const sortByMutation = (d: D) => { - if (!d.profiledMutations) { + if (!d.isProfiledMutations) { return -mutationRenderPriority[MUTATION_TYPE_NOT_PROFILED]; } else if (!d.dispMutationType) { return -mutationRenderPriority[MUTATION_TYPE_NOT_MUTATED]; @@ -339,7 +390,7 @@ export function scatterPlotZIndexSortBy< }; const sortByCna = (d: D) => { - if (!d.profiledCna) { + if (!d.isProfiledCna) { return -cnaRenderPriority[CNA_TYPE_NOT_PROFILED]; } else if (!d.dispCna) { return -cnaRenderPriority[CNA_TYPE_NO_DATA]; @@ -350,39 +401,43 @@ export function scatterPlotZIndexSortBy< } }; - let sortBy; - switch (viewType) { - case ColoringType.ClinicalData: - sortBy = [ - sortByHighlight, - (d: D) => { - if (d.dispClinicalValue === undefined) { - return Number.NEGATIVE_INFINITY; - } else { - return 1; - } - }, - ]; - break; - case ColoringType.MutationTypeAndCopyNumber: - sortBy = [sortByHighlight, sortByMutation, sortByCna]; - break; - case ColoringType.MutationType: - sortBy = [sortByHighlight, sortByMutation]; - break; - case ColoringType.CopyNumber: - sortBy = [sortByHighlight, sortByCna]; - break; + const sortByStructuralVariant = (d: D) => { + if (!d.isProfiledStructuralVariants) { + return 0; + } else if (!d.dispStructuralVariant) { + return 1; + } else { + return 2; + } + }; + + let sortBy = [sortByHighlight]; + if (ColoringType.ClinicalData in viewType) { + sortBy.push((d: D) => { + if (d.dispClinicalValue === undefined) { + return Number.NEGATIVE_INFINITY; + } else { + return 1; + } + }); + } else { + if (ColoringType.MutationType in viewType) { + sortBy.push(sortByMutation); + } + if (ColoringType.StructuralVariant in viewType) { + sortBy.push(sortByStructuralVariant); + } + if (ColoringType.CopyNumber in viewType) { + sortBy.push(sortByCna); + } } return sortBy; } export function scatterPlotLegendData( data: IPlotSampleData[], - viewType: ColoringType, + viewType: SelectedColoringTypes, plotType: PlotType, - mutationDataExists: MobxPromise, - cnaDataExists: MobxPromise, driversAnnotated: boolean, limitValueTypes: string[], highlightedLegendItems?: ObservableMap, @@ -391,129 +446,61 @@ export function scatterPlotLegendData( coloringClinicalDataLogScale?: boolean, onClickLegendItem?: (ld: LegendDataWithId) => void ): LegendDataWithId[] { - const _mutationDataExists = - mutationDataExists.isComplete && mutationDataExists.result; - const _cnaDataExists = cnaDataExists.isComplete && cnaDataExists.result; let legend: any[] = []; - switch (viewType) { - case ColoringType.ClinicalData: - if ( - coloringClinicalDataCacheEntry && - coloringClinicalDataCacheEntry.categoryToColor - ) { - legend = scatterPlotStringClinicalLegendData( - coloringClinicalDataCacheEntry, - plotType, - onClickLegendItem - ); - } else if ( - coloringClinicalDataCacheEntry && - coloringClinicalDataCacheEntry.numericalValueToColor - ) { - legend = scatterPlotNumericalClinicalLegendData( - coloringClinicalDataCacheEntry, - plotType, - coloringClinicalDataLogScale - ); - } - break; - case ColoringType.CopyNumber: - if (_cnaDataExists) { - legend = scatterPlotCnaLegendData( - data, - plotType, - onClickLegendItem - ); - } - break; - case ColoringType.LimitValCopyNumber: - if (_cnaDataExists) { - legend = scatterPlotCnaLegendData( - data, - plotType, - onClickLegendItem - ).concat( - scatterPlotLimitValLegendData( - plotType, - limitValueTypes, - onClickLegendItem - ) - ); - } - break; - case ColoringType.LimitValMutationType: - if (_mutationDataExists) { - legend = scatterPlotMutationLegendData( - data, - driversAnnotated, - true, - plotType, - onClickLegendItem - ).concat( - scatterPlotLimitValLegendData( - plotType, - limitValueTypes, - onClickLegendItem - ) - ); - } - break; - case ColoringType.MutationType: - if (_mutationDataExists) { - legend = scatterPlotMutationLegendData( + if (ColoringType.ClinicalData in viewType) { + if ( + coloringClinicalDataCacheEntry && + coloringClinicalDataCacheEntry.categoryToColor + ) { + legend = scatterPlotStringClinicalLegendData( + coloringClinicalDataCacheEntry, + plotType, + onClickLegendItem + ); + } else if ( + coloringClinicalDataCacheEntry && + coloringClinicalDataCacheEntry.numericalValueToColor + ) { + legend = scatterPlotNumericalClinicalLegendData( + coloringClinicalDataCacheEntry, + plotType, + coloringClinicalDataLogScale + ); + } + } else { + if (ColoringType.MutationType in viewType) { + legend.push( + ...scatterPlotMutationLegendData( data, driversAnnotated, - true, + false, plotType, onClickLegendItem - ); - } - break; - case ColoringType.MutationTypeAndCopyNumber: - if (_mutationDataExists && _cnaDataExists) { - legend = scatterPlotMutationLegendData( + ) + ); + } + if ( + ColoringType.CopyNumber in viewType || + ColoringType.StructuralVariant in viewType + ) { + legend.push( + ...scatterPlotCnaAndSvLegendData( data, - driversAnnotated, - false, plotType, + viewType, onClickLegendItem - ).concat( - scatterPlotCnaLegendData(data, plotType, onClickLegendItem) - ); - } - break; - case ColoringType.LimitValMutationTypeAndCopyNumber: - if (_mutationDataExists && cnaDataExists) { - legend = scatterPlotMutationLegendData( - data, - driversAnnotated, - false, + ) + ); + } + if (ColoringType.LimitVal in viewType) { + legend.push( + ...scatterPlotLimitValLegendData( plotType, + limitValueTypes, onClickLegendItem ) - .concat( - scatterPlotCnaLegendData( - data, - plotType, - onClickLegendItem - ) - ) - .concat( - scatterPlotLimitValLegendData( - plotType, - limitValueTypes, - onClickLegendItem - ) - ); - } - break; - case ColoringType.LimitVal: - legend = scatterPlotLimitValLegendData( - plotType, - limitValueTypes, - onClickLegendItem ); - break; + } } const searchIndicatorLegendData = scatterPlotSearchIndicatorLegendData( data, @@ -837,9 +824,10 @@ function scatterPlotNumericalClinicalLegendData( return legendData; } -function scatterPlotCnaLegendData( +function scatterPlotCnaAndSvLegendData( data: IPlotSampleData[], plotType: PlotType, + coloringTypes: SelectedColoringTypes, onClick?: (ld: LegendDataWithId) => void ): LegendDataWithId[] { let showNotProfiledElement = false; @@ -849,60 +837,94 @@ function scatterPlotCnaLegendData( plotType === PlotType.WaterfallPlot ? 'square' : 'circle'; const fillOpacity = plotType === PlotType.WaterfallPlot ? 1 : 0; + let showSvElement = false; + const uniqueDispCna = _.chain(data) .map(d => { + if ( + ColoringType.StructuralVariant in coloringTypes && + doesPointHaveSvData(d) + ) { + showSvElement = true; + // skip the rest, because we prioritize showing structural variant + return null; + } + const ret = d.dispCna ? d.dispCna.value : null; - if (!isPointProfiledForCna(d)) { + + if (isPointNotProfiledForCnaAndSv(d, coloringTypes)) { showNotProfiledElement = true; } return ret; }) .uniq() .filter(x => { - if (x === null) { - showNotProfiledElement = true; - } return x !== null; }) .sortBy((v: number) => -v) // sorted descending .value(); - const legendData: LegendDataWithId[] = uniqueDispCna.map(v => { - const appearance = cnaToAppearance[v as -2 | -1 | 0 | 1 | 2]; - return { - name: appearance.legendLabel, + const legendData: LegendDataWithId[] = []; + if (ColoringType.CopyNumber in coloringTypes) { + legendData.push( + ...uniqueDispCna.map(v => { + const appearance = cnaToAppearance[v as -2 | -1 | 0 | 1 | 2]; + return { + name: appearance.legendLabel, + symbol: { + stroke: appearance.stroke, + fillOpacity: fillOpacity, + fill: appearance.stroke, // for waterfall plot + type: legendSymbol, + strokeWidth: CNA_STROKE_WIDTH, + }, + highlighting: onClick && { + uid: appearance.legendLabel, + isDatumHighlighted: (d: IPlotSampleData) => { + return !!(d.dispCna && d.dispCna.value === v); + }, + onClick, + }, + }; + }) + ); + } + + if (showSvElement) { + legendData.push({ + name: svAppearance.legendLabel, symbol: { - stroke: appearance.stroke, - fillOpacity: fillOpacity, - fill: appearance.stroke, // for waterfall plot + stroke: svAppearance.stroke, + fillOpacity, + fill: svAppearance.stroke, // for waterfall plot type: legendSymbol, strokeWidth: CNA_STROKE_WIDTH, }, highlighting: onClick && { - uid: appearance.legendLabel, + uid: svAppearance.legendLabel, isDatumHighlighted: (d: IPlotSampleData) => { - return !!(d.dispCna && d.dispCna.value === v); + return !!d.dispStructuralVariant; }, onClick, }, - }; - }); + }); + } + if (showNotProfiledElement) { legendData.push({ - name: NOT_PROFILED_CNA_LEGEND_LABEL, + name: NOT_PROFILED_CNA_SV_LEGEND_LABEL(coloringTypes), symbol: { - stroke: notProfiledCnaAppearance.stroke, + stroke: notProfiledCnaAndSvAppearance.stroke, fillOpacity: fillOpacity, - fill: notProfiledCnaAppearance.stroke, // for waterfall plot + fill: notProfiledCnaAndSvAppearance.stroke, // for waterfall plot type: legendSymbol, strokeWidth: CNA_STROKE_WIDTH, + strokeOpacity: notProfiledCnaAndSvAppearance.strokeOpacity, }, highlighting: onClick && { - uid: NO_DATA_CLINICAL_LEGEND_LABEL, + uid: NOT_PROFILED_CNA_SV_LEGEND_LABEL(coloringTypes).join('\n'), isDatumHighlighted: (d: IPlotSampleData) => { - return ( - !isPointProfiledForCna(d) || !doesPointHaveCnaData(d) - ); + return isPointNotProfiledForCnaAndSv(d, coloringTypes); }, onClick, }, @@ -970,12 +992,17 @@ function makeAxisDataPromise_Molecular( { entrezGeneId: number }, AnnotatedMutation[] >, + structuralVariantCache: MobxPromiseCache< + { entrezGeneId: number }, + StructuralVariant[] + >, numericGeneMolecularDataCache: MobxPromiseCache< { entrezGeneId: number; molecularProfileId: string }, NumericGeneMolecularData[] >, entrezGeneIdToGene: MobxPromise<{ [entrezGeneId: number]: Gene }>, mutationCountBy: MutationCountBy, + structuralVariantCountBy: StructuralVariantCountBy, coverageInformation: MobxPromise, samples: MobxPromise, molecularProfileIdSuffixToMolecularProfiles: MobxPromise<{ @@ -1003,6 +1030,15 @@ function makeAxisDataPromise_Molecular( ); ret.push(coverageInformation); ret.push(samples); + } else if ( + dataType === AlterationTypeConstants.STRUCTURAL_VARIANT + ) { + // structural variant profile + promises.push( + structuralVariantCache.get({ entrezGeneId }) + ); + ret.push(coverageInformation); + ret.push(samples); } else { // non-mutation profile profileIds.forEach(profileId => { @@ -1051,6 +1087,25 @@ function makeAxisDataPromise_Molecular( samples.result! ) ); + } else if ( + dataType === AlterationTypeConstants.STRUCTURAL_VARIANT + ) { + // structural variant profile + let structuralVariants: StructuralVariant[] = []; + structuralVariants.push( + ...structuralVariantCache.get({ entrezGeneId }).result! + ); + + return Promise.resolve( + makeAxisDataPromise_Molecular_MakeStructuralVariantData( + profileIds, + hugoGeneSymbol, + structuralVariants, + coverageInformation.result!, + structuralVariantCountBy, + samples.result! + ) + ); } else { // non-mutation profile const isDiscreteCna = _.every( @@ -1196,6 +1251,71 @@ export function makeAxisDataPromise_Molecular_MakeMutationData( categoryOrder, } as IStringAxisData; } +export function makeAxisDataPromise_Molecular_MakeStructuralVariantData( + molecularProfileIds: string[], + hugoGeneSymbol: string, + structuralVariants: StructuralVariant[], + coverageInformation: CoverageInformation, + structuralVariantCountBy: StructuralVariantCountBy, + samples: Pick[] +) { + // collect mutations by sample by type + const sampleToVariantClass = _.mapValues( + _.groupBy(structuralVariants, m => m.uniqueSampleKey), + sampleMuts => _.uniq(sampleMuts.map(m => m.variantClass)) + ); + + const data = samples.map(s => { + const variantClasses = sampleToVariantClass[s.uniqueSampleKey]; + let value: string | string[]; + if (!variantClasses) { + // variantClasses would never be an empty array because its generated by _.groupBy, + // so we need only check if it exists + if ( + !_.some( + isSampleProfiledInMultiple( + s.uniqueSampleKey, + molecularProfileIds, + coverageInformation, + hugoGeneSymbol + ), + isSampleProfiled => isSampleProfiled === true + ) + ) { + // its not profiled + value = STRUCTURAL_VARIANT_PROFILE_COUNT_NOT_PROFILED; + } else { + // otherwise, its profiled and no structural variants + value = STRUCTURAL_VARIANT_PROFILE_COUNT_NOT_MUTATED; + } + } else { + // we have mutations + switch (structuralVariantCountBy) { + case StructuralVariantCountBy.VariantType: + // if more than one type, its "Multiple" + if (variantClasses.length > 1) { + value = STRUCTURAL_VARIANT_PROFILE_COUNT_MULTIPLE; + } else { + value = variantClasses; + } + break; + case StructuralVariantCountBy.MutatedVsWildType: + default: + value = STRUCTURAL_VARIANT_PROFILE_COUNT_MUTATED; + break; + } + } + return { + uniqueSampleKey: s.uniqueSampleKey, + value, + }; + }); + return { + data, + hugoGeneSymbol, + datatype: 'string', + } as IStringAxisData; +} function makeAxisDataPromise_Geneset( genesetId: string, @@ -1322,6 +1442,10 @@ export function makeAxisDataPromise( { entrezGeneId: number }, AnnotatedMutation[] >, + structuralVariantCache: MobxPromiseCache< + { entrezGeneId: number }, + StructuralVariant[] + >, numericGeneMolecularDataCache: MobxPromiseCache< { entrezGeneId: number; molecularProfileId: string }, NumericGeneMolecularData[] @@ -1405,9 +1529,11 @@ export function makeAxisDataPromise( selection.dataSourceId, selection.dataType, annotatedMutationCache, + structuralVariantCache, numericGeneMolecularDataCache, entrezGeneIdToGene, selection.mutationCountBy, + selection.structuralVariantCountBy, coverageInformation, samples, molecularProfileIdSuffixToMolecularProfiles @@ -1574,7 +1700,7 @@ export const oncoprintMutationTypeToAppearanceDrivers: { }, fusion: { symbol: 'circle', - fill: MUT_COLOR_FUSION, + fill: STRUCTURAL_VARIANT_COLOR, stroke: '#000000', strokeOpacity: NON_CNA_STROKE_OPACITY, legendLabel: 'Fusion', @@ -1648,7 +1774,7 @@ export const oncoprintMutationTypeToAppearanceDefault: { }, fusion: { symbol: 'circle', - fill: MUT_COLOR_FUSION, + fill: STRUCTURAL_VARIANT_COLOR, stroke: '#000000', strokeOpacity: NON_CNA_STROKE_OPACITY, legendLabel: 'Fusion', @@ -1676,16 +1802,17 @@ export const oncoprintMutationTypeToAppearanceDefault: { }, }; -export const notProfiledCnaAppearance = { +export const notProfiledCnaAndSvAppearance = { symbol: 'circle', - stroke: '#000000', - strokeOpacity: 0.3, + stroke: BLACK, + strokeOpacity: 1, +}; +export const notProfiledMutationsAppearance = { + symbol: 'circle', + stroke: LIGHT_GREY, + strokeOpacity: 1, + fill: '#ffffff', }; -export const notProfiledMutationsAppearance = Object.assign( - {}, - { fill: '#ffffff' }, - notProfiledCnaAppearance -); export const noDataClinicalAppearance = Object.assign( {}, notProfiledMutationsAppearance, @@ -1759,6 +1886,12 @@ const cnaToAppearance = { }, }; +const svAppearance = { + legendLabel: `Structural Variant \u00B9`, + stroke: STRUCTURAL_VARIANT_COLOR, + strokeOpacity: 1, +}; + export const limitValueAppearance = { legendLabel: `Limit value`, symbol: 'diamond', @@ -1784,12 +1917,19 @@ export const MUT_PROFILE_COUNT_MUTATED = 'Mutated'; export const MUT_PROFILE_COUNT_MULTIPLE = 'Multiple'; export const MUT_PROFILE_COUNT_NOT_MUTATED = 'No mutation'; export const MUT_PROFILE_COUNT_NOT_PROFILED = 'Not profiled'; +export const STRUCTURAL_VARIANT_PROFILE_COUNT_MUTATED = + 'With Structural Variants'; +export const STRUCTURAL_VARIANT_PROFILE_COUNT_MULTIPLE = + 'Multiple structural variants'; +export const STRUCTURAL_VARIANT_PROFILE_COUNT_NOT_MUTATED = + 'No Structural Variants'; +export const STRUCTURAL_VARIANT_PROFILE_COUNT_NOT_PROFILED = + 'Not profiled for structural variants'; export const mutTypeCategoryOrder = [ mutationTypeToDisplayName.missense, mutationTypeToDisplayName.inframe, mutationTypeToDisplayName.trunc, mutationTypeToDisplayName.splice, - mutationTypeToDisplayName.fusion, mutationTypeToDisplayName.promoter, mutationTypeToDisplayName.other, MUT_PROFILE_COUNT_MULTIPLE, @@ -1837,11 +1977,40 @@ function getMutationTypeAppearance( return oncoprintMutationTypeToAppearance[d.dispMutationType!]; } } -function getCopyNumberAppearance(d: IPlotSampleData) { - if (!doesPointHaveCnaData(d) || !isPointProfiledForCna(d)) { - return notProfiledCnaAppearance; +function getCnaAndSvAppearance( + d: IPlotSampleData, + coloringTypes: SelectedColoringTypes +) { + const hasSvData = + ColoringType.StructuralVariant in coloringTypes && + doesPointHaveSvData(d); + const hasCnaData = + ColoringType.CopyNumber in coloringTypes && doesPointHaveCnaData(d); + + const isUnprofiledCna = + ColoringType.CopyNumber in coloringTypes + ? !isPointProfiledForCna(d) + : true; + const isUnprofiledSv = + ColoringType.StructuralVariant in coloringTypes + ? !isPointProfiledForSv(d) + : true; + + if (isUnprofiledCna && isUnprofiledSv) { + return notProfiledCnaAndSvAppearance; + } else if (hasSvData) { + // prioritize sv over cna + return svAppearance; + } else if (hasCnaData) { + const cnaValue = (d.dispCna ? d.dispCna.value : 0) as + | -2 + | -1 + | 0 + | 1 + | 2; + return cnaToAppearance[cnaValue]; } else { - return cnaToAppearance[d.dispCna!.value as -2 | -1 | 0 | 1 | 2]; + return cnaToAppearance['0']; } } @@ -1854,9 +2023,10 @@ function getLimitValueAppearance(d: IPlotSampleData) { } export function makeScatterPlotPointAppearance( - coloringType: ColoringType, - mutationDataExists: MobxPromise, - cnaDataExists: MobxPromise, + coloringType: SelectedColoringTypes, + mutationDataExists: boolean, + cnaDataExists: boolean, + svDataExists: boolean, driversAnnotated: boolean, coloringMenuSelectedOption?: ColoringMenuOmnibarOption | undefined, clinicalDataCache?: ClinicalDataCache, @@ -1874,158 +2044,112 @@ export function makeScatterPlotPointAppearance( ? oncoprintMutationTypeToAppearanceDrivers : oncoprintMutationTypeToAppearanceDefault; - switch (coloringType) { - case ColoringType.ClinicalData: - if (!coloringMenuSelectedOption || !clinicalDataCache) { - break; - } + let ret = null; - const data = clinicalDataCache.get( - coloringMenuSelectedOption.info.clinicalAttribute! + if (ColoringType.ClinicalData in coloringType) { + // clinical data precludes all others + ret = makeScatterPlotPointAppearance_Clinical( + coloringMenuSelectedOption, + clinicalDataCache, + coloringLogScale + ); + } else { + let getMutApp: any = () => ({}); + let getCnaApp: any = () => ({}); + let getLimitValueApp: any = () => ({}); + if (ColoringType.MutationType in coloringType && mutationDataExists) { + getMutApp = (d: IPlotSampleData) => + getMutationTypeAppearance(d, oncoprintMutationTypeToAppearance); + } + if ( + (cnaDataExists && ColoringType.CopyNumber in coloringType) || + (svDataExists && ColoringType.StructuralVariant in coloringType) + ) { + getCnaApp = getCnaAndSvAppearance; + } + if (ColoringType.LimitVal in coloringType) { + getLimitValueApp = getLimitValueAppearance; + } + return (d: IPlotSampleData) => { + return Object.assign( + {}, + basicAppearance, + getMutApp(d), + getCnaApp(d, coloringType), + getLimitValueApp(d) ); - if (data.isComplete) { - if ( - coloringMenuSelectedOption.info.clinicalAttribute! - .datatype === 'STRING' - ) { - const categoryToColor = data.result!.categoryToColor!; - return (d: IPlotSampleData) => { - if (doesPointHaveClinicalData(d)) { - return { - stroke: '#000000', - strokeOpacity: NON_CNA_STROKE_OPACITY, - fill: - categoryToColor[ - d.dispClinicalValue! as string - ], - fillOpacity: 1, - symbol: 'circle', - }; - } else { - return noDataClinicalAppearance; - } - }; - } else if ( - coloringMenuSelectedOption.info.clinicalAttribute! - .datatype === 'NUMBER' - ) { - let numericalValueToColor: (x: number) => string; - if ( - coloringLogScale && - data.result!.logScaleNumericalValueToColor - ) { - // if log scale coloring is selected and its available, use it - numericalValueToColor = data.result! - .logScaleNumericalValueToColor; - } else { - // otherwise use linear scale - numericalValueToColor = data.result! - .numericalValueToColor!; - } + }; + } + // By default, return basic appearance + return ret || (() => basicAppearance); +} - return (d: IPlotSampleData) => { - if (doesPointHaveClinicalData(d)) { - return { - stroke: '#000000', - strokeOpacity: NON_CNA_STROKE_OPACITY, - fill: numericalValueToColor( - d.dispClinicalValue! as number - ), - fillOpacity: 1, - symbol: 'circle', - }; - } else { - return noDataClinicalAppearance; - } - }; - } - } - break; - case ColoringType.MutationTypeAndCopyNumber: +function makeScatterPlotPointAppearance_Clinical( + coloringMenuSelectedOption: ColoringMenuOmnibarOption | undefined, + clinicalDataCache: ClinicalDataCache | undefined, + coloringLogScale?: boolean +) { + let ret = null; + if (coloringMenuSelectedOption && clinicalDataCache) { + const data = clinicalDataCache.get( + coloringMenuSelectedOption.info.clinicalAttribute! + ); + if (data.isComplete) { if ( - cnaDataExists.isComplete && - cnaDataExists.result && - mutationDataExists.isComplete && - mutationDataExists.result + coloringMenuSelectedOption.info.clinicalAttribute!.datatype === + 'STRING' ) { - return (d: IPlotSampleData) => { - const cnaAppearance = getCopyNumberAppearance(d); - const mutAppearance = getMutationTypeAppearance( - d, - oncoprintMutationTypeToAppearance - ); - return Object.assign({}, mutAppearance, cnaAppearance); // overwrite with cna stroke - }; - } - break; - case ColoringType.CopyNumber: - if (cnaDataExists.isComplete && cnaDataExists.result) { - return getCopyNumberAppearance; - } - break; - case ColoringType.MutationType: - if (mutationDataExists.isComplete && mutationDataExists.result) { - return (d: IPlotSampleData) => { - return getMutationTypeAppearance( - d, - oncoprintMutationTypeToAppearance - ); + const categoryToColor = data.result!.categoryToColor!; + ret = (d: IPlotSampleData) => { + if (doesPointHaveClinicalData(d)) { + return { + stroke: '#000000', + strokeOpacity: NON_CNA_STROKE_OPACITY, + fill: + categoryToColor[d.dispClinicalValue! as string], + fillOpacity: 1, + symbol: 'circle', + }; + } else { + return noDataClinicalAppearance; + } }; - } - break; - case ColoringType.LimitValMutationTypeAndCopyNumber: - if ( - cnaDataExists.isComplete && - cnaDataExists.result && - mutationDataExists.isComplete && - mutationDataExists.result + } else if ( + coloringMenuSelectedOption.info.clinicalAttribute!.datatype === + 'NUMBER' ) { - return (d: IPlotSampleData) => { - const limitValAppearance = getLimitValueAppearance(d); - const cnaAppearance = getCopyNumberAppearance(d); - const mutAppearance = getMutationTypeAppearance( - d, - oncoprintMutationTypeToAppearance - ); - return Object.assign( - {}, - mutAppearance, - cnaAppearance, - limitValAppearance - ); // overwrite with cna stroke - }; - } - break; - case ColoringType.LimitValCopyNumber: - if (cnaDataExists.isComplete && cnaDataExists.result) { - return (d: IPlotSampleData) => { - const limitValAppearance = getLimitValueAppearance(d); - const cnaAppearance = getCopyNumberAppearance(d); - return Object.assign({}, cnaAppearance, limitValAppearance); - }; - } - break; - case ColoringType.LimitValMutationType: - if (mutationDataExists.isComplete && mutationDataExists.result) { - return (d: IPlotSampleData) => { - const limitValAppearance = getLimitValueAppearance(d); - const mutAppearance = getMutationTypeAppearance( - d, - oncoprintMutationTypeToAppearance - ); - return Object.assign({}, mutAppearance, limitValAppearance); // overwrite with appearance for limit values + let numericalValueToColor: (x: number) => string; + if ( + coloringLogScale && + data.result!.logScaleNumericalValueToColor + ) { + // if log scale coloring is selected and its available, use it + numericalValueToColor = data.result! + .logScaleNumericalValueToColor; + } else { + // otherwise use linear scale + numericalValueToColor = data.result!.numericalValueToColor!; + } + + ret = (d: IPlotSampleData) => { + if (doesPointHaveClinicalData(d)) { + return { + stroke: '#000000', + strokeOpacity: NON_CNA_STROKE_OPACITY, + fill: numericalValueToColor( + d.dispClinicalValue! as number + ), + fillOpacity: 1, + symbol: 'circle', + }; + } else { + return noDataClinicalAppearance; + } }; } - break; - case ColoringType.LimitVal: - return (d: IPlotSampleData) => { - const limitValAppearance = getLimitValueAppearance(d); - const defaultAppearance = basicAppearance; - return Object.assign({}, defaultAppearance, limitValAppearance); - }; + } } - // By default, return basic appearance - return () => basicAppearance; + return ret; } function mutationsProteinChanges( @@ -2049,7 +2173,13 @@ function mutationsProteinChanges( ); } -export function tooltipMutationsSection(mutations: AnnotatedMutation[]) { +export function tooltipMutationsSection(datum: D) { + if (!isPointProfiledForMutations(datum)) { + return Not profiled for mutations.; + } else if (datum.mutations.length === 0) { + return null; + } + const mutations = datum.mutations; const oncoKbIcon = (mutation: AnnotatedMutation) => ( (datum: D) { + if (!isPointProfiledForCna(datum)) { + return Not profiled for copy number alterations.; + } else if (datum.copyNumberAlterations.length === 0) { + return null; + } + const data = datum.copyNumberAlterations; const oncoKbIcon = (alt: AnnotatedNumericGeneMolecularData) => ( (datum: D) { + if (!isPointProfiledForSv(datum)) { + return Not profiled for structural variants.; + } else if (datum.structuralVariants.length === 0) { + return null; + } + return ( + + {`Structural Variant: `} + {joinJsx( + datum.structuralVariants.map(v => ( + {v.variantClass} + )), + {`, `} + )} + + ); +} + function tooltipClinicalDataSection( clinicalValue: string | number | undefined, clinicalAttribute?: ClinicalAttribute @@ -2206,14 +2361,10 @@ function generalScatterPlotTooltip( vertKeyThresholdType: keyof D, coloringClinicalAttribute?: ClinicalAttribute ) { - let mutationsSection: any = null; - if (d.mutations.length > 0) { - mutationsSection = tooltipMutationsSection(d.mutations); - } - let cnaSection: any = null; - if (d.copyNumberAlterations.length > 0) { - cnaSection = tooltipCnaSection(d.copyNumberAlterations); - } + const mutationsSection = tooltipMutationsSection(d); + const cnaSection = tooltipCnaSection(d); + const svSection = tooltipSvSection(d); + let clinicalDataSection: any = null; if (coloringClinicalAttribute) { clinicalDataSection = tooltipClinicalDataSection( @@ -2253,6 +2404,8 @@ function generalScatterPlotTooltip( {!!mutationsSection &&
} {cnaSection} {!!cnaSection &&
} + {svSection} + {!!svSection &&
} {clinicalDataSection}
); @@ -2279,14 +2432,9 @@ function generalWaterfallPlotTooltip( thresholdTypeKey?: keyof D, coloringClinicalAttribute?: ClinicalAttribute ) { - let mutationsSection: any = null; - if (d.mutations.length > 0) { - mutationsSection = tooltipMutationsSection(d.mutations); - } - let cnaSection: any = null; - if (d.copyNumberAlterations.length > 0) { - cnaSection = tooltipCnaSection(d.copyNumberAlterations); - } + const mutationsSection = tooltipMutationsSection(d); + const cnaSection = tooltipCnaSection(d); + const svSection = tooltipSvSection(d); let clinicalDataSection: any = null; if (coloringClinicalAttribute) { clinicalDataSection = tooltipClinicalDataSection( @@ -2315,6 +2463,8 @@ function generalWaterfallPlotTooltip( {!!mutationsSection &&
} {cnaSection} {!!cnaSection &&
} + {svSection} + {!!svSection &&
} {clinicalDataSection} ); @@ -2404,6 +2554,10 @@ export function makeBoxScatterPlotData( molecularProfileIds: string[]; data: CustomDriverNumericGeneMolecularData[]; }, + structuralVariants?: { + molecularProfileIds: string[]; + data: StructuralVariant[]; + }, selectedGeneForStyling?: Gene, clinicalData?: { clinicalAttribute: ClinicalAttribute; @@ -2417,6 +2571,7 @@ export function makeBoxScatterPlotData( coverageInformation, mutations, copyNumberAlterations, + structuralVariants, selectedGeneForStyling, clinicalData ); @@ -2451,6 +2606,10 @@ export function makeScatterPlotData( molecularProfileIds: string[]; data: CustomDriverNumericGeneMolecularData[]; }, + structuralVariants?: { + molecularProfileIds: string[]; + data: StructuralVariant[]; + }, selectedGeneForStyling?: Gene, clinicalData?: { clinicalAttribute: ClinicalAttribute; @@ -2471,6 +2630,10 @@ export function makeScatterPlotData( molecularProfileIds: string[]; data: CustomDriverNumericGeneMolecularData[]; }, + structuralVariants?: { + molecularProfileIds: string[]; + data: StructuralVariant[]; + }, selectedGeneForStyling?: Gene, clinicalData?: { clinicalAttribute: ClinicalAttribute; @@ -2491,6 +2654,10 @@ export function makeScatterPlotData( molecularProfileIds: string[]; data: AnnotatedNumericGeneMolecularData[]; }, + structuralVariants?: { + molecularProfileIds: string[]; + data: StructuralVariant[]; + }, selectedGeneForStyling?: Gene, clinicalData?: { clinicalAttribute: ClinicalAttribute; @@ -2500,12 +2667,19 @@ export function makeScatterPlotData( const mutationsMap: { [uniqueSampleKey: string]: AnnotatedMutation[]; } = mutations ? _.groupBy(mutations.data, m => m.uniqueSampleKey) : {}; + const cnaMap: { [uniqueSampleKey: string]: AnnotatedNumericGeneMolecularData[]; } = copyNumberAlterations ? _.groupBy(copyNumberAlterations.data, d => d.uniqueSampleKey) : {}; + const structuralVariantMap: { + [uniqueSampleKey: string]: StructuralVariant[]; + } = structuralVariants + ? _.groupBy(structuralVariants.data, d => d.uniqueSampleKey) + : {}; + let clinicalDataMap: { [uniqueKey: string]: ClinicalData; } = {}; @@ -2555,7 +2729,6 @@ export function makeScatterPlotData( .groupBy(mutation => { const mutationType = getOncoprintMutationType(mutation); const driverSuffix = - mutationType !== 'fusion' && mutationType !== 'promoter' && mutationType !== 'other' && mutation.putativeDriver @@ -2570,10 +2743,20 @@ export function makeScatterPlotData( mutationRenderPriority ) as OncoprintMutationType; } + let dispStructuralVariant: string | undefined = undefined; + const sampleSv: StructuralVariant[] | undefined = + structuralVariantMap[d.uniqueSampleKey]; + if (sampleSv && sampleSv.length) { + dispStructuralVariant = sampleSv + .map(sv => sv.variantClass) + .join(', '); + } + const sampleCoverageInfo = coverageInformation[d.uniqueSampleKey]; - let profiledMutations = undefined; - let profiledCna = undefined; - if (mutations || copyNumberAlterations) { + let isProfiledMutations = undefined; + let isProfiledCna = undefined; + let isProfiledStructuralVariants = undefined; + if (mutations || copyNumberAlterations || structuralVariants) { const profiledReport = makeScatterPlotData_profiledReport( selectedGeneForStyling ? selectedGeneForStyling.hugoGeneSymbol @@ -2584,19 +2767,22 @@ export function makeScatterPlotData( sampleCoverageInfo ); if (mutations) { - profiledMutations = false; - for (const molecularProfileId of mutations.molecularProfileIds) { - profiledMutations = - profiledMutations || - !!profiledReport[molecularProfileId]; - } + isProfiledMutations = _.some( + mutations.molecularProfileIds, + id => !!profiledReport[id] + ); } if (copyNumberAlterations) { - profiledCna = false; - for (const molecularProfileId of copyNumberAlterations.molecularProfileIds) { - profiledCna = - profiledCna || !!profiledReport[molecularProfileId]; - } + isProfiledCna = _.some( + copyNumberAlterations.molecularProfileIds, + id => !!profiledReport[id] + ); + } + if (structuralVariants) { + isProfiledStructuralVariants = _.some( + structuralVariants.molecularProfileIds, + id => !!profiledReport[id] + ); } } let dispClinicalValue: string | number | undefined = undefined; @@ -2620,11 +2806,14 @@ export function makeScatterPlotData( horzThresholdTypes: ([] as string[]).concat(d.thresholdType || ''), mutations: sampleMutations || [], copyNumberAlterations: sampleCopyNumberAlterations || [], + structuralVariants: sampleSv || [], dispCna, dispMutationType, + dispStructuralVariant, dispClinicalValue, - profiledCna, - profiledMutations, + isProfiledCna, + isProfiledMutations, + isProfiledStructuralVariants, }; } const data: any[] = []; @@ -2685,6 +2874,10 @@ export function makeWaterfallPlotData( molecularProfileIds: string[]; data: CustomDriverNumericGeneMolecularData[]; }, + structuralVariants?: { + molecularProfileIds: string[]; + data: StructuralVariant[]; + }, clinicalData?: { clinicalAttribute: ClinicalAttribute; data: ClinicalData[]; @@ -2700,6 +2893,12 @@ export function makeWaterfallPlotData( ? _.groupBy(copyNumberAlterations.data, d => d.uniqueSampleKey) : {}; + const structuralVariantMap: { + [uniqueSampleKey: string]: StructuralVariant[]; + } = structuralVariants + ? _.groupBy(structuralVariants.data, d => d.uniqueSampleKey) + : {}; + let clinicalDataMap: { [uniqueKey: string]: ClinicalData; } = {}; @@ -2758,7 +2957,6 @@ export function makeWaterfallPlotData( .groupBy((mutation: AnnotatedMutation) => { const mutationType = getOncoprintMutationType(mutation); const driverSuffix = - mutationType !== 'fusion' && mutationType !== 'promoter' && mutationType !== 'other' && mutation.putativeDriver @@ -2774,31 +2972,50 @@ export function makeWaterfallPlotData( ) as OncoprintMutationType; } + let dispStructuralVariant: string | undefined = undefined; + const sampleSv: StructuralVariant[] | undefined = + structuralVariantMap[d.uniqueSampleKey]; + if (sampleSv && sampleSv.length && selectedGene) { + dispStructuralVariant = sampleSv + .map(sv => sv.variantClass) + .join(', '); + } + const sampleCoverageInfo = coverageInformation[d.uniqueSampleKey]; - let profiledMutations: boolean | undefined = undefined; - let profiledCna: boolean | undefined = undefined; + let isProfiledMutations: boolean | undefined = undefined; + let isProfiledCna: boolean | undefined = undefined; + let isProfiledStructuralVariants: boolean | undefined = undefined; - if ((mutations || copyNumberAlterations) && selectedGene) { + if ( + (mutations || copyNumberAlterations || structuralVariants) && + selectedGene + ) { const profiledReport = makeWaterfallPlotData_profiledReport( selectedGene.hugoGeneSymbol, sampleCoverageInfo ); if (mutations) { - profiledMutations = false; + isProfiledMutations = false; for (const molecularProfileId of mutations.molecularProfileIds) { - profiledMutations = - profiledMutations || + isProfiledMutations = + isProfiledMutations || !!profiledReport[molecularProfileId]; } } if (copyNumberAlterations) { - profiledCna = false; + isProfiledCna = false; for (const molecularProfileId of copyNumberAlterations.molecularProfileIds) { - profiledCna = - profiledCna || !!profiledReport[molecularProfileId]; + isProfiledCna = + isProfiledCna || !!profiledReport[molecularProfileId]; } } + if (structuralVariants) { + isProfiledStructuralVariants = _.some( + structuralVariants.molecularProfileIds, + id => !!profiledReport[id] + ); + } } let dispClinicalValue: string | number | undefined = undefined; @@ -2823,12 +3040,15 @@ export function makeWaterfallPlotData( thresholdTypes: ([] as any[]).concat(d.thresholdType || undefined), mutations: sampleMutations || [], copyNumberAlterations: sampleCopyNumberAlterations || [], + structuralVariants: sampleSv || [], jitter: getJitterForCase(d.uniqueSampleKey), dispCna, dispMutationType, dispClinicalValue, - profiledCna, - profiledMutations, + dispStructuralVariant, + isProfiledCna, + isProfiledMutations, + isProfiledStructuralVariants, }); } @@ -2896,22 +3116,7 @@ function makeWaterfallPlotData_profiledReport( return ret; } -export function getCnaQueries(utilitiesSelection: ColoringMenuSelection) { - const queries: { entrezGeneId: number }[] = []; - if ( - utilitiesSelection.selectedOption !== undefined && - utilitiesSelection.selectedOption.info.entrezGeneId !== - NONE_SELECTED_OPTION_NUMERICAL_VALUE && - utilitiesSelection.selectedOption.info.entrezGeneId !== undefined - ) { - queries.push({ - entrezGeneId: utilitiesSelection.selectedOption.info.entrezGeneId, - }); - } - return _.uniqBy(queries, 'entrezGeneId'); -} - -export function getMutationQueries(utilitiesSelection: ColoringMenuSelection) { +export function getCacheQueries(utilitiesSelection: ColoringMenuSelection) { const queries: { entrezGeneId: number }[] = []; if ( utilitiesSelection.selectedOption !== undefined && @@ -2982,7 +3187,7 @@ export function getScatterPlotDownloadData( entrezGeneIdToGene ).join('; ') ); - } else if (datum.profiledMutations === false) { + } else if (datum.isProfiledMutations === false) { row.push('Not Profiled'); } else { row.push('-'); @@ -2992,7 +3197,7 @@ export function getScatterPlotDownloadData( if (datum.dispCna) { const cna = (cnaToAppearance as any)[datum.dispCna.value]; row.push(`${datum.dispCna.hugoGeneSymbol}: ${cna.legendLabel}`); - } else if (datum.profiledCna === false) { + } else if (datum.isProfiledCna === false) { row.push(`Not Profiled`); } else { row.push('-'); @@ -3048,7 +3253,7 @@ export function getWaterfallPlotDownloadData( entrezGeneIdToGene ).join('; ') ); // 4 concatenated mutations - } else if (datum.profiledMutations === false) { + } else if (datum.isProfiledMutations === false) { row.push('Not Profiled'); } else { row.push('-'); @@ -3058,7 +3263,7 @@ export function getWaterfallPlotDownloadData( if (datum.dispCna) { const cna = (cnaToAppearance as any)[datum.dispCna.value]; row.push(`${datum.dispCna.hugoGeneSymbol}: ${cna.legendLabel}`); - } else if (datum.profiledCna === false) { + } else if (datum.isProfiledCna === false) { row.push(`Not Profiled`); } else { row.push('-'); @@ -3112,7 +3317,7 @@ export function getBoxPlotDownloadData( entrezGeneIdToGene ).join('; ') ); - } else if (datum.profiledMutations === false) { + } else if (datum.isProfiledMutations === false) { row.push('Not Profiled'); } else { row.push('-'); @@ -3124,7 +3329,7 @@ export function getBoxPlotDownloadData( row.push( `${datum.dispCna.hugoGeneSymbol}: ${cna.legendLabel}` ); - } else if (datum.profiledCna === false) { + } else if (datum.isProfiledCna === false) { row.push(`Not Profiled`); } else { row.push('-'); @@ -3346,6 +3551,17 @@ export function getLimitValues(data: any[]): string[] { .value(); } +export function isAlterationTypePresent( + dataTypeOptions: PlotsTabOption[], + vertical: boolean, + alterationTypeConstants: string +) { + return ( + vertical && + !!dataTypeOptions.find(o => o.value === alterationTypeConstants) + ); +} + function stringsToOptions(vals: string[]) { return vals.map(v => ({ value: v, label: v })); } diff --git a/src/pages/resultsView/settings/ResultsPageSettings.tsx b/src/pages/resultsView/settings/ResultsPageSettings.tsx index 701b5b9999d..704e613762b 100644 --- a/src/pages/resultsView/settings/ResultsPageSettings.tsx +++ b/src/pages/resultsView/settings/ResultsPageSettings.tsx @@ -127,8 +127,8 @@ export default class ResultsPageSettings extends React.Component< !this.driverSettingsState.distinguishDrivers } />{' '} - Exclude mutations and copy number alterations of - unknown significance + Exclude alterations (mutations, structural variants + and copy number) of unknown significance
diff --git a/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.spec.ts b/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.spec.ts index 0af4475d0cc..26385f159a8 100644 --- a/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.spec.ts +++ b/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.spec.ts @@ -39,7 +39,7 @@ describe('OncoprinterGeneticUtils', () => { { sampleId: 'sample_id', hugoGeneSymbol: 'TP53', - alteration: 'fusion', + alteration: 'structuralVariant', proteinChange: 'FUSION', trackName: undefined, }, @@ -57,7 +57,7 @@ describe('OncoprinterGeneticUtils', () => { { sampleId: 'sample_id', hugoGeneSymbol: 'TP53', - alteration: 'fusion', + alteration: 'structuralVariant', proteinChange: 'FUSION', trackName: undefined, }, diff --git a/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.ts b/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.ts index 227d55d5671..e5aafc8f058 100644 --- a/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.ts +++ b/src/pages/staticPages/tools/oncoprinter/OncoprinterGeneticUtils.ts @@ -59,7 +59,7 @@ export type OncoprinterGeneticTrackDatum = Pick< | 'disp_cna' | 'disp_mrna' | 'disp_prot' - | 'disp_fusion' + | 'disp_structuralVariant' | 'disp_germ' > & { sample: string; @@ -94,7 +94,8 @@ export type OncoprinterGeneticInputLineType2 = OncoprinterGeneticInputLineType1 | 'mrnaHigh' | 'mrnaLow' | 'protHigh' - | 'protLow'; + | 'protLow' + | 'structuralVariant'; trackName?: string; isGermline?: boolean; isCustomDriver?: boolean; @@ -401,11 +402,11 @@ export function makeGeneticTrackDatum_Data( mutationType: 'indel', }); break; - case OncoprintMutationTypeEnum.FUSION: + case OncoprintMutationTypeEnum.STRUCTURAL_VARIANT: ret = Object.assign(ret, { molecularProfileAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - mutationType: 'fusion', + AlterationTypeConstants.STRUCTURAL_VARIANT, + mutationType: 'structuralVariant', }); break; case OncoprintMutationTypeEnum.PROMOTER: @@ -504,7 +505,11 @@ export function makeGeneticTrackDatum_Data( export function isAltered(d: OncoprinterGeneticTrackDatum) { return ( - d.disp_mut || d.disp_cna || d.disp_mrna || d.disp_prot || d.disp_fusion + d.disp_mut || + d.disp_cna || + d.disp_mrna || + d.disp_prot || + d.disp_structuralVariant ); } function getPercentAltered(data: OncoprinterGeneticTrackDatum[]) { @@ -743,6 +748,13 @@ export function annotateGeneticTrackData( d.driverFilter === PUTATIVE_DRIVER) ); return !excludeVUS || d.putativeDriver; + } + if ( + d.molecularProfileAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT + ) { + //TODO: fetch oncokb data for structural variants once we have + return !excludeVUS; } else { return true; } @@ -843,7 +855,7 @@ export function parseGeneticInput( `${errorPrefix}Type "${type}" is not valid - it must be "FUSION" if Alteration is "FUSION"` ); } else { - ret.alteration = lcType as OncoprintMutationType; + ret.alteration = 'structuralVariant'; ret.proteinChange = alteration; } break; diff --git a/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.spec.ts b/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.spec.ts index 1d9f6b82a88..392f348c250 100644 --- a/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.spec.ts +++ b/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.spec.ts @@ -138,10 +138,13 @@ describe('OncoprinterImportUtils', () => { data: [ { molecularProfileAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - proteinChange: 'proteinChange3', - mutationType: 'fusion', - hugoGeneSymbol: 'gene2', + AlterationTypeConstants.STRUCTURAL_VARIANT, + hugoGeneSymbol: 'gene1', + site1HugoSymbol: 'gene1', + site2HugoSymbol: 'gene2', + eventInfo: 'gene1-gene2', + variantClass: 'Fusion', + comments: 'gene1 gene1-gene2', }, { molecularProfileAlterationType: @@ -176,7 +179,7 @@ describe('OncoprinterImportUtils', () => { 'sample1 gene2 promoter PROMOTER label2\n' + 'sample1 gene2 GAIN CNA label2\n' + 'sample1 gene2 LOW EXP label2\n' + - 'sample2 gene2 proteinChange3 FUSION label2\n' + + 'sample2 gene1 STRUCTURAL_VARIANT label2\n' + 'sample2\n' + 'sample2 gene2 LOW PROT label2\n' + 'sample1\nsample2' @@ -198,7 +201,7 @@ describe('OncoprinterImportUtils', () => { 'patient1 gene2 promoter PROMOTER label2\n' + 'patient1 gene2 GAIN CNA label2\n' + 'patient1 gene2 LOW EXP label2\n' + - 'patient2 gene2 proteinChange3 FUSION label2\n' + + 'patient2 gene1 STRUCTURAL_VARIANT label2\n' + 'patient2\n' + 'patient2 gene2 LOW PROT label2\n' + 'patient1\npatient2' diff --git a/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.ts b/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.ts index 62aa8b38a89..0148e4f111b 100644 --- a/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.ts +++ b/src/pages/staticPages/tools/oncoprinter/OncoprinterImportUtils.ts @@ -31,7 +31,7 @@ const geneticAlterationToDataType: { } = { missense: AlterationTypeConstants.MUTATION_EXTENDED, inframe: AlterationTypeConstants.MUTATION_EXTENDED, - fusion: AlterationTypeConstants.FUSION, + structuralVariant: AlterationTypeConstants.STRUCTURAL_VARIANT, promoter: AlterationTypeConstants.MUTATION_EXTENDED, trunc: AlterationTypeConstants.MUTATION_EXTENDED, splice: AlterationTypeConstants.MUTATION_EXTENDED, @@ -92,6 +92,9 @@ function getOncoprinterGeneticAlteration(d: GeneticTrackDatum_Data) { case AlterationTypeConstants.MUTATION_EXTENDED: alteration = getOncoprintMutationType(d); break; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + alteration = 'structuralVariant'; + break; case AlterationTypeConstants.COPY_NUMBER_ALTERATION: alteration = cna_profile_data_to_string[d.value]; break; @@ -138,9 +141,8 @@ function getOncoprinterGeneticInputLine(parsed: OncoprinterGeneticInputLine) { line.push(parsed.alteration === 'protHigh' ? 'HIGH' : 'LOW'); line.push('PROT'); break; - case AlterationTypeConstants.FUSION: - line.push(parsed.proteinChange || 'fusion'); - line.push('FUSION'); + case AlterationTypeConstants.STRUCTURAL_VARIANT: + line.push('STRUCTURAL VARIANT'); break; } if (parsed.trackName) { diff --git a/src/pages/studyView/StudyViewConfig.ts b/src/pages/studyView/StudyViewConfig.ts index cfb7cbb57c5..d340d7ccc58 100644 --- a/src/pages/studyView/StudyViewConfig.ts +++ b/src/pages/studyView/StudyViewConfig.ts @@ -67,7 +67,7 @@ export enum ChartTypeEnum { TABLE = 'TABLE', SCATTER = 'SCATTER', MUTATED_GENES_TABLE = 'MUTATED_GENES_TABLE', - FUSION_GENES_TABLE = 'FUSION_GENES_TABLE', + STRUCTURAL_VARIANT_GENES_TABLE = 'STRUCTURAL_VARIANT_GENES_TABLE', CNA_GENES_TABLE = 'CNA_GENES_TABLE', GENOMIC_PROFILES_TABLE = 'GENOMIC_PROFILES_TABLE', CASE_LIST_TABLE = 'CASE_LIST_TABLE', @@ -83,7 +83,7 @@ export enum ChartTypeNameEnum { TABLE = 'table', SCATTER = 'density plot', MUTATED_GENES_TABLE = 'table', - FUSION_GENES_TABLE = 'table', + STRUCTURAL_VARIANT_GENES_TABLE = 'table', CNA_GENES_TABLE = 'table', GENOMIC_PROFILES_TABLE = 'table', CASE_LIST_TABLE = 'table', @@ -114,7 +114,7 @@ const studyViewFrontEnd = { PFS_SURVIVAL: 250, MUTATION_COUNT_CNA_FRACTION: 200, MUTATED_GENES_TABLE: 90, - FUSION_GENES_TABLE: 85, + STRUCTURAL_VARIANT_GENES_TABLE: 85, CNA_GENES_TABLE: 80, PATIENT_TREATMENTS_TABLE: 75, SAMPLE_TREATMENTS_TABLE: 75, @@ -181,7 +181,7 @@ const studyViewFrontEnd = { h: 2, minW: 2, }, - [ChartTypeEnum.FUSION_GENES_TABLE]: { + [ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE]: { w: 2, h: 2, minW: 2, diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts index 71c92f83594..66f488c5c81 100644 --- a/src/pages/studyView/StudyViewPageStore.ts +++ b/src/pages/studyView/StudyViewPageStore.ts @@ -54,6 +54,7 @@ import { fetchCopyNumberSegmentsForSamples, getAlterationTypesInOql, getDefaultProfilesForOql, + MolecularAlterationType_filenameSuffix, getSurvivalClinicalAttributesPrefix, } from 'shared/lib/StoreUtils'; import { PatientSurvival } from 'shared/model/PatientSurvival'; @@ -118,6 +119,9 @@ import { SpecialChartsUniqueKeyEnum, StudyWithSamples, submitToPage, + updateSavedUserPreferenceChartIds, + getFilteredMolecularProfilesByAlterationType, + getStructuralVariantSamplesCount, } from './StudyViewUtils'; import MobxPromise from 'mobxpromise'; import { SingleGeneQuery } from 'shared/lib/oql/oql-parser'; @@ -210,6 +214,7 @@ import { } from 'pages/resultsView/enrichments/EnrichmentsUtil'; import { GenericAssayDataBin, + ClinicalDataBin, GenericAssayDataBinFilter, } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPIInternal'; import { fetchGenericAssayMetaByMolecularProfileIdsGroupedByGenericAssayType } from 'shared/lib/GenericAssayUtils/GenericAssayCommonUtils'; @@ -2042,7 +2047,7 @@ export class StudyViewPageStore { @observable _filterComparisonGroups: StudyViewComparisonGroup[] = []; @observable private _filterMutatedGenesTableByCancerGenes: boolean = false; - @observable private _filterFusionGenesTableByCancerGenes: boolean = false; + @observable private _filterSVGenesTableByCancerGenes: boolean = false; @observable private _filterCNAGenesTableByCancerGenes: boolean = false; @action.bound @@ -2050,8 +2055,8 @@ export class StudyViewPageStore { this._filterMutatedGenesTableByCancerGenes = filtered; } @action.bound - updateFusionGenesTableByCancerGenesFilter(filtered: boolean): void { - this._filterFusionGenesTableByCancerGenes = filtered; + updateSVGenesTableByCancerGenesFilter(filtered: boolean): void { + this._filterSVGenesTableByCancerGenes = filtered; } @action.bound @@ -2066,10 +2071,10 @@ export class StudyViewPageStore { ); } - @computed get filterFusionGenesTableByCancerGenes(): boolean { + @computed get filterSVGenesTableByCancerGenes(): boolean { return ( this.oncokbCancerGeneFilterEnabled && - this._filterFusionGenesTableByCancerGenes + this._filterSVGenesTableByCancerGenes ); } @@ -2173,6 +2178,7 @@ export class StudyViewPageStore { haveCnaInQuery: boolean; haveMrnaInQuery: boolean; haveProtInQuery: boolean; + haveStructuralVariantInQuery: boolean; } { return getAlterationTypesInOql(this.geneQueries); } @@ -2195,6 +2201,18 @@ export class StudyViewPageStore { ] ); } + + @computed get defaultStructuralVariantProfile(): + | MolecularProfile + | undefined { + return ( + this.defaultProfilesForOql && + this.defaultProfilesForOql[ + AlterationTypeConstants.STRUCTURAL_VARIANT + ] + ); + } + @computed get defaultCnaProfile(): MolecularProfile | undefined { return ( this.defaultProfilesForOql && @@ -2740,7 +2758,7 @@ export class StudyViewPageStore { this.updateScatterPlotFilterByValues(chartUniqueKey); break; case ChartTypeEnum.MUTATED_GENES_TABLE: - case ChartTypeEnum.FUSION_GENES_TABLE: + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: case ChartTypeEnum.CNA_GENES_TABLE: this.resetGeneFilter(chartUniqueKey); break; @@ -2807,7 +2825,7 @@ export class StudyViewPageStore { } return false; case ChartTypeEnum.MUTATED_GENES_TABLE: - case ChartTypeEnum.FUSION_GENES_TABLE: + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: case ChartTypeEnum.CNA_GENES_TABLE: return this._geneFilterSet.has(chartUniqueKey); case ChartTypeEnum.GENOMIC_PROFILES_TABLE: @@ -4251,11 +4269,26 @@ export class StudyViewPageStore { default: [], }); - readonly mutationProfiles = remoteData({ + readonly studyIdToMolecularProfiles = remoteData({ await: () => [this.molecularProfiles], - invoke: async () => { + invoke: () => { + return Promise.resolve( + _.groupBy( + this.molecularProfiles.result!, + molecularProfile => molecularProfile.studyId + ) + ); + }, + onError: error => {}, + default: {}, + }); + + readonly mutationProfiles = remoteData({ + await: () => [this.studyIdToMolecularProfiles], + invoke: () => { return Promise.resolve( - this.getMolecularProfilesByAlterationType( + getFilteredMolecularProfilesByAlterationType( + this.studyIdToMolecularProfiles.result, AlterationTypeConstants.MUTATION_EXTENDED ) ); @@ -4265,14 +4298,14 @@ export class StudyViewPageStore { }); readonly structuralVariantProfiles = remoteData({ - await: () => [this.molecularProfiles], - invoke: async () => { - // TODO: currently only look for STRUCTURAL_VARIANT profiles with fusion datatype - // until database is support querying structural variant data + await: () => [this.studyIdToMolecularProfiles], + invoke: () => { + // TODO: Cleanup once fusions are removed from database return Promise.resolve( - this.getMolecularProfilesByAlterationType( + getFilteredMolecularProfilesByAlterationType( + this.studyIdToMolecularProfiles.result, AlterationTypeConstants.STRUCTURAL_VARIANT, - AlterationTypeConstants.FUSION + [DataTypeConstants.FUSION, DataTypeConstants.SV] ) ); }, @@ -4281,12 +4314,13 @@ export class StudyViewPageStore { }); readonly cnaProfiles = remoteData({ - await: () => [this.molecularProfiles], - invoke: async () => { + await: () => [this.studyIdToMolecularProfiles], + invoke: () => { return Promise.resolve( - this.getMolecularProfilesByAlterationType( + getFilteredMolecularProfilesByAlterationType( + this.studyIdToMolecularProfiles.result, AlterationTypeConstants.COPY_NUMBER_ALTERATION, - DataTypeConstants.DISCRETE + [DataTypeConstants.DISCRETE] ) ); }, @@ -4294,37 +4328,36 @@ export class StudyViewPageStore { default: [], }); - private getMolecularProfilesByAlterationType( - alterationType: string, - dataType?: string - ): MolecularProfile[] { - let molecularProfiles: MolecularProfile[] = []; - if (_.isEmpty(dataType)) { - molecularProfiles = this.molecularProfiles.result.filter( - profile => profile.molecularAlterationType === alterationType - ); - } else { - molecularProfiles = this.molecularProfiles.result.filter( - profile => - profile.molecularAlterationType === alterationType && - profile.datatype === dataType - ); - } - - return _.chain(molecularProfiles) - .groupBy(molecularProfile => molecularProfile.studyId) - .flatMap(profiles => profiles[0]) - .value(); - } - readonly filteredGenePanelData = remoteData({ - await: () => [this.molecularProfiles, this.samples], + await: () => [ + this.molecularProfiles, + this.samples, + this.structuralVariantProfiles, + ], invoke: async () => { if (_.isEmpty(this.molecularProfiles.result)) { return []; } + + //TODO: remove filtering logic fusion profiles data is fixed + //filter out structural variant/fusion profiles + const filteredMolecularProfiles: MolecularProfile[] = this.molecularProfiles.result.filter( + molecularProfile => + ![ + AlterationTypeConstants.STRUCTURAL_VARIANT, + AlterationTypeConstants.FUSION, + ].includes(molecularProfile.molecularAlterationType) + ); + + //Add appropriate structural variant/fusion profiles + this.structuralVariantProfiles.result.forEach( + structuralVariantProfile => { + filteredMolecularProfiles.push(structuralVariantProfile); + } + ); + //TODO: remove this block once fusion profiles data is fixed const studyMolecularProfilesSet = _.groupBy( - this.molecularProfiles.result, + filteredMolecularProfiles, molecularProfile => molecularProfile.studyId ); @@ -5008,9 +5041,9 @@ export class StudyViewPageStore { uniqueKey: uniqueKey, dataType: ChartMetaDataTypeEnum.GENOMIC, patientAttribute: false, - displayName: 'Fusion Genes', + displayName: 'Structural Variant Genes', priority: getDefaultPriorityByUniqueKey( - ChartTypeEnum.FUSION_GENES_TABLE + ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE ), renderWhenDataChange: true, description: '', @@ -5244,7 +5277,7 @@ export class StudyViewPageStore { _.fromPairs(this._genericAssayChartMap.toJSON()), _.fromPairs(this._clinicalDataBinFilterSet.toJSON()), this._filterMutatedGenesTableByCancerGenes, - this._filterFusionGenesTableByCancerGenes, + this._filterSVGenesTableByCancerGenes, this._filterCNAGenesTableByCancerGenes, this.currentGridLayout ); @@ -5295,7 +5328,7 @@ export class StudyViewPageStore { this.currentFocusedChartByUser = undefined; this.currentFocusedChartByUserDimension = undefined; this._filterMutatedGenesTableByCancerGenes = true; - this._filterFusionGenesTableByCancerGenes = true; + this._filterSVGenesTableByCancerGenes = true; this._filterCNAGenesTableByCancerGenes = true; this._clinicalDataBinFilterSet = observable.map( toJS(this._defaultClinicalDataBinFilterSet) @@ -5412,6 +5445,9 @@ export class StudyViewPageStore { @action.bound private loadChartSettings(chartSettings: ChartUserSetting[]): void { this.clearPageChartSettings(); + const validChartTypes = Object.values(ChartTypeEnum).map(chartType => + chartType.toString() + ); _.map(chartSettings, chartUserSettings => { if (chartUserSettings.name && chartUserSettings.groups) { type CustomGroup = { @@ -5504,8 +5540,8 @@ export class StudyViewPageStore { ? true : chartUserSettings.filterByCancerGenes; break; - case ChartTypeEnum.FUSION_GENES_TABLE: - this._filterFusionGenesTableByCancerGenes = + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: + this._filterSVGenesTableByCancerGenes = chartUserSettings.filterByCancerGenes === undefined ? true : chartUserSettings.filterByCancerGenes; @@ -5534,12 +5570,18 @@ export class StudyViewPageStore { default: break; } - this.changeChartVisibility(chartUserSettings.id, true); - chartUserSettings.chartType && - this.chartsType.set( - chartUserSettings.id, - chartUserSettings.chartType - ); + let chartType = chartUserSettings.chartType; + if (chartType) { + // map any old FUSION_GENES_TABLE chart types to STRUCTURAL_VARIANT_GENES_TABLE + if (chartType.toString() === 'FUSION_GENES_TABLE') { + chartType = ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE; + } + //only valid chart types should be visible + if (validChartTypes.includes(chartType)) { + this.changeChartVisibility(chartUserSettings.id, true); + this.chartsType.set(chartUserSettings.id, chartType); + } + } }); this.useCurrentGridLayout = true; } @@ -5618,18 +5660,27 @@ export class StudyViewPageStore { profile => profile.molecularProfileId ) ); - const fusionGeneMeta = _.find( + const structuralVariantGeneMeta = _.find( this.chartMetaSet, chartMeta => chartMeta.uniqueKey === uniqueKey ); - if (fusionGeneMeta && fusionGeneMeta.priority !== 0) { - this.changeChartVisibility(fusionGeneMeta.uniqueKey, true); + if ( + structuralVariantGeneMeta && + structuralVariantGeneMeta.priority !== 0 + ) { + this.changeChartVisibility( + structuralVariantGeneMeta.uniqueKey, + true + ); } - this.chartsType.set(uniqueKey, ChartTypeEnum.FUSION_GENES_TABLE); + this.chartsType.set( + uniqueKey, + ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE + ); this.chartsDimension.set( uniqueKey, STUDY_VIEW_CONFIG.layout.dimensions[ - ChartTypeEnum.FUSION_GENES_TABLE + ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE ] ); } @@ -6403,7 +6454,11 @@ export class StudyViewPageStore { this.studyViewFilterWithFilteredSampleIdentifiers, ], invoke: async () => { - if (!_.isEmpty(this.mutationProfiles.result)) { + if ( + !_.isEmpty(this.mutationProfiles.result) && + this.studyViewFilterWithFilteredSampleIdentifiers.result! + .sampleIdentifiers.length > 0 + ) { let mutatedGenes = await internalClient.fetchMutatedGenesUsingPOST( { studyViewFilter: this @@ -6447,7 +6502,9 @@ export class StudyViewPageStore { default: [], }); - readonly fusionGeneTableRowData = remoteData({ + readonly structuralVariantGeneTableRowData = remoteData< + MultiSelectionTableRow[] + >({ await: () => this.oncokbCancerGeneFilterEnabled ? [ @@ -6463,15 +6520,19 @@ export class StudyViewPageStore { this.studyViewFilterWithFilteredSampleIdentifiers, ], invoke: async () => { - if (!_.isEmpty(this.structuralVariantProfiles.result)) { - const fusionGenes = await internalClient.fetchFusionGenesUsingPOST( + if ( + !_.isEmpty(this.structuralVariantProfiles.result) && + this.studyViewFilterWithFilteredSampleIdentifiers.result! + .sampleIdentifiers.length > 0 + ) { + const structuralVariantGenes = await internalClient.fetchStructuralVariantGenesUsingPOST( { studyViewFilter: this .studyViewFilterWithFilteredSampleIdentifiers .result!, } ); - return fusionGenes.map(item => { + return structuralVariantGenes.map(item => { return { ...item, label: item.hugoGeneSymbol, @@ -6523,7 +6584,11 @@ export class StudyViewPageStore { this.studyViewFilterWithFilteredSampleIdentifiers, ], invoke: async () => { - if (!_.isEmpty(this.cnaProfiles.result)) { + if ( + !_.isEmpty(this.cnaProfiles.result) && + this.studyViewFilterWithFilteredSampleIdentifiers.result! + .sampleIdentifiers.length > 0 + ) { let cnaGenes = await internalClient.fetchCNAGenesUsingPOST({ studyViewFilter: this .studyViewFilterWithFilteredSampleIdentifiers.result!, @@ -6981,11 +7046,11 @@ export class StudyViewPageStore { } else return ''; } - public getFusionGenesDownloadData(): string { - if (this.fusionGeneTableRowData.result) { + public getStructuralVariantGenesDownloadData(): string { + if (this.structuralVariantGeneTableRowData.result) { const header = [ 'Gene', - '# Fusion', + '# Structural Variant', '#', 'Profiled Samples', 'Freq', @@ -6995,7 +7060,7 @@ export class StudyViewPageStore { } const data = [header.join('\t')]; _.each( - this.fusionGeneTableRowData.result, + this.structuralVariantGeneTableRowData.result, (record: MultiSelectionTableRow) => { const rowData = [ record.label, @@ -7155,22 +7220,30 @@ export class StudyViewPageStore { ) { const yAxisBinCount = MutationCountVsCnaYBinsMin; const xAxisBinCount = 50; - const bins = ( - await internalClient.fetchClinicalDataDensityPlotUsingPOST({ - xAxisAttributeId: - SpecialChartsUniqueKeyEnum.FRACTION_GENOME_ALTERED, - yAxisAttributeId: - SpecialChartsUniqueKeyEnum.MUTATION_COUNT, - xAxisStart: 0, - xAxisEnd: 1, // FGA always goes 0 to 1 - yAxisStart: 0, // mutation always starts at 0 - xAxisBinCount, - yAxisBinCount, - studyViewFilter: this - .studyViewFilterWithFilteredSampleIdentifiers - .result!, - }) - ).filter(bin => bin.count > 0); // only show points for bins with stuff in them + let bins: DensityPlotBin[] = []; + if ( + this.studyViewFilterWithFilteredSampleIdentifiers.result! + .sampleIdentifiers.length > 0 + ) { + bins = ( + await internalClient.fetchClinicalDataDensityPlotUsingPOST( + { + xAxisAttributeId: + SpecialChartsUniqueKeyEnum.FRACTION_GENOME_ALTERED, + yAxisAttributeId: + SpecialChartsUniqueKeyEnum.MUTATION_COUNT, + xAxisStart: 0, + xAxisEnd: 1, // FGA always goes 0 to 1 + yAxisStart: 0, // mutation always starts at 0 + xAxisBinCount, + yAxisBinCount, + studyViewFilter: this + .studyViewFilterWithFilteredSampleIdentifiers + .result!, + } + ) + ).filter(bin => bin.count > 0); // only show points for bins with stuff in them + } const xBinSize = 1 / xAxisBinCount; const yBinSize = Math.max(...bins.map(bin => bin.binY)) / @@ -7379,7 +7452,10 @@ export class StudyViewPageStore { }); readonly caseListSampleCounts = remoteData({ - await: () => [this.selectedSamples], + await: () => [ + this.selectedSamples, + this.studyViewFilterWithFilteredSampleIdentifiers, + ], invoke: async () => { // return empty if there are no filtered samples if (!this.hasFilteredSamples) { @@ -7808,6 +7884,7 @@ export class StudyViewPageStore { .join(','), tab_index: 'tab_visualize', }; + let molecularProfileFilters: string[] = []; if ( this.filteredVirtualStudies.result.length === 0 && @@ -7817,44 +7894,57 @@ export class StudyViewPageStore { this.alterationTypesInOQL.haveMutInQuery && this.defaultMutationProfile ) { - formOps[ - 'genetic_profile_ids_PROFILE_MUTATION_EXTENDED' - ] = this.defaultMutationProfile.molecularProfileId; + molecularProfileFilters.push( + getSuffixOfMolecularProfile(this.defaultMutationProfile) + ); + } + if ( + this.alterationTypesInOQL.haveStructuralVariantInQuery && + this.defaultStructuralVariantProfile + ) { + molecularProfileFilters.push( + getSuffixOfMolecularProfile( + this.defaultStructuralVariantProfile + ) + ); } if ( this.alterationTypesInOQL.haveCnaInQuery && this.defaultCnaProfile ) { - formOps[ - 'genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION' - ] = this.defaultCnaProfile.molecularProfileId; + molecularProfileFilters.push( + getSuffixOfMolecularProfile(this.defaultCnaProfile) + ); } if ( this.alterationTypesInOQL.haveMrnaInQuery && this.defaultMrnaProfile ) { - formOps[ - 'genetic_profile_ids_PROFILE_MRNA_EXPRESSION' - ] = this.defaultMrnaProfile.molecularProfileId; + molecularProfileFilters.push( + getSuffixOfMolecularProfile(this.defaultMrnaProfile) + ); } if ( this.alterationTypesInOQL.haveProtInQuery && this.defaultProtProfile ) { - formOps[ - 'genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION' - ] = this.defaultProtProfile.molecularProfileId; + molecularProfileFilters.push( + getSuffixOfMolecularProfile(this.defaultProtProfile) + ); } } else { - let data_priority = '0'; - let { mutation, cna } = { - mutation: !_.isEmpty(this.mutationProfiles.result), - cna: !_.isEmpty(this.cnaProfiles), - }; - if (mutation && cna) data_priority = '0'; - else if (mutation) data_priority = '1'; - else if (cna) data_priority = '2'; - formOps.data_priority = data_priority; + molecularProfileFilters = _.chain([ + ...this.mutationProfiles.result, + ...this.cnaProfiles.result, + ...this.structuralVariantProfiles.result, + ]) + .map(profile => getSuffixOfMolecularProfile(profile)) + .uniq() + .value(); + } + + if (molecularProfileFilters.length > 0) { + formOps.profileFilter = molecularProfileFilters.join(','); } if (this.chartsAreFiltered) { @@ -8034,7 +8124,7 @@ export class StudyViewPageStore { }, }); - // For mutated genes, cna genes and fusion gene charts we show number of profiled samples + // For mutated genes, cna genes and structural variant gene charts we show number of profiled samples // in the chart title. These numbers are fetched from molecularProfileSampleCountSet public getChartTitle( chartType: ChartTypeEnum, @@ -8045,18 +8135,18 @@ export class StudyViewPageStore { switch (chartType) { case ChartTypeEnum.MUTATED_GENES_TABLE: { count = this.molecularProfileSampleCountSet.result[ - 'mutations' + MolecularAlterationType_filenameSuffix.MUTATION_EXTENDED! ] ? this.molecularProfileSampleCountSet.result[ - 'mutations' + MolecularAlterationType_filenameSuffix.MUTATION_EXTENDED! ] : 0; break; } - case ChartTypeEnum.FUSION_GENES_TABLE: { - count = this.molecularProfileSampleCountSet.result['fusion'] - ? this.molecularProfileSampleCountSet.result['fusion'] - : 0; + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: { + count = getStructuralVariantSamplesCount( + this.molecularProfileSampleCountSet.result + ); break; } case ChartTypeEnum.CNA_GENES_TABLE: { diff --git a/src/pages/studyView/StudyViewUtils.spec.ts b/src/pages/studyView/StudyViewUtils.spec.ts index cd9f437e9db..4c2f455c961 100644 --- a/src/pages/studyView/StudyViewUtils.spec.ts +++ b/src/pages/studyView/StudyViewUtils.spec.ts @@ -220,7 +220,9 @@ describe('StudyViewUtils', () => { }, { geneQueries: [['GENE1']], - molecularProfileIds: ['cancer_study_fusion'], + molecularProfileIds: [ + 'cancer_study_structural_variants', + ], }, { geneQueries: [['GENE2:HOMDEL']], @@ -264,14 +266,15 @@ describe('StudyViewUtils', () => { attribute2: 'attribute2 name', attribute3: 'attribute3 name', cancer_study_sequenced: ' Mutated Genes', - cancer_study_fusion: 'Fusion Genes', + cancer_study_structural_variants: + 'Structural Variant Genes', cancer_study_cna: 'CNA Genes', }, {} as any, {} as any ).startsWith( '4 samples from 2 studies:\n- Study 1 (2 samples)\n- Study 2 (2 samples)' + - '\n\nFilters:\n- Mutated Genes:\n - GENE1\n- Fusion Genes:\n - GENE1\n- CNA Genes:' + + '\n\nFilters:\n- Mutated Genes:\n - GENE1\n- Structural Variant Genes:\n - GENE1\n- CNA Genes:' + '\n - GENE2:HOMDEL\n- attribute1 name: value1\n' + '- attribute2 name: 10 < x ≤ 0\n- attribute3 name: 2 samples\n\nCreated on' ) diff --git a/src/pages/studyView/StudyViewUtils.tsx b/src/pages/studyView/StudyViewUtils.tsx index 0b04dde9c52..c903bd49e33 100644 --- a/src/pages/studyView/StudyViewUtils.tsx +++ b/src/pages/studyView/StudyViewUtils.tsx @@ -60,6 +60,10 @@ import { CNA_TO_ALTERATION } from 'pages/resultsView/enrichments/EnrichmentsUtil import ComplexKeyMap from 'shared/lib/complexKeyDataStructures/ComplexKeyMap'; import { Datalabel } from 'shared/lib/DataUtils'; import { getSuffixOfMolecularProfile } from 'shared/lib/molecularProfileUtils'; +import { + CNAProfilesEnum, + StructuralVariantProfilesEnum, +} from 'shared/components/query/QueryStoreUtils'; import { GenericAssayDataBin, ClinicalDataBin, @@ -84,28 +88,9 @@ export enum DataType { NUMBER = 'NUMBER', } -export enum CNAProfilesEnum { - cna = 'cna', - gistic = 'gistic', - rae = 'rae', - cna_consensus = 'cna_consensus', -} - export type ClinicalDataType = 'SAMPLE' | 'PATIENT'; -export type ChartType = - | 'PIE_CHART' - | 'BAR_CHART' - | 'SURVIVAL' - | 'TABLE' - | 'SCATTER' - | 'MUTATED_GENES_TABLE' - | 'FUSION_GENES_TABLE' - | 'GENOMIC_PROFILES_TABLE' - | 'CASE_LIST_TABLE' - | 'CNA_GENES_TABLE' - | 'SAMPLE_TREATMENTS_TABLE' - | 'PATIENT_TREATMENTS_TABLE' - | 'NONE'; + +export type ChartType = keyof typeof ChartTypeEnum; export enum SpecialChartsUniqueKeyEnum { CUSTOM_SELECT = 'CUSTOM_SELECT', @@ -2340,7 +2325,7 @@ export function getChartSettingsMap( [uniqueId: string]: ClinicalDataBinFilter & { showNA?: boolean }; }, filterMutatedGenesTableByCancerGenes: boolean = true, - filterFusionGenesTableByCancerGenes: boolean = true, + filterSVGenesTableByCancerGenes: boolean = true, filterCNAGenesTableByCancerGenes: boolean = true, gridLayout?: ReactGridLayout.Layout[] ) { @@ -2366,8 +2351,8 @@ export function getChartSettingsMap( case ChartTypeEnum.MUTATED_GENES_TABLE: chartSetting.filterByCancerGenes = filterMutatedGenesTableByCancerGenes; break; - case ChartTypeEnum.FUSION_GENES_TABLE: - chartSetting.filterByCancerGenes = filterFusionGenesTableByCancerGenes; + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: + chartSetting.filterByCancerGenes = filterSVGenesTableByCancerGenes; break; case ChartTypeEnum.CNA_GENES_TABLE: chartSetting.filterByCancerGenes = filterCNAGenesTableByCancerGenes; @@ -2929,13 +2914,25 @@ export async function getGenericAssayDataAsClinicalData( }); } +export function getStructuralVariantSamplesCount(molecularProfileSampleCountSet: { + [id: string]: number; +}) { + return ( + molecularProfileSampleCountSet[StructuralVariantProfilesEnum.fusion] || + molecularProfileSampleCountSet[ + StructuralVariantProfilesEnum.structural_variants + ] || + 0 + ); +} + export function getCNASamplesCount(molecularProfileSampleCountSet: { [id: string]: number; }) { return ( molecularProfileSampleCountSet[CNAProfilesEnum.cna] || molecularProfileSampleCountSet[CNAProfilesEnum.gistic] || - molecularProfileSampleCountSet[CNAProfilesEnum.rae] || + molecularProfileSampleCountSet[CNAProfilesEnum.cna_rae] || molecularProfileSampleCountSet[CNAProfilesEnum.cna_consensus] || 0 ); @@ -2962,6 +2959,44 @@ export function getMolecularProfileSamplesSet( ); } +export function getFilteredMolecularProfilesByAlterationType( + studyIdToMolecularProfiles: { [studyId: string]: MolecularProfile[] }, + alterationType: string, + allowedDataTypes?: string[] // allowed MolecularProfile datatypes +) { + return _.reduce( + studyIdToMolecularProfiles, + (acc: MolecularProfile[], molecularProfiles) => { + let filteredMolecularProfiles = molecularProfiles.filter( + profile => { + let isFiltered = + profile.molecularAlterationType === alterationType; + if (!_.isEmpty(allowedDataTypes)) { + isFiltered = + isFiltered || + allowedDataTypes!.includes(profile.datatype); + } + return isFiltered; + } + ); + if (!_.isEmpty(allowedDataTypes)) { + const dataTypeToIndexSet = stringListToIndexSet( + allowedDataTypes! + ); + filteredMolecularProfiles = _.sortBy( + filteredMolecularProfiles, + profile => dataTypeToIndexSet[profile.datatype] + ); + } + if (!_.isEmpty(filteredMolecularProfiles)) { + acc.push(filteredMolecularProfiles[0]); + } + return acc; + }, + [] + ); +} + export function getNonZeroUniqueBins(dataBins: DataBin[]) { return _.uniq( _.reduce( diff --git a/src/pages/studyView/TableUtils.tsx b/src/pages/studyView/TableUtils.tsx index 21468626e09..f2a6a5a4d72 100644 --- a/src/pages/studyView/TableUtils.tsx +++ b/src/pages/studyView/TableUtils.tsx @@ -127,7 +127,7 @@ export function getCancerGeneFilterToggleIcon( export enum FreqColumnTypeEnum { MUTATION = 'mutations', - FUSION = 'fusions', + STRUCTURAL_VARIANT = 'structural variants', CNA = 'copy number alterations', DATA = 'data', } diff --git a/src/pages/studyView/UserSelections.tsx b/src/pages/studyView/UserSelections.tsx index 8525601baba..e5875a0c55a 100644 --- a/src/pages/studyView/UserSelections.tsx +++ b/src/pages/studyView/UserSelections.tsx @@ -42,7 +42,7 @@ import { } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPIInternal'; import { ClinicalDataFilter } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPI'; import { - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_MISSENSE, } from 'cbioportal-frontend-commons'; @@ -635,8 +635,8 @@ export default class UserSelections extends React.Component< case ChartTypeEnum.MUTATED_GENES_TABLE: color = MUT_COLOR_MISSENSE; break; - case ChartTypeEnum.FUSION_GENES_TABLE: - color = MUT_COLOR_FUSION; + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: + color = STRUCTURAL_VARIANT_COLOR; break; case ChartTypeEnum.CNA_GENES_TABLE: { const oqlParts = oql.trim().split(':'); diff --git a/src/pages/studyView/charts/ChartContainer.tsx b/src/pages/studyView/charts/ChartContainer.tsx index 30cc3ad6131..8cd4d677d53 100644 --- a/src/pages/studyView/charts/ChartContainer.tsx +++ b/src/pages/studyView/charts/ChartContainer.tsx @@ -491,10 +491,10 @@ export class ChartContainer extends React.Component { /> ); } - case ChartTypeEnum.FUSION_GENES_TABLE: { + case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: { return () => ( { { columnKey: MultiSelectionTableColumnKey.GENE }, { columnKey: - MultiSelectionTableColumnKey.NUMBER_FUSIONS, + MultiSelectionTableColumnKey.NUMBER_STRUCTURAL_VARIANTS, }, { columnKey: MultiSelectionTableColumnKey.NUMBER }, { columnKey: MultiSelectionTableColumnKey.FREQ }, diff --git a/src/pages/studyView/table/MultiSelectionTable.tsx b/src/pages/studyView/table/MultiSelectionTable.tsx index 2b61d7022b9..0f28caef0c3 100644 --- a/src/pages/studyView/table/MultiSelectionTable.tsx +++ b/src/pages/studyView/table/MultiSelectionTable.tsx @@ -55,7 +55,7 @@ export enum MultiSelectionTableColumnKey { GENE = 'Gene', MOLECULAR_PROFILE = 'Molecular Profile', CASE_LIST = 'Name', - NUMBER_FUSIONS = '# Fusion', + NUMBER_STRUCTURAL_VARIANTS = '# SV', NUMBER_MUTATIONS = '# Mut', CYTOBAND = 'Cytoband', CNA = 'CNA', @@ -97,7 +97,7 @@ const DEFAULT_COLUMN_WIDTH_RATIO: { [MultiSelectionTableColumnKey.MOLECULAR_PROFILE]: 0.6, [MultiSelectionTableColumnKey.CASE_LIST]: 0.6, [MultiSelectionTableColumnKey.NUMBER_MUTATIONS]: 0.25, - [MultiSelectionTableColumnKey.NUMBER_FUSIONS]: 0.25, + [MultiSelectionTableColumnKey.NUMBER_STRUCTURAL_VARIANTS]: 0.2, [MultiSelectionTableColumnKey.NUMBER]: 0.25, [MultiSelectionTableColumnKey.FREQ]: 0.15, [MultiSelectionTableColumnKey.CYTOBAND]: 0.25, @@ -326,7 +326,11 @@ export class MultiSelectionTable extends React.Component< name: columnKey, tooltip: Total number of mutations, headerRender: () => { - return
# Mut
; + return ( +
+ {MultiSelectionTableColumnKey.NUMBER_MUTATIONS} +
+ ); }, render: (data: MultiSelectionTableRow) => ( Total number of mutations, + [MultiSelectionTableColumnKey.NUMBER_STRUCTURAL_VARIANTS]: { + name: columnKey, + tooltip: Total number of structural variants, headerRender: () => { - return # Fusion; + return ( +
+ { + MultiSelectionTableColumnKey.NUMBER_STRUCTURAL_VARIANTS + } +
+ ); }, render: (data: MultiSelectionTableRow) => ( this.store.resetGeneFilter(chartMeta.uniqueKey); props.selectedGenes = this.store.selectedGenes; props.onGeneSelect = this.store.onCheckGene; props.title = this.store.getChartTitle( - ChartTypeEnum.FUSION_GENES_TABLE, + ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE, props.title ); - props.getData = () => this.store.getFusionGenesDownloadData(); + props.getData = () => + this.store.getStructuralVariantGenesDownloadData(); props.genePanelCache = this.store.genePanelCache; props.downloadTypes = ['Data']; - props.filterByCancerGenes = this.store.filterFusionGenesTableByCancerGenes; - props.onChangeCancerGeneFilter = this.store.updateFusionGenesTableByCancerGenesFilter; + props.filterByCancerGenes = this.store.filterSVGenesTableByCancerGenes; + props.onChangeCancerGeneFilter = this.store.updateSVGenesTableByCancerGenesFilter; break; } case ChartTypeEnum.CNA_GENES_TABLE: { diff --git a/src/shared/cache/MolecularProfilesInStudyCache.ts b/src/shared/cache/MolecularProfilesInStudyCache.ts index 864db0dbd95..81191dad714 100644 --- a/src/shared/cache/MolecularProfilesInStudyCache.ts +++ b/src/shared/cache/MolecularProfilesInStudyCache.ts @@ -5,6 +5,10 @@ import { } from 'cbioportal-ts-api-client'; import client from '../api/cbioportalClientInstance'; import * as _ from 'lodash'; +import { + DataTypeConstants, + AlterationTypeConstants, +} from 'pages/resultsView/ResultsViewPageStore'; function queryToKey(studyId: string) { return studyId; @@ -17,13 +21,27 @@ function dataToKey(molecularProfiles: MolecularProfile[], studyId: string) { async function fetch( studyIds: string[] ): Promise[]> { - const profiles: MolecularProfile[] = await client.fetchMolecularProfilesUsingPOST( + let profiles: MolecularProfile[] = await client.fetchMolecularProfilesUsingPOST( { molecularProfileFilter: { studyIds, } as MolecularProfileFilter, } ); + + //TODO: remove this block once data is fixed + profiles = profiles.map(profile => { + if ( + profile.molecularAlterationType === + AlterationTypeConstants.STRUCTURAL_VARIANT && + profile.datatype === DataTypeConstants.SV + ) { + profile.showProfileInAnalysisTab = false; + } + return profile; + }); + //TODO: remove this block once data is fixed + const profilesByStudy = _.groupBy(profiles, profile => profile.studyId); return studyIds.map(studyId => { const data = [profilesByStudy[studyId] || []]; diff --git a/src/shared/components/banners/AlterationFilterWarning.tsx b/src/shared/components/banners/AlterationFilterWarning.tsx index 22799d2ec4a..08aa6d8a8aa 100644 --- a/src/shared/components/banners/AlterationFilterWarning.tsx +++ b/src/shared/components/banners/AlterationFilterWarning.tsx @@ -19,13 +19,16 @@ export interface IAlterationFilterWarningProps { } function getVusDescription( - types: { mutation: boolean; cna: boolean }, + types: { mutation: boolean; cna: boolean; structuralVariant: boolean }, plural: boolean ) { const descriptions = []; if (types.mutation) { descriptions.push(`mutation${plural ? 's' : ''}`); } + if (types.structuralVariant) { + descriptions.push(`structural variant${plural ? 's' : ''}`); + } if (types.cna) { descriptions.push(`copy number alteration${plural ? 's' : ''}`); } @@ -79,6 +82,7 @@ export default class AlterationFilterWarning extends React.Component< return [ this.props.store.oqlFilteredMutationsReport, this.props.store.oqlFilteredMolecularDataReport, + this.props.store.oqlFilteredStructuralVariantsReport, ]; } }, @@ -87,6 +91,7 @@ export default class AlterationFilterWarning extends React.Component< const vusTypes = { mutation: false, cna: false, + structuralVariant: false, }; if (this.props.mutationsTabModeSettings) { const report = this.props.store.mutationsReportByGene.result![ @@ -104,13 +109,19 @@ export default class AlterationFilterWarning extends React.Component< mutationReport.vusAndGermline.length; const cnaVusCount = this.props.store .oqlFilteredMolecularDataReport.result!.vus.length; - vusCount = mutationVusCount + cnaVusCount; + const structuralVariantVusCount = this.props.store + .oqlFilteredStructuralVariantsReport.result!.vus.length; + vusCount = + mutationVusCount + cnaVusCount + structuralVariantVusCount; if (mutationVusCount > 0) { vusTypes.mutation = true; } if (cnaVusCount > 0) { vusTypes.cna = true; } + if (structuralVariantVusCount > 0) { + vusTypes.structuralVariant = true; + } } if (vusCount > 0) { diff --git a/src/shared/components/mutationTable/column/ChromosomeColumnFormatter.tsx b/src/shared/components/mutationTable/column/ChromosomeColumnFormatter.tsx index 96510ff3965..476c6c9d296 100644 --- a/src/shared/components/mutationTable/column/ChromosomeColumnFormatter.tsx +++ b/src/shared/components/mutationTable/column/ChromosomeColumnFormatter.tsx @@ -4,12 +4,12 @@ import { Mutation } from 'cbioportal-ts-api-client'; * @author Selcuk Onur Sumer */ export default class ChromosomeColumnFormatter { - public static getSortValue(data: Mutation[]): number | null { - const chromosome = ChromosomeColumnFormatter.getData(data); + public static getSortValue(data: Pick[]): number | null { + const chromosome = this.getData(data); if (!chromosome) { return null; } else { - return ChromosomeColumnFormatter.extractSortValue(chromosome); + return this.extractSortValue(chromosome); } } @@ -31,7 +31,7 @@ export default class ChromosomeColumnFormatter { return value; } - public static getData(data: Mutation[]): string | null { + public static getData(data: Pick[]): string | null { if (data.length > 0) { return data[0].chr; } else { diff --git a/src/shared/components/oncoprint/DataUtils.spec.ts b/src/shared/components/oncoprint/DataUtils.spec.ts index 3d7ab7fe2fb..814ae26ef02 100644 --- a/src/shared/components/oncoprint/DataUtils.spec.ts +++ b/src/shared/components/oncoprint/DataUtils.spec.ts @@ -677,6 +677,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, } ); }); @@ -700,6 +701,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'missense_rec', disp_germ: false, + disp_structuralVariant: false, }, 'missense driver with no germline' ); @@ -723,6 +725,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'inframe', disp_germ: false, + disp_structuralVariant: false, }, 'inframe non-driver' ); @@ -746,6 +749,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'trunc', disp_germ: false, + disp_structuralVariant: false, }, 'truncating non-driver' ); @@ -755,7 +759,7 @@ describe('DataUtils', () => { mutationType: 'fusion', putativeDriver: false, molecularProfileAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, + AlterationTypeConstants.STRUCTURAL_VARIANT, } as AnnotatedExtendedAlteration, ]; assert.deepEqual( @@ -768,7 +772,7 @@ describe('DataUtils', () => { disp_mrna: undefined, disp_prot: undefined, disp_mut: undefined, - disp_fusion: true, + disp_structuralVariant: true, disp_germ: undefined, }, 'fusion non-driver' @@ -794,6 +798,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'amplification' ); @@ -816,6 +821,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'gain' ); @@ -839,6 +845,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'hetloss' ); @@ -862,6 +869,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'homdel' ); @@ -884,6 +892,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'diploid' ); @@ -910,6 +919,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'missense_rec', disp_germ: true, + disp_structuralVariant: false, }, 'missense driver with germline' ); @@ -933,6 +943,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'missense_rec', disp_germ: false, + disp_structuralVariant: false, }, 'missense driver without germline' ); @@ -966,6 +977,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'missense_rec', disp_germ: true, + disp_structuralVariant: false, }, 'missense driver with germline is stronger than missense passenger' ); @@ -997,6 +1009,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'trunc_rec', disp_germ: false, + disp_structuralVariant: false, }, 'trunc driver is stronger than missense passenger w germline' ); @@ -1021,6 +1034,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'high' ); @@ -1043,6 +1057,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'low' ); @@ -1066,6 +1081,7 @@ describe('DataUtils', () => { disp_prot: 'high', disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'high' ); @@ -1088,6 +1104,7 @@ describe('DataUtils', () => { disp_prot: 'low', disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'low' ); @@ -1118,6 +1135,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'trunc_rec', disp_germ: false, + disp_structuralVariant: false, }, 'truncating driver beats missense driver' ); @@ -1147,6 +1165,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'missense_rec', disp_germ: false, + disp_structuralVariant: false, }, 'missense driver beats truncating non-driver' ); @@ -1176,6 +1195,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: 'trunc', disp_germ: false, + disp_structuralVariant: false, }, 'truncating non-driver beats missense non-driver' ); @@ -1204,6 +1224,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'amplification beats gain' ); @@ -1231,6 +1252,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'homdel beats diploid' ); @@ -1263,6 +1285,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two homdels beats one amp' ); @@ -1295,6 +1318,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two amps beats one homdel' ); @@ -1328,6 +1352,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two downs beats one high' ); @@ -1360,6 +1385,7 @@ describe('DataUtils', () => { disp_prot: undefined, disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two ups beats one low' ); @@ -1393,6 +1419,7 @@ describe('DataUtils', () => { disp_prot: 'low', disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two downs beats one high' ); @@ -1425,6 +1452,7 @@ describe('DataUtils', () => { disp_prot: 'high', disp_mut: undefined, disp_germ: undefined, + disp_structuralVariant: false, }, 'two ups beats one low' ); @@ -1500,6 +1528,7 @@ describe('DataUtils', () => { disp_prot: 'low', disp_mut: 'trunc_rec', disp_germ: false, + disp_structuralVariant: false, } ); }); diff --git a/src/shared/components/oncoprint/DataUtils.ts b/src/shared/components/oncoprint/DataUtils.ts index eece631818f..5de3a86af21 100644 --- a/src/shared/components/oncoprint/DataUtils.ts +++ b/src/shared/components/oncoprint/DataUtils.ts @@ -4,6 +4,7 @@ import { CaseAggregatedData, CustomDriverNumericGeneMolecularData, ExtendedAlteration, + AlterationTypeConstants, } from '../../../pages/resultsView/ResultsViewPageStore'; import { ClinicalAttribute, @@ -79,7 +80,6 @@ type HeatmapCaseDatum = { export type OncoprintMutationType = | 'missense' | 'inframe' - | 'fusion' | 'promoter' | 'trunc' | 'splice' @@ -93,6 +93,7 @@ export enum OncoprintMutationTypeEnum { TRUNC = 'trunc', SPLICE = 'splice', OTHER = 'other', + STRUCTURAL_VARIANT = 'structuralVariant', } export function getOncoprintMutationType( @@ -108,7 +109,6 @@ export function getOncoprintMutationType( switch (simplifiedMutationType) { case 'missense': case 'inframe': - case 'fusion': case 'splice': case 'other': return simplifiedMutationType; @@ -159,12 +159,12 @@ export function fillGeneticTrackDatum( newDatum.trackLabel = trackLabel; newDatum.data = data; - let dispFusion = false; const dispCnaCounts: { [cnaEvent: string]: number } = {}; const dispMrnaCounts: { [mrnaEvent: string]: number } = {}; const dispProtCounts: { [protEvent: string]: number } = {}; const dispMutCounts: { [mutType: string]: number } = {}; const dispGermline: { [mutType: string]: boolean } = {}; + let structuralVariantCounts: number = 0; const caseInsensitiveGermlineMatch = new RegExp( MUTATION_STATUS_GERMLINE, 'i' @@ -173,7 +173,7 @@ export function fillGeneticTrackDatum( for (const event of data) { const molecularAlterationType = event.molecularProfileAlterationType; switch (molecularAlterationType) { - case 'COPY_NUMBER_ALTERATION': + case AlterationTypeConstants.COPY_NUMBER_ALTERATION: let oncoprintCnaType = cnaDataToString[ event.value as CustomDriverNumericGeneMolecularData['value'] @@ -188,43 +188,40 @@ export function fillGeneticTrackDatum( dispCnaCounts[oncoprintCnaType] += 1; } break; - case 'MRNA_EXPRESSION': + case AlterationTypeConstants.MRNA_EXPRESSION: if (event.alterationSubType) { const mrnaEvent = event.alterationSubType; dispMrnaCounts[mrnaEvent] = dispMrnaCounts[mrnaEvent] || 0; dispMrnaCounts[mrnaEvent] += 1; } break; - case 'PROTEIN_LEVEL': + case AlterationTypeConstants.PROTEIN_LEVEL: if (event.alterationSubType) { const protEvent = event.alterationSubType; dispProtCounts[protEvent] = dispProtCounts[protEvent] || 0; dispProtCounts[protEvent] += 1; } break; - case 'MUTATION_EXTENDED': + case AlterationTypeConstants.MUTATION_EXTENDED: let oncoprintMutationType = getOncoprintMutationType( event as Pick ); - if (oncoprintMutationType === 'fusion') { - dispFusion = true; - } else { - if (event.putativeDriver) { - oncoprintMutationType += '_rec'; - } - dispGermline[oncoprintMutationType] = - dispGermline[oncoprintMutationType] || - caseInsensitiveGermlineMatch.test(event.mutationStatus); - dispMutCounts[oncoprintMutationType] = - dispMutCounts[oncoprintMutationType] || 0; - dispMutCounts[oncoprintMutationType] += 1; + if (event.putativeDriver) { + oncoprintMutationType += '_rec'; } + dispGermline[oncoprintMutationType] = + dispGermline[oncoprintMutationType] || + caseInsensitiveGermlineMatch.test(event.mutationStatus); + dispMutCounts[oncoprintMutationType] = + dispMutCounts[oncoprintMutationType] || 0; + dispMutCounts[oncoprintMutationType] += 1; + break; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + structuralVariantCounts += 1; break; } } - if (dispFusion) { - newDatum.disp_fusion = true; - } + newDatum.disp_structuralVariant = structuralVariantCounts > 0; newDatum.disp_cna = selectDisplayValue(dispCnaCounts, cnaRenderPriority); newDatum.disp_mrna = selectDisplayValue(dispMrnaCounts, mrnaRenderPriority); newDatum.disp_prot = selectDisplayValue(dispProtCounts, protRenderPriority); diff --git a/src/shared/components/oncoprint/Oncoprint.tsx b/src/shared/components/oncoprint/Oncoprint.tsx index 27a5dca10d8..693a1c8f77e 100644 --- a/src/shared/components/oncoprint/Oncoprint.tsx +++ b/src/shared/components/oncoprint/Oncoprint.tsx @@ -16,6 +16,7 @@ import { CustomDriverNumericGeneMolecularData, AnnotatedMutation, ExtendedAlteration, + AnnotatedStructuralVariant, } from '../../../pages/resultsView/ResultsViewPageStore'; import './styles.scss'; import { ShapeParams } from 'oncoprintjs/dist/js/oncoprintshape'; @@ -81,6 +82,7 @@ export interface IGenericAssayHeatmapTrackDatum extends IBaseHeatmapTrackDatum { export type GeneticTrackDatum_Data = Pick< ExtendedAlteration & AnnotatedMutation & + AnnotatedStructuralVariant & CustomDriverNumericGeneMolecularData, | 'hugoGeneSymbol' | 'molecularProfileAlterationType' @@ -97,6 +99,7 @@ export type GeneticTrackDatum_Data = Pick< | 'entrezGeneId' | 'putativeDriver' | 'mutationStatus' + | 'eventInfo' >; export type GeneticTrackDatum_ProfiledIn = { @@ -118,7 +121,7 @@ export type GeneticTrackDatum = { disp_cna?: string; disp_mrna?: string; disp_prot?: string; - disp_fusion?: boolean; + disp_structuralVariant?: boolean; disp_germ?: boolean; }; diff --git a/src/shared/components/oncoprint/ResultsViewOncoprint.tsx b/src/shared/components/oncoprint/ResultsViewOncoprint.tsx index 12d6b94c1e9..35588425dc2 100644 --- a/src/shared/components/oncoprint/ResultsViewOncoprint.tsx +++ b/src/shared/components/oncoprint/ResultsViewOncoprint.tsx @@ -1623,6 +1623,7 @@ export default class ResultsViewOncoprint extends React.Component< promises: [ this.props.store.filteredAndAnnotatedMolecularData, this.props.store.filteredAndAnnotatedMutations, + this.props.store.filteredAndAnnotatedStructuralVariants, ], }); diff --git a/src/shared/components/oncoprint/ResultsViewOncoprintUtils.tsx b/src/shared/components/oncoprint/ResultsViewOncoprintUtils.tsx index 4ff7015fae3..433b4ca63e6 100644 --- a/src/shared/components/oncoprint/ResultsViewOncoprintUtils.tsx +++ b/src/shared/components/oncoprint/ResultsViewOncoprintUtils.tsx @@ -22,6 +22,7 @@ export const alterationTypeToProfiledForText: { COPY_NUMBER_ALTERATION: 'copy number alterations', MRNA_EXPRESSION: 'mRNA expression', PROTEIN_LEVEL: 'protein expression', + STRUCTURAL_VARIANT: 'structural variants', }; export function getAnnotatingProgressMessage( diff --git a/src/shared/components/oncoprint/SortUtils.ts b/src/shared/components/oncoprint/SortUtils.ts index c0c4b16e65b..06eecc1d530 100644 --- a/src/shared/components/oncoprint/SortUtils.ts +++ b/src/shared/components/oncoprint/SortUtils.ts @@ -135,8 +135,8 @@ export function getGeneticTrackSortComparator( function mandatoryHelper(d: GeneticTrackDatum): number[] { const vector = []; - // Test fusion - if (d.disp_fusion) { + // Test structural variant + if (d.disp_structuralVariant) { vector.push(0); } else { vector.push(1); diff --git a/src/shared/components/oncoprint/TooltipUtils.spec.ts b/src/shared/components/oncoprint/TooltipUtils.spec.ts index 631c5e76e4e..9083c71c956 100644 --- a/src/shared/components/oncoprint/TooltipUtils.spec.ts +++ b/src/shared/components/oncoprint/TooltipUtils.spec.ts @@ -11,6 +11,7 @@ import { AlterationTypeConstants, AnnotatedExtendedAlteration, AnnotatedMutation, + AnnotatedStructuralVariant, } from '../../../pages/resultsView/ResultsViewPageStore'; import $ from 'jquery'; import { MolecularProfile, Mutation } from 'cbioportal-ts-api-client'; @@ -195,10 +196,17 @@ describe('Oncoprint TooltipUtils', () => { ...props, } as AnnotatedExtendedAlteration; } - function makeFusion( - props: Partial - ): AnnotatedExtendedAlteration { - return makeMutation({ alterationSubType: 'fusion', ...props }); + function makeStructuralVariant( + props: Partial + ): AnnotatedStructuralVariant { + return { + hugoGeneSymbol: 'GENE', + site1HugoSymbol: 'GENE', + molecularProfileAlterationType: + AlterationTypeConstants.STRUCTURAL_VARIANT, + variantClass: 'fusion', + ...props, + } as AnnotatedStructuralVariant; } function makeCna( props: Partial @@ -1500,11 +1508,20 @@ describe('Oncoprint TooltipUtils', () => { ); }); it('single genetic alteration in single case - fusion', () => { - datum.data = [makeFusion({ proteinChange: 'PC1' })]; + datum.data = [ + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + ]; tooltipOutput = tooltip([datum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/Event Info: GENE-GENE2/g)! + .length, 1 ); assert.equal( @@ -1514,13 +1531,23 @@ describe('Oncoprint TooltipUtils', () => { ); datum.data = [ - makeFusion({ proteinChange: 'PC1' }), - makeFusion({ proteinChange: 'PC1' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), ]; tooltipOutput = tooltip([datum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/Event Info: GENE-GENE2/g)! + .length, 1 ); assert.equal( @@ -1751,18 +1778,33 @@ describe('Oncoprint TooltipUtils', () => { ); }); it('single genetic alteration across multiple cases - fusion', () => { - datum.data = [makeFusion({ proteinChange: 'PC1' })]; + datum.data = [ + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + ]; tooltipOutput = tooltip([datum, datum, datum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1\xa0\(3\)/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, + 1 + ); + assert.equal( + tooltipOutput + .text() + .match(/Event Info: GENE-GENE2\xa0\(3\)/g)!.length, 1 ); tooltipOutput = tooltip([datum, emptyDatum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1\xa0\(1\)/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, + 1 + ); + assert.equal( + tooltipOutput + .text() + .match(/Event Info: GENE-GENE2\xa0\(1\)/g)!.length, 1 ); }); @@ -1881,17 +1923,28 @@ describe('Oncoprint TooltipUtils', () => { }); it('multiple alterations of same type in single case - fusion', () => { datum.data = [ - makeFusion({ proteinChange: 'PC1' }), - makeFusion({ proteinChange: 'PC2' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), ]; tooltipOutput = tooltip([datum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, 1 ); assert.equal( - tooltipOutput.text().match(/GENE PC2/g)!.length, + tooltipOutput.text().match(/Event Info: GENE-GENE2/g)! + .length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/Event Info: GENE-GENE2/g)! + .length, 1 ); assert.equal( @@ -2009,28 +2062,44 @@ describe('Oncoprint TooltipUtils', () => { }); it('multiple alterations of same type across multiple cases - fusion', () => { datum.data = [ - makeFusion({ proteinChange: 'PC1' }), - makeFusion({ proteinChange: 'PC2' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), ]; tooltipOutput = tooltip([datum, datum, datum, datum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1\xa0\(4\)/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, 1 ); assert.equal( - tooltipOutput.text().match(/GENE PC2\xa0\(4\)/g)!.length, + tooltipOutput + .text() + .match(/Event Info: GENE-GENE2\xa0\(4\)/g)!.length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/GENE-GENE3\xa0\(4\)/g)!.length, 1 ); tooltipOutput = tooltip([datum, datum, emptyDatum, emptyDatum]); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE PC1\xa0\(2\)/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, 1 ); assert.equal( - tooltipOutput.text().match(/GENE PC2\xa0\(2\)/g)!.length, + tooltipOutput + .text() + .match(/Event Info: GENE-GENE2\xa0\(2\)/g)!.length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/GENE-GENE3\xa0\(2\)/g)!.length, 1 ); }); @@ -2148,8 +2217,14 @@ describe('Oncoprint TooltipUtils', () => { datum.data = [ makeMutation({ proteinChange: 'PC1' }), makeMutation({ proteinChange: 'PC2' }), - makeFusion({ proteinChange: 'fusion1' }), - makeFusion({ proteinChange: 'fusion2' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), makeCna({ value: 2 }), makeCna({ value: -2 }), makeMrna({ alterationSubType: 'high' }), @@ -2170,13 +2245,18 @@ describe('Oncoprint TooltipUtils', () => { 1 ); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE fusion1/g)!.length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, 1 ); assert.equal( - tooltipOutput.text().match(/GENE fusion2/g)!.length, + tooltipOutput.text().match(/Event Info: GENE-GENE2/g)! + .length, + 1 + ); + assert.equal( + tooltipOutput.text().match(/Event Info: GENE-GENE3/g)! + .length, 1 ); @@ -2215,8 +2295,14 @@ describe('Oncoprint TooltipUtils', () => { datum.data = [ makeMutation({ proteinChange: 'PC1' }), makeMutation({ proteinChange: 'PC2' }), - makeFusion({ proteinChange: 'fusion1' }), - makeFusion({ proteinChange: 'fusion2' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE2', + eventInfo: 'GENE-GENE2', + }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), makeCna({ value: 2 }), makeCna({ value: -2 }), makeMrna({ alterationSubType: 'high' }), @@ -2226,7 +2312,10 @@ describe('Oncoprint TooltipUtils', () => { const datum2 = Object.assign({}, emptyDatum, { data: [ makeMutation({ proteinChange: 'PC1' }), - makeFusion({ proteinChange: 'fusion2' }), + makeStructuralVariant({ + site2HugoSymbol: 'GENE3', + eventInfo: 'GENE-GENE3', + }), makeCna({ value: 1 }), makeMrna({ alterationSubType: 'low' }), makeProt({ alterationSubType: 'high' }), @@ -2253,15 +2342,20 @@ describe('Oncoprint TooltipUtils', () => { 1 ); - assert.equal(tooltipOutput.text().match(/Fusion:/g)!.length, 1); assert.equal( - tooltipOutput.text().match(/GENE fusion1\xa0\(3\)/g)! - .length, + tooltipOutput.text().match(/Structural Variant:/g)!.length, 1 ); assert.equal( - tooltipOutput.text().match(/GENE fusion2\xa0\(4\)/g)! - .length, + tooltipOutput + .text() + .match(/Event Info: GENE-GENE2\xa0\(3\)/g)!.length, + 1 + ); + assert.equal( + tooltipOutput + .text() + .match(/Event Info: GENE-GENE3\xa0\(4\)/g)!.length, 1 ); diff --git a/src/shared/components/oncoprint/TooltipUtils.ts b/src/shared/components/oncoprint/TooltipUtils.ts index 6b4dece2d77..5a01b944e67 100644 --- a/src/shared/components/oncoprint/TooltipUtils.ts +++ b/src/shared/components/oncoprint/TooltipUtils.ts @@ -6,6 +6,7 @@ import { GenePanelData, MolecularProfile, Mutation, + StructuralVariant, } from 'cbioportal-ts-api-client'; import client from 'shared/api/cbioportalClientInstance'; import { @@ -465,7 +466,7 @@ export function makeGeneticTrackTooltip( alterationTypesInQuery?: string[] ) { // TODO: all the data here is old API data - function listOfMutationOrFusionDataToHTML( + function listOfMutationDataToHTML( data: any[], multipleSamplesUnderMouse: boolean ) { @@ -539,6 +540,53 @@ export function makeGeneticTrackTooltip( } ); } + function listOfStructuralVariantDataToHTML( + data: any[], + multipleSamplesUnderMouse: boolean + ) { + const countsMap = new ListIndexedMapOfCounts(); + for (const d of data) { + countsMap.increment( + d.site1HugoSymbol, + d.site2HugoSymbol, + d.eventInfo, + d.variantClass, + d.oncokb_oncogenic + ); + } + return countsMap + .entries() + .map( + ({ + key: [ + site1HugoSymbol, + site2HugoSymbol, + eventInfo, + variantClass, + oncokb_oncogenic, + ], + value: count, + }) => { + var ret = $('').addClass('nobreak'); + ret.append( + `${site1HugoSymbol}${ + site2HugoSymbol ? '-' + site2HugoSymbol : '' + }, ${variantClass}, Event Info: ${eventInfo}` + ); + if (oncokb_oncogenic) { + ret.append( + `` + ); + } + + // finally, add the number of samples with this, if multipleSamplesUnderMouse + if (multipleSamplesUnderMouse) { + ret.append(` (${count})`); + } + return ret; + } + ); + } function listOfCNAToHTML(data: any[], multipleSamplesUnderMouse: boolean) { const countsMap = new ListIndexedMapOfCounts(); for (const d of data) { @@ -634,6 +682,7 @@ export function makeGeneticTrackTooltip( return function( dataUnderMouse: Pick< GeneticTrackDatum, + | 'trackLabel' | 'data' | 'profiled_in' | 'sample' @@ -652,7 +701,7 @@ export function makeGeneticTrackTooltip( let cna: any[] = []; let mrna: any[] = []; let prot: any[] = []; - let fusions: any[] = []; + let structuralVariants: any[] = []; // collect all data under mouse for (const d of dataUnderMouse) { for (let i = 0; i < d.data.length; i++) { @@ -661,7 +710,7 @@ export function makeGeneticTrackTooltip( datum.molecularProfileAlterationType; const hugoGeneSymbol = datum.hugoGeneSymbol; switch (molecularAlterationType) { - case 'MUTATION_EXTENDED': + case AlterationTypeConstants.MUTATION_EXTENDED: { const tooltip_datum: any = {}; tooltip_datum.hugo_gene_symbol = hugoGeneSymbol; tooltip_datum.amino_acid_change = datum.proteinChange; @@ -682,12 +731,28 @@ export function makeGeneticTrackTooltip( if (oncokb_oncogenic) { tooltip_datum.oncokb_oncogenic = oncokb_oncogenic; } - (datum.alterationSubType === 'fusion' - ? fusions - : mutations - ).push(tooltip_datum); + mutations.push(tooltip_datum); + break; + } + case AlterationTypeConstants.STRUCTURAL_VARIANT: { + const tooltip_datum: any = {}; + const structuralVariantDatum: StructuralVariant = datum as any; + tooltip_datum.site1HugoSymbol = + structuralVariantDatum.site1HugoSymbol; + tooltip_datum.site2HugoSymbol = + structuralVariantDatum.site2HugoSymbol; + tooltip_datum.eventInfo = + structuralVariantDatum.eventInfo; + tooltip_datum.variantClass = + structuralVariantDatum.variantClass; + const oncokb_oncogenic = datum.oncoKbOncogenic; + if (oncokb_oncogenic) { + tooltip_datum.oncokb_oncogenic = oncokb_oncogenic; + } + structuralVariants.push(tooltip_datum); break; - case 'COPY_NUMBER_ALTERATION': + } + case AlterationTypeConstants.COPY_NUMBER_ALTERATION: if ( disp_cna.hasOwnProperty( datum.value as CustomDriverNumericGeneMolecularData['value'] @@ -714,11 +779,12 @@ export function makeGeneticTrackTooltip( cna.push(tooltip_datum); } break; - case 'MRNA_EXPRESSION': - case 'PROTEIN_LEVEL': + case AlterationTypeConstants.MRNA_EXPRESSION: + case AlterationTypeConstants.PROTEIN_LEVEL: let direction = datum.alterationSubType; let array = - molecularAlterationType === 'MRNA_EXPRESSION' + molecularAlterationType === + AlterationTypeConstants.MRNA_EXPRESSION ? mrna : prot; if (direction === 'high') { @@ -736,23 +802,23 @@ export function makeGeneticTrackTooltip( } } } - if (fusions.length > 0) { - ret.append('Fusion: '); - fusions = listOfMutationOrFusionDataToHTML( - fusions, + if (structuralVariants.length > 0) { + ret.append('Structural Variant: '); + structuralVariants = listOfStructuralVariantDataToHTML( + structuralVariants, dataUnderMouse.length > 1 ); - for (var i = 0; i < fusions.length; i++) { + for (var i = 0; i < structuralVariants.length; i++) { if (i > 0) { ret.append(','); } - ret.append(fusions[i]); + ret.append(structuralVariants[i]); } ret.append('
'); } if (mutations.length > 0) { ret.append('Mutation: '); - mutations = listOfMutationOrFusionDataToHTML( + mutations = listOfMutationDataToHTML( mutations, dataUnderMouse.length > 1 ); @@ -898,10 +964,21 @@ export function makeGeneticTrackTooltip( ); ret.append('
'); } else if (noneProfiledCount === dataUnderMouse.length) { - ret.append( - 'Not profiled in selected molecular profiles.' + - (dataUnderMouse.length > 1 ? ` (${noneProfiledCount})` : '') - ); + if ( + profiledGenePanelEntries.length || + notProfiledGenePanelEntries.length + ) { + ret.append( + `${dataUnderMouse[0].trackLabel} is not in the gene panels of the selected molecular profiles, but detected as a fusion partner` + ); + } else { + ret.append( + 'Not profiled in selected molecular profiles.' + + (dataUnderMouse.length > 1 + ? ` (${noneProfiledCount})` + : '') + ); + } ret.append('
'); } else { if (profiledInEntries.length) { diff --git a/src/shared/components/oncoprint/geneticrules.ts b/src/shared/components/oncoprint/geneticrules.ts index bc6e21e7e21..be5407580a8 100644 --- a/src/shared/components/oncoprint/geneticrules.ts +++ b/src/shared/components/oncoprint/geneticrules.ts @@ -14,7 +14,7 @@ import { CNA_COLOR_HOMDEL, MRNA_COLOR_HIGH, MRNA_COLOR_LOW, - MUT_COLOR_FUSION, + STRUCTURAL_VARIANT_COLOR, MUT_COLOR_GERMLINE, MUT_COLOR_INFRAME, MUT_COLOR_INFRAME_PASSENGER, @@ -32,7 +32,7 @@ import { // Feed this in as const MUTATION_LEGEND_ORDER = 0; -const FUSION_LEGEND_ORDER = 1; +const STRUCTURAL_VARIANT_LEGEND_ORDER = 1; const GERMLINE_LEGEND_ORDER = 2; const AMP_LEGEND_ORDER = 10; const GAIN_LEGEND_ORDER = 11; @@ -57,7 +57,7 @@ enum ShapeId { protHighRectangle = 'protHighRectangle', protLowRectangle = 'protLowRectangle', - fusionRectangle = 'fusionRectangle', + structuralVariantRectangle = 'structuralVariantRectangle', germlineRectangle = 'germlineRectangle', @@ -155,9 +155,9 @@ const shapeBank = { height: 20, z: 4, }, - [ShapeId.fusionRectangle]: { + [ShapeId.structuralVariantRectangle]: { type: 'rectangle', - fill: MUT_COLOR_FUSION, + fill: STRUCTURAL_VARIANT_COLOR, x: 0, y: 20, width: 100, @@ -350,13 +350,13 @@ const non_mutation_rule_params: GeneticAlterationRuleParams = { legend_order: PROT_LOW_LEGEND_ORDER, }, }, - // fusion - disp_fusion: { - // tall inset purple rectangle for fusion + // structural variant + disp_structuralVariant: { + // tall inset purple rectangle for structural variant true: { - shapes: [shapeBank[ShapeId.fusionRectangle]], - legend_label: 'Fusion', - legend_order: FUSION_LEGEND_ORDER, + shapes: [shapeBank[ShapeId.structuralVariantRectangle]], + legend_label: 'Structural Variant', + legend_order: STRUCTURAL_VARIANT_LEGEND_ORDER, }, }, }, diff --git a/src/shared/components/oncoprint/tabularDownload.ts b/src/shared/components/oncoprint/tabularDownload.ts index e10217716c7..4bc59cd7788 100644 --- a/src/shared/components/oncoprint/tabularDownload.ts +++ b/src/shared/components/oncoprint/tabularDownload.ts @@ -32,7 +32,7 @@ export function getTabularDownloadData( MUTATIONS: {}, MRNA: {}, PROTEIN: {}, - FUSION: {}, + STRUCTURAL_VARIANT: {}, }; //Create maps for genetic data @@ -74,8 +74,8 @@ export function getTabularDownloadData( low: 'Protein Low', high: 'Protein High', }; - const fusionMap: any = { - true: 'Fusion', + const structuralVariantMap: any = { + true: 'Structural Variant', }; //Add genetic data @@ -95,8 +95,8 @@ export function getTabularDownloadData( if (oncoprintData.PROTEIN[currentGeneName] === undefined) { oncoprintData.PROTEIN[currentGeneName] = {}; } - if (oncoprintData.FUSION[currentGeneName] === undefined) { - oncoprintData.FUSION[currentGeneName] = {}; + if (oncoprintData.STRUCTURAL_VARIANT[currentGeneName] === undefined) { + oncoprintData.STRUCTURAL_VARIANT[currentGeneName] = {}; } //Iterate over all patients/samples of the track and add them to oncoprintData for (const geneticTrackDatum of currentTrackData) { @@ -108,7 +108,7 @@ export function getTabularDownloadData( oncoprintData.MUTATIONS[currentGeneName][id] = ''; oncoprintData.MRNA[currentGeneName][id] = ''; oncoprintData.PROTEIN[currentGeneName][id] = ''; - oncoprintData.FUSION[currentGeneName][id] = ''; + oncoprintData.STRUCTURAL_VARIANT[currentGeneName][id] = ''; if (geneticTrackDatum.disp_cna !== undefined) { oncoprintData.CNA[currentGeneName][id] = cnaMap[ geneticTrackDatum.disp_cna @@ -116,12 +116,16 @@ export function getTabularDownloadData( ? cnaMap[geneticTrackDatum.disp_cna] : geneticTrackDatum.disp_cna; } - if (geneticTrackDatum.disp_fusion !== undefined) { - oncoprintData.FUSION[currentGeneName][id] = fusionMap[ - geneticTrackDatum.disp_fusion + '' + if (geneticTrackDatum.disp_structuralVariant !== undefined) { + oncoprintData.STRUCTURAL_VARIANT[currentGeneName][ + id + ] = structuralVariantMap[ + geneticTrackDatum.disp_structuralVariant + '' ] - ? fusionMap[geneticTrackDatum.disp_fusion + ''] - : geneticTrackDatum.disp_fusion; + ? structuralVariantMap[ + geneticTrackDatum.disp_structuralVariant + '' + ] + : geneticTrackDatum.disp_structuralVariant; } if (geneticTrackDatum.disp_mrna !== undefined) { oncoprintData.MRNA[currentGeneName][id] = mrnaMap[ diff --git a/src/shared/components/query/DataTypePrioritySelector.spec.tsx b/src/shared/components/query/DataTypePrioritySelector.spec.tsx deleted file mode 100644 index c9942f16a14..00000000000 --- a/src/shared/components/query/DataTypePrioritySelector.spec.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { assert } from 'chai'; -import React from 'react'; -import expect from 'expect'; -import expectJSX from 'expect-jsx'; -import { checkBoxes } from './DataTypePrioritySelector'; -import { stringListToSet } from 'cbioportal-frontend-commons'; -import { QueryStore } from './QueryStore'; - -expect.extend(expectJSX); - -describe('DataTypePrioritySelector', () => { - describe('checkBoxes', () => { - it('shows the right buttons when theres mutation and cna profiles available', () => { - const buttonLabels = stringListToSet( - checkBoxes({ mutation: true, cna: true }, {} as QueryStore).map( - x => x.props.label - ) - ); - assert.deepEqual( - buttonLabels, - stringListToSet(['Mutation', 'Copy number alterations']) - ); - }); - it('shows the right buttons when theres just mutation profiles available', () => { - const buttonLabels = stringListToSet( - checkBoxes( - { mutation: true, cna: false }, - {} as QueryStore - ).map(x => x.props.label) - ); - assert.deepEqual(buttonLabels, stringListToSet(['Mutation'])); - }); - it('shows the right buttons when theres just cna profiles available', () => { - const buttonLabels = stringListToSet( - checkBoxes( - { mutation: false, cna: true }, - {} as QueryStore - ).map(x => x.props.label) - ); - assert.deepEqual( - buttonLabels, - stringListToSet(['Copy number alterations']) - ); - }); - }); -}); diff --git a/src/shared/components/query/DataTypePrioritySelector.tsx b/src/shared/components/query/DataTypePrioritySelector.tsx index 5a984c3af62..77452051d2d 100644 --- a/src/shared/components/query/DataTypePrioritySelector.tsx +++ b/src/shared/components/query/DataTypePrioritySelector.tsx @@ -2,40 +2,60 @@ import * as _ from 'lodash'; import * as React from 'react'; import styles from './styles/styles.module.scss'; import { QueryStore, QueryStoreComponent } from './QueryStore'; -import { toJS } from 'mobx'; import { observer } from 'mobx-react'; -import { FlexRow, FlexCol } from '../flexbox/FlexBox'; +import { FlexRow } from '../flexbox/FlexBox'; import SectionHeader from '../sectionHeader/SectionHeader'; -import { AlterationTypeConstants } from '../../../pages/resultsView/ResultsViewPageStore'; import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicator'; -import { MolecularProfile } from 'cbioportal-ts-api-client'; +import { MakeMobxView } from '../MobxView'; +import { remoteData } from 'cbioportal-frontend-commons'; +import { getMolecularProfileOptions } from './QueryStoreUtils'; @observer export default class DataTypePrioritySelector extends QueryStoreComponent< {}, {} > { + readonly molecularProfileCategorySet = remoteData({ + await: () => [this.store.groupedMolecularProfilesByType], + invoke: () => { + return Promise.resolve( + getMolecularProfileOptions( + this.store.groupedMolecularProfilesByType.result + ) + ); + }, + default: [], + }); + + readonly flexRowContent = MakeMobxView({ + await: () => [this.molecularProfileCategorySet], + render: () => { + return ( +
+ {this.molecularProfileCategorySet.result.map(option => { + return ( + + ); + })} +
+ ); + }, + renderPending: () => , + renderError: () => ( + + Error loading profiles for selected studies. + + ), + }); + render() { if (!this.store.isVirtualStudyQuery) return null; - let flexRowContents: JSX.Element[] = []; - flexRowContents.push( - - ); - if (this.store.profileAvailability.isError) { - flexRowContents.push( - - Error loading profiles for selected studies. - - ); - } else if (this.store.profileAvailability.isComplete) { - flexRowContents = flexRowContents.concat( - checkBoxes(this.store.profileAvailability.result, this.store) - ); - } return ( Select Molecular Profiles: - {flexRowContents} + {this.flexRowContent.component} ); } @@ -54,51 +74,31 @@ export default class DataTypePrioritySelector extends QueryStoreComponent< export const DataTypePriorityCheckBox = observer( (props: { label: string; - state: 'mutation' | 'cna'; + id: string; + profileTypes: string[]; store: QueryStore; - dataTest: string; - }) => ( - - ) -); + }) => { + let isSelected = true; + props.profileTypes.forEach(profileType => { + isSelected = + isSelected && props.store.isProfileTypeSelected(profileType); + }); -export function checkBoxes( - availability: { mutation: boolean; cna: boolean }, - store: QueryStore -): JSX.Element[] { - let buttons = []; - if (availability.mutation) { - buttons.push( - - ); - } - if (availability.cna) { - buttons.push( - + return ( + ); } - return buttons; -} +); diff --git a/src/shared/components/query/MolecularProfileSelector.tsx b/src/shared/components/query/MolecularProfileSelector.tsx index 7f1e71e6971..e71915e4ef5 100644 --- a/src/shared/components/query/MolecularProfileSelector.tsx +++ b/src/shared/components/query/MolecularProfileSelector.tsx @@ -4,12 +4,12 @@ import { MolecularProfile } from 'cbioportal-ts-api-client'; import FontAwesome from 'react-fontawesome'; import styles from './styles/styles.module.scss'; import { observer } from 'mobx-react'; -import classNames from 'classnames'; import { FlexRow } from '../flexbox/FlexBox'; import { QueryStoreComponent } from './QueryStore'; import { DefaultTooltip } from 'cbioportal-frontend-commons'; import SectionHeader from '../sectionHeader/SectionHeader'; import AppConfig from 'appConfig'; +import { getSuffixOfMolecularProfile } from 'shared/lib/molecularProfileUtils'; @observer export default class MolecularProfileSelector extends QueryStoreComponent< @@ -27,7 +27,7 @@ export default class MolecularProfileSelector extends QueryStoreComponent< Select Genomic Profiles: @@ -36,6 +36,10 @@ export default class MolecularProfileSelector extends QueryStoreComponent< data-test="molecularProfileSelector" > {this.renderGroup('MUTATION_EXTENDED', 'Mutation')} + {this.renderGroup( + 'STRUCTURAL_VARIANT', + 'Structural Variant' + )} {this.renderGroup('COPY_NUMBER_ALTERATION', 'Copy Number')} {this.showGSVA && this.renderGroup('GENESET_SCORE', 'GSVA scores')} @@ -47,8 +51,10 @@ export default class MolecularProfileSelector extends QueryStoreComponent< 'Protein/phosphoprotein level' )} {!!( - this.store.molecularProfiles.isComplete && - !this.store.molecularProfiles.result.length + this.store.molecularProfilesInSelectedStudies + .isComplete && + !this.store.molecularProfilesInSelectedStudies.result + .length ) && ( No Genomic Profiles available for this Cancer Study @@ -117,12 +123,12 @@ export default class MolecularProfileSelector extends QueryStoreComponent< let profiles = this.store.getFilteredProfiles(molecularAlterationType); if (!profiles.length) return null; - let groupProfileIds = profiles.map( - profile => profile.molecularProfileId + const isGroupSelected = _.some(profiles, profile => + this.store.isProfileTypeSelected( + getSuffixOfMolecularProfile(profile) + ) ); - let groupIsSelected = - _.intersection(this.store.selectedProfileIds, groupProfileIds) - .length > 0; + let output: JSX.Element[] = []; if (profiles.length > 1 && !this.store.forDownloadTab) @@ -132,7 +138,7 @@ export default class MolecularProfileSelector extends QueryStoreComponent< profile={profiles[0]} type="checkbox" label={`${groupLabel}. Select one of the profiles below:`} - checked={groupIsSelected} + checked={isGroupSelected} isGroupToggle={true} /> ); @@ -146,10 +152,13 @@ export default class MolecularProfileSelector extends QueryStoreComponent< ? 'radio' : 'checkbox' } - label={profile.name} - checked={_.includes( - this.store.selectedProfileIds, - profile.molecularProfileId + label={ + profile.molecularAlterationType === 'STRUCTURAL_VARIANT' + ? groupLabel + : profile.name + } + checked={this.store.isProfileTypeSelected( + getSuffixOfMolecularProfile(profile) )} isGroupToggle={false} /> @@ -169,7 +178,7 @@ export default class MolecularProfileSelector extends QueryStoreComponent< if (this.store.forDownloadTab) return output; - if (groupIsSelected && molecularAlterationType == 'MRNA_EXPRESSION') { + if (isGroupSelected && molecularAlterationType == 'MRNA_EXPRESSION') { output.push(
Enter a z-score threshold{' '} @@ -189,7 +198,7 @@ export default class MolecularProfileSelector extends QueryStoreComponent< ); } - if (groupIsSelected && molecularAlterationType == 'PROTEIN_LEVEL') { + if (isGroupSelected && molecularAlterationType == 'PROTEIN_LEVEL') { output.push(
Enter a z-score threshold{' '} diff --git a/src/shared/components/query/QueryStore.ts b/src/shared/components/query/QueryStore.ts index 8a296e81a77..47dcbcf9b5f 100644 --- a/src/shared/components/query/QueryStore.ts +++ b/src/shared/components/query/QueryStore.ts @@ -29,11 +29,7 @@ import { } from 'cbioportal-frontend-commons'; import { labelMobxPromises, cached, debounceAsync } from 'mobxpromise'; import internalClient from '../../api/cbioportalInternalClientInstance'; -import { - MUTCommand, - SingleGeneQuery, - SyntaxError, -} from '../../lib/oql/oql-parser'; +import { SingleGeneQuery, SyntaxError } from '../../lib/oql/oql-parser'; import { parseOQLQuery } from '../../lib/oql/oqlfilter'; import memoize from 'memoize-weak-decorator'; import AppConfig from 'appConfig'; @@ -42,11 +38,7 @@ import URL from 'url'; import { buildCBioPortalPageUrl, redirectToStudyView } from '../../api/urls'; import StudyListLogic from './StudyListLogic'; import chunkMapReduce from 'shared/lib/chunkMapReduce'; -import { - currentQueryParams, - profileAvailability, - categorizedSamplesCount, -} from './QueryStoreUtils'; +import { currentQueryParams, categorizedSamplesCount } from './QueryStoreUtils'; import getOverlappingStudies from '../../lib/getOverlappingStudies'; import MolecularProfilesInStudyCache from '../../cache/MolecularProfilesInStudyCache'; @@ -72,27 +64,24 @@ import { ResultsViewURLQuery, ResultsViewURLQueryEnum, } from 'pages/resultsView/ResultsViewURLWrapper'; -import { getFilteredCustomCaseSets } from './CaseSetSelectorUtils'; -import { - REFERENCE_GENOME, - isMixedReferenceGenome, -} from 'shared/lib/referenceGenomeUtils'; +import { isMixedReferenceGenome } from 'shared/lib/referenceGenomeUtils'; +import { getSuffixOfMolecularProfile } from 'shared/lib/molecularProfileUtils'; // interface for communicating export type CancerStudyQueryUrlParams = { cancer_study_id: string; cancer_study_list?: string; - genetic_profile_ids_PROFILE_MUTATION_EXTENDED: string; - genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION: string; - genetic_profile_ids_PROFILE_MRNA_EXPRESSION: string; - genetic_profile_ids_PROFILE_METHYLATION: string; - genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION: string; - genetic_profile_ids_PROFILE_GENESET_SCORE: string; - genetic_profile_ids_PROFILE_GENERIC_ASSAY: string; + genetic_profile_ids_PROFILE_MUTATION_EXTENDED?: string; + genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION?: string; + genetic_profile_ids_PROFILE_MRNA_EXPRESSION?: string; + genetic_profile_ids_PROFILE_METHYLATION?: string; + genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION?: string; + genetic_profile_ids_PROFILE_GENESET_SCORE?: string; + genetic_profile_ids_PROFILE_GENERIC_ASSAY?: string; Z_SCORE_THRESHOLD: string; RPPA_SCORE_THRESHOLD: string; - data_priority: '0' | '1' | '2'; - profileFilter: '0' | '1' | '2'; + data_priority?: '0' | '1' | '2'; + profileFilter: string; case_set_id: string; case_ids: string; gene_list: string; @@ -122,34 +111,6 @@ export function normalizeQuery(geneQuery: string) { .toUpperCase(); } -export type CancerStudyQueryParams = Pick< - QueryStore, - | 'searchText' - | 'selectableSelectedStudyIds' - | 'dataTypePriority' - | 'selectedProfileIds' - | 'zScoreThreshold' - | 'rppaScoreThreshold' - | 'selectedSampleListId' - | 'caseIds' - | 'caseIdsMode' - | 'geneQuery' - | 'genesetQuery' ->; -export const QueryParamsKeys: (keyof CancerStudyQueryParams)[] = [ - 'searchText', - 'selectableSelectedStudyIds', - 'dataTypePriority', - 'selectedProfileIds', - 'zScoreThreshold', - 'rppaScoreThreshold', - 'selectedSampleListId', - 'caseIds', - 'caseIdsMode', - 'geneQuery', - 'genesetQuery', -]; - type GenesetId = string; export enum Focus { @@ -274,10 +235,6 @@ export class QueryStore { return Object.keys(ret); } - @computed get stateToSerialize() { - return _.pick(this, QueryParamsKeys); - } - @computed get onlyOneReferenceGenome() { const referenceGenomes = _.uniq( @@ -351,43 +308,142 @@ export class QueryStore { return this._defaultSelectedIds; } - @observable dataTypePriority = { mutation: true, cna: true }; + @observable private profileFilterSet?: ObservableMap; - // molecular profile ids - @observable.ref private _selectedProfileIds?: ReadonlyArray< - string - > = undefined; // user selection - @computed get selectedProfileIds(): ReadonlyArray { - let selectedIds; + dataTypePriorityFromUrl?: string; + profileIdsFromUrl?: string[]; + profileFilterSetFromUrl?: string[]; - if (this._selectedProfileIds !== undefined) { - selectedIds = this._selectedProfileIds; - } else { - // compute default selection - const altTypes: MolecularProfile['molecularAlterationType'][] = [ - 'MUTATION_EXTENDED', - 'COPY_NUMBER_ALTERATION', - ]; - selectedIds = []; - for (let altType of altTypes) { - let profiles = this.getFilteredProfiles(altType); - if (profiles.length) - selectedIds.push(profiles[0].molecularProfileId); + @computed get selectedProfileIdSet() { + let selectedIdSet: { [is: string]: boolean } = {}; + if (this.groupedMolecularProfilesByType.isComplete) { + const groupedMolecularProfilesByType = this + .groupedMolecularProfilesByType.result; + if (this.profileFilterSet === undefined) { + if (!this.studiesHaveChangedSinceInitialization) { + if (!_.isEmpty(this.profileFilterSetFromUrl)) { + this.profileFilterSetFromUrl!.forEach(profileFilter => { + if (groupedMolecularProfilesByType[profileFilter]) { + selectedIdSet[profileFilter] = true; + } + }); + } else if (!_.isEmpty(this.profileIdsFromUrl)) { + _.chain(this.profileIdsFromUrl) + .reduce((acc: MolecularProfile[], profileId) => { + const molecularProfile = this + .dict_molecularProfileId_molecularProfile[ + profileId + ]; + if (molecularProfile) { + acc.push(molecularProfile); + if ( + molecularProfile.molecularAlterationType === + AlterationTypeConstants.MUTATION_EXTENDED + ) { + acc = acc.concat( + this.getFilteredProfiles( + 'STRUCTURAL_VARIANT' + ) + ); + } + } + return acc; + }, []) + .forEach(profile => { + selectedIdSet[ + getSuffixOfMolecularProfile(profile) + ] = true; + }) + .value(); + } else { + const altTypes: MolecularProfile['molecularAlterationType'][] = []; + switch (this.dataTypePriorityFromUrl) { + default: + case '0': + altTypes.push('MUTATION_EXTENDED'); + altTypes.push('STRUCTURAL_VARIANT'); + altTypes.push('COPY_NUMBER_ALTERATION'); + break; + case '1': + altTypes.push('MUTATION_EXTENDED'); + altTypes.push('STRUCTURAL_VARIANT'); + break; + case '2': + altTypes.push('COPY_NUMBER_ALTERATION'); + break; + } + + let profiles = _.flatMap(altTypes, altType => + this.getFilteredProfiles(altType) + ); + + profiles.forEach(profile => { + selectedIdSet[ + getSuffixOfMolecularProfile(profile) + ] = true; + }); + } + } else { + const altTypes: MolecularProfile['molecularAlterationType'][] = [ + 'MUTATION_EXTENDED', + 'STRUCTURAL_VARIANT', + 'COPY_NUMBER_ALTERATION', + ]; + let profiles = _.flatMap(altTypes, altType => + this.getFilteredProfiles(altType) + ); + + profiles.forEach(profile => { + selectedIdSet[ + getSuffixOfMolecularProfile(profile) + ] = true; + }); + } + } else { + selectedIdSet = _.fromPairs(this.profileFilterSet.toJSON()); } } + return selectedIdSet; + } + + // used when single study is selected + @action selectMolecularProfile( + profile: MolecularProfile, + checked: boolean + ) { + let groupProfiles = this.getFilteredProfiles( + profile.molecularAlterationType + ); + + if (this.profileFilterSet === undefined) { + this.profileFilterSet = observable.map(this.selectedProfileIdSet); + } - // download tab only allows one selected profile - if (this.forDownloadTab) return selectedIds.slice(0, 1); + groupProfiles.forEach(profile => + this.profileFilterSet!.delete(getSuffixOfMolecularProfile(profile)) + ); - // query tab only allows selecting profiles with showProfileInAnalysisTab=true - return selectedIds.filter(id => { - let profile = this.dict_molecularProfileId_molecularProfile[id]; - return profile && profile.showProfileInAnalysisTab; - }); + if (checked) { + this.profileFilterSet!.set( + getSuffixOfMolecularProfile(profile), + true + ); + } } - set selectedProfileIds(value) { - this._selectedProfileIds = value; + // used when multi-study is selected + @action setProfileTypes(profileTypes: string[], checked: boolean) { + if (this.profileFilterSet === undefined) { + this.profileFilterSet = observable.map(this.selectedProfileIdSet); + } + + profileTypes.forEach(profileType => { + if (checked) { + this.profileFilterSet!.set(profileType, true); + } else { + this.profileFilterSet!.delete(profileType); + } + }); } @observable zScoreThreshold: string = '2.0'; @@ -851,27 +907,6 @@ export class QueryStore { default: [], }); - readonly molecularProfiles = remoteData({ - invoke: async () => { - if (this.physicalStudyIdsInSelection.length === 1) { - return await client.getAllMolecularProfilesInStudyUsingGET({ - studyId: this.physicalStudyIdsInSelection[0], - }); - } else { - return []; - } - }, - default: [], - onResult: () => { - if ( - !this.initiallySelected.profileIds || - this.studiesHaveChangedSinceInitialization - ) { - this._selectedProfileIds = undefined; - } - }, - }); - readonly molecularProfilesInSelectedStudies = remoteData< MolecularProfile[] >({ @@ -886,6 +921,14 @@ export class QueryStore { return _.flatten(profiles.map(d => (d.data ? d.data : []))); }, default: [], + onResult: () => { + if ( + !this.initiallySelected.profileIds || + this.studiesHaveChangedSinceInitialization + ) { + this.profileFilterSet = undefined; + } + }, }); readonly sampleListInSelectedStudies = remoteData({ @@ -919,32 +962,20 @@ export class QueryStore { }, }); - readonly profileAvailability = remoteData<{ - mutation: boolean; - cna: boolean; - }>({ + readonly groupedMolecularProfilesByType = remoteData({ await: () => [this.molecularProfilesInSelectedStudies], - invoke: () => { - return Promise.resolve( - profileAvailability( - this.molecularProfilesInSelectedStudies.result! + invoke: async () => { + return _.chain(this.molecularProfilesInSelectedStudies.result) + .filter( + molecularProfile => + molecularProfile.showProfileInAnalysisTab ) - ); - }, - default: { - mutation: false, - cna: false, - }, - onResult: () => { - if ( - !this.initiallySelected.sampleListId || - this.studiesHaveChangedSinceInitialization - ) { - this.dataTypePriority = profileAvailability( - this.molecularProfilesInSelectedStudies.result! - ); - } + .groupBy(molecularProfile => + getSuffixOfMolecularProfile(molecularProfile) + ) + .value(); }, + default: {}, }); readonly profiledSamplesCount = remoteData<{ @@ -1449,46 +1480,11 @@ export class QueryStore { } } - // DATA TYPE PRIORITY - - private calculateDataTypePriorityCode(dataTypePriority: { - mutation: boolean; - cna: boolean; - }): '0' | '1' | '2' { - let { mutation, cna } = dataTypePriority; - if (mutation && cna) return '0'; - if (mutation) return '1'; - if (cna) return '2'; - - return '0'; - } - - set dataTypePriorityCode(code: '0' | '1' | '2') { - switch (code) { - default: - case '0': - this.dataTypePriority = { mutation: true, cna: true }; - break; - case '1': - this.dataTypePriority = { mutation: true, cna: false }; - break; - case '2': - this.dataTypePriority = { mutation: false, cna: true }; - break; - } - } - - @computed get dataTypePriorityCode(): '0' | '1' | '2' { - return this.calculateDataTypePriorityCode(this.dataTypePriority); - } - // MOLECULAR PROFILE - @computed get dict_molecularProfileId_molecularProfile(): _.Dictionary< - MolecularProfile | undefined - > { + @computed get dict_molecularProfileId_molecularProfile() { return _.keyBy( - this.molecularProfiles.result, + this.molecularProfilesInSelectedStudies.result, profile => profile.molecularProfileId ); } @@ -1496,49 +1492,35 @@ export class QueryStore { getFilteredProfiles( molecularAlterationType: MolecularProfile['molecularAlterationType'] ) { - return this.molecularProfiles.result.filter(profile => { - if (profile.molecularAlterationType != molecularAlterationType) - return false; + return this.molecularProfilesInSelectedStudies.result.filter( + profile => { + if (profile.molecularAlterationType != molecularAlterationType) + return false; - return profile.showProfileInAnalysisTab || this.forDownloadTab; - }); + return profile.showProfileInAnalysisTab || this.forDownloadTab; + } + ); } - isProfileSelected(molecularProfileId: string) { - return _.includes(this.selectedProfileIds, molecularProfileId); + isProfileTypeSelected(profileType: string) { + return this.selectedProfileIdSet[profileType] || false; } - getSelectedProfileIdFromMolecularAlterationType( - molecularAlterationType: MolecularProfile['molecularAlterationType'], - selectedProfileIds?: ReadonlyArray - ): string { - for (let profileId of selectedProfileIds || this.selectedProfileIds) { - let profile = this.dict_molecularProfileId_molecularProfile[ - profileId - ]; - if ( - profile && - profile.molecularAlterationType === molecularAlterationType - ) - return profile.molecularProfileId; - } - return ''; + getSelectedProfileTypeFromMolecularAlterationType( + molecularAlterationType: MolecularProfile['molecularAlterationType'] + ) { + return this.getFilteredProfiles(molecularAlterationType) + .map(profile => getSuffixOfMolecularProfile(profile)) + .find(profile => this.isProfileTypeSelected(profile)); } get isGenesetProfileSelected() { - let result = false; - if (this.getFilteredProfiles('GENESET_SCORE')[0]) { - for (const selectedProfileId in this.selectedProfileIds) { - if ( - this.selectedProfileIds[selectedProfileId] === - this.getFilteredProfiles('GENESET_SCORE')[0] - .molecularProfileId - ) { - result = true; - } - } + const genesetProfiles = this.getFilteredProfiles('GENESET_SCORE'); + if (genesetProfiles.length > 0) { + const profileType = getSuffixOfMolecularProfile(genesetProfiles[0]); + return this.isProfileTypeSelected(profileType) || false; } - return result; + return false; } @computed get defaultProfilesForOql() { @@ -1557,6 +1539,14 @@ export class QueryStore { ] ); } + @computed get defaultStructuralVariantProfile() { + return ( + this.defaultProfilesForOql && + this.defaultProfilesForOql[ + AlterationTypeConstants.STRUCTURAL_VARIANT + ] + ); + } @computed get defaultCnaProfile() { return ( this.defaultProfilesForOql && @@ -1588,16 +1578,16 @@ export class QueryStore { if (this.selectableSelectedStudyIds.length !== 1) return undefined; let studyId = this.selectableSelectedStudyIds[0]; - let mutSelect = this.getSelectedProfileIdFromMolecularAlterationType( + let mutSelect = this.getSelectedProfileTypeFromMolecularAlterationType( 'MUTATION_EXTENDED' ); - let cnaSelect = this.getSelectedProfileIdFromMolecularAlterationType( + let cnaSelect = this.getSelectedProfileTypeFromMolecularAlterationType( 'COPY_NUMBER_ALTERATION' ); - let expSelect = this.getSelectedProfileIdFromMolecularAlterationType( + let expSelect = this.getSelectedProfileTypeFromMolecularAlterationType( 'MRNA_EXPRESSION' ); - let rppaSelect = this.getSelectedProfileIdFromMolecularAlterationType( + let rppaSelect = this.getSelectedProfileTypeFromMolecularAlterationType( 'PROTEIN_LEVEL' ); let sampleListId = studyId + '_all'; @@ -1609,16 +1599,12 @@ export class QueryStore { else if (!mutSelect && cnaSelect && !expSelect && !rppaSelect) sampleListId = studyId + '_cna'; else if (!mutSelect && !cnaSelect && expSelect && !rppaSelect) { - if (this.isProfileSelected(studyId + '_mrna_median_Zscores')) + if (this.isProfileTypeSelected('mrna_median_Zscores')) sampleListId = studyId + '_mrna'; - else if ( - this.isProfileSelected(studyId + '_rna_seq_mrna_median_Zscores') - ) + else if (this.isProfileTypeSelected('rna_seq_mrna_median_Zscores')) sampleListId = studyId + '_rna_seq_mrna'; else if ( - this.isProfileSelected( - studyId + '_rna_seq_v2_mrna_median_Zscores' - ) + this.isProfileTypeSelected('rna_seq_v2_mrna_median_Zscores') ) sampleListId = studyId + '_rna_seq_v2_mrna'; } else if ((mutSelect || cnaSelect) && expSelect && !rppaSelect) @@ -1880,15 +1866,20 @@ export class QueryStore { if (!this.selectableSelectedStudyIds.length) return 'Please select one or more cancer studies.'; - if (this.isSingleNonVirtualStudySelected) { - if (!this.selectedProfileIds.length) - return 'Please select one or more molecular profiles.'; + if (_.isEmpty(this.selectedProfileIdSet)) + return 'Please select one or more molecular profiles.'; + if (this.isSingleNonVirtualStudySelected) { if ( this.alterationTypesInOQL.haveMutInQuery && !this.defaultMutationProfile ) return 'Mutation data query specified in OQL, but no mutation profile is available for the selected study.'; + if ( + this.alterationTypesInOQL.haveStructuralVariantInQuery && + !this.defaultStructuralVariantProfile + ) + return 'Structural variant data query specified in OQL, but no structural variant profile is available for the selected study.'; if ( this.alterationTypesInOQL.haveCnaInQuery && !this.defaultCnaProfile @@ -1904,11 +1895,8 @@ export class QueryStore { !this.defaultProtProfile ) return 'Protein level data query specified in OQL, but no protein level profile is available in the selected study.'; - } else if ( - !(this.dataTypePriority.mutation || this.dataTypePriority.cna) - ) { - return 'Please select one or more molecular profiles.'; } + if ( this.selectableSelectedStudyIds.length && this.selectedSampleListId === CUSTOM_CASE_LIST_ID @@ -1932,8 +1920,8 @@ export class QueryStore { return 'Protein level filtering in the gene list (the PROT command) is not supported when doing cross cancer queries.'; } - if (this.selectedProfileIds.length !== 0) { - if (this.selectedProfileIds.length === 1) { + if (!_.isEmpty(this.selectedProfileIdSet)) { + if (Object.keys(this.selectedProfileIdSet).length === 1) { if (this.isGenesetProfileSelected) { //Only geneset profile selected if (!this.genesetQuery.length && !this.oql.query.length) { @@ -1987,37 +1975,6 @@ export class QueryStore { } } - private readonly dict_molecularAlterationType_filenameSuffix: { - [K in MolecularProfile['molecularAlterationType']]?: string; - } = { - MUTATION_EXTENDED: 'mutations', - COPY_NUMBER_ALTERATION: 'cna', - MRNA_EXPRESSION: 'mrna', - METHYLATION: 'methylation', - METHYLATION_BINARY: 'methylation', - PROTEIN_LEVEL: 'rppa', - }; - - @computed get downloadDataFilename() { - let study = - this.selectableSelectedStudyIds.length === 1 && - this.treeData.map_studyId_cancerStudy.get( - this.selectableSelectedStudyIds[0] - ); - let profile = this.dict_molecularProfileId_molecularProfile[ - this.selectedProfileIds[0] as string - ]; - - if (!this.forDownloadTab || !study || !profile) - return 'cbioportal-data.txt'; - - let suffix = - this.dict_molecularAlterationType_filenameSuffix[ - profile.molecularAlterationType - ] || profile.molecularAlterationType.toLowerCase(); - return `cbioportal-${study.studyId}-${suffix}.txt`; - } - readonly asyncUrlParams = remoteData({ await: () => [this.asyncCustomCaseSet], invoke: async () => currentQueryParams(this), @@ -2070,13 +2027,18 @@ export class QueryStore { stringListToSet(queriedStudies) ); - this._selectedProfileIds = profileIds.every(id => id === undefined) - ? undefined - : (profileIds.filter(_.identity) as string[]); + this.profileIdsFromUrl = _.compact(profileIds); this.zScoreThreshold = params.Z_SCORE_THRESHOLD || '2.0'; this.rppaScoreThreshold = params.RPPA_SCORE_THRESHOLD || '2.0'; - this.dataTypePriorityCode = - params.data_priority || params.profileFilter || '0'; + if (params.data_priority) { + this.dataTypePriorityFromUrl = params.data_priority; + } + if (params.profileFilter) { + if (isNaN(parseInt(params.profileFilter, 10))) { + this.profileFilterSetFromUrl = params.profileFilter.split(','); + } + } + this.selectedSampleListId = params.case_set_id ? params.case_set_id.toString() : ''; // must be a string even though it's integer @@ -2154,32 +2116,6 @@ export class QueryStore { this.selectedCancerTypeIds = []; } - @action selectMolecularProfile( - profile: MolecularProfile, - checked: boolean - ) { - let groupProfiles = this.getFilteredProfiles( - profile.molecularAlterationType - ); - let groupProfileIds = groupProfiles.map( - profile => profile.molecularProfileId - ); - if (this.forDownloadTab) { - // download tab only allows a single selection - this._selectedProfileIds = [profile.molecularProfileId]; - } else { - let difference = _.difference( - this.selectedProfileIds, - groupProfileIds - ); - if (checked) - this._selectedProfileIds = _.union(difference, [ - profile.molecularProfileId, - ]); - else this._selectedProfileIds = difference; - } - } - @action replaceGene(oldSymbol: string, newSymbol: string) { this.geneQuery = normalizeQuery( this.geneQuery diff --git a/src/shared/components/query/QueryStoreUtils.spec.ts b/src/shared/components/query/QueryStoreUtils.spec.ts index 7545e7f6841..3ea4b092b54 100644 --- a/src/shared/components/query/QueryStoreUtils.spec.ts +++ b/src/shared/components/query/QueryStoreUtils.spec.ts @@ -1,163 +1,9 @@ import { assert } from 'chai'; -import { - categorizedSamplesCount, - profileAvailability, -} from './QueryStoreUtils'; -import { AlterationTypeConstants } from '../../../pages/resultsView/ResultsViewPageStore'; -import { MolecularProfile, SampleList } from 'cbioportal-ts-api-client'; +import { categorizedSamplesCount } from './QueryStoreUtils'; +import { SampleList } from 'cbioportal-ts-api-client'; import { VirtualStudy } from 'shared/model/VirtualStudy'; describe('QueryStoreUtils', () => { - describe('profileAvailability', () => { - it('returns correct profile availability result in case of zero profiles', () => { - assert.deepEqual(profileAvailability([]), { - mutation: false, - cna: false, - }); - }); - it('returns correct profile availability result in case of one profile', () => { - let profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: true, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: true, - cna: false, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: false, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: false, - cna: false, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: true, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: false, - cna: true, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: false, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: false, - cna: false, - }); - }); - it('returns correct profile availability result in case of two profiles', () => { - let profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: true, - }, - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: true, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: true, - cna: true, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: false, - }, - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: true, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: false, - cna: true, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: true, - }, - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: false, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: true, - cna: false, - }); - - profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: false, - }, - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: false, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: false, - cna: false, - }); - }); - it('returns correct profile availability result in case of several profiles', () => { - let profiles = [ - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: true, - }, - { - molecularAlterationType: - AlterationTypeConstants.MUTATION_EXTENDED, - showProfileInAnalysisTab: false, - }, - { - molecularAlterationType: - AlterationTypeConstants.COPY_NUMBER_ALTERATION, - showProfileInAnalysisTab: false, - }, - ] as MolecularProfile[]; - assert.deepEqual(profileAvailability(profiles), { - mutation: true, - cna: false, - }); - }); - }); - describe('categorizedSamples', () => { let allSampleLists = [ { diff --git a/src/shared/components/query/QueryStoreUtils.ts b/src/shared/components/query/QueryStoreUtils.ts index a0449bf3615..35a88f12c6f 100644 --- a/src/shared/components/query/QueryStoreUtils.ts +++ b/src/shared/components/query/QueryStoreUtils.ts @@ -4,9 +4,29 @@ import { QueryStore, } from './QueryStore'; import { MolecularProfile, SampleList } from 'cbioportal-ts-api-client'; -import { AlterationTypeConstants } from 'pages/resultsView/ResultsViewPageStore'; import * as _ from 'lodash'; import { VirtualStudy } from 'shared/model/VirtualStudy'; +import { getSuffixOfMolecularProfile } from 'shared/lib/molecularProfileUtils'; + +export enum MutationProfilesEnum { + mutations = 'mutations', +} + +export enum CNAProfilesEnum { + cna = 'cna', + gistic = 'gistic', + cna_rae = 'cna_rae', + cna_consensus = 'cna_consensus', +} + +export enum StructuralVariantProfilesEnum { + fusion = 'fusion', // TODO: should be removed once fusion profiles are removed from data files and database + structural_variants = 'structural_variants', +} + +export enum GeneSetProfilesEnum { + gsva_scores = 'gsva_scores', +} export function currentQueryParams(store: QueryStore) { const selectableSelectedStudyIds = store.selectableSelectedStudyIds; @@ -16,73 +36,81 @@ export function currentQueryParams(store: QueryStore) { .map(caseRow => caseRow.studyId + ':' + caseRow.sampleId) .join('+'); - // select default profiles for OQL alteration types - let genetic_profile_ids_PROFILE_MUTATION_EXTENDED = store.getSelectedProfileIdFromMolecularAlterationType( - 'MUTATION_EXTENDED' - ); - if ( - store.alterationTypesInOQL.haveMutInQuery && - !genetic_profile_ids_PROFILE_MUTATION_EXTENDED && - store.defaultMutationProfile - ) { - genetic_profile_ids_PROFILE_MUTATION_EXTENDED = - store.defaultMutationProfile.molecularProfileId; - } + let profileFilters: string[] = Object.keys(store.selectedProfileIdSet); - let genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION = store.getSelectedProfileIdFromMolecularAlterationType( - 'COPY_NUMBER_ALTERATION' - ); - if ( - store.alterationTypesInOQL.haveCnaInQuery && - !genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION && - store.defaultCnaProfile - ) { - genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION = - store.defaultCnaProfile.molecularProfileId; - } + // If there is a single non-virtual study in selection, add profiles based on the alteration types in the OQL if they are not pre-selected. + // Example: If there are anything specific to mutation, ex: TP53: MUT = TRUNC INFRAME, then include/select mutation profile if it not selected in molecular profile slection section. Similarly even for other alteration type (Mutation, Structural Variant, Copy Number Alteration, mRNA Expression and Protein) in OQL + if (!store.isVirtualStudyQuery) { + // select default profiles for OQL alteration types + let selectedMutationProfileType = store.getSelectedProfileTypeFromMolecularAlterationType( + 'MUTATION_EXTENDED' + ); + if ( + store.alterationTypesInOQL.haveMutInQuery && + !selectedMutationProfileType && + store.defaultMutationProfile + ) { + profileFilters.push( + getSuffixOfMolecularProfile(store.defaultMutationProfile) + ); + } - let genetic_profile_ids_PROFILE_MRNA_EXPRESSION = store.getSelectedProfileIdFromMolecularAlterationType( - 'MRNA_EXPRESSION' - ); - if ( - store.alterationTypesInOQL.haveMrnaInQuery && - !genetic_profile_ids_PROFILE_MRNA_EXPRESSION && - store.defaultMrnaProfile - ) { - genetic_profile_ids_PROFILE_MRNA_EXPRESSION = - store.defaultMrnaProfile.molecularProfileId; - } + let selectedStructuralVariantProfileType = store.getSelectedProfileTypeFromMolecularAlterationType( + 'STRUCTURAL_VARIANT' + ); + if ( + store.alterationTypesInOQL.haveStructuralVariantInQuery && + !selectedStructuralVariantProfileType && + store.defaultStructuralVariantProfile + ) { + profileFilters.push( + getSuffixOfMolecularProfile( + store.defaultStructuralVariantProfile + ) + ); + } - let genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION = store.getSelectedProfileIdFromMolecularAlterationType( - 'PROTEIN_LEVEL' - ); - if ( - store.alterationTypesInOQL.haveProtInQuery && - !genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION && - store.defaultProtProfile - ) { - genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION = - store.defaultProtProfile.molecularProfileId; + let selectedCNAProfileType = store.getSelectedProfileTypeFromMolecularAlterationType( + 'COPY_NUMBER_ALTERATION' + ); + if ( + store.alterationTypesInOQL.haveCnaInQuery && + !selectedCNAProfileType && + store.defaultCnaProfile + ) { + profileFilters.push( + getSuffixOfMolecularProfile(store.defaultCnaProfile) + ); + } + + let selectedMRNAProfileType = store.getSelectedProfileTypeFromMolecularAlterationType( + 'MRNA_EXPRESSION' + ); + if ( + store.alterationTypesInOQL.haveMrnaInQuery && + !selectedMRNAProfileType && + store.defaultMrnaProfile + ) { + profileFilters.push( + getSuffixOfMolecularProfile(store.defaultMrnaProfile) + ); + } + + let selectedProtienProfileType = store.getSelectedProfileTypeFromMolecularAlterationType( + 'PROTEIN_LEVEL' + ); + if ( + store.alterationTypesInOQL.haveProtInQuery && + !selectedProtienProfileType && + store.defaultProtProfile + ) { + profileFilters.push( + getSuffixOfMolecularProfile(store.defaultProtProfile) + ); + } } const ret: CancerStudyQueryUrlParams = { - genetic_profile_ids_PROFILE_MUTATION_EXTENDED, - genetic_profile_ids_PROFILE_COPY_NUMBER_ALTERATION, - genetic_profile_ids_PROFILE_MRNA_EXPRESSION, - genetic_profile_ids_PROFILE_METHYLATION: - store.getSelectedProfileIdFromMolecularAlterationType( - 'METHYLATION' - ) || - store.getSelectedProfileIdFromMolecularAlterationType( - 'METHYLATION_BINARY' - ), - genetic_profile_ids_PROFILE_PROTEIN_EXPRESSION, - genetic_profile_ids_PROFILE_GENESET_SCORE: store.getSelectedProfileIdFromMolecularAlterationType( - 'GENESET_SCORE' - ), - genetic_profile_ids_PROFILE_GENERIC_ASSAY: store.getSelectedProfileIdFromMolecularAlterationType( - 'GENERIC_ASSAY' - ), cancer_study_id: selectableSelectedStudyIds.length === 1 ? selectableSelectedStudyIds[0] @@ -90,8 +118,7 @@ export function currentQueryParams(store: QueryStore) { cancer_study_list: undefined, Z_SCORE_THRESHOLD: store.zScoreThreshold, RPPA_SCORE_THRESHOLD: store.rppaScoreThreshold, - data_priority: store.dataTypePriorityCode, - profileFilter: store.dataTypePriorityCode, + profileFilter: profileFilters.join(','), case_set_id: store.selectedSampleListId || '-1', // empty string won't work case_ids, gene_list: normalizeQuery(store.geneQuery) || ' ', // empty string won't work @@ -110,29 +137,6 @@ export function currentQueryParams(store: QueryStore) { return { query: ret }; } -export function profileAvailability(molecularProfiles: MolecularProfile[]) { - let hasMutationProfile = false; - let hasCNAProfile = false; - for (const profile of molecularProfiles) { - if (!profile.showProfileInAnalysisTab) continue; - - switch (profile.molecularAlterationType) { - case AlterationTypeConstants.MUTATION_EXTENDED: - hasMutationProfile = true; - break; - case AlterationTypeConstants.COPY_NUMBER_ALTERATION: - hasCNAProfile = true; - break; - } - - if (hasMutationProfile && hasCNAProfile) break; - } - return { - mutation: hasMutationProfile, - cna: hasCNAProfile, - }; -} - export function categorizedSamplesCount( sampleLists: SampleList[], selectedStudies: string[], @@ -274,3 +278,45 @@ export function categorizedSamplesCount( ), }; } + +export function getMolecularProfileOptions(molecularProfilesByType: { + [profileType: string]: MolecularProfile[]; +}) { + const molecularProfileOptions: { + label: string; + id: string; + profileTypes: string[]; + }[] = []; + + if (molecularProfilesByType[MutationProfilesEnum.mutations]) { + molecularProfileOptions.push({ + label: 'Mutations', + id: MutationProfilesEnum.mutations, + profileTypes: [MutationProfilesEnum.mutations], + }); + } + + const structuralVariantProfileTypes = Object.keys( + StructuralVariantProfilesEnum + ).filter(profileType => molecularProfilesByType[profileType] !== undefined); + if (structuralVariantProfileTypes.length > 0) { + molecularProfileOptions.push({ + label: 'Structural variants', + id: structuralVariantProfileTypes.join('-'), + profileTypes: structuralVariantProfileTypes, + }); + } + + const cnaProfileTypes = Object.keys(CNAProfilesEnum).filter( + profileType => molecularProfilesByType[profileType] !== undefined + ); + if (cnaProfileTypes.length > 0) { + molecularProfileOptions.push({ + label: 'Copy number alterations', + id: cnaProfileTypes.join('-'), + profileTypes: cnaProfileTypes, + }); + } + + return molecularProfileOptions; +} diff --git a/src/shared/lib/Colors.ts b/src/shared/lib/Colors.ts index b7b48b21e6f..53982eb3f07 100644 --- a/src/shared/lib/Colors.ts +++ b/src/shared/lib/Colors.ts @@ -4,13 +4,13 @@ import { CNA_COLOR_GAIN, CNA_COLOR_HETLOSS, CNA_COLOR_HOMDEL, - MUT_COLOR_FUSION, MUT_COLOR_INFRAME, MUT_COLOR_MISSENSE, MUT_COLOR_MISSENSE_PASSENGER, MUT_COLOR_PROMOTER, MUT_COLOR_SPLICE, MUT_COLOR_TRUNC, + STRUCTURAL_VARIANT_COLOR, } from 'cbioportal-frontend-commons'; import { MUT_PROFILE_COUNT_NOT_MUTATED } from 'pages/resultsView/plots/PlotsTabUtils'; // Default grey @@ -81,8 +81,8 @@ export let RESERVED_CLINICAL_VALUE_COLORS: { [value: string]: string } = { missense: MUT_COLOR_MISSENSE, inframe: MUT_COLOR_INFRAME, truncating: MUT_COLOR_TRUNC, + fusion: STRUCTURAL_VARIANT_COLOR, splice: MUT_COLOR_SPLICE, - fusion: MUT_COLOR_FUSION, promoter: MUT_COLOR_PROMOTER, driver: MUT_COLOR_MISSENSE, vus: MUT_COLOR_MISSENSE_PASSENGER, diff --git a/src/shared/lib/MutationUtils.ts b/src/shared/lib/MutationUtils.ts index 00036ce6bab..f89c1b36b53 100644 --- a/src/shared/lib/MutationUtils.ts +++ b/src/shared/lib/MutationUtils.ts @@ -27,11 +27,11 @@ import { toSampleUuid } from './UuidUtils'; import { normalizeMutations } from '../components/mutationMapper/MutationMapperUtils'; import { getSimplifiedMutationType } from './oql/AccessorsForOqlFilter'; import { - MUT_COLOR_FUSION, MUT_COLOR_INFRAME, MUT_COLOR_MISSENSE, MUT_COLOR_OTHER, MUT_COLOR_TRUNC, + STRUCTURAL_VARIANT_COLOR, } from 'cbioportal-frontend-commons'; export const DEFAULT_PROTEIN_IMPACT_TYPE_COLORS: IProteinImpactTypeColors = { @@ -43,7 +43,7 @@ export const DEFAULT_PROTEIN_IMPACT_TYPE_COLORS: IProteinImpactTypeColors = { truncatingVusColor: MUT_COLOR_TRUNC_PASSENGER, spliceColor: MUT_COLOR_SPLICE, spliceVusColor: MUT_COLOR_SPLICE_PASSENGER, - fusionColor: MUT_COLOR_FUSION, + fusionColor: STRUCTURAL_VARIANT_COLOR, otherColor: MUT_COLOR_OTHER, }; diff --git a/src/shared/lib/StoreUtils.ts b/src/shared/lib/StoreUtils.ts index 5acfce65b3c..2a6da422a87 100644 --- a/src/shared/lib/StoreUtils.ts +++ b/src/shared/lib/StoreUtils.ts @@ -32,6 +32,7 @@ import { ReferenceGenomeGene, Sample, SampleFilter, + StructuralVariant, } from 'cbioportal-ts-api-client'; import defaultClient from 'shared/api/cbioportalClientInstance'; import client from 'shared/api/cbioportalClientInstance'; @@ -54,6 +55,7 @@ import { generateQueryVariantId, IHotspotIndex, IOncoKbData, + generateAnnotateStructuralVariantQueryFromGenes, isLinearClusterHotspot, } from 'cbioportal-utils'; import { getAlterationString } from 'shared/lib/CopyNumberUtils'; @@ -79,8 +81,9 @@ import AppConfig from 'appConfig'; import { getFrontendAssetUrl } from 'shared/api/urls'; import { AnnotateCopyNumberAlterationQuery, - CancerGene, + AnnotateStructuralVariantQuery, IndicatorQueryResp, + CancerGene, OncoKbAPI, OncoKBInfo, } from 'oncokb-ts-api-client'; @@ -105,6 +108,18 @@ import { } from 'shared/lib/MutationUtils'; import { ObservableMap } from 'mobx'; +export const MolecularAlterationType_filenameSuffix: { + [K in MolecularProfile['molecularAlterationType']]?: string; +} = { + MUTATION_EXTENDED: 'mutations', + COPY_NUMBER_ALTERATION: 'cna', + MRNA_EXPRESSION: 'mrna', + METHYLATION: 'methylation', + METHYLATION_BINARY: 'methylation', + PROTEIN_LEVEL: 'rppa', + STRUCTURAL_VARIANT: 'structural_variants', +}; + export const ONCOKB_DEFAULT: IOncoKbData = { indicatorMap: {}, }; @@ -798,6 +813,44 @@ export async function fetchCnaOncoKbData( } } +export async function fetchStructuralVariantOncoKbData( + uniqueSampleKeyToTumorType: { [uniqueSampleKey: string]: string }, + annotatedGenes: { [entrezGeneId: number]: boolean }, + structuralVariantData: MobxPromise, + client: OncoKbAPI = oncokbClient +) { + if ( + !structuralVariantData.result || + structuralVariantData.result.length === 0 + ) { + return ONCOKB_DEFAULT; + } else { + const alterationsToQuery = _.filter( + structuralVariantData.result, + d => + d.site1EntrezGeneId && + d.site2EntrezGeneId && + (!!annotatedGenes[d.site1EntrezGeneId] || + !!annotatedGenes[d.site2EntrezGeneId]) + ); + const queryVariants = _.uniqBy( + _.map(alterationsToQuery, datum => { + return generateAnnotateStructuralVariantQueryFromGenes( + datum.site1EntrezGeneId, + datum.site2EntrezGeneId, + cancerTypeForOncoKb( + datum.uniqueSampleKey, + uniqueSampleKeyToTumorType + ), + datum.variantClass.toUpperCase() as any + ); + }), + datum => datum.id + ); + return fetchOncoKbStructuralVariantData(queryVariants, client); + } +} + export async function fetchCnaOncoKbDataWithNumericGeneMolecularData( uniqueSampleKeyToTumorType: { [uniqueSampleKey: string]: string }, annotatedGenes: { [entrezGeneId: number]: boolean }, @@ -926,6 +979,20 @@ export async function queryOncoKbCopyNumberAlterationData( return toOncoKbData(oncokbSearch); } +export async function fetchOncoKbStructuralVariantData( + queryVariants: AnnotateStructuralVariantQuery[], + client: OncoKbAPI = oncokbClient +) { + const oncokbSearch = + queryVariants.length === 0 + ? [] + : await client.annotateStructuralVariantsPostUsingPOST_1({ + body: queryVariants, + }); + + return toOncoKbData(oncokbSearch); +} + function toOncoKbData(indicatorQueryResps: IndicatorQueryResp[]): IOncoKbData { return { indicatorMap: generateIdToIndicatorMap(indicatorQueryResps), @@ -1186,6 +1253,18 @@ export function generateMutationIdByEvent(m: Mutation): string { return mutationEventFields(m).join('_'); } +export function generateStructuralVariantId(s: StructuralVariant): string { + return [ + s.site1HugoSymbol, + s.site2HugoSymbol, + s.site1Position, + s.site2Position, + s.site1Chromosome, + s.site2Chromosome, + s.variantClass, + ].join('_'); +} + export function generateMutationIdByGeneAndProteinChangeAndEvent( m: Mutation ): string { @@ -1432,6 +1511,7 @@ export async function fetchSurvivalDataExists( export function getAlterationTypesInOql(parsedQueryLines: SingleGeneQuery[]) { let haveMutInQuery = false; + let haveStructuralVariantInQuery = false; let haveCnaInQuery = false; let haveMrnaInQuery = false; let haveProtInQuery = false; @@ -1440,6 +1520,9 @@ export function getAlterationTypesInOql(parsedQueryLines: SingleGeneQuery[]) { for (const alteration of queryLine.alterations || []) { haveMutInQuery = haveMutInQuery || alteration.alteration_type === 'mut'; + haveStructuralVariantInQuery = + haveStructuralVariantInQuery || + alteration.alteration_type === 'fusion'; haveCnaInQuery = haveCnaInQuery || alteration.alteration_type === 'cna'; haveMrnaInQuery = @@ -1450,6 +1533,7 @@ export function getAlterationTypesInOql(parsedQueryLines: SingleGeneQuery[]) { } return { haveMutInQuery, + haveStructuralVariantInQuery, haveCnaInQuery, haveMrnaInQuery, haveProtInQuery, @@ -1477,6 +1561,7 @@ export function getDefaultProfilesForOql(profiles: MolecularProfile[]) { return _.mapValues( _.keyBy([ AlterationTypeConstants.MUTATION_EXTENDED, + AlterationTypeConstants.STRUCTURAL_VARIANT, AlterationTypeConstants.COPY_NUMBER_ALTERATION, AlterationTypeConstants.MRNA_EXPRESSION, AlterationTypeConstants.PROTEIN_LEVEL, diff --git a/src/shared/lib/URLWrapper.spec.ts b/src/shared/lib/URLWrapper.spec.ts index 8e75b1f437b..36f8e44e96f 100644 --- a/src/shared/lib/URLWrapper.spec.ts +++ b/src/shared/lib/URLWrapper.spec.ts @@ -20,6 +20,7 @@ function getPlotsSelectionParam(val: T): any { dataType: val, selectedDataSourceOption: val, mutationCountBy: val, + structuralVariantCountBy: val, logScale: val, }; } diff --git a/src/shared/lib/alterationCountHelpers.spec.ts b/src/shared/lib/alterationCountHelpers.spec.ts index 543bd087bd3..c3d7689a0d7 100644 --- a/src/shared/lib/alterationCountHelpers.spec.ts +++ b/src/shared/lib/alterationCountHelpers.spec.ts @@ -297,7 +297,7 @@ describe('alterationCountHelpers', () => { homdel: 0, hetloss: 0, gain: 0, - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -311,12 +311,14 @@ describe('alterationCountHelpers', () => { cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 5, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, }; @@ -328,9 +330,8 @@ describe('alterationCountHelpers', () => { alterationsBySampleId['VTA0NDpjaG9sX251c18yMDEy'] = [ { uniqueSampleKey: 'VTA0NDpjaG9sX251c18yMDEy', - molecularProfileAlterationType: 'MUTATION_EXTENDED', - alterationSubType: 'missense', - alterationType: 'FUSION', + molecularProfileAlterationType: 'STRUCTURAL_VARIANT', + alterationType: 'STRUCTURAL_VARIANT', }, ]; @@ -367,7 +368,7 @@ describe('alterationCountHelpers', () => { homdel: 1, hetloss: 0, gain: 0, - fusion: 1, + structuralVariant: 1, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -381,12 +382,14 @@ describe('alterationCountHelpers', () => { cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 5, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, }; @@ -423,7 +426,7 @@ describe('alterationCountHelpers', () => { homdel: 0, hetloss: 0, gain: 0, - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -437,12 +440,14 @@ describe('alterationCountHelpers', () => { cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, notProfiledSamplesCounts: { mutation: 5, cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }, }, }; diff --git a/src/shared/lib/alterationCountHelpers.ts b/src/shared/lib/alterationCountHelpers.ts index ccd2622f716..5bc8ade39d8 100644 --- a/src/shared/lib/alterationCountHelpers.ts +++ b/src/shared/lib/alterationCountHelpers.ts @@ -106,7 +106,7 @@ export function countAlterationOccurences( homdel: 0, // -2 hetloss: 0, // -1 gain: 0, // 1 - fusion: 0, + structuralVariant: 0, mrnaExpressionHigh: 0, mrnaExpressionLow: 0, protExpressionHigh: 0, @@ -119,6 +119,7 @@ export function countAlterationOccurences( cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }; const notProfiledSamplesCounts = { @@ -126,6 +127,7 @@ export function countAlterationOccurences( cna: 0, expression: 0, protein: 0, + structuralVariant: 0, }; const ret: IAlterationData = { @@ -183,6 +185,12 @@ export function countAlterationOccurences( : notProfiledSamplesCounts.mutation++; break; } + case AlterationTypeConstants.STRUCTURAL_VARIANT: { + profiled + ? profiledTypeCounts.structuralVariant++ + : notProfiledSamplesCounts.structuralVariant++; + break; + } } } ); @@ -253,8 +261,8 @@ export function countAlterationOccurences( case AlterationTypeConstants.MUTATION_EXTENDED: counts.mutated++; break; - case AlterationTypeConstants.FUSION: - counts.fusion++; + case AlterationTypeConstants.STRUCTURAL_VARIANT: + counts.structuralVariant++; break; } } diff --git a/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.spec.tsx b/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.spec.tsx index f8bef6d2eea..2c0db3337a9 100644 --- a/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.spec.tsx +++ b/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.spec.tsx @@ -8,7 +8,6 @@ import sinon from 'sinon'; import { cnaEventTypeSelectInit, CopyNumberEnrichmentEventType, - fusionGroup, MutationEnrichmentEventType, mutationEventTypeSelectInit, mutationGroup, @@ -40,8 +39,6 @@ describe('AlterationEnrichmentTypeSelector', () => { 'Nonstop', ]; - const structvarCheckboxRefs = ['Fusion']; - const cnaCheckboxRefs = [ 'CheckCopynumberAlterations', 'DeepDeletion', @@ -125,8 +122,8 @@ describe('AlterationEnrichmentTypeSelector', () => { allCheckBoxesDeselected: () => allDeselected(mutationTypeCheckboxRefs), }, - fusionSection: { - pressMasterButton: () => toggleCheckbox('Fusion'), + structuralVariantSection: { + pressMasterButton: () => toggleCheckbox('StructuralVariants'), }, pressSubmitButton: () => component.findByDataTest('buttonSelectAlterations').click(), @@ -134,37 +131,18 @@ describe('AlterationEnrichmentTypeSelector', () => { }; function createStore() { - let selectedMutationEnrichmentEventTypes = [ - ...mutationGroup, - ...fusionGroup, - ].reduce((acc, type) => { - acc[type] = true; - return acc; - }, {} as { [key in MutationEnrichmentEventType]?: boolean }); - - let selectedCopyNumberEnrichmentEventTypes = { - [CopyNumberEnrichmentEventType.HOMDEL]: true, - [CopyNumberEnrichmentEventType.AMP]: true, - }; - return { selectedCopyNumberEnrichmentEventTypes: cnaEventTypeSelectInit([ { molecularAlterationType: 'COPY_NUMBER_ALTERATION', } as MolecularProfile, ]), - selectedMutationEnrichmentEventTypes: mutationEventTypeSelectInit({ - mutationProfiles: [ - { - molecularAlterationType: 'MUTATION_EXTENDED', - } as MolecularProfile, - ], - structuralVariantProfiles: [ - { - molecularAlterationType: 'STRUCTURAL_VARIANT', - } as MolecularProfile, - ], - }), + selectedMutationEnrichmentEventTypes: mutationEventTypeSelectInit([ + { + molecularAlterationType: 'MUTATION_EXTENDED', + } as MolecularProfile, + ]), + isStructuralVariantEnrichmentSelected: true, } as ComparisonStore; } @@ -177,7 +155,7 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={true} - showFusions={true} + showStructuralVariants={true} showCnas={true} />, wrapperForMenu @@ -304,13 +282,13 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={true} - showFusions={true} + showStructuralVariants={true} showCnas={true} />, wrapperForMenu ); assert.isTrue(menu.exists('input[data-test="Mutations"]')); - assert.isTrue(menu.exists('input[data-test="Fusion"]')); + assert.isTrue(menu.exists('input[data-test="StructuralVariants"]')); assert.isTrue( menu.exists('input[data-test="CheckCopynumberAlterations"]') ); @@ -324,13 +302,15 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={false} - showFusions={false} + showStructuralVariants={false} showCnas={false} />, wrapperForMenu ); assert.isFalse(menu.exists('input[data-test="Mutations"]')); - assert.isFalse(menu.exists('input[data-test="Fusion"]')); + assert.isFalse( + menu.exists('input[data-test="StructuralVariants"]') + ); assert.isFalse( menu.exists('input[data-test="CheckCopynumberAlterations"]') ); @@ -344,19 +324,21 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={true} - showFusions={false} + showStructuralVariants={false} showCnas={false} />, wrapperForMenu ); assert.isTrue(menu.exists('input[data-test="Mutations"]')); - assert.isFalse(menu.exists('input[data-test="Fusion"]')); + assert.isFalse( + menu.exists('input[data-test="StructuralVariants"]') + ); assert.isFalse( menu.exists('input[data-test="CheckCopynumberAlterations"]') ); }); - it('shows fusions section when asked', function() { + it('shows structural variant section when asked', function() { menu = mountWithCustomWrappers( { } store={createStore()} showMutations={false} - showFusions={true} + showStructuralVariants={true} showCnas={false} />, wrapperForMenu ); assert.isFalse(menu.exists('input[data-test="Mutations"]')); - assert.isTrue(menu.exists('input[data-test="Fusion"]')); + assert.isTrue(menu.exists('input[data-test="StructuralVariants"]')); assert.isFalse( menu.exists('input[data-test="CheckCopynumberAlterations"]') ); @@ -384,13 +366,15 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={false} - showFusions={false} + showStructuralVariants={false} showCnas={true} />, wrapperForMenu ); assert.isFalse(menu.exists('input[data-test="Mutations"]')); - assert.isFalse(menu.exists('input[data-test="Fusion"]')); + assert.isFalse( + menu.exists('input[data-test="StructuralVariants"]') + ); assert.isTrue( menu.exists('input[data-test="CheckCopynumberAlterations"]') ); @@ -406,7 +390,7 @@ describe('AlterationEnrichmentTypeSelector', () => { } store={createStore()} showMutations={true} - showFusions={true} + showStructuralVariants={true} showCnas={true} />, wrapperForMenu @@ -428,7 +412,7 @@ describe('AlterationEnrichmentTypeSelector', () => { it('returns mutation types to callback', function() { menu.mutationSection.pressMasterButton(); menu.cnaSection.pressMasterButton(); - menu.fusionSection.pressMasterButton(); + menu.structuralVariantSection.pressMasterButton(); menu.mutationSection.pressChildButton(); menu.pressSubmitButton(); @@ -443,7 +427,7 @@ describe('AlterationEnrichmentTypeSelector', () => { it('returns cna types to callback', function() { menu.mutationSection.pressMasterButton(); menu.cnaSection.pressMasterButton(); - menu.fusionSection.pressMasterButton(); + menu.structuralVariantSection.pressMasterButton(); menu.cnaSection.pressChildButton(); menu.pressSubmitButton(); diff --git a/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.tsx b/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.tsx index d60ba7be858..c75219d8016 100644 --- a/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.tsx +++ b/src/shared/lib/comparison/AlterationEnrichmentTypeSelector.tsx @@ -14,7 +14,6 @@ import { frameshiftDeletionGroup, frameshiftGroup, frameshiftInsertionGroup, - fusionGroup, inframeDeletionGroup, inframeGroup, inframeInsertionGroup, @@ -26,6 +25,7 @@ import { nonstopGroup, otherGroup, spliceGroup, + StructuralVariantEnrichmentEventType, truncationGroup, } from 'shared/lib/comparison/ComparisonStoreUtils'; @@ -33,7 +33,7 @@ export interface IAlterationEnrichmentTypeSelectorProps { updateSelectedEnrichmentEventTypes: (t: EnrichmentEventType[]) => void; store: ComparisonStore; showMutations?: boolean; - showFusions?: boolean; + showStructuralVariants?: boolean; showCnas?: boolean; classNames?: string; } @@ -76,6 +76,8 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< [key in CopyNumberEnrichmentEventType]?: boolean; }; + @observable private isStructuralVariantSelected?: boolean; + componentWillMount() { this.currentSelectedMutations = observable( toJS(this.props.store.selectedMutationEnrichmentEventTypes) @@ -83,6 +85,7 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< this.currentSelectedCopyNumber = observable( toJS(this.props.store.selectedCopyNumberEnrichmentEventTypes) ); + this.isStructuralVariantSelected = this.props.store.isStructuralVariantEnrichmentSelected; } @computed get isAnyMutationsSelected() { @@ -141,10 +144,6 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< return this.isAnySelectedMut(otherGroup); } - @computed get isAnyFusionsSelected() { - return this.isAnySelectedMut(fusionGroup); - } - @computed get isAnyCopyNumberSelected() { return this.isAnySelectedCna(cnaGroup); } @@ -167,6 +166,7 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< @autobind private onInputClick(event: React.MouseEvent) { + console.log((event.target as HTMLInputElement).value); switch ((event.target as HTMLInputElement).value) { case checkbox.mutations: this.toggleMutGroup( @@ -232,7 +232,8 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< this.toggleMutGroup(otherGroup, !this.isAnyOtherSelected); break; case checkbox.structvar: - this.toggleMutGroup(fusionGroup, !this.isAnyFusionsSelected); + this.isStructuralVariantSelected = !this + .isStructuralVariantSelected; break; case checkbox.cna: this.toggleCnaGroup(cnaGroup, !this.isAnyCopyNumberSelected); @@ -287,6 +288,12 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< const selectedTypes: EnrichmentEventType[] = selectedMutations.concat( selectedCopyNumber ) as EnrichmentEventType[]; + + if (this.isStructuralVariantSelected) { + selectedTypes.push( + StructuralVariantEnrichmentEventType.structural_variant + ); + } this.props.updateSelectedEnrichmentEventTypes(selectedTypes); } @@ -299,6 +306,10 @@ export default class AlterationEnrichmentTypeSelector extends React.Component< !_.isEqual( toJS(this.currentSelectedCopyNumber), toJS(this.props.store.selectedCopyNumberEnrichmentEventTypes) + ) || + !_.isEqual( + this.isStructuralVariantSelected, + this.props.store.isStructuralVariantEnrichmentSelected ) ); } @@ -542,14 +553,14 @@ export default class AlterationEnrichmentTypeSelector extends React.Component<
)} - {this.props.showFusions && ( + {this.props.showStructuralVariants && (