From 837944e45bfb31cda595deb6100f4cde7440fdd4 Mon Sep 17 00:00:00 2001 From: huyenngn Date: Tue, 5 Nov 2024 07:55:01 +0100 Subject: [PATCH 1/4] feat: Add unused ports flag and simplify height calculation --- .../collectors/default.py | 48 +++++++++---------- capellambse_context_diagrams/context.py | 3 ++ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index 0964e81..0c1c1b8 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -69,11 +69,7 @@ def process_context(self): box.children = [self.centerbox] del self.data.children[0] - stack_heights: dict[str, float | int] = { - "input": -makers.NEIGHBOR_VMARGIN, - "output": -makers.NEIGHBOR_VMARGIN, - } - self._process_ports(stack_heights) + self._process_ports() if self.diagram._display_parent_relation and self.diagram.target.owner: current = self.diagram.target.owner @@ -102,9 +98,6 @@ def process_context(self): ) generic.move_edges(owner_boxes, self.exchanges.values(), self.data) - self.centerbox.height = max( - self.centerbox.height, *stack_heights.values() - ) if self.diagram._hide_direct_children: self.centerbox.children = [] hidden = set(edge.id for edge in self.centerbox.edges) @@ -210,32 +203,46 @@ def _process_exchanges( except AttributeError: continue - for p in inc + out: + if not self.diagram._display_unused_ports: + ports = [ + p + for p in inc + out + if (inc_c.get(p.uuid) or out_c.get(p.uuid)) + ] + else: + ports = inc + out + + self.centerbox.height = max( + self.centerbox.height, + (makers.PORT_SIZE + 2 * makers.PORT_PADDING) * (len(ports) + 1), + ) + for p in ports: port = makers.make_port(p.uuid) if self.diagram._display_port_labels: port.labels = makers.make_label(p.name) + label_height = sum(label.height for label in port.labels) + self.centerbox.height += label_height self.centerbox.ports.append(port) self.centerbox.layoutOptions["portLabels.placement"] = "OUTSIDE" - return (inc + out), ex_datas + return ports, ex_datas - def _process_ports(self, stack_heights: dict[str, float | int]) -> None: + def _process_ports(self) -> None: ports, ex_datas = self._process_exchanges() - for owner, local_ports, side in port_context_collector( - ex_datas, ports - ): + for owner, local_ports in port_context_collector(ex_datas, ports): _, label_height = helpers.get_text_extent(owner.name) height = max( label_height + 2 * makers.LABEL_VPAD, - makers.PORT_PADDING - + (makers.PORT_SIZE + makers.PORT_PADDING) * len(local_ports), + (makers.PORT_SIZE + 2 * makers.PORT_PADDING) + * (len(local_ports) + 1), ) local_port_objs = [] for j in local_ports: port = makers.make_port(j.uuid) if self.diagram._display_port_labels: port.labels = makers.make_label(j.name) + height += sum(label.height for label in port.labels) local_port_objs.append(port) if box := self.global_boxes.get(owner.uuid): # type: ignore[assignment] @@ -264,8 +271,6 @@ def _process_ports(self, stack_heights: dict[str, float | int]) -> None: current = self._make_owner_box(self.diagram, current) self.common_owners.add(current.uuid) - stack_heights[side] += makers.NEIGHBOR_VMARGIN + height - def _make_box( self, obj: t.Any, @@ -401,8 +406,6 @@ class ContextInfo(t.NamedTuple): This list only contains ports that at least one of the exchanges passed into ``collect_exchanges`` sees. """ - side: t.Literal["input", "output"] - """Whether this is an input or output to the element of interest.""" def port_context_collector( @@ -428,7 +431,6 @@ def port_context_collector( """ ctx: dict[str, ContextInfo] = {} - side: t.Literal["input", "output"] for exd in exchange_datas: try: source, target = generic.collect_exchange_endpoints(exd) @@ -437,10 +439,8 @@ def port_context_collector( if source in local_ports: port = target - side = "output" elif target in local_ports: port = source - side = "input" else: continue @@ -449,7 +449,7 @@ def port_context_collector( except AttributeError: continue - info = ContextInfo(owner, [], side) + info = ContextInfo(owner, []) info = ctx.setdefault(owner.uuid, info) if port not in info.ports: info.ports.append(port) diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index ff18998..2e143da 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -251,6 +251,7 @@ class ContextDiagram(m.AbstractDiagram): [`PORT_LABEL_POSITION`][capellambse_context_diagrams.context._elkjs.PORT_LABEL_POSITION]. * hide_direct_children - Hide direct children of the object of interest. + * display_unused_ports - Display ports that are not connected to an edge. """ _display_symbols_as_boxes: bool @@ -261,6 +262,7 @@ class ContextDiagram(m.AbstractDiagram): _display_port_labels: bool _port_label_position: str _transparent_background: bool + _display_unused_ports: bool def __init__( self, @@ -288,6 +290,7 @@ def __init__( "display_port_labels": False, "port_label_position": _elkjs.PORT_LABEL_POSITION.OUTSIDE.name, "transparent_background": False, + "display_unused_ports": False, } | default_render_parameters if standard_filter := STANDARD_FILTERS.get(class_): From d1bf6c8f6d5ec9dc8770c02a9f938a3e22060838 Mon Sep 17 00:00:00 2001 From: huyenngn Date: Tue, 5 Nov 2024 09:43:40 +0100 Subject: [PATCH 2/4] fix: Adjust box height on display port labels --- .../collectors/default.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index 0c1c1b8..4732826 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -217,11 +217,8 @@ def _process_exchanges( (makers.PORT_SIZE + 2 * makers.PORT_PADDING) * (len(ports) + 1), ) for p in ports: - port = makers.make_port(p.uuid) - if self.diagram._display_port_labels: - port.labels = makers.make_label(p.name) - label_height = sum(label.height for label in port.labels) - self.centerbox.height += label_height + port, height = self._make_port(p) + self.centerbox.height += height self.centerbox.ports.append(port) self.centerbox.layoutOptions["portLabels.placement"] = "OUTSIDE" @@ -239,10 +236,8 @@ def _process_ports(self) -> None: ) local_port_objs = [] for j in local_ports: - port = makers.make_port(j.uuid) - if self.diagram._display_port_labels: - port.labels = makers.make_label(j.name) - height += sum(label.height for label in port.labels) + port, label_heights = self._make_port(j) + height += label_heights local_port_objs.append(port) if box := self.global_boxes.get(owner.uuid): # type: ignore[assignment] @@ -271,6 +266,20 @@ def _process_ports(self) -> None: current = self._make_owner_box(self.diagram, current) self.common_owners.add(current.uuid) + def _make_port( + self, port_obj: t.Any + ) -> tuple[_elkjs.ELKInputPort, int | float]: + port = makers.make_port(port_obj.uuid) + height = 0.0 + if self.diagram._display_port_labels: + port.labels = makers.make_label(port_obj.name) + height += max( + 0, + sum(label.height for label in port.labels) + - 2 * makers.PORT_PADDING, + ) + return port, height + def _make_box( self, obj: t.Any, From 49e9c6ef7ae8560ececf38c728d9429bf32aeb95 Mon Sep 17 00:00:00 2001 From: huyenngn Date: Tue, 5 Nov 2024 10:23:11 +0000 Subject: [PATCH 3/4] test: Add test for unused ports --- .../collectors/default.py | 7 +- tests/data/ContextDiagram.aird | 183 ++++++++++++++++++ tests/data/ContextDiagram.capella | 28 ++- tests/test_context_diagrams.py | 61 ++++-- 4 files changed, 255 insertions(+), 24 deletions(-) diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index 4732826..e265ae5 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -203,14 +203,11 @@ def _process_exchanges( except AttributeError: continue + ports = inc + out if not self.diagram._display_unused_ports: ports = [ - p - for p in inc + out - if (inc_c.get(p.uuid) or out_c.get(p.uuid)) + p for p in ports if (inc_c.get(p.uuid) or out_c.get(p.uuid)) ] - else: - ports = inc + out self.centerbox.height = max( self.centerbox.height, diff --git a/tests/data/ContextDiagram.aird b/tests/data/ContextDiagram.aird index 1aef84a..2c868ff 100644 --- a/tests/data/ContextDiagram.aird +++ b/tests/data/ContextDiagram.aird @@ -178,6 +178,14 @@ + + +
+
+ + + + @@ -17043,4 +17051,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KEEP_LOCATION + KEEP_SIZE + KEEP_RATIO + + + + + + + + + + + + + + + + + + + + + + + + + + KEEP_LOCATION + KEEP_SIZE + KEEP_RATIO + + + + + + + + + + + + + + + + + + KEEP_LOCATION + KEEP_SIZE + KEEP_RATIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/ContextDiagram.capella b/tests/data/ContextDiagram.capella index abddb52..d511bee 100644 --- a/tests/data/ContextDiagram.capella +++ b/tests/data/ContextDiagram.capella @@ -62,7 +62,7 @@ definition="#682bd51d-5451-4930-a97e-8bfca6c3a127" value="true"/> + value="2021-07-23T13:00:00.000+0000"/> + + + + + + + @@ -3378,6 +3390,9 @@ The predator is far away + name="SysParent" abstractType="#1921eeeb-f2fd-4b8a-9f79-0e369e7cc29c"/> + @@ -4438,6 +4455,15 @@ The predator is far away kind="FLOW"/> + + + + None: - uuid, min_size = diagram_elements.pop() + uuid, min_size, min_size_labels = diagram_elements.pop() obj = model.by_uuid(uuid) - adiag = obj.context_diagram.render(None, display_symbols_as_boxes=True) + adiag = obj.context_diagram.render( + None, display_symbols_as_boxes=True, display_port_labels=False + ) + bdiag = obj.context_diagram.render( + None, display_symbols_as_boxes=True, display_port_labels=True + ) assert adiag[uuid].size.y >= min_size - for uuid, min_size in diagram_elements: + assert bdiag[uuid].size.y >= min_size_labels + for uuid, min_size, min_size_labels in diagram_elements: obj = model.by_uuid(uuid) assert adiag[uuid].size.y >= min_size + assert bdiag[uuid].size.y >= min_size_labels def test_context_diagrams_symbol_sizing( @@ -280,3 +288,20 @@ def test_context_diagram_detects_and_handles_cycles( diag = obj.context_diagram assert diag.nodes + + +def test_context_diagram_display_unused_ports( + model: capellambse.MelodyModel, +) -> None: + obj = model.by_uuid("446d3f9f-644d-41ee-bd57-8ae0f7662db2") + unused_port_uuid = "5cbc4d2d-1b9c-4e10-914e-44d4526e4a2f" + + obj.context_diagram.render("svgdiagram", display_unused_ports=True).save( + pretty=True + ) + + adiag = obj.context_diagram.render(None, display_unused_ports=False) + bdiag = obj.context_diagram.render(None, display_unused_ports=True) + + assert unused_port_uuid not in set(element.uuid for element in adiag) + assert unused_port_uuid in set(element.uuid for element in bdiag) From d68846fd7515bcfb4fbb03b20fcca63345653636 Mon Sep 17 00:00:00 2001 From: Huyen Nguyen <48179958+huyenngn@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:29:54 +0100 Subject: [PATCH 4/4] refactor: Group port flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernst Würger --- capellambse_context_diagrams/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index 2e143da..d92bf6c 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -289,8 +289,8 @@ def __init__( "slim_center_box": True, "display_port_labels": False, "port_label_position": _elkjs.PORT_LABEL_POSITION.OUTSIDE.name, - "transparent_background": False, "display_unused_ports": False, + "transparent_background": False, } | default_render_parameters if standard_filter := STANDARD_FILTERS.get(class_):