@@ -336,7 +336,28 @@ def init_dashboard(search, n, clicks, current_id, **kwargs):
336336 first_val = all_particles [0 ] if all_particles else None
337337
338338 mf = int (tracks ['frame' ].max ())
339- marks = {0 : 'Start' , mf : 'Ende' }
339+
340+ # --- NEU: Zeit-basierte Slider Marks ---
341+ marks = {}
342+ if 'time' in tracks .columns and not tracks ['time' ].empty :
343+ time_df = tracks .drop_duplicates ('frame' ).sort_values ('frame' )
344+ all_times_sec = time_df ['time' ]
345+ scaled_times , unit = General .get_smart_time (all_times_sec )
346+ time_map = pd .Series (scaled_times .values , index = time_df ['frame' ]).to_dict ()
347+
348+ # ~10 Ticks für den Slider
349+ for i in np .linspace (0 , mf , 11 , dtype = int ):
350+ scaled_t = time_map .get (i )
351+ if scaled_t is not None :
352+ marks [i ] = f'{ int (scaled_t )} { unit [0 ]} '
353+
354+ marks [0 ] = 'Start'
355+ if mf in time_map :
356+ marks [mf ] = f'{ int (time_map [mf ])} { unit [0 ]} '
357+ else :
358+ marks [mf ] = 'Ende'
359+ else :
360+ marks = {0 : 'Start' , mf : 'Ende' }
340361
341362 return entry_id , f"✅ ID { entry_id } geladen ({ len (all_particles )} Partikel)." , options , first_val , options , mf , marks
342363
@@ -507,11 +528,11 @@ def update_view(frame, channel, pid, markers, show_cellpose, y_metric, entry_id)
507528 num_ticks = 10
508529 if len (time_vals ) > 1 :
509530 tick_indices = np .linspace (0 , len (time_vals ) - 1 , num_ticks , dtype = int )
510- tickvals_time = time_vals .iloc [tick_indices ]
511- ticktext_frame = frame_vals .iloc [tick_indices ]
531+ tickvals_time = time_vals .iloc [tick_indices ]. tolist ()
532+ ticktext_frame = frame_vals .iloc [tick_indices ]. tolist ()
512533 else :
513- tickvals_time = time_vals
514- ticktext_frame = frame_vals
534+ tickvals_time = time_vals . tolist ()
535+ ticktext_frame = frame_vals . tolist ()
515536
516537 fig_graph .update_layout (
517538 template = "plotly_white" ,
@@ -525,6 +546,35 @@ def update_view(frame, channel, pid, markers, show_cellpose, y_metric, entry_id)
525546 )
526547 return fig_img , fig_graph , current_txt
527548
549+ @app .callback (
550+ Output ('frame-slider' , 'value' ),
551+ [Input ('single-intensity-graph' , 'clickData' )],
552+ [State ('entry-id' , 'data' ), State ('particle-dropdown' , 'value' )],
553+ prevent_initial_call = True
554+ )
555+ def jump_to_frame_on_click (clickData , entry_id , pid ):
556+ if not clickData or not entry_id or not pid :
557+ return dash .no_update
558+
559+ try :
560+ # 1. Geklickten Zeitwert holen
561+ clicked_time_scaled = clickData ['points' ][0 ]['x' ]
562+
563+ # 2. Daten für das aktuelle Partikel laden
564+ data = get_data (entry_id )
565+ if not data : return dash .no_update
566+ tracks = data ['tracks' ]
567+ t_data = tracks [tracks ['particle' ] == int (pid )].sort_values ('frame' )
568+ if t_data .empty : return dash .no_update
569+
570+ # 3. Zeitachse neu skalieren, um die Skala des Graphen zu treffen
571+ time_vals_scaled , _ = General .get_smart_time (t_data ['time' ])
572+
573+ # 4. Index des nächstgelegenen Zeitwerts finden und Frame-Nummer zurückgeben
574+ closest_index = np .abs (time_vals_scaled .values - clicked_time_scaled ).argmin ()
575+ return int (t_data .iloc [closest_index ]['frame' ])
576+ except :
577+ return dash .no_update
528578
529579@app .callback (
530580 [Output ('global-spaghetti' , 'figure' ), Output ('global-heatmap' , 'figure' )],
@@ -629,19 +679,19 @@ def map_radius_for_particle(particle_group):
629679 num_points = len (avg_t )
630680 if num_points > 1 :
631681 tick_indices = np .linspace (0 , num_points - 1 , min (num_ticks , num_points ), dtype = int )
632- tickvals_time = avg_t .iloc [tick_indices ]
633- ticktext_frame = avg_frames [tick_indices ]
682+ tickvals_time = avg_t .iloc [tick_indices ]. tolist ()
683+ ticktext_frame = avg_frames [tick_indices ]. to_list ()
634684 else :
635- tickvals_time = avg_t
636- ticktext_frame = avg_frames
685+ tickvals_time = avg_t . tolist ()
686+ ticktext_frame = avg_frames . to_list ()
637687
638688 title_suffix = "Intensity (A.U.)" if col == 'intensity_measure' else "Radius (px)"
639689 fig_s .update_layout (
640690 template = "plotly_white" ,
641691 title = f"Global Traces: { title_suffix } " ,
642692 xaxis_title = f"Time ({ t_unit } )" ,
643693 yaxis_title = title_suffix ,
644- margin = dict (t = 60 ), # Platz für Titel + Achse
694+ margin = dict (t = 60 ),
645695 xaxis2 = dict (
646696 title = "Frame Number" , overlaying = 'x' , side = 'top' , matches = 'x' ,
647697 tickvals = tickvals_time , ticktext = ticktext_frame
0 commit comments