diff --git a/src/capellambse_context_diagrams/__init__.py b/src/capellambse_context_diagrams/__init__.py
index 0fbda150..256c5c49 100644
--- a/src/capellambse_context_diagrams/__init__.py
+++ b/src/capellambse_context_diagrams/__init__.py
@@ -26,7 +26,6 @@
import capellambse.model as m
from capellambse.diagram import COLORS, CSSdef, capstyle
from capellambse.metamodel import cs, fa, information, la, oa, pa, sa
-from capellambse.model import DiagramType
from . import _elkjs, context, styling
@@ -38,7 +37,7 @@
DefaultRenderParams = dict[str, t.Any]
SupportedContextClass = tuple[
- type[m.ModelElement], DiagramType, DefaultRenderParams
+ type[m.ModelElement], m.DiagramType, DefaultRenderParams
]
SupportedInterfaceContextClass = tuple[
type[m.ModelElement], dict[type[m.ModelElement], str], DefaultRenderParams
@@ -75,28 +74,29 @@ def init() -> None:
register_data_flow_view()
register_cable_tree_view()
register_custom_diagram()
+ register_diagram_layout_accessor()
# register_functional_context() XXX: Future
def register_classes() -> None:
"""Add the `context_diagram` property to the relevant model objects."""
supported_classes: list[SupportedContextClass] = [
- (oa.Entity, DiagramType.OAB, {}),
+ (oa.Entity, m.DiagramType.OAB, {}),
(
oa.OperationalActivity,
- DiagramType.OAB,
+ m.DiagramType.OAB,
{"display_parent_relation": True},
),
- (oa.OperationalCapability, DiagramType.OCB, {}),
- (sa.Mission, DiagramType.MCB, {}),
+ (oa.OperationalCapability, m.DiagramType.OCB, {}),
+ (sa.Mission, m.DiagramType.MCB, {}),
(
sa.Capability,
- DiagramType.MCB,
+ m.DiagramType.MCB,
{"display_symbols_as_boxes": False},
),
(
sa.SystemComponent,
- DiagramType.SAB,
+ m.DiagramType.SAB,
{
"display_symbols_as_boxes": True,
"display_parent_relation": True,
@@ -106,7 +106,7 @@ def register_classes() -> None:
),
(
sa.SystemFunction,
- DiagramType.SAB,
+ m.DiagramType.SAB,
{
"display_symbols_as_boxes": True,
"display_parent_relation": True,
@@ -115,7 +115,7 @@ def register_classes() -> None:
),
(
la.LogicalComponent,
- DiagramType.LAB,
+ m.DiagramType.LAB,
{
"display_symbols_as_boxes": True,
"display_parent_relation": True,
@@ -125,7 +125,7 @@ def register_classes() -> None:
),
(
la.LogicalFunction,
- DiagramType.LAB,
+ m.DiagramType.LAB,
{
"display_symbols_as_boxes": True,
"display_parent_relation": True,
@@ -134,7 +134,7 @@ def register_classes() -> None:
),
(
pa.PhysicalComponent,
- DiagramType.PAB,
+ m.DiagramType.PAB,
{
"display_parent_relation": True,
"display_port_labels": True,
@@ -143,7 +143,7 @@ def register_classes() -> None:
),
(
pa.PhysicalFunction,
- DiagramType.PAB,
+ m.DiagramType.PAB,
{
"display_parent_relation": True,
},
@@ -178,32 +178,32 @@ def register_interface_context() -> None:
(
oa.CommunicationMean,
{
- oa.EntityPkg: DiagramType.OAB.value,
- oa.Entity: DiagramType.OAB.value,
+ oa.EntityPkg: m.DiagramType.OAB.value,
+ oa.Entity: m.DiagramType.OAB.value,
},
{"include_interface": True},
),
(
fa.ComponentExchange,
{
- sa.SystemComponentPkg: DiagramType.SAB.value,
- sa.SystemComponent: DiagramType.SAB.value,
- la.LogicalComponentPkg: DiagramType.LAB.value,
- la.LogicalComponent: DiagramType.LAB.value,
- pa.PhysicalComponentPkg: DiagramType.PAB.value,
- pa.PhysicalComponent: DiagramType.PAB.value,
+ sa.SystemComponentPkg: m.DiagramType.SAB.value,
+ sa.SystemComponent: m.DiagramType.SAB.value,
+ la.LogicalComponentPkg: m.DiagramType.LAB.value,
+ la.LogicalComponent: m.DiagramType.LAB.value,
+ pa.PhysicalComponentPkg: m.DiagramType.PAB.value,
+ pa.PhysicalComponent: m.DiagramType.PAB.value,
},
{"include_interface": True, "include_port_allocations": True},
),
(
cs.PhysicalLink,
{
- sa.SystemComponentPkg: DiagramType.SAB.value,
- sa.SystemComponent: DiagramType.SAB.value,
- la.LogicalComponentPkg: DiagramType.LAB.value,
- la.LogicalComponent: DiagramType.LAB.value,
- pa.PhysicalComponentPkg: DiagramType.PAB.value,
- pa.PhysicalComponent: DiagramType.PAB.value,
+ sa.SystemComponentPkg: m.DiagramType.SAB.value,
+ sa.SystemComponent: m.DiagramType.SAB.value,
+ la.LogicalComponentPkg: m.DiagramType.LAB.value,
+ la.LogicalComponent: m.DiagramType.LAB.value,
+ pa.PhysicalComponentPkg: m.DiagramType.PAB.value,
+ pa.PhysicalComponent: m.DiagramType.PAB.value,
},
{"include_interface": True, "display_port_labels": True},
),
@@ -227,7 +227,7 @@ def register_interface_context() -> None:
"stroke-dasharray": "2",
"text_fill": COLORS["black"],
}
- for dt in (DiagramType.SAB, DiagramType.LAB, DiagramType.PAB):
+ for dt in (m.DiagramType.SAB, m.DiagramType.LAB, m.DiagramType.PAB):
capstyle.STYLES[dt.value]["Edge.PortInputAllocation"] = (
port_alloc_input_style
)
@@ -244,11 +244,11 @@ def register_functional_context() -> None:
The functional context diagrams will be available soon.
"""
attr_name = f"functional_{ATTR_NAME}"
- supported_classes: list[tuple[type[m.ModelElement], DiagramType]] = [
- (oa.Entity, DiagramType.OAB),
- (sa.SystemComponent, DiagramType.SAB),
- (la.LogicalComponent, DiagramType.LAB),
- (pa.PhysicalComponent, DiagramType.PAB),
+ supported_classes: list[tuple[type[m.ModelElement], m.DiagramType]] = [
+ (oa.Entity, m.DiagramType.OAB),
+ (sa.SystemComponent, m.DiagramType.SAB),
+ (la.LogicalComponent, m.DiagramType.LAB),
+ (pa.PhysicalComponent, m.DiagramType.PAB),
]
class_: type[m.ModelElement]
for class_, dgcls in supported_classes:
@@ -264,7 +264,7 @@ def register_physical_port_context() -> None:
m.set_accessor(
cs.PhysicalPort,
ATTR_NAME,
- context.PhysicalPortContextAccessor(DiagramType.PAB.value, {}),
+ context.PhysicalPortContextAccessor(m.DiagramType.PAB.value, {}),
)
@@ -273,7 +273,7 @@ def register_tree_view() -> None:
m.set_accessor(
information.Class,
"tree_view",
- context.ClassTreeAccessor(DiagramType.CDB.value),
+ context.ClassTreeAccessor(m.DiagramType.CDB.value),
)
@@ -284,14 +284,14 @@ def register_realization_view() -> None:
of all layers.
"""
supported_classes: list[SupportedContextClass] = [
- (oa.Entity, DiagramType.OAB, {}),
- (oa.OperationalActivity, DiagramType.OAIB, {}),
- (sa.SystemComponent, DiagramType.SAB, {}),
- (sa.SystemFunction, DiagramType.SDFB, {}),
- (la.LogicalComponent, DiagramType.LAB, {}),
- (la.LogicalFunction, DiagramType.LDFB, {}),
- (pa.PhysicalComponent, DiagramType.PAB, {}),
- (pa.PhysicalFunction, DiagramType.PDFB, {}),
+ (oa.Entity, m.DiagramType.OAB, {}),
+ (oa.OperationalActivity, m.DiagramType.OAIB, {}),
+ (sa.SystemComponent, m.DiagramType.SAB, {}),
+ (sa.SystemFunction, m.DiagramType.SDFB, {}),
+ (la.LogicalComponent, m.DiagramType.LAB, {}),
+ (la.LogicalFunction, m.DiagramType.LDFB, {}),
+ (pa.PhysicalComponent, m.DiagramType.PAB, {}),
+ (pa.PhysicalFunction, m.DiagramType.PDFB, {}),
]
styles: dict[str, dict[str, capstyle.CSSdef]] = {}
for class_, dgcls, _ in supported_classes:
@@ -315,8 +315,8 @@ def register_realization_view() -> None:
def register_data_flow_view() -> None:
supported_classes: list[SupportedContextClass] = [
- (oa.OperationalCapability, DiagramType.OAIB, {}), # portless
- (sa.Capability, DiagramType.SDFB, {}), # default
+ (oa.OperationalCapability, m.DiagramType.OAIB, {}), # portless
+ (sa.Capability, m.DiagramType.SDFB, {}), # default
]
class_: type[m.ModelElement]
for class_, dgcls, default_render_params in supported_classes:
@@ -330,7 +330,7 @@ def register_cable_tree_view() -> None:
cs.PhysicalLink,
"cable_tree",
context.CableTreeAccessor(
- DiagramType.PAB.value,
+ m.DiagramType.PAB.value,
{},
),
)
@@ -338,23 +338,23 @@ def register_cable_tree_view() -> None:
def register_custom_diagram() -> None:
"""Add the `custom_diagram` attribute to `ModelObject`s."""
- supported_classes: list[tuple[type[m.ModelElement], DiagramType]] = [
- (oa.Entity, DiagramType.OAB),
- (oa.OperationalActivity, DiagramType.OAB),
- (oa.OperationalCapability, DiagramType.OCB),
- (oa.CommunicationMean, DiagramType.OAB),
- (sa.Mission, DiagramType.MCB),
- (sa.Capability, DiagramType.MCB),
- (sa.SystemComponent, DiagramType.SAB),
- (sa.SystemFunction, DiagramType.SAB),
- (la.LogicalComponent, DiagramType.LAB),
- (la.LogicalFunction, DiagramType.LAB),
- (pa.PhysicalComponent, DiagramType.PAB),
- (pa.PhysicalFunction, DiagramType.PAB),
- (cs.PhysicalLink, DiagramType.PAB),
- (cs.PhysicalPort, DiagramType.PAB),
- (fa.ComponentExchange, DiagramType.SAB),
- (information.Class, DiagramType.CDB),
+ supported_classes: list[tuple[type[m.ModelElement], m.DiagramType]] = [
+ (oa.Entity, m.DiagramType.OAB),
+ (oa.OperationalActivity, m.DiagramType.OAB),
+ (oa.OperationalCapability, m.DiagramType.OCB),
+ (oa.CommunicationMean, m.DiagramType.OAB),
+ (sa.Mission, m.DiagramType.MCB),
+ (sa.Capability, m.DiagramType.MCB),
+ (sa.SystemComponent, m.DiagramType.SAB),
+ (sa.SystemFunction, m.DiagramType.SAB),
+ (la.LogicalComponent, m.DiagramType.LAB),
+ (la.LogicalFunction, m.DiagramType.LAB),
+ (pa.PhysicalComponent, m.DiagramType.PAB),
+ (pa.PhysicalFunction, m.DiagramType.PAB),
+ (cs.PhysicalLink, m.DiagramType.PAB),
+ (cs.PhysicalPort, m.DiagramType.PAB),
+ (fa.ComponentExchange, m.DiagramType.SAB),
+ (information.Class, m.DiagramType.CDB),
]
for class_, dgcls in supported_classes:
m.set_accessor(
@@ -362,3 +362,18 @@ def register_custom_diagram() -> None:
"custom_diagram",
context.CustomAccessor(dgcls.value, {}),
)
+
+
+def register_diagram_layout_accessor() -> None:
+ """Add the `auto_layout` attribute to `Diagram`s."""
+ render_params = {
+ m.DiagramType.SAB: {"display_symbols_as_boxes": True},
+ m.DiagramType.LAB: {"display_symbols_as_boxes": True},
+ m.DiagramType.PAB: {"display_port_labels": True},
+ }
+
+ m.set_accessor(
+ m.Diagram,
+ "auto_layout",
+ context.DiagramLayoutAccessor(render_params),
+ )
diff --git a/src/capellambse_context_diagrams/collectors/diagram_view.py b/src/capellambse_context_diagrams/collectors/diagram_view.py
new file mode 100644
index 00000000..457e5ade
--- /dev/null
+++ b/src/capellambse_context_diagrams/collectors/diagram_view.py
@@ -0,0 +1,193 @@
+# SPDX-FileCopyrightText: Copyright DB InfraGO AG and the capellambse-context-diagrams contributors
+# SPDX-License-Identifier: Apache-2.0
+
+# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Collector for the DiagramView.
+
+Functionality for collecting all model elements from a [`Diagram`][capellambse.model.diagram.Diagram]
+and conversion of it into [`_elkjs.ELKInputData`][capellambse_context_diagrams._elkjs.ELKInputData]
+for an automated layout.
+"""
+
+from __future__ import annotations
+
+import logging
+import typing as t
+
+from capellambse import model as m
+from capellambse.metamodel import cs, fa
+
+from .. import _elkjs, context
+from . import generic, makers
+
+logger = logging.getLogger(__name__)
+PortTypes: t.TypeAlias = fa.FunctionPort | fa.ComponentPort | cs.PhysicalPort
+
+
+def is_function(node: m.ModelElement) -> bool:
+ """Check if the ``node`` is a function."""
+ return isinstance(node, fa.Function)
+
+
+def is_part(node: m.ModelElement) -> bool:
+ """Check if the ``node`` is a part."""
+ return node.xtype.endswith("Part")
+
+
+def is_exchange(node: m.ModelElement) -> bool:
+ """Check if the ``node`` is an exchange."""
+ return hasattr(node, "source") and hasattr(node, "target")
+
+
+def is_allocation(node: m.ModelElement) -> bool:
+ """Check if the ``node`` is an allocation."""
+ return node.xtype.endswith("PortAllocation")
+
+
+def is_port(node: m.ModelElement) -> bool:
+ """Check if the ``node`` is a port."""
+ return node.xtype.endswith("Port")
+
+
+class Collector:
+ """Collects all model elements from a diagram for ELK."""
+
+ def __init__(self, diagram: context.ELKDiagram):
+ self.diagram = diagram
+ self._diagram = diagram.target
+ self.data = generic.collector(self.diagram, no_symbol=True)
+ self.data.children = []
+
+ self.made_elements: dict[
+ str,
+ _elkjs.ELKInputChild | _elkjs.ELKInputEdge | _elkjs.ELKInputPort,
+ ] = {}
+ self.made_boxes: dict[str, _elkjs.ELKInputChild] = {}
+ self.made_ports: dict[str, _elkjs.ELKInputPort] = {}
+ self.exchanges: dict[str, fa.AbstractExchange] = {}
+ self.global_boxes: dict[str, _elkjs.ELKInputChild] = {}
+ self.ports: dict[str, PortTypes] = {}
+ self.boxes_to_delete: set[str] = set()
+
+ def __call__(self, params: dict[str, t.Any]) -> _elkjs.ELKInputData:
+ self._get_data(params)
+ self._adjust_box_sizes(params)
+ self._solve_hierarchy(params)
+ return self.data
+
+ def _get_data(self, params: dict[str, t.Any]):
+ del params # No use for it now
+ for node in self._diagram.nodes:
+ if is_function(node):
+ self.make_all_owner_boxes(node)
+ elif is_part(node):
+ self.make_all_owner_boxes(node.type)
+ elif is_exchange(node) and not is_allocation(node):
+ self.exchanges[node.uuid] = node # type: ignore[assignment]
+ edge = _elkjs.ELKInputEdge(
+ id=node.uuid,
+ sources=[node.source.uuid],
+ targets=[node.target.uuid],
+ labels=makers.make_label(
+ node.name, max_width=makers.MAX_LABEL_WIDTH
+ ),
+ )
+ self.ports[node.source.uuid] = node.source
+ self.ports[node.target.uuid] = node.target
+ self.data.edges.append(edge)
+ elif is_port(node):
+ self.made_ports[node.uuid] = (port := self._make_port(node))
+ self.made_boxes[node.owner.uuid].ports.append(port)
+
+ if leftover_ports := set(self.ports) - set(self.made_ports):
+ logger.debug(
+ "There are ports missing in the diagram nodes: %r",
+ leftover_ports,
+ )
+ for uuid in leftover_ports:
+ port_obj = self.ports[uuid]
+ self.made_ports[uuid] = (port := self._make_port(port_obj))
+ self.made_boxes[port_obj.owner.uuid].ports.append(port)
+
+ for uuid in self.boxes_to_delete:
+ del self.global_boxes[uuid]
+
+ self.data.children.extend(self.global_boxes.values())
+
+ def make_all_owner_boxes(self, obj: m.ModelElement):
+ """Make boxes for all owners of the given object.
+
+ Notes
+ -----
+ Also makes a box for the object itself.
+ """
+ if not (obj_box := self.made_boxes.get(obj.uuid)):
+ obj_box = self._make_box(obj, no_symbol=True, slim_width=False)
+ self.made_boxes[obj.uuid] = obj_box
+
+ current: m.ModelElement | None = obj
+ while (
+ current
+ and hasattr(current, "owner")
+ and not isinstance(current.owner, generic.PackageTypes)
+ ):
+ current = self._make_owner_box(current)
+
+ def _make_owner_box(self, obj: m.ModelElement) -> m.ModelElement | None:
+ if obj.owner.uuid in self.diagram._hide_elements:
+ return None
+
+ if not (parent_box := self.made_boxes.get(obj.owner.uuid)):
+ parent_box = self._make_box(
+ obj.owner,
+ no_symbol=True,
+ layout_options=makers.DEFAULT_LABEL_LAYOUT_OPTIONS,
+ )
+ assert (obj_box := self.made_boxes.get(obj.uuid))
+ for box in (children := parent_box.children):
+ if box.id == obj.uuid:
+ box = obj_box
+ break
+ else:
+ children.append(obj_box)
+ for label in parent_box.labels:
+ label.layoutOptions = makers.DEFAULT_LABEL_LAYOUT_OPTIONS
+
+ self.boxes_to_delete.add(obj.uuid)
+ return obj.owner
+
+ def _make_box(
+ self, obj: m.ModelElement, **kwargs: t.Any
+ ) -> _elkjs.ELKInputChild:
+ box = makers.make_box(obj, **kwargs)
+ self.global_boxes[obj.uuid] = box
+ self.made_boxes[obj.uuid] = box
+ return box
+
+ def _make_port(self, obj: m.ModelElement) -> _elkjs.ELKInputPort:
+ if self.diagram._display_port_labels:
+ label = obj.name or "UNKNOWN"
+ else:
+ label = ""
+
+ return makers.make_port(obj.uuid, label=label)
+
+ def _adjust_box_sizes(self, params: dict[str, t.Any]):
+ del params # No use for it now
+ for box in self.made_boxes.values():
+ box.height += (makers.PORT_SIZE + 2 * makers.PORT_PADDING) * (
+ len(box.ports) + 1
+ )
+
+ def _solve_hierarchy(self, params: dict[str, t.Any]):
+ del params # No use for it now
+ generic.move_edges(self.made_boxes, self.exchanges.values(), self.data)
+
+
+def collect_from_diagram(
+ diagram: context.ELKDiagram, params: dict[str, t.Any]
+) -> _elkjs.ELKInputData:
+ """Return ``ELKInputData`` from a diagram."""
+ diagram._slim_center_box = False
+ return Collector(diagram)(params)
diff --git a/src/capellambse_context_diagrams/collectors/generic.py b/src/capellambse_context_diagrams/collectors/generic.py
index d7cb7566..6e67fcd8 100644
--- a/src/capellambse_context_diagrams/collectors/generic.py
+++ b/src/capellambse_context_diagrams/collectors/generic.py
@@ -60,7 +60,7 @@
def collector(
- diagram: context.ContextDiagram,
+ diagram: context.CustomDiagram,
*,
width: int | float = makers.EOI_WIDTH,
no_symbol: bool = False,
diff --git a/src/capellambse_context_diagrams/collectors/makers.py b/src/capellambse_context_diagrams/collectors/makers.py
index 29fbdf75..f3ddba40 100644
--- a/src/capellambse_context_diagrams/collectors/makers.py
+++ b/src/capellambse_context_diagrams/collectors/makers.py
@@ -214,7 +214,7 @@ def is_symbol(obj: str | m.ModelElement | None) -> bool:
return type(obj).__name__ in BOX_TO_SYMBOL
-def make_port(uuid: str) -> _elkjs.ELKInputPort:
+def make_port(uuid: str, label: str = "") -> _elkjs.ELKInputPort:
"""Return a port.
See Also
@@ -222,9 +222,11 @@ def make_port(uuid: str) -> _elkjs.ELKInputPort:
[`ELKInputPort`][capellambse_context_diagrams._elkjs.ELKInputPort] :
Input data for an ELK port.
"""
+ labels = make_label(label) if label else []
return _elkjs.ELKInputPort(
id=uuid,
width=PORT_SIZE,
height=PORT_SIZE,
+ labels=labels,
layoutOptions={"borderOffset": -4 * PORT_PADDING},
)
diff --git a/src/capellambse_context_diagrams/context.py b/src/capellambse_context_diagrams/context.py
index 85e5a91c..b8bb6033 100644
--- a/src/capellambse_context_diagrams/context.py
+++ b/src/capellambse_context_diagrams/context.py
@@ -24,6 +24,7 @@
cable_tree,
custom,
dataflow_view,
+ diagram_view,
exchanges,
get_elkdata,
realization_view,
@@ -239,6 +240,47 @@ def __get__( # type: ignore
return self._get(obj, CableTreeViewDiagram)
+class DiagramLayoutAccessor(m.Accessor):
+ """Provides access to the context diagrams."""
+
+ def __init__(
+ self,
+ dgls_to_render_params: (
+ dict[m.DiagramType, dict[str, t.Any]] | None
+ ) = None,
+ ) -> None:
+ super().__init__()
+ self._dgls_to_render_params = dgls_to_render_params or {}
+
+ @t.overload
+ def __get__(
+ self, obj: None, objtype: type[t.Any]
+ ) -> DiagramLayoutAccessor: ...
+ @t.overload
+ def __get__(
+ self, obj: m.T, objtype: type[m.T] | None = None
+ ) -> ELKDiagram: ...
+ def __get__(
+ self, obj: m.T | None, objtype: type | None = None
+ ) -> m.Accessor | ELKDiagram:
+ """Make a ContextDiagram for the given model object."""
+ del objtype
+ if obj is None: # pragma: no cover
+ return self
+ assert isinstance(obj, m.Diagram)
+ return self._get(obj)
+
+ def _get(self, obj: m.Diagram) -> m.Accessor | ELKDiagram:
+ default_render_params = self._dgls_to_render_params.get(obj.type, {})
+ new_diagram = ELKDiagram(
+ obj.type.value,
+ obj,
+ default_render_parameters=default_render_params,
+ )
+ new_diagram.filters.add(filters.NO_UUID)
+ return new_diagram
+
+
class CustomDiagram(m.AbstractDiagram):
"""An automatically generated custom diagram.
@@ -309,13 +351,13 @@ class CustomDiagram(m.AbstractDiagram):
def __init__(
self,
class_: str,
- obj: m.ModelElement,
+ obj: m.ModelElement | m.Diagram,
*,
render_styles: dict[str, styling.Styler] | None = None,
default_render_parameters: dict[str, t.Any],
) -> None:
super().__init__(obj._model)
- self.target = obj # type: ignore[misc]
+ self.target = obj # type: ignore[misc,assignment]
self.styleclass = class_
self.render_styles = render_styles or {}
@@ -576,14 +618,14 @@ def _yield_port_allocations(
self, node: _elkjs.ELKOutputNode, interface_port: _elkjs.ELKOutputPort
) -> cabc.Iterator[_elkjs.ELKOutputEdge]:
ref = cdiagram.Vector2D(node.position.x, node.position.y)
+ interface_middle = _calculate_middle(
+ interface_port.position, interface_port.size, node.position
+ )
for position, port in _get_all_ports(node, ref=ref):
if port == interface_port:
continue
port_middle = _calculate_middle(position, port.size)
- interface_middle = _calculate_middle(
- interface_port.position, interface_port.size, node.position
- )
styleclass = self.serializer.get_styleclass(port.id)
if styleclass in {"FIP", "FOP"}:
yield _create_edge(
@@ -977,6 +1019,80 @@ def _collector(
self.collector = custom.collector
+class ELKDiagram(CustomDiagram):
+ """A former diagram layouted by ELKJS."""
+
+ _include_port_allocations: bool
+ _hide_elements: set[str]
+
+ def __init__(
+ self,
+ class_: str,
+ obj: m.Diagram,
+ *,
+ render_styles: dict[str, styling.Styler] | None = None,
+ default_render_parameters: dict[str, t.Any],
+ ) -> None:
+ default_render_parameters = {
+ "include_port_allocations": True,
+ "hide_elements": set(),
+ } | default_render_parameters
+ super().__init__(
+ class_,
+ obj,
+ render_styles=render_styles,
+ default_render_parameters=default_render_parameters,
+ )
+ self.collector = diagram_view.collect_from_diagram
+ self.target: m.Diagram = obj
+
+ # self.__port_allocations = []
+ self.__nodes: m.MixedElementList | None = None
+
+ @property
+ def uuid(self) -> str:
+ """Returns diagram UUID."""
+ return f"{self.target.uuid}_elk"
+
+ @property
+ def name(self) -> str:
+ """Returns the diagram name."""
+ return f"ELK layout of {self.target.name.replace('/', '- or -')}"
+
+ @property
+ def nodes(self) -> m.MixedElementList:
+ """Return a list of all nodes visible in this diagram."""
+ if not self.__nodes:
+ self.__nodes = super().nodes
+ assert self.__nodes is not None
+ return self.__nodes
+
+ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram:
+ data = self.elk_input_data(params)
+ assert not isinstance(data, tuple)
+ layout = try_to_layout(data)
+ # if self._include_port_allocations:
+ # self._add_port_allocations(layout)
+
+ add_context(layout)
+ return self.serializer.make_diagram(
+ layout, transparent_background=self._transparent_background
+ )
+
+ # def _add_port_allocations(self, layout: _elkjs.ELKOutputData) -> None:
+ # for allocation in self.__port_allocations:
+ # if isinstance(allocation.source, fa.ComponentPort):
+ # interface_port = allocation.source
+ # port = allocation.target
+ # else:
+ # interface_port = allocation.target
+ # port = allocation.source
+
+ # allocation.source
+
+ # allocation.target
+
+
def try_to_layout(data: _elkjs.ELKInputData) -> _elkjs.ELKOutputData:
"""Try calling elkjs, raise a JSONDecodeError if it fails."""
try:
@@ -1070,7 +1186,7 @@ def calculate_label_position(
def _get_all_ports(
node: _elkjs.ELKOutputNode, ref: cdiagram.Vector2D
) -> cabc.Iterator[tuple[cdiagram.Vector2D, _elkjs.ELKOutputPort]]:
- """Yield all ports from."""
+ """Yield all ports from a given ``node`` and its children."""
for child in node.children:
if isinstance(child, _elkjs.ELKOutputPort):
yield ref + (child.position.x, child.position.y), child
diff --git a/tests/data/ContextDiagram.afm b/tests/data/ContextDiagram.afm
index 02f4486c..ca9e0093 100644
--- a/tests/data/ContextDiagram.afm
+++ b/tests/data/ContextDiagram.afm
@@ -1,6 +1,6 @@
-
-
-
+
+
+
diff --git a/tests/data/ContextDiagram.aird b/tests/data/ContextDiagram.aird
index ed0d1a45..c717e122 100644
--- a/tests/data/ContextDiagram.aird
+++ b/tests/data/ContextDiagram.aird
@@ -1,6 +1,6 @@
-
-
+
+
ContextDiagram.afm
ContextDiagram.capella
@@ -8,14 +8,14 @@
-
+
-
+
@@ -23,7 +23,7 @@
-
+
@@ -31,7 +31,7 @@
-
+
@@ -39,7 +39,7 @@
-
+
@@ -53,15 +53,15 @@
-
+
-
+
-
+
@@ -71,30 +71,30 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -102,7 +102,7 @@
-
+
@@ -110,7 +110,7 @@
-
+
@@ -118,7 +118,7 @@
-
+
@@ -126,7 +126,7 @@
-
+
@@ -134,7 +134,7 @@
-
+
@@ -142,7 +142,7 @@
-
+
@@ -150,7 +150,7 @@
-
+
@@ -158,7 +158,7 @@
-
+
@@ -166,11 +166,11 @@
-
+
-
+
@@ -178,7 +178,7 @@
-
+
@@ -186,7 +186,7 @@
-
+
@@ -908,7 +908,7 @@
-
+
@@ -917,7 +917,7 @@
-
+
@@ -938,7 +938,7 @@
-
+
@@ -950,7 +950,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -959,7 +959,7 @@
-
+
@@ -968,7 +968,7 @@
-
+
@@ -987,7 +987,7 @@
-
+
@@ -999,7 +999,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1008,12 +1008,12 @@
-
+
-
+
@@ -1025,7 +1025,7 @@
-
+
@@ -1034,7 +1034,7 @@
-
+
@@ -1043,7 +1043,7 @@
-
+
@@ -1051,7 +1051,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1062,7 +1062,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1079,7 +1079,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1088,7 +1088,7 @@
-
+
@@ -1107,7 +1107,7 @@
-
+
@@ -1116,7 +1116,7 @@
-
+
@@ -1124,7 +1124,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1138,7 +1138,7 @@
-
+
@@ -1147,7 +1147,7 @@
-
+
@@ -1169,7 +1169,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1181,7 +1181,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1190,7 +1190,7 @@
-
+
@@ -1199,12 +1199,12 @@
-
+
-
+
@@ -1218,7 +1218,7 @@
-
+
@@ -1227,7 +1227,7 @@
-
+
@@ -1246,7 +1246,7 @@
-
+
@@ -1254,7 +1254,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1767,7 +1767,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1778,7 +1778,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1811,7 +1811,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1822,7 +1822,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1833,7 +1833,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1844,7 +1844,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1855,7 +1855,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1866,7 +1866,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -1877,7 +1877,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -2584,7 +2584,7 @@
-
+
@@ -2593,7 +2593,7 @@
-
+
@@ -2615,7 +2615,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -2624,7 +2624,7 @@
-
+
@@ -2633,7 +2633,7 @@
-
+
@@ -2641,7 +2641,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -2653,7 +2653,7 @@
-
+
@@ -2662,7 +2662,7 @@
-
+
@@ -2670,7 +2670,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -2682,7 +2682,7 @@
-
+
@@ -2690,7 +2690,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -2704,7 +2704,7 @@
-
+
@@ -2723,7 +2723,7 @@
-
+
@@ -2732,7 +2732,7 @@
-
+
@@ -2741,7 +2741,7 @@
-
+
@@ -2750,7 +2750,7 @@
-
+
@@ -2759,12 +2759,12 @@
-
+
-
+
@@ -2927,7 +2927,7 @@
-
+
@@ -2943,12 +2943,12 @@
-
+
-
+
@@ -3522,7 +3522,7 @@
-
+
@@ -3531,7 +3531,7 @@
-
+
@@ -3550,7 +3550,7 @@
-
+
@@ -3559,7 +3559,7 @@
-
+
@@ -3568,7 +3568,7 @@
-
+
@@ -3577,7 +3577,7 @@
-
+
@@ -3586,7 +3586,7 @@
-
+
@@ -3595,7 +3595,7 @@
-
+
@@ -3604,7 +3604,7 @@
-
+
@@ -3612,7 +3612,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
italic
@@ -3627,7 +3627,7 @@
-
+
@@ -3636,7 +3636,7 @@
-
+
@@ -3648,7 +3648,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -3667,7 +3667,7 @@
-
+
@@ -3676,7 +3676,7 @@
-
+
@@ -3684,7 +3684,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -3744,7 +3744,7 @@
-
+
@@ -3753,7 +3753,7 @@
-
+
@@ -3762,7 +3762,7 @@
-
+
@@ -3781,7 +3781,7 @@
-
+
@@ -3790,7 +3790,7 @@
-
+
@@ -3799,7 +3799,7 @@
-
+
@@ -3808,7 +3808,7 @@
-
+
@@ -3816,7 +3816,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -5741,7 +5741,7 @@
-
+
@@ -5750,7 +5750,7 @@
-
+
@@ -5759,7 +5759,7 @@
-
+
@@ -5768,7 +5768,7 @@
-
+
@@ -5777,7 +5777,7 @@
-
+
@@ -5786,7 +5786,7 @@
-
+
@@ -5795,7 +5795,7 @@
-
+
@@ -5804,7 +5804,7 @@
-
+
@@ -5813,7 +5813,7 @@
-
+
@@ -5822,7 +5822,7 @@
-
+
@@ -5850,7 +5850,7 @@
-
+
@@ -5859,7 +5859,7 @@
-
+
@@ -5868,7 +5868,7 @@
-
+
@@ -5877,7 +5877,7 @@
-
+
@@ -5886,7 +5886,7 @@
-
+
@@ -5895,7 +5895,7 @@
-
+
@@ -5904,7 +5904,7 @@
-
+
@@ -5912,7 +5912,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -5926,7 +5926,7 @@
-
+
@@ -5935,7 +5935,7 @@
-
+
@@ -5947,7 +5947,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -5956,7 +5956,7 @@
-
+
@@ -5965,7 +5965,7 @@
-
+
@@ -5974,7 +5974,7 @@
-
+
@@ -5983,7 +5983,7 @@
-
+
@@ -5992,7 +5992,7 @@
-
+
@@ -6013,7 +6013,7 @@
-
+
@@ -6022,7 +6022,7 @@
-
+
@@ -6031,7 +6031,7 @@
-
+
@@ -6040,7 +6040,7 @@
-
+
@@ -6049,7 +6049,7 @@
-
+
@@ -6058,7 +6058,7 @@
-
+
@@ -6067,7 +6067,7 @@
-
+
@@ -6076,7 +6076,7 @@
-
+
@@ -6085,7 +6085,7 @@
-
+
@@ -6094,7 +6094,7 @@
-
+
@@ -6103,7 +6103,7 @@
-
+
@@ -6112,7 +6112,7 @@
-
+
@@ -6121,7 +6121,7 @@
-
+
@@ -6130,7 +6130,7 @@
-
+
@@ -6139,7 +6139,7 @@
-
+
@@ -6148,7 +6148,7 @@
-
+
@@ -6157,7 +6157,7 @@
-
+
@@ -6169,7 +6169,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -6206,7 +6206,7 @@
-
+
@@ -6215,7 +6215,7 @@
-
+
@@ -6224,7 +6224,7 @@
-
+
@@ -6233,7 +6233,7 @@
-
+
@@ -6242,7 +6242,7 @@
-
+
@@ -6251,7 +6251,7 @@
-
+
@@ -6260,7 +6260,7 @@
-
+
@@ -6268,7 +6268,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -8084,7 +8084,7 @@
-
+
@@ -8093,7 +8093,7 @@
-
+
@@ -8105,7 +8105,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -8114,7 +8114,7 @@
-
+
@@ -8134,7 +8134,7 @@
-
+
@@ -8143,7 +8143,7 @@
-
+
@@ -8164,7 +8164,7 @@
-
+
@@ -8186,7 +8186,7 @@
-
+
@@ -8204,7 +8204,7 @@
-
+
@@ -8225,7 +8225,7 @@
-
+
@@ -8246,7 +8246,7 @@
-
+
@@ -9204,7 +9204,7 @@
-
+
@@ -9223,7 +9223,7 @@
-
+
@@ -9231,7 +9231,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9243,7 +9243,7 @@
-
+
@@ -9251,7 +9251,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9757,7 +9757,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9768,7 +9768,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9790,7 +9790,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9801,7 +9801,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9812,7 +9812,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9823,7 +9823,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9834,7 +9834,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9845,7 +9845,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9866,7 +9866,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9877,7 +9877,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9900,7 +9900,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9988,7 +9988,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -9999,7 +9999,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -10010,7 +10010,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -10021,7 +10021,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -10933,7 +10933,7 @@
-
+
@@ -10942,7 +10942,7 @@
-
+
@@ -10954,7 +10954,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -10963,7 +10963,7 @@
-
+
@@ -10983,7 +10983,7 @@
-
+
@@ -10992,7 +10992,7 @@
-
+
@@ -11011,7 +11011,7 @@
-
+
@@ -11020,12 +11020,12 @@
-
+
-
+
@@ -11038,7 +11038,7 @@
-
+
@@ -11047,7 +11047,7 @@
-
+
@@ -11056,7 +11056,7 @@
-
+
@@ -11064,7 +11064,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11077,7 +11077,7 @@
-
+
@@ -11086,7 +11086,7 @@
-
+
@@ -11109,7 +11109,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11128,7 +11128,7 @@
-
+
@@ -11137,7 +11137,7 @@
-
+
@@ -11146,7 +11146,7 @@
-
+
@@ -11155,12 +11155,12 @@
-
+
-
+
@@ -11197,7 +11197,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11216,7 +11216,7 @@
-
+
@@ -11225,7 +11225,7 @@
-
+
@@ -11233,7 +11233,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11250,7 +11250,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11269,7 +11269,7 @@
-
+
@@ -11277,7 +11277,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11409,7 +11409,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11428,7 +11428,7 @@
-
+
@@ -11437,12 +11437,12 @@
-
+
-
+
@@ -11833,7 +11833,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11855,7 +11855,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11864,7 +11864,7 @@
-
+
@@ -11872,7 +11872,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11884,7 +11884,7 @@
-
+
@@ -11893,7 +11893,7 @@
-
+
@@ -11901,7 +11901,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11925,7 +11925,7 @@
-
+
@@ -11933,7 +11933,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11947,7 +11947,7 @@
-
+
@@ -11969,7 +11969,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -11977,7 +11977,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12001,7 +12001,7 @@
-
+
@@ -12009,7 +12009,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12036,7 +12036,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12044,7 +12044,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12361,7 +12361,7 @@
-
+
@@ -12759,7 +12759,7 @@
-
+
@@ -12789,7 +12789,7 @@
-
+
@@ -12798,7 +12798,7 @@
-
+
@@ -12806,7 +12806,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12819,7 +12819,7 @@
-
+
@@ -12828,7 +12828,7 @@
-
+
@@ -12840,12 +12840,12 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
-
+
@@ -12859,7 +12859,7 @@
-
+
@@ -12900,7 +12900,7 @@
-
+
@@ -12908,7 +12908,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12921,7 +12921,7 @@
-
+
@@ -12930,7 +12930,7 @@
-
+
@@ -12938,7 +12938,7 @@
KEEP_LOCATION
KEEP_SIZE
KEEP_RATIO
-
+
@@ -12951,7 +12951,7 @@
-
+
@@ -12960,7 +12960,7 @@
-
+