Package ete2 :: Package treeview :: Module drawer
[hide private]
[frames] | no frames]

Source Code for Module ete2.treeview.drawer

   1  __VERSION__="ete2-2.0rev104"  
   2  # #START_LICENSE########################################################### 
   3  # 
   4  # Copyright (C) 2009 by Jaime Huerta Cepas. All rights reserved.   
   5  # email: jhcepas@gmail.com 
   6  # 
   7  # This file is part of the Environment for Tree Exploration program (ETE).  
   8  # http://ete.cgenomics.org 
   9  #   
  10  # ETE is free software: you can redistribute it and/or modify it 
  11  # under the terms of the GNU General Public License as published by 
  12  # the Free Software Foundation, either version 3 of the License, or 
  13  # (at your option) any later version. 
  14  #   
  15  # ETE is distributed in the hope that it will be useful, 
  16  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  17  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  18  # GNU General Public License for more details. 
  19  #   
  20  # You should have received a copy of the GNU General Public License 
  21  # along with ETE.  If not, see <http://www.gnu.org/licenses/>. 
  22  # 
  23  # #END_LICENSE############################################################# 
  24   
  25  from sys import stderr, stdout 
  26  import time 
  27  import re 
  28  import math 
  29  import random 
  30  import types 
  31  import copy 
  32  import string 
  33  import numpy 
  34  try: 
  35      from PyQt4  import QtCore, QtGui 
  36      from PyQt4.QtGui import QPrinter 
  37  except ImportError: 
  38      import QtCore, QtGui 
  39      from QtGui import QPrinter 
  40  import layouts 
  41  import _mainwindow, _search_dialog, _show_newick, _open_newick, _about 
  42   
  43  try: 
  44      from PyQt4 import QtOpenGL 
  45      #USE_GL = True 
  46      USE_GL = False # Temporarily disabled 
  47  except ImportError: 
  48      USE_GL = False 
  49   
  50  from ete2 import Tree, PhyloTree, ClusterTree 
  51   
  52  __all__ = ["show_tree", "render_tree", "TreeImageProperties"] 
  53   
  54  DEBUG = 0 
  55  _QApp = None 
  56   
  57  _MIN_NODE_STYLE = { 
  58      "fgcolor": "#0030c1", 
  59      "bgcolor": "#FFFFFF", 
  60      "vt_line_color": "#000000", 
  61      "hz_line_color": "#000000", 
  62      "line_type": 0, 
  63      "vlwidth": 1, 
  64      "hlwidth": 1, 
  65      "size":6, 
  66      "shape": "sphere", 
  67      "faces": None, 
  68      "draw_descendants": 1, 
  69      "ymargin": 0 
  70  } 
