Skip to content

Commit

Permalink
merge: Merge PR #16 from DSD-DBS/use-native-viewport-calculation
Browse files Browse the repository at this point in the history
All space-checker methods were removed by using `aird.Diagram`'s own
viewport calculation functionality and a hidden implicit box sizing
feature of `ELK`. Children or labels with sizes
(width and height information) make any size information on the parent
useless. This behaviour is used to get enough space for big labels in
boxes from ELK natively such that adjustments aren't needed anymore.
This fixes overlaps of diagram elements.
  • Loading branch information
ewuerger authored Oct 5, 2022
2 parents 8d0f135 + b1f0473 commit 8887a82
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 117 deletions.
3 changes: 1 addition & 2 deletions capellambse_context_diagrams/collectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
def get_elkdata(
diagram: context.ContextDiagram, params: dict[str, t.Any] | None = None
) -> _elkjs.ELKInputData:
"""
High level collector function to collect needed data for ELK
"""High level collector function to collect needed data for ELK.
Parameters
----------
Expand Down
5 changes: 2 additions & 3 deletions capellambse_context_diagrams/collectors/exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def get_elkdata_for_exchanges(
collector_type: type[ExchangeCollector],
) -> _elkjs.ELKInputData:
"""Return exchange data for ELK."""
data = generic.collector(diagram)
data = makers.make_diagram(diagram)
collector = collector_type(diagram, data)
data["edges"] = collector.collect()
for comp in data["children"]:
Expand Down Expand Up @@ -161,7 +161,6 @@ def __init__(
data: _elkjs.ELKInputData,
) -> None:
super().__init__(diagram, data)
self.data["children"] = []
self.get_left_and_right()

