@@ -77,6 +77,31 @@ def __init__(self, parent=None):
7777 self .observable_to_subplot = {}
7878 self .no_plotting_rn = False
7979
80+ # DataFrame caching system for performance optimization
81+ self ._df_cache = {
82+ "measurements" : None ,
83+ "simulations" : None ,
84+ "conditions" : None ,
85+ "visualization" : None ,
86+ }
87+ self ._cache_valid = {
88+ "measurements" : False ,
89+ "simulations" : False ,
90+ "conditions" : False ,
91+ "visualization" : False ,
92+ }
93+
94+ def _invalidate_cache (self , table_name ):
95+ """Invalidate cache for specific table."""
96+ self ._cache_valid [table_name ] = False
97+
98+ def _get_cached_df (self , table_name , proxy_model ):
99+ """Get cached DataFrame or convert if invalid."""
100+ if not self ._cache_valid [table_name ]:
101+ self ._df_cache [table_name ] = proxy_to_dataframe (proxy_model )
102+ self ._cache_valid [table_name ] = True
103+ return self ._df_cache [table_name ]
104+
80105 def initialize (
81106 self , meas_proxy , sim_proxy , cond_proxy , vis_proxy , petab_model
82107 ):
@@ -86,20 +111,65 @@ def initialize(
86111 self .vis_proxy = vis_proxy
87112 self .petab_model = petab_model
88113
89- # Connect data changes
114+ # Connect cache invalidation and data changes
90115 self .options_manager .option_changed .connect (self ._debounced_plot )
116+
117+ # Measurements cache invalidation
118+ self .meas_proxy .dataChanged .connect (
119+ lambda : self ._invalidate_cache ("measurements" )
120+ )
121+ self .meas_proxy .rowsInserted .connect (
122+ lambda : self ._invalidate_cache ("measurements" )
123+ )
124+ self .meas_proxy .rowsRemoved .connect (
125+ lambda : self ._invalidate_cache ("measurements" )
126+ )
91127 self .meas_proxy .dataChanged .connect (self ._debounced_plot )
92128 self .meas_proxy .rowsInserted .connect (self ._debounced_plot )
93129 self .meas_proxy .rowsRemoved .connect (self ._debounced_plot )
130+
131+ # Conditions cache invalidation
132+ self .cond_proxy .dataChanged .connect (
133+ lambda : self ._invalidate_cache ("conditions" )
134+ )
135+ self .cond_proxy .rowsInserted .connect (
136+ lambda : self ._invalidate_cache ("conditions" )
137+ )
138+ self .cond_proxy .rowsRemoved .connect (
139+ lambda : self ._invalidate_cache ("conditions" )
140+ )
94141 self .cond_proxy .dataChanged .connect (self ._debounced_plot )
95142 self .cond_proxy .rowsInserted .connect (self ._debounced_plot )
96143 self .cond_proxy .rowsRemoved .connect (self ._debounced_plot )
144+
145+ # Simulations cache invalidation
146+ self .sim_proxy .dataChanged .connect (
147+ lambda : self ._invalidate_cache ("simulations" )
148+ )
149+ self .sim_proxy .rowsInserted .connect (
150+ lambda : self ._invalidate_cache ("simulations" )
151+ )
152+ self .sim_proxy .rowsRemoved .connect (
153+ lambda : self ._invalidate_cache ("simulations" )
154+ )
97155 self .sim_proxy .dataChanged .connect (self ._debounced_plot )
98156 self .sim_proxy .rowsInserted .connect (self ._debounced_plot )
99157 self .sim_proxy .rowsRemoved .connect (self ._debounced_plot )
158+
159+ # Visualization cache invalidation
160+ self .vis_proxy .dataChanged .connect (
161+ lambda : self ._invalidate_cache ("visualization" )
162+ )
163+ self .vis_proxy .rowsInserted .connect (
164+ lambda : self ._invalidate_cache ("visualization" )
165+ )
166+ self .vis_proxy .rowsRemoved .connect (
167+ lambda : self ._invalidate_cache ("visualization" )
168+ )
100169 self .vis_proxy .dataChanged .connect (self ._debounced_plot )
101170 self .vis_proxy .rowsInserted .connect (self ._debounced_plot )
102171 self .vis_proxy .rowsRemoved .connect (self ._debounced_plot )
172+
103173 self .visibilityChanged .connect (self ._debounced_plot )
104174
105175 self .plot_it ()
@@ -113,10 +183,11 @@ def plot_it(self):
113183 # If the dock is not visible, do not plot
114184 return
115185
116- measurements_df = proxy_to_dataframe (self .meas_proxy )
117- simulations_df = proxy_to_dataframe (self .sim_proxy )
118- conditions_df = proxy_to_dataframe (self .cond_proxy )
119- visualisation_df = proxy_to_dataframe (self .vis_proxy )
186+ # Use cached DataFrames for performance
187+ measurements_df = self ._get_cached_df ("measurements" , self .meas_proxy )
188+ simulations_df = self ._get_cached_df ("simulations" , self .sim_proxy )
189+ conditions_df = self ._get_cached_df ("conditions" , self .cond_proxy )
190+ visualisation_df = self ._get_cached_df ("visualization" , self .vis_proxy )
120191 group_by = self .options_manager .get_option ()
121192 # group_by different value in petab.visualize
122193 if group_by == "condition" :
@@ -308,7 +379,8 @@ def plot_residuals(self):
308379 return
309380
310381 problem = self .petab_model .current_petab_problem
311- simulations_df = proxy_to_dataframe (self .sim_proxy )
382+ # Reuse cached DataFrame instead of converting again
383+ simulations_df = self ._get_cached_df ("simulations" , self .sim_proxy )
312384
313385 if simulations_df .empty :
314386 return
0 commit comments