--- a/eric6/Graphics/ImportsDiagramBuilder.py Sun May 02 18:16:54 2021 +0200 +++ b/eric6/Graphics/ImportsDiagramBuilder.py Mon May 03 19:42:27 2021 +0200 @@ -128,11 +128,7 @@ self.scene.addItem(ct) return - shapes = {} - p = 10 - y = 10 - maxHeight = 0 - sceneRect = self.umlView.sceneRect() + self.__shapes = {} modules = self.__buildModulesDict() externalMods = [] @@ -175,51 +171,31 @@ impLst.append(n) if n not in externalMods: externalMods.append(n) + classNames = [] - for cls in list(modules[module].classes.keys()): - className = modules[module].classes[cls].name + for class_ in list(modules[module].classes.keys()): + className = modules[module].classes[class_].name if className not in classNames: classNames.append(className) shape = self.__addModule(module, classNames, 0.0, 0.0) - shapeRect = shape.sceneBoundingRect() - shapes[module] = (shape, impLst) - pn = p + shapeRect.width() + 10 - maxHeight = max(maxHeight, shapeRect.height()) - if pn > sceneRect.width(): - p = 10 - y += maxHeight + 10 - maxHeight = shapeRect.height() - shape.setPos(p, y) - p += shapeRect.width() + 10 - else: - shape.setPos(p, y) - p = pn + self.__shapes[module] = (shape, impLst) for module in externalMods: shape = self.__addModule(module, [], 0.0, 0.0) - shapeRect = shape.sceneBoundingRect() - shapes[module] = (shape, []) - pn = p + shapeRect.width() + 10 - maxHeight = max(maxHeight, shapeRect.height()) - if pn > sceneRect.width(): - p = 10 - y += maxHeight + 10 - maxHeight = shapeRect.height() - shape.setPos(p, y) - p += shapeRect.width() + 10 - else: - shape.setPos(p, y) - p = pn + self.__shapes[module] = (shape, []) - rect = self.umlView._getDiagramRect(10) - sceneRect = self.umlView.sceneRect() - if rect.width() > sceneRect.width(): - sceneRect.setWidth(rect.width()) - if rect.height() > sceneRect.height(): - sceneRect.setHeight(rect.height()) - self.umlView.setSceneSize(sceneRect.width(), sceneRect.height()) + # build a list of routes + nodes = [] + routes = [] + for module in self.__shapes: + nodes.append(module) + for rel in self.__shapes[module][1]: + route = (module, rel) + if route not in routes: + routes.append(route) - self.__createAssociations(shapes) + self.__arrangeNodes(nodes, routes[:]) + self.__createAssociations(routes) self.umlView.autoAdjustSceneSize(limit=True) def __addModule(self, name, classes, x, y): @@ -241,20 +217,100 @@ impW.setId(self.umlView.getItemId()) return impW - def __createAssociations(self, shapes): + def __arrangeNodes(self, nodes, routes, whiteSpaceFactor=1.2): + """ + Private method to arrange the shapes on the canvas. + + The algorithm is borrowed from Boa Constructor. + + @param nodes list of nodes to arrange + @type list of str + @param routes list of routes + @type list of tuple of (str, str) + @param whiteSpaceFactor factor to increase whitespace between + items + @type float + """ + from . import GraphicsUtilities + generations = GraphicsUtilities.sort(nodes, routes) + + # calculate width and height of all elements + sizes = [] + for generation in generations: + sizes.append([]) + for child in generation: + sizes[-1].append( + self.__shapes[child][0].sceneBoundingRect()) + + # calculate total width and total height + width = 0 + height = 0 + widths = [] + heights = [] + for generation in sizes: + currentWidth = 0 + currentHeight = 0 + + for rect in generation: + if rect.height() > currentHeight: + currentHeight = rect.height() + currentWidth += rect.width() + + # update totals + if currentWidth > width: + width = currentWidth + height += currentHeight + + # store generation info + widths.append(currentWidth) + heights.append(currentHeight) + + # add in some whitespace + width *= whiteSpaceFactor + height = height * whiteSpaceFactor - 20 + verticalWhiteSpace = 40.0 + + sceneRect = self.umlView.sceneRect() + width += 50.0 + height += 50.0 + swidth = sceneRect.width() if width < sceneRect.width() else width + sheight = sceneRect.height() if height < sceneRect.height() else height + self.umlView.setSceneSize(swidth, sheight) + + # distribute each generation across the width and the + # generations across height + y = 10.0 + for currentWidth, currentHeight, generation in ( + zip(reversed(widths), reversed(heights), reversed(generations)) + ): + x = 10.0 + # whiteSpace is the space between any two elements + whiteSpace = ( + (width - currentWidth - 20) / + (len(generation) - 1.0 or 2.0) + ) + for name in generation: + shape = self.__shapes[name][0] + shape.setPos(x, y) + rect = shape.sceneBoundingRect() + x = x + rect.width() + whiteSpace + y = y + currentHeight + verticalWhiteSpace + + def __createAssociations(self, routes): """ Private method to generate the associations between the module shapes. - @param shapes list of shapes + @param routes list of associations + @type list of tuple of (str, str) """ from .AssociationItem import AssociationItem, AssociationType - for module in list(shapes.keys()): - for rel in shapes[module][1]: - assoc = AssociationItem( - shapes[module][0], shapes[rel][0], - AssociationType.IMPORTS, - colors=self.umlView.getDrawingColors()) - self.scene.addItem(assoc) + for route in routes: + assoc = AssociationItem( + self.__shapes[route[0]][0], + self.__shapes[route[1]][0], + AssociationType.IMPORTS, + colors=self.umlView.getDrawingColors()) + self.scene.addItem(assoc) def getPersistenceData(self): """