001package com.hfg.bio.phylogeny; 002 003import java.util.*; 004import java.util.List; 005import java.util.regex.Pattern; 006import java.awt.*; 007 008import com.hfg.util.AttributeMgr; 009import com.hfg.util.collection.CollectionUtil; 010import com.hfg.util.StringUtil; 011import com.hfg.network.Edge; 012 013 014//------------------------------------------------------------------------------ 015/** 016 Object representation of a node in a Newick format phylogenetic tree. 017 Does not work with negative distance values. 018 <div> 019 @author J. Alex Taylor, hairyfatguy.com 020 </div> 021 */ 022//------------------------------------------------------------------------------ 023// com.hfg XML/HTML Coding Library 024// 025// This library is free software; you can redistribute it and/or 026// modify it under the terms of the GNU Lesser General Public 027// License as published by the Free Software Foundation; either 028// version 2.1 of the License, or (at your option) any later version. 029// 030// This library is distributed in the hope that it will be useful, 031// but WITHOUT ANY WARRANTY; without even the implied warranty of 032// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 033// Lesser General Public License for more details. 034// 035// You should have received a copy of the GNU Lesser General Public 036// License along with this library; if not, write to the Free Software 037// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 038// 039// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 040// jataylor@hairyfatguy.com 041//------------------------------------------------------------------------------ 042 043public class PhyloNode 044{ 045 private List<Edge<PhyloNode>> mEdges; 046 private String mId; 047 private String mLabel; 048 private Color mColor; 049 private String mTooltip; 050 051 private Integer mLineIndex; 052 private Integer mVericalLineStartIndex; 053 private Integer mVericalLineEndIndex; 054 private Double mAngle; 055 056 private AttributeMgr mAttributeMgr; 057 058 private static SubnodeCountComparator sSubnodeCountComparator = new PhyloNode().new SubnodeCountComparator(); 059 060 private static final Pattern sWhitespacePattern = Pattern.compile("\\s+"); 061 062 // cached values 063 private Float mDistanceFromRoot; 064 065 //-------------------------------------------------------------------------- 066 public PhyloNode setId(int inValue) 067 { 068 return setId(inValue + ""); 069 } 070 071 //-------------------------------------------------------------------------- 072 public PhyloNode setId(String inValue) 073 { 074 mId = inValue; 075 return this; 076 } 077 078 //-------------------------------------------------------------------------- 079 public String getId() 080 { 081 return mId; 082 } 083 084 085 //-------------------------------------------------------------------------- 086 public PhyloNode setLabel(String inValue) 087 { 088 mLabel = inValue; 089 return this; 090 } 091 092 //-------------------------------------------------------------------------- 093 public String getLabel() 094 { 095 return mLabel; 096 } 097 098 099 //-------------------------------------------------------------------------- 100 public PhyloNode setTooltip(String inValue) 101 { 102 mTooltip = inValue; 103 return this; 104 } 105 106 //-------------------------------------------------------------------------- 107 public String getTooltip() 108 { 109 return mTooltip; 110 } 111 112 113 //-------------------------------------------------------------------------- 114 public PhyloNode setColor(Color inValue) 115 { 116 mColor = inValue; 117 return this; 118 } 119 120 //-------------------------------------------------------------------------- 121 public Color getColor() 122 { 123 return mColor; 124 } 125 126 127 //-------------------------------------------------------------------------- 128 public List<Edge<PhyloNode>> getEdges() 129 { 130 return mEdges; 131 } 132 133 //-------------------------------------------------------------------------- 134 public void addEdge(PhyloNode in2ndNode, Float inDistance) 135 { 136 if (null == mEdges) 137 { 138 mEdges = new ArrayList<>(3); 139 } 140 141 Edge<PhyloNode> edge = new Edge<>(this, in2ndNode, inDistance); 142 mEdges.add(edge); 143 in2ndNode.addEdge(edge); 144 } 145 146 147 //-------------------------------------------------------------------------- 148 public PhyloNode setAngle(Double inAngleInRadians) 149 { 150 mAngle = inAngleInRadians; 151 return this; 152 } 153 154 //-------------------------------------------------------------------------- 155 public Double getAngle() 156 { 157 return mAngle; 158 } 159 160 //-------------------------------------------------------------------------- 161 protected void addEdge(Edge<PhyloNode> inEdge) 162 { 163 if (null == mEdges) 164 { 165 mEdges = new ArrayList<>(3); 166 } 167 168 mEdges.add(inEdge); 169 } 170 171 //-------------------------------------------------------------------------- 172 protected boolean removeEdge(Edge<PhyloNode> inEdge) 173 { 174 boolean result = false; 175 if (mEdges != null) 176 { 177 result = mEdges.remove(inEdge); 178 // Remove the edge from the other node as well. 179 if (this == inEdge.getFrom()) 180 { 181 inEdge.setFrom(null); 182 if (inEdge.getTo() != null) inEdge.getTo().removeEdge(inEdge); 183 } 184 else if (this == inEdge.getTo()) 185 { 186 inEdge.setTo(null); 187 if (inEdge.getFrom() != null) inEdge.getFrom().removeEdge(inEdge); 188 } 189 } 190 191 return result; 192 } 193 194 //-------------------------------------------------------------------------- 195 protected boolean removeEdgeFrom(PhyloNode inNode) 196 { 197 boolean result = false; 198 if (mEdges != null) 199 { 200 for (Edge<PhyloNode> edge : mEdges) 201 { 202 if (edge.getFrom() == inNode) 203 { 204 mEdges.remove(edge); 205 edge.setTo(null); 206 // Remove the edge from the other node as well. 207 edge.getFrom().removeEdge(edge); 208 result = true; 209 break; 210 } 211 } 212 } 213 214 return result; 215 } 216 217 //-------------------------------------------------------------------------- 218 public boolean isLeaf() 219 { 220 boolean isLeaf = true; 221 if (mEdges != null) 222 { 223 for (Edge edge : mEdges) 224 { 225 if (edge.getFrom().equals(this)) 226 { 227 isLeaf = false; 228 break; 229 } 230 } 231 } 232 233 return isLeaf; 234 } 235 236 //-------------------------------------------------------------------------- 237 public void makeRoot() 238 { 239 // Fan out through the tree setting the directionality to flow from the root. 240 for (Edge<PhyloNode> edge : mEdges) 241 { 242 if (edge.getFrom() != this) edge.switchDirection(); 243 244 edge.getTo().setRootDirection(edge); 245 } 246 } 247 248 //-------------------------------------------------------------------------- 249 protected void setRootDirection(Edge<PhyloNode> inEdge) 250 { 251 for (Edge<PhyloNode> edge : mEdges) 252 { 253 if (edge == inEdge) continue; 254 255 if (edge.getFrom() != this) edge.switchDirection(); 256 257 edge.getTo().setRootDirection(edge); 258 } 259 } 260 261 /* 262 //-------------------------------------------------------------------------- 263 public Float getMaxDistance() 264 { 265 float maxChildDistance = 0; 266 267 if (CollectionUtil.hasValues(mChildNodes)) 268 { 269 for (PhyloNode child : mChildNodes) 270 { 271 float childDistance = child.getMaxDistance(); 272 if (childDistance > maxChildDistance) 273 { 274 maxChildDistance = childDistance; 275 } 276 } 277 } 278 279 return maxChildDistance + (getDistance() != null ? getDistance() : 0); 280 } 281 282 //-------------------------------------------------------------------------- 283 public Float getDistance() 284 { 285 return mDistance; 286 } 287*/ 288 //-------------------------------------------------------------------------- 289 public PhyloNode getParentNode() 290 { 291 PhyloNode parent = null; 292 293 Edge<PhyloNode> parentEdge = getParentEdge(); 294 if (parentEdge != null) 295 { 296 parent = parentEdge.getFrom(); 297 } 298 299 return parent; 300 } 301 302 //-------------------------------------------------------------------------- 303 public List<PhyloNode> getChildNodes() 304 { 305 List<PhyloNode> childNodes = null; 306 if (CollectionUtil.hasValues(mEdges)) 307 { 308 for (Edge<PhyloNode> edge : mEdges) 309 { 310 if (edge.getFrom() == this) 311 { 312 if (null == childNodes) 313 { 314 childNodes = new ArrayList<>(4); 315 } 316 317 childNodes.add(edge.getTo()); 318 } 319 } 320 } 321 322 return childNodes; 323 } 324 325 //-------------------------------------------------------------------------- 326 /** 327 Returns the root-facing edge. 328 @return the root-facing Edge for the node 329 */ 330 public Edge<PhyloNode> getParentEdge() 331 { 332 Edge<PhyloNode> parentEdge = null; 333 334 if (CollectionUtil.hasValues(mEdges)) 335 { 336 for (Edge<PhyloNode> edge : mEdges) 337 { 338 if (edge.getTo() == this) 339 { 340 parentEdge = edge; 341 break; 342 } 343 } 344 } 345 346 return parentEdge; 347 } 348 349 //-------------------------------------------------------------------------- 350 /** 351 Returns the leaf-facing edges. 352 @return the leaf-facing Edge for the node 353 */ 354 public List<Edge<PhyloNode>> getLeafFacingEdges() 355 { 356 List<Edge<PhyloNode>> leafFacingEdges = new ArrayList<>(3); 357 358 if (CollectionUtil.hasValues(mEdges)) 359 { 360 for (Edge<PhyloNode> edge : mEdges) 361 { 362 if (edge.getFrom() == this) 363 { 364 leafFacingEdges.add(edge); 365 } 366 } 367 } 368 369 return leafFacingEdges; 370 } 371 372 //-------------------------------------------------------------------------- 373 public void orderEdgesByLeafCount() 374 { 375 if (! isLeaf()) 376 { 377 List<Edge<PhyloNode>> leafFacingEdges = getLeafFacingEdges(); 378 if (CollectionUtil.hasValues(leafFacingEdges)) 379 { 380 Collections.sort(leafFacingEdges, sSubnodeCountComparator); 381 } 382 383 List<Edge<PhyloNode>> newEdgeList = new ArrayList<>(mEdges.size()); 384 for (Edge<PhyloNode> edge : mEdges) 385 { 386 if (edge.getFrom() != this) 387 { 388 newEdgeList.add(edge); 389 } 390 } 391 392 newEdgeList.addAll(leafFacingEdges); 393 394 mEdges = newEdgeList; 395 } 396 } 397 398 //-------------------------------------------------------------------------- 399 public Float getDistanceFromRoot() 400 { 401 if (null == mDistanceFromRoot) 402 { 403 float distance = 0; 404 Edge<PhyloNode> parentEdge = getParentEdge(); 405 if (parentEdge != null) 406 { 407 do 408 { 409 if (parentEdge.getDistance() != null) distance += parentEdge.getDistance(); 410 } 411 while (parentEdge.getFrom() != null 412 && (parentEdge = parentEdge.getFrom().getParentEdge()) != null); 413 } 414 415 mDistanceFromRoot = distance; 416 } 417 418 return mDistanceFromRoot; 419 } 420 421 //-------------------------------------------------------------------------- 422 public Float getMaxDistanceToLeaf() 423 { 424 float maxDistance = 0; 425 426 if (! isLeaf()) 427 { 428 for (Edge<PhyloNode> edge : mEdges) 429 { 430 if (edge.getFrom() == this) 431 { 432 float childDistance = 0; 433 if (edge.getDistance() != null) childDistance += edge.getDistance(); 434 PhyloNode child = edge.getTo(); 435 if (! child.isLeaf()) childDistance += child.getMaxDistanceToLeaf(); 436 437 if (childDistance > maxDistance) 438 { 439 maxDistance = childDistance; 440 } 441 } 442 } 443 } 444 445 return maxDistance; 446 } 447 448 //-------------------------------------------------------------------------- 449 public int getSubnodeCount() 450 { 451 int subnodeCount = 0; 452 453 if (! isLeaf()) 454 { 455 for (Edge<PhyloNode> edge : mEdges) 456 { 457 if (edge.getFrom() == this) 458 { 459 subnodeCount += 1 + edge.getTo().getSubnodeCount(); 460 } 461 } 462 } 463 464 return subnodeCount; 465 } 466 467 //-------------------------------------------------------------------------- 468 /** 469 Calculates the distance between two nodes in the tree. 470 @param inNode2 the node to calculate the distance to 471 @return the distance between this node and the specified node 472 */ 473 // Three cases: 474 // 1. node2 is above node1 475 // 2. node2 is below node1 476 // 3. node2 and node1 have a common ancestor 477 public Float distanceTo(PhyloNode inNode2) 478 { 479 float result = 0; 480 float distance1 = 0; 481 float distance2 = 0; 482 483 Map<PhyloNode, Float> map1 = new HashMap<>(); 484 485 boolean done = false; 486 Edge<PhyloNode> parentEdge = getParentEdge(); 487 if (parentEdge != null) 488 { 489 do 490 { 491 map1.put(parentEdge.getTo(), distance1); 492 if (parentEdge.getTo() == inNode2) 493 { 494 result = distance1; 495 done = true; 496 break; 497 } 498 499 if (parentEdge.getDistance() != null) distance1 += parentEdge.getDistance(); 500 } 501 while (parentEdge.getFrom() != null 502 && (parentEdge = parentEdge.getFrom().getParentEdge()) != null); 503 } 504 505 506 if (!done) 507 { 508 parentEdge = inNode2.getParentEdge(); 509 if (parentEdge != null) 510 { 511 do 512 { 513 if (parentEdge.getDistance() != null) distance2 += parentEdge.getDistance(); 514 515 Float distance = map1.get(parentEdge.getFrom()); 516 if (distance != null) 517 { 518 result = distance + distance2; 519 done = true; 520 break; 521 } 522 } 523 while (parentEdge.getFrom() != null 524 && (parentEdge = parentEdge.getFrom().getParentEdge()) != null); 525 } 526 527 if (!done) 528 { 529 result = distance1 + distance2; 530 } 531 } 532 533 return result; 534 } 535 536 //-------------------------------------------------------------------------- 537 @Override 538 public String toString() 539 { 540 StringBuilder buffer = new StringBuilder(); 541 if (! isLeaf()) 542 { 543 buffer.append("("); 544 for (Edge<PhyloNode> edge : mEdges) 545 { 546 if (edge.getFrom() == this) 547 { 548 PhyloNode child = edge.getTo(); 549 if (buffer.length() > 1) buffer.append(","); 550 buffer.append(child.toString()); 551 } 552 } 553 buffer.append(")"); 554 } 555 556 if (StringUtil.isSet(getLabel())) 557 { 558 // Convert spaces to underscores. 559 String label = StringUtil.replaceAllRegexp(mLabel, sWhitespacePattern, "_"); 560 // Convert colons to underscores. 561 label = StringUtil.replaceAll(label, ":", "_"); 562 buffer.append(label); 563 } 564 565 Edge<PhyloNode> parentEdge = getParentEdge(); 566 if (parentEdge != null 567 && parentEdge.getDistance() != null) 568 { 569 buffer.append(":" + parentEdge.getDistance()); 570 } 571 572 return buffer.toString(); 573 } 574 575 576 //-------------------------------------------------------------------------- 577 public void setAttribute(String inName, Object inValue) 578 { 579 getOrInitAttributeMgr().setAttribute(inName, inValue); 580 } 581 582 //-------------------------------------------------------------------------- 583 public boolean hasAttributes() 584 { 585 return mAttributeMgr != null && mAttributeMgr.hasAttributes(); 586 } 587 588 //-------------------------------------------------------------------------- 589 public boolean hasAttribute(String inName) 590 { 591 return mAttributeMgr != null && getOrInitAttributeMgr().hasAttribute(inName); 592 } 593 594 //-------------------------------------------------------------------------- 595 public Object getAttribute(String inName) 596 { 597 return getOrInitAttributeMgr().getAttribute(inName); 598 } 599 600 //-------------------------------------------------------------------------- 601 public Collection<String> getAttributeNames() 602 { 603 return getOrInitAttributeMgr().getAttributeNames(); 604 } 605 606 //-------------------------------------------------------------------------- 607 public void clearAttributes() 608 { 609 if (mAttributeMgr != null) 610 { 611 mAttributeMgr.clearAttributes(); 612 } 613 } 614 615 //-------------------------------------------------------------------------- 616 public Object removeAttribute(String inName) 617 { 618 Object attr = null; 619 if (mAttributeMgr != null) 620 { 621 attr = getOrInitAttributeMgr().removeAttribute(inName); 622 } 623 624 return attr; 625 } 626 627 //-------------------------------------------------------------------------- 628 protected void setLineIndex(int inValue) 629 { 630 mLineIndex = inValue; 631 } 632 633 //-------------------------------------------------------------------------- 634 protected Integer getLineIndex() 635 { 636 return mLineIndex; 637 } 638 639 //-------------------------------------------------------------------------- 640 protected void setVerticalLineStartIndex(int inValue) 641 { 642 mVericalLineStartIndex = inValue; 643 } 644 645 //-------------------------------------------------------------------------- 646 protected Integer getVericalLineStartIndex() 647 { 648 return mVericalLineStartIndex; 649 } 650 651 //-------------------------------------------------------------------------- 652 protected void setVerticalLineEndIndex(int inValue) 653 { 654 mVericalLineEndIndex = inValue; 655 } 656 657 //-------------------------------------------------------------------------- 658 protected Integer getVericalLineEndIndex() 659 { 660 return mVericalLineEndIndex; 661 } 662 663 664 //-------------------------------------------------------------------------- 665 private AttributeMgr getOrInitAttributeMgr() 666 { 667 if (null == mAttributeMgr) 668 { 669 mAttributeMgr = new AttributeMgr(); 670 } 671 672 return mAttributeMgr; 673 } 674 675 676 private class SubnodeCountComparator implements Comparator<Edge<PhyloNode>> 677 { 678 public int compare(Edge<PhyloNode> edge1, Edge<PhyloNode> edge2) 679 { 680 int node1SubnodeCount = edge1.getTo().getSubnodeCount(); 681 int node2SubnodeCount = edge2.getTo().getSubnodeCount(); 682 683 if (node1SubnodeCount > node2SubnodeCount) return -1; 684 if (node1SubnodeCount < node2SubnodeCount) return 1; 685 686 if (edge1.getTo().getLabel() != null) 687 { 688 return edge1.getTo().getLabel().compareTo(edge2.getTo().getLabel()); 689 } 690 691 return 0; 692 } 693 } 694 695}