From 458aa5c463b8e7b1ee41f1ec5ef1ee4fe2836f17 Mon Sep 17 00:00:00 2001 From: Tatiana Date: Mon, 9 Sep 2024 14:37:07 -0700 Subject: [PATCH] Add custom attribute type to the Plots tab --- src/pages/resultsView/ResultsViewPage.tsx | 3 + src/pages/studyView/StudyViewPage.tsx | 4 + src/shared/components/plots/PlotsTab.tsx | 183 ++++++++++++++++-- src/shared/components/plots/PlotsTabUtils.tsx | 42 ++++ 4 files changed, 214 insertions(+), 18 deletions(-) diff --git a/src/pages/resultsView/ResultsViewPage.tsx b/src/pages/resultsView/ResultsViewPage.tsx index 09423eeade7..f5cf1b9b3b4 100644 --- a/src/pages/resultsView/ResultsViewPage.tsx +++ b/src/pages/resultsView/ResultsViewPage.tsx @@ -266,6 +266,9 @@ export default class ResultsViewPage extends React.Component< sampleKeyToSample={store.sampleKeyToSample} genes={store.genes} clinicalAttributes={store.clinicalAttributes} + customAttributes={ + store.clinicalAttributes_customCharts + } genesets={store.genesets} genericAssayEntitiesGroupByMolecularProfileId={ store.genericAssayEntitiesGroupByMolecularProfileId diff --git a/src/pages/studyView/StudyViewPage.tsx b/src/pages/studyView/StudyViewPage.tsx index 0971f3c73fe..fbfc9cf9bc2 100644 --- a/src/pages/studyView/StudyViewPage.tsx +++ b/src/pages/studyView/StudyViewPage.tsx @@ -769,6 +769,10 @@ export default class StudyViewPage extends React.Component< clinicalAttributes={ this.store.clinicalAttributes } + customAttributes={ + this.store + .clinicalAttributes_customCharts + } genesets={this.store.genesets} genericAssayEntitiesGroupByMolecularProfileId={ this.store diff --git a/src/shared/components/plots/PlotsTab.tsx b/src/shared/components/plots/PlotsTab.tsx index 814df5b16f7..b45456d5bd5 100644 --- a/src/shared/components/plots/PlotsTab.tsx +++ b/src/shared/components/plots/PlotsTab.tsx @@ -18,6 +18,7 @@ import _ from 'lodash'; import { axisHasNegativeNumbers, boxPlotTooltip, + CUSTOM_ATTR_DATA_TYPE, CLIN_ATTR_DATA_TYPE, CNA_STROKE_WIDTH, dataTypeDisplayOrder, @@ -277,6 +278,7 @@ export interface IPlotsTabProps { sampleKeyToSample: MobxPromise<_.Dictionary>; genes: MobxPromise; clinicalAttributes: MobxPromise; + customAttributes: MobxPromise; genesets: MobxPromise; genericAssayEntitiesGroupByMolecularProfileId: MobxPromise<{ [profileId: string]: GenericAssayMeta[]; @@ -696,6 +698,31 @@ export default class PlotsTab extends React.Component { ); } break; + case CUSTOM_ATTR_DATA_TYPE: + if ( + this.horzSelection.dataSourceId !== undefined && + this.customAttributesGroupByclinicalAttributeId.isComplete + ) { + const attributes = this + .customAttributesGroupByclinicalAttributeId.result![ + this.horzSelection.dataSourceId + ]; + const studyIds = attributes.map( + attribute => attribute.studyId + ); + horzAxisStudies = this.props.studies.result.filter(study => + studyIds.includes(study.studyId) + ); + components.push( +
+ Horizontal Axis: + {`${horzAxisDataSampleCount} samples from ${ + horzAxisStudies.length + } ${Pluralize('study', horzAxisStudies.length)}`} +
+ ); + } + break; default: // molecular profile if ( @@ -731,6 +758,31 @@ export default class PlotsTab extends React.Component { case NONE_SELECTED_OPTION_STRING_VALUE: isVertAxisNoneOptionSelected = true; break; + case CUSTOM_ATTR_DATA_TYPE: + if ( + this.vertSelection.dataSourceId !== undefined && + this.customAttributesGroupByclinicalAttributeId.isComplete + ) { + const attributes = this + .customAttributesGroupByclinicalAttributeId.result![ + this.vertSelection.dataSourceId + ]; + const studyIds = attributes.map( + attribute => attribute.studyId + ); + vertAxisStudies = this.props.studies.result.filter(study => + studyIds.includes(study.studyId) + ); + components.push( +
+ Vertical Axis: + {`${vertAxisDataSampleCount} samples from ${ + vertAxisStudies.length + } ${Pluralize('study', vertAxisStudies.length)}`} +
+ ); + } + break; case CLIN_ATTR_DATA_TYPE: if ( this.vertSelection.dataSourceId !== undefined && @@ -1121,7 +1173,8 @@ export default class PlotsTab extends React.Component { this._selectedGenesetOption && this._selectedGenesetOption.value === SAME_SELECTED_OPTION_STRING_VALUE && - self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE + (self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE || + self.horzSelection.dataType === CUSTOM_ATTR_DATA_TYPE) ) { // if vertical gene set option is "same as horizontal", and horizontal is clinical, then use the actual // gene set option value instead of "Same gene" option value, because that would be slightly weird UX @@ -1195,7 +1248,8 @@ export default class PlotsTab extends React.Component { this._selectedGenericAssayOption && this._selectedGenericAssayOption.value === SAME_SELECTED_OPTION_STRING_VALUE && - self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE + (self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE || + self.horzSelection.dataType === CUSTOM_ATTR_DATA_TYPE) ) { // if vertical gene set option is "same as horizontal", and horizontal is clinical, then use the actual // gene set option value instead of "Same gene" option value, because that would be slightly weird UX @@ -2063,10 +2117,45 @@ export default class PlotsTab extends React.Component { }, }); + readonly clinicalAndCustomAttributes = remoteData< + ExtendedClinicalAttribute[] + >({ + await: () => [ + this.props.clinicalAttributes, + this.props.customAttributes, + ], + invoke: () => { + return Promise.resolve([ + ...this.props.clinicalAttributes.result!, + ...this.props.customAttributes.result!, + ]); + }, + }); + + readonly clinicalAttributeOptions = remoteData({ + await: () => [this.props.clinicalAttributes], + invoke: () => + Promise.resolve( + makeClinicalAttributeOptions( + this.props.clinicalAttributes.result! + ) + ), + }); + + readonly customAttributeOptions = remoteData({ + await: () => [this.props.customAttributes], + invoke: () => + Promise.resolve( + makeClinicalAttributeOptions( + this.props.customAttributes.result! + ) + ), + }); + readonly coloringMenuOmnibarOptions = remoteData< (ColoringMenuOmnibarOption | ColoringMenuOmnibarGroup)[] >({ - await: () => [this.props.genes, this.props.clinicalAttributes], + await: () => [this.props.genes, this.clinicalAndCustomAttributes], invoke: () => { const allOptions: ( | Omit @@ -2088,7 +2177,7 @@ export default class PlotsTab extends React.Component { allOptions.push({ label: 'Clinical Attributes', - options: this.props.clinicalAttributes + options: this.clinicalAndCustomAttributes .result!.filter(a => { return ( a.clinicalAttributeId !== @@ -2104,7 +2193,6 @@ export default class PlotsTab extends React.Component { }; }), }); - if (allOptions.length > 0) { // add 'None' option to the top of the list to allow removing coloring of samples allOptions.unshift({ @@ -2365,6 +2453,7 @@ export default class PlotsTab extends React.Component { dataType !== NONE_SELECTED_OPTION_STRING_VALUE && dataType !== GENESET_DATA_TYPE && dataType !== CLIN_ATTR_DATA_TYPE && + dataType !== CUSTOM_ATTR_DATA_TYPE && !isGenericAssaySelected ); } @@ -2408,12 +2497,27 @@ export default class PlotsTab extends React.Component { readonly clinicalAttributeIdToClinicalAttribute = remoteData<{ [clinicalAttributeId: string]: ClinicalAttribute; }>({ - await: () => [this.props.clinicalAttributes, this.props.studyIds], + await: () => [this.props.studyIds, this.props.clinicalAttributes], invoke: () => { let _map: { [clinicalAttributeId: string]: ClinicalAttribute; } = _.keyBy( - this.props.clinicalAttributes.result, + this.props.clinicalAttributes.result!, + c => c.clinicalAttributeId + ); + return Promise.resolve(_map); + }, + }); + + readonly customAttributeIdToClinicalAttribute = remoteData<{ + [clinicalAttributeId: string]: ClinicalAttribute; + }>({ + await: () => [this.props.studyIds, this.props.customAttributes], + invoke: () => { + let _map: { + [clinicalAttributeId: string]: ClinicalAttribute; + } = _.keyBy( + this.props.customAttributes.result!, c => c.clinicalAttributeId ); return Promise.resolve(_map); @@ -2427,21 +2531,25 @@ export default class PlotsTab extends React.Component { invoke: () => { return Promise.resolve( _.groupBy( - this.props.clinicalAttributes.result, + this.props.clinicalAttributes.result!, c => c.clinicalAttributeId ) ); }, }); - readonly clinicalAttributeOptions = remoteData({ - await: () => [this.props.clinicalAttributes], - invoke: () => - Promise.resolve( - makeClinicalAttributeOptions( - this.props.clinicalAttributes.result! + readonly customAttributesGroupByclinicalAttributeId = remoteData<{ + [clinicalAttributeId: string]: ClinicalAttribute[]; + }>({ + await: () => [this.props.customAttributes], + invoke: () => { + return Promise.resolve( + _.groupBy( + this.props.customAttributes.result!, + c => c.clinicalAttributeId ) - ), + ); + }, }); readonly dataTypeOptions = remoteData({ @@ -2450,6 +2558,7 @@ export default class PlotsTab extends React.Component { this.clinicalAttributeOptions, this.props.molecularProfilesInStudies, this.props.genesets, + this.customAttributeOptions, ], invoke: () => { const profiles = this.props.molecularProfilesWithData.result!; @@ -2476,6 +2585,10 @@ export default class PlotsTab extends React.Component { dataTypeIds.push(CLIN_ATTR_DATA_TYPE); } + if (this.customAttributeOptions.result!.length) { + dataTypeIds.push(CUSTOM_ATTR_DATA_TYPE); + } + if ( this.props.molecularProfilesInStudies.result!.length && this.horzGenesetOptions.result && @@ -2532,6 +2645,7 @@ export default class PlotsTab extends React.Component { await: () => [ this.props.molecularProfilesInStudies, this.clinicalAttributeOptions, + this.customAttributeOptions, ], invoke: () => { const profiles = this.props.molecularProfilesInStudies.result!; @@ -2584,6 +2698,13 @@ export default class PlotsTab extends React.Component { CLIN_ATTR_DATA_TYPE ] = this.clinicalAttributeOptions.result!; } + + if (this.customAttributeOptions.result!.length) { + // add custom attributes + map[ + CUSTOM_ATTR_DATA_TYPE + ] = this.customAttributeOptions.result!; + } return Promise.resolve(map); }, }); @@ -2686,6 +2807,7 @@ export default class PlotsTab extends React.Component { @computed get hasMolecularProfile() { return (dataType: string | undefined) => dataType !== CLIN_ATTR_DATA_TYPE && + dataType !== CUSTOM_ATTR_DATA_TYPE && dataType !== AlterationTypeConstants.GENERIC_ASSAY; } @@ -2822,6 +2944,7 @@ export default class PlotsTab extends React.Component { selection.dataType !== undefined && selection.dataType !== NONE_SELECTED_OPTION_STRING_VALUE && selection.dataType !== CLIN_ATTR_DATA_TYPE && + selection.dataType !== CUSTOM_ATTR_DATA_TYPE && selection.dataType !== GENESET_DATA_TYPE && !isGenericAssaySelected(selection) ); @@ -3048,6 +3171,7 @@ export default class PlotsTab extends React.Component { return makeAxisDataPromise( this.horzSelection, this.clinicalAttributeIdToClinicalAttribute, + this.customAttributeIdToClinicalAttribute, this.props.molecularProfileIdSuffixToMolecularProfiles, this.props.patientKeyToFilteredSamples, this.props.entrezGeneIdToGene, @@ -3075,6 +3199,7 @@ export default class PlotsTab extends React.Component { return makeAxisDataPromise( this.vertSelection, this.clinicalAttributeIdToClinicalAttribute, + this.customAttributeIdToClinicalAttribute, this.props.molecularProfileIdSuffixToMolecularProfiles, this.props.patientKeyToFilteredSamples, this.props.entrezGeneIdToGene, @@ -3154,6 +3279,7 @@ export default class PlotsTab extends React.Component { this.props.molecularProfileIdSuffixToMolecularProfiles, this.props.entrezGeneIdToGene, this.clinicalAttributeIdToClinicalAttribute, + this.customAttributeIdToClinicalAttribute, this.plotType, ], invoke: () => { @@ -3164,6 +3290,7 @@ export default class PlotsTab extends React.Component { .result!, this.props.entrezGeneIdToGene.result!, this.clinicalAttributeIdToClinicalAttribute.result!, + this.customAttributeIdToClinicalAttribute.result!, this.horzLogScaleFunction ) ); @@ -3175,6 +3302,7 @@ export default class PlotsTab extends React.Component { this.props.molecularProfileIdSuffixToMolecularProfiles, this.props.entrezGeneIdToGene, this.clinicalAttributeIdToClinicalAttribute, + this.customAttributeIdToClinicalAttribute, ], invoke: () => { return Promise.resolve( @@ -3184,6 +3312,7 @@ export default class PlotsTab extends React.Component { .result!, this.props.entrezGeneIdToGene.result!, this.clinicalAttributeIdToClinicalAttribute.result!, + this.customAttributeIdToClinicalAttribute.result!, this.vertLogScaleFunction ) ); @@ -3195,6 +3324,7 @@ export default class PlotsTab extends React.Component { this.props.molecularProfileIdSuffixToMolecularProfiles, this.props.entrezGeneIdToGene, this.clinicalAttributeIdToClinicalAttribute, + this.customAttributeIdToClinicalAttribute, this.plotType, ], invoke: () => { @@ -3212,6 +3342,7 @@ export default class PlotsTab extends React.Component { .result!, this.props.entrezGeneIdToGene.result!, this.clinicalAttributeIdToClinicalAttribute.result!, + this.customAttributeIdToClinicalAttribute.result!, logScaleFunc ) ); @@ -3564,6 +3695,9 @@ export default class PlotsTab extends React.Component { : structuralVariantCountByOptions; switch (axisSelection.dataType) { + case CUSTOM_ATTR_DATA_TYPE: + dataSourceLabel = 'Custom Attribute'; + break; case CLIN_ATTR_DATA_TYPE: dataSourceLabel = 'Clinical Attribute'; break; @@ -3609,6 +3743,11 @@ export default class PlotsTab extends React.Component { .clinicalAttributeIdToClinicalAttribute.result![ dataSourceValue ].description; + } else if (axisSelection.dataType === CUSTOM_ATTR_DATA_TYPE) { + dataSourceDescription = this + .customAttributeIdToClinicalAttribute.result![ + dataSourceValue + ].description; } else { dataSourceDescription = this.props .molecularProfileIdSuffixToMolecularProfiles.result![ @@ -3939,7 +4078,9 @@ export default class PlotsTab extends React.Component { style={{ display: axisSelection.dataType === - CLIN_ATTR_DATA_TYPE + CLIN_ATTR_DATA_TYPE || + axisSelection.dataType === + CUSTOM_ATTR_DATA_TYPE ? 'none' : 'block', }} @@ -3979,7 +4120,9 @@ export default class PlotsTab extends React.Component { axisSelection.dataType === CLIN_ATTR_DATA_TYPE || axisSelection.dataType === - GENESET_DATA_TYPE + GENESET_DATA_TYPE || + axisSelection.dataType === + CUSTOM_ATTR_DATA_TYPE } loadOptions={loadOptions} cacheOptions={true} @@ -4017,7 +4160,9 @@ export default class PlotsTab extends React.Component { axisSelection.dataType === CLIN_ATTR_DATA_TYPE || axisSelection.dataType === - GENESET_DATA_TYPE + GENESET_DATA_TYPE || + axisSelection.dataType === + CUSTOM_ATTR_DATA_TYPE } /> @@ -4125,6 +4270,8 @@ export default class PlotsTab extends React.Component { undefined || axisSelection.dataType === CLIN_ATTR_DATA_TYPE || + axisSelection.dataType === + CUSTOM_ATTR_DATA_TYPE || !isGenericAssaySelected( axisSelection ) diff --git a/src/shared/components/plots/PlotsTabUtils.tsx b/src/shared/components/plots/PlotsTabUtils.tsx index 97ba12da992..d00b976b873 100644 --- a/src/shared/components/plots/PlotsTabUtils.tsx +++ b/src/shared/components/plots/PlotsTabUtils.tsx @@ -85,6 +85,7 @@ import { AnnotatedNumericGeneMolecularData } from 'shared/model/AnnotatedNumeric import { CustomDriverNumericGeneMolecularData } from 'shared/model/CustomDriverNumericGeneMolecularData'; export const CLIN_ATTR_DATA_TYPE = 'clinical_attribute'; +export const CUSTOM_ATTR_DATA_TYPE = 'custom_attribute'; export const GENESET_DATA_TYPE = 'GENESET_SCORE'; export const dataTypeToDisplayType: { [s: string]: string } = { [AlterationTypeConstants.MUTATION_EXTENDED]: 'Mutation', @@ -95,6 +96,7 @@ export const dataTypeToDisplayType: { [s: string]: string } = { [AlterationTypeConstants.METHYLATION]: 'DNA Methylation', [CLIN_ATTR_DATA_TYPE]: 'Clinical Attribute', [GENESET_DATA_TYPE]: 'Gene Sets', + [CUSTOM_ATTR_DATA_TYPE]: 'Custom Attribute', }; export const NO_GENE_OPTION = { @@ -115,6 +117,7 @@ export const mutationTypeToDisplayName: { export const dataTypeDisplayOrder = [ CLIN_ATTR_DATA_TYPE, + CUSTOM_ATTR_DATA_TYPE, AlterationTypeConstants.MUTATION_EXTENDED, AlterationTypeConstants.STRUCTURAL_VARIANT, AlterationTypeConstants.COPY_NUMBER_ALTERATION, @@ -1423,6 +1426,9 @@ export function makeAxisDataPromise( clinicalAttributeIdToClinicalAttribute: MobxPromise<{ [clinicalAttributeId: string]: ClinicalAttribute; }>, + customAttributeIdToClinicalAttribute: MobxPromise<{ + [clinicalAttributeId: string]: ClinicalAttribute; + }>, molecularProfileIdSuffixToMolecularProfiles: MobxPromise<{ [molecularProfileIdSuffix: string]: MolecularProfile[]; }>, @@ -1495,6 +1501,21 @@ export function makeAxisDataPromise( ); } break; + case CUSTOM_ATTR_DATA_TYPE: + if ( + selection.dataSourceId !== undefined && + customAttributeIdToClinicalAttribute.isComplete + ) { + const attribute = customAttributeIdToClinicalAttribute.result![ + selection.dataSourceId + ]; + ret = makeAxisDataPromise_Clinical( + attribute, + clinicalDataCache, + patientKeyToSamples + ); + } + break; case GENESET_DATA_TYPE: if ( selection.genesetId !== undefined && @@ -1553,6 +1574,9 @@ export function getAxisLabel( clinicalAttributeIdToClinicalAttribute: { [clinicalAttributeId: string]: ClinicalAttribute; }, + customAttributeIdToClinicalAttribute: { + [clinicalAttributeId: string]: ClinicalAttribute; + }, logScaleFunc: IAxisLogScaleParams | undefined ) { let label = ''; @@ -1570,6 +1594,13 @@ export function getAxisLabel( switch (selection.dataType) { case NONE_SELECTED_OPTION_STRING_VALUE: break; + case CUSTOM_ATTR_DATA_TYPE: + const customAttr = + customAttributeIdToClinicalAttribute[selection.dataSourceId!]; + if (customAttr) { + label = customAttr.displayName; + } + break; case CLIN_ATTR_DATA_TYPE: const attribute = clinicalAttributeIdToClinicalAttribute[selection.dataSourceId!]; @@ -1623,10 +1654,20 @@ export function getAxisDescription( }, clinicalAttributeIdToClinicalAttribute: { [clinicalAttributeId: string]: ClinicalAttribute; + }, + customAttributeIdToClinicalAttribute: { + [clinicalAttributeId: string]: ClinicalAttribute; } ) { let ret = ''; switch (selection.dataType) { + case CUSTOM_ATTR_DATA_TYPE: + const customAttr = + clinicalAttributeIdToClinicalAttribute[selection.dataSourceId!]; + if (customAttr) { + ret = customAttr.description; + } + break; case CLIN_ATTR_DATA_TYPE: const attribute = clinicalAttributeIdToClinicalAttribute[selection.dataSourceId!]; @@ -3169,6 +3210,7 @@ export function bothAxesNoMolecularProfile( vertSelection: AxisMenuSelection ): boolean { const noMolecularProfileDataTypes = [ + CUSTOM_ATTR_DATA_TYPE, CLIN_ATTR_DATA_TYPE, NONE_SELECTED_OPTION_STRING_VALUE, ];