@@ -954,96 +954,207 @@ def _finalize_plot(self, x_col, y_col, selected_filepaths):
954954 else :
955955 legend_title = "Multiple Files"
956956
957- leg = self .ax_main .legend (title = legend_title , labelcolor = self .CLR_CONSOLE_BG )
958- if leg :
959- leg .get_title ().set_color (self .CLR_CONSOLE_BG )
960-
961- self .ax_main .set_xscale (
962- 'log' if self .x_log_var .get () else 'linear' ) # type: ignore
963- self .ax_main .set_yscale (
964- 'log' if self .y_log_var .get () else 'linear' )
965- self .ax_main .set_xlabel (x_col )
966- self .ax_main .set_ylabel (y_col )
967-
968- if len (selected_filepaths ) == 1 :
969- self .ax_main .set_title (
970- os .path .basename (
971- selected_filepaths [0 ]),
972- fontweight = 'bold' )
973- else :
974- self .ax_main .set_title (
975- f"{ y_col } vs. { x_col } " ,
976- fontweight = 'bold' )
977- self .figure .tight_layout ()
978-
979- def _handle_load_error (self , filepath , e ):
980- """Handles errors during file loading."""
981- if filepath in self .file_data_cache :
982- self .file_data_cache [filepath ] = {
983- "path" : filepath , "headers" : [], "data" : {}}
984- self .column_source_var .set ("Columns from: (no file selected)" )
985- self .active_filepath = None
986- self .plot_data ()
987- self .x_col_cb .set ('' )
988- self .y_col_cb .set ('' )
989- self .x_col_cb ['values' ] = []
990- self .y_col_cb ['values' ] = []
991- self .live_update_var .set (False )
992- self .toggle_live_update ()
993- self .log (f"Error loading file: { traceback .format_exc ()} " )
994- messagebox .showerror (
995- "File Load Error" ,
996- f"Could not read the data file. It may be empty, malformed, or in use.\n \n Details: { e } " )
997-
998- def toggle_live_update (self ):
999- if self .live_update_var .get ():
1000- self .start_file_watcher ()
1001- else :
1002- self .stop_file_watcher ()
1003-
1004- def start_file_watcher (self ):
1005- self .stop_file_watcher () # Ensure no multiple watchers are running
1006- if self .live_update_var .get () and self .active_filepath :
1007- self .log ("Live update enabled. Watching for file changes..." )
1008- self .file_watcher_job = self .root .after (
1009- 1000 , self .check_for_updates )
1010-
1011- def stop_file_watcher (self ):
1012- if self .file_watcher_job :
1013- self .root .after_cancel (self .file_watcher_job )
1014- self .file_watcher_job = None
1015- self .log ("Live update disabled." )
1016-
1017- def check_for_updates (self ):
1018- if not self .active_filepath or not self .live_update_var .get (
1019- ) or not os .path .exists (self .active_filepath ):
1020- self .file_watcher_job = None # Stop watching if file is gone or disabled
1021- return
1022-
1023- try :
1024- file_info = self .file_data_cache [self .active_filepath ]
1025- mod_time = os .path .getmtime (self .active_filepath )
1026- current_size = os .path .getsize (self .active_filepath )
1027-
1028- if mod_time != file_info .get ('mod_time' ):
1029- if current_size > file_info .get ('size' , 0 ):
1030- # File has grown, append new data
1031- self .append_file_data ()
957+ leg = self .ax_main .legend (title = legend_title ,
958+
959+ labelcolor = self .CLR_CONSOLE_BG )
960+
961+ if leg :
962+
963+ leg .get_title ().set_color (self .CLR_CONSOLE_BG )
964+
965+
966+
967+ self .ax_main .set_xscale (
968+
969+ 'log' if self .x_log_var .get () else 'linear' ) # type: ignore
970+
971+ self .ax_main .set_yscale (
972+
973+ 'log' if self .y_log_var .get () else 'linear' )
974+
975+ self .ax_main .set_xlabel (x_col )
976+
977+ self .ax_main .set_ylabel (y_col )
978+
979+
980+
981+ if len (selected_filepaths ) == 1 :
982+
983+ self .ax_main .set_title (
984+
985+ os .path .basename (
986+
987+ selected_filepaths [0 ]),
988+
989+ fontweight = 'bold' )
990+
991+ else :
992+
993+ self .ax_main .set_title (
994+
995+ f"{ y_col } vs. { x_col } " ,
996+
997+ fontweight = 'bold' )
998+
999+ self .figure .tight_layout ()
1000+
1001+
1002+
1003+ def _handle_load_error (self , filepath , e ):
1004+
1005+ """Handles errors during file loading."""
1006+
1007+ if filepath in self .file_data_cache :
1008+
1009+ self .file_data_cache [filepath ] = {
1010+
1011+ "path" : filepath , "headers" : [], "data" : {}}
1012+
1013+ self .column_source_var .set ("Columns from: (no file selected)" )
1014+
1015+ self .active_filepath = None
1016+
1017+ self .plot_data ()
1018+
1019+ self .x_col_cb .set ('' )
1020+
1021+ self .y_col_cb .set ('' )
1022+
1023+ self .x_col_cb ['values' ] = []
1024+
1025+ self .y_col_cb ['values' ] = []
1026+
1027+ self .live_update_var .set (False )
1028+
1029+ self .toggle_live_update ()
1030+
1031+ self .log (f"Error loading file: { traceback .format_exc ()} " )
1032+
1033+ messagebox .showerror (
1034+
1035+ "File Load Error" ,
1036+
1037+ f"Could not read the data file. It may be empty, malformed, or in use.\n \n Details: { e } " )
1038+
1039+
1040+
1041+ def toggle_live_update (self ):
1042+
1043+ if self .live_update_var .get ():
1044+
1045+ self .start_file_watcher ()
1046+
10321047 else :
1033- # File was overwritten or shrunk, do a full reload
1048+
1049+ self .stop_file_watcher ()
1050+
1051+
1052+
1053+ def start_file_watcher (self ):
1054+
1055+ self .stop_file_watcher () # Ensure no multiple watchers are running
1056+
1057+ if self .live_update_var .get () and self .active_filepath :
1058+
1059+ self .log ("Live update enabled. Watching for file changes..." )
1060+
1061+ self .file_watcher_job = self .root .after (
1062+
1063+ 1000 , self .check_for_updates )
1064+
1065+
1066+
1067+ def stop_file_watcher (self ):
1068+
1069+ if self .file_watcher_job :
1070+
1071+ self .root .after_cancel (self .file_watcher_job )
1072+
1073+ self .file_watcher_job = None
1074+
1075+ self .log ("Live update disabled." )
1076+
1077+
1078+
1079+ def check_for_updates (self ):
1080+
1081+ if not self .active_filepath or not self .live_update_var .get (
1082+
1083+ ) or not os .path .exists (self .active_filepath ):
1084+
1085+ self .file_watcher_job = None # Stop watching if file is gone or disabled
1086+
1087+ return
1088+
1089+
1090+
1091+ try :
1092+
1093+ file_info = self .file_data_cache [self .active_filepath ]
1094+
1095+ mod_time = os .path .getmtime (self .active_filepath )
1096+
1097+ current_size = os .path .getsize (self .active_filepath )
1098+
1099+
1100+
1101+ if mod_time != file_info .get ('mod_time' ):
1102+
1103+ if current_size > file_info .get ('size' , 0 ):
1104+
1105+ # File has grown, append new data
1106+
1107+ self .append_file_data ()
1108+
1109+ else :
1110+
1111+ # File was overwritten or shrunk, do a full reload
1112+
1113+ self .log (
1114+
1115+ "File has been overwritten. Performing full reload..." )
1116+
1117+ self .load_file_data (self .active_filepath )
1118+
1119+ else :
1120+
1121+ # If no changes, schedule the next check
1122+
1123+ self .file_watcher_job = self .root .after (
1124+
1125+ 1000 , self .check_for_updates )
1126+
1127+
1128+
1129+ except OSError :
1130+
1131+ # File might have been deleted or is temporarily inaccessible
1132+
10341133 self .log (
1035- "File has been overwritten. Performing full reload..." )
1036- self .load_file_data (self .active_filepath )
1037- else :
1038- # If no changes, schedule the next check
1039- self .file_watcher_job = self .root .after (
1040- 1000 , self .check_for_updates )
1041-
1042- except OSError :
1043- # File might have been deleted or is temporarily inaccessible
1044- self .log (
1045- "File watcher stopped: file is inaccessible or has been deleted." )
1046- self .stop_file_watcher ()
1134+
1135+ "File watcher stopped: file is inaccessible or has been deleted." )
1136+
1137+ self .stop_file_watcher ()
1138+
1139+
1140+
1141+
1142+
1143+ if __name__ == '__main__' :
1144+
1145+ # This is ESSENTIAL for multiprocessing to work in a bundled executable
1146+
1147+ # and to prevent pickling errors with 'spawn' start method on Windows.
1148+
1149+ multiprocessing .freeze_support ()
1150+
1151+
1152+
1153+ root = tk .Tk ()
1154+
1155+ app = PlotterApp (root )
1156+
1157+ root .mainloop ()
10471158
10481159
10491160if __name__ == '__main__' :
0 commit comments