1# -*- coding: utf-8 -*-2# Copyright 2011 Takeshi KOMIYA3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.1516from __future__ import division1718import copy19from collections import defaultdict2021from blockdiag import noderenderer22from blockdiag.elements import DiagramNode23from blockdiag.utils import XY, Box, Size24from blockdiag.utils.fontmap import FontInfo, FontMap2526cellsize = 8272829class EdgeLines(object):30 def __init__(self):31 self.xy = None32 self.stroking = False33 self.polylines = []3435 def moveTo(self, x, y=None):36 self.stroking = False37 if y is None:38 self.xy = x39 else:40 self.xy = XY(x, y)4142 def lineTo(self, x, y=None):43 if y is None:44 elem = x45 else:46 elem = XY(x, y)4748 if self.stroking is False:49 self.stroking = True50 polyline = []51 if self.xy:52 polyline.append(self.xy)53 self.polylines.append(polyline)5455 if len(self.polylines[-1]) > 0:56 if self.polylines[-1][-1] == elem:57 return5859 self.polylines[-1].append(elem)6061 def lines(self):62 lines = []63 for line in self.polylines:64 start = line[0]65 for elem in list(line[1:]):66 lines.append((start, elem))67 start = elem6869 return lines707172class AutoScaler(object):73 def __init__(self, subject, scale_ratio):74 self.subject = subject75 self.scale_ratio = scale_ratio7677 def __getattr__(self, name):78 ratio = self.scale_ratio79 return self.scale(getattr(self.subject, name), ratio)8081 def __getitem__(self, name):82 ratio = self.scale_ratio83 return self.scale(self.subject[name], ratio)848586 def scale(cls, value, ratio):87 if not callable(value):88 return cls._scale(value, ratio)89 else:90 def _(*args, **kwargs):91 ret = value(*args, **kwargs)92 return cls._scale(ret, ratio)9394 return _959697 def _scale(cls, value, ratio):98 if ratio == 1:99 return value100101 klass = value.__class__102 if klass == XY:103 ret = XY(value.x * ratio, value.y * ratio)104 elif klass == Size:105 ret = Size(value.width * ratio, value.height * ratio)106 elif klass == Box:107 ret = Box(value[0] * ratio, value[1] * ratio,108 value[2] * ratio, value[3] * ratio)109 elif klass == tuple:110 ret = tuple([cls.scale(x, ratio) for x in value])111 elif klass == list:112 ret = [cls.scale(x, ratio) for x in value]113 elif klass == EdgeLines:114 ret = EdgeLines()115 ret.polylines = cls.scale(value.polylines, ratio)116 elif klass == FontInfo:117 ret = FontInfo(value.familyname, value.path, value.size * ratio)118 elif klass == int:119 ret = value * ratio120 elif klass == str:121 ret = value122 else:123 ret = cls(value, ratio)124125 return ret126127128 def original_metrics(self):129 return self.subject130131132class DiagramMetrics(object):133 cellsize = cellsize134 edge_layout = 'normal'135 node_padding = 4136 line_spacing = 2137 shadow_offset = XY(3, 6)138 page_margin = XY(0, 0)139 page_padding = [0, 0, 0, 0]140 node_width = cellsize * 16141 node_height = cellsize * 5142 span_width = cellsize * 8143 span_height = cellsize * 5144145 def __init__(self, diagram, drawer=None, fontmap=None):146 self.drawer = drawer147148 if diagram.node_width is not None:149 self.node_width = diagram.node_width150151 if diagram.node_height is not None:152 self.node_height = diagram.node_height153154 if diagram.span_width is not None:155 self.span_width = diagram.span_width156157 if diagram.span_height is not None:158 self.span_height = diagram.span_height159160 if fontmap is not None:161 self.fontmap = fontmap162 else:163 self.fontmap = FontMap()164165 if diagram.page_padding is not None:166 self.page_padding = diagram.page_padding167168 if diagram.edge_layout is not None:169 self.edge_layout = diagram.edge_layout170171 # setup spreadsheet172 sheet = self.spreadsheet = SpreadSheetMetrics(self)173 nodes = [n for n in diagram.traverse_nodes() if n.drawable]174175 node_width = self.node_width176 for x in range(diagram.colwidth):177 widths = [n.width for n in nodes if n.xy.x == x]178 if widths:179 width = max(n or node_width for n in widths)180 sheet.set_node_width(x, width)181182 node_height = self.node_height183 for y in range(diagram.colheight):184 heights = [n.height for n in nodes if n.xy.y == y]185 if heights:186 height = max(n or node_height for n in heights)187 sheet.set_node_height(y, height)188189190 def original_metrics(self):191 return self192193 def shift(self, x, y):194 metrics = copy.copy(self)195 metrics.spreadsheet = copy.copy(self.spreadsheet)196 metrics.spreadsheet.metrics = metrics197 metrics.page_margin = XY(x, y)198199 return metrics200201 def textsize(self, string, font=None, width=65535):202 return self.drawer.textsize(string, font, maxwidth=width)203204 def node(self, node):205 renderer = noderenderer.get(node.shape)206207 if hasattr(renderer, 'render'):208 return renderer(node, self)209 else:210 return self.cell(node)211212 def cell(self, node, use_padding=True):213 return self.spreadsheet.node(node, use_padding)214215 def group(self, group):216 return self.spreadsheet.node(group, use_padding=False)217218 def edge(self, edge):219 if self.edge_layout == 'flowchart':220 if edge.node1.group.orientation == 'landscape':221 return FlowchartLandscapeEdgeMetrics(edge, self)222 else:223 return FlowchartPortraitEdgeMetrics(edge, self)224 else:225 if edge.node1.group.orientation == 'landscape':226 return LandscapeEdgeMetrics(edge, self)227 else:228 return PortraitEdgeMetrics(edge, self)229230 def font_for(self, element):231 return self.fontmap.find(element)232233 def pagesize(self, width, height):234 return self.spreadsheet.pagesize(width, height)235236237class SubMetrics(object):238 def __getattr__(self, name):239 # avoid recursion-error on Python 2.6240 if 'metrics' not in self.__dict__:241 raise AttributeError()242243 return getattr(self.metrics, name)244245246class SpreadSheetMetrics(SubMetrics):247 def __init__(self, metrics):248 self.metrics = metrics249 self.node_width = defaultdict(lambda: metrics.node_width)250 self.node_height = defaultdict(lambda: metrics.node_height)251 self.span_width = defaultdict(lambda: metrics.span_width)252 self.span_height = defaultdict(lambda: metrics.span_height)253254 def set_node_width(self, x, width):255 if (width is not None and 0 < width and256 (x not in self.node_width or self.node_width[x] < width)):257 self.node_width[x] = width258259 def set_node_height(self, y, height):260 if (height is not None and 0 < height and261 (y not in self.node_height or self.node_height[y] < height)):262 self.node_height[y] = height263264 def set_span_width(self, x, width):265 if (width is not None and 0 < width and266 (x not in self.span_width or self.span_width[x] < width)):267 self.span_width[x] = width268269 def add_span_width(self, x, width):270 self.span_width[x] += width271272 def set_span_height(self, y, height):273 if (height is not None and 0 < height and274 (y not in self.span_height or self.span_height[y] < height)):275 self.span_height[y] = height276277 def add_span_height(self, y, height):278 self.span_height[y] += height279280 def node(self, node, use_padding=True):281 x1, y1 = self._node_topleft(node, use_padding)282 x2, y2 = self._node_bottomright(node, use_padding)283284 return NodeMetrics(self.metrics, x1, y1, x2, y2)285286 def _node_topleft(self, node, use_padding=True):287 x, y = node.xy288 margin = self.page_margin289 padding = self.page_padding290291 node_width = sum(self.node_width[i] for i in range(x))292 node_height = sum(self.node_height[i] for i in range(y))293 span_width = sum(self.span_width[i] for i in range(x + 1))294 span_height = sum(self.span_height[i] for i in range(y + 1))295296 if use_padding:297 width = node.width or self.metrics.node_width298 xdiff = (self.node_width[x] - width) // 2299 if xdiff < 0:300 xdiff = 0301302 height = node.height or self.metrics.node_height303 ydiff = (self.node_height[y] - height) // 2304 if ydiff < 0:305 ydiff = 0306 else:307 xdiff = 0308 ydiff = 0309310 x1 = margin.x + padding[3] + node_width + span_width + xdiff311 y1 = margin.y + padding[0] + node_height + span_height + ydiff312313 return XY(x1, y1)314315 def _node_bottomright(self, node, use_padding=True):316 x = node.xy.x + node.colwidth - 1317 y = node.xy.y + node.colheight - 1318 margin = self.page_margin319 padding = self.page_padding320321 node_width = sum(self.node_width[i] for i in range(x + 1))322 node_height = sum(self.node_height[i] for i in range(y + 1))323 span_width = sum(self.span_width[i] for i in range(x + 1))324 span_height = sum(self.span_height[i] for i in range(y + 1))325326 if use_padding:327 width = node.width or self.metrics.node_width328 xdiff = (self.node_width[x] - width) // 2329 if xdiff < 0:330 xdiff = 0331332 height = node.height or self.metrics.node_height333 ydiff = (self.node_height[y] - height) // 2334 if ydiff < 0:335 ydiff = 0336 else:337 xdiff = 0338 ydiff = 0339340 x2 = margin.x + padding[3] + node_width + span_width - xdiff341 y2 = margin.y + padding[0] + node_height + span_height - ydiff342343 return XY(x2, y2)344345 def pagesize(self, width, height):346 margin = self.metrics.page_margin347 padding = self.metrics.page_padding348349 dummy = DiagramNode(None)350 dummy.xy = XY(width - 1, height - 1)351 x, y = self._node_bottomright(dummy, use_padding=False)352 x_span = self.span_width[width]353 y_span = self.span_height[height]354 return Size(x + margin.x + padding[1] + x_span,355 y + margin.y + padding[2] + y_span)356357358class NodeMetrics(SubMetrics):359 def __init__(self, metrics, x1, y1, x2, y2):360 self.metrics = metrics361 self._box = Box(x1, y1, x2, y2)362363 def __getattr__(self, name):364 if hasattr(self._box, name):365 return getattr(self._box, name)366 else:367 return getattr(self.metrics, name)368369 def __getitem__(self, key):370 return self.box[key]371372373 def box(self):374 return self._box375376377 def marginbox(self):378 return Box(self._box.x1 - self.span_width // 8,379 self._box.y1 - self.span_height // 4,380 self._box.x2 + self.span_width // 8,381 self._box.y2 + self.span_height // 4)382383384 def corebox(self):385 return Box(self._box.x1 + self.node_padding,386 self._box.y1 + self.node_padding,387 self._box.x2 - self.node_padding * 2,388 self._box.y2 - self.node_padding * 2)389390391 def grouplabelbox(self):392 return Box(self._box.x1, self._box.y1 - self.span_height // 2,393 self._box.x2, self._box.y1)394395396class EdgeMetrics(SubMetrics):397 def __init__(self, edge, metrics):398 self.metrics = metrics399 self.edge = edge400401402 def headshapes(self):403 pass404405406 def _shaft(self):407 pass408409410 def heads(self):411 heads = []412 head1, head2 = self.headshapes413414 if head1:415 heads.append(self._head(self.edge.node1, head1))416417 if head2:418 heads.append(self._head(self.edge.node2, head2))419420 return heads421422 def _head(self, node, direct):423 head = []424 cell = self.cellsize425 node = self.node(node)426427 if direct == 'up':428 xy = node.bottom429 head.append(XY(xy.x, xy.y + 1))430 head.append(XY(xy.x - cell // 2, xy.y + cell))431 head.append(XY(xy.x, xy.y + cell * 2))432 head.append(XY(xy.x + cell // 2, xy.y + cell))433 head.append(XY(xy.x, xy.y + 1))434 elif direct == 'down':435 xy = node.top436 head.append(XY(xy.x, xy.y - 1))437 head.append(XY(xy.x - cell // 2, xy.y - cell))438 head.append(XY(xy.x, xy.y - cell * 2))439 head.append(XY(xy.x + cell // 2, xy.y - cell))440 head.append(XY(xy.x, xy.y - 1))441 elif direct == 'right':442 xy = node.left443 head.append(XY(xy.x - 1, xy.y))444 head.append(XY(xy.x - cell, xy.y - cell // 2))445 head.append(XY(xy.x - cell * 2, xy.y))446 head.append(XY(xy.x - cell, xy.y + cell // 2))447 head.append(XY(xy.x - 1, xy.y))448 elif direct == 'left':449 xy = node.right450 head.append(XY(xy.x + 1, xy.y))451 head.append(XY(xy.x + cell, xy.y - cell // 2))452 head.append(XY(xy.x + cell * 2, xy.y))453 head.append(XY(xy.x + cell, xy.y + cell // 2))454 head.append(XY(xy.x + 1, xy.y))455 elif direct == 'rup':456 xy = node.bottom457 head.append(XY(xy.x, xy.y + cell))458 head.append(XY(xy.x - cell, xy.y + 1))459 head.append(XY(xy.x, xy.y + 1 * 2))460 head.append(XY(xy.x + cell, xy.y + 1))461 head.append(XY(xy.x, xy.y + cell))462 elif direct == 'rdown':463 xy = node.top464 head.append(XY(xy.x, xy.y - cell))465 head.append(XY(xy.x - cell, xy.y - 1))466 head.append(XY(xy.x, xy.y - 1 * 2))467 head.append(XY(xy.x + cell, xy.y - 1))468 head.append(XY(xy.x, xy.y - cell))469 elif direct == 'rright':470 xy = node.left471 head.append(XY(xy.x - cell, xy.y))472 head.append(XY(xy.x - 1, xy.y - cell))473 head.append(XY(xy.x - 1 * 2, xy.y))474 head.append(XY(xy.x - 1, xy.y + cell))475 head.append(XY(xy.x - cell, xy.y))476 elif direct == 'rleft':477 xy = node.right478 head.append(XY(xy.x + cell, xy.y))479 head.append(XY(xy.x + 1, xy.y - cell))480 head.append(XY(xy.x + 1 * 2, xy.y))481 head.append(XY(xy.x + 1, xy.y + cell))482 head.append(XY(xy.x + cell, xy.y))483484 if self.edge.hstyle not in ('composition', 'aggregation'):485 head.pop(2)486487 return head488489490 def shaft(self):491 cell = self.cellsize492 lines = self._shaft493 head1, head2 = self.headshapes494495 if head1:496 pt = lines.polylines[0].pop(0)497 if head1 == 'up':498 lines.polylines[0].insert(0, XY(pt.x, pt.y + cell))499 elif head1 == 'right':500 lines.polylines[0].insert(0, XY(pt.x - cell, pt.y))501 elif head1 == 'left':502 lines.polylines[0].insert(0, XY(pt.x + cell, pt.y))503 elif head1 == 'down':504 lines.polylines[0].insert(0, XY(pt.x, pt.y - cell))505 elif head1 == 'rup':506 lines.polylines[0].insert(0, XY(pt.x, pt.y + cell))507 elif head1 == 'rright':508 lines.polylines[0].insert(0, XY(pt.x - cell, pt.y))509 elif head1 == 'rleft':510 lines.polylines[0].insert(0, XY(pt.x + cell, pt.y))511 elif head1 == 'rdown':512 lines.polylines[0].insert(0, XY(pt.x, pt.y - cell))513514 if head2:515 pt = lines.polylines[-1].pop()516 if head2 == 'up':517 lines.polylines[-1].append(XY(pt.x, pt.y + cell))518 elif head2 == 'right':519 lines.polylines[-1].append(XY(pt.x - cell, pt.y))520 elif head2 == 'left':521 lines.polylines[-1].append(XY(pt.x + cell, pt.y))522 elif head2 == 'down':523 lines.polylines[-1].append(XY(pt.x, pt.y - cell))524 elif head2 == 'rup':525 lines.polylines[-1].append(XY(pt.x, pt.y + cell))526 elif head2 == 'rright':527 lines.polylines[-1].append(XY(pt.x - cell, pt.y))528 elif head2 == 'rleft':529 lines.polylines[-1].append(XY(pt.x + cell, pt.y))530 elif head2 == 'rdown':531 lines.polylines[-1].append(XY(pt.x, pt.y - cell))532533 return lines534535536 def labelbox(self):537 pass538539540class LandscapeEdgeMetrics(EdgeMetrics):541542 def headshapes(self):543 heads = []544 _dir = self.edge.direction545546 if self.edge.dir in ('back', 'both'):547 if _dir in ('left-up', 'left', 'same',548 'right-up', 'right', 'right-down'):549 heads.append('left')550 elif _dir == 'up':551 if self.edge.skipped:552 heads.append('left')553 else:554 heads.append('down')555 elif _dir in ('left-down', 'down'):556 if self.edge.skipped:557 heads.append('left')558 else:559 heads.append('up')560561 if self.edge.hstyle in ('manyone', 'manymany'):562 heads[-1] = 'r' + heads[-1]563 else:564 heads.append(None)565566 if self.edge.dir in ('forward', 'both'):567 if _dir in ('right-up', 'right', 'right-down'):568 heads.append('right')569 elif _dir == 'up':570 heads.append('up')571 elif _dir in ('left-up', 'left', 'left-down', 'down', 'same'):572 heads.append('down')573574 if self.edge.hstyle in ('onemany', 'manymany'):575 heads[-1] = 'r' + heads[-1]576 else:577 heads.append(None)578579 return heads580581582 def _shaft(self):583 span = XY(self.span_width, self.span_height)584 _dir = self.edge.direction585586 node1 = self.node(self.edge.node1)587 cell1 = self.cell(self.edge.node1, use_padding=False)588 node2 = self.node(self.edge.node2)589 cell2 = self.cell(self.edge.node2, use_padding=False)590591 shaft = EdgeLines()592 if _dir == 'right':593 shaft.moveTo(node1.right)594595 if self.edge.skipped:596 shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)597 shaft.lineTo(cell1.right.x + span.x // 2,598 cell1.bottomright.y + span.y // 2)599 shaft.lineTo(cell2.left.x - span.x // 4,600 cell2.bottomright.y + span.y // 2)601 shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)602603 shaft.lineTo(node2.left)604605 elif _dir == 'right-up':606 shaft.moveTo(node1.right)607608 if self.edge.skipped:609 shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)610 shaft.lineTo(cell1.right.x + span.x // 2,611 cell2.bottomleft.y + span.y // 2)612 shaft.lineTo(cell2.left.x - span.x // 4,613 cell2.bottomleft.y + span.y // 2)614 shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)615 else:616 shaft.lineTo(cell2.left.x - span.x // 4, cell1.right.y)617 shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)618619 shaft.lineTo(node2.left)620621 elif _dir == 'right-down':622 shaft.moveTo(node1.right)623 shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)624625 if self.edge.skipped:626 shaft.lineTo(cell1.right.x + span.x // 2,627 cell2.topleft.y - span.y // 2)628 shaft.lineTo(cell2.left.x - span.x // 4,629 cell2.topleft.y - span.y // 2)630 shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)631 else:632 shaft.lineTo(cell1.right.x + span.x // 2, cell2.left.y)633634 shaft.lineTo(node2.left)635636 elif _dir == 'up':637 if self.edge.skipped:638 shaft.moveTo(node1.right)639 shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)640 shaft.lineTo(cell1.right.x + span.x // 4,641 cell2.bottom.y + span.y // 2)642 shaft.lineTo(cell2.bottom.x, cell2.bottom.y + span.y // 2)643 else:644 shaft.moveTo(node1.top)645646 shaft.lineTo(node2.bottom)647648 elif _dir in ('left-up', 'left', 'same'):649 shaft.moveTo(node1.right)650 shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)651 shaft.lineTo(cell1.right.x + span.x // 4,652 cell2.top.y - span.y // 2 + span.y // 8)653 shaft.lineTo(cell2.top.x,654 cell2.top.y - span.y // 2 + span.y // 8)655 shaft.lineTo(node2.top)656657 elif _dir == 'left-down':658 if self.edge.skipped:659 shaft.moveTo(node1.right)660 shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)661 shaft.lineTo(cell1.right.x + span.x // 2,662 cell2.top.y - span.y // 2)663 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)664 else:665 shaft.moveTo(node1.bottom)666 shaft.lineTo(cell1.bottom.x,667 cell2.top.y - span.y // 2)668 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)669670 shaft.lineTo(node2.top)671672 elif _dir == 'down':673 if self.edge.skipped:674 shaft.moveTo(node1.right)675 shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y)676 shaft.lineTo(cell1.right.x + span.x // 2,677 cell2.top.y - span.y // 2 + span.y // 8)678 shaft.lineTo(cell2.top.x,679 cell2.top.y - span.y // 2 + span.y // 8)680 else:681 shaft.moveTo(node1.bottom)682683 shaft.lineTo(node2.top)684685 return shaft686687688 def labelbox(self):689 span = XY(self.span_width, self.span_height)690 node = XY(self.node_width, self.node_height)691692 _dir = self.edge.direction693 node1 = self.cell(self.edge.node1, use_padding=False)694 node2 = self.cell(self.edge.node2, use_padding=False)695696 if _dir == 'right':697 if self.edge.skipped:698 box = Box(node1.bottomright.x + span.x,699 node1.bottomright.y,700 node2.bottomleft.x - span.x,701 node2.bottomleft.y + span.y // 2)702 else:703 box = Box(node1.topright.x, node1.topright.y - span.y // 8,704 node2.left.x, node2.left.y - span.y // 8)705706 elif _dir == 'right-up':707 box = Box(node2.left.x - span.x, node1.top.y - node.y // 2,708 node2.bottomleft.x, node1.top.y)709710 elif _dir == 'right-down':711 box = Box(node1.right.x, node2.topleft.y - span.y // 8,712 node1.right.x + span.x, node2.left.y - span.y // 8)713714 elif _dir in ('up', 'left-up', 'left', 'same'):715 if self.edge.node2.xy.y < self.edge.node1.xy.y:716 box = Box(node1.topright.x - span.x // 2 + span.x // 4,717 node1.topright.y - span.y // 2,718 node1.topright.x + span.x // 2 + span.x // 4,719 node1.topright.y)720 else:721 box = Box(node1.top.x + span.x // 4,722 node1.top.y - span.y,723 node1.topright.x + span.x // 4,724 node1.topright.y - span.y // 2)725726 elif _dir in ('left-down', 'down'):727 box = Box(node2.top.x + span.x // 4,728 node2.top.y - span.y,729 node2.topright.x + span.x // 4,730 node2.topright.y - span.y // 2)731732 # shrink box733 box = Box(box[0] + span.x // 8, box[1],734 box[2] - span.x // 8, box[3])735736 return box737738739class PortraitEdgeMetrics(EdgeMetrics):740741 def headshapes(self):742 heads = []743 _dir = self.edge.direction744745 if self.edge.dir in ('back', 'both'):746 if _dir == 'right':747 if self.edge.skipped:748 heads.append('up')749 else:750 heads.append('left')751 elif _dir in ('up', 'right-up', 'same'):752 heads.append('up')753 elif _dir in ('left-up', 'left'):754 heads.append('left')755 elif _dir in ('left-down', 'down', 'right-down'):756 if self.edge.skipped:757 heads.append('left')758 else:759 heads.append('up')760761 if self.edge.hstyle in ('manyone', 'manymany'):762 heads[-1] = 'r' + heads[-1]763 else:764 heads.append(None)765766 if self.edge.dir in ('forward', 'both'):767 if _dir == 'right':768 if self.edge.skipped:769 heads.append('down')770 else:771 heads.append('right')772 elif _dir in ('up', 'right-up', 'same'):773 heads.append('down')774 elif _dir in ('left-up', 'left',775 'left-down', 'down', 'right-down'):776 heads.append('down')777778 if self.edge.hstyle in ('onemany', 'manymany'):779 heads[-1] = 'r' + heads[-1]780 else:781 heads.append(None)782783 return heads784785786 def _shaft(self):787 span = XY(self.span_width, self.span_height)788 _dir = self.edge.direction789790 node1 = self.node(self.edge.node1)791 cell1 = self.cell(self.edge.node1, use_padding=False)792 node2 = self.node(self.edge.node2)793 cell2 = self.cell(self.edge.node2, use_padding=False)794795 shaft = EdgeLines()796 if _dir in ('up', 'right-up', 'same', 'right'):797 if _dir == 'right' and not self.edge.skipped:798 shaft.moveTo(node1.right)799 shaft.lineTo(node2.left)800 else:801 shaft.moveTo(node1.bottom)802 shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)803 shaft.lineTo(cell2.right.x + span.x // 4,804 cell1.bottom.y + span.y // 2)805 shaft.lineTo(cell2.right.x + span.x // 4,806 cell2.top.y - span.y // 2 + span.y // 8)807 shaft.lineTo(cell2.top.x,808 cell2.top.y - span.y // 2 + span.y // 8)809 shaft.lineTo(node2.top)810811 elif _dir == 'right-down':812 shaft.moveTo(node1.bottom)813 shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)814815 if self.edge.skipped:816 shaft.lineTo(cell2.left.x - span.x // 2,817 cell1.bottom.y + span.y // 2)818 shaft.lineTo(cell2.topleft.x - span.x // 2,819 cell2.topleft.y - span.y // 2)820 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)821 else:822 shaft.lineTo(cell2.top.x, cell1.bottom.y + span.y // 2)823824 shaft.lineTo(node2.top)825826 elif _dir in ('left-up', 'left', 'same'):827 shaft.moveTo(node1.right)828 shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y)829 shaft.lineTo(cell1.right.x + span.x // 4,830 cell2.top.y - span.y // 2 + span.y // 8)831 shaft.lineTo(cell2.top.x,832 cell2.top.y - span.y // 2 + span.y // 8)833 shaft.lineTo(node2.top)834835 elif _dir == 'left-down':836 shaft.moveTo(node1.bottom)837838 if self.edge.skipped:839 shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)840 shaft.lineTo(cell2.right.x + span.x // 2,841 cell1.bottom.y + span.y // 2)842 shaft.lineTo(cell2.right.x + span.x // 2,843 cell2.top.y - span.y // 2)844 else:845 shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2)846847 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)848 shaft.lineTo(node2.top)849850 elif _dir == 'down':851 shaft.moveTo(node1.bottom)852853 if self.edge.skipped:854 shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)855 shaft.lineTo(cell1.right.x + span.x // 2,856 cell1.bottom.y + span.y // 2)857 shaft.lineTo(cell2.right.x + span.x // 2,858 cell2.top.y - span.y // 2)859 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)860861 shaft.lineTo(node2.top)862863 return shaft864865866 def labelbox(self):867 span = XY(self.span_width, self.span_height)868869 _dir = self.edge.direction870 node1 = self.cell(self.edge.node1, use_padding=False)871 node2 = self.cell(self.edge.node2, use_padding=False)872873 if _dir == 'right':874 if self.edge.skipped:875 box = Box(node1.bottomright.x + span.x,876 node1.bottomright.y,877 node2.bottomleft.x - span.x,878 node2.bottomleft.y + span.y // 2)879 else:880 box = Box(node1.topright.x, node1.topright.y - span.y // 8,881 node2.left.x, node2.left.y - span.y // 8)882883 elif _dir == 'right-up':884 box = Box(node2.left.x - span.x, node2.left.y,885 node2.bottomleft.x, node2.bottomleft.y)886887 elif _dir == 'right-down':888 box = Box(node2.topleft.x, node2.topleft.y - span.y // 2,889 node2.top.x, node2.top.y)890891 elif _dir in ('up', 'left-up', 'left', 'same'):892 if self.edge.node2.xy.y < self.edge.node1.xy.y:893 box = Box(node1.topright.x - span.x // 2 + span.x // 4,894 node1.topright.y - span.y // 2,895 node1.topright.x + span.x // 2 + span.x // 4,896 node1.topright.y)897 else:898 box = Box(node1.top.x + span.x // 4,899 node1.top.y - span.y,900 node1.topright.x + span.x // 4,901 node1.topright.y - span.y // 2)902903 elif _dir == 'down':904 box = Box(node2.top.x + span.x // 4,905 node2.top.y - span.y // 2,906 node2.topright.x + span.x // 4,907 node2.topright.y)908909 elif _dir == 'left-down':910 box = Box(node1.bottomleft.x, node1.bottomleft.y,911 node1.bottom.x, node1.bottom.y + span.y // 2)912913 # shrink box914 box = Box(box[0] + span.x // 8, box[1],915 box[2] - span.x // 8, box[3])916917 return box918919920class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics):921922 def headshapes(self):923 heads = []924925 if self.edge.direction == 'right-down':926 if self.edge.dir in ('back', 'both'):927 if self.edge.hstyle in ('manyone', 'manymany'):928 heads.append('rup')929 else:930 heads.append('up')931 else:932 heads.append(None)933934 if self.edge.dir in ('forward', 'both'):935 if self.edge.hstyle in ('onemany', 'manymany'):936 heads.append('rright')937 else:938 heads.append('right')939 else:940 heads.append(None)941 else:942 heads = super(FlowchartLandscapeEdgeMetrics, self).headshapes943944 return heads945946947 def _shaft(self):948 if self.edge.direction == 'right-down':949 span = XY(self.span_width, self.span_height)950 node1 = self.node(self.edge.node1)951 cell1 = self.cell(self.edge.node1, use_padding=False)952 node2 = self.node(self.edge.node2)953 cell2 = self.cell(self.edge.node2, use_padding=False)954955 shaft = EdgeLines()956 shaft.moveTo(node1.bottom)957958 if self.edge.skipped:959 shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2)960 shaft.lineTo(cell2.left.x - span.x // 4,961 cell1.bottom.y + span.y // 2)962 shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y)963 else:964 shaft.lineTo(cell1.bottom.x, cell2.left.y)965966 shaft.lineTo(node2.left)967 else:968 shaft = super(FlowchartLandscapeEdgeMetrics, self)._shaft969970 return shaft971972973 def labelbox(self):974 _dir = self.edge.direction975 if _dir == 'right':976 span = XY(self.span_width, self.span_height)977 cell1 = self.cell(self.edge.node1, use_padding=False)978 cell2 = self.cell(self.edge.node2, use_padding=False)979980 if self.edge.skipped:981 box = Box(cell1.bottom.x, cell1.bottom.y,982 cell1.bottomright.x,983 cell1.bottomright.y + span.y // 2)984 else:985 box = Box(cell1.bottom.x, cell2.left.y - span.y // 2,986 cell1.bottom.x, cell2.left.y)987 else:988 box = super(FlowchartLandscapeEdgeMetrics, self).labelbox989990 return box991992993class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics):994995 def headshapes(self):996 heads = []997998 if self.edge.direction == 'right-down':999 if self.edge.dir in ('back', 'both'):1000 if self.edge.hstyle in ('manyone', 'manymany'):1001 heads.append('left')1002 else:1003 heads.append('left')1004 else:1005 heads.append(None)10061007 if self.edge.dir in ('forward', 'both'):1008 if self.edge.dir in ('onemany', 'manymany'):1009 heads.append('rdown')1010 else:1011 heads.append('down')1012 else:1013 heads.append(None)1014 else:1015 heads = super(FlowchartPortraitEdgeMetrics, self).headshapes10161017 return heads101810191020 def _shaft(self):1021 if self.edge.direction == 'right-down':1022 span = XY(self.span_width, self.span_height)1023 node1 = self.node(self.edge.node1)1024 cell1 = self.cell(self.edge.node1, use_padding=False)1025 node2 = self.node(self.edge.node2)1026 cell2 = self.cell(self.edge.node2, use_padding=False)10271028 shaft = EdgeLines()1029 shaft.moveTo(node1.right)10301031 if self.edge.skipped:1032 shaft.lineTo(cell1.right.x + span.x * 3 // 4,1033 cell1.right.y)1034 shaft.lineTo(cell1.right.x + span.x * 3 // 4,1035 cell2.topleft.y - span.y // 2)1036 shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2)1037 else:1038 shaft.lineTo(cell2.top.x, cell1.right.y)10391040 shaft.lineTo(node2.top)1041 else:1042 shaft = super(FlowchartPortraitEdgeMetrics, self)._shaft10431044 return shaft104510461047 def labelbox(self):1048 _dir = self.edge.direction1049 span = XY(self.span_width, self.span_height)1050 cell1 = self.cell(self.edge.node1, use_padding=False)1051 cell2 = self.cell(self.edge.node2, use_padding=False)10521053 if _dir == 'down':1054 box = Box(cell2.topleft.x, cell2.top.y - span.y // 2,1055 cell2.top.x, cell2.top.y)1056 elif _dir == 'right':1057 if self.edge.skipped:1058 box = Box(cell1.bottom.x, cell1.bottom.y,1059 cell1.bottomright.x,1060 cell1.bottomright.y + span.y // 2)1061 else:1062 box = Box(cell1.bottom.x, cell2.left.y - span.y // 2,1063 cell1.bottom.x, cell2.left.y)1064 else:1065 box = super(FlowchartPortraitEdgeMetrics, self).labelbox10661067 return box