def init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root): # ::: Precalculate values ::: visited = set() to_visit = [] to_visit.append(root_node) last_rotation = img.arc_start depth = 1 while to_visit: node = to_visit[-1] finished = True if node not in n2i: # Set style according to layout function item = n2i[node] = _NodeItem(node, parent.tree_layer) depth += 1 item.setZValue(depth) if node is root_node and hide_root: pass else: init_node_dimensions(node, item, n2f[node], img) #set_node_size(node, n2i, n2f, img) if not _leaf(node): # visit children starting from left to right. Very # important!! check all children[-1] and children[0] for c in reversed(node.children): if c not in visited: to_visit.append(c) finished = False # :: pre-order code here :: if not finished: continue else: to_visit.pop(-1) visited.add(node) # :: Post-order visits. Leaves are already visited here :: if img.mode == "c": if _leaf(node): crender.init_circular_leaf_item(node, n2i, n2f, last_rotation, rot_step) last_rotation += rot_step else: crender.init_circular_node_item(node, n2i, n2f) elif img.mode == "r": if _leaf(node): rrender.init_rect_leaf_item(node, n2i, n2f) else: rrender.init_rect_node_item(node, n2i, n2f)
def get_partition_center(n, n2i, n2f): down_h = n2f[n]["branch-bottom"].h up_h = n2f[n]["branch-top"].h #right_h = max(n2f[n]["branch-right"].h, n.img_style["size"]) /2 right_h = n2i[n].nodeRegion.height() / 2 up_h = max(right_h, up_h) down_h = max(right_h, down_h) fullR = n2i[n].fullRegion if _leaf(n): center = fullR.height() / 2 else: first_child_part = n2i[n.children[0]] last_child_part = n2i[n.children[-1]] c1 = first_child_part.start_y + first_child_part.center c2 = last_child_part.start_y + last_child_part.center center = c1 + ((c2 - c1) / 2) if up_h > center: center = up_h elif down_h > fullR.height() - center: center = fullR.height() - down_h return center
def get_partition_center(n, n2i, n2f): down_h = n2f[n]["branch-bottom"].h up_h = n2f[n]["branch-top"].h #right_h = max(n2f[n]["branch-right"].h, n.img_style["size"]) /2 right_h = n2i[n].nodeRegion.height()/2 up_h = max(right_h, up_h) down_h = max(right_h, down_h) fullR = n2i[n].fullRegion if _leaf(n): center = fullR.height()/2 else: first_child_part = n2i[n.children[0]] last_child_part = n2i[n.children[-1]] c1 = first_child_part.start_y + first_child_part.center c2 = last_child_part.start_y + last_child_part.center center = c1 + ((c2-c1)/2) if up_h > center: center = up_h elif down_h > fullR.height() - center: center = fullR.height() - down_h return center
def render_backgrounds(img, mainRect, bg_layer, n2i, n2f): if img.mode == "c": max_r = mainRect.width()/2.0 else: max_r = mainRect.width() for node, item in n2i.iteritems(): if _leaf(node): first_c = n2i[node] last_c = n2i[node] else: first_c = n2i[node.children[0]] last_c = n2i[node.children[-1]] if img.mode == "c": h = item.effective_height angle_start = first_c.full_start angle_end = last_c.full_end parent_radius = getattr(n2i.get(node.up, None), "radius", 0) base = parent_radius + item.nodeRegion.width() if node.img_style["node_bgcolor"].upper() != "#FFFFFF": bg1 = crender._ArcItem() r = math.sqrt(base**2 + h**2) bg1.set_arc(0, 0, parent_radius, r, angle_start, angle_end) bg1.setParentItem(item.content.bg) bg1.setPen(QtGui.QPen(QtGui.QColor(node.img_style["node_bgcolor"]))) bg1.setBrush(QtGui.QBrush(QtGui.QColor(node.img_style["node_bgcolor"]))) if node.img_style["faces_bgcolor"].upper() != "#FFFFFF": bg2 = crender._ArcItem() r = math.sqrt(base**2 + h**2) bg2.set_arc(0, 0, parent_radius, item.radius, angle_start, angle_end) bg2.setParentItem(item.content) bg2.setPen(QtGui.QPen(QtGui.QColor(node.img_style["faces_bgcolor"]))) bg2.setBrush(QtGui.QBrush(QtGui.QColor(node.img_style["faces_bgcolor"]))) if node.img_style["bgcolor"].upper() != "#FFFFFF": bg = crender._ArcItem() bg.set_arc(0, 0, parent_radius, max_r, angle_start, angle_end) bg.setPen(QtGui.QPen(QtGui.QColor(node.img_style["bgcolor"]))) bg.setBrush(QtGui.QBrush(QtGui.QColor(node.img_style["bgcolor"]))) bg.setParentItem(bg_layer) bg.setZValue(item.zValue()) if img.mode == "r": if node.img_style["bgcolor"].upper() != "#FFFFFF": bg = QtGui.QGraphicsRectItem() pos = item.content.mapToScene(0, 0) bg.setPos(pos.x(), pos.y()) bg.setRect(0, 0, max_r-pos.x(), item.fullRegion.height()) bg.setPen(QtGui.QPen(QtGui.QColor(node.img_style["bgcolor"]))) bg.setBrush(QtGui.QBrush(QtGui.QColor(node.img_style["bgcolor"]))) bg.setParentItem(bg_layer) bg.setZValue(item.zValue())
def update_branch_lengths(tree, n2i, n2f, img): for node in tree.traverse("postorder", is_leaf_fn=_leaf): item = n2i[node] ndist = 1.0 if img.force_topology else node.dist item.branch_length = ndist * img._scale w0 = 0 if item.branch_length > item.widths[1]: w0 = item.widths[0] = item.branch_length - item.widths[1] item.nodeRegion.adjust(0, 0, w0, 0) child_width = 0 if not _leaf(node): for ch in node.children: child_width = max(child_width, n2i[ch].fullRegion.width()) if w0 and img.mode == "r": n2i[ch].translate(w0, 0) item.fullRegion.setWidth(item.nodeRegion.width() + child_width)
def render_node_content(node, n2i, n2f, img): style = node.img_style item = n2i[node] item.content = _EmptyItem(item) nodeR = item.nodeRegion facesR = item.facesRegion center = item.center branch_length = item.branch_length # Node points ball_size = style["size"] vlw = style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 face_start_x = nodeR.width() - facesR.width() - vlw ball_start_x = face_start_x - ball_size if ball_size: if node.img_style["shape"] == "sphere": node_ball = _SphereItem(node) elif node.img_style["shape"] == "circle": node_ball = _CircleItem(node) elif node.img_style["shape"] == "square": node_ball = _RectItem(node) node_ball.setPos(ball_start_x, center-(ball_size/2.0)) #from qt4_gui import _BasicNodeActions #node_ball.delegate = _BasicNodeActions() #node_ball.setAcceptsHoverEvents(True) #node_ball.setCursor(QtCore.Qt.PointingHandCursor) else: node_ball = None # Branch line to parent pen = QtGui.QPen() set_pen_style(pen, style["hz_line_type"]) pen.setColor(QtGui.QColor(style["hz_line_color"])) pen.setWidth(style["hz_line_width"]) pen.setCapStyle(QtCore.Qt.FlatCap) #pen.setCapStyle(QtCore.Qt.RoundCap) #pen.setCapStyle(QtCore.Qt.SquareCap) #pen.setJoinStyle(QtCore.Qt.RoundJoin) hz_line = _LineItem() hz_line = _NodeLineItem(node) hz_line.setPen(pen) join_fix = 0 if node.up and node.up.img_style["vt_line_width"]: join_fix = node.up.img_style["vt_line_width"] hz_line.setLine(-join_fix, center, branch_length, center) if img.complete_branch_lines_when_necessary: extra_line = _LineItem(branch_length, center, ball_start_x, center) pen = QtGui.QPen() item.extra_branch_line = extra_line set_pen_style(pen, img.extra_branch_line_type) pen.setColor(QtGui.QColor(img.extra_branch_line_color)) pen.setCapStyle(QtCore.Qt.FlatCap) pen.setWidth(style["hz_line_width"]) extra_line.setPen(pen) else: extra_line = None # Attach branch-right faces to child fblock_r = n2f[node]["branch-right"] fblock_r.render() fblock_r.setPos(face_start_x, center-fblock_r.h/2.0) # Attach branch-bottom faces to child fblock_b = n2f[node]["branch-bottom"] fblock_b.render() fblock_b.setPos(item.widths[0], center + style["hz_line_width"]/2.0) # Attach branch-top faces to child fblock_t = n2f[node]["branch-top"] fblock_t.render() fblock_t.setPos(item.widths[0], center - fblock_t.h - style["hz_line_width"]/2.0) # Vertical line if not _leaf(node): if img.mode == "c": vt_line = QtGui.QGraphicsPathItem() elif img.mode == "r": vt_line = _LineItem(item) first_child = node.children[0] last_child = node.children[-1] first_child_part = n2i[node.children[0]] last_child_part = n2i[node.children[-1]] c1 = first_child_part.start_y + first_child_part.center c2 = last_child_part.start_y + last_child_part.center fx = nodeR.width() - (vlw/2.0) if first_child.img_style["hz_line_width"] > 0: c1 -= (first_child.img_style["hz_line_width"] / 2.0) if last_child.img_style["hz_line_width"] > 0: c2 += (last_child.img_style["hz_line_width"] / 2.0) vt_line.setLine(fx, c1, fx, c2) pen = QtGui.QPen() set_pen_style(pen, style["vt_line_type"]) pen.setColor(QtGui.QColor(style["vt_line_color"])) pen.setWidth(style["vt_line_width"]) pen.setCapStyle(QtCore.Qt.FlatCap) #pen.setCapStyle(QtCore.Qt.RoundCap) #pen.setCapStyle(QtCore.Qt.SquareCap) vt_line.setPen(pen) item.vt_line = vt_line else: vt_line = None item.bg = QtGui.QGraphicsItemGroup() item.movable_items = [] #QtGui.QGraphicsItemGroup() item.static_items = [] #QtGui.QGraphicsItemGroup() # Items fow which coordinates are exported in the image map item.mapped_items = [node_ball, fblock_r, fblock_b, fblock_t] for i in [node_ball, fblock_r, fblock_b, fblock_t]: if i: #item.movable_items.addToGroup(i) item.movable_items.append(i) i.setParentItem(item.content) for i in [vt_line, extra_line, hz_line]: if i: #item.static_items.addToGroup(i) item.static_items.append(i) i.setParentItem(item.content)
def set_node_size(node, n2i, n2f, img): scale = img._scale min_separation = img.min_leaf_separation item = n2i[node] if img.force_topology: branch_length = item.branch_length = 25 else: branch_length = item.branch_length = float(node.dist * scale) # Organize faces by groups #faceblock = update_node_faces(node, n2f, img) faceblock = n2f[node] aligned_height = 0 if _leaf(node): if img.mode == "r": aligned_height = faceblock["aligned"].h elif img.mode == "c": # aligned faces in circular mode are adjusted afterwords. The # min radius of the largest aligned faces will be calculated. pass # Total height required by the node. I cannot sum up the height of # all elements, since the position of some of them are forced to # be on top or at the bottom of branches. This fact can produce # and unbalanced nodeRegion center. Here, I only need to know # about the imbalance size to correct node height. The center will # be calculated later according to the parent position. top_half_h = ( (node.img_style["size"]/2.0) + node.img_style["hz_line_width"]/2.0 + faceblock["branch-top"].h ) bottom_half_h =( (node.img_style["size"]/2.0) + node.img_style["hz_line_width"]/2.0 + faceblock["branch-bottom"].h ) h1 = top_half_h + bottom_half_h h2 = max(faceblock["branch-right"].h, \ aligned_height, \ min_separation ) h = max(h1, h2) imbalance = abs(top_half_h - bottom_half_h) if imbalance > h2/2.0: h += imbalance - (h2/2.0) # This adds a vertical margin around the node elements h += img.branch_vertical_margin # Total width required by the node w = sum([max(branch_length + node.img_style["size"], faceblock["branch-top"].w + node.img_style["size"], faceblock["branch-bottom"].w + node.img_style["size"], ), faceblock["branch-right"].w] ) w += node.img_style["vt_line_width"] # rightside faces region item.facesRegion.setRect(0, 0, faceblock["branch-right"].w, h) # Node region item.nodeRegion.setRect(0, 0, w, h) # This is the node total region covered by the node item.fullRegion.setRect(0, 0, w, h)
def render(root_node, img, hide_root=False): '''main render function. hide_root option is used when render trees as Faces ''' mode = img.mode orientation = img.orientation arc_span = img.arc_span layout_fn = img._layout_handler parent = _TreeItem() n2i = parent.n2i # node to items n2f = parent.n2f # node to faces parent.bg_layer = _EmptyItem(parent) parent.tree_layer = _EmptyItem(parent) parent.float_layer = _EmptyItem(parent) parent.float_behind_layer = _EmptyItem(parent) TREE_LAYERS = [parent.bg_layer, parent.tree_layer, parent.float_layer, parent.float_behind_layer] parent.bg_layer.setZValue(0) parent.tree_layer.setZValue(2) parent.float_behind_layer.setZValue(1) parent.float_layer.setZValue(3) # This could be used to handle aligned faces in internal # nodes. virtual_leaves = 0 if img.show_branch_length: bl_face = faces.AttrFace("dist", fsize=8, ftype="Arial", fgcolor="black", formatter = "%0.3g") if img.show_branch_support: su_face = faces.AttrFace("support", fsize=8, ftype="Arial", fgcolor="darkred", formatter = "%0.3g") if img.show_leaf_name: na_face = faces.AttrFace("name", fsize=10, ftype="Arial", fgcolor="black") for n in root_node.traverse(is_leaf_fn=_leaf): set_style(n, layout_fn) if img.show_branch_length: faces.add_face_to_node(bl_face, n, 0, position="branch-top") if not _leaf(n) and img.show_branch_support: faces.add_face_to_node(su_face, n, 0, position="branch-bottom") if _leaf(n) and img.show_leaf_name: faces.add_face_to_node(na_face, n, 0, position="branch-right") if _leaf(n):# or len(n.img_style["_faces"]["aligned"]): virtual_leaves += 1 update_node_faces(n, n2f, img) rot_step = float(arc_span) / virtual_leaves #rot_step = float(arc_span) / len([n for n in root_node.traverse() if _leaf(n)]) # Calculate optimal branch length if img._scale is not None: init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) elif img.scale is None: # create items and calculate node dimensions skipping branch lengths init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) if mode == 'r': if img.optimal_scale_level == "full": scales = [(i.widths[1]/n.dist) for n,i in n2i.iteritems() if n.dist] img._scale = max(scales) if scales else 0.0 else: farthest, dist = root_node.get_farthest_leaf(topology_only=img.force_topology) img._scale = img.tree_width / dist if dist else 0.0 update_branch_lengths(root_node, n2i, n2f, img) else: img._scale = crender.calculate_optimal_scale(root_node, n2i, rot_step, img) #print "OPTIMAL circular scale", img._scale update_branch_lengths(root_node, n2i, n2f, img) init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) else: # create items and calculate node dimensions CONSIDERING branch lengths img._scale = img.scale init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) #print "USING scale", img._scale # Draw node content for node in root_node.traverse(is_leaf_fn=_leaf): if node is not root_node or not hide_root: render_node_content(node, n2i, n2f, img) # Adjust content to rect or circular layout mainRect = parent.rect() if mode == "c": tree_radius = crender.render_circular(root_node, n2i, rot_step) mainRect.adjust(-tree_radius, -tree_radius, tree_radius, tree_radius) else: iwidth = n2i[root_node].fullRegion.width() iheight = n2i[root_node].fullRegion.height() mainRect.adjust(0, 0, iwidth, iheight) tree_radius = iwidth # Add extra layers: aligned faces, floating faces, node # backgrounds, etc. The order by which the following methods are # called IS IMPORTANT render_floatings(n2i, n2f, img, parent.float_layer, parent.float_behind_layer) aligned_region_width = render_aligned_faces(img, mainRect, parent.tree_layer, n2i, n2f) render_backgrounds(img, mainRect, parent.bg_layer, n2i, n2f) # rotate if necessary in circular images. flip and adjust if mirror orientation. adjust_faces_to_tranformations(img, mainRect, n2i, n2f, TREE_LAYERS) # Rotate main image if necessary parent.setRect(mainRect) parent.setPen(QtGui.QPen(QtCore.Qt.NoPen)) if img.rotation: rect = parent.boundingRect() x = rect.x() + (rect.width()/2.0) y = rect.y() + (rect.height()/2.0) parent.setTransform(QtGui.QTransform().translate(x, y).rotate(img.rotation).translate(-x, -y)) # Creates the main tree item that will act as frame for the whole image frame = QtGui.QGraphicsRectItem() parent.setParentItem(frame) mainRect = parent.mapToScene(mainRect).boundingRect() mainRect.adjust(-img.margin_left, -img.margin_top, \ img.margin_right, img.margin_bottom) # Fix negative coordinates, so main item always starts at 0,0 topleft = mainRect.topLeft() _x = abs(topleft.x()) if topleft.x() < 0 else 0 _y = abs(topleft.y()) if topleft.y() < 0 else 0 if _x or _y: parent.moveBy(_x, _y) mainRect.adjust(_x, _y, _x, _y) # Add extra components and adjust mainRect to them add_legend(img, mainRect, frame) add_title(img, mainRect, frame) add_scale(img, mainRect, frame) frame.setRect(mainRect) # Draws a border around the tree if not img.show_border: frame.setPen(QtGui.QPen(QtCore.Qt.NoPen)) else: frame.setPen(QtGui.QPen(QtGui.QColor("black"))) return frame, n2i, n2f
def render_circular(root_node, n2i, rot_step): to_visit = [] to_visit.append(root_node) max_r = 0.0 while to_visit: node = to_visit.pop(0) if not _leaf(node): to_visit.extend(node.children) item = n2i[node] w = item.nodeRegion.width() h = item.effective_height if node is not root_node: parent_radius = n2i[node.up].radius else: parent_radius = 0 if _leaf(node): angle = rot_step else: angle = item.angle_span #full_angle = angle #full_angle = abs(item.full_end - item.full_start) r, xoffset = get_min_radius(w, h, angle, parent_radius) rotate_and_displace(item.content, item.rotation, h, parent_radius) item.radius = r max_r = max(max_r, r) if not _leaf(node): first_c = n2i[node.children[0]] last_c = n2i[node.children[-1]] # Vertical arc Line rot_end = n2i[node.children[-1]].rotation rot_start = n2i[node.children[0]].rotation C = item.vt_line C.setParentItem(item) path = QtGui.QPainterPath() # Counter clock wise path.arcMoveTo(-r, -r, r * 2, r * 2, 360 - rot_start - angle) path.arcTo(-r, -r, r*2, r * 2, 360 - rot_start - angle, angle) # Faces C.setPath(path) item.static_items.append(C) if hasattr(item, "content"): # If applies, it sets the length of the extra branch length if item.extra_branch_line: xtra = item.extra_branch_line.line().dx() if xtra > 0: xtra = xoffset + xtra else: xtra = xoffset item.extra_branch_line.setLine(item.branch_length, item.center,\ item.branch_length + xtra , item.center) item.nodeRegion.setWidth(item.nodeRegion.width()+xtra) # And moves elements if xoffset: for i in item.movable_items: i.moveBy(xoffset, 0) n2i[root_node].max_r = max_r return max_r
def render_circular(root_node, n2i, rot_step): max_r = 0.0 for node in root_node.traverse('preorder', is_leaf_fn=_leaf): item = n2i[node] w = sum(item.widths[1:5]) h = item.effective_height parent_radius = n2i[node.up].radius if node.up else item.xoff angle = rot_step if _leaf(node) else item.angle_span if hasattr(item, "radius"): r = item.radius xoffset = 0 else: r, xoffset = get_min_radius(w, h, angle, parent_radius + item.widths[0]) item.radius = r node.add_features(rad=item.radius) #if xoffset: # DEBUG ONLY. IF Scale is correct, this should not be printed # print "Offset detected in node", xoffset rotate_and_displace(item.content, item.rotation, h, parent_radius) max_r = max(max_r, r) if not _leaf(node) and len(node.children) > 1: first_c = n2i[node.children[0]] last_c = n2i[node.children[-1]] # Vertical arc Line rot_end = n2i[node.children[-1]].rotation rot_start = n2i[node.children[0]].rotation rot_span = abs(rot_end - rot_start) C = item.vt_line C.setParentItem(item) path = QtGui.QPainterPath() # Counter clock wise start = r - node.img_style["vt_line_width"]/2 path.arcMoveTo(-start, -start, start * 2, start * 2, 360 - rot_start - rot_span) path.arcTo(-start, -start, start * 2, start * 2, 360 - rot_start - rot_span, rot_span) # Faces C.setPath(path) item.static_items.append(C) if hasattr(item, "content"): # If applies, it sets the length of the extra branch length if item.extra_branch_line: xtra = item.extra_branch_line.line().dx() if xtra > 0: xtra = xoffset + xtra else: xtra = xoffset item.extra_branch_line.setLine(item.branch_length, item.center,\ item.branch_length + xtra , item.center) item.nodeRegion.setWidth(item.nodeRegion.width()+xtra) # And moves elements if xoffset: for i in item.movable_items: i.moveBy(xoffset, 0) n2i[root_node].max_r = max_r return max_r
def init_node_dimensions(node, item, faceblock, img): """Calculates width and height of all different subparts and faces of a given node. Branch lengths are not taken into account, so some dimensions must be adjusted after setting a valid scale. """ min_separation = img.min_leaf_separation if _leaf(node): aligned_height = faceblock["aligned"].h aligned_width = faceblock["aligned"].w else: aligned_height = 0 aligned_width = 0 ndist = 1.0 if img.force_topology else node.dist item.branch_length = (ndist * img._scale) if img._scale else 0 ## Calculate dimensions of the different node regions ## ## ## | ## | ------ ## b-top --------- | | | ## xoff-------------- O |b-right| | |alg | ## b-bottom --------- | | | ## | ------ ## | ## ## 0 1 2 3 4 5 ## item.xoff = 0.0 # widths w1 = max(faceblock["branch-bottom"].w, faceblock["branch-top"].w) w0 = item.branch_length - w1 if item.branch_length > w1 else 0 w2 = node.img_style["size"] w3 = faceblock["branch-right"].w w4 = node.img_style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 w5 = 0 # heights h0 = node.img_style["hz_line_width"] h1 = node.img_style["hz_line_width"] + faceblock["branch-top"].h + faceblock["branch-bottom"].h h2 = node.img_style["size"] h3 = faceblock["branch-right"].h h4 = 0 h5 = aligned_height # ignore face heights if requested if img.mode == "c" and img.allow_face_overlap: h1, h3, h5 = 0, 0, 0 item.heights = [h0, h1, h2, h3, h4, h5] item.widths = [w0, w1, w2, w3, w4, w5] # Calculate total node size total_w = sum([w0, w1, w2, w3, w4, item.xoff]) # do not count aligned faces if img.mode == "c": max_h = max(item.heights[:4] + [min_separation]) elif img.mode == "r": max_h = max(item.heights + [min_separation]) max_h += img.branch_vertical_margin # correct possible unbalanced block in branch faces h_imbalance = abs(faceblock["branch-top"].h - faceblock["branch-bottom"].h) if h_imbalance + h1 > max_h: max_h += h_imbalance item.facesRegion.setRect(0, 0, w3, max_h) item.nodeRegion.setRect(0, 0, total_w, max_h) item.fullRegion.setRect(0, 0, total_w, max_h)
def render(root_node, img, hide_root=False): '''main render function. hide_root option is used when render trees as Faces ''' mode = img.mode orientation = img.orientation arc_span = img.arc_span layout_fn = img._layout_handler parent = _TreeItem() n2i = parent.n2i # node to items n2f = parent.n2f # node to faces parent.bg_layer = _EmptyItem(parent) parent.tree_layer = _EmptyItem(parent) parent.float_layer = _EmptyItem(parent) parent.float_behind_layer = _EmptyItem(parent) TREE_LAYERS = [parent.bg_layer, parent.tree_layer, parent.float_layer, parent.float_behind_layer] parent.bg_layer.setZValue(0) parent.tree_layer.setZValue(2) parent.float_behind_layer.setZValue(1) parent.float_layer.setZValue(3) # This could be used to handle aligned faces in internal # nodes. virtual_leaves = 0 if img.show_branch_length: bl_face = faces.AttrFace("dist", fsize=8, ftype="Arial", fgcolor="black", formatter = "%0.3g") if img.show_branch_support: su_face = faces.AttrFace("support", fsize=8, ftype="Arial", fgcolor="darkred", formatter = "%0.3g") if img.show_leaf_name: na_face = faces.AttrFace("name", fsize=10, ftype="Arial", fgcolor="black") for n in root_node.traverse(is_leaf_fn=_leaf): set_style(n, layout_fn) if img.show_branch_length: faces.add_face_to_node(bl_face, n, 0, position="branch-top") if not _leaf(n) and img.show_branch_support: faces.add_face_to_node(su_face, n, 0, position="branch-bottom") if _leaf(n) and n.name and img.show_leaf_name: faces.add_face_to_node(na_face, n, 0, position="branch-right") if _leaf(n):# or len(n.img_style["_faces"]["aligned"]): virtual_leaves += 1 update_node_faces(n, n2f, img) rot_step = float(arc_span) / virtual_leaves #rot_step = float(arc_span) / len([n for n in root_node.traverse() if _leaf(n)]) # Calculate optimal branch length if img._scale is not None: init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) elif img.scale is None: # create items and calculate node dimensions skipping branch lengths init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) if mode == 'r': if img.optimal_scale_level == "full": scales = [(i.widths[1]/n.dist) for n,i in n2i.iteritems() if n.dist] img._scale = max(scales) if scales else 0.0 else: farthest, dist = root_node.get_farthest_leaf(topology_only=img.force_topology) img._scale = img.tree_width / dist if dist else 0.0 update_branch_lengths(root_node, n2i, n2f, img) else: img._scale = crender.calculate_optimal_scale(root_node, n2i, rot_step, img) #print "OPTIMAL circular scale", img._scale update_branch_lengths(root_node, n2i, n2f, img) init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) else: # create items and calculate node dimensions CONSIDERING branch lengths img._scale = img.scale init_items(root_node, parent, n2i, n2f, img, rot_step, hide_root) #print "USING scale", img._scale # Draw node content for node in root_node.traverse(is_leaf_fn=_leaf): if node is not root_node or not hide_root: render_node_content(node, n2i, n2f, img) # Adjust content to rect or circular layout mainRect = parent.rect() if mode == "c": tree_radius = crender.render_circular(root_node, n2i, rot_step) mainRect.adjust(-tree_radius, -tree_radius, tree_radius, tree_radius) else: iwidth = n2i[root_node].fullRegion.width() iheight = n2i[root_node].fullRegion.height() mainRect.adjust(0, 0, iwidth, iheight) tree_radius = iwidth # Add extra layers: aligned faces, floating faces, node # backgrounds, etc. The order by which the following methods are # called IS IMPORTANT render_floatings(n2i, n2f, img, parent.float_layer, parent.float_behind_layer) aligned_region_width = render_aligned_faces(img, mainRect, parent.tree_layer, n2i, n2f) render_backgrounds(img, mainRect, parent.bg_layer, n2i, n2f) # rotate if necessary in circular images. flip and adjust if mirror orientation. adjust_faces_to_tranformations(img, mainRect, n2i, n2f, TREE_LAYERS) # Rotate main image if necessary parent.setRect(mainRect) parent.setPen(QtGui.QPen(QtCore.Qt.NoPen)) if img.rotation: rect = parent.boundingRect() x = rect.x() + (rect.width()/2.0) y = rect.y() + (rect.height()/2.0) parent.setTransform(QtGui.QTransform().translate(x, y).rotate(img.rotation).translate(-x, -y)) # Creates the main tree item that will act as frame for the whole image frame = QtGui.QGraphicsRectItem() parent.setParentItem(frame) mainRect = parent.mapToScene(mainRect).boundingRect() mainRect.adjust(-img.margin_left, -img.margin_top, \ img.margin_right, img.margin_bottom) # Fix negative coordinates, so main item always starts at 0,0 topleft = mainRect.topLeft() _x = abs(topleft.x()) if topleft.x() < 0 else 0 _y = abs(topleft.y()) if topleft.y() < 0 else 0 if _x or _y: parent.moveBy(_x, _y) mainRect.adjust(_x, _y, _x, _y) # Add extra components and adjust mainRect to them add_legend(img, mainRect, frame) add_title(img, mainRect, frame) add_scale(img, mainRect, frame) frame.setRect(mainRect) # Draws a border around the tree if not img.show_border: frame.setPen(QtGui.QPen(QtCore.Qt.NoPen)) else: frame.setPen(QtGui.QPen(QtGui.QColor("black"))) return frame, n2i, n2f
def init_node_dimensions(node, item, faceblock, img): """Calculates width and height of all different subparts and faces of a given node. Branch lengths are not taken into account, so some dimensions must be adjusted after setting a valid scale. """ min_separation = img.min_leaf_separation if _leaf(node): aligned_height = faceblock["aligned"].h aligned_width = faceblock["aligned"].w else: aligned_height = 0 aligned_width = 0 ndist = 1.0 if img.force_topology else node.dist item.branch_length = (ndist * img._scale) if img._scale else 0 ## Calculate dimensions of the different node regions ## ## ## | ## | ------ ## b-top --------- | | | ## xoff-------------- O |b-right| | |alg | ## b-bottom --------- | | | ## | ------ ## | ## ## 0 1 2 3 4 5 ## item.xoff = 0.0 # widths w1 = max(faceblock["branch-bottom"].w, faceblock["branch-top"].w) w0 = item.branch_length - w1 if item.branch_length > w1 else 0 w2 = node.img_style["size"] w3 = faceblock["branch-right"].w w4 = node.img_style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 w5 = 0 # heights h0 = node.img_style["hz_line_width"] h1 = node.img_style["hz_line_width"] + faceblock["branch-top"].h + faceblock["branch-bottom"].h h2 = node.img_style["size"] h3 = faceblock["branch-right"].h h4 = 0 h5 = aligned_height # This fixes the problem of miss-aligned branches in ultrametric trees. If # there is nothing between the hz line and the vt line, then I prevent # vt_line_width to add extra width to the node, so node distances are # preserved in the img. if w2 == 0 and w3 == 0: w4 = 0 # ignore face heights if requested if img.mode == "c" and img.allow_face_overlap: h1, h3, h5 = 0, 0, 0 item.heights = [h0, h1, h2, h3, h4, h5] item.widths = [w0, w1, w2, w3, w4, w5] # Calculate total node size total_w = sum([w0, w1, w2, w3, w4, item.xoff]) # do not count aligned faces if img.mode == "c": max_h = max(item.heights[:4] + [min_separation]) elif img.mode == "r": max_h = max(item.heights + [min_separation]) max_h += img.branch_vertical_margin # correct possible unbalanced block in branch faces h_imbalance = abs(faceblock["branch-top"].h - faceblock["branch-bottom"].h) if h_imbalance + h1 > max_h: max_h += h_imbalance item.facesRegion.setRect(0, 0, w3, max_h) item.nodeRegion.setRect(0, 0, total_w, max_h) item.fullRegion.setRect(0, 0, total_w, max_h)
def render_aligned_faces(img, mainRect, parent, n2i, n2f): # Prepares and renders aligned face headers. Used to later # place aligned faces aligned_faces = [ [node, fb["aligned"]] for node, fb in n2f.iteritems()\ if fb["aligned"].column2faces and _leaf(node)] # If no aligned faces, just return an offset of 0 pixels if not aligned_faces: return 0 # Load header and footer if img.mode == "r": tree_end_x = mainRect.width() fb_head = _FaceGroupItem(img.aligned_header, None) fb_head.setParentItem(parent) fb_foot = _FaceGroupItem(img.aligned_foot, None) fb_foot.setParentItem(parent) surroundings = [[None,fb_foot], [None, fb_head]] mainRect.adjust(0, -fb_head.h, 0, fb_foot.h) else: tree_end_x = mainRect.width()/2.0 surroundings = [] # Place aligned faces and calculates the max size of each # column (needed to place column headers) c2max_w = {} maxh = 0 maxh_node = None for node, fb in aligned_faces + surroundings: if fb.h > maxh: maxh = fb.h maxh_node = node for c, w in fb.c2max_w.iteritems(): c2max_w[c] = max(w, c2max_w.get(c,0)) extra_width = sum(c2max_w.values()) # If rect mode, render header and footer if img.mode == "r": if img.draw_aligned_faces_as_table: fb_head.setup_grid(c2max_w) fb_foot.setup_grid(c2max_w) fb_head.render() fb_head.setPos(tree_end_x, mainRect.top()) fb_foot.render() fb_foot.setPos(tree_end_x, mainRect.bottom()-fb_foot.h) if img.orientation == 1: fb_head.flip_hz() fb_foot.flip_hz() # if no scale provided in circular mode, optimal scale is expected # to provide the correct ending point to start drawing aligned # faces. elif img.mode == "c" and (img.scale or img._scale == 0) and not img.allow_face_overlap: angle = n2i[maxh_node].angle_span rad, off = crender.get_min_radius(1, maxh, angle, tree_end_x) extra_width += rad - tree_end_x tree_end_x = rad # Place aligned faces for node, fb in aligned_faces: item = n2i[node] item.mapped_items.append(fb) if img.draw_aligned_faces_as_table: if img.aligned_table_style == 0: fb.setup_grid(c2max_w, as_grid=True) elif img.aligned_table_style == 1: fb.setup_grid(c2max_w, as_grid=False) fb.render() fb.setParentItem(item.content) if img.mode == "c": if node.up in n2i: x = tree_end_x - n2i[node.up].radius else: x = tree_end_x #fb.moveBy(tree_end_x, 0) elif img.mode == "r": x = item.mapFromScene(tree_end_x, 0).x() fb.setPos(x, item.center-(fb.h/2.0)) if img.draw_guiding_lines and _leaf(node): # -1 is to connect the two lines, otherwise there is a pixel in between guide_line = _LineItem(item.nodeRegion.width()-1, item.center, x, item.center) pen = QtGui.QPen() set_pen_style(pen, img.guiding_lines_type) pen.setColor(QtGui.QColor(img.guiding_lines_color)) pen.setCapStyle(QtCore.Qt.FlatCap) pen.setWidth(node.img_style["hz_line_width"]) guide_line.setPen(pen) guide_line.setParentItem(item.content) if img.mode == "c": mainRect.adjust(-extra_width, -extra_width, extra_width, extra_width) else: mainRect.adjust(0, 0, extra_width, 0) return extra_width
def calculate_optimal_scale(root_node, n2i, rot_step, img): """ Note: Seems to be fast. 0.5s from a tree of 10.000 leaves""" n2minradius = {} n2sumdist = {} n2sumwidth = {} visited_nodes = [] # Calcula la posicion minima de los elementos (con scale=0, es # decir, sin tener en cuenta branch lengths. for node in root_node.traverse('preorder', is_leaf_fn=_leaf): visited_nodes.append(node) ndist = node.dist if not img.force_topology else 1.0 item = n2i[node] # Uses size of all node parts, except branch length w = sum(item.widths[1:5]) h = item.effective_height parent_radius = n2minradius.get(node.up, 0) angle = rot_step if _leaf(node) else item.angle_span r, xoffset = get_min_radius(w, h, angle, parent_radius) n2minradius[node] = r n2sumdist[node] = n2sumdist.get(node.up, 0) + ndist # versed sine: the little extra line needed to complete the # radius. #vs = r - (parent_radius + xoffset + w) n2sumwidth[node] = n2sumwidth.get(node.up, 0) + sum( item.widths[2:5]) #+ vs root_opening = 0.0 most_distant = max(n2sumdist.values()) if most_distant == 0: return 0.0 best_scale = None for node in visited_nodes: item = n2i[node] ndist = node.dist if not img.force_topology else 1.0 if best_scale is None: best_scale = (n2minradius[node] - n2sumwidth[node]) / ndist if ndist else 0.0 else: #Whats the expected radius of this node? current_rad = n2sumdist[node] * best_scale + (n2sumwidth[node] + root_opening) # If still too small, it means we need to increase scale. if current_rad < n2minradius[node]: # This is a simplification of the real ecuacion needed # to calculate the best scale. Given that I'm not # taking into account the versed sine of each parent # node, the equation is actually very simple. if img.root_opening_factor: best_scale = (n2minradius[node] - (n2sumwidth[node])) / ( n2sumdist[node] + (most_distant * img.root_opening_factor)) root_opening = most_distant * best_scale * img.root_opening_factor else: best_scale = (n2minradius[node] - (n2sumwidth[node]) + root_opening) / n2sumdist[node] #print "OOps adjusting scale", ndist, best_scale, n2minradius[node], current_rad, item.heights[5], node.name # If the width of branch top/bottom faces is not covered, # we can also increase the scale to adjust it. This may # produce huge scales, so let's keep it optional if img.optimal_scale_level == "full" and \ item.widths[1] > ndist * best_scale: best_scale = item.widths[1] / ndist #print "OOps adjusting scale because branch-faces", ndist, best_scale, item.widths[1] # Adjust scale for aligned faces if not img.allow_face_overlap: aligned_h = [(n2i[node].heights[5], node) for node in visited_nodes] aligned_h.sort(reverse=True) maxh, maxh_node = aligned_h[0] angle = n2i[maxh_node].angle_span rad, off = get_min_radius(1, maxh, angle, 0.0001) min_alg_scale = None for node in visited_nodes: if n2i[node].heights[5]: new_scale = ( rad - (n2sumwidth[node] + root_opening)) / n2sumdist[node] min_alg_scale = min( new_scale, min_alg_scale) if min_alg_scale is not None else new_scale if min_alg_scale > best_scale: best_scale = min_alg_scale if root_opening: n2i[root_node].nodeRegion.adjust(root_opening, 0, root_opening, 0) n2i[root_node].fullRegion.adjust(root_opening, 0, root_opening, 0) n2i[root_node].xoff = root_opening #n2i[root_node].widths[0] += root_opening #for node in visited_nodes: # item = n2i[node] # h = item.effective_height # a = n2sumdist[node] * best_scale + n2sumwidth.get(node) # b = h/2 # item.radius = math.sqrt(a**2 + b**2) #print "root opening", root_opening #best_scale = max(best_scale, min_scale) return best_scale
def set_node_size(node, n2i, n2f, img): scale = img._scale min_separation = img.min_leaf_separation item = n2i[node] if img.force_topology: branch_length = item.branch_length = 25 else: branch_length = item.branch_length = float(node.dist * scale) # Organize faces by groups #faceblock = update_node_faces(node, n2f, img) faceblock = n2f[node] aligned_height = 0 if _leaf(node): if img.mode == "r": aligned_height = faceblock["aligned"].h elif img.mode == "c": # aligned faces in circular mode are adjusted afterwords. The # min radius of the largest aligned faces will be calculated. pass # Total height required by the node. I cannot sum up the height of # all elements, since the position of some of them are forced to # be on top or at the bottom of branches. This fact can produce # and unbalanced nodeRegion center. Here, I only need to know # about the imbalance size to correct node height. The center will # be calculated later according to the parent position. top_half_h = ( (node.img_style["size"]/2.0) + node.img_style["hz_line_width"]/2.0 + faceblock["branch-top"].h ) bottom_half_h =( (node.img_style["size"]/2.0) + node.img_style["hz_line_width"]/2.0 + faceblock["branch-bottom"].h ) h1 = top_half_h + bottom_half_h h2 = max(faceblock["branch-right"].h, \ aligned_height, \ min_separation ) h = max(h1, h2) imbalance = abs(top_half_h - bottom_half_h) if imbalance > h2/2.0: h += imbalance - (h2/2.0) # This adds a vertical margin around the node elements h += img.branch_vertical_margin # Total width required by the node w = sum([max(branch_length + node.img_style["size"], faceblock["branch-top"].w + node.img_style["size"], faceblock["branch-bottom"].w + node.img_style["size"], ), faceblock["branch-right"].w] ) # This breaks ultrametric tree visualization #w += node.img_style["vt_line_width"] # rightside faces region item.facesRegion.setRect(0, 0, faceblock["branch-right"].w, h) # Node region item.nodeRegion.setRect(0, 0, w, h) # This is the node total region covered by the node item.fullRegion.setRect(0, 0, w, h)
def calculate_optimal_scale(root_node, n2i, rot_step, img): """ Note: Seems to be fast. 0.5s from a tree of 10.000 leaves""" n2minradius = {} n2sumdist = {} n2sumwidth = {} visited_nodes = [] # Calcula la posicion minima de los elementos (con scale=0, es # decir, sin tener en cuenta branch lengths. for node in root_node.traverse('preorder', is_leaf_fn=_leaf): visited_nodes.append(node) ndist = node.dist if not img.force_topology else 1.0 item = n2i[node] # Uses size of all node parts, except branch length w = sum(item.widths[1:5]) h = item.effective_height parent_radius = n2minradius.get(node.up, 0) angle = rot_step if _leaf(node) else item.angle_span r, xoffset = get_min_radius(w, h, angle, parent_radius) n2minradius[node] = r n2sumdist[node] = n2sumdist.get(node.up, 0) + ndist # versed sine: the little extra line needed to complete the # radius. #vs = r - (parent_radius + xoffset + w) n2sumwidth[node] = n2sumwidth.get(node.up, 0) + sum(item.widths[2:5]) #+ vs root_opening = 0.0 most_distant = max(n2sumdist.values()) if most_distant == 0: return 0.0 best_scale = None for node in visited_nodes: item = n2i[node] ndist = node.dist if not img.force_topology else 1.0 if best_scale is None: best_scale = (n2minradius[node] - n2sumwidth[node]) / ndist if ndist else 0.0 else: #Whats the expected radius of this node? current_rad = n2sumdist[node] * best_scale + (n2sumwidth[node] + root_opening) # If still too small, it means we need to increase scale. if current_rad < n2minradius[node]: # This is a simplification of the real ecuacion needed # to calculate the best scale. Given that I'm not # taking into account the versed sine of each parent # node, the equation is actually very simple. if img.root_opening_factor: best_scale = (n2minradius[node] - (n2sumwidth[node])) / (n2sumdist[node] + (most_distant * img.root_opening_factor)) root_opening = most_distant * best_scale * img.root_opening_factor else: best_scale = (n2minradius[node] - (n2sumwidth[node]) + root_opening) / n2sumdist[node] #print "OOps adjusting scale", ndist, best_scale, n2minradius[node], current_rad, item.heights[5], node.name # If the width of branch top/bottom faces is not covered, # we can also increase the scale to adjust it. This may # produce huge scales, so let's keep it optional if img.optimal_scale_level == "full" and \ item.widths[1] > ndist * best_scale: best_scale = item.widths[1] / ndist #print "OOps adjusting scale because branch-faces", ndist, best_scale, item.widths[1] # Adjust scale for aligned faces if not img.allow_face_overlap: aligned_h = [(n2i[node].heights[5], node) for node in visited_nodes] aligned_h.sort(reverse=True) maxh, maxh_node = aligned_h[0] angle = n2i[maxh_node].angle_span rad, off = get_min_radius(1, maxh, angle, 0.0001) min_alg_scale = None for node in visited_nodes: if n2i[node].heights[5]: new_scale = (rad - (n2sumwidth[node] + root_opening)) / n2sumdist[node] min_alg_scale = min(new_scale, min_alg_scale) if min_alg_scale is not None else new_scale if min_alg_scale > best_scale: best_scale = min_alg_scale if root_opening: n2i[root_node].nodeRegion.adjust(root_opening, 0, root_opening, 0) n2i[root_node].fullRegion.adjust(root_opening, 0, root_opening, 0) n2i[root_node].xoff = root_opening #n2i[root_node].widths[0] += root_opening #for node in visited_nodes: # item = n2i[node] # h = item.effective_height # a = n2sumdist[node] * best_scale + n2sumwidth.get(node) # b = h/2 # item.radius = math.sqrt(a**2 + b**2) #print "root opening", root_opening #best_scale = max(best_scale, min_scale) return best_scale
def render_circular(root_node, n2i, rot_step): max_r = 0.0 for node in root_node.traverse('preorder', is_leaf_fn=_leaf): item = n2i[node] w = sum(item.widths[1:5]) h = item.effective_height parent_radius = n2i[ node.up].radius if node.up and node.up in n2i else item.xoff angle = rot_step if _leaf(node) else item.angle_span if hasattr(item, "radius"): r = item.radius xoffset = 0 else: r, xoffset = get_min_radius(w, h, angle, parent_radius + item.widths[0]) item.radius = r node.add_features(rad=item.radius) #if xoffset: # DEBUG ONLY. IF Scale is correct, this should not be printed # print "Offset detected in node", xoffset rotate_and_displace(item.content, item.rotation, h, parent_radius) max_r = max(max_r, r) if not _leaf(node) and len(node.children) > 1: first_c = n2i[node.children[0]] last_c = n2i[node.children[-1]] # Vertical arc Line rot_end = n2i[node.children[-1]].rotation rot_start = n2i[node.children[0]].rotation rot_span = abs(rot_end - rot_start) C = item.vt_line C.setParentItem(item) path = QtGui.QPainterPath() # Counter clock wise start = r - node.img_style["vt_line_width"] / 2 path.arcMoveTo(-start, -start, start * 2, start * 2, 360 - rot_start - rot_span) path.arcTo(-start, -start, start * 2, start * 2, 360 - rot_start - rot_span, rot_span) # Faces C.setPath(path) item.static_items.append(C) if hasattr(item, "content"): # If applies, it sets the length of the extra branch length if item.extra_branch_line: xtra = item.extra_branch_line.line().dx() if xtra > 0: xtra = xoffset + xtra else: xtra = xoffset item.extra_branch_line.setLine(item.branch_length, item.center,\ item.branch_length + xtra , item.center) item.nodeRegion.setWidth(item.nodeRegion.width() + xtra) # And moves elements if xoffset: for i in item.movable_items: i.moveBy(xoffset, 0) n2i[root_node].max_r = max_r return max_r
def render_node_content(node, n2i, n2f, img): style = node.img_style item = n2i[node] item.content = _EmptyItem(item) nodeR = item.nodeRegion facesR = item.facesRegion center = item.center branch_length = item.branch_length # Node points ball_size = style["size"] vlw = style["vt_line_width"] if not _leaf(node) and len(node.children) > 1 else 0.0 # face_start_x = nodeR.width() - facesR.width() - vlw face_start_x = max(0, nodeR.width() - facesR.width() - vlw) ball_start_x = face_start_x - ball_size if ball_size: if node.img_style["shape"] == "sphere": node_ball = _SphereItem(node) elif node.img_style["shape"] == "circle": node_ball = _CircleItem(node) elif node.img_style["shape"] == "square": node_ball = _RectItem(node) node_ball.setPos(ball_start_x, center-(ball_size/2.0)) #from qt4_gui import _BasicNodeActions #node_ball.delegate = _BasicNodeActions() #node_ball.setAcceptsHoverEvents(True) #node_ball.setCursor(QtCore.Qt.PointingHandCursor) else: node_ball = None # Branch line to parent pen = QtGui.QPen() set_pen_style(pen, style["hz_line_type"]) pen.setColor(QtGui.QColor(style["hz_line_color"])) pen.setWidth(style["hz_line_width"]) pen.setCapStyle(QtCore.Qt.FlatCap) #pen.setCapStyle(QtCore.Qt.RoundCap) #pen.setCapStyle(QtCore.Qt.SquareCap) #pen.setJoinStyle(QtCore.Qt.RoundJoin) hz_line = _LineItem() hz_line = _NodeLineItem(node) hz_line.setPen(pen) join_fix = 0 if img.mode == "c" and node.up and node.up.img_style["vt_line_width"]: join_fix = node.up.img_style["vt_line_width"] # fix_join_line = _LineItem() # fix_join_line = _NodeLineItem(node) # parent_style = node.up.img_style # pen = QtGui.QPen() # pen.setColor(QtGui.QColor(parent_style["vt_line_color"])) # pen.setWidth(parent_style["hz_line_width"]) # pen.setCapStyle(QtCore.Qt.FlatCap) # fix_join_line.setPen(pen) # fix_join_line.setLine(-join_fix, center, join_fix, center) # fix_join_line.setParentItem(item.content) hz_line.setLine(-join_fix, center, branch_length, center) if img.complete_branch_lines_when_necessary: extra_line = _LineItem(branch_length, center, ball_start_x, center) pen = QtGui.QPen() item.extra_branch_line = extra_line set_pen_style(pen, img.extra_branch_line_type) pen.setColor(QtGui.QColor(img.extra_branch_line_color)) pen.setCapStyle(QtCore.Qt.FlatCap) pen.setWidth(style["hz_line_width"]) extra_line.setPen(pen) else: extra_line = None # Attach branch-right faces to child fblock_r = n2f[node]["branch-right"] fblock_r.render() fblock_r.setPos(face_start_x, center-fblock_r.h/2.0) # Attach branch-bottom faces to child fblock_b = n2f[node]["branch-bottom"] fblock_b.render() fblock_b.setPos(item.widths[0], center + style["hz_line_width"]/2.0) # Attach branch-top faces to child fblock_t = n2f[node]["branch-top"] fblock_t.render() fblock_t.setPos(item.widths[0], center - fblock_t.h - style["hz_line_width"]/2.0) # Vertical line if not _leaf(node): if img.mode == "c": vt_line = QtGui.QGraphicsPathItem() elif img.mode == "r": vt_line = _LineItem(item) first_child = node.children[0] last_child = node.children[-1] first_child_part = n2i[node.children[0]] last_child_part = n2i[node.children[-1]] c1 = first_child_part.start_y + first_child_part.center c2 = last_child_part.start_y + last_child_part.center fx = nodeR.width() - (vlw/2.0) if first_child.img_style["hz_line_width"] > 0: c1 -= (first_child.img_style["hz_line_width"] / 2.0) if last_child.img_style["hz_line_width"] > 0: c2 += (last_child.img_style["hz_line_width"] / 2.0) vt_line.setLine(fx, c1, fx, c2) pen = QtGui.QPen() set_pen_style(pen, style["vt_line_type"]) pen.setColor(QtGui.QColor(style["vt_line_color"])) pen.setWidth(style["vt_line_width"]) pen.setCapStyle(QtCore.Qt.FlatCap) #pen.setCapStyle(QtCore.Qt.RoundCap) #pen.setCapStyle(QtCore.Qt.SquareCap) vt_line.setPen(pen) item.vt_line = vt_line else: vt_line = None item.bg = QtGui.QGraphicsItemGroup() item.movable_items = [] #QtGui.QGraphicsItemGroup() item.static_items = [] #QtGui.QGraphicsItemGroup() # Items fow which coordinates are exported in the image map item.mapped_items = [node_ball, fblock_r, fblock_b, fblock_t] for i in [vt_line, extra_line, hz_line]: if i: #item.static_items.addToGroup(i) item.static_items.append(i) i.setParentItem(item.content) for i in [node_ball, fblock_r, fblock_b, fblock_t]: if i: #item.movable_items.addToGroup(i) item.movable_items.append(i) i.setParentItem(item.content)
def render(root_node, img, hide_root=False): mode = img.mode orientation = img.orientation if not img.scale and img.tree_width: fnode, max_dist = root_node.get_farthest_leaf(topology_only=\ img.force_topology) if max_dist>0: img.scale = img.tree_width / max_dist else: img.scale = 1 scale = img.scale arc_span = img.arc_span last_rotation = img.arc_start layout_fn = img._layout_handler parent = _TreeItem() n2i = parent.n2i # node to items n2f = parent.n2f # node to faces parent.bg_layer = _EmptyItem(parent) parent.tree_layer = _EmptyItem(parent) parent.float_layer = _EmptyItem(parent) parent.float_behind_layer = _EmptyItem(parent) TREE_LAYERS = [parent.bg_layer, parent.tree_layer, parent.float_layer] parent.bg_layer.setZValue(0) parent.tree_layer.setZValue(2) parent.float_behind_layer.setZValue(1) parent.float_layer.setZValue(3) visited = set() to_visit = [] to_visit.append(root_node) # This could be used to handle aligned faces in internal # nodes. virtual_leaves = 0 if img.show_branch_length: bl_face = faces.AttrFace("dist", fsize=8, ftype="Arial", fgcolor="black", formatter = "%0.3g") if img.show_branch_support: su_face = faces.AttrFace("support", fsize=8, ftype="Arial", fgcolor="darkred", formatter = "%0.3g") if img.show_leaf_name: na_face = faces.AttrFace("name", fsize=10, ftype="Arial", fgcolor="black") for n in root_node.traverse(): set_style(n, layout_fn) if img.show_branch_length: faces.add_face_to_node(bl_face, n, 0, position="branch-top") if not _leaf(n) and img.show_branch_support: faces.add_face_to_node(su_face, n, 0, position="branch-bottom") if _leaf(n) and img.show_leaf_name: faces.add_face_to_node(na_face, n, 0, position="branch-right") if _leaf(n):# or len(n.img_style["_faces"]["aligned"]): virtual_leaves += 1 rot_step = float(arc_span) / virtual_leaves #rot_step = float(arc_span) / len([n for n in root_node.traverse() if _leaf(n)]) # ::: Precalculate values ::: depth = 1 while to_visit: node = to_visit[-1] finished = True if node not in n2i: # Set style according to layout function item = n2i[node] = _NodeItem(node, parent.tree_layer) item.setZValue(depth) depth += 1 if node is root_node and hide_root: pass else: set_node_size(node, n2i, n2f, img) if not _leaf(node): # visit children starting from left most to right # most. Very important!! check all children[-1] and # children[0] for c in reversed(node.children): if c not in visited: to_visit.append(c) finished = False # :: pre-order code here :: if not finished: continue else: to_visit.pop(-1) visited.add(node) # :: Post-order visits. Leaves are already visited here :: if mode == "c": if _leaf(node): crender.init_circular_leaf_item(node, n2i, n2f, last_rotation, rot_step) last_rotation += rot_step else: crender.init_circular_node_item(node, n2i, n2f) elif mode == "r": if _leaf(node): rrender.init_rect_leaf_item(node, n2i, n2f) else: rrender.init_rect_node_item(node, n2i, n2f) if node is not root_node or not hide_root: render_node_content(node, n2i, n2f, img) mainRect = parent.rect() if mode == "c": tree_radius = crender.render_circular(root_node, n2i, rot_step) mainRect.adjust( -tree_radius, -tree_radius, tree_radius, tree_radius) else: iwidth = n2i[root_node].fullRegion.width() iheight = n2i[root_node].fullRegion.height() mainRect.adjust(0, 0, iwidth, iheight) tree_radius = iwidth # The order by which the following methods IS IMPORTANT render_floatings(n2i, n2f, img, parent.float_layer, parent.float_behind_layer) aligned_region_width = render_aligned_faces(img, mainRect, parent.tree_layer, n2i, n2f) render_backgrounds(img, mainRect, parent.bg_layer, n2i, n2f) adjust_faces_to_tranformations(img, mainRect, n2i, n2f, TREE_LAYERS) parent.setRect(mainRect) parent.setPen(QtGui.QPen(QtCore.Qt.NoPen)) if img.rotation: rect = parent.boundingRect() x = rect.x() + rect.width()/2 y = rect.y() + rect.height()/2 parent.setTransform(QtGui.QTransform().translate(x, y).rotate(img.rotation).translate(-x, -y)) frame = QtGui.QGraphicsRectItem() parent.setParentItem(frame) mainRect = parent.mapToScene(mainRect).boundingRect() mainRect.adjust(-img.margin_left, -img.margin_top, \ img.margin_right, img.margin_bottom) add_legend(img, mainRect, frame) add_title(img, mainRect, frame) add_scale(img, mainRect, frame) frame.setRect(mainRect) # Draws a border around the tree if not img.show_border: frame.setPen(QtGui.QPen(QtCore.Qt.NoPen)) else: frame.setPen(QtGui.QPen(QtGui.QColor("black"))) return frame, n2i, n2f