def frame(self, lanes): dummy = elements.DiagramNode(None) dummy.xy = XY(0, 0) dummy.colwidth = self.colwidth dummy.colheight = self.colheight cell = self.cell(dummy, use_padding=False) headerbox = Box( cell.topleft.x - self.span_width // 2, (cell.topleft.y - self.node_height - self.span_height - 2), cell.topright.x + self.span_width // 2, cell.topright.y - self.span_height // 2) outline = Box(headerbox[0], headerbox[1], headerbox[2], cell.bottom.y + self.span_height // 2) separators = [(XY(headerbox[0], headerbox[3]), XY(headerbox[2], headerbox[3]))] for lane in lanes[:-1]: x = lane.xy.x + lane.colwidth + 1 m = self.cell(lane, use_padding=False) span_width = self.spreadsheet.span_width[x] // 2 x1 = m.right.x + span_width xy = (XY(x1, outline[1]), XY(x1, outline[3])) separators.append(xy) Frame = namedtuple('Frame', 'headerbox outline separators') return Frame(headerbox, outline, separators)
def render_shape(self, drawer, _, **kwargs): if kwargs.get('shadow'): return m = self.metrics center = m.cell(self.node).center dots = [center] if self.node.group.orientation == 'landscape': pt = XY(center.x, center.y - m.node_height / 2) dots.append(pt) pt = XY(center.x, center.y + m.node_height / 2) dots.append(pt) else: pt = XY(center.x - m.node_width / 3, center.y) dots.append(pt) pt = XY(center.x + m.node_width / 3, center.y) dots.append(pt) r = m.cellsize / 2 for dot in dots: box = Box(dot.x - r, dot.y - r, dot.x + r, dot.y + r) drawer.ellipse(box, fill=self.node.linecolor, outline=self.node.linecolor)
def __init__(self, node, metrics=None): super(Actor, self).__init__(node, metrics) m = metrics.cell(node) if node.label: font = metrics.font_for(self.node) textsize = metrics.textsize(node.label, font) shortside = min(m.width, m.height - textsize.height) else: textsize = Size(0, 0) shortside = min(m.width, m.height) r = self.radius = shortside // 8 # radius of actor's head self.center = metrics.cell(node).center self.connectors[0] = XY(self.center.x, self.center.y - r * 9 // 2) self.connectors[1] = XY(self.center.x + r * 4, self.center.y) self.connectors[2] = XY(self.center.x, self.center.y + r * 4 + textsize.height) self.connectors[3] = XY(self.center.x - r * 4, self.center.y) self.textbox = Box(m.left.x, self.center.y + r * 4, m.right.x, self.connectors[2].y)
def edge_shadow(self, edge): m = self.metrics dx, dy = m.shadow_offset if edge.leftnote: polygon = m.edge(edge).leftnoteshape shadow = [XY(pt.x + dx, pt.y + dy) for pt in polygon] if self.diagram.shadow_style == 'solid': self.drawer.polygon(shadow, fill=self.shadow, outline=self.shadow) else: self.drawer.polygon(shadow, fill=self.shadow, outline=self.shadow, filter='transp-blur') if edge.rightnote: polygon = m.edge(edge).rightnoteshape shadow = [XY(pt.x + dx, pt.y + dy) for pt in polygon] if self.diagram.shadow_style == 'solid': self.drawer.polygon(shadow, fill=self.shadow, outline=self.shadow) else: self.drawer.polygon(shadow, fill=self.shadow, outline=self.shadow, filter='transp-blur')
def dots_of_line(pt1, pt2): if pt1.x == pt2.x: # vertical if pt1.y > pt2.y: pt2, pt1 = pt1, pt2 for y in xrange(pt1.y, pt2.y + 1): yield XY(pt1.x, y) elif pt1.y == pt2.y: # horizontal if pt1.x > pt2.x: pt2, pt1 = pt1, pt2 for x in xrange(pt1.x, pt2.x + 1): yield XY(x, pt1.y) else: # diagonal if pt1.x > pt2.x: pt2, pt1 = pt1, pt2 # DDA (Digital Differential Analyzer) Algorithm m = float(pt2.y - pt1.y) / float(pt2.x - pt1.x) x = pt1.x y = pt1.y while x <= pt2.x + 1: yield XY(int(x), int(round(y))) x += 1 y += m
def __init__(self, node, metrics=None): super(NationalFlagImage, self).__init__(node, metrics) self.textalign = 'left' self.image_path = image_path box = metrics.cell(node).box bounded = (box[2] - box[0], box[3] - box[1]) size = images.get_image_size(image_path) size = images.calc_image_size(size, bounded) pt = metrics.cell(node).center self.image_box = Box(pt.x - size[0] / 2, pt.y - size[1] / 2, pt.x + size[0] / 2, pt.y + size[1] / 2) width = metrics.node_width / 2 - size[0] / 2 + metrics.cellsize self.textbox = Box(pt.x + size[0] / 2, pt.y - size[1] / 2, pt.x + size[0] / 2 + width, pt.y + size[1] / 2) size = self.metrics.textsize(node.label, self.metrics.font_for(None), self.textbox.width) self.connectors[0] = XY(pt.x, self.image_box[1]) self.connectors[1] = XY( self.image_box.x2 + size.width + self.metrics.node_padding, pt.y) self.connectors[2] = XY(pt.x, self.image_box[3]) self.connectors[3] = XY(self.image_box[0], pt.y)
def xy(self): if len(self.edges) == 0: return XY(0, 0) else: x = min(e.left_node.xy.x for e in self.edges) y = min(e.order for e in self.edges) + 1 return XY(x, y)
def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 3 shape = [XY(m.topleft.x + r, m.topleft.y), XY(m.topright.x, m.topright.y), XY(m.bottomright.x - r, m.bottomright.y), XY(m.bottomleft.x, m.bottomleft.y), XY(m.topleft.x + r, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style)
def layout_nodes(self, group=None): networks = self.diagram.networks if group: nodes = (n for n in self.diagram.nodes if n.group == group) else: nodes = self.diagram.nodes for node in nodes: if node.layouted: continue joined = [g for g in node.networks if g.hidden is False] y1 = min(networks.index(g) for g in node.networks) if joined: y2 = max(networks.index(g) for g in joined) else: y2 = y1 if node.group and node.group != self.diagram and group: starts = min(n.xy.x for n in group.nodes if n.layouted) else: nw = [n for n in node.networks if n.xy.y == y1][0] nodes = [n for n in self.diagram.nodes if nw in n.networks] layouted = [n for n in nodes if n.xy.x > 0] starts = 0 if layouted: layouted.sort(key=lambda a: a.xy.x) basenode = layouted[0] commonnw = set(basenode.networks) & set(node.networks) if basenode.xy.y == y1: starts = basenode.xy.x + 1 elif commonnw and list(commonnw)[0].hidden is True: starts = basenode.xy.x else: starts = basenode.xy.x + 1 - len(nodes) if starts < 0: starts = 0 for x in range(starts, len(self.diagram.nodes)): points = [XY(x, y) for y in range(y1, y2 + 1)] if not set(points) & set(self.coordinates): node.xy = XY(x, y1) node.layouted = True self.coordinates += points break if node.group and node.group != self.diagram and group is None: self.layout_nodes(node.group) if group: self.set_coordinates(group)
def rotate_diagram(cls, diagram): for node in diagram.traverse_nodes(): node.xy = XY(node.xy.y, node.xy.x) node.colwidth, node.colheight = (node.colheight, node.colwidth) for lane in diagram.lanes: lane.xy = XY(lane.xy.y, lane.xy.x) lane.colwidth, lane.colheight = (lane.colheight, lane.colwidth) size = (diagram.colheight, diagram.colwidth) diagram.colwidth, diagram.colheight = size
def __init__(self, node, metrics=None): super(MiniDiamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) c = m.center self.connectors = (XY(c.x, c.y - r), XY(c.x + r, c.y), XY(c.x, c.y + r), XY(c.x - r, c.y), XY(c.x, c.y - r)) self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left'
def altblock(self, block): m = self.metrics.cell(block) self.drawer.rectangle(m, outline=block.linecolor) box = m.textbox line = [XY(box.x1, box.y2), XY(box.x2, box.y2), XY(box.x2 + self.metrics.cellsize * 2, box.y1)] self.drawer.line(line, fill=block.linecolor) self.drawer.textarea(box, block.type, fill=block.textcolor, font=self.metrics.font_for(block))
def __init__(self, node, metrics=None): super(Square, self).__init__(node, metrics) r = min(metrics.node_width, metrics.node_height) // 2 + \ metrics.cellsize // 2 pt = metrics.cell(node).center self.connectors = [XY(pt.x, pt.y - r), # top XY(pt.x + r, pt.y), # right XY(pt.x, pt.y + r), # bottom XY(pt.x - r, pt.y)] # left self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r)
def __init__(self, node, metrics=None): super(EndPoint, self).__init__(node, metrics) m = metrics.cell(node) self.radius = metrics.cellsize self.center = m.center self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' self.connectors = [XY(self.center.x, self.center.y - self.radius), XY(self.center.x + self.radius, self.center.y), XY(self.center.x, self.center.y + self.radius), XY(self.center.x - self.radius, self.center.y)]
def __init__(self, node, metrics=None): super(Diamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) self.connectors = [XY(m.top.x, m.top.y - r), XY(m.right.x + r, m.right.y), XY(m.bottom.x, m.bottom.y + r), XY(m.left.x - r, m.left.y), XY(m.top.x, m.top.y - r)] self.textbox = Box((self.connectors[0].x + self.connectors[3].x) // 2, (self.connectors[0].y + self.connectors[3].y) // 2, (self.connectors[1].x + self.connectors[2].x) // 2, (self.connectors[1].y + self.connectors[2].y) // 2)
def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 box = m.box ellipses = [ Box(box[0], box[1], box[0] + r * 2, box[3]), Box(box[2] - r * 2, box[1], box[2], box[3]) ] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rect = Box(box[0] + r, box[1], box[2] - r, box[3]) if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3]))] for line in lines: if not kwargs.get('shadow'): drawer.line(line, fill=self.node.linecolor, style=self.node.style)
def set_node_height(self, node, height=0): xy = XY(node.xy.x, height) if self.is_marked(node.lane, xy): return False node.xy = xy self.mark_xy(node) def cmp(x, y): if x.xy.x < y.xy.y: return -1 elif x.xy.x == y.xy.y: return 0 else: return 1 count = 0 children = self.get_child_nodes(node) children.sort(key=cmp_to_key(cmp)) for child in children: if child.id in self.heightRefs: pass elif node is not None and node.xy.x >= child.xy.x: pass else: if node.lane == child.lane: h = height else: h = 0 while True: if self.set_node_height(child, h): child.xy = XY(child.xy.x, h) self.mark_xy(child) self.heightRefs.append(child.id) count += 1 break elif node.lane != child.lane: h += 1 else: if count == 0: return False h += 1 if node.lane == child.lane: height = h + 1 return True
def labelbox(self): span = XY(self.span_width, self.span_height) node = XY(self.node_width, self.node_height) _dir = self.edge.direction node1 = self.cell(self.edge.node1, use_padding=False) node2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'right': if self.edge.skipped: box = Box(node1.bottomright.x + span.x, node1.bottomright.y, node2.bottomleft.x - span.x, node2.bottomleft.y + span.y // 2) else: box = Box(node1.topright.x, node1.topright.y - span.y // 8, node2.left.x, node2.left.y - span.y // 8) elif _dir == 'right-up': box = Box(node2.left.x - span.x, node1.top.y - node.y // 2, node2.bottomleft.x, node1.top.y) elif _dir == 'right-down': box = Box(node1.right.x, node2.topleft.y - span.y // 8, node1.right.x + span.x, node2.left.y - span.y // 8) elif _dir in ('up', 'left-up', 'left', 'same'): if self.edge.node2.xy.y < self.edge.node1.xy.y: box = Box(node1.topright.x - span.x // 2 + span.x // 4, node1.topright.y - span.y // 2, node1.topright.x + span.x // 2 + span.x // 4, node1.topright.y) else: box = Box(node1.top.x + span.x // 4, node1.top.y - span.y, node1.topright.x + span.x // 4, node1.topright.y - span.y // 2) elif _dir in ('left-down', 'down'): box = Box(node2.top.x + span.x // 4, node2.top.y - span.y, node2.topright.x + span.x // 4, node2.topright.y - span.y // 2) # shrink box box = Box(box[0] + span.x // 8, box[1], box[2] - span.x // 8, box[3]) return box
def trunklines(self): metrics = self.metrics for network in self.diagram.networks: if network.hidden is False: self.trunkline(network) if (self.diagram.external_connector and (network == self.diagram.networks[0])): r = metrics.trunk_diameter // 2 pt = metrics.network(network).top pt0 = XY(pt.x, pt.y - metrics.span_height * 2 // 3) pt1 = XY(pt.x, pt.y - r) self.drawer.line([pt0, pt1], fill=network.linecolor)
def render_shape(self, drawer, format, **kwargs): super(Class, self).render_shape(drawer, format, **kwargs) fill = kwargs.get('fill') r = self.metrics.node_height / 3 if not kwargs.get('shadow'): box = self.textbox line = (XY(box[0], box[1] + r), XY(box[2], box[1] + r)) drawer.line(line, fill=fill) line = (XY(box[0], box[1] + r + 4), XY(box[2], box[1] + r + 4)) drawer.line(line, fill=fill)
def _node_topleft(self, node, use_padding=True): x, y = node.xy margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x)) node_height = sum(self.node_height[i] for i in range(y)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x1 = margin.x + padding[3] + node_width + span_width + xdiff y1 = margin.y + padding[0] + node_height + span_height + ydiff return XY(x1, y1)
def _node_bottomright(self, node, use_padding=True): x = node.xy.x + node.colwidth - 1 y = node.xy.y + node.colheight - 1 margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x + 1)) node_height = sum(self.node_height[i] for i in range(y + 1)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x2 = margin.x + padding[3] + node_width + span_width - xdiff y2 = margin.y + padding[0] + node_height + span_height - ydiff return XY(x2, y2)
def set_coordinates(self, group): self.set_group_size(group) xy = group.xy for i in range(xy.x, xy.x + group.colwidth): for j in range(xy.y, xy.y + group.colheight): self.coordinates.append(XY(i, j))
def _scale(cls, value, ratio): if ratio == 1: return value klass = value.__class__ if klass == XY: ret = XY(value.x * ratio, value.y * ratio) elif klass == Size: ret = Size(value.width * ratio, value.height * ratio) elif klass == Box: ret = Box(value[0] * ratio, value[1] * ratio, value[2] * ratio, value[3] * ratio) elif klass == tuple: ret = tuple([cls.scale(x, ratio) for x in value]) elif klass == list: ret = [cls.scale(x, ratio) for x in value] elif klass == EdgeLines: ret = EdgeLines() ret.polylines = cls.scale(value.polylines, ratio) elif klass == FontInfo: ret = FontInfo(value.familyname, value.path, value.size * ratio) elif klass == int: ret = value * ratio elif klass == str: ret = value else: ret = cls(value, ratio) return ret
def fixiate_lanes(self): height = 0 for lane in self.diagram.lanes: if self.coordinates[lane]: for node in self.diagram.nodes: if node.lane == lane: node.xy = XY(node.xy.x, node.xy.y + height) height += max(xy.y for xy in self.coordinates[lane]) + 1 nodes = [n for n in self.diagram.nodes if n.lane == lane] x = min(n.xy.x for n in nodes) y = min(n.xy.y for n in nodes) lane.xy = XY(x, y) lane.colwidth = max(n.xy.x + n.colwidth for n in nodes) - x lane.colheight = max(n.xy.y + n.colheight for n in nodes) - y
def baseline(self): dummy = elements.DiagramNode(None) dummy.xy = XY(0, 1) dummy.colwidth = self.metrics.node_count m = self.metrics.cell(dummy) r = self.metrics.cellsize * 3 return (m.x1 - r, m.x2 + r)
def bottomheight(self): height = len(self.edges) + len(self.separators) dummy = elements.DiagramNode(None) dummy.xy = XY(1, height) _, y = self.spreadsheet._node_bottomright(dummy, use_padding=False) y += self.spreadsheet.span_height[len(self.edges) + 1] // 2 return y
def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(box, outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw flap if not kwargs.get('shadow'): flap = [m.topleft, XY(m.top.x, m.top.y + r), m.topright] drawer.line(flap, fill=self.node.linecolor, style=self.node.style)
def shift(self, x, y): metrics = copy.copy(self) metrics.spreadsheet = copy.copy(self.spreadsheet) metrics.spreadsheet.metrics = metrics metrics.page_margin = XY(x, y) return metrics
def lines(self): textsize = self.textsize(self._result, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) width = self.box.width - dx + self.line_spacing base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy width -= textsize.width + self.line_spacing for char in string: charsize = self.textsize(char, scaled=True) if self.adjustBaseline: draw_xy = base_xy.shift(width, height + charsize.height) else: draw_xy = base_xy.shift(width, height) yield char, draw_xy height += charsize.height + self.line_spacing