def draw(self, node): """ Draw a curved connection between a node and its child nodes. """ E = self._eval_func(node) if node.isleaf(): return _ctx.autoclosepath(True) _ctx.stroke(node.connectioncolor) _ctx.fill(node.connectioncolor) children = node.children for child in children: linewidth = E('nodeLineWidthEnd') _ctx.strokewidth(linewidth) direction = child.direction() opp_direction = opposite_dir(direction) x1, y1 = node.connection_point(direction) x2, y2 = child.connection_point(opp_direction) if direction == Direction.Left: x2 -= linewidth / 2 elif direction == Direction.Right: x2 += linewidth / 2 if len(children) == 1: _ctx.line(x1, y1, x2, y2) else: cx1 = (x2 - x1) * E('nodeCx1Factor') cx2 = (x2 - x1) * E('nodeCx2Factor') cy1 = (y2 - y1) * E('nodeCy1Factor') cy2 = (y2 - y1) * E('nodeCy2Factor') p1x = x1 + cx1 p1y = y1 + cy1 p2x = x2 - cx2 p2y = y2 - cy2 startwidth = E('nodeLineWidthStart') - 1 sw = startwidth / 2. _ctx.beginpath(x1, y1 - sw) _ctx.curveto(p1x, p1y, p2x, p2y, x2, y2) _ctx.curveto(p2x, p2y, p1x, p1y, x1, y1 + sw) _ctx.endpath()
def childrenheight(self, node, direction): # direction is None for leaf nodes if direction == None: return 0 children = self._getchildren(node, direction) if not children or node.isleaf(): return 0 firstchild = children[0] lastchild = children[-1] child_dir = opposite_dir(direction) child_conn_ytop = firstchild.connection_point(child_dir)[1] child_conn_ybottom = lastchild.connection_point(child_dir)[1] return child_conn_ybottom - child_conn_ytop
def _calc_y(self, node, direction): E = self._eval_func(node) # Initialise branch bounding box node._branch_bboxtop = node.y node._branch_bboxbottom = node.y + node.bboxheight node_pady = E('nodePadY') if node.isleaf(): # Set the position of a leaf node node a calculate the y # position for the next leaf. Because of the way we traverse # the tree, all leaf nodes are positioned consecutively on # the y axis, separated by the node and branch paddings. node.y = self._leaf_y self._leaf_y += node.bboxheight + node_pady self._branch_pad = False else: # Depth-first traversal: we are going to calculate the # layout starting from the leaf nodes, progressing upwards # in the tree, and from top to bottom (from lower to higher # y coordinates) in terms of vertical positioning children = self._getchildren(node, direction) if not children: return branch_pad_y = E('branchPadY') if not self._branch_pad: self._leaf_y += branch_pad_y - node_pady self._branch_pad = True for child in children: self._calc_y(child, direction) if not self._branch_pad: self._leaf_y += branch_pad_y - node_pady self._branch_pad = True # At this point the whole subtree under 'node' has been # positioned correctly. The only remainig thing is to # calculate the y position of 'node' (the parent). node_dir = direction child_dir = opposite_dir(node_dir) # Calculate the y coord of the connection point for the # children firstchild = children[0] lastchild = children[-1] child_conn_ytop = firstchild.connection_point(child_dir)[1] child_conn_ybottom = lastchild.connection_point(child_dir)[1] # The actual connection point will be in between child_conn_y = (child_conn_ytop + (child_conn_ybottom - child_conn_ytop) * E('verticalAlignFactor')) # Snap parent connection position to children connection # positions vertically if E('snapParentToChildren'): children_conn_y = [c.connection_point(child_dir)[1] for c in children] # Enable snapping to half-positions 50% inbetween two # child connections if E('snapToHalfPositions'): l = [] for i in range(len(children_conn_y) - 1): y1 = children_conn_y[i] y2 = children_conn_y[i + 1] l.append(y1) l.append((y1 + y2) / 2.) l.append(children_conn_y[-1]) children_conn_y = l # Get closest connection point index d = [abs(child_conn_y - y) for y in children_conn_y] closest = d.index(min(d)) child_conn_y = children_conn_y[closest] # Calculate the y offset of the parent node in relation to # it's connection point node_conn_y = node.connection_point(node_dir)[1] node_yoffs = node_conn_y - node.y # Calculate the top and bottom y coords of the parent node_ytop = child_conn_y - node_yoffs node_ybottom = child_conn_y + (node.bboxheight - node_yoffs) # Set the position of the parent node node.y = node_ytop # Calculate the top and bottom y coords of the children children_ytop = firstchild.y children_ybottom = lastchild.y + lastchild.bboxheight # If the parent extends above the topmost child node, shift # the whole branch downwards by the same amount dy_top = children_ytop - node_ytop if dy_top > 0: if node.isroot(): node.y += dy_top for n in self._getchildren(node, direction): n.shiftbranch(0, dy_top) else: node.shiftbranch(0, dy_top) else: dy_top = 0 # If the parent extends below the bottommost child node, # offset the y start the next leaf node by the same amount if node_ybottom > children_ybottom: y = node_ybottom + branch_pad_y if y > self._leaf_y: self._leaf_y = y # Adjust y coordinates if the branch has been shifted # downwards self._leaf_y += dy_top node_ytop += dy_top node_ybottom += dy_top children_bboxtop = firstchild._branch_bboxtop + dy_top children_bboxbottom = lastchild._branch_bboxbottom + dy_top # Calculate the bounding box of the branch node._branch_bboxtop = min(children_bboxtop, node_ytop) node._branch_bboxbottom = max(children_bboxbottom, node_ybottom)
def _draw(self, node, direction=None): """ Draw a curved connection between a node and its child nodes. """ E = self._eval_func(node) children = node.getchildren(direction) if not children: return linewidth = E('lineWidth') _ctx.autoclosepath(True) _ctx.stroke(node.connectioncolor) _ctx.fill(node.connectioncolor) _ctx.strokewidth(linewidth) firstchild = children[0] lastchild = children[-1] direction = firstchild.direction() opp_direction = opposite_dir(direction) x1, y1 = node.connection_point(direction) xfirst, yfirst = firstchild.connection_point(opp_direction) # Special case: draw straight line if there's only one child if len(children) == 1: _ctx.line(x1, y1, xfirst, yfirst) return # Calculate junction point position jx = x1 + (xfirst - x1) * E('junctionXFactor') jy = y1 # Draw line from parent node to junction point _ctx.line(x1, y1, jx, jy) # Limit first & last corner radius to the available area ylast = lastchild.connection_point(opp_direction)[1] ysecond = children[1].connection_point(opp_direction)[1] ypenultimate = children[-2].connection_point(opp_direction)[1] # Starting corner radius cornerPad = E('cornerPad') r = min(E('cornerRadius'), abs(jx - xfirst) - cornerPad) r = max(r, 0) # Adjusted first (top) corner radius r1 = min(r, abs(yfirst - jy) - cornerPad) r1 = max(r1, 0) if ysecond < jy: r1 = min(r, abs(yfirst - ysecond) - cornerPad) r1 = max(r1, 0) # Adjusted last (bottom) corner radius r2 = min(r, abs(ylast - jy) - cornerPad) r2 = max(r2, 0) if ypenultimate > jy: r2 = min(r, abs(ylast - ypenultimate) - cornerPad) r2 = max(r2, 0) # Draw main branch as a single path to ensure line continuity p1 = Vector2(jx, yfirst + r1) p2 = Vector2(jx, ylast - r2) segments = [[p1, p2]] corner_style = E('cornerStyle') for i, child in enumerate(children): direction = child.direction() opp_direction = opposite_dir(direction) x2, y2 = child.connection_point(opp_direction) if direction == Direction.Left: x2 -= linewidth / 2 elif direction == Direction.Right: x2 += linewidth / 2 # Draw corners if direction == Direction.Left: a1 = 90 da = -90 dx1 = r1 * 2 dx2 = r2 * 2 else: a1 = da = 90 dx1 = dx2 = 0 x1 = jx if child is firstchild: x1 += -r1 if direction == Direction.Left else r1 if (corner_style == 'square' or abs(y2 - jy) < .001): p1 = Vector2(jx, y2) p2 = Vector2(jx, y2 + r1) segments.insert(0, [p1, p2]) p1 = Vector2(x1, y2) p2 = Vector2(jx, y2) segments.insert(0, [p1, p2]) elif corner_style == 'beveled': p1 = Vector2(x1, y2) p2 = Vector2(jx, y2 + r1) segments.insert(0, [p1, p2]) elif corner_style == 'rounded': arc = arcpath(jx - dx1, y2, r1 * 2, r1 * 2, a1, da) segments = arc + segments p1 = Vector2(x2, y2) p2 = Vector2(x1, y2) segments.insert(0, [p1, p2]) elif child is lastchild: x1 += -r2 if direction == Direction.Left else r2 if (corner_style == 'square' or abs(y2 - jy) < .001): p1 = Vector2(jx, y2 - r2) p2 = Vector2(jx, y2) segments.append([p1, p2]) p1 = Vector2(jx, y2) p2 = Vector2(x1, y2) segments.append([p1, p2]) elif corner_style == 'beveled': p1 = Vector2(jx, y2 - r2) p2 = Vector2(x1, y2) segments.append([p1, p2]) elif corner_style == 'rounded': arc = arcpath(jx - dx2, y2 - r2 * 2, r2 * 2, r2 * 2, a1 + da, da) segments = segments + arc p1 = Vector2(x1, y2) p2 = Vector2(x2, y2) segments.append([p1, p2]) else: _ctx.line(x1, y2, x2, y2) # Draw main branch path _ctx.nofill() path = createpath(_ctx, segments, close=False) _ctx.drawpath(path) # Draw junction point style = E('junctionStyle') if style == 'none': return r = E('junctionRadius') r2 = r / 2. _ctx.fill(E('junctionFillColor')) _ctx.stroke(E('junctionStrokeColor')) _ctx.strokewidth(E('junctionStrokeWidth')) if style == 'square': _ctx.rect(jx - r2, jy - r2, r, r) elif style == 'disc': _ctx.oval(jx - r2, jy - r2, r, r) elif style == 'diamond': _ctx.beginpath(jx, jy - r2) _ctx.lineto(jx + r2, jy) _ctx.lineto(jx, jy + r2) _ctx.lineto(jx - r2, jy) _ctx.lineto(jx, jy - r2) _ctx.endpath() # Draw junction sign sign = E('junctionSign') if sign == 'none': return _ctx.stroke(E('junctionSignColor')) d = E('junctionSignSize') / 2. _ctx.strokewidth(E('junctionSignStrokeWidth')) if sign in ('minus', 'plus'): _ctx.line(jx - d, jy, jx + d, jy) if sign == 'plus': _ctx.line(jx, jy - d, jx, jy + d)
def _calc_y(self, node, direction): E = self._eval_func(node) # Initialise branch bounding box node._branch_bboxtop = node.y node._branch_bboxbottom = node.y + node.bboxheight node_pady = E('nodePadY') if node.isleaf(): # Set the position of a leaf node node a calculate the y # position for the next leaf. Because of the way we traverse # the tree, all leaf nodes are positioned consecutively on # the y axis, separated by the node and branch paddings. node.y = self._leaf_y self._leaf_y += node.bboxheight + node_pady self._branch_pad = False else: # Depth-first traversal: we are going to calculate the # layout starting from the leaf nodes, progressing upwards # in the tree, and from top to bottom (from lower to higher # y coordinates) in terms of vertical positioning children = self._getchildren(node, direction) if not children: return branch_pad_y = E('branchPadY') if not self._branch_pad: self._leaf_y += branch_pad_y - node_pady self._branch_pad = True for child in children: self._calc_y(child, direction) if not self._branch_pad: self._leaf_y += branch_pad_y - node_pady self._branch_pad = True # At this point the whole subtree under 'node' has been # positioned correctly. The only remainig thing is to # calculate the y position of 'node' (the parent). node_dir = direction child_dir = opposite_dir(node_dir) # Calculate the y coord of the connection point for the # children firstchild = children[0] lastchild = children[-1] child_conn_ytop = firstchild.connection_point(child_dir)[1] child_conn_ybottom = lastchild.connection_point(child_dir)[1] # The actual connection point will be in between child_conn_y = (child_conn_ytop + (child_conn_ybottom - child_conn_ytop) * E('verticalAlignFactor')) # Snap parent connection position to children connection # positions vertically if E('snapParentToChildren'): children_conn_y = [ c.connection_point(child_dir)[1] for c in children ] # Enable snapping to half-positions 50% inbetween two # child connections if E('snapToHalfPositions'): l = [] for i in range(len(children_conn_y) - 1): y1 = children_conn_y[i] y2 = children_conn_y[i + 1] l.append(y1) l.append((y1 + y2) / 2.) l.append(children_conn_y[-1]) children_conn_y = l # Get closest connection point index d = [abs(child_conn_y - y) for y in children_conn_y] closest = d.index(min(d)) child_conn_y = children_conn_y[closest] # Calculate the y offset of the parent node in relation to # it's connection point node_conn_y = node.connection_point(node_dir)[1] node_yoffs = node_conn_y - node.y # Calculate the top and bottom y coords of the parent node_ytop = child_conn_y - node_yoffs node_ybottom = child_conn_y + (node.bboxheight - node_yoffs) # Set the position of the parent node node.y = node_ytop # Calculate the top and bottom y coords of the children children_ytop = firstchild.y children_ybottom = lastchild.y + lastchild.bboxheight # If the parent extends above the topmost child node, shift # the whole branch downwards by the same amount dy_top = children_ytop - node_ytop if dy_top > 0: if node.isroot(): node.y += dy_top for n in self._getchildren(node, direction): n.shiftbranch(0, dy_top) else: node.shiftbranch(0, dy_top) else: dy_top = 0 # If the parent extends below the bottommost child node, # offset the y start the next leaf node by the same amount if node_ybottom > children_ybottom: y = node_ybottom + branch_pad_y if y > self._leaf_y: self._leaf_y = y # Adjust y coordinates if the branch has been shifted # downwards self._leaf_y += dy_top node_ytop += dy_top node_ybottom += dy_top children_bboxtop = firstchild._branch_bboxtop + dy_top children_bboxbottom = lastchild._branch_bboxbottom + dy_top # Calculate the bounding box of the branch node._branch_bboxtop = min(children_bboxtop, node_ytop) node._branch_bboxbottom = max(children_bboxbottom, node_ybottom)