Skip to content

Commit

Permalink
feat: Add display_parent_relation to portless ContextDiagrams
Browse files Browse the repository at this point in the history
  • Loading branch information
ewuerger committed May 2, 2024
1 parent 76cc41e commit 278003c
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 4 deletions.
2 changes: 1 addition & 1 deletion capellambse_context_diagrams/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def register_classes() -> None:
"""Add the `context_diagram` property to the relevant model objects."""
supported_classes: list[SupportedClass] = [
(oa.Entity, DiagramType.OAB, {}),
(oa.OperationalActivity, DiagramType.OAIB, {}),
(oa.OperationalActivity, DiagramType.OAB, {}),
(oa.OperationalCapability, DiagramType.OCB, {}),
(ctx.Mission, DiagramType.MCB, {}),
(ctx.Capability, DiagramType.MCB, {"display_symbols_as_boxes": False}),
Expand Down
101 changes: 99 additions & 2 deletions capellambse_context_diagrams/collectors/portless.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ def collector(
}
contexts = context_collector(connections, diagram.target)
made_boxes = {centerbox["id"]: centerbox}
if diagram.display_parent_relation and diagram.target.owner is not None:
box = makers.make_box(
diagram.target.owner,
no_symbol=diagram.display_symbols_as_boxes,
layout_options=makers.DEFAULT_LABEL_LAYOUT_OPTIONS,
)
box["children"] = [centerbox]
del data["children"][0]
made_boxes[diagram.target.owner.uuid] = box

for i, exchanges, side in contexts:
var_height = generic.MARKER_PADDING + (
generic.MARKER_SIZE + generic.MARKER_PADDING
Expand All @@ -58,7 +68,7 @@ def collector(
else:
height = var_height

if box := made_boxes.get(i.uuid):
if box := made_boxes.get(i.uuid): # type: ignore[assignment]
if box is centerbox:
continue
box["height"] = height
Expand All @@ -70,10 +80,32 @@ def collector(
)
made_boxes[i.uuid] = box

if diagram.display_parent_relation:
if i == diagram.target.owner:
_move_edge_to_local_edges(box, connections, data)
elif i.owner is not None:
if not (parent_box := made_boxes.get(i.owner.uuid)):
parent_box = makers.make_box(
i.owner,
no_symbol=diagram.display_symbols_as_boxes,
)
made_boxes[i.owner.uuid] = parent_box

parent_box.setdefault("children", []).append(
made_boxes.pop(i.uuid)
)
for label in parent_box["labels"]:
label["layoutOptions"] = (
makers.DEFAULT_LABEL_LAYOUT_OPTIONS
)

_move_edge_to_local_edges(parent_box, connections, data)

stack_heights[side] += makers.NEIGHBOR_VMARGIN + height

del made_boxes[centerbox["id"]]
data["children"].extend(made_boxes.values())
_move_parent_boxes(diagram.target, data)
centerbox["height"] = max(centerbox["height"], *stack_heights.values())
if not diagram.display_symbols_as_boxes and makers.is_symbol(
diagram.target
Expand Down Expand Up @@ -140,7 +172,6 @@ def context_collector(
info = ctx.setdefault(obj.uuid, info)
if exchange not in info.connections:
info.connections.append(exchange)

return iter(ctx.values())


Expand Down Expand Up @@ -192,3 +223,69 @@ def get_exchanges(

filtered = filter(chain.from_iterable(exchanges))
yield from {i.uuid: i for i in filtered}.values()


def _move_edge_to_local_edges(
owner_box: _elkjs.ELKInputChild,
connections: list[common.GenericElement],
data: _elkjs.ELKInputData,
) -> None:
edges_to_remove: list[str] = []
for c in connections:
uuids = set()
if source_owner := c.source.owner:
uuids.add(source_owner.uuid)
if target_owner := c.target.owner:
uuids.add(target_owner.uuid)

if source_owner == target_owner and owner_box["id"] in uuids:
for edge in data["edges"]:
if edge["id"] == c.uuid:
owner_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 _move_parent_boxes(
obj: common.GenericElement, data: _elkjs.ELKInputData
) -> None:
owner_boxes: dict[str, _elkjs.ELKInputChild] = {
child["id"]: child
for child in data["children"]
if child.get("children")
}
boxes_to_remove: list[str] = []
for child in data["children"]:
if not (children := child.get("children")):
continue

owner = obj._model.by_uuid(child["id"])
if (
not (oowner := owner.owner)
or isinstance(oowner, layers.oa.EntityPkg)
or not (oowner_box := owner_boxes.get(oowner.uuid))
):
continue

oowner_box.setdefault("children", []).append(child)
boxes_to_remove.append(child["id"])
edges_to_move: list[_elkjs.ELKInputEdge] = []
for c in children:
for e in data["edges"]:
if c["id"] not in e["sources"] + e["targets"]: # type: ignore[operator]
continue

edges_to_move.append(e)

oowner_box.setdefault("edges", []).extend(edges_to_move)
edges_to_remove = {e["id"] for e in edges_to_move}
data["edges"] = [
e for e in data["edges"] if e["id"] not in edges_to_remove
]

data["children"] = [
b for b in data["children"] if b["id"] not in boxes_to_remove
]
18 changes: 17 additions & 1 deletion capellambse_context_diagrams/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
* `edge`
* `junction`.
"""
EdgeContext = tuple[
_elkjs.ELKOutputEdge,
diagram.Vector2D,
diagram.Box | diagram.Edge | None,
]

REMAP_STYLECLASS: dict[str, str] = {"Unset": "Association"}

Expand All @@ -54,6 +59,7 @@ def __init__(self, elk_diagram: context.ContextDiagram) -> None:
self.model = elk_diagram.target._model
self._diagram = elk_diagram
self._cache: dict[str, diagram.Box | diagram.Edge] = {}
self._edges: list[EdgeContext] = []

def make_diagram(
self,
Expand Down Expand Up @@ -81,6 +87,9 @@ def make_diagram(
for child in data["children"]:
self.deserialize_child(child, diagram.Vector2D(), None)

for edge, ref, parent in self._edges:
self.deserialize_child(edge, ref, parent)

self.diagram.calculate_viewport()
self.order_children()
return self.diagram
Expand Down Expand Up @@ -116,6 +125,7 @@ class type that stores all previously named classes.
styleclass: str | None = child["id"][2:].split("_", 1)[0]
else:
styleclass = self.get_styleclass(child["id"])

element: diagram.Box | diagram.Edge | diagram.Circle
if child["type"] in {"node", "port"}:
assert parent is None or isinstance(parent, diagram.Box)
Expand Down Expand Up @@ -237,7 +247,10 @@ class type that stores all previously named classes.
return

for i in child.get("children", []): # type: ignore
self.deserialize_child(i, ref, element)
if i["type"] == "edge":
self._edges.append((i, ref, parent))
else:
self.deserialize_child(i, ref, element)

def _is_hierarchical(self, uuid: str) -> bool:
def is_contained(obj: diagram.Box) -> bool:
Expand Down Expand Up @@ -282,6 +295,9 @@ def get_styleoverrides(

styleoverrides = style_condition(obj, self) or {}

if child["id"] == self._diagram.target.uuid:
styleoverrides["stroke-width"] = "3"

style: dict[str, t.Any]
if style := child.get("style", {}):
styleoverrides |= style
Expand Down

0 comments on commit 278003c

Please sign in to comment.