@@ -91,6 +91,7 @@ def mouseDragEvent(self, ev, axis=None):
9191
9292class TreePlot (pg .PlotWidget ):
9393 node_clicked = Signal (Any , bool ) # node_id, append
94+ jump_to_node = Signal (int ) # node to jump to
9495 nodes_selected = Signal (list , bool )
9596
9697 def __init__ (self ) -> pg .PlotWidget :
@@ -230,8 +231,12 @@ def _on_click(self, _, points: np.ndarray, ev: QMouseEvent) -> None:
230231 modifiers = ev .modifiers ()
231232 node_id = points [0 ].data ()
232233 append = Qt .ShiftModifier == modifiers
233- self .node_clicked .emit (node_id , append )
234- self .setFocus ()
234+ jump = Qt .ControlModifier == modifiers
235+ if jump :
236+ self .jump_to_node .emit (node_id )
237+ else :
238+ self .node_clicked .emit (node_id , append )
239+ self .setFocus ()
235240
236241 def set_data (self , track_df : pd .DataFrame , plot_type : str , feature : str ) -> None :
237242 """Updates the stored pyqtgraph content based on the given dataframe.
@@ -323,12 +328,14 @@ def _create_pyqtgraph_content(
323328
324329 def set_selection (self , selected_nodes : list [Any ], plot_type : str ) -> None :
325330 """Set the provided list of nodes to be selected. Increases the size
326- and highlights the outline with blue. Also centers the view
327- if the first selected node is not visible in the current canvas.
331+ and highlights the outline with blue.
332+
333+ Note: Single-node centering is handled separately via the center_node signal
334+ from TracksViewer. Multi-node range centering is handled here.
328335
329336 Args:
330337 selected_nodes (list[Any]): A list of node ids to be selected.
331- feature (str): the feature that is being plotted , either 'tree' or 'area '
338+ plot_type (str): the plot type being displayed , either 'tree' or 'feature '
332339 """
333340
334341 # reset to default size and color to avoid problems with the array lengths
@@ -340,9 +347,7 @@ def set_selection(self, selected_nodes: list[Any], plot_type: str) -> None:
340347 ) # just copy the size here to keep the original self.sizes intact
341348
342349 outlines = self .outline_pen .copy ()
343- axis_label = (
344- self .feature if plot_type == "feature" else "x_axis_pos"
345- ) # check what is currently being shown, to know how to scale the view
350+ axis_label = self .feature if plot_type == "feature" else "x_axis_pos"
346351
347352 if len (selected_nodes ) > 0 :
348353 x_values = []
@@ -361,11 +366,9 @@ def set_selection(self, selected_nodes: list[Any], plot_type: str) -> None:
361366 size [index ] += 5
362367 outlines [index ] = pg .mkPen (color = "c" , width = 2 )
363368
364- # Center point if a single node is selected, center range if multiple nodes
365- # are selected
366- if len (selected_nodes ) == 1 :
367- self ._center_view (x_axis_value , t )
368- else :
369+ # Center range if multiple nodes are selected (single-node centering
370+ # is handled by the center_node signal)
371+ if len (x_values ) > 1 :
369372 min_x = np .min (x_values )
370373 max_x = np .max (x_values )
371374 min_t = np .min (t_values )
@@ -398,8 +401,27 @@ def _center_range(self, min_x: int, max_x: int, min_t: int, max_t: int):
398401 else :
399402 self .autoRange ()
400403
404+ def center_on_node (self , node_id : int ) -> None :
405+ """Center the view on a specific node by ID.
406+
407+ Args:
408+ node_id: The node ID to center on.
409+ """
410+ if not hasattr (self , "track_df" ) or self .track_df is None :
411+ return
412+ node_df = self .track_df .loc [self .track_df ["node_id" ] == node_id ]
413+ if node_df .empty :
414+ return
415+ axis_label = self .feature if self .plot_type == "feature" else "x_axis_pos"
416+ x_axis_value = node_df [axis_label ].values [0 ]
417+ t = node_df ["t" ].values [0 ]
418+ self ._center_view (x_axis_value , t )
419+
401420 def _center_view (self , center_x : int , center_y : int ):
402- """Center the Viewbox on given coordinates"""
421+ """Center the Viewbox on given coordinates, preserving the current zoom level.
422+
423+ Only pans if the point is outside the current view.
424+ """
403425
404426 if self .view_direction == "horizontal" :
405427 center_x , center_y = (
@@ -420,7 +442,12 @@ def _center_view(self, center_x: int, center_y: int):
420442 ):
421443 return
422444
423- self .autoRange ()
445+ # Pan to center the point while preserving current zoom level
446+ x_width = x_range [1 ] - x_range [0 ]
447+ y_width = y_range [1 ] - y_range [0 ]
448+ new_x_range = (center_x - x_width / 2 , center_x + x_width / 2 )
449+ new_y_range = (center_y - y_width / 2 , center_y + y_width / 2 )
450+ view_box .setRange (xRange = new_x_range , yRange = new_y_range , padding = 0 )
424451
425452
426453class TreeWidget (QWidget ):
@@ -445,7 +472,9 @@ def __init__(self, viewer: napari.Viewer):
445472
446473 self .tree_widget : TreePlot = TreePlot ()
447474 self .tree_widget .node_clicked .connect (self .selected_nodes .add )
475+ self .tree_widget .jump_to_node .connect (self .tracks_viewer .center_on_node )
448476 self .tree_widget .nodes_selected .connect (self .selected_nodes .add_list )
477+ self .tracks_viewer .center_node .connect (self .tree_widget .center_on_node )
449478
450479 # Add radiobuttons for switching between different display modes
451480 self .mode_widget = TreeViewModeWidget ()
0 commit comments