diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index 3d496816..130af425 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -47,7 +47,7 @@ def init() -> None: """Initialize the extension.""" register_classes() register_interface_context() - register_class_tree() + register_tree_view() # register_functional_context() XXX: Future @@ -151,10 +151,10 @@ def register_functional_context() -> None: ) -def register_class_tree() -> None: - """Add the `tree_diagram` attribute to ``Class``es.""" +def register_tree_view() -> None: + """Add the `tree_view` attribute to ``Class``es.""" common.set_accessor( information.Class, - "tree_diagram", + "tree_view", context.ClassTreeAccessor(DiagramType.CDB.value), ) diff --git a/capellambse_context_diagrams/collectors/class_tree.py b/capellambse_context_diagrams/collectors/tree_view.py similarity index 81% rename from capellambse_context_diagrams/collectors/class_tree.py rename to capellambse_context_diagrams/collectors/tree_view.py index e19c1010..29470f05 100644 --- a/capellambse_context_diagrams/collectors/class_tree.py +++ b/capellambse_context_diagrams/collectors/tree_view.py @@ -41,28 +41,30 @@ def __init__( self.all_associations = all_associations def process_class(self, cls, params): - self._process_box(cls.target, cls.partition, params) self._process_box(cls.source, cls.partition, params) - edges = [ - assoc - for assoc in self.all_associations - if cls.prop in assoc.navigable_members - ] - assert len(edges) == 1 - if (edge_id := edges[0].uuid) not in self.made_edges: - self.made_edges.add(edge_id) - text = cls.prop.name - start, end = cls.multiplicity - if start != "1" or end != "1": - text = f"[{start}..{end}] {text}" - self.data["edges"].append( - { - "id": edge_id, - "sources": [cls.source.uuid], - "targets": [cls.target.uuid], - "labels": [makers.make_label(text)], - } - ) + + if not cls.primitive: + self._process_box(cls.target, cls.partition, params) + edges = [ + assoc + for assoc in self.all_associations + if cls.prop in assoc.navigable_members + ] + assert len(edges) == 1 + if (edge_id := edges[0].uuid) not in self.made_edges: + self.made_edges.add(edge_id) + text = cls.prop.name + start, end = cls.multiplicity + if start != "1" or end != "1": + text = f"[{start}..{end}] {text}" + self.data["edges"].append( + { + "id": edge_id, + "sources": [cls.source.uuid], + "targets": [cls.target.uuid], + "labels": [makers.make_label(text)], + } + ) if cls.generalizes: self._process_box(cls.generalizes, cls.partition, params) @@ -153,6 +155,7 @@ class ClassInfo: partition: int multiplicity: tuple[str, str] generalizes: information.Class | None = None + primitive: bool = False def get_all_classes( @@ -170,6 +173,9 @@ def get_all_classes( ) continue + if prop.type.is_primitive: + continue + edge_id = f"{root.uuid} {prop.uuid} {prop.type.uuid}" if edge_id not in classes: classes[edge_id] = _make_class_info(root, prop, partition) @@ -185,6 +191,9 @@ def get_all_classes( ) continue + if prop.type.is_primitive: + continue + edge_id = f"{root.uuid} {prop.uuid} {prop.type.uuid}" if edge_id not in classes: classes[edge_id] = _make_class_info( @@ -212,6 +221,7 @@ def _make_class_info( partition=partition, multiplicity=(start, end), generalizes=generalizes, + primitive=source.is_primitive, ) @@ -222,7 +232,11 @@ def _get_all_non_edge_properties( properties: list[_elkjs.ELKInputLabel] = [] legends: list[_elkjs.ELKInputChild] = [] for prop in obj.properties: - if isinstance(prop.type, (information.Class, type(None))): + if prop.type is None: + continue + + is_class = isinstance(prop.type, information.Class) + if is_class and not prop.type.is_primitive: continue text = f"{prop.name}: {prop.type.name}" # type: ignore[unreachable] @@ -233,8 +247,10 @@ def _get_all_non_edge_properties( continue data_types.add(prop.type.uuid) - if not isinstance(prop.type, information.datatype.Enumeration): + is_enum = isinstance(prop.type, information.datatype.Enumeration) + if not is_enum and not (is_class and prop.type.is_primitive): continue + legend = makers.make_box(prop.type, label_getter=_get_legend_labels) legend["layoutOptions"] = {} legend["layoutOptions"]["nodeSize.constraints"] = "NODE_LABELS" @@ -243,15 +259,19 @@ def _get_all_non_edge_properties( def _get_legend_labels( - obj: information.datatype.Enumeration, + obj: information.datatype.Enumeration | information.Class, ) -> cabc.Iterator[makers._LabelBuilder]: yield {"text": obj.name, "icon": (0, 0), "layout_options": {}} - if not isinstance(obj, information.datatype.Enumeration): + if isinstance(obj, information.datatype.Enumeration): + labels = [literal.name for literal in obj.literals] + elif isinstance(obj, information.Class): + labels = [prop.name for prop in obj.owned_properties] + else: return layout_options = DATA_TYPE_LABEL_LAYOUT_OPTIONS - for lit in obj.literals: + for label in labels: yield { - "text": lit.name, + "text": label, "icon": (0, 0), "layout_options": layout_options, } diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index e12ffbea..7355151a 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -16,7 +16,7 @@ from capellambse.model import common, diagram, modeltypes from . import _elkjs, filters, serializers, styling -from .collectors import class_tree, exchanges, get_elkdata +from .collectors import exchanges, get_elkdata, tree_view logger = logging.getLogger(__name__) @@ -131,7 +131,7 @@ def __get__( # type: ignore class ClassTreeAccessor(ContextAccessor): - """Provides access to the class tree diagrams.""" + """Provides access to the tree view diagrams.""" # pylint: disable=super-init-not-called def __init__(self, diagclass: str) -> None: @@ -340,12 +340,12 @@ def __init__(self, class_: str, obj: common.GenericElement, **kw) -> None: @property def uuid(self) -> str: # type: ignore """Returns the UUID of the diagram.""" - return f"{self.target.uuid}_class-tree" + return f"{self.target.uuid}_tree_view" @property def name(self) -> str: # type: ignore """Returns the name of the diagram.""" - return f"Class Tree of {self.target.name}" + return f"Tree view of {self.target.name}" def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: params.setdefault("algorithm", params.get("algorithm", "layered")) @@ -362,7 +362,7 @@ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: "layered.edgeLabels.sideSelection", params.pop("edgeLabelsSide", "SMART_DOWN"), ) - data, legend = class_tree.collector(self, params) + data, legend = tree_view.collector(self, params) params["elkdata"] = data class_diagram = super()._create_diagram(params) width, height = class_diagram.viewport.size diff --git a/capellambse_context_diagrams/serializers.py b/capellambse_context_diagrams/serializers.py index 538cd944..d9f14e34 100644 --- a/capellambse_context_diagrams/serializers.py +++ b/capellambse_context_diagrams/serializers.py @@ -67,7 +67,8 @@ def make_diagram(self, data: _elkjs.ELKOutputData) -> diagram.Diagram: from the input data. """ self.diagram = diagram.Diagram( - self._diagram.name, styleclass=self._diagram.styleclass + self._diagram.name.replace("/", "\\"), + styleclass=self._diagram.styleclass, ) for child in data["children"]: self.deserialize_child(child, diagram.Vector2D(), None) diff --git a/docs/gen_images.py b/docs/gen_images.py index f9004f57..6a54b009 100644 --- a/docs/gen_images.py +++ b/docs/gen_images.py @@ -94,7 +94,7 @@ def generate_hierarchy_image() -> None: def generate_class_tree_images() -> None: obj = model.by_uuid(class_tree_uuid) - diag = obj.tree_diagram + diag = obj.tree_view with mkdocs_gen_files.open(f"{str(dest / diag.name)}.svg", "w") as fd: print(diag.render("svg"), file=fd) with mkdocs_gen_files.open( diff --git a/docs/class-tree.md b/docs/tree_view.md similarity index 77% rename from docs/class-tree.md rename to docs/tree_view.md index 40709290..f3147ed7 100644 --- a/docs/class-tree.md +++ b/docs/tree_view.md @@ -3,25 +3,25 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Class Tree Diagram +# Tree View Diagram With release [`v0.5.35`](https://github.com/DSD-DBS/py-capellambse/releases/tag/v0.5.35) of [py-capellambse](https://github.com/DSD-DBS/py-capellambse) you can access the -`.tree_diagram` on [`Class`][capellambse.model.crosslayer.information.Class] -objects. A class tree diagram shows a tree made from all properties of the +`.tree_view` on [`Class`][capellambse.model.crosslayer.information.Class] +objects. A tree view diagram shows a tree made from all properties of the parent class. -??? example "Class Tree of Root" +??? example "Tree view of Root" ``` py import capellambse model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") - diag = model.by_uuid("b7c7f442-377f-492c-90bf-331e66988bda").tree_diagram + diag = model.by_uuid("b7c7f442-377f-492c-90bf-331e66988bda").tree_view diag.render("svgdiagram").save_drawing(pretty=True) ```
- -
[CDB] Class Tree Diagram of Root
+ +
[CDB] Tree View Diagram of Root
Additional rendering parameters enable the control over the layout computed by @@ -55,13 +55,13 @@ classes is its own partition. Here is an example that shows how convenient these parameters can be passed before rendering: -??? example "Class Tree of Root" +??? example "Tree View of Root" ``` py import capellambse model = capellambse.MelodyModel("tests/data/ContextDiagram.aird") - diag = model.by_uuid("b7c7f442-377f-492c-90bf-331e66988bda").tree_diagram + diag = model.by_uuid("b7c7f442-377f-492c-90bf-331e66988bda").tree_view diag.render( "svgdiagram", edgeRouting="ORTHOGONAL", @@ -71,8 +71,8 @@ before rendering: ).save_drawing(pretty=True) ```
- -
[CDB] Class Tree Diagram of Root
+ +
[CDB] Tree View Diagram of Root
They are optional and don't need to be set all together. @@ -80,4 +80,4 @@ They are optional and don't need to be set all together. ## Check out the code To understand the collection have a look into the -[`class_tree`][capellambse_context_diagrams.collectors.class_tree] module. +[`class_tree`][capellambse_context_diagrams.collectors.tree_view] module. diff --git a/mkdocs.yml b/mkdocs.yml index de5e508a..a0463c35 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,8 +89,8 @@ nav: - Extras: - Filters: extras/filters.md - Styling: extras/styling.md - - Class Tree: - - Overview: class-tree.md + - Tree View: + - Overview: tree_view.md - Code Reference: reference/ extra_css: diff --git a/tests/test_class_tree_diagrams.py b/tests/test_tree_views.py similarity index 86% rename from tests/test_class_tree_diagrams.py rename to tests/test_tree_views.py index 66e20866..04526076 100644 --- a/tests/test_class_tree_diagrams.py +++ b/tests/test_tree_views.py @@ -8,12 +8,12 @@ @pytest.mark.parametrize("fmt", ["svgdiagram", "svg", None]) -def test_tree_diagram_gets_rendered_successfully( +def test_tree_view_gets_rendered_successfully( model: capellambse.MelodyModel, fmt: str ) -> None: obj = model.by_uuid(CLASS_UUID) - diag = obj.tree_diagram + diag = obj.tree_view assert diag.render(fmt) @@ -24,7 +24,7 @@ def test_tree_diagram_gets_rendered_successfully( @pytest.mark.parametrize( "edgeLabelsSide", ["ALWAYS_DOWN", "DIRECTION_DOWN", "SMART_DOWN"] ) -def test_tree_diagram_renders_with_additional_params( +def test_tree_view_renders_with_additional_params( model: capellambse.MelodyModel, edgeRouting: str, direction: str, @@ -33,7 +33,7 @@ def test_tree_diagram_renders_with_additional_params( ) -> None: obj = model.by_uuid(CLASS_UUID) - diag = obj.tree_diagram + diag = obj.tree_view assert diag.render( "svgdiagram",