001package org.biopax.paxtools.io.sbgn; 002 003import java.util.*; 004 005import org.ivis.layout.*; 006import org.ivis.layout.sbgn.SbgnPDNode; 007import org.ivis.layout.sbgn.SbgnProcessNode; 008import org.ivis.layout.sbgn.SbgnPDLayout; 009import org.sbgn.bindings.Arc; 010import org.sbgn.bindings.Port; 011import org.sbgn.bindings.Glyph; 012import org.sbgn.bindings.Sbgn; 013import org.sbgn.bindings.Bbox; 014 015/** 016 * SBGNLayoutManager Class: Applies layout by using ChiLay to Sbgn graph generated by paxtools. 017 * @author: Istemi Bahceci 018 * */ 019public class SBGNLayoutManager 020{ 021 // Layout and root objects 022 private Layout layout; 023 private VCompound root; 024 025 // mapping between view and layout level 026 private HashMap <VNode, LNode> viewToLayout; 027 private HashMap <String, VNode> layoutToView; 028 private HashMap <Glyph,VNode> glyphToVNode; 029 private HashMap <String, Glyph> idToGLyph; 030 private HashMap <String, Glyph> idToCompartmentGlyphs; 031 private HashMap <String, Glyph> portIDToOwnerGlyph; 032 private HashMap <String,Arc> idToArcs; 033 034 /** 035 * Applies CoSE layout to the given SBGN model. 036 * 037 * @param sbgn Sbgn object to which layout will be applied 038 * @return Laid out sbgn object 039 */ 040 public Sbgn createLayout(Sbgn sbgn) 041 { 042 viewToLayout = new HashMap(); 043 glyphToVNode = new HashMap(); 044 idToGLyph = new HashMap(); 045 idToCompartmentGlyphs = new HashMap(); 046 portIDToOwnerGlyph = new HashMap(); 047 layoutToView = new HashMap(); 048 idToArcs = new HashMap<String, Arc>(); 049 050 // Using Compound spring embedder layout 051 this.layout = new SbgnPDLayout(); 052 053 // This list holds the glyphs that will be deleted after corresponding glyph is added to child glyph of another glyph. 054 ArrayList <Glyph> deletedList = new ArrayList<Glyph>(); 055 056 LGraphManager graphMgr = this.layout.getGraphManager(); 057 LGraph lRoot = graphMgr.addRoot(); 058 this.root = new VCompound(new Glyph()); 059 060 // Detect compartment glyphs and put them in a hashmap, also set compartment glyphs of members of complexes. 061 for (Glyph g: sbgn.getMap().getGlyph()) 062 { 063 if(g.getClazz().equals("compartment")) 064 { 065 idToCompartmentGlyphs.put(g.getId(), g); 066 } 067 068 //Add compartment ref to the all children of this node. 069 if(g.getCompartmentRef() != null) 070 { 071 Glyph compartment = (Glyph)g.getCompartmentRef(); 072 setCompartmentRefForComplexMembers(g, compartment); 073 } 074 } 075 076 077 // Add glyphs to the compartment glyphs according to their "compartmentRef" field. 078 for (Glyph g: sbgn.getMap().getGlyph()) 079 { 080 if(g.getCompartmentRef() != null) 081 { 082 Glyph containerCompartment = (Glyph)g.getCompartmentRef(); 083 idToCompartmentGlyphs.get(containerCompartment.getId()).getGlyph().add(g); 084 deletedList.add(g); 085 } 086 } 087 088 // Delete the duplicate glyphs, after they are moved to corresponding compartment glyph. 089 for (Glyph g: deletedList) 090 { 091 sbgn.getMap().getGlyph().remove(g); 092 } 093 094 // initialize the map for keeping ports and their owner glyphs 095 // with entries like: <portID, ownerGlyph> 096 initPortIdToGlyphMap(sbgn.getMap().getGlyph()); 097 098 //Remove ports from source and target field of ports 099 //replace them with owner glyphs of these ports 100 removePortsFromArcs(sbgn.getMap().getArc()); 101 102 // Assign logical operator and Process nodes to compartment 103 assignProcessAndLogicOpNodesToCompartment(sbgn); 104 105 // Create Vnodes for ChiLay layout component 106 createVNodes(root, sbgn.getMap().getGlyph()); 107 108 for (VNode vNode: this.root.children) 109 { 110 this.createLNode(vNode, null, this.layout); 111 } 112 113 // Create LEdges for ChiLay layout component 114 createLEdges(sbgn.getMap().getArc(), this.layout); 115 graphMgr.updateBounds(); 116 117 // Apply layout 118 this.layout.runLayout(); 119 graphMgr.updateBounds(); 120 121 // Here if any SbgnProcessNode node is returned from SBGNPD Layout 122 // this means that we will have two additional port info. We should 123 // add this information to libSbgn objects 124 for(Object lNode: this.layout.getAllNodes()) 125 { 126 if( (LNode)lNode instanceof SbgnProcessNode) 127 { 128 //Set geometry of corresponding node 129 VNode vNode = layoutToView.get(((SbgnProcessNode) lNode).label); 130 Bbox tempBbox = vNode.glyph.getBbox(); 131 tempBbox.setX((float)(((SbgnProcessNode) lNode).getLeft())); 132 tempBbox.setY((float) (((SbgnProcessNode) lNode).getTop())); 133 vNode.glyph.setBbox(tempBbox); 134 135 //Created port objects in layout level 136 SbgnPDNode inputLPort = ((SbgnProcessNode) lNode).getInputPort(); 137 SbgnPDNode outputLPort = ((SbgnProcessNode) lNode).getOutputPort(); 138 139 // New port objects 140 Port inputPort = new Port(); 141 Port outputPort = new Port(); 142 143 // Set port attributes 144 inputPort.setX((float) (inputLPort.getCenterX())); 145 inputPort.setY((float)(inputLPort.getCenterY())); 146 inputPort.setId(inputLPort.label); 147 148 outputPort.setX((float)(outputLPort.getCenterX())); 149 outputPort.setY((float) (outputLPort.getCenterY())); 150 outputPort.setId((outputLPort.label)); 151 152 //Clear existing ports ! 153 vNode.glyph.getPort().clear(); 154 //Connect existing arcs to newly created ports 155 //These ports are created by ChiLay and SBGNPD Layout 156 connectArcToPort(inputLPort, inputPort); 157 connectArcToPort(outputLPort, outputPort); 158 159 //Add ports to the corresponding glyph 160 vNode.glyph.getPort().add(inputPort); 161 vNode.glyph.getPort().add(outputPort); 162 } 163 } 164 165 // Update the bounds 166 for (VNode vNode: this.root.children) 167 { 168 updateCompoundBounds(vNode.glyph, vNode.glyph.getGlyph()); 169 } 170 171 // Clear inside of the compartmentGlyphs 172 for (Glyph compGlyph: idToCompartmentGlyphs.values()) 173 { 174 //Again add the members of compartments 175 for(Glyph memberGlyph:compGlyph.getGlyph() ) 176 { 177 sbgn.getMap().getGlyph().add(memberGlyph); 178 } 179 compGlyph.getGlyph().clear(); 180 } 181 182 return sbgn; 183 } 184 185 /** 186 * This method connects the existing arcs to the newly created 187 * ports which are created by ChiLay and SBGNPD Layout. 188 * @param lPort l level port object. 189 * @param vPort v level port object. 190 * */ 191 public void connectArcToPort(SbgnPDNode lPort, Port vPort) 192 { 193 //Iterate over the edges of l level port 194 for (Object e: (lPort.getEdges())) 195 { 196 //Ignore rigid edges 197 if(((LEdge)e).type.equals("rigid edge")) 198 continue; 199 200 //Determine the if vPort is source or target 201 Arc arc = idToArcs.get(((LEdge)e).label); 202 if( lPort.label.equals(((LEdge)e).getSource().label )) 203 { 204 arc.setSource(vPort); 205 } 206 else if ( lPort.label.equals(((LEdge)e).getTarget().label ) ) 207 { 208 arc.setTarget(vPort); 209 } 210 } 211 } 212 213 /** 214 * This method finds process nodes and logical operator nodes in sbgn map and assigns them to a compartment by using majority rule. 215 * @param sbgn Given Sbgn map. 216 * */ 217 public void assignProcessAndLogicOpNodesToCompartment(Sbgn sbgn) 218 { 219 // Create a hashmap for keeping a node( generally logical operators and process nodes ) and its neighbours. 220 // TreeMap value of the hash map keeps track of compartment nodes that includes neighbours of the node by String id and 221 // Integer value holds the number of occurences of that compartment among the neighbours of the node as parent. 222 HashMap <String, HashMap<String,Integer>> nodetoNeighbours = new HashMap<String, HashMap<String,Integer>>(); 223 List<Glyph> glyphList = sbgn.getMap().getGlyph(); 224 List<Arc> arcList = sbgn.getMap().getArc(); 225 226 // Keeps track of process and logical operator nodes that will be assigned to a compartment. 227 ArrayList<Glyph> targetNodes = new ArrayList<Glyph>(); 228 229 //Iterate over glyphs of sbgn map 230 for(Glyph glyph: glyphList) 231 { 232 // Here logical operator nodes and process nodes are interested ! 233 if(glyph.getClazz().equals("process") ||glyph.getClazz().equals("omitted process")||glyph.getClazz().equals( "uncertain process") || glyph.getClazz().equals("phenotype")|| 234 glyph.getClazz().equals("association") || glyph.getClazz().equals("dissociation") ||glyph.getClazz().equals("and")||glyph.getClazz().equals("or")||glyph.getClazz().equals("not")) 235 { 236 // Add a new value to hash map and also store the node as target node 237 String processGlyphID = glyph.getId(); 238 String rootID = "root"; 239 nodetoNeighbours.put(processGlyphID, new HashMap<String, Integer>()); 240 targetNodes.add(glyph); 241 242 // Iterate over arc list 243 for(Arc arc: arcList) 244 { 245 Glyph target = null; 246 Glyph source = null; 247 248 // If source and target of node is port find its owner glyph ! else just assign it. 249 if(arc.getSource() instanceof Port) 250 source = portIDToOwnerGlyph.get(((Port)arc.getSource()).getId()); 251 else 252 source = (Glyph)arc.getSource(); 253 254 if(arc.getTarget() instanceof Port) 255 target = portIDToOwnerGlyph.get(((Port)arc.getTarget()).getId()); 256 else 257 target = (Glyph)arc.getTarget(); 258 259 // If source of any arc is our node, then target must be neighbour of this node ! 260 if(source.getId().equals(processGlyphID)) 261 { 262 populateCompartmentOccurencesMap(target, nodetoNeighbours.get(processGlyphID)); 263 } 264 // same as target part !! 265 else if(target.getId().equals(processGlyphID)) 266 { 267 populateCompartmentOccurencesMap(source, nodetoNeighbours.get(processGlyphID)); 268 } 269 } 270 } 271 } 272 273 //Finally assign nodes to compartments by majority rule 274 for(Glyph glyph: targetNodes) 275 { 276 String id = glyph.getId(); 277 HashMap<String, Integer> compartmentsOfTargetNode = nodetoNeighbours.get(id); 278 279 // Finally sort the hashmap to obtain the compartment that includes majority of the neighbours of the targetNode 280 List<Map.Entry<String,Integer>> entries = new LinkedList<Map.Entry<String,Integer>>(compartmentsOfTargetNode.entrySet()); 281 Collections.sort(entries, new Comparator<Map.Entry<String,Integer>>() { 282 283 @Override 284 public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) 285 { 286 return -(o1.getValue().compareTo(o2.getValue())); 287 } 288 }); 289 290 if(entries.size() > 0) 291 { 292 // if the process belongs to root do not make any changes 293 if(entries.get(0).getKey().equals("root")) 294 continue; 295 296 Glyph compartment = idToCompartmentGlyphs.get(entries.get(0).getKey()); 297 //Set compartmentRef of process glyph also! 298 glyph.setCompartmentRef(compartment); 299 compartment.getGlyph().add(glyph); 300 301 //Remove it from sbgn also 302 sbgn.getMap().getGlyph().remove(glyph); 303 } 304 } 305 } 306 307 /** 308 * Updates a hashmap by incrementing the number of nodes in the compartment glyph that includes targetGlyph. 309 * It is assumed that given hashmap includes compartment ids' as keys and number of nodes as values.This method 310 * is an utility method that will be used to populate a hashmap while determining the compartment node of a process 311 * node by majority rule. 312 * 313 * @param targetGlyph glyph whose occurence will be updated in the given hashmap. 314 * @param compartmentIDandOccurenceMap Map that references number of nodes in a compartment by compartment ids . 315 * */ 316 public void populateCompartmentOccurencesMap(Glyph targetGlyph, HashMap<String, Integer> compartmentIDandOccurenceMap) 317 { 318 String rootID = "root"; 319 320 // if compartment ref of targetGlyph node is not null, increment its occurence by 1 321 if(targetGlyph.getCompartmentRef() != null) 322 { 323 Glyph containerCompartment = (Glyph)targetGlyph.getCompartmentRef(); 324 String compartmentID = containerCompartment.getId(); 325 Integer compartmentOccurrenceValue = compartmentIDandOccurenceMap.get(compartmentID); 326 327 if( compartmentOccurrenceValue != null) 328 { 329 compartmentIDandOccurenceMap.put(compartmentID, compartmentOccurrenceValue + 1); 330 } 331 else 332 compartmentIDandOccurenceMap.put(compartmentID, 1); 333 } 334 // else targetGlyph is in root graph so increment root graphs counter value by 1 335 else 336 { 337 Integer compartmentOccurrenceValue = compartmentIDandOccurenceMap.get(rootID); 338 339 if( compartmentOccurrenceValue != null) 340 { 341 compartmentIDandOccurenceMap.put(rootID, compartmentOccurrenceValue + 1); 342 } 343 else 344 compartmentIDandOccurenceMap.put(rootID, 1); 345 } 346 } 347 348 /** 349 * Updates bounds of a compound node ( i.e. complex glyph ) from its children . 350 * @param parent compound glyph. 351 * @param childGlyphs related children of parent . 352 * */ 353 public void updateCompoundBounds(Glyph parent,List<Glyph> childGlyphs) 354 { 355 float PAD = (float) 2.0; 356 float minX = Float.MAX_VALUE; float minY = Float.MAX_VALUE; 357 float maxX = Float.MIN_VALUE; float maxY = Float.MIN_VALUE; 358 359 for (Glyph tmpGlyph:childGlyphs) 360 { 361 if(!tmpGlyph.getClazz().equals("unit of information") && !tmpGlyph.getClazz().equals("state variable") ) 362 { 363 if(tmpGlyph.getGlyph().size() > 0) 364 updateCompoundBounds(tmpGlyph, tmpGlyph.getGlyph()); 365 366 float w = tmpGlyph.getBbox().getW(); 367 float h = tmpGlyph.getBbox().getH(); 368 369 // Verify MIN and MAX x/y again: 370 minX = Math.min(minX, (tmpGlyph.getBbox().getX())); 371 minY = Math.min(minY, (tmpGlyph.getBbox().getY())); 372 maxX = Math.max(maxX, (tmpGlyph.getBbox().getX())+w); 373 maxY = Math.max(maxY, (tmpGlyph.getBbox().getY())+h); 374 375 if (minX == Float.MAX_VALUE) minX = 0; 376 if (minY == Float.MAX_VALUE) minY = 0; 377 if (maxX == Float.MIN_VALUE) maxX = 0; 378 if (maxY == Float.MIN_VALUE) maxY = 0; 379 380 parent.getBbox().setX(minX - PAD); 381 parent.getBbox().setY(minY - PAD); 382 parent.getBbox().setW(maxX - parent.getBbox().getX() + PAD); 383 parent.getBbox().setH(maxY - parent.getBbox().getY() + PAD); 384 } 385 } 386 } 387 388 /** 389 * Recursively creates VNodes from Glyphs of Sbgn. 390 * 391 * @param parent Parent of the glyphs that are passed as second arguement. 392 * @param glyphs Glyphs that are child of parent which is passed as first arguement. 393 * 394 * */ 395 public void createVNodes(VCompound parent,List<Glyph> glyphs) 396 { 397 for(Glyph glyph: glyphs ) 398 { 399 if (!glyph.getClazz().equals("state variable") && !glyph.getClazz().equals("unit of information") ) 400 { 401 if(glyph.getClazz().equals("process")) 402 { 403 VCompound v = new VCompound(glyph); 404 } 405 406 if(!this.isChildless(glyph)) 407 { 408 VCompound v = new VCompound(glyph); 409 410 idToGLyph.put(glyph.getId(), glyph); 411 glyphToVNode.put(glyph, v); 412 parent.children.add(v); 413 createVNodes(v, glyph.getGlyph()); 414 } 415 416 else 417 { 418 VNode v = new VNode(glyph); 419 idToGLyph.put(glyph.getId(), glyph); 420 glyphToVNode.put(glyph, v); 421 parent.children.add(v); 422 } 423 } 424 } 425 } 426 427 /** 428 * Creates LNodes from Arcs of Sbgn and adds it to the passed layout object. 429 * 430 * @param arcs List of arc objects from which the LEdges will be constructed for ChiLay Layout component. 431 * @param layout layout object to which the created LEdges added. 432 * 433 * */ 434 public void createLEdges(List<Arc> arcs, Layout layout) 435 { 436 for(Arc arc: arcs ) 437 { 438 LEdge lEdge = layout.newEdge(null); 439 lEdge.type = arc.getClazz(); 440 lEdge.label = arc.getId(); 441 LNode sourceLNode = this.viewToLayout.get(glyphToVNode.get(arc.getSource())); 442 LNode targetLNode = this.viewToLayout.get(glyphToVNode.get(arc.getTarget())); 443 idToArcs.put(arc.getId(), arc); 444 445 // Add edge to the layout 446 this.layout.getGraphManager().add(lEdge, sourceLNode, targetLNode); 447 } 448 } 449 450 /** 451 * Helper function for creating LNode objects from VNode objects and adds them to the given layout. 452 * 453 * @param vNode VNode object from which a corresponding LNode object will be created. 454 * @param parent parent of vNode, if not null vNode will be added to layout as child node. 455 * @param layout layout object to which the created LNodes added. 456 * */ 457 458 public void createLNode(VNode vNode,VNode parent,Layout layout) 459 { 460 LNode lNode = ((SbgnPDLayout)layout).newNode(vNode); 461 lNode.type = vNode.glyph.getClazz(); 462 lNode.label = vNode.glyph.getId(); 463 LGraph rootLGraph = layout.getGraphManager().getRoot(); 464 465 //Add corresponding nodes to corresponding maps 466 this.viewToLayout.put(vNode, lNode); 467 layoutToView.put(lNode.label,vNode); 468 469 // if the vNode has a parent, add the lNode as a child of the parent l-node. 470 // otherwise, add the node to the root graph. 471 if (parent != null) 472 { 473 LNode parentLNode = this.viewToLayout.get(parent); 474 parentLNode.getChild().add(lNode); 475 } 476 477 else 478 { 479 rootLGraph.add(lNode); 480 } 481 482 lNode.setLocation(vNode.glyph.getBbox().getX(),vNode.glyph.getBbox().getY()); 483 484 if (vNode instanceof VCompound) 485 { 486 VCompound vCompound = (VCompound) vNode; 487 // add new LGraph to the graph manager for the compound node 488 layout.getGraphManager().add(layout.newGraph(null), lNode); 489 // for each VNode in the node set create an LNode 490 for (VNode vChildNode: vCompound.getChildren()) 491 { 492 this.createLNode(vChildNode, vCompound, layout); 493 } 494 } 495 496 else 497 { 498 lNode.setWidth(vNode.glyph.getBbox().getW()); 499 lNode.setHeight(vNode.glyph.getBbox().getH()); 500 } 501 } 502 503 /** 504 * This method recursively set compartmentRef fields of members of any complex glyphs 505 * as same as complex's compartmentRef 506 * 507 * @param glyph target glyph whose compartmentRef(compartment parameter) will be set. 508 * @param compartment compartmentRef value that will be set. 509 * */ 510 public void setCompartmentRefForComplexMembers(Glyph glyph, Glyph compartment) 511 { 512 glyph.setCompartmentRef(compartment); 513 if(glyph.getCompartmentRef() != null && glyph.getGlyph().size() > 0) 514 { 515 for(Glyph g: glyph.getGlyph() ) 516 { 517 setCompartmentRefForComplexMembers(g, compartment); 518 } 519 } 520 } 521 522 /** 523 * This method replaces ports of arc objects with their owners. 524 * @param arcs Arc list of SBGN model 525 * */ 526 public void removePortsFromArcs(List<Arc> arcs) 527 { 528 for(Arc arc: arcs ) 529 { 530 // If source is port, first clear port indicators else retrieve it from hashmaps 531 if (arc.getSource() instanceof Port ) 532 { 533 Glyph source = portIDToOwnerGlyph.get(((Port)arc.getSource()).getId()); 534 arc.setSource(source); 535 } 536 537 // If target is port, first clear port indicators else retrieve it from hashmaps 538 if (arc.getTarget() instanceof Port) 539 { 540 Glyph target = portIDToOwnerGlyph.get(((Port)arc.getTarget()).getId()); 541 arc.setTarget(target); 542 } 543 } 544 } 545 546 /** 547 * This method initializes map for glyphs and their respective ports. 548 * @param glyphs Glyph list of SBGN model 549 * */ 550 public void initPortIdToGlyphMap(List<Glyph> glyphs) 551 { 552 for(Glyph glyph: glyphs) 553 { 554 for(Port p: glyph.getPort()) 555 { 556 portIDToOwnerGlyph.put(p.getId(), glyph ); 557 } 558 if(glyph.getGlyph().size() > 0) 559 initPortIdToGlyphMap(glyph.getGlyph()); 560 } 561 } 562 563 /** 564 * Returns true if a glyph includes child glyphs (state and info glyphs are out of count!) 565 * @param targetGlyph target glyph that will be queried. 566 * @return true/false 567 * */ 568 public boolean isChildless(Glyph targetGlyph) 569 { 570 boolean checker = true; 571 for(Glyph glyph: targetGlyph.getGlyph() ) 572 { 573 if ( !glyph.getClazz().equals("state variable") && !glyph.getClazz().equals("unit of information") ) 574 { 575 checker = false; 576 break; 577 } 578 } 579 return checker; 580 } 581}