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}
Clear All Filters
Get bookmark link for this filter
}
>
);
} else {
return null;
}
}
}