diff --git a/capellambse_context_diagrams/collectors/generic.py b/capellambse_context_diagrams/collectors/generic.py index fe60433b..3665494c 100644 --- a/capellambse_context_diagrams/collectors/generic.py +++ b/capellambse_context_diagrams/collectors/generic.py @@ -126,22 +126,12 @@ def exchange_data_collector( if data.is_hierarchical: target, source = source, target - label = collect_label(data.exchange) - for filter in data.filter_iterable: - try: - label = filters.FILTER_LABEL_ADJUSTERS[filter]( - data.exchange, label - ) - except KeyError: - logger.exception( - "There is no filter labelled: '%s' in filters.FILTER_LABEL_ADJUSTERS", - filter, - ) - params = (data.params or {}).copy() # Remove simple render parameters from params no_edgelabels: bool = params.pop("no_edgelabels", False) params.pop("transparent_background", False) + font_family = params.pop("font_family", "Open Sans") + font_size = params.pop("font_size", 12) render_adj: dict[str, t.Any] = {} for name, value in params.items(): @@ -149,7 +139,8 @@ def exchange_data_collector( filters.RENDER_ADJUSTERS[name](value, data.exchange, render_adj) except KeyError: logger.exception( - "There is no render parameter solver labelled: '%s' in filters.RENDER_ADJUSTERS", + "There is no render parameter solver labelled: '%s' " + "in filters.RENDER_ADJUSTERS", name, ) @@ -160,19 +151,25 @@ def exchange_data_collector( "targets": [render_adj.get("targets", target.uuid)], }, ) + + label = collect_label(data.exchange) + for filter in data.filter_iterable: + try: + label = filters.FILTER_LABEL_ADJUSTERS[filter]( + data.exchange, label + ) + except KeyError: + logger.exception( + "There is no filter labelled: '%s' in " + "filters.FILTER_LABEL_ADJUSTERS", + filter, + ) + if label and not no_edgelabels: - width, height = helpers.extent_func(label) - data.elkdata["edges"][-1]["labels"] = [ - { - "text": render_adj.get("labels_text", label), - "width": render_adj.get( - "labels_width", width + 2 * makers.LABEL_HPAD - ), - "height": render_adj.get( - "labels_height", height + 2 * makers.LABEL_VPAD - ), - } - ] + data.elkdata["edges"][-1]["labels"] = makers.make_label( + render_adj.get("labels_text", label), + max_width=makers.MAX_LABEL_WIDTH, + ) return source, target diff --git a/capellambse_context_diagrams/collectors/makers.py b/capellambse_context_diagrams/collectors/makers.py index 1451e1f0..47ed582c 100644 --- a/capellambse_context_diagrams/collectors/makers.py +++ b/capellambse_context_diagrams/collectors/makers.py @@ -17,10 +17,12 @@ """Default size of ports in pixels.""" PORT_PADDING = 2 """Default padding of ports in pixels.""" -LABEL_HPAD = 5 +LABEL_HPAD = 3 """Horizontal padding left and right of the label.""" LABEL_VPAD = 1 """Vertical padding above and below the label.""" +MAX_LABEL_WIDTH = 200 +"""Maximum width for edge labels.""" NEIGHBOR_VMARGIN = 20 """Vertical space between two neighboring boxes.""" EOI_WIDTH = 100 @@ -87,7 +89,7 @@ def make_label( [`ELKInputLabel`][capellambse_context_diagrams._elkjs.ELKInputLabel]. """ label_width, label_height = chelpers.get_text_extent(text) - icon_width, icon_height = icon + icon_width, _ = icon lines = [text] if max_width is not None and label_width > max_width: lines, _, _ = svghelpers.check_for_horizontal_overflow( @@ -105,11 +107,10 @@ def make_label( { "text": line, "width": icon_width + label_width + 2 * LABEL_HPAD, - "height": icon_height + label_height + 2 * LABEL_VPAD, + "height": label_height + 2 * LABEL_VPAD, "layoutOptions": layout_options, } ) - icon_height *= 0 return labels @@ -178,7 +179,7 @@ def calculate_height_and_width( """Calculate the size (width and height) from given labels for a box.""" icon = icon_size + icon_padding * 2 _height = sum(label["height"] + 2 * LABEL_VPAD for label in labels) + icon - min_width = max(label["width"] + 2 * LABEL_HPAD for label in labels) + icon + min_width = max(label["width"] + 2 * LABEL_HPAD for label in labels) width = min_width if slim_width else max(width, min_width) return width, max(height, _height) diff --git a/capellambse_context_diagrams/serializers.py b/capellambse_context_diagrams/serializers.py index 1689c5a5..d786130b 100644 --- a/capellambse_context_diagrams/serializers.py +++ b/capellambse_context_diagrams/serializers.py @@ -180,28 +180,31 @@ class type that stores all previously named classes. self._cache[child["id"]] = element elif child["type"] == "label": assert parent is not None - if isinstance(parent, diagram.Box) and not parent.port: + if not parent.port: if parent.JSON_TYPE != "symbol": parent.styleoverrides |= self.get_styleoverrides(child) - parent.labels.append( - diagram.Box( - ref + (child["position"]["x"], child["position"]["y"]), - (child["size"]["width"], child["size"]["height"]), - labels=[child["text"]], - styleoverrides=self.get_styleoverrides(child), + if parent.labels: + label_box = parent.labels[-1] + label_box.labels.append(child["text"]) + label_box.size = diagram.Vector2D( + max(label_box.size.x, child["size"]["width"]), + label_box.size.y + child["size"]["height"], ) - ) - else: - assert isinstance(parent, diagram.Edge) - parent.labels.append( - diagram.Box( - ref + (child["position"]["x"], child["position"]["y"]), - (child["size"]["width"], child["size"]["height"]), - labels=[child["text"]], - styleoverrides=self.get_styleoverrides(child), + label_box.pos = diagram.Vector2D( + min(label_box.pos.x, ref.x + child["position"]["x"]), + label_box.pos.y, + ) + else: + parent.labels.append( + diagram.Box( + ref + + (child["position"]["x"], child["position"]["y"]), + (child["size"]["width"], child["size"]["height"]), + labels=[child["text"]], + styleoverrides=self.get_styleoverrides(child), + ) ) - ) element = parent elif child["type"] == "junction": diff --git a/tests/test_filters.py b/tests/test_filters.py index 06efe458..fb912359 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -68,8 +68,9 @@ def start_filter_apply_test( def get_ExchangeItems(edge: diagram.Edge) -> list[str]: - assert isinstance(edge.labels[0].labels[0], str) - match = EX_PTRN.match(edge.labels[0].labels[0]) + label = " ".join(edge.labels[0].labels) + assert isinstance(label, str) + match = EX_PTRN.match(label) assert match is not None return match.group(1).split(", ") @@ -128,8 +129,8 @@ def test_context_diagrams_FEX_EX_ITEMS_is_applied( assert isinstance(aedge, diagram.Edge) assert len(aedge.labels) == 1 - assert isinstance(aedge.labels[0].labels[0], str) - assert aedge.labels[0].labels[0] == expected_label + assert isinstance(aedge.labels[0].labels, list) + assert [" ".join(aedge.labels[0].labels)] == [expected_label] @pytest.mark.parametrize("uuid", (FNC_UUID, INTERF_UUID)) @@ -145,7 +146,7 @@ def test_context_diagrams_FEX_OR_EX_ITEMS_is_applied( assert isinstance(aedge, diagram.Edge) - label = aedge.labels[0].labels[0] + label = " ".join(aedge.labels[0].labels) if edge.exchange_items: eitem_label_frag = ", ".join( (exi.name for exi in edge.exchange_items) @@ -189,7 +190,10 @@ def test_context_diagrams_NO_UUID_is_applied(model: MelodyModel) -> None: aedge = aird_diag[CAP_EXPLOIT] assert isinstance(aedge, diagram.Edge) - assert aedge.labels[0].labels == ["[CapabilityExploitation] to Capability"] + assert ( + " ".join(aedge.labels[0].labels) + == "[CapabilityExploitation] to Capability" + ) def test_context_diagrams_no_edgelabels_render_param_is_applied( diff --git a/tests/test_labels.py b/tests/test_labels.py index 472ec5ee..e448c43e 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -11,11 +11,13 @@ pytest.param( "d817767f-68b7-49a5-aa47-13419d41df0a", [ - ["Really long label"], - ["that needs"], - ["wrapping else"], - ["its parent box is"], - ["also very long!"], + "Really long", + "label that", + "needs", + "wrapping else", + "its parent box", + "is also very", + "long!", ], id="LogicalFunction", ), @@ -27,6 +29,5 @@ def test_context_diagrams( obj = model.by_uuid(uuid) diagram = obj.context_diagram.render(None) - labels = [label.labels for label in diagram[uuid].labels] - assert labels == expected_labels + assert diagram[uuid].labels[0].labels == expected_labels