diff --git a/src/pyplaml/diagram.py b/src/pyplaml/diagram.py index 81cf1ba83e2096884b9cb0885b73e14ecf15fe20..658cb481a985e76550d0e1345705186c86fe2759 100644 --- a/src/pyplaml/diagram.py +++ b/src/pyplaml/diagram.py @@ -6,6 +6,11 @@ from .diagram_object import DiagramObject class Diagram(VGroup): + """Container for diagram objects. + + Objects with duplicate names are merged into one, + merging can be defined in method append_to_diagram in any DiagramObject. + """ def __init__(self, layout: DiagramLayout | None = None, **kwargs): super().__init__(**kwargs) @@ -14,9 +19,13 @@ class Diagram(VGroup): self.tagged: typing.Dict[str, set[DiagramObject]] = {} self.last_object: DiagramObject | None = None + self.__layout_scale_x = 1 + self.__layout_scale_y = 1 + self.do_show_icons = True def add(self, *vmobjects: DiagramObject): + """Adds objects to diagram. Checks duplicate names and merges them.""" for o in vmobjects: exists = o.get_key() in self.objects o = o.append_to_diagram(self) @@ -27,7 +36,11 @@ class Diagram(VGroup): super().add(o) def apply_layout(self, scale_x: float = 1, scale_y: float = 1): + """Applies provided layout. Edges are automatically redrawn, to match new DiagramObjects positions.""" if self.layout is not None: + self.__layout_scale_x = scale_x + self.__layout_scale_y = scale_y + positions = self.layout.apply( {k: v for k, v in self.objects.items() if isinstance(v, pyplaml.DiagramClass | pyplaml.DiagramNote) and v.do_draw}, @@ -42,24 +55,26 @@ class Diagram(VGroup): for e in self[name].edges: e.redraw() - def objects_degree(self): + def objects_by_degree(self): + """Returns dictionary of objects where key is their key and value their degree.""" degrees = {} - for n, o in self.objects.items(): + for _, o in self.objects.items(): if hasattr(o, "edges"): - if n in degrees: - degrees[n] += len(o.edges) + if o.get_key() in degrees: + degrees[o.get_key()] += len(o.edges) else: - degrees[n] = len(o.edges) + degrees[o.get_key()] = len(o.edges) for e in o.edges: - if e.target.name in degrees: - degrees[e.target.name] += 1 + if e.target.get_key() in degrees: + degrees[e.target.get_key()] += 1 else: - degrees[e.target.name] = 1 + degrees[e.target.get_key()] = 1 return degrees def add_to_tagged(self, tag: str, obj: DiagramObject): + """Adds object to specified tag. Objects can be later hidden/removed/shown by their tags.""" if tag in self.tagged: self.tagged[tag].add(obj) else: @@ -67,57 +82,83 @@ class Diagram(VGroup): self.tagged[tag].add(obj) def remove_by_tag(self, tag: str): + """Removes all objects with specified tag from diagram. Removed objects are invisible and don't matter in diagram layout.""" if tag in self.tagged: for o in self.tagged[tag]: - o.do_draw = False - o.set_opacity(0) + self.remove_object(o.get_key()) def restore_by_tag(self, tag: str): + """Restores all previously removed objects by tag.""" if tag in self.tagged: for o in self.tagged[tag]: - o.do_draw = True - o.set_opacity(1) + self.restore_object(o.get_key()) def hide_by_tag(self, tag: str): + """Hides objects by tag. Hidden objects are invisible, but still matter in diagram layout.""" if tag in self.tagged: for o in self.tagged[tag]: - o.is_hidden = True - o.set_opacity(0) + self.hide_object(o.get_key()) def show_by_tag(self, tag: str): + """Shows previously hidden objects by tag.""" if tag in self.tagged: for o in self.tagged[tag]: - o.is_hidden = False - o.set_opacity(1) + self.show_object(o.get_key()) def show_icons(self, show: bool): + """Sets if icons of DiagramClass objects should be drawn.""" self.do_show_icons = show for n, o in self.objects.items(): if isinstance(o, pyplaml.DiagramClass): o.set_show_icon(show) def remove_unlinked(self): - for n, deg in self.objects_degree().items(): - if deg == 0: - self[n].do_draw = False - self[n].set_opacity(0) + """Removes all objects with no inbound/outbound DiagramEdges. Removed objects are invisible and don't matter in diagram layout.""" + objects = self.objects_by_degree() + if objects: + for n, deg in objects.items(): + if deg == 0: + self.remove_object(n) def restore_unlinked(self): - for n, deg in self.objects_degree().items(): - if deg == 0: - self[n].do_draw = True - self[n].set_opacity(1) + """Restores all objects with no inbound/outbound DiagramEdges.""" + objects = self.objects_by_degree() + if objects: + for n, deg in objects.items(): + if deg == 0: + self.restore_object(n) def hide_unlinked(self): - print(self.objects_degree()) - for n, deg in self.objects_degree().items(): + """Hides all objects with no inbound/outbound DiagramEdges. Hidden objects are invisible, but still matter in diagram layout.""" + for n, deg in self.objects_by_degree().items(): if deg == 0: - self[n].set_opacity(0) + self.hide_object(n) def show_unlinked(self): - for n, deg in self.objects_degree().items(): + """Shows all objects with no inbound/outbound DiagramEdges.""" + for n, deg in self.objects_by_degree().items(): if deg == 0: - self[n].set_opacity(1) + self.show_object(n) + + def hide_object(self, key: str): + """Hides an object, object still matters in diagram layout.""" + self[key].set_opacity(0) + + def show_object(self, key: str): + """Shows previously hidden object""" + self[key].set_opacity(1) + + def remove_object(self, key: str): + """Removes an object, object no longer matters in diagram layout.""" + self[key].do_draw = False + self[key].set_opacity(0) + self.remove(self[key]) + + def restore_object(self, key: str): + """Restores previously removed object""" + self[key].do_draw = True + self[key].set_opacity(1) + self.add(self.objects.pop(key)) def __setitem__(self, key: str, val: DiagramObject): if not isinstance(val, DiagramObject): diff --git a/src/pyplaml/diagram_edge.py b/src/pyplaml/diagram_edge.py index afec5c758705976a646974e5a843671dd31df871..825491670c6277335980eb735a8670d525dc5e55 100644 --- a/src/pyplaml/diagram_edge.py +++ b/src/pyplaml/diagram_edge.py @@ -60,7 +60,7 @@ class DiagramEdge(DiagramObject): def redraw(self): super().redraw() - if self.source.is_hidden or self.target.is_hidden: + if self.source.background_stroke_opacity or self.target.background_stroke_opacity: return always_redraw(self.__line_updater) diff --git a/src/pyplaml/diagram_object.py b/src/pyplaml/diagram_object.py index 84ed981d85c126865b04ee16c7b6bb2dd77a892e..0e1038a6fffcbbf81a324ba1f993261856863790 100644 --- a/src/pyplaml/diagram_object.py +++ b/src/pyplaml/diagram_object.py @@ -16,7 +16,6 @@ class DiagramObject(VGroup): super().__init__(**kwargs) self.name = name self.alias = None - self.is_hidden = False self.do_draw = True self.notes: Dict[pyplaml.Direction, List[pyplaml.DiagramNote]] = {} @@ -73,9 +72,8 @@ class DiagramObject(VGroup): return self.name def __repr__(self): - return "({}) \"{}\", hidden: {}, draw: {}".format( + return "({}) \"{}\", draw: {}".format( self.__class__.__name__, self.get_key() or "NAME NOT SET", - "yes" if self.is_hidden else "no", "yes" if self.do_draw else "no", ) diff --git a/src/pyplaml/puml_parser.py b/src/pyplaml/puml_parser.py index 2effa7f1bb8fa167bba573278841bf67e4a016a6..0b052dab7783ea131a3e0b8b38acec41e91eb4d6 100644 --- a/src/pyplaml/puml_parser.py +++ b/src/pyplaml/puml_parser.py @@ -289,11 +289,8 @@ class PUMLParser(object): if p[2][0] == "@": if p[2] == "@unlinked": self.diagram.remove_unlinked() - return - - self.diagram[p[2]].do_draw = False - self.diagram[p[2]].set_opacity(0) + self.diagram.remove_object(self.diagram[p[2]]) def p_remove_by_tag(self, p): """ @@ -308,11 +305,8 @@ class PUMLParser(object): if p[2][0] == "@": if p[2] == "@unlinked": self.diagram.restore_unlinked() - return - - self.diagram[p[2]].do_draw = True - self.diagram[p[2]].set_opacity(1) + self.diagram.restore_object(self.diagram[p[2]].get_key()) def p_restore_by_tag(self, p): """ @@ -332,8 +326,7 @@ class PUMLParser(object): elif name == "circle": self.diagram.show_icons(False) else: - self.diagram[name].is_hidden = True - self.diagram[name].set_opacity(0) + self.diagram.hide_object(self.diagram[name]) def p_hide_by_tag(self, p): """ @@ -353,8 +346,7 @@ class PUMLParser(object): elif name == "circle": self.diagram.show_icons(True) else: - self.diagram[p[2]].is_hidden = False - self.diagram[p[2]].set_opacity(1) + self.diagram.show_object(self.diagram[p[2]]) def p_show_by_tag(self, p): """