1# #START_LICENSE########################################################### 2# 3# 4# This file is part of the Environment for Tree Exploration program 5# (ETE). http://etetoolkit.org 6# 7# ETE is free software: you can redistribute it and/or modify it 8# under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# ETE is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 15# License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with ETE. If not, see <http://www.gnu.org/licenses/>. 19# 20# 21# ABOUT THE ETE PACKAGE 22# ===================== 23# 24# ETE is distributed under the GPL copyleft license (2008-2015). 25# 26# If you make use of ETE in published work, please cite: 27# 28# Jaime Huerta-Cepas, Joaquin Dopazo and Toni Gabaldon. 29# ETE: a python Environment for Tree Exploration. Jaime BMC 30# Bioinformatics 2010,:24doi:10.1186/1471-2105-11-24 31# 32# Note that extra references to the specific methods implemented in 33# the toolkit may be available in the documentation. 34# 35# More info at http://etetoolkit.org. Contact: huerta@embl.de 36# 37# 38# #END_LICENSE############################################################# 39import math 40import re 41import six 42 43from .qt import * 44from . import qt4_circular_render as crender 45from . import qt4_rect_render as rrender 46from .main import _leaf, NodeStyle, _FaceAreas, tracktime, TreeStyle 47from .node_gui_actions import _NodeActions as _ActionDelegator 48from .qt4_face_render import update_node_faces, _FaceGroupItem, _TextFaceItem 49from .templates import _DEFAULT_STYLE, apply_template 50from . import faces 51 52 53## | General scheme of node content 54## |==========================================================================================================================| 55## | fullRegion | 56## | nodeRegion |================================================================================| 57## | | fullRegion || 58## | | nodeRegion |=======================================|| 59## | | | fullRegion ||| 60## | | | nodeRegion ||| 61## | | | |branch_length | nodeSize | facesRegion||| 62## | | branch_length | nodesize|faces-right |=======================================|| 63## | | |(facesRegion)|=======================================|| 64## | | | fullRegion || 65## | | | nodeRegion || 66## | faces-top | | | | branch_length | nodeSize | facesRegion|| 67## | branch_length | NodeSize |faces-right | |=======================================|| 68## | faces-bottom | |(facesRegion)|================================================================================| 69## | |=======================================| | 70## | | fullRegion | | 71## | | nodeRegion | | 72## | | branch_length | nodeSize | facesRegion| | 73## | |=======================================| | 74## |==========================================================================================================================| 75 76class _CircleItem(QGraphicsEllipseItem, _ActionDelegator): 77 def __init__(self, node): 78 self.node = node 79 d = node.img_style["size"] 80 QGraphicsEllipseItem.__init__(self, 0, 0, d, d) 81 _ActionDelegator.__init__(self) 82 83 self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"]))) 84 self.setPen(QPen(QColor(self.node.img_style["fgcolor"]))) 85 86class _RectItem(QGraphicsRectItem, _ActionDelegator): 87 def __init__(self, node): 88 self.node = node 89 d = node.img_style["size"] 90 QGraphicsRectItem.__init__(self, 0, 0, d, d) 91 _ActionDelegator.__init__(self) 92 self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"]))) 93 self.setPen(QPen(QColor(self.node.img_style["fgcolor"]))) 94 95class _SphereItem(QGraphicsEllipseItem, _ActionDelegator): 96 def __init__(self, node): 97 self.node = node 98 d = node.img_style["size"] 99 r = d/2.0 100 QGraphicsEllipseItem.__init__(self, 0, 0, d, d) 101 _ActionDelegator.__init__(self) 102 #self.setBrush(QBrush(QColor(self.node.img_style["fgcolor"]))) 103 self.setPen(QPen(QColor(self.node.img_style["fgcolor"]))) 104 gradient = QRadialGradient(r, r, r,(d)/3,(d)/3) 105 gradient.setColorAt(0.05, Qt.white); 106 gradient.setColorAt(1, QColor(self.node.img_style["fgcolor"])); 107 self.setBrush(QBrush(gradient)) 108 # self.setPen(Qt.NoPen) 109 110class _EmptyItem(QGraphicsItem): 111 def __init__(self, parent=None): 112 QGraphicsItem.__init__(self) 113 self.setParentItem(parent) 114 115 # qt4.6+ Only 116 try: 117 self.setFlags(QGraphicsItem.ItemHasNoContents) 118 except: 119 pass 120 121 def boundingRect(self): 122 return QRectF(0,0,0,0) 123 124 def paint(self, *args, **kargs): 125 return 126 127class _TreeItem(QGraphicsRectItem): 128 def __init__(self, parent=None): 129 QGraphicsRectItem.__init__(self) 130 self.setParentItem(parent) 131 self.n2i = {} 132 self.n2f = {} 133 134class _NodeItem(_EmptyItem): 135 def __init__(self, node, parent): 136 _EmptyItem.__init__(self, parent) 137 self.node = node 138 self.nodeRegion = QRectF() 139 self.facesRegion = QRectF() 140 self.fullRegion = QRectF() 141 self.highlighted = False 142 143class _NodeLineItem(QGraphicsLineItem, _ActionDelegator): 144 def __init__(self, node, *args, **kargs): 145 self.node = node 146 QGraphicsLineItem.__init__(self, *args, **kargs) 147 _ActionDelegator.__init__(self) 148 def paint(self, painter, option, widget): 149 QGraphicsLineItem.paint(self, painter, option, widget) 150 151class _LineItem(QGraphicsLineItem): 152 def paint(self, painter, option, widget): 153 QGraphicsLineItem.paint(self, painter, option, widget) 154 155class _PointerItem(QGraphicsRectItem): 156 def __init__(self, parent=None): 157 QGraphicsRectItem.__init__(self,0,0,0,0, parent) 158 self.color = QColor("blue") 159 self._active = False 160 self.setBrush(QBrush(Qt.NoBrush)) 161 162 def paint(self, p, option, widget): 163 p.setPen(self.color) 164 p.drawRect(self.rect()) 165 return 166 # Draw info text 167 font = QFont("Arial",13) 168 text = "%d selected." % len(self.get_selected_nodes()) 169 textR = QFontMetrics(font).boundingRect(text) 170 if self.rect().width() > textR.width() and \ 171 self.rect().height() > textR.height()/2.0 and 0: # OJO !!!! 172 p.setPen(QPen(self.color)) 173 p.setFont(QFont("Arial",13)) 174 p.drawText(self.rect().bottomLeft().x(),self.rect().bottomLeft().y(),text) 175 176 def get_selected_nodes(self): 177 selPath = QPainterPath() 178 selPath.addRect(self.rect()) 179 self.scene().setSelectionArea(selPath) 180 return [i.node for i in self.scene().selectedItems()] 181 182 def setActive(self,bool): 183 self._active = bool 184 185 def isActive(self): 186 return self._active 187 188class _TreeScene(QGraphicsScene): 189 def __init__(self): 190 QGraphicsScene.__init__(self) 191 self.view = None 192 193 def init_values(self, tree, img, n2i, n2f): 194 self.master_item = _EmptyItem() 195 self.tree = tree 196 self.n2i = n2i 197 self.n2f = n2f 198 self.img = img 199 200 def draw(self): 201 self.img._scale = None 202 tree_item, self.n2i, self.n2f = render(self.tree, self.img) 203 if self.master_item: 204 self.removeItem(self.master_item) 205 tree_item, n2i, n2f = render(self.tree, self.img) 206 self.init_values(self.tree, self.img, n2i, n2f) 207 self.addItem(self.master_item) 208 tree_item.setParentItem(self.master_item) 209 self.setSceneRect(tree_item.rect()) 210 211#@tracktime 212def render(root_node, img, hide_root=False): 213 '''main render function. hide_root option is used when render 214 trees as Faces 215 216 ''' 217 mode = img.mode 218 orientation = img.orientation 219 220 arc_span = img.arc_span 221 222 layout_fn = img._layout_handler 223 224 parent = _TreeItem() 225 n2i = parent.n2i # node to items 226 n2f = parent.n2f # node to faces 227 228 parent.bg_layer = _EmptyItem(parent) 229 parent.tree_layer = _EmptyItem(parent) 230 parent.float_layer = _EmptyItem(parent) 231 parent.float_behind_layer = _EmptyItem(parent) 232 233 TREE_LAYERS = [parent.bg_layer, parent.tree_layer, 234 parent.float_layer, parent.float_behind_layer] 235 236 parent.bg_layer.setZValue(0) 237 parent.tree_layer.setZValue(2) 238 239 parent.float_behind_layer.setZValue(1) 240 parent.float_layer.setZValue(3) 241 242 # This could be used to handle aligned faces in internal 243 # nodes. 244 virtual_leaves = 0 245 246 if img.show_branch_length: 247 bl_face = faces.AttrFace("dist", fsize=8, ftype="Arial", fgcolor="black", formatter = "%0.3g") 248 if img.show_branch_support: 249 su_face = faces.AttrFace("support", fsize=8, ftype="Arial", fgcolor="darkred", formatter = "%0.3g") 250 if img.show_leaf_name: 251 na_face = faces.AttrFace("name", fsize=10, ftype="Arial", fgcolor="black") 252 253 for n in root_node.traverse(is_leaf_fn=_leaf): 254 set_style(n, layout_fn) 255 256 if img.show_branch_length: 257 faces.add_face_to_node(bl_face, n, 0, position="branch-top") 258 259 if not _leaf(n) and img.show_branch_support: 260 faces.add_face_to_node(su_face, n, 0, position="branch-bottom") 261 262 if _leaf(n) and n.name and img.show_leaf_name: 263 faces.add_face_to_node(na_face, n, 0, position="branch-right") 264 265 if _leaf(n):# or len(n.img_style["_faces"]["aligned"]): 266 virtual_leaves += 1 267 268 update_node_faces(n, n2f, img) 269 270 rot_step = float(arc_span) / virtual_leaves 271 #rot_step = float(arc_span) / len([n for n in root_node.traverse() if _leaf(n)]) 272 273 # Calculate optimal branch length 274 if img._scale is not None: 275 init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) 276 elif img.scale is None: 277 # create items and calculate node dimensions skipping branch lengths 278 init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) 279 if mode == 'r': 280 if img.optimal_scale_level == "full": 281 scales = [(i.widths[1]/n.dist) for n,i in six.iteritems(n2i) if n.dist] 282 img._scale = max(scales) if scales else 0.0 283 else: 284 farthest, dist = root_node.get_farthest_leaf(topology_only=img.force_topology) 285 img._scale = img.tree_width / dist if dist else 0.0 286 update_branch_lengths(root_node, n2i, n2f, img) 287 else: 288 img._scale = crender.calculate_optimal_scale(root_node, n2i, rot_step, img) 289 #print "OPTIMAL circular scale", img._scale 290 update_branch_lengths(root_node, n2i, n2f, img) 291 init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) 292 else: 293 # create items and calculate node dimensions CONSIDERING branch lengths 294 img._scale = img.scale 295 init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) 296 297 #print "USING scale", img._scale 298 # Draw node content 299 for node in root_node.traverse(is_leaf_fn=_leaf): 300 if node is not root_node or not hide_root: 301 render_node_content(node, n2i, n2f, img) 302 303 # Adjust content to rect or circular layout 304 mainRect = parent.rect() 305 306 if mode == "c": 307 tree_radius = crender.render_circular(root_node, n2i, rot_step) 308 mainRect.adjust(-tree_radius, -tree_radius, tree_radius, tree_radius) 309 else: 310 iwidth = n2i[root_node].fullRegion.width() 311 iheight = n2i[root_node].fullRegion.height() 312 mainRect.adjust(0, 0, iwidth, iheight) 313 tree_radius = iwidth 314 315 # Add extra layers: aligned faces, floating faces, node 316 # backgrounds, etc. The order by which the following methods are 317 # called IS IMPORTANT 318 render_floatings(n2i, n2f, img, parent.float_layer, parent.float_behind_layer) 319 320 aligned_region_width = render_aligned_faces(img, mainRect, parent.tree_layer, n2i, n2f) 321 322 render_backgrounds(img, mainRect, parent.bg_layer, n2i, n2f) 323 324 # rotate if necessary in circular images. flip and adjust if mirror orientation. 325 adjust_faces_to_tranformations(img, mainRect, n2i, n2f, TREE_LAYERS) 326 327 # Rotate main image if necessary 328 parent.setRect(mainRect) 329 parent.setPen(QPen(Qt.NoPen)) 330 331 if img.rotation: 332 rect = parent.boundingRect() 333 x = rect.x() + (rect.width()/2.0) 334 y = rect.y() + (rect.height()/2.0) 335 parent.setTransform(QTransform().translate(x, y).rotate(img.rotation).translate(-x, -y)) 336 337 # Creates the main tree item that will act as frame for the whole image 338 frame = QGraphicsRectItem() 339 parent.setParentItem(frame) 340 mainRect = parent.mapToScene(mainRect).boundingRect() 341 342 mainRect.adjust(-img.margin_left, -img.margin_top, \ 343 img.margin_right, img.margin_bottom) 344 345 # Fix negative coordinates, so main item always starts at 0,0 346 topleft = mainRect.topLeft() 347 _x = abs(topleft.x()) if topleft.x() < 0 else 0 348 _y = abs(topleft.y()) if topleft.y() < 0 else 0 349 if _x or _y: 350 parent.moveBy(_x, _y) 351 mainRect.adjust(_x, _y, _x, _y) 352 353 # Add extra components and adjust mainRect to them 354 add_legend(img, mainRect, frame) 355 add_title(img, mainRect, frame) 356 add_scale(img, mainRect, frame) 357 frame.setRect(mainRect) 358 359 # Draws a border around the tree 360 if not img.show_border: 361 frame.setPen(QPen(Qt.NoPen)) 362 else: 363 frame.setPen(QPen(QColor("black"))) 364 365 return frame, n2i, n2f 366 367def adjust_faces_to_tranformations(img, mainRect, n2i, n2f, tree_layers): 368 if img.mode == "c": 369 rotate_inverted_faces(n2i, n2f, img) 370 elif img.mode == "r" and img.orientation == 1: 371 for layer in tree_layers: 372 layer.setTransform(QTransform().translate(0, 0).scale(-1,1).translate(0, 0)) 373 layer.moveBy(mainRect.width(),0) 374 for faceblock in six.itervalues(n2f): 375 for pos, fb in six.iteritems(faceblock): 376 fb.flip_hz() 377 378def add_legend(img, mainRect, parent): 379 if img.legend: 380 legend = _FaceGroupItem(img.legend, None) 381 legend.setup_grid() 382 legend.render() 383 lg_w, lg_h = legend.get_size() 384 dw = max(0, lg_w-mainRect.width()) 385 legend.setParentItem(parent) 386 if img.legend_position == 1: 387 mainRect.adjust(0, -lg_h, dw, 0) 388 legend.setPos(mainRect.topLeft()) 389 elif img.legend_position == 2: 390 mainRect.adjust(0, -lg_h, dw, 0) 391 pos = mainRect.topRight() 392 legend.setPos(pos.x()-lg_w, pos.y()) 393 elif img.legend_position == 3: 394 legend.setPos(mainRect.bottomLeft()) 395 mainRect.adjust(0, 0, dw, lg_h) 396 elif img.legend_position == 4: 397 pos = mainRect.bottomRight() 398 legend.setPos(pos.x()-lg_w, pos.y()) 399 mainRect.adjust(0, 0, dw, lg_h) 400 401def add_title(img, mainRect, parent): 402 if img.title: 403 title = _FaceGroupItem(img.title, None) 404 title.setup_grid() 405 title.render() 406 lg_w, lg_h = title.get_size() 407 dw = max(0, lg_w-mainRect.width()) 408 title.setParentItem(parent) 409 mainRect.adjust(0, -lg_h, dw, 0) 410 title.setPos(mainRect.topLeft()) 411 412def add_legend(img, mainRect, parent): 413 if img.legend: 414 legend = _FaceGroupItem(img.legend, None) 415 legend.setup_grid() 416 legend.render() 417 lg_w, lg_h = legend.get_size() 418 dw = max(0, lg_w-mainRect.width()) 419 legend.setParentItem(parent) 420 if img.legend_position == 1: 421 mainRect.adjust(0, -lg_h, dw, 0) 422 legend.setPos(mainRect.topLeft()) 423 elif img.legend_position == 2: 424 mainRect.adjust(0, -lg_h, dw, 0) 425 pos = mainRect.topRight() 426 legend.setPos(pos.x()-lg_w, pos.y()) 427 elif img.legend_position == 3: 428 legend.setPos(mainRect.bottomLeft()) 429 mainRect.adjust(0, 0, dw, lg_h) 430 elif img.legend_position == 4: 431 pos = mainRect.bottomRight() 432 legend.setPos(pos.x()-lg_w, pos.y()) 433 mainRect.adjust(0, 0, dw, lg_h) 434 435def add_scale(img, mainRect, parent): 436 if img.show_scale: 437 if img.scale_length is None: 438 length = 50.0 439 else: 440 length = img.scale_length * img._scale 441 if length > mainRect.width(): 442 max_value = mainRect.width() / img._scale 443 raise ValueError('Custom scale bar length (TreeStyle.scale_length) is larger than the tree image' 444 'Use values between 0 and %g' %max_value) 445 446 scaleItem = _EmptyItem() 447 customPen = QPen(QColor("black"), 1) 448 449 if img.force_topology: 450 wtext = "Force topology is enabled!\nBranch lengths do not represent real values." 451 warning_text = QGraphicsSimpleTextItem(wtext) 452 warning_text.setFont(QFont("Arial", 8)) 453 warning_text.setBrush( QBrush(QColor("darkred"))) 454 warning_text.setPos(0, 32) 455 warning_text.setParentItem(scaleItem) 456 else: 457 line = QGraphicsLineItem(scaleItem) 458 line2 = QGraphicsLineItem(scaleItem) 459 line3 = QGraphicsLineItem(scaleItem) 460 line.setPen(customPen) 461 line2.setPen(customPen) 462 line3.setPen(customPen) 463 464 line.setLine(0, 5, length, 5) 465 line2.setLine(0, 0, 0, 10) 466 line3.setLine(length, 0, length, 10) 467 length_text = float(length) / img._scale if img._scale else 0.0 468 scale_text = "%g" %(length_text) 469 scale = QGraphicsSimpleTextItem(scale_text) 470 scale.setParentItem(scaleItem) 471 scale.setPos(0, 10) 472 473 scaleItem.setParentItem(parent) 474 dw = max(0, length - mainRect.width()) 475 scaleItem.setPos(mainRect.bottomLeft()) 476 scaleItem.moveBy(img.margin_left, 0) 477 mainRect.adjust(0, 0, 0, length) 478 479def rotate_inverted_faces(n2i, n2f, img): 480 for node, faceblock in six.iteritems(n2f): 481 item = n2i[node] 482 if item.rotation > 90 and item.rotation < 270: 483 for pos, fb in six.iteritems(faceblock): 484 fb.rotate(181) 485 486def render_backgrounds(img, mainRect, bg_layer, n2i, n2f): 487 488 if img.mode == "c": 489 max_r = mainRect.width()/2.0 490 else: 491 max_r = mainRect.width() 492 493 for node, item in six.iteritems(n2i): 494 if _leaf(node): 495 first_c = n2i[node] 496 last_c = n2i[node] 497 else: 498 first_c = n2i[node.children[0]] 499 last_c = n2i[node.children[-1]] 500 501 if img.mode == "c": 502 h = item.effective_height 503 angle_start = first_c.full_start 504 angle_end = last_c.full_end 505 parent_radius = getattr(n2i.get(node.up, None), "radius", 0) 506 base = parent_radius + item.nodeRegion.width() 507 508 # if node.img_style["node_bgcolor"].upper() != "#FFFFFF": 509 # bg1 = crender._ArcItem() 510 # r = math.sqrt(base**2 + h**2) 511 # bg1.set_arc(0, 0, parent_radius, r, angle_start, angle_end) 512 # bg1.setParentItem(item.content.bg) 513 # bg1.setPen(QPen(QColor(node.img_style["node_bgcolor"]))) 514 # bg1.setBrush(QBrush(QColor(node.img_style["node_bgcolor"]))) 515 516 # if node.img_style["faces_bgcolor"].upper() != "#FFFFFF": 517 # bg2 = crender._ArcItem() 518 # r = math.sqrt(base**2 + h**2) 519 # bg2.set_arc(0, 0, parent_radius, item.radius, angle_start, angle_end) 520 # bg2.setParentItem(item.content) 521 # bg2.setPen(QPen(QColor(node.img_style["faces_bgcolor"]))) 522 # bg2.setBrush(QBrush(QColor(node.img_style["faces_bgcolor"]))) 523 524 if node.img_style["bgcolor"].upper() != "#FFFFFF": 525 bg = crender._ArcItem() 526 bg.set_arc(0, 0, parent_radius, max_r, angle_start, angle_end) 527 bg.setPen(QPen(QColor(node.img_style["bgcolor"]))) 528 bg.setBrush(QBrush(QColor(node.img_style["bgcolor"]))) 529 bg.setParentItem(bg_layer) 530 bg.setZValue(item.zValue()) 531 532 if img.mode == "r": 533 if node.img_style["bgcolor"].upper() != "#FFFFFF": 534 bg = QGraphicsRectItem() 535 pos = item.content.mapToScene(0, 0) 536 bg.setPos(pos.x(), pos.y()) 537 bg.setRect(0, 0, max_r-pos.x(), item.fullRegion.height()) 538 bg.setPen(QPen(QColor(node.img_style["bgcolor"]))) 539 bg.setBrush(QBrush(QColor(node.img_style["bgcolor"]))) 540 bg.setParentItem(bg_layer) 541 bg.setZValue(item.zValue()) 542 543def set_node_size(node, n2i, n2f, img): 544 scale = img._scale 545 min_separation = img.min_leaf_separation 546 547 item = n2i[node] 548 if img.force_topology: 549 branch_length = item.branch_length = 25 550 else: 551 branch_length = item.branch_length = float(node.dist * scale) 552 553 # Organize faces by groups 554 #faceblock = update_node_faces(node, n2f, img) 555 faceblock = n2f[node] 556 aligned_height = 0 557 if _leaf(node): 558 if img.mode == "r": 559 aligned_height = faceblock["aligned"].h 560 elif img.mode == "c": 561 # aligned faces in circular mode are adjusted afterwords. The 562 # min radius of the largest aligned faces will be calculated. 563 pass 564 565 # Total height required by the node. I cannot sum up the height of 566 # all elements, since the position of some of them are forced to 567 # be on top or at the bottom of branches. This fact can produce 568 # and unbalanced nodeRegion center. Here, I only need to know 569 # about the imbalance size to correct node height. The center will 570 # be calculated later according to the parent position. 571 top_half_h = ( (node.img_style["size"]/2.0) + 572 node.img_style["hz_line_width"]/2.0 + 573 faceblock["branch-top"].h ) 574 575 bottom_half_h =( (node.img_style["size"]/2.0) + 576 node.img_style["hz_line_width"]/2.0 + 577 faceblock["branch-bottom"].h ) 578 579 h1 = top_half_h + bottom_half_h 580 h2 = max(faceblock["branch-right"].h, \ 581 aligned_height, \ 582 min_separation ) 583 h = max(h1, h2) 584 imbalance = abs(top_half_h - bottom_half_h) 585 if imbalance > h2/2.0: 586 h += imbalance - (h2/2.0) 587 588 # This adds a vertical margin around the node elements 589 h += img.branch_vertical_margin 590 591 # Total width required by the node 592 w = sum([max(branch_length + node.img_style["size"], 593 faceblock["branch-top"].w + node.img_style["size"], 594 faceblock["branch-bottom"].w + node.img_style["size"], 595 ), 596 faceblock["branch-right"].w] 597 ) 598 599 # This breaks ultrametric tree visualization 600 #w += node.img_style["vt_line_width"] 601 602 # rightside faces region 603 item.facesRegion.setRect(0, 0, faceblock["branch-right"].w, h) 604 605 # Node region 606 item.nodeRegion.setRect(0, 0, w, h) 607 608 # This is the node total region covered by the node 609 item.fullRegion.setRect(0, 0, w, h) 610 611def render_node_content(node, n2i, n2f, img): 612 style = node.img_style 613 item = n2i[node] 614 item.content = _EmptyItem(item) 615 616 nodeR = item.nodeRegion 617 facesR = item.facesRegion 618 center = item.center 619 branch_length = item.branch_length 620 621 # Node points 622 ball_size = style["size"] 623 624 625 vlw = style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 626 627 # face_start_x = nodeR.width() - facesR.width() - vlw 628 face_start_x = max(0, nodeR.width() - facesR.width() - vlw) 629 ball_start_x = face_start_x - ball_size 630 631 if ball_size: 632 if node.img_style["shape"] == "sphere": 633 node_ball = _SphereItem(node) 634 elif node.img_style["shape"] == "circle": 635 node_ball = _CircleItem(node) 636 elif node.img_style["shape"] == "square": 637 node_ball = _RectItem(node) 638 639 node_ball.setPos(ball_start_x, center-(ball_size/2.0)) 640 641 #from qt4_gui import _BasicNodeActions 642 #node_ball.delegate = _BasicNodeActions() 643 #node_ball.setAcceptHoverEvents(True) 644 #node_ball.setCursor(Qt.PointingHandCursor) 645 646 else: 647 node_ball = None 648 649 # Branch line to parent 650 pen = QPen() 651 set_pen_style(pen, style["hz_line_type"]) 652 pen.setColor(QColor(style["hz_line_color"])) 653 pen.setWidth(style["hz_line_width"]) 654 pen.setCapStyle(Qt.FlatCap) 655 #pen.setCapStyle(Qt.RoundCap) 656 #pen.setCapStyle(Qt.SquareCap) 657 #pen.setJoinStyle(Qt.RoundJoin) 658 hz_line = _LineItem() 659 hz_line = _NodeLineItem(node) 660 hz_line.setPen(pen) 661 662 join_fix = 0 663 if img.mode == "c" and node.up and node.up.img_style["vt_line_width"]: 664 join_fix = node.up.img_style["vt_line_width"] 665 # fix_join_line = _LineItem() 666 # fix_join_line = _NodeLineItem(node) 667 # parent_style = node.up.img_style 668 # pen = QPen() 669 # pen.setColor(QColor(parent_style["vt_line_color"])) 670 # pen.setWidth(parent_style["hz_line_width"]) 671 # pen.setCapStyle(Qt.FlatCap) 672 # fix_join_line.setPen(pen) 673 # fix_join_line.setLine(-join_fix, center, join_fix, center) 674 # fix_join_line.setParentItem(item.content) 675 676 hz_line.setLine(-join_fix, center, branch_length, center) 677 678 if img.complete_branch_lines_when_necessary: 679 extra_line = _LineItem(branch_length, center, ball_start_x, center) 680 pen = QPen() 681 item.extra_branch_line = extra_line 682 set_pen_style(pen, img.extra_branch_line_type) 683 pen.setColor(QColor(img.extra_branch_line_color)) 684 pen.setCapStyle(Qt.FlatCap) 685 pen.setWidth(style["hz_line_width"]) 686 extra_line.setPen(pen) 687 else: 688 extra_line = None 689 690 # Attach branch-right faces to child 691 fblock_r = n2f[node]["branch-right"] 692 fblock_r.render() 693 fblock_r.setPos(face_start_x, center-fblock_r.h/2.0) 694 695 # Attach branch-bottom faces to child 696 fblock_b = n2f[node]["branch-bottom"] 697 fblock_b.render() 698 fblock_b.setPos(item.widths[0], center + style["hz_line_width"]/2.0) 699 700 # Attach branch-top faces to child 701 fblock_t = n2f[node]["branch-top"] 702 fblock_t.render() 703 fblock_t.setPos(item.widths[0], center - fblock_t.h - style["hz_line_width"]/2.0) 704 705 # Vertical line 706 if not _leaf(node): 707 if img.mode == "c": 708 vt_line = QGraphicsPathItem() 709 710 elif img.mode == "r": 711 vt_line = _LineItem(item) 712 first_child = node.children[0] 713 last_child = node.children[-1] 714 first_child_part = n2i[node.children[0]] 715 last_child_part = n2i[node.children[-1]] 716 c1 = first_child_part.start_y + first_child_part.center 717 c2 = last_child_part.start_y + last_child_part.center 718 fx = nodeR.width() - (vlw/2.0) 719 if first_child.img_style["hz_line_width"] > 0: 720 c1 -= (first_child.img_style["hz_line_width"] / 2.0) 721 if last_child.img_style["hz_line_width"] > 0: 722 c2 += (last_child.img_style["hz_line_width"] / 2.0) 723 vt_line.setLine(fx, c1, fx, c2) 724 725 pen = QPen() 726 set_pen_style(pen, style["vt_line_type"]) 727 pen.setColor(QColor(style["vt_line_color"])) 728 pen.setWidth(style["vt_line_width"]) 729 pen.setCapStyle(Qt.FlatCap) 730 #pen.setCapStyle(Qt.RoundCap) 731 #pen.setCapStyle(Qt.SquareCap) 732 vt_line.setPen(pen) 733 item.vt_line = vt_line 734 else: 735 vt_line = None 736 737 item.bg = QGraphicsItemGroup() 738 item.movable_items = [] #QGraphicsItemGroup() 739 item.static_items = [] #QGraphicsItemGroup() 740 741 # Items fow which coordinates are exported in the image map 742 item.mapped_items = [node_ball, fblock_r, fblock_b, fblock_t] 743 744 745 for i in [vt_line, extra_line, hz_line]: 746 if i: 747 #item.static_items.addToGroup(i) 748 item.static_items.append(i) 749 i.setParentItem(item.content) 750 for i in [node_ball, fblock_r, fblock_b, fblock_t]: 751 if i: 752 #item.movable_items.addToGroup(i) 753 item.movable_items.append(i) 754 i.setParentItem(item.content) 755 756 757 #item.movable_items.setParentItem(item.content) 758 #item.static_items.setParentItem(item.content) 759 760def set_pen_style(pen, line_style): 761 if line_style == 0: 762 pen.setStyle(Qt.SolidLine) 763 elif line_style == 1: 764 pen.setStyle(Qt.DashLine) 765 elif line_style == 2: 766 pen.setStyle(Qt.DotLine) 767 768def set_style(n, layout_func): 769 #if not isinstance(getattr(n, "img_style", None), NodeStyle): 770 # print "Style of", n.name ,"is None" 771 # n.set_style() 772 # n.img_style = NodeStyle() 773 774 n._temp_faces = _FaceAreas() 775 776 for func in layout_func: 777 func(n) 778 779def render_floatings(n2i, n2f, img, float_layer, float_behind_layer): 780 #floating_faces = [ [node, fb["float"]] for node, fb in n2f.iteritems() if "float" in fb] 781 782 for node, faces in six.iteritems(n2f): 783 face_set = [ [float_layer, faces.get("float", None)], 784 [float_behind_layer, faces.get("float-behind",None)]] 785 786 for parent_layer,fb in face_set: 787 if not fb: 788 continue 789 790 item = n2i[node] 791 fb.setParentItem(parent_layer) 792 793 try: 794 xtra = item.extra_branch_line.line().dx() 795 except AttributeError: 796 xtra = 0 797 798 if img.mode == "c": 799 # Floatings are positioned over branches 800 crender.rotate_and_displace(fb, item.rotation, fb.h, item.radius - item.nodeRegion.width() + xtra) 801 # Floatings are positioned starting from the node circle 802 #crender.rotate_and_displace(fb, item.rotation, fb.h, item.radius - item.nodeRegion.width()) 803 804 elif img.mode == "r": 805 start = item.branch_length + xtra - fb.w #if fb.w < item.branch_length else 0.0 806 fb.setPos(item.content.mapToScene(start, item.center - (fb.h/2.0))) 807 808 z = item.zValue() 809 if not img.children_faces_on_top: 810 z = -z 811 812 fb.setZValue(z) 813 fb.update_columns_size() 814 fb.render() 815 816def render_aligned_faces(img, mainRect, parent, n2i, n2f): 817 # Prepares and renders aligned face headers. Used to later 818 # place aligned faces 819 aligned_faces = [ [node, fb["aligned"]] for node, fb in six.iteritems(n2f)\ 820 if fb["aligned"].column2faces and _leaf(node)] 821 822 # If no aligned faces, just return an offset of 0 pixels 823 if not aligned_faces: 824 return 0 825 826 # Load header and footer 827 if img.mode == "r": 828 tree_end_x = mainRect.width() 829 830 fb_head = _FaceGroupItem(img.aligned_header, None) 831 fb_head.setParentItem(parent) 832 fb_foot = _FaceGroupItem(img.aligned_foot, None) 833 fb_foot.setParentItem(parent) 834 surroundings = [[None,fb_foot], [None, fb_head]] 835 mainRect.adjust(0, -fb_head.h, 0, fb_foot.h) 836 else: 837 tree_end_x = mainRect.width()/2.0 838 surroundings = [] 839 840 # Place aligned faces and calculates the max size of each 841 # column (needed to place column headers) 842 c2max_w = {} 843 maxh = 0 844 maxh_node = None 845 for node, fb in aligned_faces + surroundings: 846 if fb.h > maxh: 847 maxh = fb.h 848 maxh_node = node 849 for c, w in six.iteritems(fb.c2max_w): 850 c2max_w[c] = max(w, c2max_w.get(c,0)) 851 extra_width = sum(c2max_w.values()) 852 853 # If rect mode, render header and footer 854 if img.mode == "r": 855 if img.draw_aligned_faces_as_table: 856 fb_head.setup_grid(c2max_w) 857 fb_foot.setup_grid(c2max_w) 858 859 fb_head.render() 860 fb_head.setPos(tree_end_x, mainRect.top()) 861 fb_foot.render() 862 fb_foot.setPos(tree_end_x, mainRect.bottom()-fb_foot.h) 863 if img.orientation == 1: 864 fb_head.flip_hz() 865 fb_foot.flip_hz() 866 867 # if no scale provided in circular mode, optimal scale is expected 868 # to provide the correct ending point to start drawing aligned 869 # faces. 870 elif img.mode == "c" and (img.scale or img._scale == 0) and not img.allow_face_overlap: 871 angle = n2i[maxh_node].angle_span 872 rad, off = crender.get_min_radius(1, maxh, angle, tree_end_x) 873 extra_width += rad - tree_end_x 874 tree_end_x = rad 875 876 # Place aligned faces 877 for node, fb in aligned_faces: 878 item = n2i[node] 879 item.mapped_items.append(fb) 880 if img.draw_aligned_faces_as_table: 881 if img.aligned_table_style == 0: 882 fb.setup_grid(c2max_w, as_grid=True) 883 elif img.aligned_table_style == 1: 884 fb.setup_grid(c2max_w, as_grid=False) 885 886 fb.render() 887 fb.setParentItem(item.content) 888 if img.mode == "c": 889 if node.up in n2i: 890 x = tree_end_x - n2i[node.up].radius 891 else: 892 x = tree_end_x 893 #fb.moveBy(tree_end_x, 0) 894 elif img.mode == "r": 895 x = item.mapFromScene(tree_end_x, 0).x() 896 897 fb.setPos(x, item.center-(fb.h/2.0)) 898 899 if img.draw_guiding_lines and _leaf(node): 900 # -1 is to connect the two lines, otherwise there is a pixel in between 901 guide_line = _LineItem(item.nodeRegion.width()-1, item.center, x, item.center) 902 pen = QPen() 903 set_pen_style(pen, img.guiding_lines_type) 904 pen.setColor(QColor(img.guiding_lines_color)) 905 pen.setCapStyle(Qt.FlatCap) 906 pen.setWidth(node.img_style["hz_line_width"]) 907 guide_line.setPen(pen) 908 guide_line.setParentItem(item.content) 909 910 if img.mode == "c": 911 mainRect.adjust(-extra_width, -extra_width, extra_width, extra_width) 912 else: 913 mainRect.adjust(0, 0, extra_width, 0) 914 return extra_width 915 916def get_tree_img_map(n2i, x_scale=1, y_scale=1): 917 MOTIF_ITEMS = set([faces.QGraphicsTriangleItem, 918 faces.QGraphicsEllipseItem, 919 faces.QGraphicsDiamondItem, 920 faces.QGraphicsRectItem, 921 faces.QGraphicsRoundRectItem]) 922 node_list = [] 923 face_list = [] 924 node_areas = {} 925 #nid = 0 926 for n, main_item in six.iteritems(n2i): 927 #n.add_feature("_nid", str(nid)) 928 nid = n._nid 929 930 rect = main_item.mapToScene(main_item.fullRegion).boundingRect() 931 x1 = x_scale * rect.x() 932 y1 = y_scale * rect.y() 933 x2 = x_scale * (rect.x() + rect.width()) 934 y2 = y_scale * (rect.y() + rect.height()) 935 node_areas[nid] = [x1, y1, x2, y2] 936 937 for item in main_item.mapped_items: 938 if isinstance(item, _CircleItem) \ 939 or isinstance(item, _SphereItem) \ 940 or isinstance(item, _RectItem): 941 r = item.boundingRect() 942 rect = item.mapToScene(r).boundingRect() 943 x1 = x_scale * rect.x() 944 y1 = y_scale * rect.y() 945 x2 = x_scale * (rect.x() + rect.width()) 946 y2 = y_scale * (rect.y() + rect.height()) 947 node_list.append([x1, y1, x2, y2, nid, None]) 948 elif isinstance(item, _FaceGroupItem): 949 if item.column2faces: 950 for f in item.childItems(): 951 r = f.boundingRect() 952 rect = f.mapToScene(r).boundingRect() 953 x1 = x_scale * rect.x() 954 y1 = y_scale * rect.y() 955 x2 = x_scale * (rect.x() + rect.width()) 956 y2 = y_scale * (rect.y() + rect.height()) 957 if isinstance(f, _TextFaceItem): 958 face_list.append([x1, y1, x2, y2, nid, str(getattr(f, "face_label", f.text()))]) 959 elif isinstance(f, faces.SeqMotifRectItem): 960 #face_list.append([x1, y1, x2, y2, nid, str(getattr(f, "face_label", None))]) 961 for mf in f.childItems(): 962 r = mf.boundingRect() 963 rect = mf.mapToScene(r).boundingRect() 964 x1 = x_scale * rect.x() 965 y1 = y_scale * rect.y() 966 x2 = x_scale * (rect.x() + rect.width()) 967 y2 = y_scale * (rect.y() + rect.height()) 968 try: 969 label = "Motif:%s" %mf.childItems()[0].text 970 except Exception: 971 label = "" 972 face_list.append([x1, y1, x2, y2, nid, label]) 973 else: 974 face_list.append([x1, y1, x2, y2, nid, getattr(f, "face_label", None)]) 975 #nid += 1 976 977 return {"nodes": node_list, "faces": face_list, "node_areas": node_areas} 978 979#@tracktime 980def init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root): 981 # ::: Precalculate values ::: 982 visited = set() 983 to_visit = [] 984 to_visit.append(root_node) 985 last_rotation = img.arc_start 986 depth = 1 987 while to_visit: 988 node = to_visit[-1] 989 finished = True 990 if node not in n2i: 991 # Set style according to layout function 992 item = n2i[node] = _NodeItem(node, parent.tree_layer) 993 depth += 1 994 995 item.setZValue(depth) 996 if node is root_node and hide_root: 997 pass 998 else: 999 init_node_dimensions(node, item, n2f[node], img) 1000 #set_node_size(node, n2i, n2f, img) 1001 1002 if not _leaf(node): 1003 # visit children starting from left to right. Very 1004 # important!! check all children[-1] and children[0] 1005 for c in reversed(node.children): 1006 if c not in visited: 1007 to_visit.append(c) 1008 finished = False 1009 # :: pre-order code here :: 1010 if not finished: 1011 continue 1012 else: 1013 to_visit.pop(-1) 1014 visited.add(node) 1015 1016 # :: Post-order visits. Leaves are already visited here :: 1017 if img.mode == "c": 1018 if _leaf(node): 1019 crender.init_circular_leaf_item(node, n2i, n2f, last_rotation, rot_step) 1020 last_rotation += rot_step 1021 else: 1022 crender.init_circular_node_item(node, n2i, n2f) 1023 1024 elif img.mode == "r": 1025 if _leaf(node): 1026 rrender.init_rect_leaf_item(node, n2i, n2f) 1027 else: 1028 rrender.init_rect_node_item(node, n2i, n2f) 1029 1030 1031def init_node_dimensions(node, item, faceblock, img): 1032 """Calculates width and height of all different subparts and faces 1033 of a given node. Branch lengths are not taken into account, so some 1034 dimensions must be adjusted after setting a valid scale. 1035 """ 1036 1037 min_separation = img.min_leaf_separation 1038 1039 if _leaf(node): 1040 aligned_height = faceblock["aligned"].h 1041 aligned_width = faceblock["aligned"].w 1042 else: 1043 aligned_height = 0 1044 aligned_width = 0 1045 1046 ndist = 1.0 if img.force_topology else node.dist 1047 item.branch_length = (ndist * img._scale) if img._scale else 0 1048 ## Calculate dimensions of the different node regions 1049 ## 1050 ## 1051 ## | 1052 ## | ------ 1053 ## b-top --------- | | | 1054 ## xoff-------------- O |b-right| | |alg | 1055 ## b-bottom --------- | | | 1056 ## | ------ 1057 ## | 1058 ## 1059 ## 0 1 2 3 4 5 1060 ## 1061 1062 item.xoff = 0.0 1063 # widths 1064 w1 = max(faceblock["branch-bottom"].w, faceblock["branch-top"].w) 1065 w0 = item.branch_length - w1 if item.branch_length > w1 else 0 1066 w2 = node.img_style["size"] 1067 w3 = faceblock["branch-right"].w 1068 w4 = node.img_style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 1069 w5 = 0 1070 # heights 1071 h0 = node.img_style["hz_line_width"] 1072 h1 = node.img_style["hz_line_width"] + faceblock["branch-top"].h + faceblock["branch-bottom"].h 1073 h2 = node.img_style["size"] 1074 h3 = faceblock["branch-right"].h 1075 h4 = 0 1076 h5 = aligned_height 1077 1078 # This fixes the problem of miss-aligned branches in ultrametric trees. If 1079 # there is nothing between the hz line and the vt line, then I prevent 1080 # vt_line_width to add extra width to the node, so node distances are 1081 # preserved in the img. 1082 if w2 == 0 and w3 == 0: 1083 w4 = 0 1084 1085 # ignore face heights if requested 1086 if img.mode == "c" and img.allow_face_overlap: 1087 h1, h3, h5 = 0, 0, 0 1088 1089 item.heights = [h0, h1, h2, h3, h4, h5] 1090 item.widths = [w0, w1, w2, w3, w4, w5] 1091 1092 # Calculate total node size 1093 total_w = sum([w0, w1, w2, w3, w4, item.xoff]) # do not count aligned faces 1094 1095 if img.mode == "c": 1096 max_h = max(item.heights[:4] + [min_separation]) 1097 elif img.mode == "r": 1098 max_h = max(item.heights + [min_separation]) 1099 1100 max_h += img.branch_vertical_margin 1101 1102 # correct possible unbalanced block in branch faces 1103 h_imbalance = abs(faceblock["branch-top"].h - faceblock["branch-bottom"].h) 1104 if h_imbalance + h1 > max_h: 1105 max_h += h_imbalance 1106 1107 item.facesRegion.setRect(0, 0, w3, max_h) 1108 item.nodeRegion.setRect(0, 0, total_w, max_h) 1109 item.fullRegion.setRect(0, 0, total_w, max_h) 1110 1111def update_branch_lengths(tree, n2i, n2f, img): 1112 for node in tree.traverse("postorder", is_leaf_fn=_leaf): 1113 item = n2i[node] 1114 ndist = 1.0 if img.force_topology else node.dist 1115 item.branch_length = ndist * img._scale 1116 w0 = 0 1117 1118 if item.branch_length > item.widths[1]: 1119 w0 = item.widths[0] = item.branch_length - item.widths[1] 1120 item.nodeRegion.adjust(0, 0, w0, 0) 1121 1122 child_width = 0 1123 if not _leaf(node): 1124 for ch in node.children: 1125 child_width = max(child_width, n2i[ch].fullRegion.width()) 1126 if w0 and img.mode == "r": 1127 #n2i[ch].translate(w0, 0) # deprecated in qt4.8 1128 n2i[ch].moveBy(w0, 0) 1129 item.fullRegion.setWidth(item.nodeRegion.width() + child_width) 1130 1131def init_tree_style(t, ts): 1132 custom_ts = True 1133 if not ts: 1134 custom_ts = False 1135 ts = TreeStyle() 1136 1137 if not ts.layout_fn: 1138 cl = t.__class__ 1139 try: 1140 ts_template = _DEFAULT_STYLE[cl] 1141 except KeyError as e: 1142 pass 1143 else: 1144 if not custom_ts: 1145 apply_template(ts, ts_template) 1146 else: 1147 ts.layout_fn = ts_template.get("layout_fn", None) 1148 1149 return ts 1150 1151