From 075074e2e63643e772f1ec7436debac35b517081 Mon Sep 17 00:00:00 2001 From: Qi-Xuan Lu Date: Fri, 5 Apr 2024 14:40:27 -0400 Subject: [PATCH] Mutation pie chart and multi-selection table (#4794) * group gene selection options by alteration type * add mutation-data-counts endpoint and revamp renderAttributes giant switch statements * add sub menu options for mutation profile * get mutation data and fix display text on add chart * fix filter and show pill tags for mutation count data * add mutation type table and disable group comparison * get mutation table download data * support and fix intersect and union selection on mutation table * ui update for mutation chart * fix pilltags, apis, and select * cleanup code * resolve comments from Karthik * resolve Aaron's comments * resolve Study View Page missing data * fix selecting more rows in mutation counts table * fix pilltag errors on intersection selection * rename wild type to mutation type * fix type errors and conflicts * fix GenomicDataCount * fix e2e tests on SummaryTab * update any to Partial in SummaryTab * resolve Aaron's comments * remove categories grouping and change colors for mutation pie chart * fix mutation categories type assertion * revert colors.ts changes and add callback to change Mutated vs Wild Type pie chart --------- Co-authored-by: Qi-Xuan Lu --- ...ical_selector_element_chrome_1600x1000.png | Bin 22306 -> 22519 bytes .../src/generated/CBioPortalAPIInternal.ts | 171 ++- src/pages/studyView/StudyViewConfig.ts | 7 + src/pages/studyView/StudyViewPageStore.ts | 924 ++++++------- src/pages/studyView/StudyViewUtils.tsx | 626 ++++++++- src/pages/studyView/UserSelections.tsx | 91 +- .../addChartButton/AddChartButton.tsx | 3 +- .../geneLevelSelection/GeneLevelSelection.tsx | 100 +- src/pages/studyView/charts/ChartContainer.tsx | 78 +- .../studyPageHeader/StudyPageHeader.tsx | 3 + .../studyView/table/MultiSelectionTable.tsx | 59 +- src/pages/studyView/tabs/SummaryTab.tsx | 1157 +++++++++-------- src/shared/constants.ts | 10 + 13 files changed, 2082 insertions(+), 1147 deletions(-) diff --git a/end-to-end-test/local/screenshots/reference/add_custom_data_tab_should_have_numerical_and_categorical_selector_element_chrome_1600x1000.png b/end-to-end-test/local/screenshots/reference/add_custom_data_tab_should_have_numerical_and_categorical_selector_element_chrome_1600x1000.png index 8dd058fa1b4a1e53031f5915211c120f165b6286..4ef6109c7db92b733d63c64a5001f0bf796ce838 100644 GIT binary patch literal 22519 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 Y_tnK5Y!<~A`u?G?S4HNTl=hYX1>@8fMF0Q* literal 22306 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 diff --git a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts index f270f516551..1ed2f0d49fb 100644 --- a/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts +++ b/packages/cbioportal-ts-api-client/src/generated/CBioPortalAPIInternal.ts @@ -714,10 +714,10 @@ export type GenomicDataBinFilter = { export type GenomicDataCount = { 'count': number - 'label': string - 'uniqueCount': number + 'label': string + 'value': string }; @@ -907,9 +907,8 @@ export type MutationDataFilter = { 'profileType': string - 'values': Array < Array < DataFilterValue > - > - + 'values': Array < Array< DataFilterValue > > + }; export type MutationPositionIdentifier = { 'entrezGeneId': number @@ -4815,6 +4814,85 @@ export default class CBioPortalAPIInternal { return response.body; }); }; + + fetchMutationDataCountsUsingPOSTURL(parameters: { + 'genomicDataCountFilter': GenomicDataCountFilter, + $queryParameters ? : any + }): string { + let queryParameters: any = {}; + let path = '/api/mutation-data-counts/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 mutation data counts by GenomicDataCountFilter + * @method + * @name CBioPortalAPIInternal#fetchMutationDataCountsUsingPOST + * @param {} genomicDataCountFilter - Genomic data count filter + */ + fetchMutationDataCountsUsingPOSTWithHttpInfo(parameters: { + 'genomicDataCountFilter': GenomicDataCountFilter, + $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 = '/api/mutation-data-counts/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['genomicDataCountFilter'] !== undefined) { + body = parameters['genomicDataCountFilter']; + } + + if (parameters['genomicDataCountFilter'] === undefined) { + reject(new Error('Missing required parameter: genomicDataCountFilter')); + 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 mutation data counts by GenomicDataCountFilter + * @method + * @name CBioPortalAPIInternal#fetchMutationDataCountsUsingPOST + * @param {} genomicDataCountFilter - Genomic data count filter + */ + fetchMutationDataCountsUsingPOST(parameters: { + 'genomicDataCountFilter': GenomicDataCountFilter, + $queryParameters ? : any, + $domain ? : string + }): Promise < Array < GenomicDataCountItem > + > { + return this.fetchMutationDataCountsUsingPOSTWithHttpInfo(parameters).then(function(response: request.Response) { + return response.body; + }); + }; + fetchMolecularProfileSampleCountsUsingPOSTURL(parameters: { 'studyViewFilter' ? : StudyViewFilter, $queryParameters ? : any @@ -5542,89 +5620,6 @@ export default class CBioPortalAPIInternal { return response.body; }); }; - fetchMutationDataCountsUsingPOSTURL(parameters: { - 'projection' ? : "ID" | "SUMMARY" | "DETAILED" | "META", - 'genomicDataCountFilter' ? : GenomicDataCountFilter, - $queryParameters ? : any - }): string { - let queryParameters: any = {}; - let path = '/api/mutation-data-counts/fetch'; - if (parameters['projection'] !== undefined) { - queryParameters['projection'] = parameters['projection']; - } - - 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 mutation data counts by GenomicDataCountFilter - * @method - * @name CBioPortalAPIInternal#fetchMutationDataCountsUsingPOST - * @param {string} projection - Level of detail of the response - * @param {} genomicDataCountFilter - A web service for supplying JSON formatted data to cBioPortal clients. Please note that this API is currently in beta and subject to change. - */ - fetchMutationDataCountsUsingPOSTWithHttpInfo(parameters: { - 'projection' ? : "ID" | "SUMMARY" | "DETAILED" | "META", - 'genomicDataCountFilter' ? : GenomicDataCountFilter, - $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 = '/api/mutation-data-counts/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['projection'] !== undefined) { - queryParameters['projection'] = parameters['projection']; - } - - if (parameters['genomicDataCountFilter'] !== undefined) { - body = parameters['genomicDataCountFilter']; - } - - 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 mutation data counts by GenomicDataCountFilter - * @method - * @name CBioPortalAPIInternal#fetchMutationDataCountsUsingPOST - * @param {string} projection - Level of detail of the response - * @param {} genomicDataCountFilter - A web service for supplying JSON formatted data to cBioPortal clients. Please note that this API is currently in beta and subject to change. - */ - fetchMutationDataCountsUsingPOST(parameters: { - 'projection' ? : "ID" | "SUMMARY" | "DETAILED" | "META", - 'genomicDataCountFilter' ? : GenomicDataCountFilter, - $queryParameters ? : any, - $domain ? : string - }): Promise < ResponseEntityListGenomicDataCountItem > { - return this.fetchMutationDataCountsUsingPOSTWithHttpInfo(parameters).then(function(response: request.Response) { - return response.body; - }); - }; getAllReferenceGenomeGenesUsingGETURL(parameters: { 'genomeName': string, $queryParameters ? : any diff --git a/src/pages/studyView/StudyViewConfig.ts b/src/pages/studyView/StudyViewConfig.ts index 5c87f20b3aa..166ef974a1f 100644 --- a/src/pages/studyView/StudyViewConfig.ts +++ b/src/pages/studyView/StudyViewConfig.ts @@ -69,6 +69,7 @@ export enum ChartTypeEnum { SCATTER = 'SCATTER', VIOLIN_PLOT_TABLE = 'VIOLIN_PLOT_TABLE', MUTATED_GENES_TABLE = 'MUTATED_GENES_TABLE', + MUTATION_TYPE_COUNTS_TABLE = 'MUTATION_TYPE_COUNTS_TABLE', STRUCTURAL_VARIANT_GENES_TABLE = 'STRUCTURAL_VARIANT_GENES_TABLE', STRUCTURAL_VARIANTS_TABLE = 'STRUCTURAL_VARIANTS_TABLE', CNA_GENES_TABLE = 'CNA_GENES_TABLE', @@ -92,6 +93,7 @@ export enum ChartTypeNameEnum { SCATTER = 'density plot', VIOLIN_PLOT_TABLE = 'table', MUTATED_GENES_TABLE = 'table', + MUTATION_TYPE_COUNTS_TABLE = 'table', STRUCTURAL_VARIANT_GENES_TABLE = 'table', STRUCTURAL_VARIANTS_TABLE = 'table', CNA_GENES_TABLE = 'table', @@ -207,6 +209,11 @@ const studyViewFrontEnd = { h: 2, minW: 2, }, + [ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE]: { + w: 2, + h: 2, + minW: 2, + }, [ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE]: { w: 2, h: 2, diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts index 7cfca8f2d77..44e28488c05 100644 --- a/src/pages/studyView/StudyViewPageStore.ts +++ b/src/pages/studyView/StudyViewPageStore.ts @@ -45,8 +45,6 @@ import { GenericAssayData, GenericAssayDataBin, GenericAssayDataBinFilter, - GenericAssayDataCountFilter, - GenericAssayDataCountItem, GenericAssayDataFilter, GenericAssayDataMultipleStudyFilter, GenericAssayMeta, @@ -58,6 +56,7 @@ import { MolecularDataMultipleStudyFilter, MolecularProfile, MolecularProfileFilter, + MutationDataFilter, Mutation, MutationFilter, MutationMultipleStudyFilter, @@ -127,7 +126,6 @@ import { findInvalidMolecularProfileIds, geneFilterQueryFromOql, geneFilterQueryToOql, - generateXvsYScatterPlotDownloadData, getAllClinicalDataByStudyViewFilter, getBinBounds, getCategoricalFilterValues, @@ -135,7 +133,6 @@ import { getChartSettingsMap, getClinicalDataCountWithColorByClinicalDataCount, getCNAByAlteration, - getCNAColorByAlteration, getCNASamplesCount, getDataIntervalFilterValues, getDefaultPriorityByUniqueKey, @@ -143,7 +140,6 @@ import { getFilteredMolecularProfilesByAlterationType, getFilteredSampleIdentifiers, getFilteredStudiesWithSamples, - getFrequencyStr, getGenericAssayChartUniqueKey, getGenericAssayDataAsClinicalData, getGenomicChartUniqueKey, @@ -153,10 +149,8 @@ import { getMolecularProfileIdsFromUniqueKey, getNonZeroUniqueBins, getPriorityByClinicalAttribute, - getQValue, getRequestedAwaitPromisesForClinicalData, getSamplesByExcludingFiltersOnChart, - getSampleToClinicalData, getStructuralVariantSamplesCount, getUniqueKey, getUniqueKeyFromGeneFilterMolecularProfileIds, @@ -181,6 +175,13 @@ import { submitToPage, transformSampleDataToSelectedSampleClinicalData, updateCustomIntervalFilter, + invokeGenomicDataCount, + invokeMutationDataCount, + invokeGenericAssayDataCount, + getDefaultClinicalDataBinFilter, + getCustomChartDownloadData, + generateColorMapKey, + MutationCategorization, } from './StudyViewUtils'; import { SingleGeneQuery } from 'shared/lib/oql/oql-parser'; import autobind from 'autobind-decorator'; @@ -231,7 +232,7 @@ import { StudyViewComparisonGroup, } from '../groupComparison/GroupComparisonUtils'; import { LoadingPhase } from '../groupComparison/GroupComparisonLoading'; -import { sleep, sleepUntil } from '../../shared/lib/TimeUtils'; +import { sleepUntil } from '../../shared/lib/TimeUtils'; import ComplexKeyMap from '../../shared/lib/complexKeyDataStructures/ComplexKeyMap'; import MobxPromiseCache from 'shared/lib/MobxPromiseCache'; import { CancerGene, IndicatorQueryResp } from 'oncokb-ts-api-client'; @@ -248,6 +249,8 @@ import { StudyViewPageTabKeyEnum } from 'pages/studyView/StudyViewPageTabs'; import { AlterationTypeConstants, DataTypeConstants, + MutationOptionConstants, + MutationOptionConstantsLabel, REQUEST_ARG_ENUM, } from 'shared/constants'; import { @@ -456,6 +459,7 @@ export type GenomicChart = { profileType: string; hugoGeneSymbol: string; dataType?: string; + mutationOptionType?: string; }; export type GenericAssayChart = { @@ -1990,6 +1994,7 @@ export class StudyViewPageStore const chartInfo = this._geneSpecificChartMap.get( chartMeta.uniqueKey )!; + comparisonId = await this.createCnaGeneComparisonSession( chartMeta, [chartInfo.hugoGeneSymbol], @@ -2096,6 +2101,10 @@ export class StudyViewPageStore ChartUniqueKey, GenomicDataFilter >({}, { deep: false }); + private _mutationDataFilterSet = observable.map< + ChartUniqueKey, + MutationDataFilter + >({}, { deep: false }); private _genericAssayDataFilterSet = observable.map< ChartUniqueKey, GenericAssayDataFilter @@ -2275,6 +2284,20 @@ export class StudyViewPageStore }); } + if (!_.isEmpty(filters.mutationDataFilters)) { + filters.mutationDataFilters!.forEach(mutationDataFilter => { + const uniqueKey = getGenomicChartUniqueKey( + mutationDataFilter.hugoGeneSymbol, + mutationDataFilter.profileType, + mutationDataFilter.categorization + ); + this._mutationDataFilterSet.set( + uniqueKey, + _.clone(mutationDataFilter) + ); + }); + } + if (!_.isEmpty(filters.genericAssayDataFilters)) { filters.genericAssayDataFilters!.forEach(genericAssayDataFilter => { const uniqueKey = getGenericAssayChartUniqueKey( @@ -2465,6 +2488,9 @@ export class StudyViewPageStore public genomicDataCountPromises: { [id: string]: MobxPromise; } = {}; + public mutationDataCountPromises: { + [id: string]: MobxPromise; + } = {}; public genericAssayChartPromises: { [id: string]: MobxPromise; } = {}; @@ -2821,6 +2847,7 @@ export class StudyViewPageStore this._geneFilterSet.clear(); this._structVarFilterSet.clear(); this._genomicDataFilterSet.clear(); + this._mutationDataFilterSet.clear(); this._genericAssayDataFilterSet.clear(); this._chartSampleIdentifiersFilterSet.clear(); this.preDefinedCustomChartFilterSet.clear(); @@ -3102,13 +3129,116 @@ export class StudyViewPageStore ): void { trackStudyViewFilterEvent('genomicCategoricalData', this); + const chart = this._geneSpecificChartMap.get(uniqueKey); const dataFilterValues: DataFilterValue[] = getCategoricalFilterValues( values ); - this.updateGenomicDataFiltersByValues( - uniqueKey, - _.cloneDeep(dataFilterValues) + if (chart!.mutationOptionType) { + this.updateMutationDataFilters(uniqueKey, [_.cloneDeep(values)]); + } else { + this.updateGenomicDataFiltersByValues( + uniqueKey, + _.cloneDeep(dataFilterValues) + ); + } + } + + @action.bound + updateMutationDataFilters( + uniqueKey: string, + valueArrays: string[][] + ): void { + trackStudyViewFilterEvent('mutationCategoricalData', this); + + // valueArrays represent a two-dimensional array that supports union and + // intersection selection on samples + if (_.some(valueArrays, valueArray => valueArray.length !== 0)) { + const dataFilterValues: DataFilterValue[][] = valueArrays.map( + valueArray => + valueArray.map(value => { + return { value: value } as DataFilterValue; + }) + ); + const chart = this._geneSpecificChartMap.get(uniqueKey); + const mutationDataFilter: MutationDataFilter = { + hugoGeneSymbol: chart!.hugoGeneSymbol, + profileType: chart!.profileType, + values: dataFilterValues, + categorization: chart! + .mutationOptionType! as MutationCategorization, + }; + this._mutationDataFilterSet.set(uniqueKey, mutationDataFilter); + } else { + // delete mutationDataFilter if valueArrays is empty + this._mutationDataFilterSet.delete(uniqueKey); + } + } + + @action.bound + addMutationDataFilters(uniqueKey: string, valueArrays: string[][]): void { + trackStudyViewFilterEvent('mutationCategoricalData', this); + + let dataFilterValues: DataFilterValue[][] = valueArrays.map( + valueArray => + valueArray.map(value => { + return { value: value } as DataFilterValue; + }) ); + + if (this._mutationDataFilterSet.has(uniqueKey)) { + const values = toJS( + this._mutationDataFilterSet.get(uniqueKey)!.values + ); + + dataFilterValues = values.concat(dataFilterValues); + } + + const chart = this._geneSpecificChartMap.get(uniqueKey); + const mutationDataFilter: MutationDataFilter = { + hugoGeneSymbol: chart!.hugoGeneSymbol, + profileType: chart!.profileType, + values: dataFilterValues, + categorization: chart! + .mutationOptionType! as MutationCategorization, + }; + this._mutationDataFilterSet.set(uniqueKey, mutationDataFilter); + } + + @action.bound + removeMutationDataFilter(uniqueKey: string, toBeRemoved: string): void { + const dataFilterValues = toJS( + this._mutationDataFilterSet.get(uniqueKey)!.values + ); + + const newDataFilterValues = _.reduce( + dataFilterValues, + (acc, next: DataFilterValue[]) => { + const newGroup = next.filter( + dataFilterValue => dataFilterValue.value !== toBeRemoved + ); + if (newGroup.length > 0) { + acc.push(newGroup); + } + + return acc; + }, + [] as DataFilterValue[][] + ); + + if (newDataFilterValues.length === 0) { + this._mutationDataFilterSet.delete(uniqueKey); + } else { + const chart = this._geneSpecificChartMap.get(uniqueKey); + const newMutationDataFilter: MutationDataFilter = { + hugoGeneSymbol: chart!.hugoGeneSymbol, + profileType: chart!.profileType, + values: newDataFilterValues, + categorization: chart! + .mutationOptionType! as MutationCategorization, + }; + + this._mutationDataFilterSet.set(uniqueKey, newMutationDataFilter); + } } @action.bound @@ -3270,6 +3400,9 @@ export class StudyViewPageStore hugoGeneSymbol: chart!.hugoGeneSymbol, profileType: chart!.profileType, values: values, + ...(chart!.mutationOptionType + ? { categorization: chart!.mutationOptionType } + : {}), }; this._genomicDataFilterSet.set(uniqueKey, genomicDataFilter); } else { @@ -3503,6 +3636,19 @@ export class StudyViewPageStore return DataType.NUMBER; } + public getChartMetaDataType(uniqueKey: string): ChartMetaDataTypeEnum { + if (this.isGeneSpecificChart(uniqueKey)) { + return ChartMetaDataTypeEnum.GENE_SPECIFIC; + } else if (this.isGenericAssayChart(uniqueKey)) { + return ChartMetaDataTypeEnum.GENERIC_ASSAY; + } else if (this.isUserDefinedCustomDataChart(uniqueKey)) { + return ChartMetaDataTypeEnum.CUSTOM_DATA; + } else { + // Always returns CLINICAL chart if no other chart types matched + return ChartMetaDataTypeEnum.CLINICAL; + } + } + @action changeChartVisibility(uniqueKey: string, visible: boolean): void { if (visible) { @@ -3571,6 +3717,9 @@ export class StudyViewPageStore case ChartTypeEnum.CNA_GENES_TABLE: this.resetGeneFilter(chartUniqueKey); break; + case ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE: + this.updateMutationDataFilters(chartUniqueKey, [[]]); + break; case ChartTypeEnum.GENOMIC_PROFILES_TABLE: this.setGenomicProfilesFilter([]); break; @@ -3607,6 +3756,7 @@ export class StudyViewPageStore ); this.preDefinedCustomChartFilterSet.delete(chartUniqueKey); this._genomicDataFilterSet.delete(chartUniqueKey); + this._mutationDataFilterSet.delete(chartUniqueKey); this._genericAssayDataFilterSet.delete(chartUniqueKey); break; @@ -3653,6 +3803,8 @@ export class StudyViewPageStore case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: case ChartTypeEnum.CNA_GENES_TABLE: return this._geneFilterSet.has(chartUniqueKey); + case ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE: + return this._mutationDataFilterSet.has(chartUniqueKey); case ChartTypeEnum.STRUCTURAL_VARIANTS_TABLE: return this._structVarFilterSet.has(chartUniqueKey); case ChartTypeEnum.GENOMIC_PROFILES_TABLE: @@ -3951,6 +4103,10 @@ export class StudyViewPageStore return Array.from(this._genomicDataFilterSet.values()); } + @computed get mutationDataFilters(): MutationDataFilter[] { + return Array.from(this._mutationDataFilterSet.values()); + } + @computed get genericAssayDataFilters(): GenericAssayDataFilter[] { return Array.from(this._genericAssayDataFilterSet.values()); } @@ -3964,15 +4120,25 @@ export class StudyViewPageStore get filtersProxy(): StudyViewFilter { const filters: Partial = {}; - const genomicDataFilters = this.genomicDataFilters; + if (this.genomicDataFilters.length > 0) { + filters.genomicDataFilters = this.genomicDataFilters; + } - if (genomicDataFilters.length > 0) { - filters.genomicDataFilters = genomicDataFilters; + if (this.mutationDataFilters.length > 0) { + filters.mutationDataFilters = this.mutationDataFilters.map( + filter => { + return { + ...filter, + categorization: filter.categorization as + | 'MUTATED' + | 'MUTATION_TYPE', + }; + } + ); } - const genericAssayDataFilters = this.genericAssayDataFilters; - if (genericAssayDataFilters.length > 0) { - filters.genericAssayDataFilters = genericAssayDataFilters; + if (this.genericAssayDataFilters.length > 0) { + filters.genericAssayDataFilters = this.genericAssayDataFilters; } if (this.clinicalDataFilters.length > 0) { @@ -4191,6 +4357,15 @@ export class StudyViewPageStore : []; } + @autobind + public getMutationDataFiltersByUniqueKey(uniqueKey: string): string[][] { + return this._mutationDataFilterSet.has(uniqueKey) + ? this._mutationDataFilterSet.get(uniqueKey)!.values.map(value => { + return value.map(innerValue => innerValue.value); + }) + : []; + } + @autobind public getGenericAssayDataFiltersByUniqueKey( uniqueKey: string @@ -4488,6 +4663,63 @@ export class StudyViewPageStore this.changeChartVisibility(uniqueKey, false); } + public addColorToCategories( + counts: ClinicalDataCount[], + attributeId: string, + getDisplayedValue?: (value: string) => string, + getDisplayedColor?: (value: string) => string + ): ClinicalDataCountSummary[] { + return getClinicalDataCountWithColorByClinicalDataCount(counts).map( + item => { + if (getDisplayedValue) { + item.displayedValue = getDisplayedValue(item.value); + } + + if (getDisplayedColor) { + return { + ...item, + color: getDisplayedColor(item.value), + }; + } + + let colorMapKey = generateColorMapKey(attributeId, item.value); + // If the item doesn't has an assigned color + if (!this.chartItemToColor.has(colorMapKey)) { + // If the color has not been used + if ( + !this.chartToUsedColors + .get(attributeId) + ?.has(item.color) + ) { + this.chartItemToColor.set(colorMapKey, item.color); + this.chartToUsedColors + .get(attributeId) + ?.add(item.color); + } else { + // Pick up a new color if the color has been used + let d = { + value: item.value, + count: item.count, + }; + let newColor = pickNewColorForClinicData( + d, + this.chartToUsedColors.get(attributeId) || new Set() + ); + this.chartItemToColor.set(colorMapKey, newColor); + this.chartToUsedColors.get(attributeId)?.add(newColor); + item.color = newColor; + } + return item; + } else { + return { + ...item, + color: this.chartItemToColor.get(colorMapKey)!, + }; + } + } + ); + } + public getClinicalDataCount( chartMeta: ChartMeta ): MobxPromise { @@ -4577,66 +4809,6 @@ export class StudyViewPageStore return this.clinicalDataCountPromises[uniqueKey]; } - private addColorToCategories( - counts: ClinicalDataCount[], - attributeId: string, - getDisplayedValue?: (value: string) => string, - getDisplayedColor?: (value: string) => string - ): ClinicalDataCountSummary[] { - return getClinicalDataCountWithColorByClinicalDataCount(counts).map( - item => { - if (getDisplayedValue) { - item.displayedValue = getDisplayedValue(item.value); - } - - if (getDisplayedColor) { - return { - ...item, - color: getDisplayedColor(item.value), - }; - } - - let colorMapKey = this.generateColorMapKey( - attributeId, - item.value - ); - // If the item doesn't has an assigned color - if (!this.chartItemToColor.has(colorMapKey)) { - // If the color has not been used - if ( - !this.chartToUsedColors - .get(attributeId) - ?.has(item.color) - ) { - this.chartItemToColor.set(colorMapKey, item.color); - this.chartToUsedColors - .get(attributeId) - ?.add(item.color); - } else { - // Pick up a new color if the color has been used - let d = { - value: item.value, - count: item.count, - }; - let newColor = pickNewColorForClinicData( - d, - this.chartToUsedColors.get(attributeId) || new Set() - ); - this.chartItemToColor.set(colorMapKey, newColor); - this.chartToUsedColors.get(attributeId)?.add(newColor); - item.color = newColor; - } - return item; - } else { - return { - ...item, - color: this.chartItemToColor.get(colorMapKey)!, - }; - } - } - ); - } - public getCustomDataCount( chartMeta: ChartMeta ): MobxPromise { @@ -4752,7 +4924,7 @@ export class StudyViewPageStore invoke: async () => { let resultDataBins: DataBin[] = []; if (!this._customDataBinFilterSet.has(uniqueKey)) { - const attribute: ClinicalDataBinFilter = this.getDefaultClinicalDataBinFilter( + const attribute: ClinicalDataBinFilter = getDefaultClinicalDataBinFilter( chartMeta.clinicalAttribute! ); const result = await internalClient.fetchCustomDataBinCountsUsingPOST( @@ -4850,47 +5022,31 @@ export class StudyViewPageStore ] = remoteData({ await: () => [], invoke: async () => { - let res: ClinicalDataCountSummary[] = []; + const res: ClinicalDataCountSummary[] = []; const chartInfo = this._genericAssayChartMap.get( chartMeta.uniqueKey ); if (chartInfo) { - let result: GenericAssayDataCountItem[] = []; - - result = await internalClient.fetchGenericAssayDataCountsUsingPOST( - { - genericAssayDataCountFilter: { - genericAssayDataFilters: [ - { - stableId: - chartInfo.genericAssayEntityId, - profileType: chartInfo.profileType, - } as GenericAssayDataFilter, - ], - studyViewFilter: this.filters, - } as GenericAssayDataCountFilter, - } + const result = await invokeGenericAssayDataCount( + chartInfo, + this.filters ); - let data = result.find( - d => d.stableId === chartInfo.genericAssayEntityId - ); - let counts: ClinicalDataCount[] = []; - let stableId: string = ''; - if (data !== undefined) { - counts = data.counts.map(c => { - return { - count: c.count, - value: c.value, - } as ClinicalDataCount; - }); - stableId = data.stableId; - if (!this.chartToUsedColors.has(stableId)) { - this.chartToUsedColors.set(stableId, new Set()); - } + if (_.isEmpty(result)) { + return res; } - return this.addColorToCategories(counts, stableId); + if (!this.chartToUsedColors.has(result!.stableId)) { + this.chartToUsedColors.set( + result!.stableId, + new Set() + ); + } + + return this.addColorToCategories( + result!.counts, + result!.stableId + ); } return res; }, @@ -4911,53 +5067,26 @@ export class StudyViewPageStore >({ await: () => [this.selectedSamples], invoke: async () => { - let res: ClinicalDataCountSummary[] = []; + const res: ClinicalDataCountSummary[] = []; const chartInfo = this._geneSpecificChartMap.get( chartMeta.uniqueKey ); //only invoke if there are filtered samples if (chartInfo && this.hasFilteredSamples) { - const result = await internalClient.fetchGenomicDataCountsUsingPOST( - { - genomicDataCountFilter: { - genomicDataFilters: [ - { - hugoGeneSymbol: - chartInfo.hugoGeneSymbol, - profileType: chartInfo.profileType, - }, - ] as any, - studyViewFilter: this.filters, - }, - } - ); - - let data = result.find( - d => - d.hugoGeneSymbol === chartInfo.hugoGeneSymbol && - d.profileType === chartInfo.profileType + const result = await invokeGenomicDataCount( + chartInfo, + this.filters ); - let counts: ClinicalDataCount[] = []; - let profileType: string = ''; - if (data !== undefined) { - counts = data.counts.map(c => { - return { - count: c.count, - value: c.value, - } as ClinicalDataCount; - }); - profileType = data.profileType; + if (_.isEmpty(result)) { + return res; } return this.addColorToCategories( - counts, - profileType, - getCNAByAlteration, - value => - getCNAColorByAlteration( - getCNAByAlteration(value) - ) + result!.counts, + result!.profileType, + result!.getDisplayedValue, + result!.getDisplayedColor ); } return res; @@ -4969,8 +5098,36 @@ export class StudyViewPageStore return this.genomicDataCountPromises[chartMeta.uniqueKey]; } - private generateColorMapKey(id: string, value: string): string { - return `${id}.${value}`; + public getMutationTypeChartDataCount( + chartMeta: ChartMeta + ): MobxPromise { + if ( + !this.mutationDataCountPromises.hasOwnProperty(chartMeta.uniqueKey) + ) { + this.mutationDataCountPromises[chartMeta.uniqueKey] = remoteData< + MultiSelectionTableRow[] + >({ + await: () => [this.selectedSamples], + invoke: async () => { + const res: MultiSelectionTableRow[] = []; + const chartInfo = this._geneSpecificChartMap.get( + chartMeta.uniqueKey + ); + // only invoke if there are filtered samples + if (chartInfo && this.hasFilteredSamples) { + return invokeMutationDataCount( + chartInfo, + this.filters, + this.selectedSamples.result.length + ); + } + return res; + }, + onError: () => {}, + default: [], + }); + } + return this.mutationDataCountPromises[chartMeta.uniqueKey]; } @autobind @@ -5583,18 +5740,6 @@ export class StudyViewPageStore ); } - private getDefaultClinicalDataBinFilter( - attribute: ClinicalAttribute - ): ClinicalDataBinFilter & { - showNA?: boolean | undefined; - } { - return { - attributeId: attribute.clinicalAttributeId, - disableLogScale: false, - showNA: false, - } as ClinicalDataBinFilter & { showNA?: boolean }; - } - readonly resourceDefinitions = remoteData({ await: () => [this.queriedPhysicalStudies], invoke: () => { @@ -5697,7 +5842,7 @@ export class StudyViewPageStore clinicalAttributes.forEach((obj: ClinicalAttribute) => { if (obj.datatype === DataType.NUMBER) { const uniqueKey = getUniqueKey(obj); - let filter = this.getDefaultClinicalDataBinFilter(obj); + let filter = getDefaultClinicalDataBinFilter(obj); if (STUDY_VIEW_CONFIG.initialBins[uniqueKey]) { filter.customBins = @@ -6238,7 +6383,8 @@ export class StudyViewPageStore newCharts.forEach(newChart => { const uniqueKey = getGenomicChartUniqueKey( newChart.hugoGeneSymbol, - newChart.profileType + newChart.profileType, + newChart.mutationOptionType ); if (this._geneSpecificChartMap.has(uniqueKey)) { @@ -6255,6 +6401,9 @@ export class StudyViewPageStore patientAttribute: false, renderWhenDataChange: false, priority: 0, + ...(newChart.mutationOptionType + ? { mutationOptionType: newChart.mutationOptionType } + : {}), }; this._geneSpecificCharts.set(uniqueKey, chartMeta); @@ -6272,6 +6421,21 @@ export class StudyViewPageStore profileType: newChart.profileType, } as any); this.chartsDimension.set(uniqueKey, { w: 2, h: 1 }); + } else if ( + newChart.mutationOptionType && + newChart.mutationOptionType === + MutationOptionConstants.MUTATION_TYPE + ) { + this.chartsType.set( + uniqueKey, + ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE + ); + this.chartsDimension.set( + uniqueKey, + STUDY_VIEW_CONFIG.layout.dimensions[ + ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE + ] + ); } else { this.chartsType.set(uniqueKey, ChartTypeEnum.PIE_CHART); this.chartsDimension.set( @@ -6769,6 +6933,9 @@ export class StudyViewPageStore if (!_.isEmpty(this.initialFilters.genomicDataFilters)) { pending = pending || this.molecularProfileOptions.isPending; } + if (!_.isEmpty(this.initialFilters.mutationDataFilters)) { + pending = pending || this.molecularProfileOptions.isPending; + } if (!_.isEmpty(this.initialFilters.genericAssayDataFilters)) { pending = pending || this.genericAssayProfileOptionsByType.isPending; @@ -7101,6 +7268,16 @@ export class StudyViewPageStore dataType: molecularProfileOption?.dataType || DataType.NUMBER, + ...(chartUserSettings.profileType === + MolecularAlterationType_filenameSuffix.MUTATION_EXTENDED + ? { + mutationOptionType: + chartUserSettings.chartType === + ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE + ? MutationOptionConstants.MUTATION_TYPE + : MutationOptionConstants.MUTATED, + } + : {}), }, ], true @@ -7701,7 +7878,7 @@ export class StudyViewPageStore invoke: async () => { return _.chain(this.defaultVisibleAttributes.result) .filter(attr => attr.datatype === DataType.NUMBER) - .map(this.getDefaultClinicalDataBinFilter) + .map(getDefaultClinicalDataBinFilter) .uniqBy(attr => attr.attributeId) .value(); }, @@ -7793,6 +7970,49 @@ export class StudyViewPageStore } ); } + + if (!_.isEmpty(this.initialFilters.mutationDataFilters)) { + const molecularProfileOptionByTypeMap = _.keyBy( + this.molecularProfileOptions.result, + molecularProfileOption => molecularProfileOption.value + ); + _.each( + this.initialFilters.mutationDataFilters, + mutationDataFilter => { + if ( + molecularProfileOptionByTypeMap[ + mutationDataFilter.profileType + ] !== undefined + ) { + const molecularProfileOption = + molecularProfileOptionByTypeMap[ + mutationDataFilter.profileType + ]; + this.addGeneSpecificCharts( + [ + { + name: `${ + mutationDataFilter.hugoGeneSymbol + }: ${mutationDataFilter.profileType}: ${ + MutationOptionConstantsLabel[ + mutationDataFilter.categorization + ] + }`, + description: molecularProfileOption.label, + profileType: mutationDataFilter.profileType, + hugoGeneSymbol: + mutationDataFilter.hugoGeneSymbol, + dataType: molecularProfileOption.dataType, + mutationOptionType: + mutationDataFilter.categorization, + }, + ], + true + ); + } + } + ); + } } @action @@ -8725,7 +8945,12 @@ export class StudyViewPageStore public async getChartAllDataDownload(chartMeta: ChartMeta) { // handle custom chart if (this.isUserDefinedCustomDataChart(chartMeta.uniqueKey)) { - return this.getCustomChartDownloadData(chartMeta); + return getCustomChartDownloadData( + chartMeta, + this.selectedSamples.result!, + this.selectedPatients, + this._customChartsSelectedCases.get(chartMeta.uniqueKey) + ); } let clinicalDataList: ClinicalData[] = []; @@ -8819,354 +9044,6 @@ export class StudyViewPageStore ); } - public getCustomChartDownloadData(chartMeta: ChartMeta): Promise { - return new Promise(resolve => { - if (chartMeta && chartMeta.uniqueKey) { - let isPatientChart = chartMeta.patientAttribute; - let header = ['Study ID', 'Patient ID']; - - if (!isPatientChart) { - header.push('Sample ID'); - } - header.push(chartMeta.displayName); - let data = [header.join('\t')]; - if ( - chartMeta.uniqueKey === - SpecialChartsUniqueKeyEnum.CANCER_STUDIES - ) { - data = data.concat( - this.selectedSamples.result!.map((sample: Sample) => { - return [ - sample.studyId || Datalabel.NA, - sample.patientId || Datalabel.NA, - sample.sampleId || Datalabel.NA, - sample.studyId, - ].join('\t'); - }) - ); - } else if ( - this._customChartsSelectedCases.has(chartMeta.uniqueKey) - ) { - if (isPatientChart) { - data = data.concat( - this.selectedPatients.map((patient: Patient) => { - let record = _.find( - this._customChartsSelectedCases.get( - chartMeta.uniqueKey - ), - ( - caseIdentifier: CustomChartIdentifierWithValue - ) => { - return ( - caseIdentifier.studyId === - patient.studyId && - patient.patientId === - caseIdentifier.patientId - ); - } - ); - return [ - patient.studyId || Datalabel.NA, - patient.patientId || Datalabel.NA, - record === undefined ? 'NA' : record.value, - ].join('\t'); - }) - ); - } else { - data = data.concat( - this.selectedSamples.result!.map( - (sample: Sample) => { - let record = _.find( - this._customChartsSelectedCases.get( - chartMeta.uniqueKey - ), - ( - caseIdentifier: CustomChartIdentifierWithValue - ) => { - return ( - caseIdentifier.studyId === - sample.studyId && - sample.sampleId === - caseIdentifier.sampleId - ); - } - ); - return [ - sample.studyId || Datalabel.NA, - sample.patientId || Datalabel.NA, - sample.sampleId || Datalabel.NA, - record === undefined - ? 'NA' - : record.value, - ].join('\t'); - } - ) - ); - } - } - - resolve(data.join('\n')); - } else { - resolve(''); - } - }); - } - - public async getScatterDownloadData( - chartUniqueKey: ChartUniqueKey - ): Promise { - const chartInfo = this.getXvsYScatterChartInfo(chartUniqueKey)!; - const selectedSamples = await toPromise(this.selectedSamples); - const [xData, yData] = await Promise.all([ - getSampleToClinicalData(selectedSamples, chartInfo.xAttr), - getSampleToClinicalData(selectedSamples, chartInfo.yAttr), - ]); - return generateXvsYScatterPlotDownloadData( - chartInfo.xAttr, - chartInfo.yAttr, - selectedSamples, - xData, - yData - ); - } - - public getSurvivalDownloadData(chartMeta: ChartMeta): string { - const matchedPlot = _.find( - this.survivalPlots.result, - plot => plot.id === chartMeta.uniqueKey - ); - if (matchedPlot && this.survivalData.result) { - const data: string[] = []; - - // find the unique clinical attribute ids - const uniqueClinicalAttributeIds = matchedPlot.associatedAttrs; - - // add the header row - data.push( - ['Study ID', 'Patient ID', ...uniqueClinicalAttributeIds].join( - '\t' - ) - ); - - // add the data rows - const selectedPatientMap = _.reduce( - this.selectedPatients, - (acc, next) => { - acc[next.uniquePatientKey] = next; - return acc; - }, - {} as { [uniquePatientKey: string]: Patient } - ); - this.selectedPatientKeys.result.forEach(uniquePatientKey => { - const clinicalDataList = this.survivalData.result[ - uniquePatientKey - ]; - const row: string[] = []; - - if (clinicalDataList && clinicalDataList.length > 0) { - row.push(clinicalDataList[0].studyId || Datalabel.NA); - row.push(clinicalDataList[0].patientId || Datalabel.NA); - const keyed = _.keyBy( - clinicalDataList, - 'clinicalAttributeId' - ); - - _.each(uniqueClinicalAttributeIds, id => { - row.push( - keyed[id] - ? keyed[id].value || Datalabel.NA - : Datalabel.NA - ); - }); - } else { - const selectedPatient = - selectedPatientMap[uniquePatientKey]; - if (selectedPatient) { - row.push(selectedPatient.studyId || Datalabel.NA); - row.push(selectedPatient.patientId || Datalabel.NA); - - _.each(uniqueClinicalAttributeIds, () => { - row.push(Datalabel.NA); - }); - } - } - - data.push(row.join('\t')); - }); - - return data.join('\n'); - } else { - return ''; - } - } - - public async getMutatedGenesDownloadData(): Promise { - if (this.mutatedGeneTableRowData.result) { - let header = [ - 'Gene', - 'MutSig(Q-value)', - '# Mut', - '#', - 'Profiled Samples', - 'Freq', - ]; - if (this.oncokbCancerGeneFilterEnabled) { - header.push('Is Cancer Gene (source: OncoKB)'); - } - let data = [header.join('\t')]; - _.each( - this.mutatedGeneTableRowData.result, - (record: MultiSelectionTableRow) => { - let rowData = [ - record.label, - record.qValue === undefined - ? '' - : getQValue(record.qValue), - record.totalCount, - record.numberOfAlteredCases, - record.numberOfProfiledCases, - getFrequencyStr( - (record.numberOfAlteredCases / - record.numberOfProfiledCases) * - 100 - ), - ]; - if (this.oncokbCancerGeneFilterEnabled) { - rowData.push( - this.oncokbCancerGeneFilterEnabled - ? record.isCancerGene - ? 'Yes' - : 'No' - : 'NA' - ); - } - data.push(rowData.join('\t')); - } - ); - return data.join('\n'); - } else return ''; - } - - public getStructuralVariantGenesDownloadData(): string { - if (this.structuralVariantGeneTableRowData.result) { - const header = [ - 'Gene', - '# Structural Variant', - '#', - 'Profiled Samples', - 'Freq', - ]; - if (this.oncokbCancerGeneFilterEnabled) { - header.push('Is Cancer Gene (source: OncoKB)'); - } - const data = [header.join('\t')]; - _.each( - this.structuralVariantGeneTableRowData.result, - (record: MultiSelectionTableRow) => { - const rowData = [ - record.label, - record.totalCount, - record.numberOfAlteredCases, - record.numberOfProfiledCases, - getFrequencyStr( - (record.numberOfAlteredCases / - record.numberOfProfiledCases) * - 100 - ), - ]; - if (this.oncokbCancerGeneFilterEnabled) { - rowData.push( - this.oncokbCancerGeneFilterEnabled - ? record.isCancerGene - ? 'Yes' - : 'No' - : 'NA' - ); - } - data.push(rowData.join('\t')); - } - ); - return data.join('\n'); - } else return ''; - } - - public async getGenesCNADownloadData(): Promise { - if (this.cnaGeneTableRowData.result) { - let header = [ - 'Gene', - 'Gistic(Q-value)', - 'Cytoband', - 'CNA', - 'Profiled Samples', - '#', - 'Freq', - ]; - if (this.oncokbCancerGeneFilterEnabled) { - header.push('Is Cancer Gene (source: OncoKB)'); - } - let data = [header.join('\t')]; - _.each( - this.cnaGeneTableRowData.result, - (record: MultiSelectionTableRow) => { - let rowData = [ - record.label, - record.qValue === undefined - ? '' - : getQValue(record.qValue), - record.cytoband, - getCNAByAlteration(record.alteration!), - record.numberOfAlteredCases, - record.numberOfProfiledCases, - getFrequencyStr( - (record.numberOfAlteredCases / - record.numberOfProfiledCases) * - 100 - ), - ]; - if (this.oncokbCancerGeneFilterEnabled) { - rowData.push( - this.oncokbCancerGeneFilterEnabled - ? record.isCancerGene - ? 'Yes' - : 'No' - : 'NA' - ); - } - data.push(rowData.join('\t')); - } - ); - return data.join('\n'); - } else return ''; - } - public async getPatientTreatmentDownloadData(): Promise { - if (this.patientTreatments.result) { - const header = ['Treatment', '#']; - let data = [header.join('\t')]; - _.each( - this.patientTreatments.result, - (record: PatientTreatmentRow) => { - let rowData = [record.treatment, record.count]; - data.push(rowData.join('\t')); - } - ); - return data.join('\n'); - } else return ''; - } - public async getSampleTreatmentDownloadData(): Promise { - if (this.sampleTreatments.result) { - const header = ['Treatment', 'Pre/Post', '#']; - let data = [header.join('\t')]; - _.each( - this.sampleTreatments.result, - (record: SampleTreatmentRow) => { - let rowData = [record.treatment, record.time, record.count]; - data.push(rowData.join('\t')); - } - ); - return data.join('\n'); - } else return ''; - } - readonly survivalEntryMonths = remoteData< { [uniquePatientKey: string]: number } | undefined >({ @@ -9431,6 +9308,7 @@ export class StudyViewPageStore AlterationTypeConstants.PROTEIN_LEVEL, AlterationTypeConstants.METHYLATION, AlterationTypeConstants.COPY_NUMBER_ALTERATION, + AlterationTypeConstants.MUTATION_EXTENDED, ].includes(molecularProfile.molecularAlterationType); }); }, @@ -9448,8 +9326,10 @@ export class StudyViewPageStore if (!acc.has(profileType)) { acc.set( profileType, - DataTypeConstants.DISCRETE === - molecularProfile.datatype + [ + DataTypeConstants.DISCRETE, + DataTypeConstants.MAF, + ].includes(molecularProfile.datatype) ? DataType.STRING : DataType.NUMBER ); @@ -9466,10 +9346,16 @@ export class StudyViewPageStore await: () => [ this.molecularProfileTypeToDataType, this.molecularProfileSampleCounts, + this.molecularProfiles, ], invoke: async () => { const profileTypeToDataTypeSet = this.molecularProfileTypeToDataType .result; + const profileTypeToAlterationTypeSet = _.keyBy( + this.molecularProfiles.result, + molecularProfile => + getSuffixOfMolecularProfile(molecularProfile) + ); return this.molecularProfileSampleCounts.result .filter(datum => profileTypeToDataTypeSet.has(datum.uniqueKey)) @@ -9482,6 +9368,9 @@ export class StudyViewPageStore dataType: profileTypeToDataTypeSet.get(datum.uniqueKey) || DataType.STRING, + alterationType: + profileTypeToAlterationTypeSet[datum.uniqueKey] + .molecularAlterationType, }; }); }, @@ -10232,7 +10121,8 @@ export class StudyViewPageStore let count = 0; if (this.molecularProfileSampleCountSet.result !== undefined) { switch (chartType) { - case ChartTypeEnum.MUTATED_GENES_TABLE: { + case ChartTypeEnum.MUTATED_GENES_TABLE: + case ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE: { count = this.molecularProfileSampleCountSet.result[ MolecularAlterationType_filenameSuffix.MUTATION_EXTENDED! ] diff --git a/src/pages/studyView/StudyViewUtils.tsx b/src/pages/studyView/StudyViewUtils.tsx index 0816244612e..cb62d6c4e44 100644 --- a/src/pages/studyView/StudyViewUtils.tsx +++ b/src/pages/studyView/StudyViewUtils.tsx @@ -17,15 +17,19 @@ import { GenePanelData, GenericAssayData, GenericAssayDataBin, + GenericAssayDataFilter, GenericAssayDataMultipleStudyFilter, GenomicDataBin, GenomicDataCount, MolecularDataMultipleStudyFilter, MolecularProfile, NumericGeneMolecularData, + Patient, PatientIdentifier, + PatientTreatmentRow, Sample, SampleIdentifier, + SampleTreatmentRow, StructuralVariantFilterQuery, StudyViewFilter, } from 'cbioportal-ts-api-client'; @@ -36,6 +40,7 @@ import { BinMethodOption, GenericAssayChart, GenomicChart, + SurvivalType, XvsYChartSettings, XvsYScatterChart, XvsYViolinChart, @@ -65,7 +70,13 @@ import { stringListToIndexSet, toPromise, } from 'cbioportal-frontend-commons'; -import { DEFAULT_NA_COLOR, getClinicalValueColor } from 'shared/lib/Colors'; +import { + CLI_NO_COLOR, + CLI_YES_COLOR, + DEFAULT_NA_COLOR, + DEFAULT_UNKNOWN_COLOR, + getClinicalValueColor, +} from 'shared/lib/Colors'; import { StudyViewComparisonGroup } from '../groupComparison/GroupComparisonUtils'; import styles from './styles.module.scss'; import { getGroupParameters } from 'pages/groupComparison/comparisonGroupManager/ComparisonGroupManagerUtils'; @@ -88,10 +99,21 @@ import { import { getServerConfig } from 'config/config'; import joinJsx from 'shared/lib/joinJsx'; import { BoundType, NumberRange } from 'range-ts'; -import { ClinicalEventTypeCount } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPIInternal'; +import { + ClinicalEventTypeCount, + GenericAssayDataCountFilter, + GenericAssayDataCountItem, + GenomicDataCountFilter, + GenomicDataFilter, +} from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPIInternal'; import { queryContainsStructVarAlteration } from 'shared/lib/oql/oqlfilter'; import { toast } from 'react-toastify'; import { useCallback } from 'react'; +import { MutationOptionConstants } from 'shared/constants'; +import { MolecularAlterationType_filenameSuffix } from 'shared/lib/StoreUtils'; +import { MultiSelectionTableRow } from './table/MultiSelectionTable'; +import Survival from 'pages/groupComparison/Survival'; +import { StructVarMultiSelectionTableRow } from './table/StructuralVariantMultiSelectionTable'; // Cannot use ClinicalDataTypeEnum here for the strong type. The model in the type is not strongly typed export enum ClinicalDataTypeEnum { @@ -151,6 +173,7 @@ export enum ChartMetaDataTypeEnum { export type ChartMeta = { clinicalAttribute?: ClinicalAttribute; genericAssayType?: string; + mutationOptionType?: string; uniqueKey: string; displayName: string; description: string; @@ -189,6 +212,7 @@ export type MolecularProfileOption = { label: string; description: string; dataType: string; + alterationType: string; patientLevel?: boolean; }; @@ -201,6 +225,8 @@ export type DataBin = { start: number; }; +export type MutationCategorization = 'MUTATED' | 'MUTATION_TYPE'; + export const SPECIAL_CHARTS: ChartMetaWithDimensionAndChartType[] = [ { uniqueKey: SpecialChartsUniqueKeyEnum.CANCER_STUDIES, @@ -786,9 +812,12 @@ export function getUniqueKey(attribute: ClinicalAttribute): string { export function getGenomicChartUniqueKey( hugoGeneSymbol: string, - profileType: string + profileType: string, + mutationOptionType?: string ): string { - return hugoGeneSymbol + '_' + profileType; + return mutationOptionType + ? hugoGeneSymbol + '_' + profileType + '_' + mutationOptionType + : hugoGeneSymbol + '_' + profileType; } export function getGenericAssayChartUniqueKey( @@ -989,6 +1018,28 @@ export function getVirtualStudyDescription( } }); + _.each(filter.mutationDataFilters || [], mutationDataFilter => { + const uniqueKey = getGenomicChartUniqueKey( + mutationDataFilter.hugoGeneSymbol, + mutationDataFilter.profileType, + mutationDataFilter.categorization + ); + + const name = attributeNamesSet[uniqueKey]; + + if (name) { + _.each(mutationDataFilter.values || [], value => { + filterLines.push( + `- ${name}: ${intervalFiltersDisplayValue( + value, + () => {}, + true + )}` + ); + }); + } + }); + _.each( filter.genericAssayDataFilters || [], genericAssayDataFilters => { @@ -1043,6 +1094,7 @@ export function isFiltered( _.isEmpty(filter.structuralVariantFilters) && _.isEmpty(filter.genomicProfiles) && _.isEmpty(filter.genomicDataFilters) && + _.isEmpty(filter.mutationDataFilters) && _.isEmpty(filter.genericAssayDataFilters) && _.isEmpty(filter.caseLists) && _.isEmpty(filter.customDataFilters) && @@ -1860,6 +1912,32 @@ export function getCNAColorByAlteration(alteration: string): string { } } +export function transformMutatedType(type: string): string { + if (type === undefined || type === 'NA') return type; + + // Split the input string by underscores + var words = type.split('_'); + + // Capitalize the first letter of each word + var capitalizedWords = words.map(_.capitalize); + + // Join the words back together with a space between them + return capitalizedWords.join(' '); +} + +export function getMutationColorByCategorization(type: string): string { + switch (type) { + case 'Mutated': + return CLI_YES_COLOR; + case 'Not Mutated': + return CLI_NO_COLOR; + case 'Not Profiled': + return DEFAULT_NA_COLOR; + default: + return DEFAULT_UNKNOWN_COLOR; + } +} + export function getDefaultChartTypeByClinicalAttribute( clinicalAttribute: ClinicalAttribute ): ChartType | undefined { @@ -4006,3 +4084,543 @@ function ToastSuppressor(props: any) { ); } + +export function getDefaultClinicalDataBinFilter( + attribute: ClinicalAttribute +): ClinicalDataBinFilter & { + showNA?: boolean | undefined; +} { + return { + attributeId: attribute.clinicalAttributeId, + disableLogScale: false, + showNA: false, + } as ClinicalDataBinFilter & { showNA?: boolean }; +} + +export function generateColorMapKey(id: string, value: string): string { + return `${id}.${value}`; +} + +export async function invokeGenericAssayDataCount( + chartInfo: GenericAssayChart, + filters: StudyViewFilter +) { + const result: GenericAssayDataCountItem[] = await internalClient.fetchGenericAssayDataCountsUsingPOST( + { + genericAssayDataCountFilter: { + genericAssayDataFilters: [ + { + stableId: chartInfo.genericAssayEntityId, + profileType: chartInfo.profileType, + } as GenericAssayDataFilter, + ], + studyViewFilter: filters, + } as GenericAssayDataCountFilter, + } + ); + + let data = result.find(d => d.stableId === chartInfo.genericAssayEntityId); + let counts: ClinicalDataCount[] = []; + let stableId: string = ''; + if (data !== undefined) { + counts = data.counts.map(c => { + return { + count: c.count, + value: c.value, + } as ClinicalDataCount; + }); + stableId = data.stableId; + + return { stableId: stableId, counts: counts }; + } + + return undefined; +} + +export async function invokeGenomicDataCount( + chartInfo: GenomicChart, + filters: StudyViewFilter +) { + let result = []; + let getDisplayedValue; + let getDisplayedColor; + let params = { + genomicDataCountFilter: { + genomicDataFilters: [ + { + hugoGeneSymbol: chartInfo.hugoGeneSymbol, + profileType: chartInfo.profileType, + } as GenomicDataFilter, + ], + studyViewFilter: filters, + }, + } as any; + + // mutation data counts (pie chart) + if ( + chartInfo.profileType === + MolecularAlterationType_filenameSuffix.MUTATION_EXTENDED + ) { + params = { + ...params, + $queryParameters: { + projection: 'SUMMARY', + }, + }; + result = await internalClient.fetchMutationDataCountsUsingPOST(params); + getDisplayedValue = transformMutatedType; + getDisplayedColor = (value: string) => + getMutationColorByCategorization(transformMutatedType(value)); + } else { + result = await internalClient.fetchGenomicDataCountsUsingPOST(params); + getDisplayedValue = getCNAByAlteration; + getDisplayedColor = (value: string | number) => + getCNAColorByAlteration(getCNAByAlteration(value)); + } + + const data = result.find( + d => + d.hugoGeneSymbol === chartInfo.hugoGeneSymbol && + d.profileType === chartInfo.profileType + ); + + let counts: ClinicalDataCount[] = []; + let profileType: string = ''; + if (data !== undefined) { + counts = data.counts.map(c => { + return { + count: c.count, + value: c.value, + } as ClinicalDataCount; + }); + profileType = data.profileType; + + return { + counts: counts, + profileType: profileType, + getDisplayedValue: getDisplayedValue, + getDisplayedColor: getDisplayedColor, + }; + } + + return undefined; +} + +export async function invokeMutationDataCount( + chartInfo: GenomicChart, + filters: StudyViewFilter, + profiledCases: number +) { + const params = { + genomicDataCountFilter: { + genomicDataFilters: [ + { + hugoGeneSymbol: chartInfo.hugoGeneSymbol, + profileType: chartInfo.profileType, + } as GenomicDataFilter, + ], + studyViewFilter: filters, + }, + $queryParameters: { + projection: 'DETAILED', + }, + } as any; + + const result = await internalClient.fetchMutationDataCountsUsingPOST( + params + ); + + const data = result.find( + d => + d.hugoGeneSymbol === chartInfo.hugoGeneSymbol && + d.profileType === chartInfo.profileType + ); + + let counts: MultiSelectionTableRow[] = []; + if (data !== undefined) { + counts = data.counts.map(c => { + return { + uniqueKey: c.value, + label: c.label, + // "Altered" and "Profiled" really just mean + // "numerator" and "denominator" in percent + // calculation of table. Here, they mean + // "# filtered samples in profile" and "# filtered samples overall" + numberOfAlteredCases: c.uniqueCount, + numberOfProfiledCases: profiledCases, + totalCount: c.count, + } as MultiSelectionTableRow; + }); + } + + return counts; +} + +export async function getCustomChartDownloadData( + chartMeta: ChartMeta, + selectedSamples: Sample[], + selectedPatients: Patient[], + caseIdentifiers?: CustomChartIdentifierWithValue[] +): Promise { + return new Promise(resolve => { + if (chartMeta && chartMeta.uniqueKey) { + let isPatientChart = chartMeta.patientAttribute; + let header = ['Study ID', 'Patient ID']; + + if (!isPatientChart) { + header.push('Sample ID'); + } + header.push(chartMeta.displayName); + let data = [header.join('\t')]; + if ( + chartMeta.uniqueKey === + SpecialChartsUniqueKeyEnum.CANCER_STUDIES + ) { + data = data.concat( + selectedSamples.map((sample: Sample) => { + return [ + sample.studyId || Datalabel.NA, + sample.patientId || Datalabel.NA, + sample.sampleId || Datalabel.NA, + sample.studyId, + ].join('\t'); + }) + ); + } else if (!_.isEmpty(caseIdentifiers)) { + if (isPatientChart) { + data = data.concat( + selectedPatients.map((patient: Patient) => { + let record = _.find( + caseIdentifiers, + ( + caseIdentifier: CustomChartIdentifierWithValue + ) => { + return ( + caseIdentifier.studyId === + patient.studyId && + patient.patientId === + caseIdentifier.patientId + ); + } + ); + return [ + patient.studyId || Datalabel.NA, + patient.patientId || Datalabel.NA, + record === undefined ? 'NA' : record.value, + ].join('\t'); + }) + ); + } else { + data = data.concat( + selectedSamples.map((sample: Sample) => { + let record = _.find( + caseIdentifiers, + ( + caseIdentifier: CustomChartIdentifierWithValue + ) => { + return ( + caseIdentifier.studyId === + sample.studyId && + sample.sampleId === + caseIdentifier.sampleId + ); + } + ); + return [ + sample.studyId || Datalabel.NA, + sample.patientId || Datalabel.NA, + sample.sampleId || Datalabel.NA, + record === undefined ? 'NA' : record.value, + ].join('\t'); + }) + ); + } + } + + resolve(data.join('\n')); + } else { + resolve(''); + } + }); +} + +export async function getScatterDownloadData( + chartInfo: XvsYScatterChart, + promise: MobxPromise +): Promise { + const selectedSamples = await toPromise(promise); + const [xData, yData] = await Promise.all([ + getSampleToClinicalData(selectedSamples, chartInfo.xAttr), + getSampleToClinicalData(selectedSamples, chartInfo.yAttr), + ]); + return generateXvsYScatterPlotDownloadData( + chartInfo.xAttr, + chartInfo.yAttr, + selectedSamples, + xData, + yData + ); +} + +export function getSurvivalDownloadData( + chartMeta: ChartMeta, + survivalPlots: SurvivalType[], + survivalDataMap: { [id: string]: ClinicalData[] }, + selectedPatients: Patient[], + selectedPatientKeys: string[] +): string { + const matchedPlot = _.find( + survivalPlots, + plot => plot.id === chartMeta.uniqueKey + ); + if (matchedPlot && survivalDataMap) { + const data: string[] = []; + + // find the unique clinical attribute ids + const uniqueClinicalAttributeIds = matchedPlot.associatedAttrs; + + // add the header row + data.push( + ['Study ID', 'Patient ID', ...uniqueClinicalAttributeIds].join('\t') + ); + + // add the data rows + const selectedPatientMap = _.reduce( + selectedPatients, + (acc, next) => { + acc[next.uniquePatientKey] = next; + return acc; + }, + {} as { [uniquePatientKey: string]: Patient } + ); + selectedPatientKeys.forEach(uniquePatientKey => { + const clinicalDataList = survivalDataMap[uniquePatientKey]; + const row: string[] = []; + + if (clinicalDataList && clinicalDataList.length > 0) { + row.push(clinicalDataList[0].studyId || Datalabel.NA); + row.push(clinicalDataList[0].patientId || Datalabel.NA); + const keyed = _.keyBy(clinicalDataList, 'clinicalAttributeId'); + + _.each(uniqueClinicalAttributeIds, id => { + row.push( + keyed[id] + ? keyed[id].value || Datalabel.NA + : Datalabel.NA + ); + }); + } else { + const selectedPatient = selectedPatientMap[uniquePatientKey]; + if (selectedPatient) { + row.push(selectedPatient.studyId || Datalabel.NA); + row.push(selectedPatient.patientId || Datalabel.NA); + + _.each(uniqueClinicalAttributeIds, () => { + row.push(Datalabel.NA); + }); + } + } + + data.push(row.join('\t')); + }); + + return data.join('\n'); + } else { + return ''; + } +} + +export async function getMutatedGenesDownloadData( + promise: MobxPromise, + oncokbCancerGeneFilterEnabled: boolean +): Promise { + if (promise.result) { + let header = [ + 'Gene', + 'MutSig(Q-value)', + '# Mut', + '#', + 'Profiled Samples', + 'Freq', + ]; + if (oncokbCancerGeneFilterEnabled) { + header.push('Is Cancer Gene (source: OncoKB)'); + } + let data = [header.join('\t')]; + _.each(promise.result, (record: MultiSelectionTableRow) => { + let rowData = [ + record.label, + record.qValue === undefined ? '' : getQValue(record.qValue), + record.totalCount, + record.numberOfAlteredCases, + record.numberOfProfiledCases, + getFrequencyStr( + (record.numberOfAlteredCases / + record.numberOfProfiledCases) * + 100 + ), + ]; + if (oncokbCancerGeneFilterEnabled) { + rowData.push( + oncokbCancerGeneFilterEnabled + ? record.isCancerGene + ? 'Yes' + : 'No' + : 'NA' + ); + } + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} + +export function getStructuralVariantGenesDownloadData( + promise: + | MobxPromise + | MobxPromise, + oncokbCancerGeneFilterEnabled: boolean +): string { + if (promise.result) { + const header = [ + 'Gene', + '# Structural Variant', + '#', + 'Profiled Samples', + 'Freq', + ]; + if (oncokbCancerGeneFilterEnabled) { + header.push('Is Cancer Gene (source: OncoKB)'); + } + const data = [header.join('\t')]; + _.each(promise.result, (record: MultiSelectionTableRow) => { + const rowData = [ + record.label, + record.totalCount, + record.numberOfAlteredCases, + record.numberOfProfiledCases, + getFrequencyStr( + (record.numberOfAlteredCases / + record.numberOfProfiledCases) * + 100 + ), + ]; + if (oncokbCancerGeneFilterEnabled) { + rowData.push( + oncokbCancerGeneFilterEnabled + ? record.isCancerGene + ? 'Yes' + : 'No' + : 'NA' + ); + } + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} + +export async function getGenesCNADownloadData( + promise: MobxPromise, + oncokbCancerGeneFilterEnabled: boolean +): Promise { + if (promise.result) { + let header = [ + 'Gene', + 'Gistic(Q-value)', + 'Cytoband', + 'CNA', + 'Profiled Samples', + '#', + 'Freq', + ]; + if (oncokbCancerGeneFilterEnabled) { + header.push('Is Cancer Gene (source: OncoKB)'); + } + let data = [header.join('\t')]; + _.each(promise.result, (record: MultiSelectionTableRow) => { + let rowData = [ + record.label, + record.qValue === undefined ? '' : getQValue(record.qValue), + record.cytoband, + getCNAByAlteration(record.alteration!), + record.numberOfAlteredCases, + record.numberOfProfiledCases, + getFrequencyStr( + (record.numberOfAlteredCases / + record.numberOfProfiledCases) * + 100 + ), + ]; + if (oncokbCancerGeneFilterEnabled) { + rowData.push( + oncokbCancerGeneFilterEnabled + ? record.isCancerGene + ? 'Yes' + : 'No' + : 'NA' + ); + } + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} + +export async function getPatientTreatmentDownloadData( + promise: MobxPromise +): Promise { + if (promise.result) { + const header = ['Treatment', '#']; + let data = [header.join('\t')]; + _.each(promise.result, (record: PatientTreatmentRow) => { + let rowData = [record.treatment, record.count]; + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} + +export async function getSampleTreatmentDownloadData( + promise: MobxPromise +): Promise { + if (promise.result) { + const header = ['Treatment', 'Pre/Post', '#']; + let data = [header.join('\t')]; + _.each(promise.result, (record: SampleTreatmentRow) => { + let rowData = [record.treatment, record.time, record.count]; + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} + +export async function getMutationTypesDownloadData( + promise: MobxPromise +): Promise { + if (promise.result) { + let header = [ + 'Mutation Event', + '# Mut', + '#', + 'Profiled Samples', + 'Freq', + ]; + let data = [header.join('\t')]; + _.each(promise.result, (record: MultiSelectionTableRow) => { + let rowData = [ + record.label, + record.totalCount, + record.numberOfAlteredCases, + record.numberOfProfiledCases, + getFrequencyStr( + (record.numberOfAlteredCases / + record.numberOfProfiledCases) * + 100 + ), + ]; + data.push(rowData.join('\t')); + }); + return data.join('\n'); + } else return ''; +} diff --git a/src/pages/studyView/UserSelections.tsx b/src/pages/studyView/UserSelections.tsx index 790d66622df..cea8a51a53e 100644 --- a/src/pages/studyView/UserSelections.tsx +++ b/src/pages/studyView/UserSelections.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import _ from 'lodash'; +import _, { filter } from 'lodash'; import { observer } from 'mobx-react'; import { computed, makeObservable, runInAction } from 'mobx'; import styles from './styles.module.scss'; @@ -77,6 +77,7 @@ export interface IUserSelectionsProps { uniqueKey: string, values: DataFilterValue[] ) => void; + removeMutationDataFilter: (uniqueKey: string, value: string) => void; updateGenericAssayDataFilter: ( uniqueKey: string, values: DataFilterValue[] @@ -220,7 +221,7 @@ export default class UserSelections extends React.Component< } ); - // Genomic Bar chart filters + // Genomic chart filters _.reduce( this.props.filter.genomicDataFilters || [], (acc, genomicDataFilter) => { @@ -290,6 +291,62 @@ export default class UserSelections extends React.Component< components ); + // Mutation chart filters + _.reduce( + this.props.filter.mutationDataFilters || [], + (acc, mutationDataFilter) => { + const uniqueKey = getGenomicChartUniqueKey( + mutationDataFilter.hugoGeneSymbol, + mutationDataFilter.profileType, + mutationDataFilter.categorization + ); + const chartMeta = this.props.attributesMetaSet[uniqueKey]; + if (chartMeta) { + const filters = mutationDataFilter.values.map( + dataFilterValues => { + return ( + 1} + /> + ); + } + ); + + acc.push( +
+ + {chartMeta.displayName} + , + 1} + />, + ]} + operation={':'} + group={false} + /> +
+ ); + } + return acc; + }, + components + ); + // Generic Assay chart filters let genericAssayFilterComponents: JSX.Element[] = []; if (this.props.filter.genericAssayDataFilters) { @@ -969,6 +1026,36 @@ export default class UserSelections extends React.Component< }); } + private groupedMutationDataFilters( + dataFilterValues: DataFilterValue[], + chartMeta: ChartMeta & { chartType: ChartType } + ): JSX.Element { + return ( + { + return ( + + this.props.removeMutationDataFilter( + chartMeta.uniqueKey, + dataFilterValue.value + ) + } + store={this.props.store} + /> + ); + })} + operation={'or'} + group={false} + /> + ); + } + submitHesitantFilters() { this.props.store.submitQueuedFilterUpdates(); } diff --git a/src/pages/studyView/addChartButton/AddChartButton.tsx b/src/pages/studyView/addChartButton/AddChartButton.tsx index 9fd8215ef84..5fb6dd2f153 100644 --- a/src/pages/studyView/addChartButton/AddChartButton.tsx +++ b/src/pages/studyView/addChartButton/AddChartButton.tsx @@ -849,7 +849,8 @@ class AddChartTabs extends React.Component { if (charts.length === 1) { const uniqueKey = getGenomicChartUniqueKey( charts[0].hugoGeneSymbol, - charts[0].profileType + charts[0].profileType, + charts[0].mutationOptionType ); this.updateInfoMessage( `${charts[0].name} ${ diff --git a/src/pages/studyView/addChartButton/geneLevelSelection/GeneLevelSelection.tsx b/src/pages/studyView/addChartButton/geneLevelSelection/GeneLevelSelection.tsx index ffe9a2556af..d4b9a06958a 100644 --- a/src/pages/studyView/addChartButton/geneLevelSelection/GeneLevelSelection.tsx +++ b/src/pages/studyView/addChartButton/geneLevelSelection/GeneLevelSelection.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import _ from 'lodash'; +import _, { List } from 'lodash'; import { GenomicChart } from 'pages/studyView/StudyViewPageStore'; import { observer } from 'mobx-react'; import { action, computed, makeObservable, observable } from 'mobx'; @@ -17,6 +17,12 @@ import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicato import ErrorMessage from 'shared/components/ErrorMessage'; import { Gene } from 'cbioportal-ts-api-client'; import { MolecularProfileOption } from 'pages/studyView/StudyViewUtils'; +import { + AlterationTypeConstants, + MutationOptionConstants, + MutationOptionConstantsLabel, +} from 'shared/constants'; +import autobind from 'autobind-decorator'; export interface IGeneLevelSelectionProps { molecularProfileOptionsPromise: MobxPromise; @@ -25,6 +31,20 @@ export interface IGeneLevelSelectionProps { containerWidth: number; } +const molecularProfileSubOptions = [ + { + value: MutationOptionConstants.MUTATED, + label: MutationOptionConstantsLabel[MutationOptionConstants.MUTATED], + profileType: AlterationTypeConstants.MUTATION_EXTENDED, + }, + { + value: MutationOptionConstants.MUTATION_TYPE, + label: + MutationOptionConstantsLabel[MutationOptionConstants.MUTATION_TYPE], + profileType: AlterationTypeConstants.MUTATION_EXTENDED, + }, +]; + @observer export default class GeneLevelSelection extends React.Component< IGeneLevelSelectionProps, @@ -40,6 +60,12 @@ export default class GeneLevelSelection extends React.Component< profileName: string; description: string; dataType: string; + alterationType: string; + }; + + @observable private _selectedSubProfileOption?: { + value: string; + label: string; }; @observable private _oql?: { @@ -61,14 +87,14 @@ export default class GeneLevelSelection extends React.Component< if (this.selectedOption !== undefined) { const charts = this.validGenes.map(gene => { return { - name: - gene.hugoGeneSymbol + - ': ' + - this.selectedOption!.profileName, + name: this.getChartName(gene.hugoGeneSymbol), description: this.selectedOption!.description, profileType: this.selectedOption!.value, hugoGeneSymbol: gene.hugoGeneSymbol, dataType: this.selectedOption!.dataType, + ...(this.selectedSubOption + ? { mutationOptionType: this.selectedSubOption.value } + : {}), }; }); this.props.onSubmit(charts); @@ -80,6 +106,31 @@ export default class GeneLevelSelection extends React.Component< if (option && option.value) { this._selectedProfileOption = option; } + + if ( + !molecularProfileSubOptions + .map(subOption => subOption.label) + .includes(option.alterationType) + ) { + this._selectedSubProfileOption = undefined; + } + } + + @action.bound + private handleSubSelect(option: any) { + if (option && option.value) { + this._selectedSubProfileOption = option; + } + } + + @autobind + private getChartName(hugoGeneSymbol: string): string { + return ( + hugoGeneSymbol + + ': ' + + this.selectedOption!.profileName + + (this.selectedSubOption ? ': ' + this.selectedSubOption!.label : '') + ); } @computed @@ -93,6 +144,24 @@ export default class GeneLevelSelection extends React.Component< return undefined; } + @computed + private get selectedSubOption() { + if (this._selectedSubProfileOption !== undefined) { + return this._selectedSubProfileOption; + } + + if ( + this.selectedOption !== undefined && + molecularProfileSubOptions + .map(option => option.profileType) + .includes(this.selectedOption.alterationType) + ) { + return molecularProfileSubOptions[0]; + } + + return undefined; + } + @computed private get isQueryInvalid() { return ( @@ -175,7 +244,8 @@ export default class GeneLevelSelection extends React.Component<
+ + {this.selectedOption && + molecularProfileSubOptions + .map(option => option.profileType) + .includes(this.selectedOption.alterationType) && ( +
+ +
+ )} + {/*
*/} diff --git a/src/pages/studyView/charts/ChartContainer.tsx b/src/pages/studyView/charts/ChartContainer.tsx index eaad4c0e4ae..ab3c4ea2c8f 100644 --- a/src/pages/studyView/charts/ChartContainer.tsx +++ b/src/pages/studyView/charts/ChartContainer.tsx @@ -395,7 +395,8 @@ export class ChartContainer extends React.Component { return ( this.props.promise.isComplete && this.props.promise.result!.length > 1 && - COMPARISON_CHART_TYPES.indexOf(this.props.chartType) > -1 + COMPARISON_CHART_TYPES.indexOf(this.props.chartType) > -1 && + !this.props.chartMeta.mutationOptionType ); } @@ -486,7 +487,7 @@ export class ChartContainer extends React.Component { } @computed get comparisonButtonForTables() { - if (this.selectedRowsKeys!.length >= 2) { + if (this.selectedRowsKeys!.length >= 2 && this.comparisonPagePossible) { return { content: (
{ )} ref={this.handlers.ref} onUserSelection={this.handlers.onValueSelection} - openComparisonPage={this.openComparisonPage} + openComparisonPage={ + this.comparisonPagePossible + ? this.openComparisonPage + : undefined + } filters={this.props.filters} data={this.props.promise.result} placement={this.placement} @@ -661,6 +666,73 @@ export class ChartContainer extends React.Component { ); }; } + case ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE: { + return () => { + const numColumn: MultiSelectionTableColumn = { + columnKey: MultiSelectionTableColumnKey.NUMBER, + }; + if (this.props.store.isGlobalMutationFilterActive) { + numColumn.columnTooltip = ( + + Total number of mutations +
+ This table is filtered based on selections in + the Alteration Filter menu. +
+ ); + } + + return ( + + ); + }; + } case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: { return () => { const numColumn: MultiSelectionTableColumn = { diff --git a/src/pages/studyView/studyPageHeader/StudyPageHeader.tsx b/src/pages/studyView/studyPageHeader/StudyPageHeader.tsx index d24859600ae..2fc3db07271 100644 --- a/src/pages/studyView/studyPageHeader/StudyPageHeader.tsx +++ b/src/pages/studyView/studyPageHeader/StudyPageHeader.tsx @@ -95,6 +95,9 @@ export default class StudyPageHeader extends React.Component< updateGenomicDataFilter={ this.props.store.updateGenomicDataFiltersByValues } + removeMutationDataFilter={ + this.props.store.removeMutationDataFilter + } updateGenericAssayDataFilter={ this.props.store .updateGenericAssayDataFiltersByValues diff --git a/src/pages/studyView/table/MultiSelectionTable.tsx b/src/pages/studyView/table/MultiSelectionTable.tsx index 4cb9ad68a54..c2b17617273 100644 --- a/src/pages/studyView/table/MultiSelectionTable.tsx +++ b/src/pages/studyView/table/MultiSelectionTable.tsx @@ -56,6 +56,7 @@ export enum MultiSelectionTableColumnKey { GENE = 'Gene', MOLECULAR_PROFILE = 'Molecular Profile', CASE_LIST = 'Name', + MUTATION_TYPE = 'Mutation Type', NUMBER_STRUCTURAL_VARIANTS = '# SV', NUMBER_MUTATIONS = '# Mut', CYTOBAND = 'Cytoband', @@ -79,8 +80,8 @@ export type BaseMultiSelectionTableProps = { onChangeSelectedRows: (rowsKeys: string[]) => void; selectedRowsKeys: string[]; cancerGeneFilterEnabled?: boolean; - genePanelCache: MobxPromiseCache<{ genePanelId: string }, GenePanel>; - filterByCancerGenes: boolean; + genePanelCache?: MobxPromiseCache<{ genePanelId: string }, GenePanel>; + filterByCancerGenes?: boolean; onChangeCancerGeneFilter: (filtered: boolean) => void; alterationFilterEnabled?: boolean; filterAlterations?: boolean; @@ -92,8 +93,8 @@ export type MultiSelectionTableProps = BaseMultiSelectionTableProps & { extraButtons?: IFixedHeaderTableProps< MultiSelectionTableRow >['extraButtons']; - selectedGenes: string[]; - onGeneSelect: (hugoGeneSymbol: string) => void; + selectedGenes?: string[]; + onGeneSelect?: (hugoGeneSymbol: string) => void; columns: MultiSelectionTableColumn[]; promise: MobxPromise; }; @@ -104,6 +105,7 @@ const DEFAULT_COLUMN_WIDTH_RATIO: { [MultiSelectionTableColumnKey.GENE]: 0.35, [MultiSelectionTableColumnKey.MOLECULAR_PROFILE]: 0.6, [MultiSelectionTableColumnKey.CASE_LIST]: 0.6, + [MultiSelectionTableColumnKey.MUTATION_TYPE]: 0.35, [MultiSelectionTableColumnKey.NUMBER_MUTATIONS]: 0.25, [MultiSelectionTableColumnKey.NUMBER_STRUCTURAL_VARIANTS]: 0.2, [MultiSelectionTableColumnKey.NUMBER]: 0.25, @@ -174,7 +176,7 @@ export class MultiSelectionTable extends React.Component< return ( ); }, @@ -264,6 +266,39 @@ export class MultiSelectionTable extends React.Component< }, width: columnWidth, }, + [MultiSelectionTableColumnKey.MUTATION_TYPE]: { + name: columnKey, + headerRender: () => { + return ( +
+ {columnKey} +
+ ); + }, + render: (data: MultiSelectionTableRow) => { + return ( +
+ +
+ ); + }, + sortBy: (data: MultiSelectionTableRow) => data.label, + defaultSortDirection: 'asc' as 'asc', + filter: ( + data: MultiSelectionTableRow, + filterString: string, + filterStringUpper: string + ) => { + return data.label.toUpperCase().includes(filterStringUpper); + }, + width: columnWidth, + }, [MultiSelectionTableColumnKey.NUMBER]: { name: columnKey, tooltip: {getTooltip(this.props.tableType, false)}, @@ -482,6 +517,7 @@ export class MultiSelectionTable extends React.Component< [MultiSelectionTableColumnKey.GENE]: 0, [MultiSelectionTableColumnKey.MOLECULAR_PROFILE]: 0, [MultiSelectionTableColumnKey.CASE_LIST]: 0, + [MultiSelectionTableColumnKey.MUTATION_TYPE]: 0, [MultiSelectionTableColumnKey.NUMBER_MUTATIONS]: correctMargin( getFixedHeaderNumberCellMargin( columnWidth, @@ -748,9 +784,14 @@ export class MultiSelectionTable extends React.Component< return _.reduce( this.props.filters, (acc, next, index) => { - next.forEach(key => { - acc[key] = index; - }); + if (Array.isArray(next)) { + next.forEach(key => { + acc[key] = index; + }); + } else { + acc[next] = index; + } + return acc; }, {} as { [id: string]: number } diff --git a/src/pages/studyView/tabs/SummaryTab.tsx b/src/pages/studyView/tabs/SummaryTab.tsx index db6b8d2d2ce..ee0c9fff2ee 100644 --- a/src/pages/studyView/tabs/SummaryTab.tsx +++ b/src/pages/studyView/tabs/SummaryTab.tsx @@ -33,6 +33,15 @@ import { DataBin, makeDensityScatterPlotTooltip, logScalePossible, + ChartMetaDataTypeEnum, + getMutationTypesDownloadData, + getScatterDownloadData, + getSurvivalDownloadData, + getMutatedGenesDownloadData, + getStructuralVariantGenesDownloadData, + getGenesCNADownloadData, + getPatientTreatmentDownloadData, + getSampleTreatmentDownloadData, } from '../StudyViewUtils'; import { DataType } from 'cbioportal-frontend-commons'; import DelayedRender from 'shared/components/DelayedRender'; @@ -53,6 +62,7 @@ export class StudySummaryTab extends React.Component< > { private store: StudyViewPageStore; private handlers: any; + private chartTypeConfig: any; @observable showErrorMessage = true; @@ -144,6 +154,21 @@ export class StudySummaryTab extends React.Component< values ); }, + onSetMutationDataValues: ( + chartMeta: ChartMeta, + values: string[][] + ) => { + this.store.updateMutationDataFilters( + chartMeta.uniqueKey, + values + ); + }, + onAddMutationDataValues: ( + chartMeta: ChartMeta, + values: string[][] + ) => { + this.store.addMutationDataFilters(chartMeta.uniqueKey, values); + }, onGenericAssayDataBinSelection: ( chartMeta: ChartMeta, dataBins: GenericAssayDataBin[] @@ -163,355 +188,424 @@ export class StudySummaryTab extends React.Component< ); }, }; - } - - renderAttributeChart = (chartMeta: ChartMeta) => { - const props: Partial = { - chartMeta: chartMeta, - chartType: this.props.store.chartsType.get(chartMeta.uniqueKey), - store: this.props.store, - dimension: this.store.chartsDimension.get(chartMeta.uniqueKey), - title: chartMeta.displayName, - filters: [], - onDeleteChart: this.handlers.onDeleteChart, - isNewlyAdded: this.handlers.isNewlyAdded, - studyViewFilters: this.store.filters, - analysisGroupsSettings: this.store.analysisGroupsSettings, - cancerGeneFilterEnabled: this.store.oncokbCancerGeneFilterEnabled, - setComparisonConfirmationModal: this.store - .setComparisonConfirmationModal, - }; - switch (this.store.chartsType.get(chartMeta.uniqueKey)) { - case ChartTypeEnum.PIE_CHART: { - //if the chart is one of the custom charts then get the appropriate promise - if ( - this.store.isUserDefinedCustomDataChart(chartMeta.uniqueKey) - ) { - props.filters = this.store + this.chartTypeConfig = ( + chartMeta: ChartMeta, + props: Partial + ) => ({ + [ChartTypeEnum.PIE_CHART]: () => ({ + commonProps: { + onChangeChartType: this.handlers.onChangeChartType, + getData: (dataType?: DataType) => + this.store.getChartDownloadableData( + chartMeta, + dataType + ), + downloadTypes: ['Summary Data', 'Full Data', 'SVG', 'PDF'], + }, + [ChartMetaDataTypeEnum.CUSTOM_DATA]: () => ({ + filters: this.store .getCustomDataFiltersByUniqueKey(chartMeta.uniqueKey) .map( clinicalDataFilterValue => clinicalDataFilterValue.value - ); - props.onValueSelection = this.handlers.setCustomChartCategoricalFilters; - props.onResetSelection = this.handlers.setCustomChartCategoricalFilters; - props.promise = this.store.getCustomDataCount(chartMeta); - } else if ( - this.store.isGenericAssayChart(chartMeta.uniqueKey) - ) { - props.filters = this.store + ), + onValueSelection: this.handlers + .setCustomChartCategoricalFilters, + onResetSelection: this.handlers + .setCustomChartCategoricalFilters, + promise: this.store.getCustomDataCount(chartMeta), + }), + [ChartMetaDataTypeEnum.GENERIC_ASSAY]: () => ({ + filters: this.store .getGenericAssayDataFiltersByUniqueKey( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey ) .map( genericAssayDataFilter => genericAssayDataFilter.value - ); - props.onValueSelection = this.handlers.onGenericAssayCategoricalValueSelection; - props.onResetSelection = this.handlers.onGenericAssayCategoricalValueSelection; - props.promise = this.store.getGenericAssayChartDataCount( + ), + onValueSelection: this.handlers + .onGenericAssayCategoricalValueSelection, + onResetSelection: this.handlers + .onGenericAssayCategoricalValueSelection, + promise: this.store.getGenericAssayChartDataCount( chartMeta - ); - } else if ( - this.store.isGeneSpecificChart(chartMeta.uniqueKey) - ) { - props.promise = this.store.getGenomicChartDataCount( - chartMeta - ); - props.filters = this.store - .getGenomicDataFiltersByUniqueKey(chartMeta.uniqueKey) - .map( - genomicDataFilterValue => - genomicDataFilterValue.value - ); - props.onValueSelection = this.handlers.onGenomicDataCategoricalValueSelection; - props.onResetSelection = this.handlers.onGenomicDataCategoricalValueSelection; - } else { - props.promise = this.store.getClinicalDataCount(chartMeta); - props.filters = this.store + ), + }), + [ChartMetaDataTypeEnum.GENE_SPECIFIC]: () => ({ + promise: this.store.getGenomicChartDataCount(chartMeta), + filters: chartMeta.mutationOptionType + ? this.store.getMutationDataFiltersByUniqueKey( + chartMeta.uniqueKey + ).length > 0 + ? this.store.getMutationDataFiltersByUniqueKey( + chartMeta.uniqueKey + )[0] + : [] + : this.store + .getGenomicDataFiltersByUniqueKey( + chartMeta.uniqueKey + ) + .map( + genomicDataFilterValue => + genomicDataFilterValue.value + ), + onValueSelection: this.handlers + .onGenomicDataCategoricalValueSelection, + onResetSelection: this.handlers + .onGenomicDataCategoricalValueSelection, + }), + [ChartMetaDataTypeEnum.CLINICAL]: () => ({ + promise: this.store.getClinicalDataCount(chartMeta), + filters: this.store .getClinicalDataFiltersByUniqueKey(chartMeta.uniqueKey) .map( clinicalDataFilterValue => clinicalDataFilterValue.value - ); - props.onValueSelection = this.handlers.onValueSelection; - props.onResetSelection = this.handlers.onValueSelection; - } - props.onChangeChartType = this.handlers.onChangeChartType; - props.getData = (dataType?: DataType) => - this.store.getChartDownloadableData(chartMeta, dataType); - props.downloadTypes = [ - 'Summary Data', - 'Full Data', - 'SVG', - 'PDF', - ]; - break; - } - case ChartTypeEnum.BAR_CHART: { - //if the chart is one of the custom charts then get the appropriate promise - if (this.store.isGeneSpecificChart(chartMeta.uniqueKey)) { - props.promise = this.store.getGenomicChartDataBin( - chartMeta - ); - props.filters = this.store.getGenomicDataFiltersByUniqueKey( - props.chartMeta!.uniqueKey - ); - props.onDataBinSelection = this.handlers.onGenomicDataBinSelection; - props.onResetSelection = this.handlers.onGenomicDataBinSelection; - props.getData = () => - this.store.getChartDownloadableData(chartMeta); - } else if ( - this.store.isGenericAssayChart(chartMeta.uniqueKey) - ) { - props.promise = this.store.getGenericAssayChartDataBin( - chartMeta - ); - props.filters = this.store.getGenericAssayDataFiltersByUniqueKey( - props.chartMeta!.uniqueKey - ); - props.onDataBinSelection = this.handlers.onGenericAssayDataBinSelection; - props.onResetSelection = this.handlers.onGenericAssayDataBinSelection; - props.getData = () => - this.store.getChartDownloadableData(chartMeta); - } else { - if ( - this.store.isUserDefinedCustomDataChart( - chartMeta.uniqueKey - ) - ) { - props.promise = this.store.getCustomDataNumerical( - chartMeta - ); - props.onDataBinSelection = this.handlers.onCustomChartDataBinSelection; - props.filters = this.store.getCustomDataFiltersByUniqueKey( - chartMeta.uniqueKey - ); - } else { - props.promise = this.store.getClinicalDataBin( - chartMeta - ); - props.onDataBinSelection = this.handlers.onDataBinSelection; - props.filters = this.store.getClinicalDataFiltersByUniqueKey( - chartMeta.uniqueKey - ); - } - props.onResetSelection = this.handlers.onDataBinSelection; - props.getData = () => - this.store.getChartDownloadableData(chartMeta); - } - props.onToggleLogScale = this.handlers.onToggleLogScale; - props.onToggleNAValue = this.handlers.onToggleNAValue; - props.showLogScaleToggle = this.store.isLogScaleToggleVisible( - chartMeta.uniqueKey, - props.promise!.result - ); - props.logScaleChecked = this.store.isLogScaleChecked( - chartMeta.uniqueKey - ); - props.isShowNAChecked = this.store.isShowNAChecked( - chartMeta.uniqueKey - ); - props.showNAToggle = this.store.isShowNAToggleVisible( - props.promise!.result - ); - props.downloadTypes = ['Data', 'SVG', 'PDF']; - break; - } - case ChartTypeEnum.TABLE: { - if ( - this.store.isUserDefinedCustomDataChart(chartMeta.uniqueKey) - ) { - props.filters = this.store + ), + onValueSelection: this.handlers.onValueSelection, + onResetSelection: this.handlers.onValueSelection, + }), + }), + [ChartTypeEnum.BAR_CHART]: () => ({ + commonProps: { + onToggleNAValue: this.handlers.onToggleNAValue, + logScaleChecked: this.store.isLogScaleChecked( + chartMeta.uniqueKey + ), + isShowNAChecked: this.store.isShowNAChecked( + chartMeta.uniqueKey + ), + downloadTypes: ['Data', 'SVG', 'PDF'], + }, + [ChartMetaDataTypeEnum.GENE_SPECIFIC]: () => ({ + promise: this.store.getGenomicChartDataBin(chartMeta), + filters: this.store.getGenomicDataFiltersByUniqueKey( + chartMeta.uniqueKey + ), + onDataBinSelection: this.handlers.onGenomicDataBinSelection, + onResetSelection: this.handlers.onGenomicDataBinSelection, + getData: () => + this.store.getChartDownloadableData(chartMeta), + showLogScaleToggle: this.store.isLogScaleToggleVisible( + chartMeta.uniqueKey, + this.store.getGenomicChartDataBin(chartMeta).result + ), + showNAToggle: this.store.isShowNAToggleVisible( + this.store.getGenomicChartDataBin(chartMeta).result! + ), + }), + [ChartMetaDataTypeEnum.GENERIC_ASSAY]: () => ({ + promise: this.store.getGenericAssayChartDataBin(chartMeta), + filters: this.store.getGenericAssayDataFiltersByUniqueKey( + chartMeta.uniqueKey + ), + onDataBinSelection: this.handlers + .onGenericAssayDataBinSelection, + onResetSelection: this.handlers + .onGenericAssayDataBinSelection, + getData: () => + this.store.getChartDownloadableData(chartMeta), + showLogScaleToggle: this.store.isLogScaleToggleVisible( + chartMeta.uniqueKey, + this.store.getGenericAssayChartDataBin(chartMeta).result + ), + showNAToggle: this.store.isShowNAToggleVisible( + this.store.getGenericAssayChartDataBin(chartMeta) + .result! + ), + }), + [ChartMetaDataTypeEnum.CUSTOM_DATA]: () => ({ + promise: this.store.getCustomDataNumerical(chartMeta), + onDataBinSelection: this.handlers + .onCustomChartDataBinSelection, + filters: this.store.getCustomDataFiltersByUniqueKey( + chartMeta.uniqueKey + ), + onResetSelection: this.handlers.onDataBinSelection, + getData: () => + this.store.getChartDownloadableData(chartMeta), + showLogScaleToggle: this.store.isLogScaleToggleVisible( + chartMeta.uniqueKey, + this.store.getCustomDataNumerical(chartMeta).result + ), + showNAToggle: this.store.isShowNAToggleVisible( + this.store.getCustomDataNumerical(chartMeta).result! + ), + }), + [ChartMetaDataTypeEnum.CLINICAL]: () => ({ + promise: this.store.getClinicalDataBin(chartMeta), + onDataBinSelection: this.handlers.onDataBinSelection, + filters: this.store.getClinicalDataFiltersByUniqueKey( + chartMeta.uniqueKey + ), + onResetSelection: this.handlers.onDataBinSelection, + getData: () => + this.store.getChartDownloadableData(chartMeta), + showLogScaleToggle: this.store.isLogScaleToggleVisible( + chartMeta.uniqueKey, + this.store.getClinicalDataBin(chartMeta).result + ), + showNAToggle: this.store.isShowNAToggleVisible( + this.store.getClinicalDataBin(chartMeta).result! + ), + }), + }), + [ChartTypeEnum.TABLE]: () => ({ + commonProps: { + onChangeChartType: this.handlers.onChangeChartType, + getData: () => + this.store.getChartDownloadableData(chartMeta), + downloadTypes: ['Data'], + }, + [ChartMetaDataTypeEnum.CUSTOM_DATA]: () => ({ + filters: this.store .getCustomDataFiltersByUniqueKey(chartMeta.uniqueKey) .map( clinicalDataFilterValue => clinicalDataFilterValue.value - ); - props.onValueSelection = this.handlers.setCustomChartCategoricalFilters; - props.onResetSelection = this.handlers.setCustomChartCategoricalFilters; - props.promise = this.store.getCustomDataCount(chartMeta); - } else if ( - this.store.isGenericAssayChart(chartMeta.uniqueKey) - ) { - props.filters = this.store + ), + onValueSelection: this.handlers + .setCustomChartCategoricalFilters, + onResetSelection: this.handlers + .setCustomChartCategoricalFilters, + promise: this.store.getCustomDataCount(chartMeta), + }), + [ChartMetaDataTypeEnum.GENERIC_ASSAY]: () => ({ + filters: this.store .getGenericAssayDataFiltersByUniqueKey( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey ) .map( genericAssayDataFilter => genericAssayDataFilter.value - ); - props.onValueSelection = this.handlers.onGenericAssayCategoricalValueSelection; - props.onResetSelection = this.handlers.onGenericAssayCategoricalValueSelection; - props.promise = this.store.getGenericAssayChartDataCount( - chartMeta - ); - } else if ( - this.store.isGeneSpecificChart(chartMeta.uniqueKey) - ) { - props.promise = this.store.getGenomicChartDataCount( + ), + onValueSelection: this.handlers + .onGenericAssayCategoricalValueSelection, + onResetSelection: this.handlers + .onGenericAssayCategoricalValueSelection, + promise: this.store.getGenericAssayChartDataCount( chartMeta - ); - props.filters = this.store - .getGenomicDataFiltersByUniqueKey(chartMeta.uniqueKey) - .map( - genomicDataFilterValue => - genomicDataFilterValue.value - ); - props.onValueSelection = this.handlers.onGenomicDataCategoricalValueSelection; - props.onResetSelection = this.handlers.onGenomicDataCategoricalValueSelection; - } else { - props.filters = this.store + ), + }), + [ChartMetaDataTypeEnum.GENE_SPECIFIC]: () => ({ + promise: this.store.getGenomicChartDataCount(chartMeta), + filters: chartMeta.mutationOptionType + ? this.store.getMutationDataFiltersByUniqueKey( + chartMeta.uniqueKey + ).length > 0 + ? this.store.getMutationDataFiltersByUniqueKey( + chartMeta.uniqueKey + )[0] + : [] + : this.store + .getGenomicDataFiltersByUniqueKey( + chartMeta.uniqueKey + ) + .map( + genomicDataFilterValue => + genomicDataFilterValue.value + ), + onValueSelection: this.handlers + .onGenomicDataCategoricalValueSelection, + onResetSelection: this.handlers + .onGenomicDataCategoricalValueSelection, + }), + [ChartMetaDataTypeEnum.CLINICAL]: () => ({ + filters: this.store .getClinicalDataFiltersByUniqueKey(chartMeta.uniqueKey) .map( clinicalDataFilterValue => clinicalDataFilterValue.value - ); - props.promise = this.store.getClinicalDataCount(chartMeta); - props.onValueSelection = this.handlers.onValueSelection; - props.onResetSelection = this.handlers.onValueSelection; - } - props.onChangeChartType = this.handlers.onChangeChartType; - props.getData = dataType => - this.store.getChartDownloadableData(chartMeta, dataType); - props.downloadTypes = ['Summary Data', 'Full Data']; - break; - } - case ChartTypeEnum.MUTATED_GENES_TABLE: { - props.filters = this.store.getGeneFiltersByUniqueKey( + ), + promise: this.store.getClinicalDataCount(chartMeta), + onValueSelection: this.handlers.onValueSelection, + onResetSelection: this.handlers.onValueSelection, + }), + }), + [ChartTypeEnum.MUTATED_GENES_TABLE]: () => ({ + filters: this.store.getGeneFiltersByUniqueKey( chartMeta.uniqueKey - ); - props.promise = this.store.mutatedGeneTableRowData; - props.onValueSelection = this.store.addGeneFilters; - props.onResetSelection = () => - this.store.resetGeneFilter(chartMeta.uniqueKey); - props.selectedGenes = this.store.selectedGenes; - props.onGeneSelect = this.store.onCheckGene; - props.id = 'mutated-genes-table'; - props.title = this.store.getChartTitle( + ), + promise: this.store.mutatedGeneTableRowData, + onValueSelection: this.store.addGeneFilters, + onResetSelection: () => + this.store.resetGeneFilter(chartMeta.uniqueKey), + selectedGenes: this.store.selectedGenes, + onGeneSelect: this.store.onCheckGene, + id: 'mutated-genes-table', + title: this.store.getChartTitle( ChartTypeEnum.MUTATED_GENES_TABLE, props.title - ); - props.getData = () => this.store.getMutatedGenesDownloadData(); - props.genePanelCache = this.store.genePanelCache; - props.downloadTypes = ['Data']; - props.filterByCancerGenes = this.store.filterMutatedGenesTableByCancerGenes; - props.onChangeCancerGeneFilter = this.store.updateMutatedGenesTableByCancerGenesFilter; - props.alterationFilterEnabled = getServerConfig().skin_show_settings_menu; - props.filterAlterations = this.store.isGlobalMutationFilterActive; - break; - } - case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: { - props.filters = this.store.getGeneFiltersByUniqueKey( + ), + getData: () => + getMutatedGenesDownloadData( + this.store.mutatedGeneTableRowData, + this.store.oncokbCancerGeneFilterEnabled + ), + genePanelCache: this.store.genePanelCache, + downloadTypes: ['Data'], + filterByCancerGenes: this.store + .filterMutatedGenesTableByCancerGenes, + onChangeCancerGeneFilter: this.store + .updateMutatedGenesTableByCancerGenesFilter, + alterationFilterEnabled: getServerConfig() + .skin_show_settings_menu, + filterAlterations: this.store.isGlobalMutationFilterActive, + }), + [ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE]: () => ({ + filters: this.store.getMutationDataFiltersByUniqueKey( chartMeta.uniqueKey - ); - props.promise = this.store.structuralVariantGeneTableRowData; - props.onValueSelection = this.store.addGeneFilters; - props.onResetSelection = () => - this.store.resetGeneFilter(chartMeta.uniqueKey); - props.selectedGenes = this.store.selectedGenes; - props.onGeneSelect = this.store.onCheckGene; - props.title = this.store.getChartTitle( + ), + promise: this.store.getMutationTypeChartDataCount(chartMeta), + onValueSelection: this.handlers.onAddMutationDataValues, + onResetSelection: this.handlers.onSetMutationDataValues, + id: 'mutation-type-counts-table', + title: this.store.getChartTitle( + ChartTypeEnum.MUTATION_TYPE_COUNTS_TABLE, + props.title + ), + getData: () => + getMutationTypesDownloadData( + this.store.getMutationTypeChartDataCount(chartMeta) + ), + downloadTypes: ['Data'], + onChangeCancerGeneFilter: this.store + .updateMutatedGenesTableByCancerGenesFilter, + }), + [ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE]: () => ({ + filters: this.store.getGeneFiltersByUniqueKey( + chartMeta.uniqueKey + ), + promise: this.store.structuralVariantGeneTableRowData, + onValueSelection: this.store.addGeneFilters, + onResetSelection: () => + this.store.resetGeneFilter(chartMeta.uniqueKey), + selectedGenes: this.store.selectedGenes, + onGeneSelect: this.store.onCheckGene, + title: this.store.getChartTitle( ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE, props.title - ); - props.getData = () => - this.store.getStructuralVariantGenesDownloadData(); - props.genePanelCache = this.store.genePanelCache; - props.downloadTypes = ['Data']; - props.filterByCancerGenes = this.store.filterSVGenesTableByCancerGenes; - props.onChangeCancerGeneFilter = this.store.updateSVGenesTableByCancerGenesFilter; - props.alterationFilterEnabled = getServerConfig().skin_show_settings_menu; - props.filterAlterations = this.store.isGlobalMutationFilterActive; - break; - } - case ChartTypeEnum.STRUCTURAL_VARIANTS_TABLE: { - props.filters = this.store.getGeneFiltersByUniqueKey( + ), + getData: () => + getStructuralVariantGenesDownloadData( + this.store.structuralVariantGeneTableRowData, + this.store.oncokbCancerGeneFilterEnabled + ), + genePanelCache: this.store.genePanelCache, + downloadTypes: ['Data'], + filterByCancerGenes: this.store.filterSVGenesTableByCancerGenes, + onChangeCancerGeneFilter: this.store + .updateSVGenesTableByCancerGenesFilter, + alterationFilterEnabled: getServerConfig() + .skin_show_settings_menu, + filterAlterations: this.store.isGlobalMutationFilterActive, + }), + [ChartTypeEnum.STRUCTURAL_VARIANTS_TABLE]: () => ({ + filters: this.store.getGeneFiltersByUniqueKey( chartMeta.uniqueKey - ); - props.promise = this.store.structuralVariantTableRowData; - props.onValueSelection = this.store.addStructVarFilters; - props.onResetSelection = () => - this.store.resetGeneFilter(chartMeta.uniqueKey); - props.selectedStructuralVariants = this.store.selectedStructuralVariants; - props.onStructuralVariantSelect = this.store.onCheckStructuralVariant; - props.title = this.store.getChartTitle( + ), + promise: this.store.structuralVariantTableRowData, + onValueSelection: this.store.addStructVarFilters, + onResetSelection: () => + this.store.resetGeneFilter(chartMeta.uniqueKey), + selectedStructuralVariants: this.store + .selectedStructuralVariants, + onStructuralVariantSelect: this.store.onCheckStructuralVariant, + title: this.store.getChartTitle( ChartTypeEnum.STRUCTURAL_VARIANTS_TABLE, props.title - ); - props.getData = () => - this.store.getStructuralVariantGenesDownloadData(); - props.genePanelCache = this.store.genePanelCache; - props.downloadTypes = ['Data']; - props.filterByCancerGenes = this.store.filterStructVarsTableByCancerGenes; - props.onChangeCancerGeneFilter = this.store.updateStructVarsTableByCancerGenesFilter; - props.alterationFilterEnabled = getServerConfig().skin_show_settings_menu; - props.filterAlterations = this.store.isGlobalMutationFilterActive; - break; - } - case ChartTypeEnum.CNA_GENES_TABLE: { - props.filters = this.store.getGeneFiltersByUniqueKey( + ), + getData: () => + getStructuralVariantGenesDownloadData( + this.store.structuralVariantTableRowData, + this.store.oncokbCancerGeneFilterEnabled + ), + genePanelCache: this.store.genePanelCache, + downloadTypes: ['Data'], + filterByCancerGenes: this.store + .filterStructVarsTableByCancerGenes, + onChangeCancerGeneFilter: this.store + .updateStructVarsTableByCancerGenesFilter, + alterationFilterEnabled: getServerConfig() + .skin_show_settings_menu, + filterAlterations: this.store.isGlobalMutationFilterActive, + }), + [ChartTypeEnum.CNA_GENES_TABLE]: () => ({ + filters: this.store.getGeneFiltersByUniqueKey( chartMeta.uniqueKey - ); - props.promise = this.store.cnaGeneTableRowData; - props.onValueSelection = this.store.addGeneFilters; - props.onResetSelection = () => - this.store.resetGeneFilter(chartMeta.uniqueKey); - props.selectedGenes = this.store.selectedGenes; - props.onGeneSelect = this.store.onCheckGene; - props.title = this.store.getChartTitle( + ), + promise: this.store.cnaGeneTableRowData, + onValueSelection: this.store.addGeneFilters, + onResetSelection: () => + this.store.resetGeneFilter(chartMeta.uniqueKey), + selectedGenes: this.store.selectedGenes, + onGeneSelect: this.store.onCheckGene, + title: this.store.getChartTitle( ChartTypeEnum.CNA_GENES_TABLE, props.title - ); - props.getData = () => this.store.getGenesCNADownloadData(); - props.genePanelCache = this.store.genePanelCache; - props.downloadTypes = ['Data']; - props.filterByCancerGenes = this.store.filterCNAGenesTableByCancerGenes; - props.onChangeCancerGeneFilter = this.store.updateCNAGenesTableByCancerGenesFilter; - props.alterationFilterEnabled = getServerConfig().skin_show_settings_menu; - props.filterAlterations = this.store.isGlobalAlterationFilterActive; - break; - } - case ChartTypeEnum.GENOMIC_PROFILES_TABLE: { - props.filters = toJS(this.store.genomicProfilesFilter); - props.promise = this.store.molecularProfileSampleCounts; - props.onValueSelection = this.store.addGenomicProfilesFilter; - props.onResetSelection = () => { + ), + getData: () => + getGenesCNADownloadData( + this.store.cnaGeneTableRowData, + this.store.oncokbCancerGeneFilterEnabled + ), + genePanelCache: this.store.genePanelCache, + downloadTypes: ['Data'], + filterByCancerGenes: this.store + .filterCNAGenesTableByCancerGenes, + onChangeCancerGeneFilter: this.store + .updateCNAGenesTableByCancerGenesFilter, + alterationFilterEnabled: getServerConfig() + .skin_show_settings_menu, + filterAlterations: this.store.isGlobalAlterationFilterActive, + }), + [ChartTypeEnum.GENOMIC_PROFILES_TABLE]: () => ({ + filters: toJS(this.store.genomicProfilesFilter), + promise: this.store.molecularProfileSampleCounts, + onValueSelection: this.store.addGenomicProfilesFilter, + onResetSelection: () => { this.store.setGenomicProfilesFilter([]); - }; - break; - } - case ChartTypeEnum.CASE_LIST_TABLE: { - props.filters = toJS(this.store.caseListsFilter); - props.promise = this.store.caseListSampleCounts; - props.onValueSelection = this.store.addCaseListsFilter; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.CASE_LIST_TABLE]: () => ({ + filters: toJS(this.store.caseListsFilter), + promise: this.store.caseListSampleCounts, + onValueSelection: this.store.addCaseListsFilter, + onResetSelection: () => { this.store.setCaseListsFilter([]); + }, + }), + [ChartTypeEnum.SURVIVAL]: () => { + const props: Partial = { + promise: this.store.survivalPlots, + getData: () => + getSurvivalDownloadData( + chartMeta, + this.store.survivalPlots.result, + this.store.survivalData.result, + this.store.selectedPatients, + this.store.selectedPatientKeys.result + ), + patientToAnalysisGroup: this.store.patientToAnalysisGroup, + downloadTypes: ['Data', 'SVG', 'PDF'], + description: this.store.survivalDescriptions.result + ? this.store.survivalDescriptions.result[ + chartMeta.uniqueKey.substring( + 0, + chartMeta.uniqueKey.lastIndexOf('_SURVIVAL') + ) + ][0] + : undefined, + onToggleSurvivalPlotLeftTruncation: this.handlers + .onToggleSurvivalPlotLeftTruncation, + survivalPlotLeftTruncationChecked: this.store.survivalPlotLeftTruncationToggleMap?.get( + chartMeta.uniqueKey + ), + onDataBinSelection: this.handlers.onDataBinSelection, }; - break; - } - case ChartTypeEnum.SURVIVAL: { - props.promise = this.store.survivalPlots; - props.getData = () => - this.store.getSurvivalDownloadData(chartMeta); - props.patientToAnalysisGroup = this.store.patientToAnalysisGroup; - props.downloadTypes = ['Data', 'SVG', 'PDF']; - props.description = this.store.survivalDescriptions.result - ? this.store.survivalDescriptions.result![ - chartMeta.uniqueKey.substring( - 0, - chartMeta.uniqueKey.lastIndexOf('_SURVIVAL') - ) - ][0] - : undefined; - props.onToggleSurvivalPlotLeftTruncation = this.handlers.onToggleSurvivalPlotLeftTruncation; - props.survivalPlotLeftTruncationChecked = this.store.survivalPlotLeftTruncationToggleMap?.get( - chartMeta!.uniqueKey - ); - /* start of left truncation adjustment related settings */ - // Currently, left truncation is only appliable for Overall Survival data + if ( this.store.isLeftTruncationAvailable.result && new RegExp('OS_SURVIVAL').test(chartMeta.uniqueKey) @@ -521,237 +615,266 @@ export class StudySummaryTab extends React.Component< 'OS_SURVIVAL' ]?.survivalDataWithoutLeftTruncation; } - props.onDataBinSelection = this.handlers.onDataBinSelection; - /* end of left truncation adjustment related settings */ - break; - } - case ChartTypeEnum.VIOLIN_PLOT_TABLE: + + return props; + }, + [ChartTypeEnum.VIOLIN_PLOT_TABLE]: () => { const chartInfo = this.store.getXvsYViolinChartInfo( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey )!; const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey )!; - props.filters = [ - ...this.store.getClinicalDataFiltersByUniqueKey( - chartInfo.categoricalAttr.clinicalAttributeId - ), - ...this.store.getClinicalDataFiltersByUniqueKey( + const props: Partial = { + filters: [ + ...this.store.getClinicalDataFiltersByUniqueKey( + chartInfo.categoricalAttr.clinicalAttributeId + ), + ...this.store.getClinicalDataFiltersByUniqueKey( + chartInfo.numericalAttr.clinicalAttributeId + ), + ], + title: `${chartInfo.numericalAttr.displayName}${ + settings.violinLogScale ? ' (log)' : '' + } vs ${chartInfo.categoricalAttr.displayName}`, + promise: this.store.clinicalViolinDataCache.get({ + chartInfo, + violinLogScale: !!settings.violinLogScale, + }), + axisLabelX: chartInfo.categoricalAttr.displayName, + axisLabelY: chartInfo.numericalAttr.displayName, + showLogScaleToggle: logScalePossible( chartInfo.numericalAttr.clinicalAttributeId ), - ]; - props.title = `${chartInfo.numericalAttr.displayName}${ - settings.violinLogScale ? ' (log)' : '' - } vs ${chartInfo.categoricalAttr.displayName}`; - props.promise = this.store.clinicalViolinDataCache.get({ - chartInfo, - violinLogScale: !!settings.violinLogScale, - }); - props.axisLabelX = chartInfo.categoricalAttr.displayName; - props.axisLabelY = chartInfo.numericalAttr.displayName; - props.showLogScaleToggle = logScalePossible( - chartInfo.numericalAttr.clinicalAttributeId - ); - props.logScaleChecked = settings.violinLogScale; - props.onToggleLogScale = () => { - const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey - )!; - settings.violinLogScale = !settings.violinLogScale; - }; - props.showViolinPlotToggle = true; - props.violinPlotChecked = settings.showViolin; - props.onToggleViolinPlot = () => { - const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey - )!; - settings.showViolin = !settings.showViolin; - }; - props.showBoxPlotToggle = true; - props.boxPlotChecked = settings.showBox; - props.onToggleBoxPlot = () => { - const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey - )!; - settings.showBox = !settings.showBox; - }; - props.onValueSelection = ( - type: 'categorical' | 'numerical', - values: string[] | { start: number; end: number } - ) => { - switch (type) { - case 'categorical': - this.store.updateClinicalAttributeFilterByValues( - chartInfo.categoricalAttr.clinicalAttributeId, - (values as string[]).map( - value => ({ value } as DataFilterValue) - ) - ); - break; - case 'numerical': - this.store.updateClinicalDataCustomIntervalFilter( - chartInfo.numericalAttr.clinicalAttributeId, - values as { start: number; end: number } - ); - break; - } - }; - props.selectedCategories = this.store - .getClinicalDataFiltersByUniqueKey( - chartInfo.categoricalAttr.clinicalAttributeId - ) - .map(x => x.value); - props.onResetSelection = () => { - this.store.updateClinicalAttributeFilterByValues( - chartInfo.categoricalAttr.clinicalAttributeId, - [] - ); - this.store.updateClinicalAttributeFilterByValues( - chartInfo.numericalAttr.clinicalAttributeId, - [] - ); + logScaleChecked: settings.violinLogScale, + onToggleLogScale: () => { + const settings = this.store.getXvsYChartSettings( + chartMeta.uniqueKey + )!; + settings.violinLogScale = !settings.violinLogScale; + }, + showViolinPlotToggle: true, + violinPlotChecked: settings.showViolin, + onToggleViolinPlot: () => { + const settings = this.store.getXvsYChartSettings( + chartMeta.uniqueKey + )!; + settings.showViolin = !settings.showViolin; + }, + showBoxPlotToggle: true, + boxPlotChecked: settings.showBox, + onToggleBoxPlot: () => { + const settings = this.store.getXvsYChartSettings( + chartMeta.uniqueKey + )!; + settings.showBox = !settings.showBox; + }, + onValueSelection: ( + type: 'categorical' | 'numerical', + values: string[] | { start: number; end: number } + ) => { + switch (type) { + case 'categorical': + this.store.updateClinicalAttributeFilterByValues( + chartInfo.categoricalAttr + .clinicalAttributeId, + (values as string[]).map( + value => ({ value } as DataFilterValue) + ) + ); + break; + case 'numerical': + this.store.updateClinicalDataCustomIntervalFilter( + chartInfo.numericalAttr.clinicalAttributeId, + values as { start: number; end: number } + ); + break; + } + }, + selectedCategories: this.store + .getClinicalDataFiltersByUniqueKey( + chartInfo.categoricalAttr.clinicalAttributeId + ) + .map(x => x.value), + onResetSelection: () => { + this.store.updateClinicalAttributeFilterByValues( + chartInfo.categoricalAttr.clinicalAttributeId, + [] + ); + this.store.updateClinicalAttributeFilterByValues( + chartInfo.numericalAttr.clinicalAttributeId, + [] + ); + }, }; - break; - case ChartTypeEnum.SCATTER: { - props.filters = this.store.getScatterPlotFiltersByUniqueKey( - props.chartMeta!.uniqueKey - ); + return props; + }, + [ChartTypeEnum.SCATTER]: () => { const chartInfo = this.store.getXvsYScatterChartInfo( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey )!; const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey + chartMeta.uniqueKey )!; - props.title = props.chartMeta!.displayName; - props.promise = this.store.clinicalDataDensityCache.get({ - chartInfo, - xAxisLogScale: !!settings.xLogScale, - yAxisLogScale: !!settings.yLogScale, - }); - props.showLogScaleXToggle = logScalePossible( - chartInfo.xAttr.clinicalAttributeId - ); - props.showLogScaleYToggle = logScalePossible( - chartInfo.yAttr.clinicalAttributeId - ); - props.logScaleXChecked = settings.xLogScale; - props.logScaleYChecked = settings.yLogScale; - props.onToggleLogScaleX = () => { - const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey - )!; - settings.xLogScale = !settings.xLogScale; - }; - props.onToggleLogScaleY = () => { - const settings = this.store.getXvsYChartSettings( - props.chartMeta!.uniqueKey - )!; - settings.yLogScale = !settings.yLogScale; - }; - props.onSwapAxes = () => { - this.store.swapXvsYChartAxes(props.chartMeta!.uniqueKey); - }; - props.plotDomain = chartInfo.plotDomain; - props.axisLabelX = `${chartInfo.xAttr.displayName}${ - settings.xLogScale ? ' (log)' : '' - }`; - props.axisLabelY = `${chartInfo.yAttr.displayName}${ - settings.yLogScale ? ' (log)' : '' - }`; - props.tooltip = makeDensityScatterPlotTooltip( - chartInfo, - settings - ); - props.onValueSelection = (bounds: RectangleBounds) => { - this.store.updateScatterPlotFilterByValues( - props.chartMeta!.uniqueKey, - bounds - ); - }; - props.onResetSelection = () => { - this.store.updateScatterPlotFilterByValues( - props.chartMeta!.uniqueKey - ); + const props: Partial = { + filters: this.store.getScatterPlotFiltersByUniqueKey( + chartMeta.uniqueKey + ), + title: chartMeta.displayName, + promise: this.store.clinicalDataDensityCache.get({ + chartInfo, + xAxisLogScale: !!settings.xLogScale, + yAxisLogScale: !!settings.yLogScale, + }), + showLogScaleXToggle: logScalePossible( + chartInfo.xAttr.clinicalAttributeId + ), + showLogScaleYToggle: logScalePossible( + chartInfo.yAttr.clinicalAttributeId + ), + logScaleXChecked: settings.xLogScale, + logScaleYChecked: settings.yLogScale, + onToggleLogScaleX: () => { + const settings = this.store.getXvsYChartSettings( + chartMeta.uniqueKey + )!; + settings.xLogScale = !settings.xLogScale; + }, + onToggleLogScaleY: () => { + const settings = this.store.getXvsYChartSettings( + chartMeta.uniqueKey + )!; + settings.yLogScale = !settings.yLogScale; + }, + onSwapAxes: () => { + this.store.swapXvsYChartAxes(chartMeta.uniqueKey); + }, + plotDomain: chartInfo.plotDomain, + axisLabelX: `${chartInfo.xAttr.displayName}${ + settings.xLogScale ? ' (log)' : '' + }`, + axisLabelY: `${chartInfo.yAttr.displayName}${ + settings.yLogScale ? ' (log)' : '' + }`, + tooltip: makeDensityScatterPlotTooltip(chartInfo, settings), + onValueSelection: (bounds: RectangleBounds) => { + this.store.updateScatterPlotFilterByValues( + chartMeta.uniqueKey, + bounds + ); + }, + onResetSelection: () => { + this.store.updateScatterPlotFilterByValues( + chartMeta.uniqueKey + ); + }, + sampleToAnalysisGroup: this.store.sampleToAnalysisGroup, + getData: () => + getScatterDownloadData( + chartInfo, + this.store.selectedSamples + ), + downloadTypes: ['Data', 'SVG', 'PDF'], }; - props.sampleToAnalysisGroup = this.store.sampleToAnalysisGroup; - props.getData = () => - this.store.getScatterDownloadData( - props.chartMeta!.uniqueKey - ); - props.downloadTypes = ['Data', 'SVG', 'PDF']; - break; - } - case ChartTypeEnum.SAMPLE_TREATMENTS_TABLE: { - props.filters = this.store.sampleTreatmentFiltersAsStrings; - props.promise = this.store.sampleTreatments; - props.onValueSelection = this.store.onTreatmentSelection; - props.getData = () => - this.store.getSampleTreatmentDownloadData(); - props.downloadTypes = ['Data']; - props.onResetSelection = () => { + return props; + }, + [ChartTypeEnum.SAMPLE_TREATMENTS_TABLE]: () => ({ + filters: this.store.sampleTreatmentFiltersAsStrings, + promise: this.store.sampleTreatments, + onValueSelection: this.store.onTreatmentSelection, + getData: () => + getSampleTreatmentDownloadData(this.store.sampleTreatments), + downloadTypes: ['Data'], + onResetSelection: () => { this.store.clearSampleTreatmentFilters(); - }; - break; - } - case ChartTypeEnum.SAMPLE_TREATMENT_GROUPS_TABLE: { - props.filters = this.store.sampleTreatmentGroupFiltersAsStrings; - props.promise = this.store.sampleTreatmentGroups; - props.onValueSelection = this.store.onTreatmentSelection; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.SAMPLE_TREATMENT_GROUPS_TABLE]: () => ({ + filters: this.store.sampleTreatmentGroupFiltersAsStrings, + promise: this.store.sampleTreatmentGroups, + onValueSelection: this.store.onTreatmentSelection, + onResetSelection: () => { this.store.clearSampleTreatmentGroupFilters(); - }; - break; - } - case ChartTypeEnum.SAMPLE_TREATMENT_TARGET_TABLE: { - props.filters = this.store.sampleTreatmentTargetFiltersAsStrings; - props.promise = this.store.sampleTreatmentTarget; - props.onValueSelection = this.store.onTreatmentSelection; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.SAMPLE_TREATMENT_TARGET_TABLE]: () => ({ + filters: this.store.sampleTreatmentTargetFiltersAsStrings, + promise: this.store.sampleTreatmentTarget, + onValueSelection: this.store.onTreatmentSelection, + onResetSelection: () => { this.store.clearSampleTreatmentTargetFilters(); - }; - break; - } - case ChartTypeEnum.PATIENT_TREATMENTS_TABLE: { - props.filters = this.store.patientTreatmentFiltersAsStrings; - props.promise = this.store.patientTreatments; - props.onValueSelection = this.store.onTreatmentSelection; - props.getData = () => - this.store.getPatientTreatmentDownloadData(); - props.downloadTypes = ['Data']; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.PATIENT_TREATMENTS_TABLE]: () => ({ + filters: this.store.patientTreatmentFiltersAsStrings, + promise: this.store.patientTreatments, + onValueSelection: this.store.onTreatmentSelection, + getData: () => + getPatientTreatmentDownloadData( + this.store.patientTreatments + ), + downloadTypes: ['Data'], + onResetSelection: () => { this.store.clearPatientTreatmentFilters(); - }; - break; - } - case ChartTypeEnum.PATIENT_TREATMENT_GROUPS_TABLE: { - props.filters = this.store.patientTreatmentGroupFiltersAsStrings; - props.promise = this.store.patientTreatmentGroups; - props.onValueSelection = this.store.onTreatmentSelection; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.PATIENT_TREATMENT_GROUPS_TABLE]: () => ({ + filters: this.store.patientTreatmentGroupFiltersAsStrings, + promise: this.store.patientTreatmentGroups, + onValueSelection: this.store.onTreatmentSelection, + onResetSelection: () => { this.store.clearPatientTreatmentGroupFilters(); - }; - break; - } - case ChartTypeEnum.PATIENT_TREATMENT_TARGET_TABLE: { - props.filters = this.store.patientTreatmentTargetFiltersAsStrings; - props.promise = this.store.patientTreatmentTarget; - props.onValueSelection = this.store.onTreatmentSelection; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.PATIENT_TREATMENT_TARGET_TABLE]: () => ({ + filters: this.store.patientTreatmentTargetFiltersAsStrings, + promise: this.store.patientTreatmentTarget, + onValueSelection: this.store.onTreatmentSelection, + onResetSelection: () => { this.store.clearPatientTreatmentTargetFilters(); - }; - break; - } - case ChartTypeEnum.CLINICAL_EVENT_TYPE_COUNTS_TABLE: { - props.filters = this.store.clinicalEventTypeFiltersRawStrings; - props.promise = this.store.clinicalEventTypeCounts; - props.onValueSelection = this.store.addClinicalEventTypeFilter; - props.onResetSelection = () => { + }, + }), + [ChartTypeEnum.CLINICAL_EVENT_TYPE_COUNTS_TABLE]: () => ({ + filters: this.store.clinicalEventTypeFiltersRawStrings, + promise: this.store.clinicalEventTypeCounts, + onValueSelection: this.store.addClinicalEventTypeFilter, + onResetSelection: () => { this.store.resetClinicalEventTypeFilter(); - }; - break; - } - default: - break; + }, + }), + }); + } + + renderAttributeChart = (chartMeta: ChartMeta) => { + let props: Partial = { + chartMeta: chartMeta, + chartType: this.props.store.chartsType.get(chartMeta.uniqueKey), + store: this.props.store, + dimension: this.store.chartsDimension.get(chartMeta.uniqueKey), + title: chartMeta.displayName, + filters: [], + onDeleteChart: this.handlers.onDeleteChart, + isNewlyAdded: this.handlers.isNewlyAdded, + studyViewFilters: this.store.filters, + analysisGroupsSettings: this.store.analysisGroupsSettings, + cancerGeneFilterEnabled: this.store.oncokbCancerGeneFilterEnabled, + setComparisonConfirmationModal: this.store + .setComparisonConfirmationModal, + }; + + const chartType = this.store.chartsType.get(chartMeta.uniqueKey)!; + const subTypeProps = this.chartTypeConfig(chartMeta, props)[ + chartType + ](); + + if (subTypeProps.commonProps) { + // charts that have sub types + const subProps = subTypeProps[ + this.store.getChartMetaDataType(chartMeta.uniqueKey) + ](); + props = { ...props, ...subProps, ...subTypeProps.commonProps }; + } else { + props = { ...props, ...subTypeProps }; } // Using custom component inside of the GridLayout creates too many chaos as mentioned here diff --git a/src/shared/constants.ts b/src/shared/constants.ts index c311c6ae065..cbe918b94fb 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -129,3 +129,13 @@ export const DataTypeConstants = { }; export const SUPPORTED_DAT_METHODS = ['oauth2', 'uuid']; + +export const MutationOptionConstants = { + MUTATED: 'MUTATED', + MUTATION_TYPE: 'MUTATION_TYPE', +}; + +export const MutationOptionConstantsLabel = { + [MutationOptionConstants.MUTATED]: 'Mutated vs Not Mutated', + [MutationOptionConstants.MUTATION_TYPE]: 'Mutation Types', +};