Source code for schrodinger.ui.sequencealignment.tree_area

"""
This class implements a tree area widget that actually displays and manipulates
phylogenetic trees.

Copyright Schrodinger, LLC. All rights reserved.
"""

# Contributors: Piotr Rotkiewicz

from past.utils import old_div

from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets

from . import constants
from . import utils


[docs]class TreeArea(QtWidgets.QWidget): """ This class implements a tree area widget that performs actual display and manipulation of a neighbor joining tree associated with the sequences. """
[docs] def __init__(self, parent=None): # Initialize base class. QtWidgets.QWidget.__init__(self, parent) self.viewer = None # Parent sequence viewer widget self.font_size = 11 # of widget self.setMinimumWidth(100) #after resizing below the widget collapses. #: In current implementation all branches have identical lengths. self.branch_length = 0 # of Tree self.margin = 0 #Distance between widget edges and the tree. self.setMouseTracking(True) # for tooltips.
[docs] def setViewer(self, viewer): """ Set parent widget. :type viewer: `SequenceViewer` :param viewer: parent sequence viewer widget """ self.viewer = viewer
[docs] def showToolTip(self, pos, node): """ Draws a node tooltip. :type node: `TreeNode` :param node: node """ if node and node.name and self.viewer.has_tooltips: tip = "Node: " + node.name QtWidgets.QToolTip.showText(pos, tip) else: QtWidgets.QToolTip.hideText()
[docs] def mouseMoveEvent(self, event): """ Handles Qt mouse move event. :type event: QMouseMoveEvent :param event: mouse move event """ if not self.viewer.has_tooltips: return x = event.x() y = event.y() if self.viewer.sequence_group.tree: node = self.viewer.sequence_group.tree.findNode(x, y, self.box_size) self.showToolTip(event.globalPos(), node)
[docs] def mousePressEvent(self, event): """ Handles Qt mouse press event. :type event: QMousePressEvent :param event: mouse press event """ x = event.x() y = event.y() # Initially, reset the clicked node. self.clicked_node = None # Find the node the user clicked on. if self.viewer.sequence_group.tree: self.clicked_node = self.viewer.sequence_group.tree.findNode( x, y, self.box_size) # If right mouse button was pressed, just popup a menu and quit. if event.buttons() & QtCore.Qt.RightButton: if self.viewer.tree_popup_menu: self.viewer.tree_popup_menu(event.globalPos(), self.clicked_node) return # Left mouse button action depends on a current mode. if event.buttons() & QtCore.Qt.LeftButton: if not (event.modifiers() & QtCore.Qt.ControlModifier): self.viewer.sequence_group.unselectAllSequences() if self.clicked_node: self.swapBranches() return # Repaint the viewer. self.viewer.repaint()
[docs] def swapBranches(self): """ This method swaps branches of a clicked node. """ if self.clicked_node: # Swap branches. self.clicked_node.swap() # Change sequence order according to the tree. self.viewer.sequence_group.sortByTreeOrder() # Sequence order has changed, so we need to re-generate rows. self.viewer.generateRows() # Repaint the viewer. self.viewer.repaint() # Move mouse cursor to the new node position. x, y = self.clicked_node.original_node.coordinates widget_pos = self.mapToGlobal(self.pos()) x += widget_pos.x() y += widget_pos.y() QtGui.QCursor.setPos(x, y) # Draw tooltip. self.showToolTip(QtCore.QPoint(x, y), self.clicked_node)
[docs] def selectSequences(self): """ Selects sequences that belong to a clicked node. """ if self.clicked_node: # Recursively select sequences of the clicked node. self.clicked_node.selectSequences() # Repaint the viewer. self.viewer.repaint()
[docs] def hideSequences(self): """ Hides sequences that belong to the clicked node. """ if self.clicked_node: # Recursively hide sequences of the clicked node. self.clicked_node.hideSequences() # Regenerate rows. self.viewer.generateRows() # Repaint the viewer. self.viewer.repaint()
[docs] def drawTree(self, painter, tree, position, level): """ Draws the tree using a specified painter at a given vertical position. :type painter: QPainter :param painter: target painter surface :type tree: `TreeNode` :param tree: tree to be drawn :type position: int :param position: vertical tree position in painter coordinates :type level: int :param level: drawing recursion level """ n_branches = 0 avg_position = 0 min_position = self.height() max_position = 0 for branch in tree.branches: if len(branch.branches) > 0: position, branch_position, min_pos, max_pos = self.drawTree( painter, branch, position, level + 1) x_pos = self.margin + branch.level * \ self.branch_length - self.branch_length painter.drawLine(x_pos, branch_position, x_pos + self.branch_length, branch_position) painter.drawLine(x_pos + self.branch_length, min_pos, x_pos + self.branch_length, max_pos) rx = x_pos + self.branch_length ry = branch_position branch.original_node.coordinates = None if min_pos < max_pos: # Store the node coordinates for mouse interaction. branch.original_node.coordinates = (rx, ry) # Draw a node box. painter.drawEllipse(QtCore.QPoint(rx, ry), self.half_box_size, self.half_box_size) if branch_position < min_position: min_position = branch_position if branch_position > max_position: max_position = branch_position n_branches += 1 avg_position += branch_position else: # This is a leaf. x_pos = self.margin + tree.level * self.branch_length painter.drawLine(x_pos, position, self.width() - self.margin, position) n_branches += 1 avg_position += position if position < min_position: min_position = position if position > max_position: max_position = position position += self.viewer.font_height if not branch.sequences[0].collapsed: if not self.viewer.group_annotations: for child in branch.sequences[0].children: if child.visible: position += self.viewer.font_height if n_branches > 0: avg_position /= n_branches if level == 0: rx = self.margin ry = 0.5 * (min_position + max_position) tree.original_node.coordinates = (rx, ry) painter.drawLine(x_pos, min_position, x_pos, max_position) painter.drawEllipse(QtCore.QPoint(rx, ry), self.half_box_size, self.half_box_size) return position, avg_position, min_position, max_position
[docs] def paintEvent(self, event): """ Handles Qt paint event for the `TreeArea`. :type event: QPaintEvent :param event: Qt paint event """ painter = QtGui.QPainter(self) try: self.paintTreeArea(painter, event) except: # This should never happen, however, just to be safe # we ignore the painting exception. Uncomment the line below # for debugging. if constants.MSV_DEBUG: utils.print_error("Tree area painter exception.")
[docs] def paintTreeArea(self, painter, event, clip=True): """ Actually paints the tree area on a specified painter. :type painter: QPainter :param painter: painter used to draw the tree area :type event: QPaintEvent :param event: Qt paint event """ rows = self.viewer.rows font_height = self.viewer.font_height font_xheight = self.viewer.font_xheight self.half_box_size = int(self.viewer.font_height * 0.125) + 1 if self.half_box_size % 2: self.half_box_size += 1 self.box_size = self.half_box_size * 2 width = self.width() height = self.height() painter.setPen(QtCore.Qt.white) painter.setBrush(QtGui.QBrush(QtCore.Qt.white)) if clip: painter.setClipRect(0, 0, width - 1, height - 1) painter.drawRect(0, 0, width, height) y_position = self.viewer.margin painter.setPen(QtCore.Qt.black) tree = self.viewer.sequence_group.tree if not tree: return self.margin = self.viewer.margin + 5 separator_pen = QtGui.QPen(QtCore.Qt.gray) separator_pen.setStyle(QtCore.Qt.DotLine) self.branch_length = \ old_div((self.width() - 2 * self.margin), float(tree.maxLevel() + 1)) self.tree_line_pen = QtGui.QPen(QtCore.Qt.black) self.tree_line_pen.setStyle(QtCore.Qt.SolidLine) self.node_box_brush = QtGui.QBrush(QtCore.Qt.black) tree_drawn = False for row_idx, row in enumerate(rows): if row_idx < self.viewer.top_row and clip: continue if y_position > height and clip: break # Display a single row. seq, start, end, row_height = row # Display the sequence only if it is visible. if seq and seq.visible: if seq.type == constants.SEQ_AMINO_ACIDS: if tree and not tree_drawn: painter.setPen(self.tree_line_pen) painter.setBrush(self.node_box_brush) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) visible_tree = tree.getVisibleTree() self.drawTree(painter, visible_tree, y_position + 0.5 * font_height, 0) painter.setRenderHint(QtGui.QPainter.Antialiasing, False) tree_drawn = True if seq.type == constants.SEQ_SEPARATOR: painter.setPen(separator_pen) painter.drawLine( 0, y_position + font_height - font_xheight - 2, width, y_position + font_height - font_xheight - 2) tree_drawn = False y_position = y_position + font_height * row_height