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
def lines(self): textsize = self.textsize(self._result, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) if self.adjustBaseline: draw_xy = base_xy.shift(dx, height + textsize.height) else: draw_xy = base_xy.shift(dx, height) yield string, draw_xy height += textsize.height + self.line_spacing
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 func(self, *args, **kwargs): args = list(args) if kwargs.get('filter') not in ('blur', 'transp-blur'): return fn(self, *args, **kwargs) else: box = get_shape_box(*args) args[0] = get_abs_coordinate(box, *args) size = Size(box.width + PADDING * 2, box.height + PADDING * 2) shadow = create_shadow(self, size, *args, **kwargs) xy = XY(box.x1 - PADDING, box.y1 - PADDING) self.paste(shadow, xy, shadow)
def lifeline(self, node): delayed = [] for sep in self.separators: if sep.type == 'delay': delayed.append(sep) lines = [] d = self.cellsize pt = self.node(node).bottom for sep in delayed: m = self.cell(sep) y1 = m.top.y y2 = m.bottom.y lines.append(((pt, XY(pt.x, y1)), '8,4')) lines.append(((XY(pt.x, y1 + d), XY(pt.x, y2 - d)), '2,8')) pt = XY(pt.x, y2) y = self.bottomheight + self.cellsize * 4 lines.append(((pt, XY(pt.x, y)), '8,4')) return lines
def __init__(self, elemid): self.id = unquote(elemid) self.label = '' self.xy = XY(0, 0) self.group = None self.drawable = False self.order = 0 self.color = self.basecolor self.width = None self.height = None self.colwidth = 1 self.colheight = 1 self.stacked = False
def racknumber(self, rack, number): if rack.descending: y = rack.colheight - number - 1 else: y = number dummy = elements.DiagramNode(None) dummy.xy = XY(rack.xy.x, y) dummy.colwidth = 1 dummy.colheight = 1 box = self.cell(dummy, use_padding=False).box return Box(0, box[1], box[0], box[3])
def outlinebox(self): corners = [] for string, xy in self.lines: textsize = self.textsize(string) width = textsize[0] * self.scale height = textsize[1] * self.scale if self.adjustBaseline: xy = XY(xy.x, xy.y - textsize[1]) corners.append(xy) corners.append(XY(xy.x + width, xy.y + height)) if corners: box = Box(min(p.x for p in corners) - self.padding, min(p.y for p in corners) - self.line_spacing, max(p.x for p in corners) + self.padding, max(p.y for p in corners) + self.line_spacing) else: box = Box(self.box[0], self.box[1], self.box[0], self.box[1]) return box
def shift_shadow(self, value): xdiff = self.metrics.shadow_offset.x ydiff = self.metrics.shadow_offset.y if isinstance(value, XY): ret = XY(value.x + xdiff, value.y + ydiff) elif isinstance(value, Box): ret = Box(value.x1 + xdiff, value.y1 + ydiff, value.x2 + xdiff, value.y2 + ydiff) elif isinstance(value, (list, tuple)): ret = [self.shift_shadow(x) for x in value] return ret
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 __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 _draw_background(self): metrics = self.metrics pagesize = self.pagesize() margin = metrics.page_margin for i in range(self.diagram.height + 2): height = margin.y + i * metrics.cellsize * 4 _from = XY(margin.x, height) _to = XY(pagesize.x - margin.x, height) self.drawer.line((_from, _to), fill=self.fill) # left side of frame line = (XY(margin.x, margin.y), XY(margin.x, pagesize.y - margin.y)) self.drawer.line(line, fill=self.fill) # right side of textbox line = (XY(margin.x + 10 * metrics.cellsize, margin.y), XY(margin.x + 10 * metrics.cellsize, pagesize.y - margin.y)) self.drawer.line(line, fill=self.fill) # right side of frame line = (XY(pagesize.x - margin.x, margin.y), XY(pagesize.x - margin.x, pagesize.y - margin.y)) self.drawer.line(line, fill=self.fill) for i in range(self.diagram.width - 1): width = margin.x + (i + 1 + 5) * metrics.cellsize * 2 _from = XY(width, margin.y) _to = XY(width, pagesize.y - margin.y) self.drawer.line((_from, _to), fill='gray') # Smoothing back-ground images. if self.format == 'PNG': self.drawer.smoothCanvas()
def arc(self, box, start, end, **kwargs): fill = kwargs.get('fill') w = box.width / 2 h = box.height / 2 if start > end: end += 360 endpoints = ellipse_endpoints(1, w, h, start, end) pt1 = XY(box.x + w + round(endpoints[0].x, 0), box.y + h + round(endpoints[0].y, 0)) pt2 = XY(box.x + w + round(endpoints[1].x, 0), box.y + h + round(endpoints[1].y, 0)) if end - start > 180: largearc = 1 else: largearc = 0 pd = pathdata(pt1[0], pt1[1]) pd.ellarc(w, h, 0, largearc, 1, pt2[0], pt2[1]) p = path(pd, fill="none", stroke=rgb(fill), **drawing_params(kwargs)) self.svg.addElement(p)
def edge_textsize(self, edge): width = 0 height = 0 if edge.label: if edge.direction == 'self': cell = self.cell(edge.node1) width = self.edge(edge).right - cell.center.x else: width = (self.cell(edge.right_node).center.x - self.cell(edge.left_node).center.x - self.cellsize * 4 ) # 4: width of activity and padding width, height = self.textsize(edge.label, width=width, font=self.font_for(edge)) return XY(width, height)
def fixiate(self, fixiate_nodes=False): if self.separated: self.colwidth = 1 self.colheight = 1 return elif len(self.nodes) > 0: self.colwidth = max(x.xy.x + x.colwidth for x in self.nodes) self.colheight = max(x.xy.y + x.colheight for x in self.nodes) for node in self.nodes: if fixiate_nodes: node.xy = XY(self.xy.x + node.xy.x, self.xy.y + node.xy.y) if isinstance(node, NodeGroup): node.fixiate(fixiate_nodes)
def failedmark(self): lines = [] if self.edge.failed: r = self.metrics.cellsize if self.edge.direction == 'right': pt = self.shaft[-1] lines.append((XY(pt.x + r, pt.y - r), XY(pt.x + r * 3, pt.y + r))) lines.append((XY(pt.x + r, pt.y + r), XY(pt.x + r * 3, pt.y - r))) else: pt = self.shaft[0] lines.append((XY(pt.x - r * 3, pt.y - r), XY(pt.x - r, pt.y + r))) lines.append((XY(pt.x - r * 3, pt.y + r), XY(pt.x - r, pt.y - r))) return lines
def build(self, tree): self.diagram = Diagram() self.instantiate(None, None, tree) for network in self.diagram.networks: nodes = [n for n in self.diagram.nodes if network in n.networks] if len(nodes) == 0: self.diagram.networks.remove(network) for i, network in enumerate(self.diagram.networks): network.xy = XY(0, i) for subgroup in self.diagram.groups: if len(subgroup.nodes) == 0: self.diagram.groups.remove(subgroup) for node in self.diagram.nodes: if len(node.networks) == 0: msg = "DiagramNode %s does not belong to any networks" raise RuntimeError(msg % node.id) # show networks including same nodes for nw in self.diagram.networks: if nw.hidden and len(nw.nodes) == 2: def is_same(x): return set(nodes) & set(x.nodes) == set(nodes) nodes = nw.nodes for n in self.diagram.networks: if n != nw and is_same(n): nw.hidden = False break # show network for multiple peer networks from same node nodes = [] for nw in self.diagram.networks: if nw.hidden and len(nw.nodes) == 2: nodes.append(nw.nodes[0]) # parent node (FROM node) for node in [n for n in set(nodes) if nodes.count(n) > 1]: for network in node.networks: if len(network.nodes) == 2: network.hidden = False return self.diagram
def set_node_xpos(self, depth=0): for node in self.diagram.nodes: if node.xy.x != depth: continue for child in self.get_child_nodes(node): if self.is_circular_ref(node, child): pass elif node == child: pass elif child.xy.x > node.xy.x + node.colwidth: pass else: child.xy = XY(node.xy.x + node.colwidth, 0) depther_node = [x for x in self.diagram.nodes if x.xy.x > depth] if len(depther_node) > 0: self.set_node_xpos(depth + 1)
def dots(box, cycle, start=0, end=360): # calcrate rendering pattern from cycle base = 0 rendered = [] for index in range(0, len(cycle), 2): i, j = cycle[index:index + 2] for n in range(base * 2, (base + i) * 2): rendered.append(n) base += i + j a = float(box.width) / 2 b = float(box.height) / 2 du = 1 _max = sum(cycle) * 2 center = box.center for i, coord in enumerate(_coordinates(du, a, b, start, end)): if i % _max in rendered: yield XY(center.x + coord[0], center.y + coord[1])
def build(self, tree): self.diagram = Diagram() self.diagram = self.instantiate(self.diagram, tree) _min = min(n._to or n._from for n in self.diagram.nodes) _max = max(n._to or n._from for n in self.diagram.nodes) self.diagram.width = (_max - _min).days + 1 self.diagram.height = len(self.diagram.nodes) for i, node in enumerate(self.diagram.nodes): node.xy = XY((node._from - _min).days, i) if node._to: node.width = (node._to - node._from).days + 1 else: node.width = 1 return self.diagram
def labelbox(self): _dir = self.edge.direction if _dir == 'right': span = XY(self.span_width, self.span_height) cell1 = self.cell(self.edge.node1, use_padding=False) cell2 = self.cell(self.edge.node2, use_padding=False) if self.edge.skipped: box = Box(cell1.bottom.x, cell1.bottom.y, cell1.bottomright.x, cell1.bottomright.y + span.y // 2) else: box = Box(cell1.bottom.x, cell2.left.y - span.y // 2, cell1.bottom.x, cell2.left.y) else: box = super(FlowchartLandscapeEdgeMetrics, self).labelbox return box
def lineTo(self, x, y=None): if y is None: elem = x else: elem = XY(x, y) if self.stroking is False: self.stroking = True polyline = [] if self.xy: polyline.append(self.xy) self.polylines.append(polyline) if len(self.polylines[-1]) > 0: if self.polylines[-1][-1] == elem: return self.polylines[-1].append(elem)
def render_shape_outline(self, drawer, **kwargs): m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[2], box[1] + r), XY(box[2], box[3] - r)), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3])), (XY(box[0], box[1] + r), XY(box[0], box[3] - r))] for line in lines: drawer.line(line, fill=self.node.linecolor, style=self.node.style) r2 = r * 2 arcs = [(Box(box[0], box[1], box[0] + r2, box[1] + r2), 180, 270), (Box(box[2] - r2, box[1], box[2], box[1] + r2), 270, 360), (Box(box[2] - r2, box[3] - r2, box[2], box[3]), 0, 90), (Box(box[0], box[3] - r2, box[0] + r2, box[3]), 90, 180)] for arc in arcs: drawer.arc(arc[0], arc[1], arc[2], fill=self.node.linecolor, style=self.node.style)
def rightnoteshape(self): if not self.edge.rightnote: return [] r = self.metrics.cellsize box = self.rightnotebox return [ XY(box[0], box[1]), XY(box[2], box[1]), XY(box[2] + r, box[1] + r), XY(box[2] + r, box[3]), XY(box[0], box[3]), XY(box[0], box[1]) ]
def run(self): filled = {} for field in self.split_field_by_column(): x = field.number % self.diagram.colwidth y = field.number // self.diagram.colwidth if filled.get(y) is None: filled[y] = {} for rx in range(x, x + field.colwidth): if filled[y].get(rx): msg = ("Field '%s' is conflicted to other field\n" % field.label) raise AttributeError(msg) filled[y][rx] = True if self.diagram.scale_direction == "right_to_left": x = self.diagram.colwidth - x - field.colwidth field.xy = XY(x, y) self.diagram.fixiate()
def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) xdiff = self.metrics.node_width // 4 ydiff = self.metrics.node_height // 4 shape = [ XY(m.topleft.x + xdiff, m.topleft.y), XY(m.topright.x - xdiff, m.topleft.y), XY(m.topright.x, m.topright.y + ydiff), XY(m.topright.x, m.bottomright.y), XY(m.topleft.x, m.bottomleft.y), XY(m.topleft.x, m.topleft.y + ydiff), XY(m.topleft.x + xdiff, 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 adjust_node_widths(self): i = 0 linked_widths = {} widths = [] while i < self.colheight: levels = self.get_linked_levels(i) if levels: linked_widths[i] = [] for level in levels: nodes = self.items(level) if nodes: width = max(n.xy.x for n in nodes) + 1 widths.append(width) linked_widths[i].append(width) i += len(levels) else: i += 1 i = 0 self.colwidth = lcm(*widths) or 1 while i < self.colheight: levels = self.get_linked_levels(i) if levels: colwidth = max(linked_widths[i]) or 1 for level in levels: nodes = self.items(level) if nodes: width = self.colwidth // colwidth for node in nodes: node.xy = XY(node.xy.x * width, node.xy.y) node.colwidth = width i += len(levels) else: i += 1
def _groups(self): # Store nodes and edges of subgroups nodes = {self.diagram: self.diagram.nodes} edges = {self.diagram: self.diagram.edges} levels = {self.diagram: self.diagram.level} for group in self.diagram.traverse_groups(): nodes[group] = group.nodes edges[group] = group.edges levels[group] = group.level groups = {} orders = {} for node in self.diagram.traverse_nodes(): groups[node] = node.group orders[node] = node.order for group in self.diagram.traverse_groups(): yield group # Restore nodes, groups and edges for g in nodes: g.nodes = nodes[g] g.edges = edges[g] g.level = levels[g] for n in groups: n.group = groups[n] n.order = orders[n] n.xy = XY(0, 0) n.colwidth = 1 n.colheight = 1 n.separated = False for edge in DiagramEdge.find_all(): edge.skipped = False edge.crosspoints = [] yield self.diagram
def _shaft(self): if self.edge.direction == 'right-down': span = XY(self.span_width, self.span_height) node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell1.bottom.x, cell2.left.y) shaft.lineTo(node2.left) else: shaft = super(FlowchartLandscapeEdgeMetrics, self)._shaft return shaft
def head(self): cell = self.metrics.cellsize head = [] if self.edge.direction == 'right': xy = self.shaft[-1] head.append(XY(xy.x - cell, xy.y - cell // 2)) head.append(xy) head.append(XY(xy.x - cell, xy.y + cell // 2)) elif self.edge.direction == 'left': xy = self.shaft[0] head.append(XY(xy.x + cell, xy.y - cell // 2)) head.append(xy) head.append(XY(xy.x + cell, xy.y + cell // 2)) else: # self xy = self.shaft[-1] head.append(XY(xy.x + cell, xy.y - cell // 2)) head.append(xy) head.append(XY(xy.x + cell, xy.y + cell // 2)) return head
def body_part(self): r = self.radius m = self.metrics.cell(self.node) bodyC = m.center neckWidth = r * 2 // 3 # neck size arm = r * 4 # arm length armWidth = r bodyWidth = r * 2 // 3 # half of body width bodyHeight = r legXout = r * 7 // 2 # toe outer position legYout = bodyHeight + r * 3 legXin = r * 2 # toe inner position legYin = bodyHeight + r * 3 return [ XY(bodyC.x + neckWidth, bodyC.y - r * 2), XY(bodyC.x + neckWidth, bodyC.y - armWidth), # neck end XY(bodyC.x + arm, bodyC.y - armWidth), XY(bodyC.x + arm, bodyC.y), # right arm end XY(bodyC.x + bodyWidth, bodyC.y), # right body end XY(bodyC.x + bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x + legXout, bodyC.y + legYout), XY(bodyC.x + legXin, bodyC.y + legYin), XY(bodyC.x, bodyC.y + (bodyHeight * 2)), # body bottom center XY(bodyC.x - legXin, bodyC.y + legYin), XY(bodyC.x - legXout, bodyC.y + legYout), XY(bodyC.x - bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x - bodyWidth, bodyC.y), # left body end XY(bodyC.x - arm, bodyC.y), XY(bodyC.x - arm, bodyC.y - armWidth), XY(bodyC.x - neckWidth, bodyC.y - armWidth), # left arm end XY(bodyC.x - neckWidth, bodyC.y - r * 2) ]
def right(self): pt = self.box.right return XY(pt.x + self.span_width // 2, pt.y)