diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index ec9186c8..32a47771 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -45,35 +45,60 @@ def collector( except AttributeError: continue + global_boxes = {centerbox["id"]: centerbox} + if diagram.display_parent_relation: + box = makers.make_box( + diagram.target.parent, + no_symbol=diagram.display_symbols_as_boxes, + ) + box["children"] = [centerbox] + del data["children"][0] + global_boxes[diagram.target.parent.uuid] = box + stack_heights: dict[str, float | int] = { "input": -makers.NEIGHBOR_VMARGIN, "output": -makers.NEIGHBOR_VMARGIN, } - global_boxes = {centerbox["id"]: centerbox} child_boxes: list[_elkjs.ELKInputChild] = [] - for i, local_ports, side in port_context_collector(ex_datas, ports): - _, label_height = helpers.get_text_extent(i.name) + for child, local_ports, side in port_context_collector(ex_datas, ports): + _, label_height = helpers.get_text_extent(child.name) height = max( label_height + 2 * makers.LABEL_VPAD, makers.PORT_PADDING + (makers.PORT_SIZE + makers.PORT_PADDING) * len(local_ports), ) - if box := global_boxes.get(i.uuid): + if box := global_boxes.get(child.uuid): # type: ignore[assignment] if box is centerbox: continue - box["ports"].extend( + box.setdefault("ports", []).extend( [makers.make_port(j.uuid) for j in local_ports] ) box["height"] += height else: box = makers.make_box( - i, height=height, no_symbol=diagram.display_symbols_as_boxes + child, + height=height, + no_symbol=diagram.display_symbols_as_boxes, ) box["ports"] = [makers.make_port(j.uuid) for j in local_ports] - if i.parent.uuid == centerbox["id"]: + if child.parent.uuid == centerbox["id"]: child_boxes.append(box) else: - global_boxes[i.uuid] = box + global_boxes[child.uuid] = box + + if diagram.display_parent_relation: + if child == diagram.target.parent: + _move_edge_to_local_edges( + box, connections, local_ports, diagram, data + ) + elif child.parent == diagram.target.parent: + parent_box = global_boxes[child.parent.uuid] + parent_box.setdefault("children", []).append( + global_boxes.pop(child.uuid) + ) + _move_edge_to_local_edges( + parent_box, connections, local_ports, diagram, data + ) stack_heights[side] += makers.NEIGHBOR_VMARGIN + height @@ -87,6 +112,31 @@ def collector( return data +def _move_edge_to_local_edges( + box: _elkjs.ELKInputChild, + connections: list[common.GenericElement], + local_ports: list[common.GenericElement], + diagram: context.ContextDiagram, + data: _elkjs.ELKInputData, +) -> None: + edges_to_remove: list[str] = [] + for c in connections: + if ( + c.target in local_ports + and c.source in diagram.target.ports + or c.source in local_ports + and c.target in diagram.target.ports + ): + for edge in data["edges"]: + if edge["id"] == c.uuid: + box.setdefault("edges", []).append(edge) + edges_to_remove.append(edge["id"]) + + data["edges"] = [ + e for e in data["edges"] if e["id"] not in edges_to_remove + ] + + def port_collector( target: common.GenericElement | common.ElementList, diagram_type: DT ) -> list[common.GenericElement]: diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index ab8b5639..c57b8833 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -231,6 +231,9 @@ class ContextDiagram(diagram.AbstractDiagram): avoids the object of interest to become one giant, oversized symbol in the middle of the diagram, and instead keeps the symbol small and only enlarges the surrounding box. + display_parent_relation + Display objects with a parent relationship to the object of + interest as the parent box. slim_center_box Minimal width for the center box, containing just the icon and the label. This is False if hierarchy was identified. @@ -253,6 +256,7 @@ def __init__( *, render_styles: dict[str, styling.Styler] | None = None, display_symbols_as_boxes: bool = False, + display_parent_relation: bool = False, include_inner_objects: bool = False, slim_center_box: bool = True, ) -> None: @@ -264,6 +268,7 @@ def __init__( self.serializer = serializers.DiagramSerializer(self) self.__filters: cabc.MutableSet[str] = self.FilterSet(self) self.display_symbols_as_boxes = display_symbols_as_boxes + self.display_parent_relation = display_parent_relation self.include_inner_objects = include_inner_objects self.slim_center_box = slim_center_box diff --git a/capellambse_context_diagrams/serializers.py b/capellambse_context_diagrams/serializers.py index f88f074c..84084602 100644 --- a/capellambse_context_diagrams/serializers.py +++ b/capellambse_context_diagrams/serializers.py @@ -107,7 +107,7 @@ def deserialize_child( class type that stores all previously named classes. """ styleclass: str | None = self.get_styleclass(child["id"]) - element: diagram.Box | diagram.Edge + element: diagram.Box | diagram.Edge | diagram.Circle if child["type"] in {"node", "port"}: assert parent is None or isinstance(parent, diagram.Box) has_symbol_cls = False @@ -205,7 +205,7 @@ class type that stores all previously named classes. pos += self.diagram[self._diagram.target.uuid].pos element = diagram.Circle( - pos, + ref + pos, 5, uuid=child["id"], styleclass=self.get_styleclass(uuid),