matplotly 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
matplotly/_commands.py ADDED
@@ -0,0 +1,109 @@
1
+ """Undo/redo command system for figure modifications."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Callable
6
+
7
+
8
+ @dataclass
9
+ class Command:
10
+ """A single property change on a matplotlib artist."""
11
+
12
+ artist: Any
13
+ property_name: str
14
+ old_value: Any
15
+ new_value: Any
16
+ # For complex operations that can't use simple set_*
17
+ apply_fn: Callable[[], None] | None = None
18
+ revert_fn: Callable[[], None] | None = None
19
+ # Human-readable description for code gen
20
+ description: str = ""
21
+
22
+ def execute(self) -> None:
23
+ if self.apply_fn is not None:
24
+ self.apply_fn()
25
+ else:
26
+ setter = getattr(self.artist, f"set_{self.property_name}", None)
27
+ if setter is not None:
28
+ setter(self.new_value)
29
+
30
+ def undo(self) -> None:
31
+ if self.revert_fn is not None:
32
+ self.revert_fn()
33
+ else:
34
+ setter = getattr(self.artist, f"set_{self.property_name}", None)
35
+ if setter is not None:
36
+ setter(self.old_value)
37
+
38
+
39
+ @dataclass
40
+ class BatchCommand:
41
+ """A group of commands applied/reverted together as one undo step."""
42
+
43
+ commands: list[Command] = field(default_factory=list)
44
+ description: str = ""
45
+
46
+ def execute(self) -> None:
47
+ for cmd in self.commands:
48
+ cmd.execute()
49
+
50
+ def undo(self) -> None:
51
+ for cmd in reversed(self.commands):
52
+ cmd.undo()
53
+
54
+
55
+ class CommandStack:
56
+ """Manages undo/redo stacks with a maximum depth."""
57
+
58
+ def __init__(self, max_depth: int = 100,
59
+ on_change: Callable[[], None] | None = None):
60
+ self._undo_stack: list[Command | BatchCommand] = []
61
+ self._redo_stack: list[Command | BatchCommand] = []
62
+ self._max_depth = max_depth
63
+ self._on_change = on_change
64
+
65
+ @property
66
+ def can_undo(self) -> bool:
67
+ return len(self._undo_stack) > 0
68
+
69
+ @property
70
+ def can_redo(self) -> bool:
71
+ return len(self._redo_stack) > 0
72
+
73
+ @property
74
+ def history(self) -> list[Command | BatchCommand]:
75
+ return list(self._undo_stack)
76
+
77
+ def execute(self, cmd: Command | BatchCommand) -> None:
78
+ """Execute a command and push it onto the undo stack."""
79
+ cmd.execute()
80
+ self._undo_stack.append(cmd)
81
+ if len(self._undo_stack) > self._max_depth:
82
+ self._undo_stack.pop(0)
83
+ self._redo_stack.clear()
84
+ if self._on_change:
85
+ self._on_change()
86
+
87
+ def undo(self) -> None:
88
+ if not self._undo_stack:
89
+ return
90
+ cmd = self._undo_stack.pop()
91
+ cmd.undo()
92
+ self._redo_stack.append(cmd)
93
+ if self._on_change:
94
+ self._on_change()
95
+
96
+ def redo(self) -> None:
97
+ if not self._redo_stack:
98
+ return
99
+ cmd = self._redo_stack.pop()
100
+ cmd.execute()
101
+ self._undo_stack.append(cmd)
102
+ if self._on_change:
103
+ self._on_change()
104
+
105
+ def clear(self) -> None:
106
+ self._undo_stack.clear()
107
+ self._redo_stack.clear()
108
+ if self._on_change:
109
+ self._on_change()