Conversation
There was a problem hiding this comment.
Good patch! The dependency graph feature looks solid. I have a few suggestions for improvements.
Also, could you replace the demo movie in top-level README.md with the following URL?
https://github.com/user-attachments/assets/576b9ecd-81cd-4b26-948c-65cea6ce49af
| self.root = self._build_graph(root_entity, depth=0, visited=set()) | ||
|
|
||
| def _build_graph( | ||
| self, entity: RosEntity, depth: int, visited: set[str] |
There was a problem hiding this comment.
The visited parameter is unused here. Since max_depth already prevents infinite recursion, we can remove this parameter to clean up the interface.
| def update_graph(self) -> None: | ||
| if self._tree: | ||
| self._tree.remove() | ||
|
|
||
| if self._entity is None: | ||
| return | ||
|
|
||
| self._tree = Tree(TreeLabel.label(self._entity), data=self._entity) | ||
| self.mount(self._tree) | ||
|
|
||
| graph = RosDependencyGraph(self._entity, self._ros, max_depth=EXPANSION_DEPTH) | ||
| self._populate_tree(self._tree.root, graph.root) | ||
| self._tree.root.expand() |
There was a problem hiding this comment.
The node expansion logic is duplicated between on_tree_node_selected() and update_graph(). Both methods follow the same pattern:
- Create
RosDependencyGraph - Call
_populate_tree() - Expand the node
How about creating a helper method like _rebuild_node() to reduce duplication?
| def update_graph(self) -> None: | |
| if self._tree: | |
| self._tree.remove() | |
| if self._entity is None: | |
| return | |
| self._tree = Tree(TreeLabel.label(self._entity), data=self._entity) | |
| self.mount(self._tree) | |
| graph = RosDependencyGraph(self._entity, self._ros, max_depth=EXPANSION_DEPTH) | |
| self._populate_tree(self._tree.root, graph.root) | |
| self._tree.root.expand() | |
| def update_graph(self) -> None: | |
| if self._tree: | |
| self._tree.remove() | |
| if self._entity is None: | |
| return | |
| self._tree = Tree(TreeLabel.label(self._entity), data=self._entity) | |
| self.mount(self._tree) | |
| self._rebuild_node(self._tree.root, self._entity) |
There was a problem hiding this comment.
I have consolidate the node expansion logic in _populate_tree() 340d4ab
| def on_tree_node_selected(self, event: Tree.NodeSelected) -> None: | ||
| node = event.node | ||
| entity = node.data # type: RosEntity | None | ||
|
|
||
| if not isinstance(entity, RosEntity): | ||
| return | ||
|
|
||
| if entity.type != RosEntityType.Node: | ||
| return | ||
|
|
||
| if node.children and all(child.is_expanded for child in node.children): | ||
| return | ||
|
|
||
| if self._subtree_depth(node) > EXPANSION_DEPTH: | ||
| return | ||
|
|
||
| try: | ||
| node._children.clear() | ||
| node._expanded = False | ||
|
|
||
| graph = RosDependencyGraph(entity, self._ros, max_depth=EXPANSION_DEPTH) | ||
| self._populate_tree(node, graph.root) | ||
| node.expand() | ||
| except Exception as e: | ||
| node.add_leaf(TreeLabel.ERROR(str(e))) |
There was a problem hiding this comment.
The conditional logic in on_tree_node_selected() could be extracted into _should_expand_node() for better readability.
| def on_tree_node_selected(self, event: Tree.NodeSelected) -> None: | |
| node = event.node | |
| entity = node.data # type: RosEntity | None | |
| if not isinstance(entity, RosEntity): | |
| return | |
| if entity.type != RosEntityType.Node: | |
| return | |
| if node.children and all(child.is_expanded for child in node.children): | |
| return | |
| if self._subtree_depth(node) > EXPANSION_DEPTH: | |
| return | |
| try: | |
| node._children.clear() | |
| node._expanded = False | |
| graph = RosDependencyGraph(entity, self._ros, max_depth=EXPANSION_DEPTH) | |
| self._populate_tree(node, graph.root) | |
| node.expand() | |
| except Exception as e: | |
| node.add_leaf(TreeLabel.ERROR(str(e))) | |
| def on_tree_node_selected(self, event: Tree.NodeSelected) -> None: | |
| node = event.node | |
| entity = node.data | |
| if not self._should_expand_node(node, entity): | |
| return | |
| self._rebuild_node(node, entity) | |
| def _should_expand_node(self, node: TreeNode, entity: RosEntity | None) -> bool: | |
| if not isinstance(entity, RosEntity): | |
| return False | |
| if entity.type != RosEntityType.Node: | |
| return False | |
| if node.children and all(child.is_expanded for child in node.children): | |
| return False | |
| if self._subtree_depth(node) > EXPANSION_DEPTH: | |
| return False | |
| return True | |
| def _rebuild_node(self, node: TreeNode, entity: RosEntity) -> None: | |
| try: | |
| node._children.clear() | |
| node._expanded = False | |
| graph = RosDependencyGraph(entity, self._ros, max_depth=EXPANSION_DEPTH) | |
| self._populate_tree(node, graph.root) | |
| node.expand() | |
| except Exception as e: | |
| node.add_leaf(TreeLabel.error(str(e))) |
There was a problem hiding this comment.
I refactored on_tree_node_selected as you pointed out. Thanks! 226414d
4114785 to
653f46c
Compare
|
I accidentally pushed the documentation update to this branch. Please ignore it. |

Overview
This introduces a new feature to visualize ROS2 entity dependencies as an interactive tree structure. It adds a dependency graph model, a dedicated UI panel, and integrates this into the entity inspection workflow.

Highlights
Dependency Graph Model
Implemented RosDependencyGraph and RosDependencyNode classes to recursively model hierarchical relationships between ROS entities.
Supports:
Skips system topics (e.g., /parameter_events) to maintain clarity.
Graph UI Panel
Added a new widget: RosEntityGraphPanel that renders the dependency tree.
Features:
Notes
Tree nodes are color-coded: