import autobind from "autobind-decorator" ; import { action , computed , makeObservable , observable } from "mobx" ; import { observer } from "mobx-react" ; import { IGeneticAlterationRuleSetParams } from 'oncoprintjs' ; import React from 'react' ; import { Col , Row } from "react-bootstrap" ; import { toast , ToastContainer } from 'react-toastify' ; import 'react-toastify/dist/ReactToastify.min.css' ; import ReactTooltip from 'react-tooltip' ; import "../css/pmv1.css" ; import "../css/pmv2.css" ; import '../css/qtip.css' ; import "../css/supp.css" ; import pathways from "../data/pathways.json" ; import EditorActionsManager from "../managers/EditorActionsManager" ; import FileOperationsManager from '../managers/FileOperationsManager' ; import GridOptionsManager from '../managers/GridOptionsManager' ; import ViewOperationsManager from '../managers/ViewOperationsManager' ; import AboutModal from '../modals/AboutModal' ; import CBioHelpModal from '../modals/CBioHelpModal' ; import ConfirmationModal from '../modals/ConfirmationModal' ; import GridSettings from '../modals/GridSettings' ; import LayoutProperties from '../modals/LayoutProperties' ; import PathwayDetailsModal from '../modals/PathwayDetailsModal' ; import ProfilesColorSchemeModal from "../modals/ProfilesColorSchemeModal" ; import ProfilesModal from '../modals/ProfilesModal' ; import QuickHelpModal from '../modals/QuickHelpModal' ; import StudyModal from '../modals/StudyModal' ; import Ranking from "../ui/Ranking" ; import Toolbar from "../ui/Toolbar" ; import CBioPortalAccessor from '../utils/CBioPortalAccessor' ; import PathwayActions from '../utils/PathwayActions' ; import SaveLoadUtility from '../utils/SaveLoadUtility' ; import Buttonbar from "./Buttonbar" ; import CytoscapeArea from "./CytoscapeArea" ; import Menubar from './Menubar' ; import Sidebar from './Sidebar' ; const maxHeapFn = require ( '@datastructures-js/max-heap' ) ; let maxHeap = maxHeapFn ( ) ; interface IPathwayMapperProps { isCBioPortal : boolean ; genes : any [ ] ; isCollaborative ? : boolean ; cBioAlterationData ? : ICBioData [ ] ; sampleIconData ? : ISampleIconData ; pathwayName ? : string ; alterationData ? : IAlterationData ; onAddGenes ? : ( selectedGenes : string [ ] ) => void ; changePathwayHandler ? : ( pathwayGenes : string [ ] ) => void ; addGenomicDataHandler ? : ( addGenomicData : ( alterationData : ICBioData [ ] ) => void ) => void ; tableComponent ? : ( data : IPathwayMapperTable [ ] , selectedPathway : string , onPathwaySelect : ( pathway : string ) => void ) => JSX . Element ; validGenes ? : any ; toast : any ; showMessage : ( message : string ) => void ; patientView ? : boolean ; messageBanner ? : ( ) => JSX . Element ; } export interface ICBioData { altered : number ; gene : string ; percentAltered : string ; sequenced : number ; geneticTrackData ? : any [ ] ; geneticTrackRuleSetParams ? : IGeneticAlterationRuleSetParams ; } export interface ISampleIconData { sampleIndex : { [ s ] : number ; } ; sampleColors : { [ s ] : string ; } ; } /**
 * Maps integer values to color code strings
 */ export interface IColorValueMap { [ value ] : string ; } export enum EModalType { STUDY , CONFIRMATION , PROFILES , ABOUT , PW_DETAILS , GRID , HELP , LAYOUT , CHELP , PROFILES_COLOR_SCHEME } export interface IPathwayData { title : string ; description : string ; nodes : any [ ] ; edges : any [ ] ; } export interface IAlterationData { [ key ] : { [ key ] : number ; } ; } export interface IProfileMetaData { profileId : string ; studyId ? : string ; enabled : boolean ; } export interface IDataTypeMetaData { enabled : boolean ; checked : boolean ; profile : string ; } export interface IPathwayMapperTable { name : string ; score : number ; genes : string [ ] ; } @ observer export class PathwayMapper extends React . Component < IPathwayMapperProps , { } > { static readonly CBIO_PROFILE_NAME = "cBioPortal_data" ; readonly MAX_ALLOWED_PROFILES_ENABLED = 6 ; @ observable selectedPathway : string ; fileManager : FileOperationsManager ; @ observable editor : EditorActionsManager ; pathwayActions : PathwayActions ; @ observable isModalShown : boolean [ ] ; @ observable colorValueMap : IColorValueMap ; portalAccessor : CBioPortalAccessor ; @ observable alterationData : IAlterationData ; @ observable patientData : any [ ] [ ] = [ ] ; @ observable pathwayGeneMap : { [ key ] : { [ key ] : string ; } ; } = { } ; bestPathwaysAlgos : any [ ] [ ] = [ ] ; @ observable oldName = "" ; @ observable profiles : IProfileMetaData [ ] = [ ] ; setActiveEdge : ( edgeId : number ) => void ; viewOperationsManager : ViewOperationsManager ; gridOptionsManager : GridOptionsManager ; constructor ( props ) { super ( props ) ; makeObservable ( this ) ; this . fileManager = new FileOperationsManager ( ) ; this . pathwayActions = new PathwayActions ( this . pathwayHandler , this . profiles , this . fileManager , this . handleOpen , this . props . isCBioPortal , this . props . isCollaborative ) ; this . selectedPathway = "" ; if ( this . props . pathwayName ) { this . pathwayActions . changePathway ( this . props . pathwayName ) ; } this . isModalShown = [ false , false , false , false , false , false , false , false , false , false ] ; this . alterationData = { } ; this . extractAllGenes ( ) ; this . colorValueMap = { '-100' : "#0000ff" , '0' : "#ffffff" , '100' : "#ff0000" } ; if ( this . props . isCBioPortal ) { if ( this . props . cBioAlterationData ) { if ( this . props . patientView ) { this . calculatePatientData ( this . props . cBioAlterationData ) ; this . addSampleIconData ( this . props . sampleIconData ) ; } else { this . calculateAlterationData ( this . props . cBioAlterationData ) ; } } if ( this . props . addGenomicDataHandler ) { this . props . addGenomicDataHandler ( this . addGenomicData ) ; } this . profiles . push ( { profileId : PathwayMapper . CBIO_PROFILE_NAME , enabled : true } ) ; this . getBestPathway ( 0 ) ; this . getBestPathway ( 1 ) ; this . getBestPathway ( 2 ) ; this . getBestPathway ( 3 ) ; } } @ action . bound setColorMapping ( colorValueMap ) { this . colorValueMap = colorValueMap ; this . editor . updateGenomicDataColorScheme ( this . colorValueMap ) ; } @ action setSelectedPathway ( pathway ) { this . selectedPathway = pathway ; } @ action setEditor ( editor ) { this . editor = editor ; } @ action addProfile ( profile ) { this . profiles . push ( profile ) ; } @ action toggleProfileEnabled ( index ) { this . profiles [ index ] . enabled = ! this . profiles [ index ] . enabled ; } calculateAlterationData ( cBioAlterationData ) { this . alterationData [ PathwayMapper . CBIO_PROFILE_NAME ] = { } ; cBioAlterationData . forEach ( ( geneAltData ) => { const perc = ( geneAltData . altered / geneAltData . sequenced ) * 100 ; this . alterationData [ PathwayMapper . CBIO_PROFILE_NAME ] [ geneAltData . gene ] = ( ( Object . is ( perc , NaN ) ? - 101 : perc ) ) ; } ) ; } calculatePatientData ( cBioAlterationData ) { this . alterationData [ PathwayMapper . CBIO_PROFILE_NAME ] = { } ; const allTypes = cBioAlterationData . map ( x => x . gene ) ; const uniqueTypes = allTypes . filter ( ( x , i , a ) => a . indexOf ( x ) == i ) ; this . patientData [ "PatientView" ] = 1 ; uniqueTypes . forEach ( x => { this . patientData [ x ] = { } ; } ) ; cBioAlterationData . forEach ( ( geneAltData ) => { const perc = ( geneAltData . altered / geneAltData . sequenced ) * 100 ; this . alterationData [ PathwayMapper . CBIO_PROFILE_NAME ] [ geneAltData . gene ] = ( ( Object . is ( perc , NaN ) ? - 101 : perc ) ) ; this . patientData [ geneAltData . gene ] [ geneAltData . percentAltered ] = ( ( Object . is ( perc , NaN ) ? - 101 : perc ) ) ; this . patientData [ geneAltData . gene ] [ "geneticTrackData" ] = geneAltData . geneticTrackData ; this . patientData [ geneAltData . gene ] [ "geneticTrackRuleSetParams" ] = geneAltData . geneticTrackRuleSetParams ; } ) ; } addSampleIconData ( sampleIconData ) { if ( sampleIconData ) { this . patientData [ "sampleColors" ] = sampleIconData . sampleColors ; this . patientData [ "sampleIndex" ] = sampleIconData . sampleIndex ; } } getGeneStudyMap ( studyGeneMap ) { const genomicDataMap = { } ; for ( const cancerStudy of Object . keys ( studyGeneMap ) ) { const cancerData = studyGeneMap [ cancerStudy ] ; for ( const geneSymbol of Object . keys ( cancerData ) ) { if ( genomicDataMap [ geneSymbol ] === undefined ) genomicDataMap [ geneSymbol ] = { } ; genomicDataMap [ geneSymbol ] [ cancerStudy ] = studyGeneMap [ cancerStudy ] [ geneSymbol ] . toFixed ( 2 ) ; } } return genomicDataMap ; } getAlterationAveragePerGene ( genomicDataMap ) { const geneAlterationMap = { } ; for ( const gene of Object . keys ( genomicDataMap ) ) { let sum = 0 , count = 0 ; for ( const alteration of Object . values ( genomicDataMap [ gene ] ) ) { sum += parseFloat ( alteration as string ) ; count ++ ; } if ( count === 0 ) { geneAlterationMap [ gene ] = 0 ; } else { geneAlterationMap [ gene ] = sum / count ; } } return geneAlterationMap ; } param rankingMode getBestPathway ( rankingMode ) { const genomicDataMap = this . getGeneStudyMap ( this . alterationData ) ; const alterationPerGene = this . getAlterationAveragePerGene ( genomicDataMap ) ; maxHeap = maxHeapFn ( ) ; const matchedGenesMap = { } ; const bestPathways = [ ] ; for ( const pathwayName in this . pathwayGeneMap ) { if ( this . pathwayGeneMap . hasOwnProperty ( pathwayName ) ) { const genesMatching = [ ] ; let sumOfAlterations = 0 ; for ( const gene of this . props . genes ) { if ( this . pathwayGeneMap [ pathwayName ] . hasOwnProperty ( gene . hugoGeneSymbol ) && this . pathwayGeneMap [ pathwayName ] [ gene . hugoGeneSymbol ] === "GENE" ) { genesMatching . push ( gene . hugoGeneSymbol ) ; sumOfAlterations += alterationPerGene [ gene . hugoGeneSymbol ] ; } } matchedGenesMap [ pathwayName ] = genesMatching ; let geneCount = 0 ; for ( const geneType of Object . values ( this . pathwayGeneMap [ pathwayName ] ) ) { if ( geneType === "GENE" ) { geneCount ++ ; } } if ( rankingMode === 0 ) { maxHeap . insert ( genesMatching . length , { pathwayName : pathwayName } ) ; } else if ( rankingMode === 1 ) { maxHeap . insert ( genesMatching . length / geneCount * 100 , { pathwayName : pathwayName } ) ; } else if ( rankingMode === 2 ) { maxHeap . insert ( sumOfAlterations , { pathwayName : pathwayName } ) ; } else if ( rankingMode === 3 ) { maxHeap . insert ( genesMatching . length * sumOfAlterations / geneCount , { pathwayName : pathwayName } ) ; } } } while ( maxHeap . size ( ) > 0 ) { const top = maxHeap . extractMax ( ) ; const pathwayName = top . getValue ( ) . pathwayName ; bestPathways . push ( { score : top . getKey ( ) , genesMatched : matchedGenesMap [ pathwayName ] , pathwayName : pathwayName } ) ; } if ( this . bestPathwaysAlgos . length === 0 ) this . setSelectedPathway ( bestPathways [ 0 ] . pathwayName ) ; this . bestPathwaysAlgos . push ( bestPathways ) ; } @ autobind includePathway ( pathwayData ? , pathwayName ? ) { const genes = pathwayData . nodes ; const geneHash = { } ; for ( const gene of genes ) { if ( gene . data . type === "GENE" ) geneHash [ gene . data . name ] = gene . data . type ; } this . pathwayGeneMap [ pathwayData . title ] = geneHash ; } extractAllGenes ( ) { for ( const pathwayName in pathways ) { if ( pathways . hasOwnProperty ( pathwayName ) ) { const pathwayData = SaveLoadUtility . parseGraph ( pathways [ pathwayName ] , true ) ; this . includePathway ( pathwayData ) ; } } } loadRedirectedPortalData ( ) { if ( ! this . props . alterationData ) { return ; } const redirectedProfiles = Object . keys ( this . props . alterationData ) . map ( ( data ) : IProfileMetaData => ( { profileId : data , enabled : true } ) ) ; redirectedProfiles . forEach ( ( redirectedProfile ) => { this . addProfile ( redirectedProfile ) ; } ) ; this . editor . addPortalGenomicData ( this . props . alterationData , this . editor . getEmptyGroupID ( ) ) ; } exists ( profileId ) { let exists = false ; this . profiles . forEach ( ( profile ) => { if ( profile . profileId === profileId ) { exists = true ; } } ) ; return exists ; } @ autobind loadFromCBio ( dataTypes , studyData ) { if ( ! this . pathwayActions . doesCyHaveElements ( ) ) { toast . warn ( 'Your pathway is empty!' ) ; return ; } for ( const metadata of Object . values ( dataTypes ) ) { if ( ! metadata . checked ) { continue ; } if ( this . exists ( metadata . profile ) ) { toast . warn ( metadata . profile + " already exists." ) ; continue ; } const studyId = studyData [ 0 ] ; const profileId = metadata . profile ; const enableNewProfile = this . profiles . length < this . MAX_ALLOWED_PROFILES_ENABLED ; const newProfile = { studyId : studyId , profileId : profileId , enabled : enableNewProfile } ; this . addProfile ( newProfile ) ; const genes = this . editor . cy . nodes ( ) . filter ( node => node . data ( "type" ) === "GENE" ) . map ( node => node . data ( "name" ) ) ; this . portalAccessor . getProfileData ( { caseSetId : studyId , geneticProfileId : profileId , genes : genes } , ( data ) => { this . editor . addPortalGenomicData ( data , this . editor . getEmptyGroupID ( ) ) ; let visibilityObject = { } ; visibilityObject [ newProfile . profileId ] = newProfile . enabled ; this . editor . updateGenomicDataVisibility ( visibilityObject ) ; } ) ; } } @ autobind setActiveEdgeHandler ( setActiveEdge ) { this . setActiveEdge = setActiveEdge ; } @ autobind colorSchemeChangeCallback ( colorScheme ) { this . colorValueMap = colorScheme ; } @ autobind addGenomicData ( cBioAlterationData ) { this . calculateAlterationData ( cBioAlterationData ) ; this . editor . removeGenomicData ( ) ; this . editor . addPortalGenomicData ( this . alterationData , this . editor . getEmptyGroupID ( ) ) ; } @ autobind emphasizeQueryGenes ( ) { this . pathwayActions . emphasizeQueryGenes ( this . props . genes . map ( ( gene ) => gene . hugoGeneSymbol ) ) ; } render ( ) { const isCBioPortal = this . props . isCBioPortal ; const cytoComp = < CytoscapeArea profiles = { this . profiles } isCbioPortal = { this . props . isCBioPortal } isCollaborative = { this . props . isCollaborative } setActiveEdge = { this . setActiveEdge } editorHandler = { this . editorHandler } selectedPathway = { this . selectedPathway } pathwayHandler = { this . pathwayHandler } handleOpen = { this . handleOpen } onPathwayChangeCompleted = { this . emphasizeQueryGenes } genomicDataOverlayColorScheme = { this . colorValueMap } colorSchemeChangeCallback = { this . colorSchemeChangeCallback } / > ; return ( < div className = "pathwayMapper" > < div style = { isCBioPortal ? { width : window . innerWidth * 0.99 } : { } } className = { isCBioPortal ? "cBioMode container" : "customMargins" } > { ! isCBioPortal && < React . Fragment > < div > < Menubar pathwayActions = { this . pathwayActions } handleOpen = { this . handleOpen } setActiveEdge = { this . setActiveEdge } / > < /div>     < div > < Buttonbar pathwayActions = { this . pathwayActions } handleOpen = { this . handleOpen } / > < /div> < /React.Fragment>} { isCBioPortal && < Row style = { { marginBottom : "6px" } } > < Col xs = { 2 } style = { { paddingRight : '0px' , marginBottom : '5px' } } > < Toolbar pathwayActions = { this . pathwayActions } selectedPathway = { this . selectedPathway } alterationData = { this . alterationData } genes = { this . props . genes } handleOpen = { this . handleOpen } validGenes = { this . props . validGenes } showMessage = { this . props . showMessage } pathwayGenes = { Object . keys ( this . pathwayGeneMap [ this . selectedPathway ] ) } onAddGenes = { this . props . onAddGenes } patientView = { this . props . patientView } / > < /Col> { this . props . messageBanner ? < Col xs = { 4 } style = { { maxHeight : '32px' , paddingRight : '0px' } } > { this . props . messageBanner ( ) } < /Col> : < Col xs = { 4 } style = { { maxHeight : '32px' , paddingRight : '0px' } } > < /Col>} < Col xs = { 3 } style = { { paddingLeft : "0px" , marginTop : "12px" , textAlign : "right" , paddingRight : "25px" } } > { this . selectedPathway } < /Col> < /Row>} < div className = { isCBioPortal ? "row" : "mainContentWrapper" } > { ( ! isCBioPortal && < div > < Sidebar pathwayActions = { this . pathwayActions } setActiveEdgeHandler = { this . setActiveEdgeHandler } handleOpen = { this . handleOpen } / > < /div>)} { isCBioPortal ? ( < Col xs = { 9 } > { cytoComp } < div style = { { paddingRight : "9px" , textAlign : "right" , fontSize : "13px" } } > Powered by < a href = "https://github.com/iVis-at-Bilkent/pathway-mapper" target = "_blank" > PathwayMapper < /a></div > < /Col>) : ( cytoComp ) } { ( isCBioPortal && < Col xs = { 3 } style = { { paddingLeft : "0px" } } > < Ranking pathwayActions = { this . pathwayActions } bestPathwaysAlgos = { this . bestPathwaysAlgos } tableComponent = { this . props . tableComponent } patientView = { this . props . patientView } / > < /Col>)} < /div> { } { ( < div id = "invisibles" > { ( < div id = "pm-modals" > < ProfilesModal profiles = { this . profiles } editor = { this . editor } show = { this . isModalShown [ EModalType . PROFILES ] } handleClose = { this . handleClose } handleProfileLabelClicked = { this . toggleProfileEnabled } enabledProfileCountLimit = { this . MAX_ALLOWED_PROFILES_ENABLED } / > < PathwayDetailsModal show = { this . isModalShown [ EModalType . PW_DETAILS ] } handleClose = { this . handleClose } pathwayActions = { this . pathwayActions } / > < GridSettings show = { this . isModalShown [ EModalType . GRID ] } handleClose = { this . handleClose } pathwayActions = { this . pathwayActions } / > < QuickHelpModal show = { this . isModalShown [ EModalType . HELP ] } handleClose = { this . handleClose } / > < LayoutProperties show = { this . isModalShown [ EModalType . LAYOUT ] } handleClose = { this . handleClose } pathwayActions = { this . pathwayActions } / > < ConfirmationModal show = { this . isModalShown [ EModalType . CONFIRMATION ] } handleClose = { this . handleClose } / > < CBioHelpModal show = { this . isModalShown [ EModalType . CHELP ] } handleClose = { this . handleClose } patientView = { this . props . patientView } / > < AboutModal show = { this . isModalShown [ EModalType . ABOUT ] } handleClose = { this . handleClose } / > < ProfilesColorSchemeModal show = { this . isModalShown [ EModalType . PROFILES_COLOR_SCHEME ] } handleClose = { this . handleClose } colorValueMapping = { this . colorValueMap } handleColorMappingChange = { this . setColorMapping } / > < /div>)} { ! this . props . isCBioPortal && < React . Fragment > < StudyModal show = { this . isModalShown [ EModalType . STUDY ] } loadFromCBio = { this . loadFromCBio } handleClose = { this . handleClose } / > < ToastContainer position = "bottom-left" autoClose = { 5000 } hideProgressBar = { false } newestOnTop = { false } closeOnClick rtl = { false } pauseOnFocusLoss draggable pauseOnHover / > < /React.Fragment>} < ReactTooltip clickable = { true } className = { isCBioPortal ? "" : "pmTip" } style = { { maxWidth : "350px" , zIndex : 9999999 } } /> < input id = "myInput" type = "file" ref = { ( ref ) => { this . pathwayActions . setUploaders ( ref , false ) ; } } style = { { display : 'none' } } onChange = { ( e ) => { this . pathwayActions . onChangeFile ( e , false ) ; } } /> < input id = "myInput2" type = "file" ref = { ( ref ) => { this . pathwayActions . setUploaders ( ref , true ) ; } } style = { { display : 'none' } } onChange = { ( e ) => { this . pathwayActions . onChangeFile ( e , true ) ; } } /> < input id = "myInput3" type = "file" ref = { ( ref ) => { this . pathwayActions . setOverlayUploader ( ref ) ; } } style = { { display : 'none' } } onChange = { ( e ) => { this . pathwayActions . overlayFromText ( e . target . files [ 0 ] ) ; } } /> < /div>)} < /div> < /div>); } componentDidMount ( ) { if ( ! this . props . isCBioPortal ) { $ ( ".container" ) . css ( 'width' , "auto" ) ; $ ( ".container" ) . css ( 'paddingLeft' , 0 ) ; $ ( ".container" ) . css ( 'marginLeft' , 5 ) ; } else { this . pathwayActions . emphasizeQueryGenes ( this . props . genes . map ( ( gene ) => gene . hugoGeneSymbol ) ) ; } } componentDidUpdate ( ) { if ( this . props . isCBioPortal ) { this . pathwayActions . emphasizeQueryGenes ( this . props . genes . map ( ( gene ) => gene . hugoGeneSymbol ) ) ; } } @ action . bound handleOpen ( modalId ) { this . isModalShown [ modalId ] = true ; } @ action . bound handleClose ( modalId ) { this . isModalShown [ modalId ] = false ; } @ autobind editorHandler ( editor , eh , undoRedoManager ) { this . setEditor ( editor ) ; this . gridOptionsManager = new GridOptionsManager ( this . editor . cy ) ; this . viewOperationsManager = new ViewOperationsManager ( this . editor , this . editor . cy ) ; this . pathwayActions . editorHandler ( editor , eh , undoRedoManager , this . viewOperationsManager , this . gridOptionsManager ) ; if ( this . props . isCBioPortal ) { if ( this . props . patientView ) { this . editor . addPortalGenomicData ( this . patientData , this . editor . getEmptyGroupID ( ) ) ; } else { this . editor . addPortalGenomicData ( this . alterationData , this . editor . getEmptyGroupID ( ) ) ; } } else { this . portalAccessor = new CBioPortalAccessor ( ) ; this . loadRedirectedPortalData ( ) ; } } @ autobind pathwayHandler ( pathway ) { this . setSelectedPathway ( pathway ) ; if ( this . pathwayGeneMap [ pathway ] && this . props . changePathwayHandler ) this . props . changePathwayHandler ( Object . keys ( this . pathwayGeneMap [ pathway ] ) . filter ( gene => ( ! this . alterationData [ PathwayMapper . CBIO_PROFILE_NAME ] . hasOwnProperty ( gene ) ) ) ) ; } } export default PathwayMapper ;