def get_left_and_right(self) -> None:
Expand All @@ -177,7 +176,7 @@ def make_boxes(
comp: common.GenericElement, functions: list[common.GenericElement]
) -> None:
if comp.uuid not in made_children:
box = makers.make_box(comp)
box = makers.make_box(comp, no_symbol=True)
box["children"] = [
makers.make_box(c)
for c in functions
Expand Down
15 changes: 6 additions & 9 deletions capellambse_context_diagrams/collectors/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,12 @@ def collector(
width: int | float = makers.EOI_WIDTH,
no_symbol: bool = False,
) -> _elkjs.ELKInputData:
"""Returns `ELKInputData` with only centerbox in children and config."""
return {
"id": diagram.uuid,
"layoutOptions": _elkjs.get_global_layered_layout_options(),
"children": [
makers.make_box(diagram.target, width=width, no_symbol=no_symbol)
],
"edges": [],
}
"""Returns ``ELKInputData`` with only centerbox in children and config."""
data = makers.make_diagram(diagram)
data["children"] = [
makers.make_box(diagram.target, width=width, no_symbol=no_symbol)
]
return data


def collect_exchange_endpoints(
Expand Down
15 changes: 13 additions & 2 deletions capellambse_context_diagrams/collectors/makers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
from capellambse.model import common, layers
from capellambse.svg.decorations import icon_padding, icon_size

from .. import _elkjs
from .. import _elkjs, context

PORT_SIZE = 10
"""Default size of ports in pixels."""
PORT_PADDING = 2
"""Default padding of ports in pixels."""
LABEL_HPAD = 15
"""Horizontal padding left and right of the label."""
LABEL_VPAD = 5
LABEL_VPAD = 1
"""Vertical padding above and below the label."""
NEIGHBOR_VMARGIN = 20
"""Vertical space between two neighboring boxes."""
Expand Down Expand Up @@ -45,6 +45,16 @@
"""


def make_diagram(diagram: context.ContextDiagram) -> _elkjs.ELKInputData:
"""Return basic skeleton for ``ContextDiagram``s."""
return {
"id": diagram.uuid,
"layoutOptions": _elkjs.get_global_layered_layout_options(),
"children": [],
"edges": [],
}


def make_box(
obj: common.GenericElement,
*,
Expand Down Expand Up @@ -90,6 +100,7 @@ def make_label(obj: common.GenericElement) -> _elkjs.ELKInputLabel:
"text": obj.name,
"width": label_width + 2 * LABEL_HPAD,
"height": label_height + 2 * LABEL_VPAD,
"layoutOptions": {"nodeLabels.placement": "INSIDE, V_TOP, H_CENTER"},
}


Expand Down
100 changes: 2 additions & 98 deletions capellambse_context_diagrams/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@

import logging

from capellambse import aird, helpers
from capellambse import aird
from capellambse.model import common
from capellambse.svg.decorations import icon_padding, icon_size

from . import _elkjs, collectors, context

Expand Down Expand Up @@ -71,17 +70,7 @@ def make_diagram(self, data: _elkjs.ELKOutputData) -> aird.Diagram:
for child in data["children"]:
self.deserialize_child(child, aird.Vector2D(), None)

space_checkers = {
"box": self.check_boxlabel_space,
"edge": self.check_edgelabel_space,
"symbol": lambda _: None, # No need since label is outside
}
for element in self._cache.values():
if not element.port:
space_checkers[element.JSON_TYPE](element)
if element.JSON_TYPE != "edge":
self.check_viewBox_space(element)

self.aird_diagram.calculate_viewport()
return self.aird_diagram

def deserialize_child(
Expand Down Expand Up @@ -232,91 +221,6 @@ def get_styleoverrides(
styleoverrides = style_condition(obj)
return styleoverrides

def check_boxlabel_space(self, box: aird.Box) -> None:
"""Check if size of parent boxes is enough for their label."""
if not box.children or any(child.port for child in box.children):
return

child_dist = min(
[
abs(child.pos.y - box.pos.y)
for child in box.children
if isinstance(child, aird.Box)
]
)

label = box.label
if isinstance(label, aird.Box):
label = label.label
elif label is None:
return

assert isinstance(label, str)
lines = helpers.word_wrap(
label, box.size.x - (2 * icon_padding + icon_size)
)
_, labelheight = helpers.extent_func(label)
labelspace = aird.Vector2D(
0,
child_dist
- (2 * collectors.makers.LABEL_VPAD + len(lines) * labelheight),
)
if labelspace.y >= 0:
return

box.pos += labelspace
box.size += abs(labelspace)
assert self.aird_diagram.viewport is not None
viewport_dist = aird.Vector2D(
0, box.pos.y + labelspace.y - self.aird_diagram.viewport.pos.y
)
if viewport_dist.y <= 0:
self.aird_diagram.viewport.pos += viewport_dist
self.aird_diagram.viewport.size += abs(viewport_dist)

def check_edgelabel_space(self, edge: aird.Edge) -> None:
"""Check if any label is outside of the viewport."""
if not edge.labels:
return

assert self.aird_diagram.viewport is not None
lower_bound = (
self.aird_diagram.viewport.pos + self.aird_diagram.viewport.size
)
for label in edge.labels:
self._fix_lower_space(lower_bound, label)

def check_viewBox_space(self, box: aird.Box) -> None:
"""Check if given box and its label fits inside the diagram."""
assert self.aird_diagram.viewport is not None
upper_bound = self.aird_diagram.viewport.pos
lower_bound = upper_bound + self.aird_diagram.viewport.size
self._fix_lower_space(lower_bound, box)
self._fix_upper_space(upper_bound, box)
if isinstance(box.label, aird.Box):
self._fix_lower_space(lower_bound, box.label)
self._fix_upper_space(upper_bound, box.label)

def _fix_lower_space(
self, lower_bound: aird.Vector2D, box: aird.Box
) -> None:
assert self.aird_diagram.viewport is not None
space = lower_bound - (box.pos + box.size)
self.aird_diagram.viewport.size += aird.Vector2D(
abs(space.x) if space.x <= 0 else 0,
abs(space.y) if space.y <= 0 else 0,
)

def _fix_upper_space(
self, upper_bound: aird.Vector2D, box: aird.Box
) -> None:
assert self.aird_diagram.viewport is not None
space = box.pos - upper_bound
self.aird_diagram.viewport.pos -= aird.Vector2D(
abs(space.x) if space.x <= 0 else 0,
abs(space.y) if space.y <= 0 else 0,
)


def get_styleclass(obj: common.GenericElement) -> str:
"""Return the styleclass for a given `obj`."""
Expand Down
6 changes: 3 additions & 3 deletions tests/test_context_diagrams.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def test_context_diagrams(
),
pytest.param(
[
("6241d0c5-65d2-4c0b-b79c-a2a8ed7273f6", 20),
("344a405e-c7e5-4367-8a9a-41d3d9a27f81", 20),
("230c4621-7e0a-4d0a-9db2-d4ba5e97b3df", 45),
("6241d0c5-65d2-4c0b-b79c-a2a8ed7273f6", 17),
("344a405e-c7e5-4367-8a9a-41d3d9a27f81", 17),
("230c4621-7e0a-4d0a-9db2-d4ba5e97b3df", 38),
],
id="SystemComponent Root",
),
Expand Down

0 comments on commit 8887a82

Please sign in to comment.