import * as React from 'react'; import { observer } from 'mobx-react'; import { MakeMobxView } from '../../../shared/components/MobxView'; import LoadingIndicator from '../../../shared/components/loadingIndicator/LoadingIndicator'; import { getServerConfig, ServerConfigHelpers } from '../../../config/config'; import { MSKTab, MSKTabs } from '../../../shared/components/MSKTabs/MSKTabs'; import { PatientViewPageStore } from '../clinicalInformation/PatientViewPageStore'; import SampleManager from '../SampleManager'; import { IColumnVisibilityDef } from '../../../shared/components/columnVisibilityControls/ColumnVisibilityControls'; import { action, computed, makeObservable, observable } from 'mobx'; import autobind from 'autobind-decorator'; import PatientViewMutationsDataStore from './PatientViewMutationsDataStore'; import { ClinicalDataBySampleId, Mutation } from 'cbioportal-ts-api-client'; import MutationOncoprint from './oncoprint/MutationOncoprint'; import LabeledCheckbox from '../../../shared/components/labeledCheckbox/LabeledCheckbox'; import PatientViewMutationTable from './PatientViewMutationTable'; import { GeneFilterOption } from './GeneFilterMenu'; import { isFusion } from '../../../shared/lib/MutationUtils'; import PatientViewUrlWrapper from '../PatientViewUrlWrapper'; import WindowStore from '../../../shared/components/window/WindowStore'; import VAFChartWrapper from 'pages/patientView/timeline/VAFChartWrapper'; import TimelineWrapper from 'pages/patientView/timeline/TimelineWrapper'; import VAFChartWrapperStore from '../timeline/VAFChartWrapperStore'; import { ExtendedMutationTableColumnType } from 'shared/components/mutationTable/MutationTable'; import _ from 'lodash'; import { extractColumnNames } from 'shared/components/mutationMapper/MutationMapperUtils'; import SampleNotProfiledAlert from 'shared/components/SampleNotProfiledAlert'; export interface IPatientViewMutationsTabProps { patientViewPageStore: PatientViewPageStore; urlWrapper: PatientViewUrlWrapper; mutationTableColumnVisibility?: { [columnId: string]: boolean }; samples?: ClinicalDataBySampleId[]; onMutationTableColumnVisibilityToggled: ( columnId: string, columnVisibility?: IColumnVisibilityDef[] ) => void; sampleManager: SampleManager | null; sampleIds: string[]; mergeOncoKbIcons?: boolean; onOncoKbIconToggle?: (mergeIcons: boolean) => void; } enum PlotTab { LINE_CHART = 'lineChart', HEATMAP = 'heatmap', TIMELINE = 'timeline', } export const LOCAL_STORAGE_PLOT_TAB_KEY = 'patient_view_mutations_tab__vaf_plot_choice'; @observer export default class PatientViewMutationsTab extends React.Component< IPatientViewMutationsTabProps, {} > { constructor(props: IPatientViewMutationsTabProps) { super(props); makeObservable(this); } get showTimeline() { return ( this.props.urlWrapper.query.genomicEvolutionSettings .showTimeline === 'true' ); } set showTimeline(o: boolean) { this.props.urlWrapper.updateURL({ genomicEvolutionSettings: Object.assign( {}, this.props.urlWrapper.query.genomicEvolutionSettings, { showTimeline: o.toString(), } ), }); } @action.bound private toggleTimeline() { this.showTimeline = !this.showTimeline; } private vafChartWrapperStore = new VAFChartWrapperStore({ isOnlySequentialModeAvailable: () => { const isOnlySequentialOrderingAvailable = this.props.sampleManager?.isOnlySequentialOrderingAvailable( this.props.patientViewPageStore.clinicalEvents.result ); return isOnlySequentialOrderingAvailable == undefined ? false : isOnlySequentialOrderingAvailable; }, }); private dataStore = new PatientViewMutationsDataStore( () => this.mergedMutations, this.props.urlWrapper ); private vafLineChartSvg: SVGElement | null = null; get vafLineChartLogScale() { return ( this.props.urlWrapper.query.genomicEvolutionSettings .logScaleChart === 'true' ); } set vafLineChartLogScale(o: boolean) { this.props.urlWrapper.updateURL(currentParams => { currentParams.genomicEvolutionSettings.logScaleChart = o.toString(); return currentParams; }); } get vafLineChartZeroToOneYAxis() { const urlValue = this.props.urlWrapper.query.genomicEvolutionSettings .yAxisDataRangeInChart; return !urlValue || urlValue === 'true'; // default true } set vafLineChartZeroToOneYAxis(o: boolean) { this.props.urlWrapper.updateURL(currentParams => { currentParams.genomicEvolutionSettings.yAxisDataRangeInChart = o.toString(); return currentParams; }); } // TODO: replace this with URL stuff @observable private _plotTab = localStorage.getItem(LOCAL_STORAGE_PLOT_TAB_KEY) || PlotTab.LINE_CHART; @computed get plotTab() { return this._plotTab; } @action.bound private setPlotTab(id: PlotTab) { this._plotTab = id; localStorage.setItem(LOCAL_STORAGE_PLOT_TAB_KEY, id); } @autobind private vafLineChartSvgRef(elt: SVGElement | null) { this.vafLineChartSvg = elt; } @computed get mergedMutations() { // remove fusions return this.props.patientViewPageStore.mergedMutationDataIncludingUncalledFilteredByGene.filter( mutationArray => { return !isFusion(mutationArray[0]); } ); } readonly vafLineChart = MakeMobxView({ await: () => [ this.props.patientViewPageStore.coverageInformation, this.props.patientViewPageStore.samples, this.props.patientViewPageStore.mutationMolecularProfileId, ], renderPending: () => , render: () => ( <> {this.props.sampleManager && ( )} ), showLastRenderWhenPending: true, }); @autobind private onTableRowClick(d: Mutation[]) { if (d.length) { this.dataStore.toggleSelectedMutation(d[0]); } } @autobind private onTableRowMouseEnter(d: Mutation[]) { if (d.length) { this.dataStore.setMouseOverMutation(d[0]); } } @autobind private onTableRowMouseLeave() { this.dataStore.setMouseOverMutation(null); } @autobind private onFilterGenesMutationTable(option: GeneFilterOption): void { this.props.patientViewPageStore.mutationTableGeneFilterOption = option; } readonly table = MakeMobxView({ await: () => [ this.props.patientViewPageStore.mutationData, this.props.patientViewPageStore.uncalledMutationData, this.props.patientViewPageStore.oncoKbAnnotatedGenes, this.props.patientViewPageStore.studyIdToStudy, this.props.patientViewPageStore.sampleToMutationGenePanelId, this.props.patientViewPageStore.genePanelIdToEntrezGeneIds, ], renderPending: () => , render: () => (
this.dataStore.setOnlyShowSelectedInTable( !this.dataStore.onlyShowSelectedInTable ) } labelProps={{ style: { marginRight: 10 } }} inputProps={{ 'data-test': 'TableShowOnlyHighlighted' }} > Show only selected mutations
), }); @computed get columns(): ExtendedMutationTableColumnType[] { const namespaceColumnNames = extractColumnNames( this.dataStore.namespaceColumnConfig ); return _.concat( PatientViewMutationTable.defaultProps.columns, namespaceColumnNames ); } readonly timeline = MakeMobxView({ await: () => [this.props.patientViewPageStore.clinicalEvents], render: () => { if ( this.props.sampleManager !== null && this.props.patientViewPageStore.clinicalEvents.result!.length > 0 ) { return (
{this.showTimeline && (
)}
); } else { return null; } }, }); readonly tabUI = MakeMobxView({ await: () => [ this.table, this.vafLineChart, this.timeline, this.props.patientViewPageStore .genePanelDataByMolecularProfileIdAndSampleId, this.props.patientViewPageStore.mutationMolecularProfile, ], renderPending: () => ( ), render: () => { // if there are unprofiled samples, list them here const alert = ( ); return (
{this.timeline.component} {this.vafLineChart.component}
{this.table.component}
); }, }); render() { return this.tabUI.component; } }