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]
+ );
+ }
+}