import autobind from "autobind-decorator" ; import cytoscape from 'cytoscape' ; import $ from "jquery" ; import { observer } from "mobx-react" ; import React from 'react' ; import pathways from "../data/pathways.json" ; import compartmentImg from "../images/nodes/compartment.svg" ; import complexImg from "../images/nodes/complex.svg" ; import familyImg from "../images/nodes/family.svg" ; import geneImg from "../images/nodes/gene.svg" ; import processImg from "../images/nodes/process.svg" ; import resizeCue from '../images/resizeCue.svg' ; import ContextMenuManager from "../managers/ContextMenuManager" ; import EditorActionsManager from "../managers/EditorActionsManager" ; import QtipManager from "../managers/QtipManager" ; import ShareDBManager from "../managers/ShareDBManager" ; import ViewOperationsManager from "../managers/ViewOperationsManager" ; import CBioPortalAccessor from "../utils/CBioPortalAccessor" ; import DragDropNodeAddPlugin from "../utils/DragDropNodeAddPlugin" ; import SaveLoadUtility from "../utils/SaveLoadUtility" ; import { EModalType , IColorValueMap , IProfileMetaData } from './react-pathway-mapper' ; window . $ = $ ; const edgeHandles = require ( 'cytoscape-edgehandles' ) ; const edgeEditing = require ( 'cytoscape-edge-editing' ) ; const fcose = require ( 'cytoscape-fcose' ) ; const nodeEditing = require ( 'cytoscape-node-editing' ) ; const undoRedo = require ( 'cytoscape-undo-redo' ) ; const panzoom = require ( 'cytoscape-panzoom' ) ; const styleSheet = require ( '../utils/GraphStyleSheet.tsx' ) ; const panzoomOpts = require ( '../utils/PanzoomOptions.tsx' ) ; const navigator = require ( 'cytoscape-navigator' ) ; const contextMenus = require ( 'cytoscape-context-menus' ) ; const konva = require ( 'konva' ) ; const viewUtilities = require ( 'cytoscape-view-utilities' ) ; const gridGuide = require ( 'cytoscape-grid-guide' ) ; const popper = require ( 'cytoscape-popper' ) ; const layoutUtilities = require ( 'cytoscape-layout-utilities' ) ; type PathwayMapperType = { isCollaborative : boolean ; isCbioPortal : boolean ; editorHandler : Function ; selectedPathway : string ; setActiveEdge : Function ; profiles : IProfileMetaData [ ] ; pathwayHandler : ( pathwayName : string ) => void ; handleOpen : ( modalId : EModalType ) => void ; onPathwayChangeCompleted : ( ) => void ; genomicDataOverlayColorScheme : IColorValueMap ; colorSchemeChangeCallback : ( IColorValueMap ) => void ; } ; @ observer export default class CytoscapeArea extends React . Component < PathwayMapperType , { } > { private cy : any ; private cyDiv : HTMLDivElement | undefined ; private editor : EditorActionsManager ; private edgeAddingMode : any ; private viewOperationsManager : ViewOperationsManager ; private qtipManager : QtipManager ; private genomicDataExplorerView : any ; private pathwayDetailsView : any ; private viewUtilities : any ; private isCollaborative : boolean ; private isCbioPortal : boolean ; private shareDBManager : ShareDBManager ; private cxtMenuManager : ContextMenuManager | undefined ; private dragDropNodeAddManager : DragDropNodeAddPlugin ; private undoRedoManager : any ; private portalAccessor : CBioPortalAccessor ; private isMountedFirst = true ; private eh : any ; private lastSelectedEdge : any ; constructor ( props ) { super ( props ) ; this . isCollaborative = props . isCollaborative ; this . edgeAddingMode = 0 ; this . isCbioPortal = props . isCbioPortal ; } componentWillUpdate ( nextProps ) { if ( this . props . selectedPathway !== nextProps . selectedPathway ) { this . getPathway ( nextProps . selectedPathway ) ; } } componentDidUpdate ( prevProps ) { if ( this . isCbioPortal && prevProps . selectedPathway !== this . props . selectedPathway ) { this . props . onPathwayChangeCompleted ( ) ; } } getPathway ( selectedPathway ) { if ( ! selectedPathway || selectedPathway === '' ) return ; const data = pathways [ selectedPathway ] ; if ( ! data ) { return ; } const parsedGraph = SaveLoadUtility . parseGraph ( data , true ) ; const allEles = parsedGraph ; this . editor . loadFile ( allEles . nodes , allEles . edges ) ; } render ( ) { return ( < div className = { this . props . isCbioPortal ? "" : "cyContainer" } style = { { "border" : `3px solid ${ this . isCbioPortal ? "#4389c0" : "#1abc9c" }` } } > < div ref = { this . cyDivHandler } id = "cy" style = { { "height" : this . isCbioPortal ? "800px" : "100%" , "borderRadius" : "6px" , marginTop : "0px" } } /> < div className = "cytoscape-navigator-wrapper" > < /div> < /div>); } componentDidMount ( ) { if ( this . isMountedFirst ) { this . init ( ) ; this . isMountedFirst = false ; } this . getPathway ( this . props . selectedPathway ) ; } @ autobind cyDivHandler ( div ) { this . cyDiv = div ; } init ( ) { this . portalAccessor = new CBioPortalAccessor ( ) ; this . initCyJS ( ) ; this . initCyHandlers ( ) ; this . initKeyboardHandlers ( ) ; this . initUndoRedoFunctionality ( ) ; this . initCBioPortalFunctionalities ( ) ; this . placePanzoomAndOverlay ( ) ; } placePanzoomAndOverlay ( ) { var offset = 5 ; var topCy = $ ( this . cyDiv ) . offset ( ) . top ; var leftCy = $ ( this . cyDiv ) . offset ( ) . left ; var heightCy = $ ( this . cyDiv ) . outerHeight ( ) ; var widthCy = $ ( this . cyDiv ) . outerWidth ( ) ; var heightNavigator = $ ( '.cytoscape-navigator-wrapper' ) . outerHeight ( ) ; var widthNavigator = $ ( '.cytoscape-navigator-wrapper' ) . outerWidth ( ) ; if ( ! this . isCbioPortal ) { $ ( '.cytoscape-navigator-wrapper' ) . css ( 'top' , heightCy + topCy - heightNavigator - offset + 16 ) ; $ ( '.cytoscape-navigator-wrapper' ) . css ( 'left' , widthCy + leftCy - widthNavigator - offset + 24 - 0.5 + 0.35 ) ; } else { $ ( '.cytoscape-navigator-wrapper' ) . css ( 'bottom' , 10.5 ) ; $ ( '.cytoscape-navigator-wrapper' ) . css ( 'right' , 0 ) ; } $ ( '.cytoscape-navigator-wrapper' ) . css ( 'z-index' , 1039 ) ; $ ( '.cy-panzoom' ) . css ( 'position' , 'relative' ) ; $ ( '.cy-panzoom' ) . css ( 'top' , 2 ) ; $ ( '.cy-panzoom' ) . css ( 'left' , widthCy - 51 ) ; $ ( '.cy-panzoom' ) . css ( 'z-index' , 1039 ) ; $ ( '.cy-panzoom' ) . css ( 'width' , 200 ) ; } getPathwayData ( ) { return this . pathwayDetailsView . getPathwayData ( ) ; } initCyJS ( ) { try { panzoom ( cytoscape , $ ) ; } catch ( err ) { console . log ( err ) ; } try { navigator ( cytoscape ) ; } catch ( err ) { console . log ( err ) ; } try { gridGuide ( cytoscape , $ ) ; } catch ( err ) { console . log ( err ) ; } try { undoRedo ( cytoscape ) ; } catch ( err ) { console . log ( err ) ; } try { contextMenus ( cytoscape , $ ) ; } catch ( err ) { console . log ( err ) ; } try { nodeEditing ( cytoscape , $ , konva ) ; } catch ( err ) { console . log ( err ) ; } try { edgeEditing ( cytoscape , $ , konva ) ; } catch ( err ) { console . log ( err ) ; } try { viewUtilities ( cytoscape , $ ) ; } catch ( err ) { console . log ( err ) ; } try { edgeHandles ( cytoscape , $ ) ; } catch ( err ) { console . log ( err ) ; } try { popper ( cytoscape ) ; } catch ( err ) { console . log ( err ) ; } try { fcose ( cytoscape ) ; } catch ( err ) { console . log ( err ) ; } try { layoutUtilities ( cytoscape ) ; } catch ( err ) { console . log ( err ) ; } this . cy = cytoscape ( { container : this . cyDiv , boxSelectionEnabled : true , autounselectify : false , wheelSensitivity : 0.1 , style : styleSheet , textureOnViewport : false , motionBlur : true , layout : { name : 'preset' } } ) ; this . edgeAddingMode = 0 ; this . undoRedoManager = this . cy . undoRedo ( ) ; this . shareDBManager = new ShareDBManager ( ( ) => { const dbDoc = this . shareDBManager . getDoc ( ) ; } ) ; this . editor = new EditorActionsManager ( this . isCollaborative , this . shareDBManager , this . cy , this . isCbioPortal , this . undoRedoManager , this . portalAccessor , this . props . profiles , this . props . genomicDataOverlayColorScheme , this . props . colorSchemeChangeCallback ) ; this . shareDBManager . setEditor ( this . editor ) ; if ( this . isCollaborative ) { this . shareDBManager . initShareDB ( ) ; } this . qtipManager = new QtipManager ( this . cy , this . editor ) ; this . cxtMenuManager = ! this . isCbioPortal ? new ContextMenuManager ( this . cy , this . editor , this . props . handleOpen , this . undoRedoManager , this . props . isCollaborative ) : undefined ; this . dragDropNodeAddManager = new DragDropNodeAddPlugin ( this . editor , this . cy , this . props . pathwayHandler ) ; this . cy . panzoom ( panzoomOpts ) ; this . cy . layoutUtilities ( { desiredAspectRatio : this . cy . width ( ) / this . cy . height ( ) } ) ; this . cy . nodeadd ( { components : [ { container : $ ( '#simpleNodeDiv' ) , nodeType : 'Gene' , icon : geneImg } , { container : $ ( '#familyNodeDiv' ) , nodeType : 'Family' , icon : familyImg } , { container : $ ( '#complexNodeDiv' ) , nodeType : 'Complex' , icon : complexImg } , { container : $ ( '#compartmentNodeDiv' ) , nodeType : 'Compartment' , icon : compartmentImg } , { container : $ ( '#processNodeDiv' ) , nodeType : 'Process' , icon : processImg } ] } ) ; const self = this ; const edgeHandleDefaults = { preview : true , stackOrder : 4 , handleSize : 10 , handleColor : '#1abc9c' , handleLineType : 'ghost' , handleLineWidth : 1 , handleNodes : 'node' , hoverDelay : 1 , cxt : false , enabled : false , toggleOffOnLeave : true , edgeType : function ( sourceNode , targetNode ) { return 'flat' ; } , handlePosition : function ( node ) { return 'middle top' ; } , loopAllowed : function ( node ) { return false ; } , nodeLoopOffset : - 50 , nodeParams : function ( sourceNode , targetNode ) { return { } ; } , edgeParams : function ( sourceNode , targetNode , i ) { return { } ; } , start : function ( sourceNode ) { var type = self . getGlobalEdgeType ( ) ; } , complete : function ( sourceNode , targetNodes , addedEntities ) { self . cy . remove ( addedEntities ) ; self . editor . addEdge ( { source : sourceNode . id ( ) , target : targetNodes [ 0 ] . id ( ) , type : self . getGlobalEdgeType ( window . edgeAddingMode ) , pubmedIDs : [ ] , name : "" } ) ; } , stop : function ( sourceNode ) { self . edgeAddingMode = - 1 ; self . eh . disable ( ) ; self . eh . hide ( ) ; self . props . setActiveEdge ( - 1 ) ; } , } ; this . eh = this . cy . edgehandles ( edgeHandleDefaults ) ; this . eh . disable ( ) ; this . props . editorHandler ( this . editor , this . eh , this . undoRedoManager ) ; if ( ! this . isCbioPortal ) { this . cy . nodeEditing ( { padding : 5 , undoable : true , grappleSize : 8 , grappleColor : "#ffc90e" , inactiveGrappleStroke : "inside 1px blue" , boundingRectangle : true , boundingRectangleLineDash : [ 4 , 8 ] , boundingRectangleLineColor : "ffc90e" , boundingRectangleLineWidth : 1.5 , zIndex : 999 , moveSelectedNodesOnKeyEvents : function ( ) { return true ; } , minWidth : function ( node ) { var data = node . data ( "resizeMinWidth" ) ; return data ? data : 15 ; } , minHeight : function ( node ) { var data = node . data ( "resizeMinHeight" ) ; return data ? data : 15 ; } , getCompoundMinWidth : function ( node ) { return node . style ( 'min-width' ) ; } , getCompoundMinHeight : function ( node ) { return node . style ( 'min-height' ) ; } , getCompoundMinWidthBiasRight : function ( node ) { return node . style ( 'min-width-bias-right' ) ; } , getCompoundMinWidthBiasLeft : function ( node ) { return node . style ( 'min-width-bias-left' ) ; } , getCompoundMinHeightBiasTop : function ( node ) { return node . style ( 'min-height-bias-top' ) ; } , getCompoundMinHeightBiasBottom : function ( node ) { return node . style ( 'min-height-bias-bottom' ) ; } , isFixedAspectRatioResizeMode : function ( node ) { return node . is ( ".fixedAspectRatioResizeMode" ) ; } , isNoResizeMode : function ( node ) { return undefined ; } , setWidth : function ( node , width ) { node . data ( 'w' , width ) ; } , setHeight : function ( node , height ) { node . data ( 'h' , height ) ; } , setCompoundMinWidth : function ( node , minWidth ) { node . style ( 'min-width' , minWidth ) ; } , setCompoundMinHeight : function ( node , minHeight ) { node . style ( 'min-height' , minHeight ) ; } , setCompoundMinWidthBiasLeft : function ( node , minWidthBiasLeft ) { node . style ( 'min-width-bias-left' , minWidthBiasLeft ) ; } , setCompoundMinWidthBiasRight : function ( node , minHeightBiasRight ) { node . style ( 'min-width-bias-right' , minHeightBiasRight ) ; } , setCompoundMinHeightBiasTop : function ( node , minHeightBiasTop ) { node . style ( 'min-height-bias-top' , minHeightBiasTop ) ; } , setCompoundMinHeightBiasBottom : function ( node , minHeightBiasBottom ) { node . style ( 'min-height-bias-bottom' , minHeightBiasBottom ) ; } , cursors : { default : "default" , inactive : "not-allowed" , nw : "nw-resize" , n : "n-resize" , ne : "ne-resize" , e : "e-resize" , se : "se-resize" , s : "s-resize" , sw : "sw-resize" , w : "w-resize" } , resizeToContentCueImage : resizeCue , resizeToContentFunction : this . editor . resizeNodesToContent . bind ( this . editor ) , } ) ; } var navDefaults = { container : '.cytoscape-navigator-wrapper' , viewLiveFramerate : 0 , thumbnailEventFramerate : 10 , thumbnailLiveFramerate : false , dblClickDelay : 200 , removeCustomContainer : true , rerenderDelay : 100 } ; this . cy . navigator ( navDefaults ) ; const viewUtilitiesOpts = { node : { highlighted : { 'border-width' : 2 , 'border-color' : '#bc1142' } , unhighlighted : { 'opacity' : function ( ele ) { return ele . css ( 'opacity' ) ; } } } , edge : { highlighted : { } , unhighlighted : { 'opacity' : function ( ele ) { return ele . css ( 'opacity' ) ; } } } , setVisibilityOnHide : false , setDisplayOnHide : true , neighbor : ( ) => { return false ; } , neighborSelectTime : 500 } ; this . viewUtilities = this . cy . viewUtilities ( viewUtilitiesOpts ) ; this . editor . setViewUtilities ( this . viewUtilities ) ; this . placePanzoomAndOverlay ( ) ; } getGlobalEdgeType ( ) { var type = "NONE" ; if ( window . edgeAddingMode === 1 ) { type = 'ACTIVATES' ; } else if ( window . edgeAddingMode === 2 ) { type = 'INHIBITS' ; } else if ( window . edgeAddingMode === 3 ) { type = 'INDUCES' ; } else if ( window . edgeAddingMode === 4 ) { type = 'REPRESSES' ; } else if ( window . edgeAddingMode === 5 ) { type = 'BINDS' ; } return type ; } initCyHandlers ( ) { var that = this ; var tappedBefore ; var tappedTimeout ; this . cy . on ( 'tap' , function ( event ) { var tappedNow = event . target ; if ( tappedTimeout !== - 1 && tappedBefore !== - 1 ) { clearTimeout ( tappedTimeout ) ; } if ( tappedBefore === tappedNow ) { tappedNow . trigger ( 'doubleTap' ) ; tappedBefore = - 1 ; } else { tappedTimeout = setTimeout ( function ( ) { tappedBefore = - 1 ; } , 300 ) ; tappedBefore = tappedNow ; } } ) ; this . cy . on ( 'doubleTap' , 'node' , function ( e ) { if ( that . props . isCbioPortal ) return ; const eventIsDirect = ( e . target === this ) ; if ( eventIsDirect ) { that . qtipManager . addQtipToElements ( e . target ) ; e . target . trigger ( "showqtipevent" ) ; } } ) ; this . cy . on ( 'doubleTap' , 'edge' , function ( e ) { if ( that . props . isCbioPortal ) return ; const eventIsDirect = ( e . target === this ) ; if ( eventIsDirect ) { that . qtipManager . addQtipToElements ( e . target ) ; e . target . trigger ( "showqtipevent" ) ; } } ) ; this . cy . on ( 'select' , 'node' , ( e ) => { this . editor . pushSelectedNodeStack ( e . target ) ; } ) ; this . cy . on ( 'unselect' , 'node' , ( e ) => { this . editor . removeElementFromSelectedNodeStack ( e . target ) ; } ) ; this . cy . on ( 'free' , 'node' , ( e ) => { var selectedNodes = this . cy . nodes ( ':selected' ) ; var nodes = e . target ; nodes = nodes . union ( nodes . descendants ( ) ) ; nodes = nodes . union ( selectedNodes ) ; this . editor . moveElements ( nodes ) ; } ) ; this . cy . on ( 'layoutstop' , ( ) => { this . editor . postLayout ( ) ; } ) ; this . cy . on ( "nodeediting.resizeend" , ( _e , _type , node ) => { node . data ( 'w' , node . width ( ) ) ; node . data ( 'h' , node . height ( ) ) ; this . editor . resizeElements ( node ) ; } ) ; this . cy . on ( 'select' , 'edge' , ( e ) => { this . lastSelectedEdge = e . target ; } ) ; this . cy . on ( 'bendPointMovement' , ( ) => { this . editor . updateEdgeAnchorPoints ( this . lastSelectedEdge ) ; } ) ; this . cy . on ( 'nodeediting.moveend' , ( ) => { this . editor . changeNodePositionsByArrows ( this . cy . nodes ( ":selected" ) ) ; } ) ; } initKeyboardHandlers ( ) { if ( ! this . isCollaborative && ! this . isCbioPortal ) { $ ( document ) . keydown ( ( e ) => { if ( e . which === 89 && ( e . ctrlKey || e . metaKey ) ) { this . undoRedoManager . redo ( ) ; } else if ( e . which === 90 && ( e . ctrlKey || e . metaKey ) ) { this . undoRedoManager . undo ( ) ; } } ) ; } else { $ ( 'a[role="redo"]' ) . hide ( ) ; $ ( 'a[role="undo"]' ) . hide ( ) ; } $ ( document ) . keydown ( ( e ) => { if ( e . which === 65 && ( e . ctrlKey || e . metaKey ) ) { var tn = document . activeElement . tagName ; if ( tn != "TEXTAREA" && tn != "INPUT" ) { e . preventDefault ( ) ; this . cy . elements ( ) . select ( ) ; } } else if ( e . which === 8 || e . which === 46 ) { var tn = document . activeElement . tagName ; if ( tn != "TEXTAREA" && tn != "INPUT" ) { var selectedElements = this . cy . $ ( ':selected' ) ; this . editor . removeElement ( selectedElements ) ; } } } ) ; } initUndoRedoFunctionality ( ) { if ( this . isCollaborative || this . isCbioPortal ) { $ ( '[role="undo"]' ) . hide ( ) ; $ ( '[role="redo"]' ) . hide ( ) ; } } initCBioPortalFunctionalities ( ) { if ( this . isCbioPortal ) { const contextMenu = this . cy . contextMenus ( 'get' ) ; contextMenu . destroy ( ) ; } } }