@@ -400,6 +400,7 @@ def __init__(
400400 else :
401401 self .silhouette = silhouette
402402 self ._scalar_bar = None
403+ self ._scalar_bar_ticks = None
403404 # for now only one time label can be added
404405 # since it is the same for all figures
405406 self ._time_label_added = False
@@ -591,6 +592,8 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
591592 self ._picked_patches = {key : list () for key in all_keys }
592593 self ._picked_points = dict ()
593594 self ._mouse_no_mvt = - 1
595+ self ._show_hover_info = False
596+ self ._hover_caption = None
594597
595598 # Derived parameters:
596599 self .playback_speed = self .default_playback_speed_value
@@ -626,6 +629,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
626629 self ._configure_scalar_bar ()
627630 self ._configure_shortcuts ()
628631 self ._configure_picking ()
632+ self ._configure_hover ()
629633 self ._configure_dock ()
630634 self ._configure_tool_bar ()
631635 self ._configure_status_bar ()
@@ -687,6 +691,8 @@ def _clean(self):
687691 "picked_renderer" ,
688692 "act_data_smooth" ,
689693 "_scalar_bar" ,
694+ "_scalar_bar_ticks" ,
695+ "_hover_caption" ,
690696 "actions" ,
691697 "widgets" ,
692698 "geo" ,
@@ -775,6 +781,8 @@ def _configure_scalar_bar(self):
775781 self ._scalar_bar .SetHeight (0.6 )
776782 self ._scalar_bar .SetWidth (0.05 )
777783 self ._scalar_bar .SetPosition (0.02 , 0.2 )
784+ # the tick actor repositions itself on every render (see
785+ # _add_scalarbar_ticks), so no explicit update is needed here
778786
779787 def _configure_dock_playback_widget (self , name ):
780788 len_time = len (self ._data ["time" ]) - 1
@@ -1198,6 +1206,74 @@ def _configure_picking(self):
11981206 )
11991207 subscribe (self , "vertex_select" , self ._on_vertex_select )
12001208
1209+ def _configure_hover (self ):
1210+ self ._hover_caption = self ._create_caption ()
1211+ self .plotter .add_actor (
1212+ self ._hover_caption ,
1213+ name = None ,
1214+ culling = False ,
1215+ pickable = False ,
1216+ reset_camera = False ,
1217+ render = False ,
1218+ )
1219+
1220+ @_auto_weakref
1221+ def on_surface_hover (iren , event ):
1222+ self ._on_surface_hover (iren , event )
1223+
1224+ self .plotter .iren .add_observer ("MouseMoveEvent" , on_surface_hover )
1225+
1226+ def _on_surface_hover (self , iren , event ): # event == "MouseMoveEvent"
1227+ if not self ._show_hover_info :
1228+ return
1229+ from pyvista import DataSetMapper
1230+
1231+ x , y = iren .GetEventPosition ()
1232+ picked_renderer = iren .FindPokedRenderer (x , y )
1233+ vtk_picker = self ._renderer ._picker
1234+ vtk_picker .Pick (x , y , 0 , picked_renderer )
1235+ cell_id = vtk_picker .GetCellId ()
1236+ mapper = vtk_picker .GetMapper ()
1237+ if not isinstance (mapper , DataSetMapper ) or cell_id == - 1 :
1238+ if self ._hover_caption .GetVisibility ():
1239+ self ._hover_caption .SetVisibility (False )
1240+ self ._renderer ._update ()
1241+ return # didn't find a mesh
1242+ for _ , this_mesh in self .layered_meshes .items ():
1243+ if this_mesh ._polydata is mapper .dataset :
1244+ mesh = this_mesh ._polydata
1245+ break
1246+ else :
1247+ return
1248+ pos = np .array (vtk_picker .GetPickPosition ())
1249+ vtk_cell = mesh .GetCell (cell_id )
1250+ cell = [
1251+ vtk_cell .GetPointId (point_id )
1252+ for point_id in range (vtk_cell .GetNumberOfPoints ())
1253+ ]
1254+ vert_pos = mesh .points [cell ]
1255+ vertex_id = cell [np .argmin (np .linalg .norm (vert_pos - pos , axis = 1 ))]
1256+ _ , _ , azimuth , elevation , _ = self ._renderer .get_camera (rigid = self ._rigid )
1257+ text = (
1258+ f"vertex { vertex_id } \n "
1259+ f"({ pos [0 ]:.1f} , { pos [1 ]:.1f} , { pos [2 ]:.1f} ) mm\n "
1260+ f"az { azimuth :.0f} \N{DEGREE SIGN} el { elevation :.0f} \N{DEGREE SIGN} "
1261+ )
1262+ self ._hover_caption .SetCaption (text )
1263+ self ._hover_caption .SetAttachmentPoint (* pos )
1264+ self ._hover_caption .SetVisibility (True )
1265+ actor = self ._hover_caption .GetTextActor ()
1266+ wh = np .zeros (2 )
1267+ actor .GetSize (self .plotter .renderer , wh )
1268+ self ._hover_caption .SetPosition2 (wh )
1269+ self ._renderer ._update ()
1270+
1271+ def _toggle_hover_info (self ):
1272+ self ._show_hover_info = not self ._show_hover_info
1273+ if not self ._show_hover_info and self ._hover_caption is not None :
1274+ self ._hover_caption .SetVisibility (False )
1275+ self ._renderer ._update ()
1276+
12011277 def _configure_tool_bar (self ):
12021278 if not hasattr (self ._renderer , "_tool_bar" ) or self ._renderer ._tool_bar is None :
12031279 self ._renderer ._tool_bar_initialize (name = "Toolbar" )
@@ -1240,6 +1316,12 @@ def save_movie(filename):
12401316 desc = "Clear traces" ,
12411317 func = self .clear_glyphs ,
12421318 )
1319+ self ._renderer ._tool_bar_add_button (
1320+ name = "hover_info" ,
1321+ desc = "Toggle vertex/camera hover info" ,
1322+ func = self ._toggle_hover_info ,
1323+ icon_name = "information" ,
1324+ )
12431325 self ._renderer ._tool_bar_add_spacer ()
12441326 self ._renderer ._tool_bar_add_button (
12451327 name = "help" ,
@@ -1263,13 +1345,14 @@ def _rotate_camera(self, which, value):
12631345
12641346 def _configure_shortcuts (self ):
12651347 # Remove the default key binding
1266- if getattr (self , "iren" , None ) is not None :
1348+ if getattr (self . plotter , "iren" , None ) is not None :
12671349 self .plotter .iren .clear_key_event_callbacks ()
12681350 # Then, we add our own:
12691351 self .plotter .add_key_event ("i" , self .toggle_interface )
12701352 self .plotter .add_key_event ("s" , self .apply_auto_scaling )
12711353 self .plotter .add_key_event ("r" , self .restore_user_scaling )
12721354 self .plotter .add_key_event ("c" , self .clear_glyphs )
1355+ self .plotter .add_key_event ("v" , self ._toggle_hover_info )
12731356 for key , which , amt in (
12741357 ("Left" , "azimuth" , 10 ),
12751358 ("Right" , "azimuth" , - 10 ),
@@ -1658,6 +1741,7 @@ def _configure_help(self):
16581741 ("s" , "Apply auto-scaling" ),
16591742 ("r" , "Restore original clim" ),
16601743 ("c" , "Clear all traces" ),
1744+ ("v" , "Toggle vertex/camera hover info" ),
16611745 ("n" , "Shift the time forward by the playback speed" ),
16621746 ("b" , "Shift the time backward by the playback speed" ),
16631747 ("Space" , "Start/Pause playback" ),
@@ -2013,6 +2097,9 @@ def add_data(
20132097 self ._all_data [key ]["fmid" ] = fmid
20142098 self ._all_data [key ]["fmax" ] = fmax
20152099 self ._all_data [key ]["colorbar_fmt" ] = (colorbar_kwargs or {}).get ("fmt" )
2100+ self ._all_data [key ]["colorbar_title" ] = (colorbar_kwargs or {}).get (
2101+ "title" , key if key != "data" else None
2102+ )
20162103 self .set_time_interpolation (self .time_interpolation )
20172104 self ._update_colormap_range ()
20182105
@@ -2067,7 +2154,9 @@ def add_data(
20672154 fmt = _auto_scalar_bar_fmt (self ._cmap_range ),
20682155 )
20692156 kwargs .update (colorbar_kwargs or {})
2070- self ._scalar_bar = self ._renderer .scalarbar (** kwargs )
2157+ self ._scalar_bar , self ._scalar_bar_ticks = self ._renderer .scalarbar (
2158+ ** kwargs
2159+ )
20712160 self ._set_camera (** views_dicts [hemi ][v ])
20722161
20732162 # 4) update the scalar bar and opacity (and render)
@@ -3706,6 +3795,10 @@ def _update_colormap_range(self, fmin=None, fmid=None, fmax=None, alpha=None):
37063795 rng = self ._cmap_range
37073796 ctable = self ._data ["ctable" ]
37083797 fmt = self ._data ["colorbar_fmt" ] or _auto_scalar_bar_fmt (rng )
3798+ if self ._scalar_bar is not None :
3799+ self ._renderer .set_scalarbar_title (
3800+ self ._scalar_bar , self ._data ["colorbar_title" ]
3801+ )
37093802 for hemi in ["lh" , "rh" , "vol" ]:
37103803 hemi_data = self ._data .get (hemi )
37113804 if hemi_data is not None :
0 commit comments