import  uuid 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  threading 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  numpy  as  np 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  collections  import  deque 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								import  dearpygui . dearpygui  as  dpg 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								from  abc  import  ABC ,  abstractmethod 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								class  ViewPanel ( ABC ) : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  """ Abstract base class for all view panels that can be displayed in a plot container """ 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  __init__ ( self ,  panel_id :  str  =  None ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . panel_id  =  panel_id  or  str ( uuid . uuid4 ( ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . title  =  " Untitled Panel " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  clear ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  create_ui ( self ,  parent_tag :  str ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  destroy_ui ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  get_panel_type ( self )  - >  str : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  update ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  to_dict ( self )  - >  dict : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @classmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @abstractmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  load_from_dict ( cls ,  data :  dict ,  data_manager ,  playback_manager ,  worker_manager ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    pass 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								class  TimeSeriesPanel ( ViewPanel ) : 
 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  __init__ ( self ,  data_manager ,  playback_manager ,  worker_manager ,  panel_id :  str  |  None  =  None ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    super ( ) . __init__ ( panel_id ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . data_manager  =  data_manager 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . playback_manager  =  playback_manager 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . worker_manager  =  worker_manager 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . title  =  " Time Series Plot " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . plot_tag  =  f " plot_ { self . panel_id } " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . x_axis_tag  =  f " { self . plot_tag } _x_axis " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . y_axis_tag  =  f " { self . plot_tag } _y_axis " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . timeline_indicator_tag  =  f " { self . plot_tag } _timeline " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _ui_created  =  False 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _series_data :  dict [ str ,  tuple [ np . ndarray ,  np . ndarray ] ]  =  { } 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _last_plot_duration  =  0 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _update_lock  =  threading . RLock ( ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _results_deque :  deque [ tuple [ str ,  list ,  list ] ]  =  deque ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _new_data  =  False 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _last_x_limits  =  ( 0.0 ,  0.0 ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _queued_x_sync :  tuple  |  None  =  None 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _queued_reallow_x_zoom  =  False 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _total_segments  =  self . playback_manager . num_segments 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								  def  to_dict ( self )  - >  dict : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    return  { 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      " type " :  " timeseries " , 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      " title " :  self . title , 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      " series_paths " :  list ( self . _series_data . keys ( ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    } 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @classmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  load_from_dict ( cls ,  data :  dict ,  data_manager ,  playback_manager ,  worker_manager ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    panel  =  cls ( data_manager ,  playback_manager ,  worker_manager ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    panel . title  =  data . get ( " title " ,  " Time Series Plot " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    panel . _series_data  =  { path :  ( np . array ( [ ] ) ,  np . array ( [ ] ) )  for  path  in  data . get ( " series_paths " ,  [ ] ) } 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    return  panel 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  create_ui ( self ,  parent_tag :  str ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . data_manager . add_observer ( self . on_data_loaded ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . playback_manager . add_x_axis_observer ( self . _on_x_axis_sync ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  dpg . plot ( height = - 1 ,  width = - 1 ,  tag = self . plot_tag ,  parent = parent_tag ,  drop_callback = self . _on_series_drop ,  payload_type = " TIMESERIES_PAYLOAD " ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . add_plot_legend ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . add_plot_axis ( dpg . mvXAxis ,  no_label = True ,  tag = self . x_axis_tag ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . add_plot_axis ( dpg . mvYAxis ,  no_label = True ,  tag = self . y_axis_tag ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      timeline_series_tag  =  dpg . add_inf_line_series ( x = [ 0 ] ,  label = " Timeline " ,  parent = self . y_axis_tag ,  tag = self . timeline_indicator_tag ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      dpg . bind_item_theme ( timeline_series_tag ,  " timeline_theme " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _new_data  =  True 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    self . _queued_x_sync  =  self . playback_manager . x_axis_bounds 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _ui_created  =  True 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  update ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  not  self . _ui_created : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      if  self . _queued_x_sync : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        min_time ,  max_time  =  self . _queued_x_sync 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _queued_x_sync  =  None 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        dpg . set_axis_limits ( self . x_axis_tag ,  min_time ,  max_time ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _last_x_limits  =  ( min_time ,  max_time ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _fit_y_axis ( min_time ,  max_time ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _queued_reallow_x_zoom  =  True  # must wait a frame before allowing user changes so that axis limits take effect 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  self . _queued_reallow_x_zoom : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _queued_reallow_x_zoom  =  False 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        if  tuple ( dpg . get_axis_limits ( self . x_axis_tag ) )  ==  self . _last_x_limits : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          dpg . set_axis_limits_auto ( self . x_axis_tag ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          self . _queued_x_sync  =  self . _last_x_limits  # retry, likely too early 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  self . _new_data :   # handle new data in main thread 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _new_data  =  False 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  self . _total_segments  >  0 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          dpg . set_axis_limits_constraints ( self . x_axis_tag ,  - 10 ,  self . _total_segments  *  60  +  10 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _fit_y_axis ( * dpg . get_axis_limits ( self . x_axis_tag ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        for  series_path  in  list ( self . _series_data . keys ( ) ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          self . add_series ( series_path ,  update = True ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      current_limits  =  dpg . get_axis_limits ( self . x_axis_tag ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      # downsample if plot zoom changed significantly 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      plot_duration  =  current_limits [ 1 ]  -  current_limits [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  plot_duration  >  self . _last_plot_duration  *  2  or  plot_duration  <  self . _last_plot_duration  *  0.5 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _downsample_all_series ( plot_duration ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      # sync x-axis if changed by user 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  self . _last_x_limits  !=  current_limits : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . playback_manager . set_x_axis_bounds ( current_limits [ 0 ] ,  current_limits [ 1 ] ,  source_panel = self ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _last_x_limits  =  current_limits 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _fit_y_axis ( current_limits [ 0 ] ,  current_limits [ 1 ] ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      while  self . _results_deque :   # handle downsampled results in main thread 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        results  =  self . _results_deque . popleft ( ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        for  series_path ,  downsampled_time ,  downsampled_values  in  results : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          series_tag  =  f " series_ { self . panel_id } _ { series_path } " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          if  dpg . does_item_exist ( series_tag ) : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								            dpg . set_value ( series_tag ,  ( downsampled_time ,  downsampled_values . astype ( float ) ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      # update timeline 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      current_time_s  =  self . playback_manager . current_time_s 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . set_value ( self . timeline_indicator_tag ,  [ [ current_time_s ] ,  [ 0 ] ] ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      # update timeseries legend label 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      for  series_path ,  ( time_array ,  value_array )  in  self . _series_data . items ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        position  =  np . searchsorted ( time_array ,  current_time_s ,  side = ' right ' )  -  1 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  position  > =  0  and  ( current_time_s  -  time_array [ position ] )  < =  1.0 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          value  =  value_array [ position ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          formatted_value  =  f " { value : .5f } "  if  np . issubdtype ( type ( value ) ,  np . floating )  else  str ( value ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          series_tag  =  f " series_ { self . panel_id } _ { series_path } " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          if  dpg . does_item_exist ( series_tag ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								            dpg . configure_item ( series_tag ,  label = f " { series_path } :  { formatted_value } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								  def  _on_x_axis_sync ( self ,  min_time :  float ,  max_time :  float ,  source_panel ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      if  source_panel  !=  self : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _queued_x_sync  =  ( min_time ,  max_time ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  _fit_y_axis ( self ,  x_min :  float ,  x_max :  float ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  not  self . _series_data : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . set_axis_limits ( self . y_axis_tag ,  - 1 ,  1 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    global_min  =  float ( ' inf ' ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    global_max  =  float ( ' -inf ' ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    found_data  =  False 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    for  time_array ,  value_array  in  self . _series_data . values ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  len ( time_array )  ==  0 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        continue 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      start_idx ,  end_idx  =  np . searchsorted ( time_array ,  [ x_min ,  x_max ] ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      end_idx  =  min ( end_idx ,  len ( time_array )  -  1 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  start_idx  < =  end_idx : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        y_slice  =  value_array [ start_idx : end_idx  +  1 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        series_min ,  series_max  =  np . min ( y_slice ) ,  np . max ( y_slice ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        global_min  =  min ( global_min ,  series_min ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        global_max  =  max ( global_max ,  series_max ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        found_data  =  True 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  not  found_data : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      dpg . set_axis_limits ( self . y_axis_tag ,  - 1 ,  1 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  global_min  ==  global_max : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      padding  =  max ( abs ( global_min )  *  0.1 ,  1.0 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      y_min ,  y_max  =  global_min  -  padding ,  global_max  +  padding 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      range_size  =  global_max  -  global_min 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      padding  =  range_size  *  0.1 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      y_min ,  y_max  =  global_min  -  padding ,  global_max  +  padding 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    dpg . set_axis_limits ( self . y_axis_tag ,  y_min ,  y_max ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  _downsample_all_series ( self ,  plot_duration ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    plot_width  =  dpg . get_item_rect_size ( self . plot_tag ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  plot_width  < =  0  or  plot_duration  < =  0 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      return 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . _last_plot_duration  =  plot_duration 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    target_points_per_second  =  plot_width  /  plot_duration 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    work_items  =  [ ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    for  series_path ,  ( time_array ,  value_array )  in  self . _series_data . items ( ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  len ( time_array )  ==  0 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        continue 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      series_duration  =  time_array [ - 1 ]  -  time_array [ 0 ]  if  len ( time_array )  >  1  else  1 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      points_per_second  =  len ( time_array )  /  series_duration 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  points_per_second  >  target_points_per_second  *  2 : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        target_points  =  max ( int ( target_points_per_second  *  series_duration ) ,  plot_width ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        work_items . append ( ( series_path ,  time_array ,  value_array ,  target_points ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      elif  dpg . does_item_exist ( f " series_ { self . panel_id } _ { series_path } " ) : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        dpg . set_value ( f " series_ { self . panel_id } _ { series_path } " ,  ( time_array ,  value_array . astype ( float ) ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  work_items : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . worker_manager . submit_task ( 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        TimeSeriesPanel . _downsample_worker ,  work_items ,  callback = lambda  results :  self . _results_deque . append ( results ) ,  task_id = f " downsample_ { self . panel_id } " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  add_series ( self ,  series_path :  str ,  update :  bool  =  False ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  update  or  series_path  not  in  self . _series_data : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _series_data [ series_path ]  =  self . data_manager . get_timeseries ( series_path ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      time_array ,  value_array  =  self . _series_data [ series_path ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      series_tag  =  f " series_ { self . panel_id } _ { series_path } " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  dpg . does_item_exist ( series_tag ) : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        dpg . set_value ( series_tag ,  ( time_array ,  value_array . astype ( float ) ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      else : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        line_series_tag  =  dpg . add_line_series ( x = time_array ,  y = value_array . astype ( float ) ,  label = series_path ,  parent = self . y_axis_tag ,  tag = series_tag ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								        dpg . bind_item_theme ( line_series_tag ,  " line_theme " ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      self . _fit_y_axis ( * dpg . get_axis_limits ( self . x_axis_tag ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      plot_duration  =  dpg . get_axis_limits ( self . x_axis_tag ) [ 1 ]  -  dpg . get_axis_limits ( self . x_axis_tag ) [ 0 ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . _downsample_all_series ( plot_duration ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  destroy_ui ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . data_manager . remove_observer ( self . on_data_loaded ) 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								      self . playback_manager . remove_x_axis_observer ( self . _on_x_axis_sync ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  dpg . does_item_exist ( self . plot_tag ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        dpg . delete_item ( self . plot_tag ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . _ui_created  =  False 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  get_panel_type ( self )  - >  str : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    return  " timeseries " 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  clear ( self ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      for  series_path  in  list ( self . _series_data . keys ( ) ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . remove_series ( series_path ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  remove_series ( self ,  series_path :  str ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  series_path  in  self . _series_data : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  dpg . does_item_exist ( f " series_ { self . panel_id } _ { series_path } " ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          dpg . delete_item ( f " series_ { self . panel_id } _ { series_path } " ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        del  self . _series_data [ series_path ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  on_data_loaded ( self ,  data :  dict ) : 
  
						 
					
						
							
								
							 
							
								
									
										 
								
							 
							
								 
							
							
								    with  self . _update_lock : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      self . _new_data  =  True 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  data . get ( ' metadata_loaded ' ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _total_segments  =  data . get ( ' total_segments ' ,  0 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        limits  =  ( - 10 ,  self . _total_segments  *  60  +  10 ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        self . _queued_x_sync  =  limits 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  _on_series_drop ( self ,  sender ,  app_data ,  user_data ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    self . add_series ( app_data ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  @staticmethod 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								  def  _downsample_worker ( series_path ,  time_array ,  value_array ,  target_points ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    if  len ( time_array )  < =  target_points : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      return  series_path ,  time_array ,  value_array 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    step  =  len ( time_array )  /  target_points 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    indices  =  [ ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    for  i  in  range ( target_points ) : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      start_idx  =  int ( i  *  step ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      end_idx  =  int ( ( i  +  1 )  *  step ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      if  start_idx  ==  end_idx : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        indices . append ( start_idx ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								      else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        bucket_values  =  value_array [ start_idx : end_idx ] 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        min_idx  =  start_idx  +  np . argmin ( bucket_values ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        max_idx  =  start_idx  +  np . argmax ( bucket_values ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        if  min_idx  !=  max_idx : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          indices . extend ( [ min ( min_idx ,  max_idx ) ,  max ( min_idx ,  max_idx ) ] ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        else : 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								          indices . append ( min_idx ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    indices  =  sorted ( set ( indices ) ) 
  
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    return  series_path ,  time_array [ indices ] ,  value_array [ indices ]