71 72 -class TreeImageProperties:
73 - def __init__(self):
74 self.force_topology = False 75 self.draw_branch_length = False 76 self.align_leaf_faces = False 77 self.orientation = 0 78 self.style = 0 # 0 = Normal, 1 = Diagonal lines 79 self.general_font_type = "Verdana" 80 self.branch_length_font_color = "#222" 81 self.branch_length_font_size = 6 82 self.branch_support_font_color = "red" 83 self.branch_support_font_size = 9 84 self.tree_width = 200 # This is used to scale 85 # the tree branches 86 self.min_branch_separation = 1 87 self.search_node_bg = "#cccccc" 88 self.search_node_fg = "#ff0000"
89
90 -def logger(level,*msg):
91 """ Just to manage how to print messages """ 92 msg = map(str, msg) 93 # critrical error 94 if level == -1: 95 print >>stderr,"* Error: ", " ".join(msg) 96 # warning 97 elif level == 0: 98 print >>stderr,"* Warning: ", " ".join(msg) 99 # info 100 elif level == 1: 101 if DEBUG == 1: 102 print >>stdout,"* Info: ", " ".join(msg) 103 # debug 104 elif level == 2: 105 if DEBUG == 1: 106 print >>stderr,"* Debug: ", " ".join(msg) 107 else: 108 print >>stderr,"* ", " ".join(msg) 109 return
110
111 -def show_tree(t, style=None, img_properties=None):
112 """ Interactively shows a tree.""" 113 global _QApp 114 115 if not style: 116 if t.__class__ == PhyloTree: 117 style = "phylogeny" 118 elif t.__class__ == ClusterTree: 119 style = "large" 120 else: 121 style = "basic" 122 123 if not _QApp: 124 _QApp = QtGui.QApplication(["ETE"]) 125 126 scene = _TreeScene() 127 mainapp = _MainApp(scene) 128 129 130 if not img_properties: 131 img_properties = TreeImageProperties() 132 scene.initialize_tree_scene(t, style, \ 133 tree_properties=img_properties) 134 scene.draw() 135 136 mainapp.show() 137 _QApp.exec_()
138
139 -def render_tree(t, imgName, w=None, h=None, style=None, \ 140 img_properties = None, header=None):
141 """ Render tree image into a PNG file.""" 142 143 if not style: 144 if t.__class__ == PhyloTree: 145 style = "phylogeny" 146 elif t.__class__ == ClusterTree: 147 style = "large" 148 else: 149 style = "basic" 150 151 152 global _QApp 153 if not _QApp: 154 _QApp = QtGui.QApplication(["ETE"]) 155 156 scene = _TreeScene() 157 if not img_properties: 158 img_properties = TreeImageProperties() 159 scene.initialize_tree_scene(t, style, 160 tree_properties=img_properties) 161 scene.draw() 162 scene.save(imgName, w=w, h=h, header=header)
163
164 165 # ################# 166 # NON PUBLIC STUFF 167 # ################# 168 169 170 171 172 -class NewickDialog(QtGui.QDialog):
173 - def __init__(self, node, *args):
174 QtGui.QDialog.__init__(self, *args) 175 self.node = node
176
177 - def update_newick(self):
178 f= int(self._conf.nwFormat.currentText()) 179 self._conf.features_list.selectAll() 180 if self._conf.useAllFeatures.isChecked(): 181 features = [] 182 elif self._conf.features_list.count()==0: 183 features = None 184 else: 185 features = set() 186 for i in self._conf.features_list.selectedItems(): 187 features.add(str(i.text())) 188 189 nw = self.node.write(format=f, features=features) 190 self._conf.newickBox.setText(nw)
191
192 - def add_feature(self):
193 aName = str(self._conf.attrName.text()).strip() 194 if aName != '': 195 self._conf.features_list.addItem( aName) 196 self.update_newick()
197 - def del_feature(self):
198 r = self._conf.features_list.currentRow() 199 self._conf.features_list.takeItem(r) 200 self.update_newick()
201
202 - def set_custom_features(self):
203 state = self._conf.useAllFeatures.isChecked() 204 self._conf.features_list.setDisabled(state) 205 self._conf.attrName.setDisabled(state) 206 self.update_newick()
207
208 -class _MainApp(QtGui.QMainWindow):
209 - def __init__(self, scene, *args):
210 QtGui.QMainWindow.__init__(self, *args) 211 self.scene = scene 212 self.view = _MainView(scene) 213 scene.view = self.view 214 _mainwindow.Ui_MainWindow().setupUi(self) 215 scene.view = self.view 216 self.view.centerOn(0,0) 217 splitter = QtGui.QSplitter() 218 splitter.addWidget(self.view) 219 splitter.addWidget(scene.propertiesTable) 220 self.setCentralWidget(splitter) 221 222 # I create a single dialog to keep the last search options 223 self.searchDialog = QtGui.QDialog() 224 # Don't know if this is the best way to set up the dialog and 225 # its variables 226 self.searchDialog._conf = _search_dialog.Ui_Dialog() 227 self.searchDialog._conf.setupUi(self.searchDialog)
228 229 230 @QtCore.pyqtSignature("")
231 - def on_actionETE_triggered(self):
232 try: 233 __VERSION__ 234 except: 235 __VERSION__= "developmnet branch" 236 237 d = QtGui.QDialog() 238 d._conf = _about.Ui_About() 239 d._conf.setupUi(d) 240 d._conf.version.setText("Version: %s" %__VERSION__) 241 d._conf.version.setAlignment(QtCore.Qt.AlignHCenter) 242 d.exec_()
243 244 @QtCore.pyqtSignature("")
245 - def on_actionZoomOut_triggered(self):
246 self.view.safe_scale(0.8,0.8)
247 248 @QtCore.pyqtSignature("")
249 - def on_actionZoomIn_triggered(self):
250 self.view.safe_scale(1.2,1.2)
251 252 @QtCore.pyqtSignature("")
253 - def on_actionZoomInX_triggered(self):
254 self.scene.props.tree_width += 20 255 self.scene.draw()
256 257 @QtCore.pyqtSignature("")
259 if self.scene.props.tree_width >20: 260 self.scene.props.tree_width -= 20 261 self.scene.draw()
262 263 @QtCore.pyqtSignature("")
264 - def on_actionZoomInY_triggered(self):
265 if self.scene.props.min_branch_separation < \ 266 self.scene.min_real_branch_separation: 267 self.scene.props.min_branch_separation = \ 268 self.scene.min_real_branch_separation 269 self.scene.props.min_branch_separation += 5 270 self.scene.draw()
271 272 @QtCore.pyqtSignature("")
274 if self.scene.props.min_branch_separation > 5: 275 self.scene.props.min_branch_separation -= 5 276 self.scene.draw()
277 278 @QtCore.pyqtSignature("")
280 self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
281 282 @QtCore.pyqtSignature("")
284 if self.scene.highlighter.isVisible(): 285 R = self.scene.highlighter.rect() 286 else: 287 R = self.scene.selector.rect() 288 if R.width()>0 and R.height()>0: 289 290 291 self.view.fitInView(R.x(), R.y(), R.width(),\ 292 R.height(), QtCore.Qt.KeepAspectRatio)
293 294 @QtCore.pyqtSignature("")
296 ok = self.searchDialog.exec_() 297 if ok: 298 setup = self.searchDialog._conf 299 mType = setup.attrType.currentIndex() 300 aName = str(setup.attrName.text()) 301 if mType >= 2 and mType <=6: 302 try: 303 aValue = float(setup.attrValue.text()) 304 except ValueError: 305 QtGui.QMessageBox.information(self, "!",\ 306 "A numeric value is expected") 307 return 308 elif mType == 7: 309 aValue = re.compile(str(setup.attrValue.text())) 310 elif mType == 0 or mType == 1: 311 aValue = str(setup.attrValue.text()) 312 313 if mType == 0 or mType == 2: 314 cmpFn = lambda x,y: x == y 315 elif mType == 1: 316 cmpFn = lambda x,y: y in x 317 elif mType == 3: 318 cmpFn = lambda x,y: x >= y 319 elif mType == 4: 320 cmpFn = lambda x,y: x > y 321 elif mType == 5: 322 cmpFn = lambda x,y: x <= y 323 elif mType == 6: 324 cmpFn = lambda x,y: x < y 325 elif mType == 7: 326 cmpFn = lambda x,y: re.search(y, x) 327 328 for n in self.scene.startNode.traverse(): 329 if setup.leaves_only.isChecked() and not n.is_leaf(): 330 continue 331 if hasattr(n, aName) \ 332 and cmpFn(getattr(n, aName), aValue ): 333 self.scene.highlight_node(n)
334 335 @QtCore.pyqtSignature("")
337 # This could be much more efficient 338 for n in self.scene._highlighted_nodes.keys(): 339 self.scene.unhighlight_node(n)
340 341 @QtCore.pyqtSignature("")
343 self.scene.props.draw_branch_length ^= True 344 self.scene.draw()
345 346 @QtCore.pyqtSignature("")
348 self.scene.props.force_topology ^= True 349 self.scene.draw()
350 351 @QtCore.pyqtSignature("")
353 d = NewickDialog(self.scene.startNode) 354 d._conf = _show_newick.Ui_Newick() 355 d._conf.setupUi(d) 356 d.update_newick() 357 d.exec_()
358 359 @QtCore.pyqtSignature("")
361 self.scene.props.orientation ^= 1 362 self.scene.draw()
363 364 @QtCore.pyqtSignature("")
366 self.scene.props.style = 0 367 self.scene.draw()
368 369 @QtCore.pyqtSignature("")
371 self.scene.props.style = 1 372 self.scene.draw()
373 374 @QtCore.pyqtSignature("")
375 - def on_actionOpen_triggered(self):
376 377 d = QtGui.QFileDialog() 378 d._conf = _open_newick.Ui_OpenNewick() 379 d._conf.setupUi(d) 380 d.exec_() 381 return 382 383 384 fname = QtGui.QFileDialog.getOpenFileName(self ,"Open File", 385 "/home", 386 ) 387 388 389 try: 390 t = Tree(str(fname)) 391 except Exception, e: 392 print e 393 else: 394 self.scene.initialize_tree_scene(t, "basic", TreeImageProperties()) 395 self.scene.draw()
396 397 @QtCore.pyqtSignature("")
399 fname = QtGui.QFileDialog.getSaveFileName(self ,"Save File", 400 "/home", 401 "Newick (*.nh *.nhx *.nw )") 402 nw = self.scene.startNode.write() 403 try: 404 OUT = open(fname,"w") 405 except Exception, e: 406 print e 407 else: 408 OUT.write(nw) 409 OUT.close()
410 411 @QtCore.pyqtSignature("")
413 F = QtGui.QFileDialog(self) 414 if F.exec_(): 415 imgName = str(F.selectedFiles()[0]) 416 if not imgName.endswith(".pdf"): 417 imgName += ".pdf" 418 self.scene.save(imgName)
419 420 421 @QtCore.pyqtSignature("")
423 if not self.scene.selector.isVisible(): 424 return QtGui.QMessageBox.information(self, "!",\ 425 "You must select a region first") 426 427 F = QtGui.QFileDialog(self) 428 if F.exec_(): 429 imgName = str(F.selectedFiles()[0]) 430 if not imgName.endswith(".pdf"): 431 imgName += ".pdf" 432 self.scene.save(imgName, take_region=True)
433 434 435 @QtCore.pyqtSignature("")
437 text,ok = QtGui.QInputDialog.getText(self,\ 438 "Paste Newick",\ 439 "Newick:") 440 if ok: 441 try: 442 t = Tree(str(text)) 443 except Exception,e: 444 print e 445 else: 446 self.scene.initialize_tree_scene(t,"basic", TreeImageProperties()) 447 self.scene.draw()
448
449 450 # This function should be reviewed. Probably there are better ways to 451 # do de same, or at least less messy ways... So far this is what I 452 # have :) 453 -class _TableItem(QtGui.QItemDelegate):
454 - def __init__(self, parent=None):
455 QtGui.QItemDelegate.__init__(self, parent) 456 self.propdialog = parent
457
458 - def paint(self, painter, option, index):
459 self.propdialog.tableView.setRowHeight(index.row(), 18) 460 QtGui.QItemDelegate.paint(self, painter, option, index)
461
462 - def createEditor(self, parent, option, index):
463 # Edit only values, not property names 464 if index.column() != 1: 465 logger(2, "NOT EDITABLE COLUMN") 466 return None 467 468 originalValue = index.model().data(index) 469 if not self.isSupportedType(originalValue.type()): 470 logger(2, "data type not suppoerted for editting") 471 return None 472 473 if re.search("^#[0-9ABCDEFabcdef]{6}$",str(originalValue.toString())): 474 origc = QtGui.QColor(str(originalValue.toString())) 475 color = QtGui.QColorDialog.getColor(origc) 476 if color.isValid(): 477 self.propdialog._edited_indexes.add( (index.row(), index.column()) ) 478 index.model().setData(index,QtCore.QVariant(color.name())) 479 self.propdialog.apply_changes() 480 481 return None 482 else: 483 editField = QtGui.QLineEdit(parent) 484 editField.setFrame(False) 485 validator = QtGui.QRegExpValidator(QtCore.QRegExp(".+"), editField) 486 editField.setValidator(validator) 487 self.connect(editField, QtCore.SIGNAL("returnPressed()"), 488 self.commitAndCloseEditor) 489 self.connect(editField, QtCore.SIGNAL("returnPressed()"), 490 self.propdialog.apply_changes) 491 self.propdialog._edited_indexes.add( (index.row(), index.column()) ) 492 return editField
493
494 - def setEditorData(self, editor, index):
495 value = index.model().data(index) 496 if editor is not None: 497 editor.setText(self.displayText(value))
498
499 - def isSupportedType(valueType):
500 return True
501 502 isSupportedType = staticmethod(isSupportedType)
503 - def displayText(self,value):
504 return value.toString()
505
506 - def commitAndCloseEditor(self):
507 editor = self.sender() 508 self.emit(QtCore.SIGNAL("commitData(QWidget *)"), editor) 509 self.emit(QtCore.SIGNAL("closeEditor(QWidget *)"), editor)
510
511 -class _PropModeChooser(QtGui.QWidget):
512 - def __init__(self,scene, *args):
513 QtGui.QWidget.__init__(self,*args)
514
515 -class _PropertiesDialog(QtGui.QWidget):
516 - def __init__(self,scene, *args):
517 QtGui.QWidget.__init__(self,*args) 518 self.scene = scene 519 self._mode = 0 520 self.layout = QtGui.QVBoxLayout() 521 self.tableView = QtGui.QTableView() 522 self.tableView.verticalHeader().setVisible(False) 523 self.tableView.horizontalHeader().setVisible(False) 524 self.tableView.setVerticalHeader(None) 525 self.layout.addWidget(self.tableView) 526 self.setLayout(self.layout) 527 self.tableView.setGeometry ( 0, 0, 200,200 )
528 529
530 - def update_properties(self, node):
531 self.node = node 532 self._edited_indexes = set([]) 533 self._style_indexes = set([]) 534 self._prop_indexes = set([]) 535 536 self.get_prop_table(node)
537
538 - def get_props_in_nodes(self, nodes):
539 # sorts properties and faces of selected nodes 540 self.prop2nodes = {} 541 self.prop2values = {} 542 self.style2nodes = {} 543 self.style2values = {} 544 545 for n in nodes: 546 for pname in n.features: 547 pvalue = getattr(n,pname) 548 if type(pvalue) == int or \ 549 type(pvalue) == float or \ 550 type(pvalue) == str : 551 self.prop2nodes.setdefault(pname,[]).append(n) 552 self.prop2values.setdefault(pname,[]).append(pvalue) 553 554 for pname,pvalue in n.img_style.iteritems(): 555 if type(pvalue) == int or \ 556 type(pvalue) == float or \ 557 type(pvalue) == str : 558 self.style2nodes.setdefault(pname,[]).append(n) 559 self.style2values.setdefault(pname,[]).append(pvalue)
560
561 - def get_prop_table(self, node):
562 if self._mode == 0: # node 563 self.get_props_in_nodes([node]) 564 elif self._mode == 1: # childs 565 self.get_props_in_nodes(node.get_leaves()) 566 elif self._mode == 2: # partition 567 self.get_props_in_nodes([node]+node.get_descendants()) 568 569 total_props = len(self.prop2nodes) + len(self.style2nodes.keys()) 570 self.model = QtGui.QStandardItemModel(total_props, 2) 571 # self.tableView = QtGui.QTableView() 572 self.tableView.setModel(self.model) 573 self.delegate = _TableItem(self) 574 self.tableView.setItemDelegate(self.delegate) 575 576 row = 0 577 for name,nodes in self.prop2nodes.iteritems(): 578 value= getattr(nodes[0],name) 579 580 index1 = self.model.index(row, 0, QtCore.QModelIndex()) 581 index2 = self.model.index(row, 1, QtCore.QModelIndex()) 582 583 self.model.setData(index1, QtCore.QVariant(name)) 584 v = QtCore.QVariant(value) 585 self.model.setData(index2, v) 586 587 self._prop_indexes.add( (index1, index2) ) 588 row +=1 589 590 for name in self.style2nodes.iterkeys(): 591 value= self.style2values[name][0] 592 593 index1 = self.model.index(row, 0, QtCore.QModelIndex()) 594 index2 = self.model.index(row, 1, QtCore.QModelIndex()) 595 596 self.model.setData(index1, QtCore.QVariant(name)) 597 v = QtCore.QVariant(value) 598 self.model.setData(index2, v) 599 # Creates a variant element 600 self._style_indexes.add( (index1, index2) ) 601 row +=1 602 return
603
604 - def apply_changes(self):
605 # Apply changes on styles 606 for i1, i2 in self._style_indexes: 607 if (i2.row(), i2.column()) not in self._edited_indexes: 608 continue 609 name = str(self.model.data(i1).toString()) 610 value = str(self.model.data(i2).toString()) 611 for n in self.style2nodes[name]: 612 typedvalue = type(n.img_style[name])(value) 613 try: 614 n.img_style[name] = typedvalue 615 except: 616 logger(-1, "Wrong format for attribute:", name) 617 break 618 619 # Apply changes on properties 620 for i1, i2 in self._prop_indexes: 621 if (i2.row(), i2.column()) not in self._edited_indexes: 622 continue 623 name = str(self.model.data(i1).toString()) 624 value = str(self.model.data(i2).toString()) 625 for n in self.prop2nodes[name]: 626 try: 627 setattr(n, name, type(getattr(n,name))(value)) 628 except Exception, e: 629 logger(-1, "Wrong format for attribute:", name) 630 print e 631 break 632 self.update_properties(self.node) 633 self.scene.draw() 634 return
635
636 -class _TextItem(QtGui.QGraphicsSimpleTextItem):
637 """ Manage faces on Scene"""
638 - def __init__(self,face,node,*args):
639 QtGui.QGraphicsSimpleTextItem.__init__(self,*args) 640 self.node = node 641 self.face = face
642
643 - def hoverEnterEvent (self,e):
644 # if self.scene().selector.isVisible(): 645 # self.scene().mouseMoveEvent(e) 646 647 R = self.node.fullRegion.getRect() 648 if self.scene().props.orientation == 0: 649 width = self.scene().i_width-self.node._x 650 self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3])) 651 elif self.scene().props.orientation == 1: 652 width = self.node._x-self.scene().i_width 653 self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3])) 654 self.scene().highlighter.setVisible(True)
655
656 - def hoverLeaveEvent (self,e):
657 self.scene().highlighter.setVisible(False)
658
659 - def mousePressEvent(self,e):
660 pass
661
662 - def mouseReleaseEvent(self,e):
663 logger(2,"released in scene", e.button) 664 if e.button() == QtCore.Qt.RightButton: 665 self.node._QtItem_.showActionPopup() 666 elif e.button() == QtCore.Qt.LeftButton: 667 self.scene().propertiesTable.update_properties(self.node)
668
669 670 -class _FaceItem(QtGui.QGraphicsPixmapItem):
671 """ Manage faces on Scene"""
672 - def __init__(self,face,node,*args):
673 QtGui.QGraphicsPixmapItem.__init__(self,*args) 674 self.node = node 675 self.face = face
676
677 - def hoverEnterEvent (self,e):
678 # if self.scene().selector.isVisible(): 679 # self.scene().mouseMoveEvent(e) 680 681 R = self.node.fullRegion.getRect() 682 if self.scene().props.orientation == 0: 683 width = self.scene().i_width-self.node._x 684 self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3])) 685 elif self.scene().props.orientation == 1: 686 width = self.node._x-self.scene().i_width 687 self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3])) 688 self.scene().highlighter.setVisible(True)
689
690 - def hoverLeaveEvent (self,e):
691 self.scene().highlighter.setVisible(False)
692
693 - def mousePressEvent(self,e):
694 pass
695
696 - def mouseReleaseEvent(self,e):
697 logger(2,"released in scene", e.button) 698 if e.button() == QtCore.Qt.RightButton: 699 self.node._QtItem_.showActionPopup() 700 elif e.button() == QtCore.Qt.LeftButton: 701 self.scene().propertiesTable.update_properties(self.node)
702
703 704 -class _NodeItem(QtGui.QGraphicsRectItem):
705 - def __init__(self,node):
706 self.node = node 707 self.radius = node.img_style["size"]/2 708 QtGui.QGraphicsRectItem.__init__(self,0,0,self.radius*2,self.radius*2)
709
710 - def paint(self, p, option, widget):
711 if self.node.img_style["shape"] == "sphere": 712 r = self.radius 713 gradient = QtGui.QRadialGradient(r, r, r,(r*2)/3,(r*2)/3) 714 gradient.setColorAt(0.05, QtCore.Qt.white); 715 gradient.setColorAt(0.9, QtGui.QColor(self.node.img_style["fgcolor"])); 716 p.setBrush(QtGui.QBrush(gradient)) 717 p.setPen(QtCore.Qt.NoPen) 718 p.drawEllipse(self.rect()) 719 elif self.node.img_style["shape"] == "square": 720 p.fillRect(self.rect(),QtGui.QBrush(QtGui.QColor(self.node.img_style["fgcolor"]))) 721 elif self.node.img_style["shape"] == "circle": 722 p.setBrush(QtGui.QBrush(QtGui.QColor(self.node.img_style["fgcolor"]))) 723 p.setPen(QtGui.QPen(QtGui.QColor(self.node.img_style["fgcolor"]))) 724 p.drawEllipse(self.rect())
725 726
727 - def hoverEnterEvent (self,e):
728 R = self.node.fullRegion.getRect() 729 if self.scene().props.orientation == 0: 730 width = self.scene().i_width-self.node._x 731 self.scene().highlighter.setRect(QtCore.QRectF(self.node._x,self.node._y,width,R[3])) 732 elif self.scene().props.orientation == 1: 733 width = self.node._x-self.scene().i_width 734 self.scene().highlighter.setRect(QtCore.QRectF(width,self.node._y,width,R[3])) 735 self.scene().highlighter.setVisible(True)
736
737 - def hoverLeaveEvent (self,e):
738 self.scene().highlighter.setVisible(False)
739
740 - def mousePressEvent(self,e):
741 logger(2,"Pressed in scene", e.button)
742
743 - def mouseReleaseEvent(self,e):
744 logger(2,"released in scene", e.button) 745 if e.button() == QtCore.Qt.RightButton: 746 self.showActionPopup() 747 elif e.button() == QtCore.Qt.LeftButton: 748 self.scene().propertiesTable.update_properties(self.node)
749 750
751 - def showActionPopup(self):
752 contextMenu = QtGui.QMenu() 753 if self.node.collapsed: 754 contextMenu.addAction( "Expand" , self.toggle_collapse) 755 else: 756 contextMenu.addAction( "Collapse" , self.toggle_collapse) 757 758 contextMenu.addAction( "Set as outgroup" , self.set_as_outgroup) 759 contextMenu.addAction( "Swap branches" , self.swap_branches) 760 contextMenu.addAction( "Delete node" , self.delete_node) 761 contextMenu.addAction( "Delete partition" , self.detach_node) 762 contextMenu.addAction( "Add childs" , self.add_childs) 763 contextMenu.addAction( "Populate partition" , self.populate_partition) 764 if self.node.up is not None and\ 765 self.scene().startNode == self.node: 766 contextMenu.addAction( "Back to parent", self.back_to_parent_node) 767 else: 768 contextMenu.addAction( "Extract" , self.set_start_node) 769 770 if self.scene().buffer_node: 771 contextMenu.addAction( "Paste partition" , self.paste_partition) 772 773 contextMenu.addAction( "Cut partition" , self.cut_partition) 774 contextMenu.addAction( "Show newick" , self.show_newick) 775 contextMenu.exec_(QtGui.QCursor.pos())
776
777 - def show_newick(self):
778 d = NewickDialog(self.node) 779 d._conf = _show_newick.Ui_Newick() 780 d._conf.setupUi(d) 781 d.update_newick() 782 d.exec_() 783 return False
784
785 - def delete_node(self):
786 self.node.delete() 787 self.scene().draw()
788
789 - def detach_node(self):
790 self.node.detach() 791 self.scene().draw()
792
793 - def swap_branches(self):
794 self.node.swap_childs() 795 self.scene().draw()
796
797 - def add_childs(self):
798 n,ok = QtGui.QInputDialog.getInteger(None,"Add childs","Number of childs to add:",1,1) 799 if ok: 800 for i in xrange(n): 801 ch = self.node.add_child() 802 self.scene().set_style_from(self.scene().startNode,self.scene().layout_func)
803
804 - def void(self):
805 logger(0,"Not implemented yet") 806 return True
807
808 - def set_as_outgroup(self):
809 self.scene().startNode.set_outgroup(self.node) 810 self.scene().set_style_from(self.scene().startNode, self.scene().layout_func) 811 self.scene().draw()
812
813 - def toggle_collapse(self):
814 self.node.collapsed ^= True 815 self.scene().draw()
816
817 - def cut_partition(self):
818 self.scene().buffer_node = self.node 819 self.node.detach() 820 self.scene().draw()
821
822 - def paste_partition(self):
823 if self.scene().buffer_node: 824 self.node.add_child(self.scene().buffer_node) 825 self.scene().set_style_from(self.scene().startNode,self.scene().layout_func) 826 self.scene().buffer_node= None 827 self.scene().draw()
828
829 - def populate_partition(self):
830 n, ok = QtGui.QInputDialog.getInteger(None,"Populate partition","Number of nodes to add:",2,1) 831 if ok: 832 self.node.populate(n) 833 self.scene().set_style_from(self.scene().startNode,self.scene().layout_func) 834 self.scene().draw()
835
836 - def set_start_node(self):
837 self.scene().startNode = self.node 838 self.scene().draw()
839
840 - def back_to_parent_node(self):
841 self.scene().startNode = self.node.up 842 self.scene().draw()
843
844 845 -class _SelectorItem(QtGui.QGraphicsRectItem):
846 - def __init__(self):
847 self.Color = QtGui.QColor("blue") 848 self._active = False 849 QtGui.QGraphicsRectItem.__init__(self,0,0,0,0)
850
851 - def paint(self, p, option, widget):
852 p.setPen(self.Color) 853 p.drawRect(self.rect().x(),self.rect().y(),self.rect().width(),self.rect().height()) 854 return 855 # Draw info text 856 font = QtGui.QFont("Arial",13) 857 text = "%d selected." % len(self.get_selected_nodes()) 858 textR = QtGui.QFontMetrics(font).boundingRect(text) 859 if self.rect().width() > textR.width() and \ 860 self.rect().height() > textR.height()/2 and 0: # OJO !!!! 861 p.setPen(QtGui.QPen(self.Color)) 862 p.setFont(QtGui.QFont("Arial",13)) 863 p.drawText(self.rect().bottomLeft().x(),self.rect().bottomLeft().y(),text)
864
865 - def get_selected_nodes(self):
866 selPath = QtGui.QPainterPath() 867 selPath.addRect(self.rect()) 868 self.scene().setSelectionArea(selPath) 869 return [i.node for i in self.scene().selectedItems()]
870
871 - def setActive(self,bool):
872 self._active = bool
873
874 - def isActive(self):
875 return self._active
876
877 878 -class _HighlighterItem(QtGui.QGraphicsRectItem):
879 - def __init__(self):
880 self.Color = QtGui.QColor("red") 881 self._active = False 882 QtGui.QGraphicsRectItem.__init__(self,0,0,0,0)
883
884 - def paint(self, p, option, widget):
885 p.setPen(self.Color) 886 p.drawRect(self.rect().x(),self.rect().y(),self.rect().width(),self.rect().height()) 887 return
888
889 890 891 -class _MainView(QtGui.QGraphicsView):
892 - def __init__(self,*args):
893 QtGui.QGraphicsView.__init__(self,*args) 894 895 #self.setViewportUpdateMode(QtGui.QGraphicsView.FullViewportUpdate) 896 #self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) 897 #self.setOptimizationFlag (QtGui.QGraphicsView.DontAdjustForAntialiasing) 898 #self.setOptimizationFlag (QtGui.QGraphicsView.DontSavePainterState) 899 900 if USE_GL: 901 F = QtOpenGL.QGLFormat() 902 F.setSampleBuffers(True) 903 print F.sampleBuffers() 904 self.setViewport(QtOpenGL.QGLWidget(F)) 905 self.setRenderHints(QtGui.QPainter.Antialiasing) 906 else: 907 self.setRenderHints(QtGui.QPainter.Antialiasing or QtGui.QPainter.SmoothPixmapTransform ) 908 self.setViewportUpdateMode(QtGui.QGraphicsView.SmartViewportUpdate)
909 910
911 - def resizeEvent(self, e):
912 QtGui.QGraphicsView.resizeEvent(self, e) 913 logger(2, "RESIZE VIEW!!")
914 915
916 - def safe_scale(self, xfactor, yfactor):
917 self.setTransformationAnchor(self.AnchorUnderMouse) 918 919 xscale = self.matrix().m11() 920 yscale = self.matrix().m22() 921 srect = self.sceneRect() 922 923 if (xfactor>1 and xscale>200000) or \ 924 (yfactor>1 and yscale>200000): 925 QtGui.QMessageBox.information(self, "!",\ 926 "Hey! I'm not an electron microscope?") 927 return 928 929 # Do not allow to reduce scale to a value producing height or with smaller than 20 pixels 930 # No restrictions to zoomin 931 if (yfactor<1 and srect.width() * yscale < 20): 932 pass 933 elif (xfactor<1 and srect.width() * xscale < 20): 934 pass 935 else: 936 self.scale(xfactor, yfactor)
937 938
939 - def wheelEvent(self,e):
940 factor = (-e.delta() / 360.0) 941 942 # Ctrl+Shift -> Zoom in X 943 if (e.modifiers() & QtCore.Qt.ControlModifier) and (e.modifiers() & QtCore.Qt.ShiftModifier): 944 self.safe_scale(1+factor, 1) 945 946 # Ctrl+Alt -> Zomm in Y 947 elif (e.modifiers() & QtCore.Qt.ControlModifier) and (e.modifiers() & QtCore.Qt.AltModifier): 948 self.safe_scale(1,1+factor) 949 950 # Ctrl -> Zoom X,Y 951 elif e.modifiers() & QtCore.Qt.ControlModifier: 952 self.safe_scale(1-factor, 1-factor) 953 954 # Shift -> Horizontal scroll 955 elif e.modifiers() & QtCore.Qt.ShiftModifier: 956 if e.delta()>0: 957 self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()-20 ) 958 else: 959 self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()+20 ) 960 # No modifiers -> Vertival scroll 961 else: 962 if e.delta()>0: 963 self.verticalScrollBar().setValue(self.verticalScrollBar().value()-20 ) 964 else: 965 self.verticalScrollBar().setValue(self.verticalScrollBar().value()+20 )
966
967 - def keyPressEvent(self,e):
968 key = e.key() 969 logger(1, "****** Pressed key: ", key, QtCore.Qt.LeftArrow) 970 if key == QtCore.Qt.Key_Left: 971 self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()-20 ) 972 self.update() 973 elif key == QtCore.Qt.Key_Right: 974 self.horizontalScrollBar().setValue(self.horizontalScrollBar().value()+20 ) 975 self.update() 976 elif key == QtCore.Qt.Key_Up: 977 self.verticalScrollBar().setValue(self.verticalScrollBar().value()-20 ) 978 self.update() 979 elif key == QtCore.Qt.Key_Down: 980 self.verticalScrollBar().setValue(self.verticalScrollBar().value()+20 ) 981 self.update() 982 QtGui.QGraphicsView.keyPressEvent(self,e)
983
984 -class _TreeScene(QtGui.QGraphicsScene):
985 - def __init__(self, rootnode=None, style=None, *args):
986 QtGui.QGraphicsScene.__init__(self,*args) 987 988 self.view = None 989 # Config variables 990 self.buffer_node = None # Used to copy and paste 991 self.layout_func = None # Layout function 992 self.startNode = rootnode # Node to start drawing 993 self.scale = 0 # Tree branch scale used to draw 994 self.max_w_aligned_face = 0 # Stores the max width of aligned faces 995 self.min_real_branch_separation = 0 996 self.selectors = [] 997 self._highlighted_nodes = {} 998 999 # Qt items 1000 self.selector = None 1001 self.mainItem = None # Qt Item which is parent of all other items 1002 self.propertiesTable = _PropertiesDialog(self)
1003
1004 - def initialize_tree_scene(self, tree, style, tree_properties):
1005 self.tree = tree # Pointer to original tree 1006 self.startNode = tree # Node to start drawing 1007 self.max_w_aligned_face = 0 # Stores the max width of aligned faces 1008 1009 # Load image attributes 1010 self.props = tree_properties 1011 1012 # Validates layout function 1013 if type(style) == types.FunctionType or\ 1014 type(style) == types.MethodType: 1015 self.layout_func = style 1016 else: 1017 try: 1018 self.layout_func = getattr(layouts,style) 1019 except: 1020 raise ValueError, "Required layout is not a function pointer nor a valid layout name." 1021 1022 # Set the scene background 1023 self.setBackgroundBrush(QtGui.QColor("white")) 1024 1025 # Set nodes style 1026 self.set_style_from(self.startNode,self.layout_func) 1027 1028 self.propertiesTable.update_properties(self.startNode)
1029
1030 - def highlight_node(self, n):
1031 self.unhighlight_node(n) 1032 r = QtGui.QGraphicsRectItem(self.mainItem) 1033 self._highlighted_nodes[n] = r 1034 1035 R = n.fullRegion.getRect() 1036 width = self.i_width-n._x 1037 r.setRect(QtCore.QRectF(n._x,n._y,width,R[3])) 1038 1039 #r.setRect(0,0, n.fullRegion.width(), n.fullRegion.height()) 1040 1041 #r.setPos(n.scene_pos) 1042 # Don't know yet why do I have to add 2 pixels :/ 1043 #r.moveBy(0,0) 1044 r.setZValue(-1) 1045 r.setPen(QtGui.QColor(self.props.search_node_fg)) 1046 r.setBrush(QtGui.QColor(self.props.search_node_bg))
1047 1048 # self.view.horizontalScrollBar().setValue(n._x) 1049 # self.view.verticalScrollBar().setValue(n._y) 1050
1051 - def unhighlight_node(self, n):
1052 if n in self._highlighted_nodes and \ 1053 self._highlighted_nodes[n] is not None: 1054 self.removeItem(self._highlighted_nodes[n]) 1055 del self._highlighted_nodes[n]
1056 1057
1058 - def mousePressEvent(self,e):
1059 logger(2, "Press en view") 1060 self.selector.setRect(e.scenePos().x(),e.scenePos().y(),0,0) 1061 self.selector.startPoint = QtCore.QPointF(e.scenePos().x(),e.scenePos().y()) 1062 self.selector.setActive(True) 1063 self.selector.setVisible(True) 1064 QtGui.QGraphicsScene.mousePressEvent(self,e)
1065
1066 - def mouseReleaseEvent(self,e):
1067 logger(2, "Release en view") 1068 curr_pos = e.scenePos() 1069 x = min(self.selector.startPoint.x(),curr_pos.x()) 1070 y = min(self.selector.startPoint.y(),curr_pos.y()) 1071 w = max(self.selector.startPoint.x(),curr_pos.x()) - x 1072 h = max(self.selector.startPoint.y(),curr_pos.y()) - y 1073 if self.selector.startPoint == curr_pos: 1074 self.selector.setVisible(False) 1075 else: 1076 logger(2, self.selector.get_selected_nodes()) 1077 self.selector.setActive(False) 1078 QtGui.QGraphicsScene.mouseReleaseEvent(self,e)
1079
1080 - def mouseMoveEvent(self,e):
1081 1082 curr_pos = e.scenePos() 1083 if self.selector.isActive(): 1084 x = min(self.selector.startPoint.x(),curr_pos.x()) 1085 y = min(self.selector.startPoint.y(),curr_pos.y()) 1086 w = max(self.selector.startPoint.x(),curr_pos.x()) - x 1087 h = max(self.selector.startPoint.y(),curr_pos.y()) - y 1088 self.selector.setRect(x,y,w,h) 1089 QtGui.QGraphicsScene.mouseMoveEvent(self, e)
1090
1091 - def mouseDoubleClickEvent(self,e):
1092 logger(2, "Double click") 1093 QtGui.QGraphicsScene.mouseDoubleClickEvent(self,e)
1094
1095 - def save(self, imgName, w=None, h=None, header=None, \ 1096 dpi=150, take_region=False):
1097 ext = imgName.split(".")[-1].upper() 1098 1099 1100 root = self.startNode 1101 aspect_ratio = root.fullRegion.height() / root.fullRegion.width() 1102 1103 # auto adjust size 1104 if w is None and h is None: 1105 w = dpi * 6.4 1106 h = w * aspect_ratio 1107 if h>dpi * 11: 1108 h = dpi * 11 1109 w = h / aspect_ratio 1110 1111 elif h is None: 1112 h = w * aspect_ratio 1113 elif w is None: 1114 w = h / aspect_ratio 1115 1116 if ext == "PDF" or ext == "PS": 1117 format = QPrinter.PostScriptFormat if ext == "PS" else QPrinter.PdfFormat 1118 printer = QPrinter(QPrinter.HighResolution) 1119 printer.setResolution(dpi) 1120 printer.setOutputFormat(format) 1121 printer.setPageSize(QPrinter.A4) 1122 1123 pageTopLeft = printer.pageRect().topLeft() 1124 paperTopLeft = printer.paperRect().topLeft() 1125 # For PS -> problems with margins 1126 # print paperTopLeft.x(), paperTopLeft.y() 1127 # print pageTopLeft.x(), pageTopLeft.y() 1128 # print printer.paperRect().height(), printer.pageRect().height() 1129 topleft = pageTopLeft - paperTopLeft 1130 1131 printer.setFullPage(True); 1132 printer.setOutputFileName(imgName); 1133 pp = QtGui.QPainter(printer) 1134 if header: 1135 pp.setFont(QtGui.QFont("Verdana",12)) 1136 pp.drawText(topleft.x(),20, header) 1137 targetRect = QtCore.QRectF(topleft.x(), 20 + (topleft.y()*2), w, h) 1138 else: 1139 targetRect = QtCore.QRectF(topleft.x(), topleft.y()*2, w, h) 1140 1141 if take_region: 1142 self.selector.setVisible(False) 1143 self.render(pp, targetRect, self.selector.rect()) 1144 self.selector.setVisible(True) 1145 else: 1146 self.render(pp, targetRect, self.sceneRect()) 1147 pp.end() 1148 return 1149 else: 1150 targetRect = QtCore.QRectF(0, 0, w, h) 1151 ii= QtGui.QImage(w, \ 1152 h, \ 1153 QtGui.QImage.Format_ARGB32) 1154 pp = QtGui.QPainter(ii) 1155 pp.setRenderHint(QtGui.QPainter.Antialiasing ) 1156 pp.setRenderHint(QtGui.QPainter.TextAntialiasing) 1157 pp.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) 1158 if take_region: 1159 self.selector.setVisible(False) 1160 self.render(pp, targetRect, self.selector.rect()) 1161 self.selector.setVisible(True) 1162 else: 1163 self.render(pp, targetRect, self.sceneRect()) 1164 pp.end() 1165 ii.save(imgName)
1166 1167 # Undocummented and untested
1168 - def save_by_chunks(self,imgName="img.out",rect=None):
1169 max_width = 10000 1170 max_height = 10000 1171 if imgName.endswith(".png"): 1172 imgName = ''.join(imgName.split('.')[:-1]) 1173 1174 if rect is None: 1175 rect = self.sceneRect() 1176 1177 width = int(rect.width()) 1178 height = int(rect.height()) 1179 1180 start_x = 0 1181 missing_width = width 1182 counter_column = 1 1183 for w in xrange(start_x, width, max_width): 1184 start_y = 0 1185 missing_height = height 1186 counter_row = 1 1187 for h in xrange(start_y, height, max_height): 1188 chunk_width = min( missing_width, max_width ) 1189 chunk_height = min( missing_height, max_height ) 1190 temp_rect = QtCore.QRectF( rect.x()+w, \ 1191 rect.y()+h, 1192 chunk_width, \ 1193 chunk_height ) 1194 if chunk_height<=0 or chunk_width <=0: 1195 break 1196 ii= QtGui.QImage(chunk_width, \ 1197 chunk_height, \ 1198 QtGui.QImage.Format_RGB32) 1199 pp = QtGui.QPainter(ii) 1200 targetRect = QtCore.QRectF(0, 0, temp_rect.width(), temp_rect.height()) 1201 self.render(pp, targetRect, temp_rect) 1202 ii.saves("%s-%s_%s.png" %(imgName,counter_row,counter_column)) 1203 counter_row += 1 1204 1205 missing_height -= chunk_height 1206 pp.end() 1207 counter_column += 1 1208 missing_width -= chunk_width
1209
1210 - def draw(self):
1211 # Clean previous items from scene by removing the main parent 1212 if self.mainItem: 1213 self.removeItem(self.mainItem) 1214 1215 #Clean_highlighting rects 1216 for n in self._highlighted_nodes: 1217 self._highlighted_nodes[n] = None 1218 1219 # Recreates main parent and add it to scene 1220 self.mainItem = QtGui.QGraphicsRectItem() 1221 self.addItem(self.mainItem) 1222 # Recreates selector item (used to zoom etc...) 1223 self.selector = _SelectorItem() 1224 self.selector.setParentItem(self.mainItem) 1225 self.selector.setVisible(False) 1226 self.selector.setZValue(2) 1227 1228 self.highlighter = _HighlighterItem() 1229 self.highlighter.setParentItem(self.mainItem) 1230 self.highlighter.setVisible(False) 1231 self.highlighter.setZValue(2) 1232 self.min_real_branch_separation = 0 1233 1234 # Get branch scale 1235 fnode, max_dist = self.startNode.get_farthest_leaf(topology_only=\ 1236 self.props.force_topology) 1237 1238 if max_dist>0: 1239 self.scale = self.props.tree_width / max_dist 1240 else: 1241 self.scale = 1 1242 1243 # Updates all nodes regions and their faces 1244 t1 = time.time() 1245 self.update_node_areas(self.startNode) 1246 logger(2, "Ellapsed time for updating node areas:",time.time()-t1) 1247 # Get picture dimensions 1248 self.i_width = self.startNode.fullRegion.width() 1249 self.i_height = self.startNode.fullRegion.height() 1250 1251 1252 logger(1, "IMAGE dimension=",self.i_width,"x",self.i_height) 1253 # Render tree on scene 1254 t2 = time.time() 1255 self.render_node(self.startNode,0,0) 1256 logger(2, "Time for rendering", time.time()-t2) 1257 1258 # size correcton for aligned faces 1259 self.i_width += self.max_w_aligned_face 1260 # New pos for tree when inverse orientation 1261 if self.props.orientation == 1: 1262 self.startNode._QtItem_.moveBy(self.max_w_aligned_face,0) 1263 1264 # Tree border 1265 #border = self.addRect(0,0,self.i_width, self.i_height) 1266 #border = self.addRect(0,0,self.i_width-self.max_w_aligned_face,self.i_height) 1267 #border = self.addRect(0,0, self.sceneRect().width(), self.sceneRect().height()) 1268 #border.setParentItem(self.mainItem) 1269 self.add_scale(1 ,self.i_height+4) 1270 1271 #Re-establish node marks 1272 for n in self._highlighted_nodes: 1273 self.highlight_node(n) 1274 1275 self.setSceneRect(-2,-2,self.i_width+4,self.i_height+50) 1276 logger(2, "Number of items in scene:", len(self.items()))
1277
1278 - def add_scale(self,x,y):
1279 size = 50 1280 customPen = QtGui.QPen(QtGui.QColor("black"),1) 1281 1282 line = QtGui.QGraphicsLineItem(self.mainItem) 1283 line2 = QtGui.QGraphicsLineItem(self.mainItem) 1284 line3 = QtGui.QGraphicsLineItem(self.mainItem) 1285 line.setPen(customPen) 1286 line2.setPen(customPen) 1287 line3.setPen(customPen) 1288 1289 line.setLine(x,y+20,size,y+20) 1290 line2.setLine(x,y+15,x,y+25) 1291 line3.setLine(size,y+15,size,y+25) 1292 1293 scale_text = "%0.2f" % float(size/ self.scale) 1294 scale = QtGui.QGraphicsSimpleTextItem(scale_text) 1295 scale.setParentItem(self.mainItem) 1296 scale.setPos(x,y+20) 1297 1298 if self.props.force_topology: 1299 wtext = "Force topology is enabled!\nBranch lengths does not represent original values." 1300 warning_text = QtGui.QGraphicsSimpleTextItem(wtext) 1301 warning_text.setFont( QtGui.QFont("Arial", 8)) 1302 warning_text.setBrush( QtGui.QBrush(QtGui.QColor("darkred"))) 1303 warning_text.setPos(x, y+32) 1304 warning_text.setParentItem(self.mainItem)
1305
1306 - def set_style_from(self,node,layout_func):
1307 for n in [node]+node.get_descendants(): 1308 n.img_style = copy.copy(_MIN_NODE_STYLE) 1309 n.img_style["faces"] = [] 1310 layout_func(n)
1311
1312 - def update_node_areas(self,node):
1313 """ This recursive function scans all nodes hunging from the given 1314 root node and calculates the coordinates and room necessary to 1315 draw a rectangular tree. IT reads the face content of each 1316 node, which ones have to be drawn, and how much room they 1317 use. """ 1318 child_rects = [] 1319 # First, go for childs 1320 if not node.is_leaf() and node.img_style["draw_descendants"]==1: 1321 for ch in node.children: 1322 # Recursivity 1323 rect = self.update_node_areas(ch) 1324 child_rects.append(rect) 1325 1326 # This is the node region covered by column faces and the 1327 # horizontal line drawn using the branch length and scale. 1328 1329 if self.props.force_topology: 1330 node.dist_xoffset = 60#self.scale 1331 else: 1332 node.dist_xoffset = float(node.dist * self.scale) 1333 1334 min_node_height = max(node.img_style["size"], node.img_style["hlwidth"]*2) 1335 max_w = 0 1336 max_aligned_w = 0 1337 max_h = 0 1338 # Each stack is drawn as a different column 1339 for stack in node.img_style["faces"]: 1340 stack_h = 0 1341 stack_w = 0 1342 aligned_f_w = 0 1343 aligned_f_h = 0 1344 for face in stack: 1345 f, aligned, pixmap = face 1346 if aligned and not node.is_leaf(): 1347 continue 1348 # Sets the working node from which required info will 1349 # be retrived. Thus, same face can be used for many nodes. 1350 f.node = node 1351 # If pixmap face, updates image 1352 if f.type == "pixmap": 1353 f.update_pixmap() # using current working node 1354 face[2] = f.pixmap 1355 1356 if node.is_leaf() and aligned: 1357 aligned_f_w = max(aligned_f_w, f._width()) #+ f.xmargin*2 1358 aligned_f_h += f._height() #+ f.ymargin * 2 1359 else: 1360 stack_w = max(stack_w, f._width()) #+ f.xmargin*2 1361 stack_h += f._height() #+ f.ymargin*2 1362 1363 max_aligned_w += aligned_f_w 1364 max_w += stack_w 1365 max_h = numpy.max([aligned_f_h, stack_h, max_h]) 1366 max_w +=1 1367 # Updates the max width spend by aligned faces 1368 if max_aligned_w > self.max_w_aligned_face: 1369 self.max_w_aligned_face = max_aligned_w 1370 1371 # Dist and faces region 1372 node.facesRegion = QtCore.QRectF(0,0,max_w,max_h) 1373 w = node.dist_xoffset + max_w + node.img_style["size"] 1374 h = max(max_h, min_node_height, self.props.min_branch_separation) + node.img_style["ymargin"]*2 1375 if self.min_real_branch_separation < h: 1376 self.min_real_branch_separation = h 1377 1378 node.nodeRegion = QtCore.QRectF(0,0,w,h) 1379 1380 # This piece of code fixes an old and annoying bug by which nodes with 1381 # faces larger than the sum of child node region were badly 1382 # drawn (badly centered and using space from other nodes) 1383 if not node.is_leaf() and node.img_style["draw_descendants"] == 1: 1384 max_child_w = 0 1385 sum_child_h = 0 1386 # y correction is used to fix cases in which the height of 1387 # parent nodes is greater than the sum of child 1388 # heights. Then, an y offset is calculated as the missing 1389 # amount of pixels. This correction is used by the render 1390 # algorithm to draw child 'y_correction" pixels bellow the 1391 # expected position. 1392 node._y_correction = 0 1393 start2 = 0 1394 for ch in node.children: 1395 # Updates the max width used by childs 1396 if ch.fullRegion.width()>max_child_w: 1397 max_child_w = ch.fullRegion.width() 1398 # Stores the 'y' center of the first child node 1399 if ch == node.children[0]: 1400 start1 = ch.__center 1401 # And the 'y' center of the last child node 1402 elif ch == node.children[-1]: 1403 start2 = sum_child_h + ch.__center 1404 sum_child_h += ch.fullRegion.height() 1405 1406 # Knowing the centers of first and last child nodes, we 1407 # know the center of current node. This center splits the 1408 # current node space into two unequal pieces: above and 1409 # bellow. 1410 above = (start1 +((start2 - start1)/2.0)) 1411 bellow = sum_child_h - above 1412 newh = 0 1413 1414 # Current node faces will be drawn centered to the node 1415 # position, so half of the faces space should fit in the 1416 # above region, and the other half in the bellow region. 1417 # If not, the height of current node is increased to 1418 # reserve the required space. 1419 # 1420 # The space is missing in the above region, an y offset is 1421 # set to permit child nodes to be drawn a bit more down 1422 # than expected. 1423 if above < (h/2): 1424 newh = sum_child_h + ((h/2.0) - above) 1425 node._y_correction = ((h/2.0) - above) 1426 if bellow < h/2: 1427 if newh >0: 1428 newh += ((h/2.0) - bellow) 1429 else: 1430 newh = sum_child_h + ((h/2) - bellow) 1431 1432 # If current node faces do not exceed the sum of child 1433 # heights, then current node height is the sum of child 1434 # heights 1435 if newh == 0: 1436 newh = sum_child_h 1437 h = newh 1438 # Increases node width the max child width 1439 w += max_child_w 1440 # And stores current node center, which is the center 1441 # calculated using the child node centers + the 1442 # y_correction (if any) 1443 node.__center = (start1 +((start2 - start1)/2.0)) + node._y_correction 1444 else: 1445 node.__center = h/2 1446 node._y_correction = 0 1447 1448 # This is the node total region covered by the node 1449 node.fullRegion = QtCore.QRectF(0,0,w,h) 1450 1451 # Sets the total room needed for this node 1452 return node.fullRegion
1453
1454 - def rotate_node(self,node,angle,x=None,y=None):
1455 if x and y: 1456 x = node.fullRegion.width()/2 1457 y = node.fullRegion.height()/2 1458 node._QtItem_.setTransform(QtGui.QTransform().translate(x, y).rotate(angle).translate(-x, -y)); 1459 else: 1460 node._QtItem_.rotate(angle)
1461
1462 - def render_node(self,node , x, y,level=0):
1463 """ Traverse the tree structure and render each node using the 1464 regions, sizes, and faces previously loaded. """ 1465 1466 # Node's stuff 1467 orientation = self.props.orientation 1468 r = node.img_style["size"]/2 1469 fh = node.facesRegion.width() 1470 1471 node._QtItem_ = _NodeItem(node) 1472 node._QtItem_.setAcceptsHoverEvents(True) 1473 1474 # RIGHT TO LEFT 1475 if orientation == 1: 1476 if node == self.startNode: 1477 x = self.i_width-x 1478 1479 # Add main node QGItem. Each node item has as parent the 1480 # parent node item 1481 if node==self.startNode: 1482 node._QtItem_.setParentItem(self.mainItem) 1483 scene_pos = node._QtItem_.pos() 1484 node.scene_pos = scene_pos 1485 1486 # node x,y starting positions 1487 node._x = x 1488 node._y = y 1489 1490 # colour rect as node background 1491 if node.img_style["bgcolor"].upper() != "#FFFFFF": 1492 background = QtGui.QGraphicsRectItem(self.mainItem) 1493 background.setZValue(-1000+level) 1494 color = QtGui.QColor(node.img_style["bgcolor"]) 1495 background.setBrush(color) 1496 background.setPen(color) 1497 if orientation == 0: 1498 background.setRect(node._x,node._y,self.i_width-node._x+self.max_w_aligned_face,node.fullRegion.height()) 1499 elif orientation == 1: 1500 background.setRect(node._x-node.fullRegion.width(),node._y,self.i_width,node.fullRegion.height()) 1501 # Draw node and lines 1502 if not node.is_leaf() and node.img_style["draw_descendants"]==1: 1503 # Corrections ... say something else, don't you think? 1504 # node_height = 0 1505 # for ch in node.get_children(): 1506 # node_height += ch.fullRegion.height() 1507 1508 # if node.fullRegion.height() >= node_height: 1509 # y_correction = node.fullRegion.height() - node_height 1510 # else: 1511 # y_correction = 0 1512 1513 # y_correction = node._y_correction 1514 # recursivity: call render function for every child 1515 next_y = y + node._y_correction#/2 1516 for ch in node.get_children(): 1517 dist_to_child = ch.dist * self.scale 1518 if orientation == 0: 1519 next_x = x+node.nodeRegion.width() 1520 elif orientation == 1: 1521 next_x = x-node.nodeRegion.width() 1522 1523 self.render_node(ch,next_x, next_y,level+1) 1524 next_y += ch.fullRegion.height() 1525 1526 node._centered_y = ((node.children[0]._centered_y + node.children[-1]._centered_y)/2) 1527 # Draw an internal node. Take global pos. 1528 1529 # Place node at the correct pos in Scene 1530 ch._QtItem_.setParentItem(node._QtItem_) 1531 if orientation == 0: 1532 node._QtItem_.setPos(x+node.dist_xoffset,node._centered_y-node.img_style["size"]/2) 1533 elif orientation == 1: 1534 node._QtItem_.setPos(x-node.dist_xoffset-node.img_style["size"],node._centered_y-node.img_style["size"]/2) 1535 for ch in node.children: 1536 scene_pos = ch._QtItem_.pos() 1537 ch.scene_pos = scene_pos 1538 ch._QtItem_.setParentItem(node._QtItem_) 1539 ch._QtItem_.setPos(node._QtItem_.mapFromScene(scene_pos) ) 1540 1541 # Draws the startNode branch when it is not the absolute root 1542 if node == self.startNode: 1543 y = node._QtItem_.pos().y()+ node.img_style["size"]/2 1544 self.add_branch(self.mainItem,0,y,node.dist_xoffset,y,node.dist,node.support, node.img_style["hz_line_color"], node.img_style["hlwidth"], node.img_style["line_type"]) 1545 1546 # RECTANGULAR STYLE 1547 if self.props.style == 0: 1548 vt_line = QtGui.QGraphicsLineItem(node._QtItem_) 1549 customPen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(node.img_style["vt_line_color"])), node.img_style["vlwidth"]) 1550 if node.img_style["line_type"]==1: 1551 customPen.setStyle(QtCore.Qt.DashLine) 1552 vt_line.setPen(customPen) 1553 1554 ch1_y = node._QtItem_.mapFromScene(0,node.children[0]._centered_y).y() 1555 ch2_y = node._QtItem_.mapFromScene(0,node.children[-1]._centered_y).y() 1556 1557 # Draw hz lines of childs 1558 for ch in node.children: 1559 ch_pos = node._QtItem_.mapFromScene(ch._x, ch._centered_y) 1560 if orientation == 0: 1561 self.add_branch(node._QtItem_,fh+r*2,ch_pos.y(),fh+r*2+ch.dist_xoffset ,ch_pos.y(),ch.dist, ch.support, ch.img_style["hz_line_color"], ch.img_style["hlwidth"], ch.img_style["line_type"]) 1562 elif orientation == 1: 1563 self.add_branch(node._QtItem_,-fh,ch_pos.y(),-fh-ch.dist_xoffset ,ch_pos.y(),ch.dist,ch.support,ch.img_style["hz_line_color"], ch.img_style["hlwidth"], ch.img_style["line_type"]) 1564 # Draw vertical line 1565 if orientation == 0: 1566 vt_line.setLine(fh+r*2,ch1_y,fh+(r*2),ch2_y) 1567 elif orientation == 1: 1568 vt_line.setLine(-fh,ch1_y,-fh,ch2_y) 1569 1570 # DIAGONAL STYLE 1571 elif self.props.style == 1: 1572 # Draw lines from node to childs 1573 for ch in node.children: 1574 if orientation == 0: 1575 ch_x = ch._QtItem_.x() 1576 ch_y = ch._QtItem_.y()+ch.img_style["size"]/2 1577 self.add_branch(node._QtItem_,fh+node.img_style["size"],r,ch_x,ch_y,ch.dist,ch.support, ch.img_style["hz_line_color"], 1, ch.img_style["line_type"]) 1578 elif orientation == 1: 1579 ch_x = ch._QtItem_.x() 1580 ch_y = ch._QtItem_.y()+ch.img_style["size"]/2 1581 self.add_branch(node._QtItem_,-fh,r,ch_x+(r*2),ch_y,ch.dist,ch.support, ch.img_style["hz_line_color"], 1, ch.img_style["line_type"]) 1582 1583 self.add_faces(node,orientation) 1584 1585 else: 1586 # Draw terminal node 1587 node._centered_y = y+node.fullRegion.height()/2 1588 if orientation == 0: 1589 node._QtItem_.setPos(x+node.dist_xoffset, node._centered_y-r) 1590 elif orientation == 1: 1591 node._QtItem_.setPos(x-node.dist_xoffset-node.img_style["size"], node._centered_y-r) 1592 1593 self.add_faces(node,orientation)
1594
1595 - def add_branch(self,parent_item,x1,y1,x2,y2,dist,support,color,width,line_type):
1596 hz_line = QtGui.QGraphicsLineItem(parent_item) 1597 customPen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(color)), width) 1598 if line_type == 1: 1599 customPen.setStyle(QtCore.Qt.DashLine) 1600 hz_line.setPen(customPen) 1601 hz_line.setLine(x1,y1,x2,y2) 1602 1603 if self.props.draw_branch_length: 1604 distText = "%0.3f" % dist 1605 if support is not None: 1606 distText += "(%0.2f)" % support 1607 font = QtGui.QFont(self.props.general_font_type,self.props.branch_length_font_size) 1608 rect = QtGui.QFontMetrics(font).boundingRect(0,0,0,0,QtCore.Qt.AlignTop,distText) 1609 b_length = QtGui.QGraphicsSimpleTextItem(distText) 1610 b_length.setFont(font) 1611 b_length.setBrush(QtGui.QColor(self.props.branch_length_font_color)) 1612 b_length.setParentItem(hz_line) 1613 b_length.setPos(x1,y1) 1614 if y1 != y2: 1615 offset = 0 1616 angle = math.atan((y2-y1)/(x2-x1))*360/ (2*math.pi) 1617 if y1>y2: 1618 offset = rect.height() 1619 b_length.setTransform(QtGui.QTransform().translate(0, y1-offset).rotate(angle).translate(0,-y1));
1620
1621 - def add_faces(self,node,orientation):
1622 # sphere radius 1623 r = node.img_style["size"]/2 1624 1625 # ... 1626 if orientation==0: 1627 aligned_start_x = node._QtItem_.mapFromScene(self.i_width,0).x() 1628 start_x = node.img_style["size"] 1629 elif orientation==1: 1630 start_x = 0 1631 aligned_start_x = node._QtItem_.mapFromScene(0,0).x() 1632 1633 for stack in node.img_style["faces"]: 1634 # get each stack's height and width 1635 stack_h = 0 1636 stack_w = 0 1637 aligned_stack_w = 0 1638 aligned_stack_h = 0 1639 # Extract height and width of al faces in this stack 1640 # Get max width and cumulated height 1641 for f, aligned, pixmap in stack: 1642 if aligned and not node.is_leaf(): 1643 continue 1644 f.node = node 1645 if node.is_leaf() and aligned: 1646 aligned_stack_w = max(aligned_stack_w , f._width()) #+ f.xmargin*2 1647 aligned_stack_h += f._height() #+ f.ymargin*2 1648 else: 1649 stack_w = max(stack_w ,f._width() ) #+f.xmargin*2 1650 stack_h += f._height() #+f.ymargin*2 1651 1652 # Creates a GGraphicsItem object for each face 1653 cumulated_y = 0 1654 cumulated_aligned_y = 0 1655 for j, face in enumerate(stack): 1656 f, aligned, pixmap = face 1657 if aligned and not node.is_leaf(): 1658 continue 1659 # Sets the face's working node 1660 f.node = node 1661 # add each face of this stack into the correct position. 1662 if node.is_leaf() and aligned: 1663 start_y = cumulated_aligned_y + (-aligned_stack_h/2)+r #+ f.ymargin 1664 else: 1665 start_y = cumulated_y - (stack_h/2) +r #+ f.ymargin 1666 1667 # If face is text type, add it as an QGraphicsTextItem 1668 if f.type == "text": 1669 obj = _TextItem(f, node, f.get_text()) 1670 obj.setFont(f.font) 1671 obj.setBrush(QtGui.QBrush(f.fgcolor)) 1672 obj.setParentItem(node._QtItem_) 1673 obj.setAcceptsHoverEvents(True) 1674 # Marks the y starting point 1675 # start_y -= f._height()/2 1676 # obj2 = QtGui.QGraphicsEllipseItem(0,0,1,1) 1677 # obj2.setBrush(QtGui.QBrush(QtGui.QColor("red"))) 1678 # obj2.setParentItem(obj) 1679 else: 1680 # Loads the pre-generated pixmap 1681 obj = _FaceItem(f, node, pixmap) 1682 obj.setAcceptsHoverEvents(True) 1683 obj.setParentItem(node._QtItem_) 1684 1685 # If face is aligned and node is terminal, place face 1686 # at aligned margin 1687 if node.is_leaf() and aligned: 1688 # Set face position 1689 if orientation ==0: 1690 obj.setPos(aligned_start_x + f.xmargin, start_y)# + f.ymargin) 1691 elif orientation ==1: 1692 obj.setPos(aligned_start_x-f._width() - f.xmargin , start_y)# + f.ymargin) 1693 cumulated_aligned_y += f._height()# + f.ymargin*2 1694 # If face has to be draw within the node room 1695 else: 1696 # Set face position 1697 if orientation ==0: 1698 obj.setPos(start_x, start_y) 1699 elif orientation ==1: 1700 obj.setPos(start_x-f._width(),start_y) 1701 cumulated_y += f._height() 1702 1703 # next stack will start with this x_offset 1704 if orientation == 0: 1705 start_x += stack_w 1706 aligned_start_x += aligned_stack_w 1707 elif orientation ==1: 1708 start_x -= stack_w 1709 aligned_start_x -= aligned_start_x
1710