diff --git a/src/pages/patientView/PatientViewPageTabs.tsx b/src/pages/patientView/PatientViewPageTabs.tsx index f8f29d0496e..6a559571855 100644 --- a/src/pages/patientView/PatientViewPageTabs.tsx +++ b/src/pages/patientView/PatientViewPageTabs.tsx @@ -37,6 +37,7 @@ import { HelpWidget } from 'shared/components/HelpWidget/HelpWidget'; import MutationTableWrapper from './mutation/MutationTableWrapper'; import { PatientViewPageInner } from 'pages/patientView/PatientViewPage'; import { Else, If } from 'react-if'; +import ExpressionTableWrapper from './expression/ExpressionTableWrapper'; export enum PatientViewPageTabs { Summary = 'summary', @@ -49,6 +50,7 @@ export enum PatientViewPageTabs { TrialMatchTab = 'trialMatchTab', MutationalSignatures = 'mutationalSignatures', PathwayMapper = 'pathways', + Expression = 'expression', } export const PatientViewResourceTabPrefix = 'openResource_'; @@ -488,6 +490,49 @@ export function tabs( ); + pageComponent.patientViewPageStore.isExpressionProfiledForPatient + .isComplete && + pageComponent.patientViewPageStore.isExpressionProfiledForPatient + .result && + tabs.push( + + {pageComponent.patientViewPageStore + .mrnaExpressionDataByGeneThenProfile.isComplete && + pageComponent.patientViewPageStore + .proteinExpressionDataByGeneThenProfile.isComplete && + pageComponent.patientViewPageStore.mutationData.isComplete && + pageComponent.patientViewPageStore.structuralVariantData + .isComplete && + pageComponent.patientViewPageStore.cnaDataByGeneThenProfile + .isComplete && + pageComponent.patientViewPageStore.allEntrezGeneIdsToGene + .isComplete && + pageComponent.patientViewPageStore.allHugoGeneSymbolsToGene + .isComplete && + pageComponent.patientViewPageStore + .analysisMrnaExpressionProfiles.isComplete && + pageComponent.patientViewPageStore + .analysisProteinExpressionProfiles.isComplete ? ( + + ) : ( + + )} + + ); + tabs.push( { + return getClient().getAllGenesUsingGET({ + projection: 'SUMMARY', + }); + }, + }); + + readonly allEntrezGeneIdsToGene = remoteData<{ + [entrezGeneId: number]: { + hugoGeneSymbol: string; + entrezGeneId: number; + }; + }>({ + await: () => [this.allGenes], + invoke: () => + Promise.resolve( + _.keyBy(this.allGenes.result, gene => gene.entrezGeneId) + ), + default: {}, + }); + + readonly allHugoGeneSymbolsToGene = remoteData<{ + [hugoGeneSymbol: string]: { + hugoGeneSymbol: string; + entrezGeneId: number; + }; + }>({ + await: () => [this.allGenes], + invoke: () => + Promise.resolve( + _.keyBy(this.allGenes.result, gene => gene.hugoGeneSymbol) + ), + default: {}, + }); + + readonly cnaDataByGeneThenProfile = remoteData({ + await: () => [this.cnaProfiles], + invoke: async () => { + const cnaMap: Record< + number, + Record + > = {}; + await Promise.all( + this.cnaProfiles.result.map(async p => { + let data = await getClient().fetchAllMolecularDataInMolecularProfileUsingPOST( + { + projection: 'DETAILED', + molecularProfileId: p.molecularProfileId, + molecularDataFilter: { + sampleIds: this.sampleIds, + } as MolecularDataFilter, + } + ); + data.map(d => { + if (!cnaMap[d.entrezGeneId]) { + cnaMap[d.entrezGeneId] = {}; + } + if (!cnaMap[d.entrezGeneId][d.molecularProfileId]) { + cnaMap[d.entrezGeneId][d.molecularProfileId] = []; + } + cnaMap[d.entrezGeneId][d.molecularProfileId].push(d); + }); + }) + ); + return cnaMap; + }, + default: {}, + }); + + readonly cnaProfiles = remoteData( + { + await: () => [this.molecularProfilesInStudy], + invoke: () => { + return Promise.resolve( + this.molecularProfilesInStudy.result.filter( + p => + p.molecularAlterationType === + 'COPY_NUMBER_ALTERATION' + ) + ); + }, + }, + [] + ); + + readonly mrnaExpressionDataByGeneThenProfile = remoteData({ + await: () => [this.mrnaExpressionProfiles], + invoke: async () => { + const mrnaExpressionMap: Record< + number, + Record + > = {}; + await Promise.all( + this.mrnaExpressionProfiles.result.map(async p => { + let data = await getClient().fetchAllMolecularDataInMolecularProfileUsingPOST( + { + projection: 'DETAILED', + molecularProfileId: p.molecularProfileId, + molecularDataFilter: { + sampleIds: this.sampleIds, + } as MolecularDataFilter, + } + ); + data.map(d => { + if (!mrnaExpressionMap[d.entrezGeneId]) { + mrnaExpressionMap[d.entrezGeneId] = {}; + } + if ( + !mrnaExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ] + ) { + mrnaExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ] = []; + } + mrnaExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ].push(d); + }); + }) + ); + return mrnaExpressionMap; + }, + default: {}, + }); + + readonly mrnaExpressionProfiles = remoteData( + { + await: () => [this.molecularProfilesInStudy], + invoke: () => { + return Promise.resolve( + this.molecularProfilesInStudy.result.filter( + p => p.molecularAlterationType === 'MRNA_EXPRESSION' + ) + ); + }, + }, + [] + ); + + readonly analysisMrnaExpressionProfiles = remoteData( + { + await: () => [this.mrnaExpressionProfiles], + invoke: () => { + return Promise.resolve( + this.mrnaExpressionProfiles.result.filter( + p => p.showProfileInAnalysisTab + ) + ); + }, + }, + [] + ); + + readonly proteinExpressionDataByGeneThenProfile = remoteData({ + await: () => [this.proteinExpressionProfiles], + invoke: async () => { + const proteinExpressionMap: Record< + number, + Record + > = {}; + await Promise.all( + this.proteinExpressionProfiles.result.map(async p => { + let data = await getClient().fetchAllMolecularDataInMolecularProfileUsingPOST( + { + projection: 'DETAILED', + molecularProfileId: p.molecularProfileId, + molecularDataFilter: { + sampleIds: this.sampleIds, + } as MolecularDataFilter, + } + ); + data.map(d => { + if (!proteinExpressionMap[d.entrezGeneId]) { + proteinExpressionMap[d.entrezGeneId] = {}; + } + if ( + !proteinExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ] + ) { + proteinExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ] = []; + } + proteinExpressionMap[d.entrezGeneId][ + d.molecularProfileId + ].push(d); + }); + }) + ); + return proteinExpressionMap; + }, + default: {}, + }); + + readonly proteinExpressionProfiles = remoteData( + { + await: () => [this.molecularProfilesInStudy], + invoke: () => { + return Promise.resolve( + this.molecularProfilesInStudy.result.filter( + p => p.molecularAlterationType === 'PROTEIN_LEVEL' + ) + ); + }, + }, + [] + ); + + readonly analysisProteinExpressionProfiles = remoteData( + { + await: () => [this.proteinExpressionProfiles], + invoke: () => { + return Promise.resolve( + this.proteinExpressionProfiles.result.filter( + p => p.showProfileInAnalysisTab + ) + ); + }, + }, + [] + ); + + readonly isExpressionProfiledForPatient = remoteData({ + await: () => [ + this.mrnaExpressionProfiles, + this.proteinExpressionProfiles, + this.derivedUniquePatientKey, + this.coverageInformation, + ], + invoke: () => { + const expressionProfileIds = [ + ...this.mrnaExpressionProfiles.result, + ...this.proteinExpressionProfiles.result, + ].map(p => p.molecularProfileId); + return Promise.resolve( + _.some( + isPatientProfiledInMultiple( + this.derivedUniquePatientKey.result, + expressionProfileIds, + this.coverageInformation.result + ) + ) + ); + }, + }); + // public set patientIdsInCohort(cohortIds: string[]) { // // cannot put action on setter // runInAction(() => (this._patientIdsInCohort = cohortIds || [])); @@ -819,6 +1073,16 @@ export class PatientViewPageStore { default: '', }); + readonly derivedUniquePatientKey = remoteData({ + await: () => [this.samples], + invoke: async () => { + for (let sample of this.samples.result) + return sample.uniquePatientKey; + return ''; + }, + default: '', + }); + readonly clinicalDataPatient = remoteData({ await: () => this.pageMode === 'patient' ? [] : [this.derivedPatientId], diff --git a/src/pages/patientView/expression/ExpressionTableWrapper.tsx b/src/pages/patientView/expression/ExpressionTableWrapper.tsx new file mode 100644 index 00000000000..75e37f9ec99 --- /dev/null +++ b/src/pages/patientView/expression/ExpressionTableWrapper.tsx @@ -0,0 +1,868 @@ +import * as React from 'react'; +import LazyMobXTable, { + Column, +} from 'shared/components/lazyMobXTable/LazyMobXTable'; +import _ from 'lodash'; +import { observer } from 'mobx-react'; +import { computed, makeObservable } from 'mobx'; +import { PatientViewPageStore } from '../clinicalInformation/PatientViewPageStore'; +import { prepareExpressionRowDataForTable } from 'shared/lib/StoreUtils'; +import { + Mutation, + NumericGeneMolecularData, + StructuralVariant, +} from 'cbioportal-ts-api-client'; +import { getAlterationString } from 'shared/lib/CopyNumberUtils'; +import { SampleLabelHTML } from 'shared/components/sampleLabel/SampleLabel'; +import { getCNAByAlteration } from 'pages/studyView/StudyViewUtils'; +import { getCnaTypes } from 'shared/lib/pathwayMapper/PathwayMapperHelpers'; +import { getCNAColorByAlteration } from '../PatientViewPageUtils'; +import TumorColumnFormatter from '../mutation/column/TumorColumnFormatter'; +import ProteinChangeColumnFormatter from 'shared/components/mutationTable/column/ProteinChangeColumnFormatter'; +import AnnotationColumnFormatter from 'shared/components/mutationTable/column/AnnotationColumnFormatter'; +import { + calculateOncoKbContentPadding, + DEFAULT_ONCOKB_CONTENT_WIDTH, +} from 'shared/lib/AnnotationColumnUtils'; +import autobind from 'autobind-decorator'; +export interface IExpressionTableWrapperProps { + store: PatientViewPageStore; + mergeOncoKbIcons?: boolean; +} + +class ExpressionTable extends LazyMobXTable {} + +type ExpressionTableColumn = Column & { order: number }; + +export interface IExpressionRow { + hugoGeneSymbol: string; + mrnaExpression: Record; + proteinExpression: Record; + mutations: Mutation[]; + structuralVariants: StructuralVariant[]; + cna: Record; +} + +@observer +export default class ExpressionTableWrapper extends React.Component< + IExpressionTableWrapperProps, + {} +> { + constructor(props: IExpressionTableWrapperProps) { + super(props); + makeObservable(this); + } + + @computed get expressionDataForTable() { + return prepareExpressionRowDataForTable( + this.props.store.mrnaExpressionDataByGeneThenProfile.result, + this.props.store.proteinExpressionDataByGeneThenProfile.result, + this.props.store.mutationData.result, + this.props.store.structuralVariantData.result, + this.props.store.cnaDataByGeneThenProfile.result, + this.props.store.allEntrezGeneIdsToGene.result + ); + } + + @computed get defaultMrnaExpressionProfile() { + if (this.props.store.analysisMrnaExpressionProfiles.result.length > 0) { + return this.props.store.analysisMrnaExpressionProfiles.result[0]; + } else if (this.props.store.mrnaExpressionProfiles.result.length > 0) { + return this.props.store.mrnaExpressionProfiles.result[0]; + } + } + + @computed get defaultProteinExpressionProfile() { + if ( + this.props.store.analysisProteinExpressionProfiles.result.length > 0 + ) { + return this.props.store.analysisProteinExpressionProfiles.result[0]; + } else if ( + this.props.store.proteinExpressionProfiles.result.length > 0 + ) { + return this.props.store.proteinExpressionProfiles.result[0]; + } + } + + @autobind + protected resolveTumorType(mutation: Mutation) { + // first, try to get it from uniqueSampleKeyToTumorType map + if (this.props.store.uniqueSampleKeyToTumorType) { + return this.props.store.uniqueSampleKeyToTumorType[ + mutation.uniqueSampleKey + ]; + } + + // second, try the study cancer type + if (this.props.store.studyIdToStudy.result) { + const studyMetaData = this.props.store.studyIdToStudy.result[ + mutation.studyId + ]; + + if (studyMetaData.cancerTypeId !== 'mixed') { + return studyMetaData.cancerType.name; + } + } + + // return Unknown, this should not happen... + return 'Unknown'; + } + + @computed get columns() { + const columns: ExpressionTableColumn[] = []; + const hasMultipleSamples: boolean = + this.props.store.samples.result.length > 1; + + columns.push({ + name: 'Gene', + render: (d: IExpressionRow[]) => {d[0].hugoGeneSymbol}, + filter: ( + d: IExpressionRow[], + filterString: string, + filterStringUpper: string + ) => { + return d[0].hugoGeneSymbol.indexOf(filterStringUpper) > -1; + }, + download: (d: IExpressionRow[]) => d[0].hugoGeneSymbol, + sortBy: (d: IExpressionRow[]) => d[0].hugoGeneSymbol, + visible: true, + order: 20, + }); + + if (this.defaultMrnaExpressionProfile) { + columns.push({ + name: this.defaultMrnaExpressionProfile.name, + render: (d: IExpressionRow[]) => { + if ( + d[0].mrnaExpression?.[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ], + d => d.value + ).toFixed(2); + return ( + + {mean} ( + {d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ].map((data, i) => ( + + {data.value.toFixed(2)}{' '} + + {i < + d[0].mrnaExpression[ + this + .defaultMrnaExpressionProfile! + .molecularProfileId + ].length - + 1 && ', '} + + ))} + ) + + ); + } else { + return ( + + {d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ][0].value.toFixed(2)} + + ); + } + } + return ; + }, + download: (d: IExpressionRow[]) => { + if ( + d[0].mrnaExpression?.[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ], + d => d.value + ).toFixed(2); + return mean; + } else { + return d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ][0].value.toFixed(2); + } + } else { + return ''; + } + }, + sortBy: (d: IExpressionRow[]) => { + if ( + d[0].mrnaExpression?.[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ], + d => d.value + ); + return mean; + } else { + return d[0].mrnaExpression[ + this.defaultMrnaExpressionProfile! + .molecularProfileId + ][0].value; + } + } else { + return null; + } + }, + visible: true, + order: 25, + }); + } + + this.props.store.mrnaExpressionProfiles.result.map((p, i) => { + if ( + p.molecularProfileId !== + this.defaultMrnaExpressionProfile?.molecularProfileId + ) { + columns.push({ + name: p.name, + render: (d: IExpressionRow[]) => { + if (d[0].mrnaExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[p.molecularProfileId], + d => d.value + ).toFixed(2); + return ( + + {mean} ( + {d[0].mrnaExpression[ + p.molecularProfileId + ].map((data, i) => ( + + {data.value.toFixed(2)}{' '} + + {i < + d[0].mrnaExpression[ + p.molecularProfileId + ].length - + 1 && ', '} + + ))} + ) + + ); + } else { + return ( + + {d[0].mrnaExpression[ + p.molecularProfileId + ][0].value.toFixed(2)} + + ); + } + } + return ; + }, + download: (d: IExpressionRow[]) => { + if (d[0].mrnaExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[p.molecularProfileId], + d => d.value + ).toFixed(2); + return mean; + } else { + return d[0].mrnaExpression[ + p.molecularProfileId + ][0].value.toFixed(2); + } + } else { + return ''; + } + }, + sortBy: (d: IExpressionRow[]) => { + if (d[0].mrnaExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].mrnaExpression[p.molecularProfileId], + d => d.value + ); + return mean; + } else { + return d[0].mrnaExpression[ + p.molecularProfileId + ][0].value; + } + } else { + return null; + } + }, + visible: false, + order: 30, + }); + } + }); + + if (this.defaultProteinExpressionProfile) { + columns.push({ + name: this.defaultProteinExpressionProfile.name, + render: (d: IExpressionRow[]) => { + if ( + d[0].mrnaExpression?.[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ], + d => d.value + ).toFixed(2); + return ( + + {mean} ( + {d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ].map((data, i) => ( + + {data.value.toFixed(2)}{' '} + + {i < + d[0].proteinExpression[ + this + .defaultProteinExpressionProfile! + .molecularProfileId + ].length - + 1 && ', '} + + ))} + ) + + ); + } else { + return ( + + {d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ][0].value.toFixed(2)} + + ); + } + } + return ; + }, + download: (d: IExpressionRow[]) => { + if ( + d[0].proteinExpression?.[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ], + d => d.value + ).toFixed(2); + return mean; + } else { + return d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ][0].value.toFixed(2); + } + } else { + return ''; + } + }, + sortBy: (d: IExpressionRow[]) => { + if ( + d[0].proteinExpression?.[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ] + ) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ], + d => d.value + ); + return mean; + } else { + return d[0].proteinExpression[ + this.defaultProteinExpressionProfile! + .molecularProfileId + ][0].value; + } + } else { + return null; + } + }, + visible: true, + order: 35, + }); + } + + this.props.store.proteinExpressionProfiles.result.map((p, i) => { + if ( + p.molecularProfileId !== + this.defaultProteinExpressionProfile?.molecularProfileId + ) { + columns.push({ + name: p.name, + render: (d: IExpressionRow[]) => { + if (d[0].proteinExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + p.molecularProfileId + ], + d => d.value + ).toFixed(2); + return ( + + {mean} ( + {d[0].proteinExpression[ + p.molecularProfileId + ].map((data, i) => ( + + {data.value.toFixed(2)}{' '} + + {i < + d[0].proteinExpression[ + p.molecularProfileId + ].length - + 1 && ', '} + + ))} + ) + + ); + } else { + return ( + + {d[0].proteinExpression[ + p.molecularProfileId + ][0].value.toFixed(2)} + + ); + } + } + return ; + }, + download: (d: IExpressionRow[]) => { + if (d[0].proteinExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + p.molecularProfileId + ], + d => d.value + ).toFixed(2); + return mean; + } else { + return d[0].proteinExpression[ + p.molecularProfileId + ][0].value.toFixed(2); + } + } else { + return ''; + } + }, + sortBy: (d: IExpressionRow[]) => { + if (d[0].proteinExpression?.[p.molecularProfileId]) { + if (hasMultipleSamples) { + const mean = _.meanBy( + d[0].proteinExpression[ + p.molecularProfileId + ], + d => d.value + ); + return mean; + } else { + return d[0].proteinExpression[ + p.molecularProfileId + ][0].value; + } + } else { + return null; + } + }, + visible: false, + order: 40, + }); + } + }); + + if (this.props.store.mutationMolecularProfile.result) { + columns.push({ + name: this.props.store.mutationMolecularProfile.result.name, + render: (d: IExpressionRow[]) => { + if (d[0]?.mutations) { + return ( + <> + + {ProteinChangeColumnFormatter.renderWithMutationStatus( + d[0].mutations, + this.props.store + .indexedVariantAnnotations + )} + {AnnotationColumnFormatter.renderFunction( + d[0].mutations, + { + oncoKbData: this.props.store + .oncoKbData, + oncoKbCancerGenes: this.props.store + .oncoKbCancerGenes, + usingPublicOncoKbInstance: this + .props.store + .usingPublicOncoKbInstance, + mergeOncoKbIcons: this.props + .mergeOncoKbIcons, + oncoKbContentPadding: calculateOncoKbContentPadding( + DEFAULT_ONCOKB_CONTENT_WIDTH + ), + enableCivic: false, + enableOncoKb: true, + enableHotspot: false, + enableRevue: false, + resolveTumorType: this + .resolveTumorType, + } + )} + + {hasMultipleSamples && + TumorColumnFormatter.renderFunction( + d[0].mutations, + this.props.store.sampleManager.result!, + this.props.store + .sampleToDiscreteGenePanelId.result, + this.props.store + .genePanelIdToEntrezGeneIds.result + )} + + ); + } else if ( + _.every( + TumorColumnFormatter.getProfiledSamplesForGene( + this.props.store.allHugoGeneSymbolsToGene + .result[d[0].hugoGeneSymbol].entrezGeneId, + this.props.store.sampleIds, + this.props.store.sampleToMutationGenePanelId + .result, + this.props.store.genePanelIdToEntrezGeneIds + .result + ), + profiledStatus => !!!profiledStatus + ) + ) { + return ( + + + + + + ); + } else { + return ; + } + }, + download: (d: IExpressionRow[]) => + d[0]?.mutations ? d[0].mutations[0].proteinChange : '', + sortBy: (d: IExpressionRow[]) => + d[0]?.mutations ? d[0].mutations[0].proteinChange : null, + visible: true, + order: 45, + }); + } + + if (this.props.store.structuralVariantProfile.result) { + columns.push({ + name: this.props.store.structuralVariantProfile.result.name, + render: (d: IExpressionRow[]) => { + return ( + <> + + {d[0]?.structuralVariants + ? d[0].structuralVariants[0].eventInfo + : ''} + + {d[0]?.structuralVariants ? ( + TumorColumnFormatter.renderFunction( + d[0].structuralVariants.map(datum => { + // if both are available, return both genes in an array + // otherwise, return whichever is available + const genes = + datum.site1EntrezGeneId && + datum.site2EntrezGeneId + ? [ + datum.site1EntrezGeneId, + datum.site2EntrezGeneId, + ] + : datum.site1EntrezGeneId || + datum.site2EntrezGeneId; + return { + sampleId: datum.sampleId, + entrezGeneId: genes, + sv: true, + }; + }), + this.props.store.sampleManager.result!, + this.props.store.sampleToDiscreteGenePanelId + .result, + this.props.store.genePanelIdToEntrezGeneIds + .result + ) + ) : ( + + )} + + ); + }, + download: (d: IExpressionRow[]) => + d[0]?.structuralVariants[0].eventInfo, + sortBy: (d: IExpressionRow[]) => + d[0]?.structuralVariants + ? d[0].structuralVariants[0].eventInfo + : null, + visible: true, + order: 50, + }); + } + + if (this.props.store.discreteMolecularProfile.result) { + columns.push({ + name: this.props.store.discreteMolecularProfile.result.name, + render: (d: IExpressionRow[]) => { + let color = getCNAColorByAlteration( + d[0].cna?.[ + this.props.store.discreteMolecularProfile.result! + .molecularProfileId + ] + ? getCNAByAlteration( + d[0].cna[ + this.props.store.discreteMolecularProfile + .result!.molecularProfileId + ][0].value + ) + : '' + ); + return ( + + {d[0].cna?.[ + this.props.store.discreteMolecularProfile + .result!.molecularProfileId + ] + ? getCnaTypes( + d[0].cna[ + this.props.store + .discreteMolecularProfile.result! + .molecularProfileId + ][0].value + ) + : ''} + + ); + }, + download: (d: IExpressionRow[]) => + d[0].cna?.[ + this.props.store.discreteMolecularProfile.result! + .molecularProfileId + ] + ? getAlterationString( + d[0].cna[ + this.props.store.discreteMolecularProfile + .result!.molecularProfileId + ][0].value + ) + : '', + sortBy: (d: IExpressionRow[]) => { + if ( + d[0].cna?.[ + this.props.store.discreteMolecularProfile.result! + .molecularProfileId + ] + ) { + return d[0].cna[ + this.props.store.discreteMolecularProfile.result! + .molecularProfileId + ][0].value; + } else { + return null; + } + }, + visible: true, + order: 55, + }); + } + + this.props.store.cnaProfiles.result.map((p, i) => { + if ( + p.molecularProfileId !== + this.props.store.discreteMolecularProfile.result + ?.molecularProfileId + ) { + columns.push({ + name: p.name, + render: (d: IExpressionRow[]) => ( + + {d[0].cna?.[p.molecularProfileId] + ? d[0].cna[ + p.molecularProfileId + ][0].value.toFixed(2) + : ''} + + ), + download: (d: IExpressionRow[]) => + d[0].cna?.[p.molecularProfileId] + ? d[0].cna[p.molecularProfileId][0].value.toFixed(2) + : '', + sortBy: (d: IExpressionRow[]) => { + if (d[0].cna?.[p.molecularProfileId]) { + return d[0].cna[p.molecularProfileId][0].value; + } else { + return null; + } + }, + visible: false, + order: 60, + }); + } + }); + + const orderedColumns = _.sortBy( + columns, + (c: ExpressionTableColumn) => c.order + ); + return orderedColumns; + } + + public render() { + return ( +
+ +
+ ); + } +} diff --git a/src/shared/lib/StoreUtils.ts b/src/shared/lib/StoreUtils.ts index 4713bd898df..8196618602d 100644 --- a/src/shared/lib/StoreUtils.ts +++ b/src/shared/lib/StoreUtils.ts @@ -109,6 +109,7 @@ import { } from 'shared/model/CustomDriverAnnotationInfo'; import { AnnotatedNumericGeneMolecularData } from 'shared/model/AnnotatedNumericGeneMolecularData'; import { EnsemblFilter } from 'genome-nexus-ts-api-client'; +import { IExpressionRow } from 'pages/patientView/expression/ExpressionTableWrapper'; export const MolecularAlterationType_filenameSuffix: { [K in MolecularProfile['molecularAlterationType']]?: string; @@ -2011,3 +2012,60 @@ export function buildProteinChange(sv: StructuralVariant) { return `${genes[0]} intragenic`; } } + +export function prepareExpressionRowDataForTable( + mrnaExpressionDataByGeneThenProfile: Record< + string, + Record + >, + proteinExpressionDataByGeneThenProfile: Record< + string, + Record + >, + mutationData: Mutation[], + structuralVariantData: StructuralVariant[], + cnaDataByGeneThenProfile: Record< + string, + Record + >, + allEntrezGeneIdsToGene: { + [entrezGeneId: number]: { + hugoGeneSymbol: string; + entrezGeneId: number; + }; + } +): IExpressionRow[][] { + const tableData: IExpressionRow[][] = []; + + const mrnaEntrezGeneIds = Object.keys( + mrnaExpressionDataByGeneThenProfile + ).map(Number); + const proteinEntrezGeneIds = Object.keys( + proteinExpressionDataByGeneThenProfile + ).map(Number); + const entrezGeneIds = _.uniq([ + ...mrnaEntrezGeneIds, + ...proteinEntrezGeneIds, + ]); + + const geneToMutationDataMap = _.groupBy(mutationData, d => d.entrezGeneId); + const geneToStructuralVariantDataMap = _.groupBy( + structuralVariantData, + d => d.site1EntrezGeneId + ); + + for (const entrezGeneId of entrezGeneIds) { + let expressionRowForTable: IExpressionRow = { + hugoGeneSymbol: allEntrezGeneIdsToGene[entrezGeneId].hugoGeneSymbol, + mrnaExpression: mrnaExpressionDataByGeneThenProfile[entrezGeneId], + proteinExpression: + proteinExpressionDataByGeneThenProfile[entrezGeneId], + mutations: geneToMutationDataMap[entrezGeneId], + structuralVariants: geneToStructuralVariantDataMap[entrezGeneId], + cna: cnaDataByGeneThenProfile[entrezGeneId], + }; + + tableData.push([expressionRowForTable]); + } + return tableData; +} diff --git a/src/shared/lib/isSampleProfiled.ts b/src/shared/lib/isSampleProfiled.ts index b090cbfdfa3..1de4964f181 100644 --- a/src/shared/lib/isSampleProfiled.ts +++ b/src/shared/lib/isSampleProfiled.ts @@ -103,3 +103,58 @@ export function isSampleProfiledInProfile( genePanelMap[profileId][sampleId].profiled === true ); } + +function getPatientProfiledReport( + uniquePatientKey: string, + coverageInformation: CoverageInformation, + hugoGeneSymbol?: string +): { [molecularProfileId: string]: boolean } { + // returns a map whose keys are the profiles which the patient is profiled in + const patientCoverage = coverageInformation.patients[uniquePatientKey]; + + // no patient coverage for patient + if (!patientCoverage) return {}; + + const byGeneCoverage = hugoGeneSymbol + ? patientCoverage.byGene[hugoGeneSymbol] + : _.flatten(_.values(patientCoverage.byGene)); + + // no by gene coverage for gene AND there's no allGene data available + if (!byGeneCoverage && patientCoverage.allGenes.length === 0) return {}; + + const ret: { [m: string]: boolean } = {}; + + // does molecular profile appear in the GENE specific panel data + for (const gpData of byGeneCoverage || []) { + ret[gpData.molecularProfileId] = true; + } + + // does molecular profile appear in CROSS gene panel data + for (const gpData of patientCoverage.allGenes) { + ret[gpData.molecularProfileId] = true; + } + + return ret; +} + +export function isPatientProfiledInMultiple( + uniquePatientKey: string, + molecularProfileIds: string[] | undefined, + coverageInformation: CoverageInformation, + hugoGeneSymbol?: string +): boolean[] { + // returns empty list if molecularProfileIds is undefined + if (!molecularProfileIds) { + return []; + } else { + // returns boolean[] in same order as molecularProfileIds + const profiledReport = getPatientProfiledReport( + uniquePatientKey, + coverageInformation, + hugoGeneSymbol + ); + return molecularProfileIds.map( + molecularProfileId => !!profiledReport[molecularProfileId] + ); + } +}