Skip to content

Commit

Permalink
merge: Merge pull request #56 from DSD-DBS/fix-class-tree-diagram
Browse files Browse the repository at this point in the history
Fix class tree diagram
  • Loading branch information
ewuerger authored Nov 3, 2023
2 parents 544da00 + bce2c01 commit 8e3ce6d
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 56 deletions.
8 changes: 4 additions & 4 deletions capellambse_context_diagrams/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -153,6 +155,7 @@ class ClassInfo:
partition: int
multiplicity: tuple[str, str]
generalizes: information.Class | None = None
primitive: bool = False


def get_all_classes(
Expand All @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -212,6 +221,7 @@ def _make_class_info(
partition=partition,
multiplicity=(start, end),
generalizes=generalizes,
primitive=source.is_primitive,
)


Expand All @@ -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]
Expand All @@ -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"
Expand All @@ -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,
}
10 changes: 5 additions & 5 deletions capellambse_context_diagrams/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"))
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion capellambse_context_diagrams/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docs/gen_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
24 changes: 12 additions & 12 deletions docs/class-tree.md → docs/tree_view.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```
<figure markdown>
<img src="../assets/images/Class Tree of Root.svg">
<figcaption>[CDB] Class Tree Diagram of Root</figcaption>
<img src="../assets/images/Tree view of Root.svg">
<figcaption>[CDB] Tree View Diagram of Root</figcaption>
</figure>

Additional rendering parameters enable the control over the layout computed by
Expand Down Expand Up @@ -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",
Expand All @@ -71,13 +71,13 @@ before rendering:
).save_drawing(pretty=True)
```
<figure markdown>
<img src="../assets/images/Class Tree of Root-params.svg">
<figcaption>[CDB] Class Tree Diagram of Root</figcaption>
<img src="../assets/images/Tree view of Root-params.svg">
<figcaption>[CDB] Tree View Diagram of Root</figcaption>
</figure>

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.
4 changes: 2 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
Expand All @@ -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",
Expand Down

0 comments on commit 8e3ce6d

Please sign in to comment.