diff --git a/src/pages/patientView/PatientViewPage.tsx b/src/pages/patientView/PatientViewPage.tsx index b85e2e00e5c..61806e50044 100644 --- a/src/pages/patientView/PatientViewPage.tsx +++ b/src/pages/patientView/PatientViewPage.tsx @@ -514,6 +514,10 @@ export class PatientViewPageInner extends React.Component< onMutationalSignatureVersionChange(version: string) { this.pageStore.setMutationalSignaturesVersion(version); } + @action.bound + onSampleIdChange(sample: string) { + this.pageStore.setSampleMutationalSignatureData(sample); + } @computed get columns(): ExtendedMutationTableColumnType[] { const namespaceColumnNames = extractColumnNames( diff --git a/src/pages/patientView/PatientViewPageTabs.tsx b/src/pages/patientView/PatientViewPageTabs.tsx index a952b1a2373..de3b0049381 100644 --- a/src/pages/patientView/PatientViewPageTabs.tsx +++ b/src/pages/patientView/PatientViewPageTabs.tsx @@ -94,7 +94,6 @@ export function tabs( sampleManager: SampleManager | null ) { const tabs: JSX.Element[] = []; - tabs.push( ); diff --git a/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.spec.tsx b/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.spec.tsx index cc04d76059b..732ec57ca7f 100644 --- a/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.spec.tsx +++ b/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.spec.tsx @@ -12,7 +12,7 @@ const sampleMutationalSignatureMeta = [ mutationalSignatureId: 'firstMutationalSignature', name: 'Mutational Signature 1', description: 'Mutational Signature 1', - url: 'url 1', + url: 'COSMIC/FakeMutationalSignature1', category: 'category 1', confidenceStatement: 'Signature 1, the aging signature, is detected in this case.', @@ -21,7 +21,7 @@ const sampleMutationalSignatureMeta = [ mutationalSignatureId: 'secondMutationalSignature', name: 'Mutational Signature 2', description: 'Mutational Signature 2', - url: 'url 2', + url: 'COSMIC/FakeMutationalSignature2', category: 'category 2', confidenceStatement: 'Signature 2, the APOBEC signature, is detected in this case. This signature often coccurs with signature 13, the other APOBEC signature', @@ -89,6 +89,7 @@ describe('ClinicalInformationMutationalSignatureTable', () => { confidence: 0.8, }, }, + url: 'COSMIC/FakeMutationalSignature1', }, { name: 'Mutational Signature 2', @@ -98,6 +99,7 @@ describe('ClinicalInformationMutationalSignatureTable', () => { confidence: 0.4, }, }, + url: 'COSMIC/FakeMutationalSignature2', }, ]); }); diff --git a/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.tsx b/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.tsx index bea3d12d1ae..f174ec138ca 100644 --- a/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.tsx +++ b/src/pages/patientView/clinicalInformation/ClinicalInformationMutationalSignatureTable.tsx @@ -8,11 +8,12 @@ import { IMutationalSignature } from '../../../shared/model/MutationalSignature' import { getMutationalSignaturePercentage } from '../../../shared/lib/FormatUtils'; import _ from 'lodash'; import { observer } from 'mobx-react'; -import { computed, makeObservable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { MUTATIONAL_SIGNATURES_SIGNIFICANT_PVALUE_THRESHOLD } from 'shared/lib/GenericAssayUtils/MutationalSignaturesUtils'; export interface IClinicalInformationMutationalSignatureTableProps { data: IMutationalSignature[]; + parentCallback: (childData: string, visibility: boolean) => void; } class MutationalSignatureTable extends LazyMobXTable {} @@ -26,13 +27,13 @@ interface IMutationalSignatureRow { confidence: number; }; }; + url: string; } export function prepareMutationalSignatureDataForTable( mutationalSignatureData: IMutationalSignature[] ): IMutationalSignatureRow[] { const tableData: IMutationalSignatureRow[] = []; - //group data by mutational signature //[{id: mutationalsignatureid, samples: [{}, {}]}] let sampleInvertedDataByMutationalSignature: Array = _( @@ -44,15 +45,17 @@ export function prepareMutationalSignatureDataForTable( .map((mutationalSignatureSampleData, name) => ({ name, samples: mutationalSignatureSampleData, + url: mutationalSignatureSampleData[0].meta.url, })) .value(); - for (const mutationalSignature of sampleInvertedDataByMutationalSignature) { let mutationalSignatureRowForTable: IMutationalSignatureRow = { name: '', sampleValues: {}, + url: '', }; mutationalSignatureRowForTable.name = mutationalSignature.name; + mutationalSignatureRowForTable.url = mutationalSignature.url; for (const sample of mutationalSignature.samples) { mutationalSignatureRowForTable.sampleValues[sample.sampleId] = { value: sample.value, @@ -63,17 +66,28 @@ export function prepareMutationalSignatureDataForTable( } return tableData; } - @observer export default class ClinicalInformationMutationalSignatureTable extends React.Component< IClinicalInformationMutationalSignatureTableProps, {} > { + @observable selectedSignature = ''; + sendData = () => { + this.props.parentCallback(this.selectedSignature, true); + }; + constructor(props: IClinicalInformationMutationalSignatureTableProps) { super(props); makeObservable(this); } + @action.bound getMutationalSignatureProfileData( + e: React.MouseEvent + ): void { + this.selectedSignature = e.currentTarget.innerHTML; + this.sendData(); + } + @computed get uniqueSamples() { return _.map(_.uniqBy(this.props.data, 'sampleId'), uniqSample => ({ id: uniqSample.sampleId, @@ -83,13 +97,21 @@ export default class ClinicalInformationMutationalSignatureTable extends React.C @computed get tableData() { return prepareMutationalSignatureDataForTable(this.props.data); } + readonly firstCol = 'name'; @computed get columns(): Column[] { return [ { name: 'Mutational Signature', render: (data: IMutationalSignatureRow) => ( - {data[this.firstCol]} + + {data[this.firstCol]} + ), download: (data: IMutationalSignatureRow) => `${data[this.firstCol]}`, @@ -109,6 +131,8 @@ export default class ClinicalInformationMutationalSignatureTable extends React.C render: (data: IMutationalSignatureRow) => data.sampleValues[col.id].confidence < MUTATIONAL_SIGNATURES_SIGNIFICANT_PVALUE_THRESHOLD ? ( //if it's a significant signature, bold the contribution + // Based on significant pvalue the span is created with style.mutationalSignatureValue for bold (sign) + // or normal styling (not signficant) {getMutationalSignaturePercentage( data.sampleValues[col.id].value diff --git a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts index d4ed5ac909e..59158feb67f 100644 --- a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts +++ b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts @@ -182,6 +182,7 @@ import { import { IMutationalSignature, IMutationalSignatureMeta, + IMutationalCounts, } from 'shared/model/MutationalSignature'; import { GenericAssayTypeConstants } from 'shared/lib/GenericAssayUtils/GenericAssayConfig'; @@ -702,6 +703,106 @@ export class PatientViewPageStore { [] ); + readonly fetchAllMutationalSignatureCountMetaData = remoteData({ + await: () => [this.fetchAllMutationalSignatureData], + invoke: async () => { + const mutationalSignatureCountStableIds = _.chain( + this.fetchAllMutationalSignatureData.result + ) + .map((data: GenericAssayData) => data.stableId) + .uniq() + .filter(stableId => + stableId.includes( + MutationalSignatureStableIdKeyWord.MutationalSignatureCountKeyWord + ) + ) + .value(); + if (mutationalSignatureCountStableIds.length > 0) { + return client.fetchGenericAssayMetaUsingPOST({ + genericAssayMetaFilter: { + genericAssayStableIds: mutationalSignatureCountStableIds, + } as GenericAssayMetaFilter, + }); + } else { + return Promise.resolve([]); + } + }, + }); + readonly mutationalSignatureCountDataGroupedByVersion = remoteData( + { + await: () => [ + this.fetchAllMutationalSignatureData, + this.mutationData, + this.fetchAllMutationalSignatureCountMetaData, + ], + invoke: async () => { + const countData = this.fetchAllMutationalSignatureData.result.filter( + data => + data.molecularProfileId.includes( + MutationalSignatureStableIdKeyWord.MutationalSignatureCountKeyWord + ) + ); + let signatureLabelMap = this.fetchAllMutationalSignatureCountMetaData.result!.map( + (metaData: GenericAssayMeta) => { + const nameSig: string = + 'MUTATION_TYPE' in + metaData.genericEntityMetaProperties + ? metaData.genericEntityMetaProperties[ + 'MUTATION_TYPE' + ] + : ''; + const classSig: string = + 'MUTATION_CLASS' in + metaData.genericEntityMetaProperties + ? metaData.genericEntityMetaProperties[ + 'MUTATION_CLASS' + ] + : ''; + const mutNameSig: string = + 'NAME' in metaData.genericEntityMetaProperties + ? metaData.genericEntityMetaProperties['NAME'] + : ''; + const signatureId = metaData.stableId; + return { + stableId: signatureId, + signatureLabel: nameSig, + signatureClass: classSig, + name: mutNameSig, + }; + } + ); + const result: IMutationalCounts[] = []; + // only loop the contribution data then find and fill in the paired confidence data + if (countData && countData.length > 0) { + for (const count of countData) { + let mutationalSignatureChartData: IMutationalCounts = {} as IMutationalCounts; + mutationalSignatureChartData.patientId = + count.patientId; + mutationalSignatureChartData.sampleId = count.sampleId; + mutationalSignatureChartData.studyId = count.studyId; + mutationalSignatureChartData.uniquePatientKey = + count.uniquePatientKey; + mutationalSignatureChartData.uniqueSampleKey = + count.uniqueSampleKey; + mutationalSignatureChartData.version = + _.last(count.molecularProfileId.split('_')) || ''; + mutationalSignatureChartData.count = parseFloat( + count.value + ); + mutationalSignatureChartData.mutationalSignatureLabel = + signatureLabelMap + .filter(obj => obj.stableId === count.stableId) + .map(obj => obj.name)[0] || ''; + + result.push(mutationalSignatureChartData); + } + } + return Promise.resolve(_.groupBy(result, data => data.version)); + }, + }, + {} + ); + readonly mutationalSignatureMetaGroupByStableId = remoteData<{ [stableId: string]: IMutationalSignatureMeta; }>({ @@ -815,6 +916,40 @@ export class PatientViewPageStore { [] ); + @observable _selectedSampleIdMutationalSignatureData: string; + @action + setSampleMutationalSignatureData(sample: string) { + this._selectedSampleIdMutationalSignatureData = sample; + } + @computed get selectedSampleMutationalSignatureData() { + return ( + this._selectedSampleIdMutationalSignatureData || + this.fetchAllMutationalSignatureData.result + .filter(data => + data.molecularProfileId.includes( + MutationalSignatureStableIdKeyWord.MutationalSignatureCountKeyWord + ) + ) + .map(sample => sample.sampleId)[0] + ); + } + @computed get selectedSampleUniqueKeyMutationalSignatureData() { + const sampleToFilter = this.selectedSampleMutationalSignatureData; + return this.samplesWithUniqueKeys.result + .filter(item => item.sampleId === sampleToFilter) + .map(item => item.uniqueSampleKey); + } + @computed get samplesWithCountDataAvailable(): string[] { + return this.fetchAllMutationalSignatureData.result + .filter(data => + data.molecularProfileId.includes( + MutationalSignatureStableIdKeyWord.MutationalSignatureCountKeyWord + ) + ) + .map(sample => sample.sampleId) + .filter((value, index, self) => self.indexOf(value) === index); + } + readonly samplesWithoutCancerTypeClinicalData = remoteData( { await: () => [this.samples, this.clinicalDataForSamples], diff --git a/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.spec.tsx b/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.spec.tsx new file mode 100644 index 00000000000..509ae7ce14d --- /dev/null +++ b/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.spec.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import { assert } from 'chai'; +import { IMutationalCounts } from 'shared/model/MutationalSignature'; +import { IMutationalSignature } from 'shared/model/MutationalSignature'; +import { + IColorDataBar, + IMutationalBarChartProps, + getColorsForSignatures, +} from './MutationalSignatureBarChart'; + +const sampleMutationalSignatureDataWithoutClass = [ + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID1', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: '', + version: 'v2', + count: 15, + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + uniquePatientKey: '34a8e91b3', + sampleId: 'sampleID2', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: '', + version: 'v2', + count: 12, + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + uniquePatientKey: '34a8e91b3', + sampleId: 'sampleID3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: '', + version: 'v2', + count: 20, + }, +]; + +const sampleMutationalSignatureData: IMutationalCounts[] = [ + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID1', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: 'C>T', + version: 'v2', + count: 15, + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + uniquePatientKey: '34a8e91b3', + sampleId: 'sampleID2', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: 'C>T', + version: 'v2', + count: 12, + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + uniquePatientKey: '34a8e91b3', + sampleId: 'sampleID3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[T>A]G', + mutationalSignatureClass: 'T>A', + version: 'v2', + count: 20, + }, +]; + +describe('MutationalSignatureBarChart', () => { + it('Takes unsorted IMutationalCounts[] and transforms it to sorted IColorDataChart', () => { + let result = getColorsForSignatures(sampleMutationalSignatureData); + assert.deepEqual(result, [ + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID1', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: 'C>T', + version: 'v2', + count: 15, + colorValue: 'red', + label: 'A[C>T]G', + group: 'C>T', + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID2', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + mutationalSignatureClass: 'C>T', + version: 'v2', + count: 12, + colorValue: 'red', + label: 'A[C>T]G', + group: 'C>T', + }, + + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID3', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[T>A]G', + mutationalSignatureClass: 'T>A', + version: 'v2', + count: 20, + colorValue: 'grey', + label: 'A[T>A]G', + group: 'T>A', + }, + ]); + }); + it('Takes unsorted IMutationalCounts[] and transforms it to unsorted IColorDataChart', () => { + let result = getColorsForSignatures( + sampleMutationalSignatureDataWithoutClass + ); + assert.deepEqual(result, [ + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID1', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + version: 'v2', + count: 15, + colorValue: '#EE4B2B', + label: 'A[C>T]G', + mutationalSignatureClass: '', + }, + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID2', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + version: 'v2', + count: 12, + colorValue: '#EE4B2B', + label: 'A[C>T]G', + mutationalSignatureClass: '', + }, + + { + uniqueSampleKey: 's09e3B34', + patientId: 'TestPatient001', + sampleId: 'sampleID3', + uniquePatientKey: '34a8e91b3', + studyId: 'TestStudy001', + mutationalSignatureLabel: 'A[C>T]G', + version: 'v2', + count: 20, + colorValue: '#EE4B2B', + label: 'A[C>T]G', + mutationalSignatureClass: '', + }, + ]); + }); +}); diff --git a/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.tsx b/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.tsx new file mode 100644 index 00000000000..ccd20a527b1 --- /dev/null +++ b/src/pages/patientView/mutationalSignatures/MutationalSignatureBarChart.tsx @@ -0,0 +1,340 @@ +import * as React from 'react'; +import { + VictoryBar, + VictoryAxis, + VictoryChart, + VictoryLabel, + VictoryStack, + VictoryTooltip, + VictoryLegend, +} from 'victory'; +import { action } from 'mobx'; +import { observer } from 'mobx-react'; +import _ from 'lodash'; +import { IMutationalCounts } from 'shared/model/MutationalSignature'; + +export interface IMutationalBarChartProps { + signature: string; + width: number; + height: number; + refStatus: boolean; + data: IMutationalCounts[]; + version: string; + sample: string; +} + +export interface IColorDataBar extends IMutationalCounts { + colorValue: string; + label: string; +} + +export interface colorMapProps { + name: string; + category: string; + color: string; +} + +const colorMap: colorMapProps[] = [ + { + name: 'C>A', + category: 'Single base substitution (C>A)', + color: 'lightblue', + }, + { + name: 'C>G', + category: 'Single base substitution (C>G)', + color: 'darkblue', + }, + { name: 'C>T', category: 'Single base substitution (C>T)', color: 'red' }, + { name: 'T>A', category: 'Single base substitution (T>A)', color: 'grey' }, + { name: 'T>C', category: 'Single base substitution (T>C)', color: 'green' }, + { name: 'T>G', category: 'Single base substitution (T>G)', color: 'pink' }, + { name: 'reference', category: 'reference', color: '#1e97f3' }, + { + name: 'AC>', + category: 'Doublet base substitution (AC>NN)', + color: 'skyblue', + }, + { + name: 'AT>', + category: 'Doublet base substitution (AT>NN)', + color: 'blue', + }, + { + name: 'CC>', + category: 'Doublet base substitution (CC>NN)', + color: 'lightgreen', + }, + { + name: 'CG>', + category: 'Doublet base substitution (CG>NN)', + color: 'darkgreen', + }, + { + name: 'CT>', + category: 'Doublet base substitution (CT>NN)', + color: 'pink', + }, + { + name: 'CG>', + category: 'Doublet base substitution (CG>NN)', + color: 'darkred', + }, + { + name: 'TA>', + category: 'Doublet base substitution (TA>NN)', + color: 'sand', + }, + { + name: 'TC>', + category: 'Doublet base substitution (TC>NN)', + color: 'orange', + }, + { + name: 'TG>', + category: 'Doublet base substitution (TG>NN)', + color: 'lila', + }, + { + name: 'TT>', + category: 'Doublet base substitution (TT>NN)', + color: 'purple', + }, + { + name: 'GC>', + category: 'Doublet base substitution (GC>NN)', + color: 'gold', + }, + { name: '1:Del:C', category: '1bp insertion (T)', color: '#f39c12' }, + { name: '1:Del:T', category: '1bp insertion (T)', color: '#d68910' }, + { name: '1:Ins:C', category: '1bp insertion (C)', color: '#82E0AA' }, + { name: '1:Ins:T', category: '1bp insertion (T)', color: '#28b463' }, + { name: '2:Del:R', category: '2bp deletion at repeats', color: '#f1948a' }, + { name: '3:Del:R', category: '3bp deletion at repeats', color: '#ec7063' }, + { name: '4:Del:R', category: '4bp deletion at repeats', color: '#e74c3c' }, + { name: '5:Del:R', category: '5bp deletion at repeats', color: '#cb4335' }, + { name: '2:Ins:M', category: '2bp insertion at repeats', color: '#aed6f1' }, + { name: '3:Ins:M', category: '3bp insertion at repeats', color: '#85c1e9' }, + { name: '4:Ins:M', category: '4bp insertion at repeats', color: '#85c1e9' }, + { name: '5:Ins:M', category: '5bp insertion at repeats', color: '#3498db' }, + { + name: 'Microhomology (Deletion length 2)', + category: 'Microhomology (Deletion length 2)', + color: '#c39bd3', + }, + { + name: 'Microhomology (Deletion length 3)', + category: 'Microhomology (Deletion length 3)', + color: '#9b59b6', + }, + { + name: 'Microhomology (Deletion length 4)', + category: 'Microhomology (Deletion length 4)', + color: '#7d3c98', + }, + { + name: 'Microhomology (Deletion length 5)', + category: 'Microhomology (Deletion length 5)', + color: '#4a235a', + }, +]; + +export function transformMutationalSignatureData(dataset: IMutationalCounts[]) { + const transformedDataSet = dataset.map((obj: IMutationalCounts) => { + let referenceTransformed = -Math.abs(obj.count); + return { ...obj, referenceTransformed }; + }); + return transformedDataSet; +} + +export function getColorsForSignatures(dataset: IMutationalCounts[]) { + const colorTableData = dataset.map((obj: IMutationalCounts) => { + if (obj.mutationalSignatureClass !== '') { + let colorIdentity = colorMap.filter(cmap => { + if (obj.mutationalSignatureLabel.match(cmap.name) !== null) { + return cmap.color; + } + }); + const label = obj.mutationalSignatureLabel; + const group = + colorIdentity.length > 0 ? colorIdentity[0].name : 'unkown'; + const colorValue = + colorIdentity.length > 0 ? colorIdentity[0].color : '#EE4B2B'; + return { ...obj, colorValue, label, group }; + } else { + const label = obj.mutationalSignatureLabel; + const colorValue = '#EE4B2B'; + return { ...obj, colorValue, label }; + } + }); + if (colorTableData[0].hasOwnProperty('group')) { + const colorTableDataSorted = _.sortBy(colorTableData, 'group'); + return colorTableDataSorted; + } else { + return colorTableData; + } +} + +@observer +export default class MutationalBarChart extends React.Component< + IMutationalBarChartProps, + {} +> { + constructor(props: IMutationalBarChartProps) { + super(props); + } + + @action formatLegendColor(data: colorMapProps[]) { + let labelsPresent: string[] = []; + this.props.data.map((obj: IMutationalCounts) => { + colorMap.filter(cmap => { + if (obj.mutationalSignatureLabel.match(cmap.name) !== null) { + labelsPresent.push(cmap.name); + } + }); + }); + let dataLegend = data.filter((obj2: colorMapProps) => { + if (labelsPresent.includes(obj2.name)) { + return obj2; + } + }); + let legend = dataLegend.map((obj: colorMapProps) => { + let entry = { + name: obj.category, + symbol: { fill: obj.color }, + }; + return entry; + }); + return legend; + } + @action yAxisDomain(): number[] { + const maxValue = this.props.data.reduce( + (previous: IMutationalCounts, current: IMutationalCounts) => { + return current.count > previous.count ? current : previous; + } + ); + const minValue = this.props.data.reduce( + (previous: IMutationalCounts, current: IMutationalCounts) => { + return current.count < previous.count ? current : previous; + } + ); + return [minValue.count, maxValue.count + 0.1 * maxValue.count]; + } + + public render() { + return ( + + + + + + + } + barRatio={0.8} + barWidth={5} + data={getColorsForSignatures(this.props.data)} + x="label" + y="count" + style={{ + data: { + fill: (d: IColorDataBar) => d.colorValue, + stroke: 'black', + strokeWidth: 0.4, + }, + }} + /> + {this.props.refStatus && ( + } + barRatio={0.8} + barWidth={1} + data={transformMutationalSignatureData( + this.props.data + )} + x="id" + y="referenceTransformed" + style={{ + data: { + fill: '#1e97f3', + stroke: 'black', + strokeWidth: 0.8, + }, + }} + /> + )} + + {this.props.refStatus && ( + + )} + {!this.props.refStatus && ( + + )} + {this.props.refStatus && ( + ''} + style={{ + axis: { stroke: 'white', strokeWidth: 2 }, + }} + /> + )} + {!this.props.refStatus && ( + ''} + style={{ + axis: { stroke: 'black', strokeWidth: 2 }, + axisLabel: { fontSize: 8, padding: 30 }, + tickLabels: { fontSize: 8, padding: 5 }, + }} + /> + )} + + + ); + } +} diff --git a/src/pages/patientView/mutationalSignatures/MutationalSignaturesContainer.tsx b/src/pages/patientView/mutationalSignatures/MutationalSignaturesContainer.tsx index bbb417b947d..182ad564d20 100644 --- a/src/pages/patientView/mutationalSignatures/MutationalSignaturesContainer.tsx +++ b/src/pages/patientView/mutationalSignatures/MutationalSignaturesContainer.tsx @@ -1,23 +1,37 @@ import * as React from 'react'; -import { observer } from 'mobx-react'; -import { computed, action, makeObservable } from 'mobx'; -import autobind from 'autobind-decorator'; -import FeatureTitle from 'shared/components/featureTitle/FeatureTitle'; -import { IMutationalSignature } from 'shared/model/MutationalSignature'; +import { Observer, observer } from 'mobx-react'; +import { computed, action, makeObservable, observable } from 'mobx'; +import { + IMutationalSignature, + IMutationalCounts, +} from 'shared/model/MutationalSignature'; import ClinicalInformationMutationalSignatureTable from '../clinicalInformation/ClinicalInformationMutationalSignatureTable'; import Select from 'react-select'; -import { MolecularProfile } from 'cbioportal-ts-api-client'; +import { + ClinicalDataBySampleId, + MolecularProfile, + Sample, +} from 'cbioportal-ts-api-client'; import { getVersionOption, getVersionOptions, + getSampleOptions, + getSampleOption, + MutationalSignaturesVersion, } from 'shared/lib/GenericAssayUtils/MutationalSignaturesUtils'; import _ from 'lodash'; +import MutationalBarChart from 'pages/patientView/mutationalSignatures/MutationalSignatureBarChart'; +import SignatureTextBox from 'pages/patientView/mutationalSignatures/SignatureTextBox'; export interface IMutationalSignaturesContainerProps { data: { [version: string]: IMutationalSignature[] }; profiles: MolecularProfile[]; - onVersionChange: (version: string) => void; version: string; + sample: string; + samples: string[]; + onVersionChange: (version: string) => void; + onSampleChange: (sample: string) => void; + dataCount: { [version: string]: IMutationalCounts[] }; } @observer @@ -25,10 +39,41 @@ export default class MutationalSignaturesContainer extends React.Component< IMutationalSignaturesContainerProps, {} > { + state = { + signatureProfile: this.props.data[this.props.version][0].meta.name, + signatureURL: this.props.data[this.props.version][0].meta.url, + signatureDescription: this.props.data[this.props.version][0].meta + .description, + visible: false, + }; + + callbackFunction = (childData: string, visibility: boolean) => { + this.setState({ + signatureProfile: childData, + visible: visibility, + signatureURL: this.props.data[this.props.version].filter(obj => { + if (childData === obj.meta.name) { + return obj; + } + })[0].meta.url, + signatureDescription: this.props.data[this.props.version].filter( + obj => { + if (childData === obj.meta.name) { + return obj; + } + } + )[0].meta.description, + }); + }; constructor(props: IMutationalSignaturesContainerProps) { super(props); makeObservable(this); } + + @observable _selectedSignature: string = this.state.signatureProfile; + @observable _selectedData: IMutationalCounts[] = this.props.dataCount[ + this.props.version + ]; @computed get availableVersions() { // mutational signatures version is stored in the profile id // split the id by "_", the last part is the version info @@ -41,6 +86,22 @@ export default class MutationalSignaturesContainer extends React.Component< .value(); } + @computed get availableSamples() { + return this.props.samples; + } + @observable urlSignature: string; + @observable descriptionSignature: string; + @observable modalVisible: boolean = this.state.visible; + @computed get selectURLSignature(): string { + let urlLink = this.props.data[this.props.version][0].meta.url; + return urlLink; + } + @computed get selectDescriptionSignature(): string { + let description = this.props.data[this.props.version][0].meta + .description; + return description; + } + @computed get selectedVersion(): string { // all versions is defined in the MutationalSignaturesVersion return ( @@ -51,9 +112,39 @@ export default class MutationalSignaturesContainer extends React.Component< ); } + @computed get selectedSample(): string { + return this.props.sample; + } + @observable + _mutationalSignatureCountDataGroupedByVersionForSample: IMutationalCounts[]; + @computed get mutationalSignatureCountDataGroupedByVersionForSample() { + const sampleIdToFilter = this.props.samples.filter( + x => x === this.props.sample + )[0]; + return ( + this._mutationalSignatureCountDataGroupedByVersionForSample || + this.props.dataCount[this.props.version] + .map(item => item) + .filter(subItem => subItem.sampleId === sampleIdToFilter) + ); + } + + @action.bound changeSignature(name: string): void { + this._selectedSignature = name; + } + @action.bound private onVersionChange(option: { label: string; value: string }): void { this.props.onVersionChange(option.value); + this.state.signatureProfile = this.props.data[ + option.value + ][0].meta.name; + this.state.visible = false; + } + + @action.bound + private onSampleChange(sample: { label: string; value: string }): void { + this.props.onSampleChange(sample.value); } public render() { @@ -66,36 +157,83 @@ export default class MutationalSignaturesContainer extends React.Component< style={{ display: 'inline-block', marginLeft: 5, - width: 400, + width: 800, }} > - + + + + {this.props.samples.length > 1 && ( + + + + )} + + this.callbackFunction('', false)} + > {this.props.data && ( - - + {!_.isEmpty(this.props.dataCount) && ( + + )} + + + )} diff --git a/src/pages/patientView/mutationalSignatures/SignatureTextBox.tsx b/src/pages/patientView/mutationalSignatures/SignatureTextBox.tsx new file mode 100644 index 00000000000..eeb664c19bb --- /dev/null +++ b/src/pages/patientView/mutationalSignatures/SignatureTextBox.tsx @@ -0,0 +1,55 @@ +import React, { CSSProperties } from 'react'; +import { action, computed, makeObservable, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { boolean, number, string } from 'yargs'; +import { IMutationalSignature } from 'shared/model/MutationalSignature'; +import { adjustVisibility } from 'shared/components/alterationsTableUtils'; +import { Modal } from 'react-bootstrap'; +import { ISelectedTrialFeedbackFormData } from 'pages/patientView/trialMatch/TrialMatchTable'; + +export interface ISignatureTextBoXProps { + height: number; + width: number; + url: string; + description: string; + signature: string; + show: boolean; + onHide: () => void; + parentCallback: (childData: string, visibility: boolean) => void; +} + +export default class SignatureTextBox extends React.Component< + ISignatureTextBoXProps, + {} +> { + @observable selectedSignature: string = ''; + + sendData = () => { + this.props.parentCallback(this.props.signature, false); + }; + + public render() { + return ( + + + {'COSMIC signature'} + + + + Signature: + {this.props.signature} + + + Description: + {this.props.description} + + + + Go to signature on Cosmic website + + + + + ); + } +} diff --git a/src/shared/lib/GenericAssayUtils/MutationalSignaturesUtils.tsx b/src/shared/lib/GenericAssayUtils/MutationalSignaturesUtils.tsx index cb072baff82..59b4e26d1a1 100644 --- a/src/shared/lib/GenericAssayUtils/MutationalSignaturesUtils.tsx +++ b/src/shared/lib/GenericAssayUtils/MutationalSignaturesUtils.tsx @@ -16,6 +16,7 @@ export enum MutationalSignaturesVersion { export enum MutationalSignatureStableIdKeyWord { MutationalSignatureContributionKeyWord = 'contribution', MutationalSignatureConfidenceKeyWord = 'pvalue', + MutationalSignatureCountKeyWord = 'matrix', } export const MUTATIONAL_SIGNATURES_SIGNIFICANT_PVALUE_THRESHOLD = 0.05; @@ -71,6 +72,19 @@ export function getVersionOptions(versions: string[]) { }); } +export function getSampleOption(sample: string) { + return { + label: 'Sample ' + sample, + value: sample, + }; +} + +export function getSampleOptions(samples: string[]) { + return samples.map(sample => { + return getSampleOption(sample); + }); +} + export type ISampleProgressBarProps = { contribution: string; color: string; diff --git a/src/shared/model/MutationalSignature.ts b/src/shared/model/MutationalSignature.ts index bea0cb3a5cf..b06d29e4229 100644 --- a/src/shared/model/MutationalSignature.ts +++ b/src/shared/model/MutationalSignature.ts @@ -20,3 +20,15 @@ export interface IMutationalSignatureMeta { category: string; confidenceStatement: string; } + +export interface IMutationalCounts { + uniqueSampleKey: string; + patientId: string; + uniquePatientKey: string; + studyId: string; + sampleId: string; + mutationalSignatureLabel: string; + mutationalSignatureClass: string; + version: string; + count: number; +}
+ Description: + {this.props.description} +
+ + Go to signature on Cosmic website + +