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

Source Code for Module ete2.treeview.drawer

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