001package org.biopax.paxtools.io.sbgn; 002 003import java.util.ArrayList; 004import java.util.Iterator; 005import java.util.List; 006 007import org.sbgn.bindings.Port; 008import org.ivis.layout.LGraphObject; 009import org.ivis.layout.LNode; 010import org.ivis.layout.Updatable; 011import org.ivis.layout.sbgn.SbgnProcessNode; 012import org.sbgn.bindings.Glyph; 013import org.sbgn.bindings.Port; 014import org.sbgn.bindings.Bbox; 015import org.ivis.layout.cose.CoSEGraph; 016 017 018 019/** 020 * VNode Class 021 * @author: Istemi Bahceci 022 * */ 023 024public class VNode implements Updatable 025{ 026 //Glyph attribute of this VNode 027 public Glyph glyph; 028 public int clusterID; 029 030 ArrayList <Glyph> stateGlyphs; 031 ArrayList <Glyph> infoGlyphs; 032 033 /*Glyph class types*/ 034 private static final String MACROMOLECULE = "macromolecule"; 035 private static final String UNIT_OF_INFORMATION = "unit of information"; 036 private static final String STATE_VARIABLE = "state variable"; 037 private static final String SOURCE_AND_SINK = "source and sink"; 038 private static final String ASSOCIATION = "association"; 039 private static final String DISSOCIATION = "dissociation"; 040 private static final String OMITTED_PROCESS = "omitted process"; 041 private static final String UNCERTAIN_PROCESS = "uncertain process"; 042 private static final String SIMPLE_CHEMICAL = "simple chemical"; 043 private static final String PROCESS = "process"; 044 private static final String COMPLEX = "complex"; 045 private static final String AND = "and"; 046 private static final String OR = "or"; 047 private static final String NOT = "not"; 048 private static final String PHENOTYPE = "phenotype"; 049 private static final String PERTURBING_AGENT = "perturbing agent"; 050 private static final String TAG = "tag"; 051 private static final String NUCLEIC_ACID_FEATURE = "nucleic acid feature"; 052 private static final String UNSPECIFIED_ENTITY = "unspecified entity"; 053 054 // Constats used for determining "state of information" and "unit of information" glyph widths according to 055 // their labels 056 private static final int LOWERCASE_LETTER_PIXEL_WIDTH = 6; 057 private static final int UPPERCASE_LETTER_PIXEL_WIDTH = 9; 058 private static final int MAX_STATE_AND_INFO_WIDTH = 50; 059 private static final int MAX_STATE_AND_INFO_HEIGHT = 15; 060 private static final int OFFSET_BTW_INFO_GLYPHS = 5; 061 private static final int MAX_INFO_BOX_NUMBER = 4; 062 private static final int MAX_MACROMOLECULE_HEIGHT_WITH_INFO_BOXES = 25; 063 064 /*Glyph Size Constants for layout*/ 065 private static Bound SOURCE_AND_SINK_BOUND; 066 private static Bound LOGICAL_OPERATOR_BOUND; 067 private static Bound PROCESS_NODES_BOUND; 068 private static Bound MACROMOLECULE_BOUND; 069 private static Bound NUCLEIC_ACID_FEATURE_BOUND; 070 private static Bound SIMPLE_CHEMICAL_BOUND; 071 private static Bound UNSPECIFIED_ENTITY_BOUND; 072 private static Bound PHENOTYPE_BOUND; 073 private static Bound PERTURBING_AGENT_BOUND; 074 private static Bound TAG_BOUND; 075 private static Bound INFO_BOUND; 076 private static Bound STATE_BOUND; 077 private static Bound COMPLEX_BOUND; 078 079 080 /** 081 * Default Constructor, sets the geometry of the bounds which are attributes of this class 082 * @param g glyph 083 * */ 084 public VNode(Glyph g) 085 { 086 SOURCE_AND_SINK_BOUND = new Bound(15,15); 087 LOGICAL_OPERATOR_BOUND = new Bound(15,15); 088 PROCESS_NODES_BOUND = new Bound(15,15); 089 090 MACROMOLECULE_BOUND = new Bound(48,20); 091 NUCLEIC_ACID_FEATURE_BOUND = new Bound(50,20); 092 093 SIMPLE_CHEMICAL_BOUND = new Bound(48,20); 094 UNSPECIFIED_ENTITY_BOUND = new Bound(40,40); 095 PHENOTYPE_BOUND = new Bound(50,20); 096 TAG_BOUND = new Bound(50,20); 097 PERTURBING_AGENT_BOUND = new Bound(50,20); 098 COMPLEX_BOUND = new Bound(48,20); 099 100 INFO_BOUND = new Bound(MAX_STATE_AND_INFO_WIDTH,MAX_STATE_AND_INFO_HEIGHT); 101 STATE_BOUND = new Bound(MAX_STATE_AND_INFO_WIDTH,MAX_STATE_AND_INFO_HEIGHT); 102 103 stateGlyphs = new ArrayList<Glyph>(); 104 infoGlyphs = new ArrayList<Glyph>(); 105 106 this.glyph = g; 107 108 this.setSizeAccordingToClass(); 109 } 110 111 /** 112 * Function that will take place when VNode objects will update in layout process of ChiLay 113 * 114 * @param lGraphObj LGraphObject for whom the update will take place. 115 */ 116 public void update(LGraphObject lGraphObj) 117 { 118 if (lGraphObj instanceof CoSEGraph) 119 { 120 return; 121 } 122 123 LNode lNode = (LNode)lGraphObj; 124 125 this.glyph.getBbox().setX((float) lNode.getLeft()); 126 this.glyph.getBbox().setY((float) lNode.getTop()); 127 this.placeStateAndInfoGlyphs(); 128 } 129 130 /** 131 * Sets the bound of this VNode by given width and height 132 * @param w new width 133 * @param h new height 134 */ 135 public void setBounds(float w, float h) 136 { 137 this.glyph.getBbox().setW(w); 138 this.glyph.getBbox().setH(h); 139 } 140 141 /** 142 * Chooses a proper bound for this VNode according to its class. 143 */ 144 public void setSizeAccordingToClass() 145 { 146 String glyphClass = this.glyph.getClazz(); 147 148 /* 149 * need to add bbox objects 150 */ 151 Bbox b = new Bbox(); 152 this.glyph.setBbox(b); 153 154 if (glyphClass == SOURCE_AND_SINK) 155 { 156 setBounds(SOURCE_AND_SINK_BOUND.getWidth(), SOURCE_AND_SINK_BOUND.getHeight()); 157 } 158 159 else if (glyphClass == AND || glyphClass == OR || glyphClass == NOT ) 160 { 161 setBounds(LOGICAL_OPERATOR_BOUND.getWidth(), LOGICAL_OPERATOR_BOUND.getHeight()); 162 } 163 164 else if (glyphClass == ASSOCIATION || glyphClass == DISSOCIATION || glyphClass == OMITTED_PROCESS || 165 glyphClass == UNCERTAIN_PROCESS || glyphClass == PROCESS) 166 { 167 setBounds(PROCESS_NODES_BOUND.getWidth(), PROCESS_NODES_BOUND.getHeight()); 168 } 169 170 else if (glyphClass == SIMPLE_CHEMICAL) 171 { 172 setBounds(SIMPLE_CHEMICAL_BOUND.getWidth(), SIMPLE_CHEMICAL_BOUND.getHeight()); 173 } 174 175 else if (glyphClass == UNSPECIFIED_ENTITY) 176 { 177 setBounds(UNSPECIFIED_ENTITY_BOUND.getWidth(), UNSPECIFIED_ENTITY_BOUND.getHeight()); 178 } 179 180 else if (glyphClass == MACROMOLECULE) 181 { 182 setBounds(MACROMOLECULE_BOUND.getWidth(), MACROMOLECULE_BOUND.getHeight()); 183 } 184 185 else if (glyphClass == NUCLEIC_ACID_FEATURE) 186 { 187 setBounds(NUCLEIC_ACID_FEATURE_BOUND.getWidth(), NUCLEIC_ACID_FEATURE_BOUND.getHeight()); 188 } 189 190 else if (glyphClass == STATE_VARIABLE) 191 { 192 setBounds(STATE_BOUND.getWidth(), STATE_BOUND.getHeight()); 193 } 194 195 else if (glyphClass == UNIT_OF_INFORMATION) 196 { 197 setBounds(INFO_BOUND.getWidth(), INFO_BOUND.getHeight()); 198 } 199 200 else if (glyphClass == PHENOTYPE) 201 { 202 setBounds(PHENOTYPE_BOUND.getWidth(), PHENOTYPE_BOUND.getHeight()); 203 } 204 205 else if (glyphClass == PERTURBING_AGENT) 206 { 207 setBounds(PERTURBING_AGENT_BOUND.getWidth(), PERTURBING_AGENT_BOUND.getHeight()); 208 } 209 210 else if (glyphClass == TAG) 211 { 212 setBounds(TAG_BOUND.getWidth(), TAG_BOUND.getHeight()); 213 } 214 215 else if (glyphClass == COMPLEX) 216 { 217 setBounds(COMPLEX_BOUND.getWidth(), COMPLEX_BOUND.getHeight()); 218 } 219 220 if( this.glyph.getClone() != null ) 221 { 222 Bbox glyphBbox = this.glyph.getBbox(); 223 setBounds(3*glyphBbox.getW()/4, 3*glyphBbox.getH()/4); 224 } 225 226 if (glyphClass == MACROMOLECULE || glyphClass == NUCLEIC_ACID_FEATURE || glyphClass == SIMPLE_CHEMICAL || glyphClass == COMPLEX) 227 { 228 updateSizeForStateAndInfo(); 229 } 230 231 } 232 233 /** 234 * Calculates required width according to the given list state and info glyphs of this VNode. 235 * This method also previously computes the width and height of state and info glyphs 236 * according to their label. 237 * 238 * @param stateORinfoList list that keeps state or info glyphs of this VNode 239 * @return new width that is adjusted so that all glyphs in stateORinfoList are included. 240 * */ 241 public int calcReqWidthByStateAndInfos(List<Glyph> stateORinfoList) 242 { 243 int wholeSize = 0; 244 int count = 0; 245 246 for (Glyph tmpGlyph: stateORinfoList) 247 { 248 String text; 249 250 if (tmpGlyph.getState() != null) 251 { 252 text = tmpGlyph.getState().getValue(); 253 254 if (tmpGlyph.getState().getVariable() != null && 255 tmpGlyph.getState().getVariable().length() > 0) 256 { 257 if(tmpGlyph.getState().getVariable() != null) 258 text += "@" + tmpGlyph.getState().getVariable(); 259 } 260 } 261 else if (tmpGlyph.getLabel() != null) 262 { 263 text = tmpGlyph.getLabel().getText(); 264 } 265 else 266 { 267 throw new RuntimeException("Encountered an information glyph with no state " + 268 "variable (as modification boxes should have) and no label (as molecule type " + 269 "boxed should have). glyph = " + tmpGlyph); 270 } 271 272 int numOfUpper = 0; 273 int numOfLower = 0; 274 275 for (int i = 0; i < text.length(); i++) 276 { 277 if (Character.isLowerCase(text.charAt(i))) 278 { 279 numOfLower++; 280 281 } else 282 numOfUpper++; 283 } 284 285 Bbox b = new Bbox(); 286 tmpGlyph.setBbox(b); 287 288 //Set width 289 float requiredSize = numOfLower * LOWERCASE_LETTER_PIXEL_WIDTH + numOfUpper * UPPERCASE_LETTER_PIXEL_WIDTH; 290 if (requiredSize < MAX_STATE_AND_INFO_WIDTH) 291 tmpGlyph.getBbox().setW(requiredSize); 292 else 293 tmpGlyph.getBbox().setW(STATE_BOUND.width); 294 295 //Set height 296 tmpGlyph.getBbox().setH(MAX_STATE_AND_INFO_HEIGHT); 297 298 if (count < MAX_INFO_BOX_NUMBER / 2) 299 wholeSize += tmpGlyph.getBbox().getW(); 300 301 count++; 302 303 } 304 305 return wholeSize; 306 } 307 308 /** 309 * If glyph attribute of this VNode object includes any "state of information" or "unit of information" glyphs, this method updates the 310 * size of VNode accordingly. 311 * */ 312 public void updateSizeForStateAndInfo() 313 { 314 315 // Find all state and info glyphs 316 for (Glyph glyph : this.glyph.getGlyph()) 317 { 318 if (glyph.getClazz() == STATE_VARIABLE) 319 { 320 stateGlyphs.add(glyph); 321 } 322 else if (glyph.getClazz() == UNIT_OF_INFORMATION) 323 { 324 infoGlyphs.add(glyph); 325 } 326 } 327 328 //Calculate "state of information" glyphs' sizes 329 int wholeWidthOfStates = calcReqWidthByStateAndInfos(stateGlyphs); 330 int wholeWidthOfInfos = calcReqWidthByStateAndInfos(infoGlyphs); 331 332 // Calculate positions 333 int numOfStates = stateGlyphs.size(); 334 int numOfInfos = infoGlyphs.size(); 335 336 //Adjust heights so that info box offsets are taken into account in layout. 337 if(numOfStates > 0 || numOfInfos > 0) 338 this.glyph.getBbox().setH(this.glyph.getBbox().getH()+MAX_STATE_AND_INFO_HEIGHT/2); 339 340 //Half of the info boxes on the upper side, half on the bottom side. 341 numOfStates = (numOfStates >= MAX_INFO_BOX_NUMBER/2) ? MAX_INFO_BOX_NUMBER/2 : numOfStates; 342 numOfInfos = (numOfInfos >= MAX_INFO_BOX_NUMBER/2) ? MAX_INFO_BOX_NUMBER/2 : numOfInfos; 343 344 float requiredWidthForStates = (numOfStates+1) * OFFSET_BTW_INFO_GLYPHS + wholeWidthOfStates; 345 float requiredWidthForInfos = (numOfInfos+1) * OFFSET_BTW_INFO_GLYPHS + wholeWidthOfInfos; 346 347 348 if (this.glyph.getBbox().getW() < requiredWidthForStates || this.glyph.getBbox().getW() < requiredWidthForInfos ) 349 { 350 this.glyph.getBbox().setW(Math.max(requiredWidthForStates, requiredWidthForInfos)); 351 } 352 } 353 354 /** 355 * Places state and info glyphs of this node 356 * */ 357 public void placeStateAndInfoGlyphs() 358 { 359 int numOfStates = stateGlyphs.size(); 360 int numOfInfos = infoGlyphs.size(); 361 362 //Adjust heights so that inf obox offsets are taken into account in layout. 363 if(numOfStates > 0 || numOfInfos > 0) 364 this.glyph.getBbox().setH(this.glyph.getBbox().getH()-MAX_STATE_AND_INFO_HEIGHT/2); 365 366 float parent_y_up = this.glyph.getBbox().getY()-INFO_BOUND.height/2; 367 float parent_y_bot = this.glyph.getBbox().getY()+this.glyph.getBbox().getH()-INFO_BOUND.height/2;; 368 float parent_x_up = this.glyph.getBbox().getX(); 369 float parentWidth = this.glyph.getBbox().getW(); 370 String parentID = this.glyph.getId(); 371 372 int usedWidth = 0; 373 for (int i = 0; i < numOfStates; i++) 374 { 375 Glyph tmpglyph = stateGlyphs.get(i); 376 if(numOfStates == 1) 377 { 378 tmpglyph.getBbox().setX(parent_x_up+parentWidth/2-tmpglyph.getBbox().getW()/2); 379 tmpglyph.getBbox().setY(parent_y_bot); 380 //set dummy id 381 tmpglyph.setId(parentID + ".state." + (i+1) ); 382 break; 383 } 384 385 //set dummy id 386 tmpglyph.setId(parentID + ".state." + (i+1) ); 387 388 tmpglyph.getBbox().setX(parent_x_up+(i+1)*OFFSET_BTW_INFO_GLYPHS + usedWidth); 389 tmpglyph.getBbox().setY(parent_y_bot); 390 391 usedWidth += tmpglyph.getBbox().getW(); 392 393 } 394 395 usedWidth = 0; 396 for (int i = 0; i < numOfInfos; i++) 397 { 398 Glyph tmpglyph = infoGlyphs.get(i); 399 if(numOfInfos == 1) 400 { 401 tmpglyph.getBbox().setX(parent_x_up+parentWidth/2-tmpglyph.getBbox().getW()/2); 402 tmpglyph.getBbox().setY(parent_y_up); 403 //set dummy id 404 tmpglyph.setId(parentID + ".info." + (i+1) ); 405 break; 406 } 407 408 //set dummy id 409 tmpglyph.setId(parentID + ".info." + (i+1) ); 410 411 tmpglyph.getBbox().setX(parent_x_up+(i+1)*OFFSET_BTW_INFO_GLYPHS + usedWidth); 412 tmpglyph.getBbox().setY(parent_y_up); 413 414 usedWidth += tmpglyph.getBbox().getW(); 415 } 416 } 417 418 /** 419 * Inner Class for glyph bounds 420 * */ 421 public class Bound 422 { 423 public float width; 424 public float height; 425 426 public Bound(float width, float height) 427 { 428 this.width = width; 429 this.height = height; 430 } 431 432 public float getWidth() 433 { 434 return width; 435 } 436 437 public void setWidth(float width) 438 { 439 this.width = width; 440 } 441 442 public float getHeight() 443 { 444 return height; 445 } 446 447 public void setHeight(float height) 448 { 449 this.height = height; 450 } 451 } 452}