import * as React from 'react'; import _ from 'lodash'; import { observer } from 'mobx-react'; import { computed, makeObservable } from 'mobx'; import styles from './styles.module.scss'; import { DataFilterValue, AndedPatientTreatmentFilters, AndedSampleTreatmentFilters, GeneFilterQuery, PatientTreatmentFilter, SampleTreatmentFilter, } from 'cbioportal-ts-api-client'; import { DataType, FilterIconMessage, ChartType, getGenomicChartUniqueKey, getGenericAssayChartUniqueKey, DataBin, updateCustomIntervalFilter, } from 'pages/studyView/StudyViewUtils'; import { ChartMeta, geneFilterQueryToOql, getCNAColorByAlteration, getPatientIdentifiers, getSelectedGroupNames, getUniqueKeyFromMolecularProfileIds, intervalFiltersDisplayValue, StudyViewFilterWithSampleIdentifierFilters, } from 'pages/studyView/StudyViewUtils'; import { PillTag } from '../../shared/components/PillTag/PillTag'; import { GroupLogic } from './filters/groupLogic/GroupLogic'; import classnames from 'classnames'; import { STUDY_VIEW_CONFIG, ChartTypeEnum } from './StudyViewConfig'; import { DEFAULT_NA_COLOR } from 'shared/lib/Colors'; import { caseCounts, getSampleIdentifiers, StudyViewComparisonGroup, } from '../groupComparison/GroupComparisonUtils'; import { DefaultTooltip } from 'cbioportal-frontend-commons'; import { OredPatientTreatmentFilters, OredSampleTreatmentFilters, } from 'cbioportal-ts-api-client'; import { ClinicalDataFilter } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPI'; import { STRUCTURAL_VARIANT_COLOR, MUT_COLOR_MISSENSE, } from 'cbioportal-frontend-commons'; import { StudyViewPageStore } from 'pages/studyView/StudyViewPageStore'; export interface IUserSelectionsProps { store: StudyViewPageStore; filter: StudyViewFilterWithSampleIdentifierFilters; customChartsFilter: ClinicalDataFilter[]; numberOfSelectedSamplesInCustomSelection: number; comparisonGroupSelection: StudyViewComparisonGroup[]; attributesMetaSet: { [id: string]: ChartMeta & { chartType: ChartType } }; updateClinicalDataFilterByValues: ( uniqueKey: string, values: DataFilterValue[] ) => void; updateCustomChartFilter: (uniqueKey: string, values: string[]) => void; removeGeneFilter: (uniqueKey: string, oql: string) => void; updateGenomicDataIntervalFilter: ( uniqueKey: string, values: DataFilterValue[] ) => void; updateGenericAssayDataFilter: ( uniqueKey: string, values: DataFilterValue[] ) => void; removeCustomSelectionFilter: () => void; removeComparisonGroupSelectionFilter: () => void; clearAllFilters: () => void; clinicalAttributeIdToDataType: { [key: string]: string }; onBookmarkClick: () => void; molecularProfileNameSet: { [key: string]: string }; caseListNameSet: { [key: string]: string }; removeGenomicProfileFilter: (value: string) => void; removeCaseListsFilter: (value: string) => void; removeTreatmentsFilter: ( andedIndex: number, oredIndex: number, metaKey: string ) => void; } @observer export default class UserSelections extends React.Component< IUserSelectionsProps, {} > { constructor(props: IUserSelectionsProps) { super(props); makeObservable(this); } @computed get showFilters() { //return isFiltered(this.props.filter) return this.allComponents.length > 0; } @computed get allComponents() { let components = [] as JSX.Element[]; // Show the filter for the custom selection if (this.props.numberOfSelectedSamplesInCustomSelection > 0) { components.push(
Custom Selection , 1 ? 's' : '' }`} backgroundColor={ STUDY_VIEW_CONFIG.colors.theme .clinicalFilterContent } onDelete={() => this.props.removeCustomSelectionFilter() } />, ]} operation={':'} group={false} />
); } // Show the filter for the comparison group selection if (this.props.comparisonGroupSelection.length > 0) { const numSamples = getSampleIdentifiers( this.props.comparisonGroupSelection ).length; const numPatients = getPatientIdentifiers( this.props.comparisonGroupSelection ).length; components.push(
{getSelectedGroupNames( this.props.comparisonGroupSelection )} , this.props.removeComparisonGroupSelectionFilter() } />, ]} operation={':'} group={false} />
); } this.renderClinicalDataFilters( this.props.filter.clinicalDataFilters, components, this.props.updateClinicalDataFilterByValues ); this.renderClinicalDataFilters( this.props.filter.customDataFilters, components, (uniqueKey: string, values: DataFilterValue[]) => { this.props.updateCustomChartFilter( uniqueKey, values.map(datum => datum.value) ); } ); // Genomic Bar chart filters _.reduce( this.props.filter.genomicDataFilters || [], (acc, genomicDataIntervalFilter) => { const uniqueKey = getGenomicChartUniqueKey( genomicDataIntervalFilter.hugoGeneSymbol, genomicDataIntervalFilter.profileType ); const chartMeta = this.props.attributesMetaSet[uniqueKey]; if (chartMeta) { acc.push(
{chartMeta.displayName} , this.renderDataBinFilter( genomicDataIntervalFilter.values, ( chartUniqueKey: string, newRange: { start?: number; end?: number; } ) => { updateCustomIntervalFilter( newRange, chartMeta, this.props.store .getGenomicChartDataBin, this.props.store .getGenomicDataIntervalFiltersByUniqueKey, this.props.store .updateCustomBins, this.props.store .updateGenomicDataIntervalFilters ); }, this.props .updateGenomicDataIntervalFilter, chartMeta ), ]} operation={':'} group={false} />
); } return acc; }, components ); // Generic Assay chart filters let genericAssayFilterComponents: JSX.Element[] = []; if (this.props.filter.genericAssayDataFilters) { _.forEach( this.props.filter.genericAssayDataFilters, genericAssayDataFilter => { const uniqueKey = getGenericAssayChartUniqueKey( genericAssayDataFilter.stableId, genericAssayDataFilter.profileType ); const chartMeta = this.props.attributesMetaSet[uniqueKey]; if (chartMeta) { genericAssayFilterComponents.push(
{chartMeta.displayName} , { updateCustomIntervalFilter( newRange, chartMeta, this.props.store .getGenericAssayChartDataBin, this.props.store .getGenericAssayDataFiltersByUniqueKey, this.props.store .updateCustomBins, this.props.store .updateGenericAssayDataFilters ); } )} backgroundColor={ STUDY_VIEW_CONFIG.colors.theme .clinicalFilterContent } onDelete={() => this.props.updateGenericAssayDataFilter( chartMeta.uniqueKey, [] ) } />, ]} operation={':'} group={false} />
); } } ); } // All custom data charts this.renderClinicalDataFilters( this.props.customChartsFilter, components, (uniqueKey: string, values: DataFilterValue[]) => { this.props.updateCustomChartFilter( uniqueKey, values.map(datum => datum.value) ); } ); _.reduce( this.props.filter.geneFilters || [], (acc, geneFilter) => { const uniqueKey = getUniqueKeyFromMolecularProfileIds( geneFilter.molecularProfileIds ); const chartMeta = this.props.attributesMetaSet[uniqueKey]; if (chartMeta) { acc.push(
{ return ( 1} /> ); } )} operation={'and'} group={false} />
); } return acc; }, components ); if (!_.isEmpty(this.props.filter.genomicProfiles)) { components.push(
{ return ( 1} /> ); } )} operation={'and'} group={false} />
); } if (!_.isEmpty(this.props.filter.caseLists)) { components.push(
{ return ( 1} /> ); } )} operation={'and'} group={false} />
); } // push to components if (genericAssayFilterComponents) { components.push(...genericAssayFilterComponents); } if ( this.props.filter.sampleTreatmentFilters && this.props.filter.sampleTreatmentFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.sampleTreatmentFilters, 'SAMPLE_TREATMENTS' ); components.push(f); } if ( this.props.filter.patientTreatmentFilters && this.props.filter.patientTreatmentFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.patientTreatmentFilters, 'PATIENT_TREATMENTS' ); components.push(f); } if ( this.props.filter.sampleTreatmentGroupFilters && this.props.filter.sampleTreatmentGroupFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.sampleTreatmentGroupFilters, 'SAMPLE_TREATMENT_GROUPS' ); components.push(f); } if ( this.props.filter.patientTreatmentGroupFilters && this.props.filter.patientTreatmentGroupFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.patientTreatmentGroupFilters, 'PATIENT_TREATMENT_GROUPS' ); components.push(f); } if ( this.props.filter.sampleTreatmentTargetFilters && this.props.filter.sampleTreatmentTargetFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.sampleTreatmentTargetFilters, 'SAMPLE_TREATMENT_TARGET' ); components.push(f); } if ( this.props.filter.patientTreatmentTargetFilters && this.props.filter.patientTreatmentTargetFilters.filters.length > 0 ) { const f = this.renderTreatmentFilter( this.props.filter.patientTreatmentTargetFilters, 'PATIENT_TREATMENT_TARGET' ); components.push(f); } return components; } private renderClinicalDataFilters( filters: ClinicalDataFilter[], components: JSX.Element[], onDelete: (chartUniqueKey: string, values: DataFilterValue[]) => void ) { return _.reduce( filters || [], (acc, clinicalDataFilter) => { const chartMeta = this.props.attributesMetaSet[ clinicalDataFilter.attributeId ]; if (chartMeta) { const dataType = this.props.clinicalAttributeIdToDataType[ clinicalDataFilter.attributeId ]; let dataFilterComponent = dataType === DataType.STRING ? this.renderCategoricalDataFilter( clinicalDataFilter, onDelete, chartMeta ) : this.renderDataBinFilter( clinicalDataFilter.values, ( chartUniqueKey: string, newRange: { start?: number; end?: number } ) => { updateCustomIntervalFilter( newRange, chartMeta, this.props.store.getClinicalDataBin, this.props.store .getClinicalDataFiltersByUniqueKey, this.props.store.updateCustomBins, this.props.store .updateClinicalDataIntervalFilters ); }, onDelete, chartMeta ); acc.push(
{chartMeta.displayName} , dataFilterComponent, ]} operation={':'} group={false} />
); } return acc; }, components ); } // Bar chart filter private renderDataBinFilter( values: DataFilterValue[], onUpdate: ( chartUniqueKey: string, update: { start?: number; end?: number } ) => void, onDelete: (chartUniqueKey: string, values: DataFilterValue[]) => void, chartMeta: ChartMeta ): JSX.Element { return ( onUpdate(chartMeta.uniqueKey, update) )} backgroundColor={ STUDY_VIEW_CONFIG.colors.theme.clinicalFilterContent } onDelete={() => onDelete(chartMeta.uniqueKey, [])} /> ); } // Pie chart filter private renderCategoricalDataFilter( clinicalDataFilter: ClinicalDataFilter, onDelete: (chartUniqueKey: string, values: DataFilterValue[]) => void, chartMeta: ChartMeta & { chartType: ChartType } ): JSX.Element { return ( { return ( onDelete( chartMeta.uniqueKey, _.remove( clinicalDataFilter.values, value => value.value !== clinicalDataFilterValue.value ) ) } /> ); } )} operation={'or'} group={false} /> ); } private renderTreatmentFilter( f: AndedPatientTreatmentFilters | AndedSampleTreatmentFilters, metaKey: string ): JSX.Element { type OuterFilter = | OredPatientTreatmentFilters | OredSampleTreatmentFilters; type InnerFilter = PatientTreatmentFilter | SampleTreatmentFilter; // the gross "as any" casting shouldn't be necessary for a type of // OredSampleTreatmentFilters[] | OredPatientTreatmentFilters[], // but the compiler complains if I remove it const filters = (f.filters as any).map( (oFilter: OuterFilter, oIndex: number) => { const pills = (oFilter.filters as any).map( (iFilter: InnerFilter, iIndex: number) => { const str = iFilter.hasOwnProperty('time') ? (iFilter as SampleTreatmentFilter).time : ''; return ( { this.props.removeTreatmentsFilter( oIndex, iIndex, metaKey ); }} /> ); } ); return ( ); } ); return ( ); } private groupedGeneQueries( geneQueries: GeneFilterQuery[], chartMeta: ChartMeta & { chartType: ChartType } ): JSX.Element[] { return geneQueries.map(geneQuery => { let color = DEFAULT_NA_COLOR; let displayGeneSymbol = geneQuery.hugoGeneSymbol; switch (chartMeta.chartType) { case ChartTypeEnum.MUTATED_GENES_TABLE: color = MUT_COLOR_MISSENSE; break; case ChartTypeEnum.STRUCTURAL_VARIANT_GENES_TABLE: color = STRUCTURAL_VARIANT_COLOR; break; case ChartTypeEnum.CNA_GENES_TABLE: { if (geneQuery.alterations.length === 1) { let tagColor = getCNAColorByAlteration( geneQuery.alterations[0] ); if (tagColor) { color = tagColor; } } break; } } return ( } onDelete={() => this.props.removeGeneFilter( chartMeta.uniqueKey, geneFilterQueryToOql(geneQuery) ) } /> ); }); } private groupedGenomicProfiles(genomicProfiles: string[]): JSX.Element[] { return genomicProfiles.map(profile => { return ( this.props.removeGenomicProfileFilter(profile) } /> ); }); } private groupedCaseLists(caseLists: string[]): JSX.Element[] { return caseLists.map(caseList => { return ( this.props.removeCaseListsFilter(caseList)} /> ); }); } render() { if (this.showFilters) { return (
{this.allComponents} Get bookmark link for this filter
} > ); } else { return null; } } }