commit 66c432e6d16a2f78e1fbd543b083b4031ba7aac5
parent fbf864b32f697fe4f46caf44299522b64a399f17
Author: Andy Khramtsov <>
Date: Sun, 31 May 2026 17:06:34 +0300
feat: visualize subtree
Diffstat:
1 file changed, 72 insertions(+), 35 deletions(-)
diff --git a/src/rsdeps/pages/home.py b/src/rsdeps/pages/home.py
@@ -457,20 +457,48 @@ def draw_graph_figure(
highlight_nodes.add(selected_node)
highlight_nodes.update(adjacency_list[selected_node])
+ subtree_nodes = set()
+
+ def build_subtree():
+ node_buffer = []
+ if selected_node:
+ node_buffer.append(selected_node)
+ subtree_nodes.add(selected_node)
+
+ graph = {str(node): set() for node in df_graph.nodes["node_id"]}
+ for arc in df_graph.arcs.iter_rows(named=True):
+ if arc["target"] is not None:
+ graph[str(arc["source"])].add(str(arc["target"]))
+
+ while node_buffer:
+ source = node_buffer.pop()
+ for target in graph[source]:
+ if target not in subtree_nodes:
+ node_buffer.append(target)
+ subtree_nodes.add(target)
+
+ if subtree:
+ build_subtree()
+
edges = EdgeFigData(
x=[],
y=[],
)
+ edges_faint = EdgeFigData(
+ x=[],
+ y=[],
+ )
+
edges_highlight = EdgeFigData(
x=[],
y=[],
)
- for row in df_graph.arcs.iter_rows(named=True):
- if not row["target"]:
+ for arc in df_graph.arcs.iter_rows(named=True):
+ if not arc["target"]:
continue
- source, target = str(row["source"]), str(row["target"])
+ source, target = str(arc["source"]), str(arc["target"])
if source in positions and target in positions:
source_x, source_y = positions[source]
target_x, target_y = positions[target]
@@ -481,6 +509,9 @@ def draw_graph_figure(
):
edges_highlight.x.extend([source_x, target_x, None])
edges_highlight.y.extend([source_y, target_y, None])
+ elif selected_node and (not subtree or source not in subtree_nodes or target not in subtree_nodes):
+ edges_faint.x.extend([source_x, target_x, None])
+ edges_faint.y.extend([source_y, target_y, None])
else:
edges.x.extend([source_x, target_x, None])
edges.y.extend([source_y, target_y, None])
@@ -488,56 +519,59 @@ def draw_graph_figure(
def build_line_scatter(edge: EdgeFigData, width: float, color: str) -> go.Scatter:
return go.Scatter(x=edge.x, y=edge.y, line=dict(width=width, color=color), hoverinfo="skip", mode="lines")
- edge_scatter = build_line_scatter(edges, width=1.0, color=colors.line_faint if selected_node else colors.line)
+ edge_scatter = build_line_scatter(edges, width=1.0, color=colors.line)
+ edge_scatter_faint = build_line_scatter(edges_faint, width=1.0, color=colors.line_faint)
edge_scatter_highlight = build_line_scatter(edges_highlight, width=1.5, color=colors.line_highlight)
- nodes = NodeFigData(
- x=[],
- y=[],
- color=[],
- label=[],
- label_color=[],
- hover_text=[],
- outline_color=[],
- custom_data=[],
- )
+ def init_nodes():
+ return NodeFigData(
+ x=[],
+ y=[],
+ color=[],
+ label=[],
+ label_color=[],
+ hover_text=[],
+ outline_color=[],
+ custom_data=[],
+ )
- nodes_highlight = NodeFigData(
- x=[],
- y=[],
- color=[],
- label=[],
- label_color=[],
- hover_text=[],
- outline_color=[],
- custom_data=[],
- )
+ nodes = init_nodes()
+ nodes_faint = init_nodes()
+ nodes_highlight = init_nodes()
- for row in df_graph.nodes.iter_rows(named=True):
- node_id = row["node_id"]
+ for arc in df_graph.nodes.iter_rows(named=True):
+ node_id = arc["node_id"]
if node_id not in positions:
continue
- nodes_appending = nodes if (not selected_node) or (node_id not in highlight_nodes) else nodes_highlight
+ nodes_appending = nodes
+
+ if selected_node:
+ if node_id in highlight_nodes:
+ nodes_appending = nodes_highlight
+ elif subtree and node_id in subtree_nodes:
+ nodes_appending = nodes
+ else:
+ nodes_appending = nodes_faint
x, y = positions[node_id]
nodes_appending.x.append(x)
nodes_appending.y.append(y)
- nodes_appending.label.append(row["short_label"])
- nodes_appending.hover_text.append(parse_node_details(row["full_details"]))
+ nodes_appending.label.append(arc["short_label"])
+ nodes_appending.hover_text.append(parse_node_details(arc["full_details"]))
nodes_appending.custom_data.append(node_id)
- if (not selected_node) or (node_id in highlight_nodes):
- nodes_appending.color.append(colors.node_main if row["full_details"]["explicit"] else colors.node)
+ if nodes_appending == nodes_faint:
+ nodes_appending.color.append(colors.node_faint)
+ nodes_appending.label_color.append(colors.text_faint)
+ nodes_appending.outline_color.append(colors.outline)
+ else:
+ nodes_appending.color.append(colors.node_main if arc["full_details"]["explicit"] else colors.node)
nodes_appending.label_color.append(colors.text)
nodes_appending.outline_color.append(
colors.outline_highlight if node_id == selected_node else colors.outline
)
- else:
- nodes_appending.color.append(colors.node_faint)
- nodes_appending.label_color.append(colors.text_faint)
- nodes_appending.outline_color.append(colors.outline)
def build_node_scatter(node: NodeFigData) -> go.Scatter:
return go.Scatter(
@@ -568,10 +602,13 @@ def draw_graph_figure(
)
node_scatter = build_node_scatter(nodes)
+ node_scatter_faint = build_node_scatter(nodes_faint)
node_scatter_highlight = build_node_scatter(nodes_highlight)
fig = go.Figure(
data=[
+ edge_scatter_faint,
+ node_scatter_faint,
edge_scatter,
node_scatter,
edge_scatter_highlight,