toucan-plot 0.2.7__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {toucan_plot-0.2.7/src/toucan_plot.egg-info → toucan_plot-0.4.1}/PKG-INFO +19 -19
  2. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/README.md +1 -1
  3. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/pyproject.toml +10 -3
  4. toucan_plot-0.4.1/src/toucan_plot/__init__.py +0 -0
  5. toucan_plot-0.4.1/src/toucan_plot/assets/ico/toucan-plot.png +0 -0
  6. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/main.py +171 -10
  7. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/utils/__init__.py +2 -0
  8. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/utils/loaders.py +38 -0
  9. toucan_plot-0.2.7/LICENSE +0 -21
  10. toucan_plot-0.2.7/PKG-INFO +0 -130
  11. toucan_plot-0.2.7/setup.cfg +0 -4
  12. toucan_plot-0.2.7/src/toucan_plot.egg-info/SOURCES.txt +0 -37
  13. toucan_plot-0.2.7/src/toucan_plot.egg-info/dependency_links.txt +0 -1
  14. toucan_plot-0.2.7/src/toucan_plot.egg-info/entry_points.txt +0 -2
  15. toucan_plot-0.2.7/src/toucan_plot.egg-info/requires.txt +0 -8
  16. toucan_plot-0.2.7/src/toucan_plot.egg-info/top_level.txt +0 -1
  17. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/__main__.py +0 -0
  18. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/adjustments-dark.svg +0 -0
  19. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/adjustments-light.svg +0 -0
  20. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-autofit-height-dark.svg +0 -0
  21. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-autofit-height-light.svg +0 -0
  22. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-narrow-left-dark.svg +0 -0
  23. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-narrow-left-light.svg +0 -0
  24. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-narrow-right-dark.svg +0 -0
  25. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/arrow-narrow-right-light.svg +0 -0
  26. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/device-floppy-dark.svg +0 -0
  27. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/device-floppy-light.svg +0 -0
  28. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/hand-stop-dark.svg +0 -0
  29. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/hand-stop-light.svg +0 -0
  30. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/home-dark.svg +0 -0
  31. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/home-light.svg +0 -0
  32. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/ico/toucan-plot.ico +0 -0
  33. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/layout-grid-add-dark.svg +0 -0
  34. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/layout-grid-add-light.svg +0 -0
  35. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/math-function-dark.svg +0 -0
  36. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/math-function-light.svg +0 -0
  37. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/ruler-measure-dark.svg +0 -0
  38. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/ruler-measure-light.svg +0 -0
  39. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/zoom-dark.svg +0 -0
  40. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/assets/zoom-light.svg +0 -0
  41. {toucan_plot-0.2.7 → toucan_plot-0.4.1}/src/toucan_plot/utils/styles.py +0 -0
@@ -1,22 +1,22 @@
1
- Metadata-Version: 2.4
2
- Name: toucan-plot
3
- Version: 0.2.7
4
- Summary: A versatile PyQt6 and Matplotlib-based plotting tool for CSV and SMV data files.
5
- License-Expression: MIT
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: pyqt6>=6.0
10
- Requires-Dist: matplotlib>=3.0
11
- Requires-Dist: numpy>=1.20
12
- Requires-Dist: pyqtdarktheme>=2.1.0
13
- Requires-Dist: scienceplots>=2.2.1
14
- Requires-Dist: python-can>=4.0
15
- Requires-Dist: cantools>=39.0
16
- Requires-Dist: asammdf>=7.0
17
- Dynamic: license-file
18
-
19
- # Toucan-Plot
1
+ Metadata-Version: 2.4
2
+ Name: toucan-plot
3
+ Version: 0.4.1
4
+ Summary: A versatile PyQt6 and Matplotlib-based plotting tool for CSV and SMV data files.
5
+ License-Expression: MIT
6
+ Requires-Dist: pyqt6>=6.0
7
+ Requires-Dist: matplotlib>=3.0
8
+ Requires-Dist: numpy>=1.20
9
+ Requires-Dist: pyqtdarktheme>=2.1.0
10
+ Requires-Dist: scienceplots>=2.2.1
11
+ Requires-Dist: python-can>=4.0
12
+ Requires-Dist: cantools>=39.0
13
+ Requires-Dist: asammdf>=7.0
14
+ Requires-Dist: pyarrow>=23.0.1
15
+ Requires-Dist: chardet>=7.4.3
16
+ Requires-Python: >=3.13
17
+ Description-Content-Type: text/markdown
18
+
19
+ # TouCAN-Plot
20
20
 
21
21
  An interactive PyQt6 + Matplotlib plotting tool for CSV, SMV, and CAN bus log files.
22
22
 
@@ -1,4 +1,4 @@
1
- # Toucan-Plot
1
+ # TouCAN-Plot
2
2
 
3
3
  An interactive PyQt6 + Matplotlib plotting tool for CSV, SMV, and CAN bus log files.
4
4
 
@@ -1,10 +1,14 @@
1
+ [build-system]
2
+ build-backend = "uv_build"
3
+ requires = ["uv-build>=0.11.14,<0.12"]
4
+
1
5
  [project]
2
6
  name = "toucan-plot"
3
- version = "0.2.7"
7
+ version = "0.4.1"
8
+ requires-python = ">=3.13"
4
9
  description = "A versatile PyQt6 and Matplotlib-based plotting tool for CSV and SMV data files."
5
10
  license = "MIT"
6
11
  readme = { file = "README.md", content-type = "text/markdown" }
7
- requires-python = ">=3.11"
8
12
  dependencies = [
9
13
  "pyqt6>=6.0",
10
14
  "matplotlib>=3.0",
@@ -14,6 +18,8 @@ dependencies = [
14
18
  "python-can>=4.0",
15
19
  "cantools>=39.0",
16
20
  "asammdf>=7.0",
21
+ "pyarrow>=23.0.1",
22
+ "chardet>=7.4.3",
17
23
  ]
18
24
 
19
25
  [project.scripts]
@@ -24,7 +30,8 @@ toucan_plot = ["assets/**/*.svg", "assets/**/*.ico"]
24
30
 
25
31
  [dependency-groups]
26
32
  dev = [
27
- "nuitka>=4.0.7",
33
+ "nuitka[all]>=4.1",
34
+ "uv-build>=0.11.14",
28
35
  "zstandard>=0.25.0",
29
36
  ]
30
37
  lint = [
File without changes
@@ -9,7 +9,7 @@ from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
9
9
  from matplotlib.backends.backend_qt import NavigationToolbar2QT as NavigationToolbar
10
10
  import qdarktheme
11
11
 
12
- from toucan_plot.utils import styles, csv_load_worker, can_log_load_worker, mf4_load_worker
12
+ from toucan_plot.utils import styles, csv_load_worker, can_log_load_worker, mf4_load_worker, feather_load_worker
13
13
 
14
14
  LEGEND_POSITIONS = [
15
15
  'best', 'upper right', 'upper left', 'lower left', 'lower right',
@@ -21,7 +21,7 @@ LEGEND_POSITIONS = [
21
21
  class MainWindow(QtWidgets.QMainWindow):
22
22
  def __init__(self):
23
23
  super().__init__()
24
- self.setWindowTitle('Toucan-Plot')
24
+ self.setWindowTitle('TouCAN-Plot')
25
25
  self.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), 'assets', 'ico', 'toucan-plot.ico')))
26
26
  self.resize(900, 600)
27
27
 
@@ -687,6 +687,7 @@ class MainWindow(QtWidgets.QMainWindow):
687
687
  dbc_files = [p for p in paths if os.path.splitext(p)[1].lower() == '.dbc']
688
688
  csv_files = [p for p in paths if os.path.splitext(p)[1].lower() in ('.csv', '.smv')]
689
689
  mf4_files = [p for p in paths if os.path.splitext(p)[1].lower() in ('.mf4', '.mf4z')]
690
+ feather_files = [p for p in paths if os.path.splitext(p)[1].lower() == '.feather']
690
691
 
691
692
  first_loaded = False
692
693
 
@@ -715,19 +716,27 @@ class MainWindow(QtWidgets.QMainWindow):
715
716
  self._merged_mode = True
716
717
  self._merge_load_mf4(path, os.path.basename(path))
717
718
 
719
+ for path in feather_files:
720
+ if not first_loaded:
721
+ self._load_feather(path)
722
+ first_loaded = True
723
+ else:
724
+ self._merged_mode = True
725
+ self._merge_load_feather(path, os.path.basename(path))
726
+
718
727
  if self._merged_mode and self.series_list:
719
728
  file_names = [f['name'] for f in self._loaded_files]
720
729
  self.update_plot()
721
- self.setWindowTitle(f'Toucan-Plot — Merged: {", ".join(file_names)}')
730
+ self.setWindowTitle(f'TouCAN-Plot — Merged: {", ".join(file_names)}')
722
731
 
723
732
  def open_file(self):
724
- file_filter = "All supported files (*.csv *.smv *.blf *.trc *.asc *.dbc *.mf4 *.mf4z);;CSV files (*.csv);;SMV files (*.smv);;MF4 files (*.mf4 *.mf4z);;CAN files (*.blf *.trc *.asc *.dbc);;All files (*)"
733
+ file_filter = "All supported files (*.csv *.smv *.blf *.trc *.asc *.dbc *.mf4 *.mf4z *.feather);;CSV files (*.csv *.smv);;MF4 files (*.mf4 *.mf4z);;Feather files (*.feather);;CAN files (*.blf *.trc *.asc *.dbc);;All files (*)"
725
734
  paths, _ = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open File', '', file_filter)
726
735
  self.open_files(paths)
727
736
 
728
737
  def merge_file(self):
729
738
  """Open additional files and append their series to the existing plot session."""
730
- file_filter = "All supported files (*.csv *.smv *.blf *.trc *.asc *.dbc *.mf4 *.mf4z);;CSV files (*.csv);;SMV files (*.smv);;MF4 files (*.mf4 *.mf4z);;CAN files (*.blf *.trc *.asc *.dbc);;All files (*)"
739
+ file_filter = "All supported files (*.csv *.smv *.blf *.trc *.asc *.dbc *.mf4 *.mf4z *.feather);;CSV files (*.csv *.smv);;MF4 files (*.mf4 *.mf4z);;Feather files (*.feather);;CAN files (*.blf *.trc *.asc *.dbc);;All files (*)"
731
740
  paths, _ = QtWidgets.QFileDialog.getOpenFileNames(self, 'Merge Files', '', file_filter)
732
741
  if not paths:
733
742
  return
@@ -738,6 +747,7 @@ class MainWindow(QtWidgets.QMainWindow):
738
747
  dbc_files = [p for p in paths if os.path.splitext(p)[1].lower() == '.dbc']
739
748
  csv_files = [p for p in paths if os.path.splitext(p)[1].lower() in ('.csv', '.smv')]
740
749
  mf4_files = [p for p in paths if os.path.splitext(p)[1].lower() in ('.mf4', '.mf4z')]
750
+ feather_files = [p for p in paths if os.path.splitext(p)[1].lower() == '.feather']
741
751
 
742
752
  for path in csv_files:
743
753
  basename = os.path.basename(path)
@@ -752,10 +762,14 @@ class MainWindow(QtWidgets.QMainWindow):
752
762
  basename = os.path.basename(path)
753
763
  self._merge_load_mf4(path, basename)
754
764
 
765
+ for path in feather_files:
766
+ basename = os.path.basename(path)
767
+ self._merge_load_feather(path, basename)
768
+
755
769
  if self.series_list:
756
770
  file_names = [f['name'] for f in self._loaded_files]
757
771
  self.update_plot()
758
- self.setWindowTitle(f'Toucan-Plot — Merged: {", ".join(file_names)}')
772
+ self.setWindowTitle(f'TouCAN-Plot — Merged: {", ".join(file_names)}')
759
773
  self.show_series_selector()
760
774
 
761
775
  def _merge_load_csv(self, path, prefix):
@@ -972,7 +986,7 @@ class MainWindow(QtWidgets.QMainWindow):
972
986
  self._merge_action.setEnabled(True)
973
987
 
974
988
  self.update_plot()
975
- self.setWindowTitle(f'Toucan-Plot — {basename}')
989
+ self.setWindowTitle(f'TouCAN-Plot — {basename}')
976
990
  self.show_series_selector()
977
991
 
978
992
  def _load_blf(self, blf_path, dbc_paths):
@@ -1053,7 +1067,7 @@ class MainWindow(QtWidgets.QMainWindow):
1053
1067
  self._merge_action.setEnabled(True)
1054
1068
 
1055
1069
  self.update_plot()
1056
- self.setWindowTitle(f'Toucan-Plot — {basename}')
1070
+ self.setWindowTitle(f'TouCAN-Plot — {basename}')
1057
1071
  self.show_series_selector()
1058
1072
 
1059
1073
  def _load_mf4(self, path):
@@ -1134,7 +1148,7 @@ class MainWindow(QtWidgets.QMainWindow):
1134
1148
  self._merge_action.setEnabled(True)
1135
1149
 
1136
1150
  self.update_plot()
1137
- self.setWindowTitle(f'Toucan-Plot — {basename}')
1151
+ self.setWindowTitle(f'TouCAN-Plot — {basename}')
1138
1152
  self.show_series_selector()
1139
1153
 
1140
1154
  def _merge_load_mf4(self, path, prefix):
@@ -1203,6 +1217,153 @@ class MainWindow(QtWidgets.QMainWindow):
1203
1217
  file_entry['series_indices'].append(idx)
1204
1218
  self._loaded_files.append(file_entry)
1205
1219
 
1220
+ def _load_feather(self, path):
1221
+ """Load a Feather file using a worker process with live progress."""
1222
+ queue = multiprocessing.Queue()
1223
+ proc = multiprocessing.Process(target=feather_load_worker, args=(path, queue))
1224
+
1225
+ progress = QtWidgets.QProgressDialog(f'Loading {os.path.basename(path)}...', None, 0, 100, self)
1226
+ progress.setWindowTitle('Loading')
1227
+ progress.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
1228
+ progress.setMinimumDuration(0)
1229
+ progress.setValue(0)
1230
+
1231
+ result_data = {}
1232
+
1233
+ def poll():
1234
+ try:
1235
+ while not queue.empty():
1236
+ msg = queue.get_nowait()
1237
+ if msg[0] == 'progress':
1238
+ pct = msg[1]
1239
+ label = msg[2] if len(msg) > 2 else ''
1240
+ if pct >= 0:
1241
+ progress.setValue(pct)
1242
+ if label:
1243
+ progress.setLabelText(label)
1244
+ elif msg[0] == 'result':
1245
+ result_data['data'] = msg[1]
1246
+ progress.setValue(100)
1247
+ progress.close()
1248
+ elif msg[0] == 'error':
1249
+ result_data['error'] = msg[1]
1250
+ progress.close()
1251
+ except Exception:
1252
+ pass
1253
+
1254
+ timer = QtCore.QTimer(progress)
1255
+ timer.timeout.connect(poll)
1256
+ timer.start(50)
1257
+ proc.start()
1258
+ progress.exec()
1259
+ timer.stop()
1260
+ proc.join(timeout=10)
1261
+
1262
+ if 'error' in result_data:
1263
+ QtWidgets.QMessageBox.warning(self, 'Error', result_data['error'])
1264
+ return
1265
+ if 'data' not in result_data:
1266
+ return
1267
+
1268
+ x_col, all_columns = result_data['data']
1269
+ x_array = all_columns[x_col]
1270
+
1271
+ self._x = x_array
1272
+ self._all_columns = all_columns
1273
+ self._x_col_name = x_col
1274
+ self._merged_mode = False
1275
+
1276
+ # Clear existing series and subplots
1277
+ self.series_list.clear()
1278
+ self.subplot_series.clear()
1279
+ self.subplot_series_props.clear()
1280
+ self.subplot_axes_labels.clear()
1281
+ self.subplot_config.clear()
1282
+ self._loaded_files.clear()
1283
+
1284
+ # Build series_list from all_columns
1285
+ basename = os.path.basename(path)
1286
+ file_entry = {'name': basename, 'series_indices': []}
1287
+ for name in all_columns:
1288
+ if name == x_col:
1289
+ continue
1290
+ data = all_columns[name]
1291
+ idx = len(self.series_list)
1292
+ self.series_list.append((name, lambda x, f, _d=data: _d, x_array))
1293
+ file_entry['series_indices'].append(idx)
1294
+ self._loaded_files.append(file_entry)
1295
+ self._merge_action.setEnabled(True)
1296
+
1297
+ self.update_plot()
1298
+ self.setWindowTitle(f'TouCAN-Plot — {basename}')
1299
+ self.show_series_selector()
1300
+
1301
+ def _merge_load_feather(self, path, prefix):
1302
+ """Load a Feather file and append its series (prefixed) to the existing series_list."""
1303
+ queue = multiprocessing.Queue()
1304
+ proc = multiprocessing.Process(target=feather_load_worker, args=(path, queue))
1305
+
1306
+ progress = QtWidgets.QProgressDialog(f'Loading {os.path.basename(path)}...', None, 0, 100, self)
1307
+ progress.setWindowTitle('Loading')
1308
+ progress.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
1309
+ progress.setMinimumDuration(0)
1310
+ progress.setValue(0)
1311
+
1312
+ result_data = {}
1313
+
1314
+ def poll():
1315
+ try:
1316
+ while not queue.empty():
1317
+ msg = queue.get_nowait()
1318
+ if msg[0] == 'progress':
1319
+ pct = msg[1]
1320
+ label = msg[2] if len(msg) > 2 else ''
1321
+ if pct >= 0:
1322
+ progress.setValue(pct)
1323
+ if label:
1324
+ progress.setLabelText(label)
1325
+ elif msg[0] == 'result':
1326
+ result_data['data'] = msg[1]
1327
+ progress.setValue(100)
1328
+ progress.close()
1329
+ elif msg[0] == 'error':
1330
+ result_data['error'] = msg[1]
1331
+ progress.close()
1332
+ except Exception:
1333
+ pass
1334
+
1335
+ timer = QtCore.QTimer(progress)
1336
+ timer.timeout.connect(poll)
1337
+ timer.start(50)
1338
+ proc.start()
1339
+ progress.exec()
1340
+ timer.stop()
1341
+ proc.join(timeout=10)
1342
+
1343
+ if 'error' in result_data:
1344
+ QtWidgets.QMessageBox.warning(self, 'Error', result_data['error'])
1345
+ return
1346
+ if 'data' not in result_data:
1347
+ return
1348
+
1349
+ x_col, all_columns = result_data['data']
1350
+ x_array = all_columns[x_col]
1351
+
1352
+ if len(self.series_list) == 0:
1353
+ self._x = x_array
1354
+ self._x_col_name = x_col
1355
+
1356
+ file_entry = {'name': prefix, 'series_indices': []}
1357
+ for name in all_columns:
1358
+ if name == x_col:
1359
+ continue
1360
+ data = all_columns[name]
1361
+ prefixed_name = f'{prefix}: {name}'
1362
+ idx = len(self.series_list)
1363
+ self.series_list.append((prefixed_name, lambda x, f, _d=data: _d, x_array))
1364
+ file_entry['series_indices'].append(idx)
1365
+ self._loaded_files.append(file_entry)
1366
+
1206
1367
  # ---- Measure cursors ----
1207
1368
 
1208
1369
  def _toggle_measure(self, checked: bool):
@@ -2016,7 +2177,7 @@ def main():
2016
2177
  import argparse
2017
2178
  parser = argparse.ArgumentParser(
2018
2179
  prog='toucan-plot',
2019
- description='Toucan-Plot — Interactive plotting tool for CSV, SMV, and CAN log (BLF/TRC/ASC) files.',
2180
+ description='TouCAN-Plot — Interactive plotting tool for CSV, SMV, and CAN log (BLF/TRC/ASC) files.',
2020
2181
  epilog='BLF, TRC, and ASC files require at least one DBC file for signal decoding.\n'
2021
2182
  'Example: toucan-plot log.blf signals.dbc',
2022
2183
  )
@@ -4,6 +4,7 @@ from .loaders import (
4
4
  csv_load_worker,
5
5
  can_log_load_worker,
6
6
  mf4_load_worker,
7
+ feather_load_worker,
7
8
  )
8
9
 
9
10
  __all__ = [
@@ -11,4 +12,5 @@ __all__ = [
11
12
  "csv_load_worker",
12
13
  "can_log_load_worker",
13
14
  "mf4_load_worker",
15
+ "feather_load_worker",
14
16
  ]
@@ -217,3 +217,41 @@ def mf4_load_worker(path, queue):
217
217
  queue.put(('result', (x_col, all_columns)))
218
218
  except Exception as e:
219
219
  queue.put(('error', str(e)))
220
+
221
+
222
+ def feather_load_worker(path, queue):
223
+ """Multiprocessing worker for Feather loading via pandas. Sends progress via queue."""
224
+ try:
225
+ import pandas as pd
226
+
227
+ queue.put(('progress', 5, 'Reading Feather file...'))
228
+ df = pd.read_feather(path)
229
+
230
+ if df.empty:
231
+ queue.put(('error', 'Feather file is empty or has no data rows.'))
232
+ return
233
+
234
+ queue.put(('progress', 30, 'Building columns...'))
235
+ all_columns = {}
236
+ num_cols = len(df.columns)
237
+
238
+ x_col = None
239
+ for candidate in ('Time', 'timestamp', 'time'):
240
+ if candidate in df.columns:
241
+ x_col = candidate
242
+ break
243
+ if x_col is None:
244
+ x_col = df.columns[0]
245
+
246
+ for col_idx, col_name in enumerate(df.columns):
247
+ try:
248
+ all_columns[col_name] = df[col_name].to_numpy(dtype=float, na_value=np.nan)
249
+ except (ValueError, TypeError):
250
+ continue
251
+ pct = 30 + int(65 * (col_idx + 1) / num_cols)
252
+ queue.put(('progress', pct, f'Processing column {col_idx + 1}/{num_cols}...'))
253
+
254
+ queue.put(('progress', 98, 'Finalizing...'))
255
+ queue.put(('result', (x_col, all_columns)))
256
+ except Exception as e:
257
+ queue.put(('error', str(e)))
toucan_plot-0.2.7/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Claudio Emanoel Barbosa Lima
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,130 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: toucan-plot
3
- Version: 0.2.7
4
- Summary: A versatile PyQt6 and Matplotlib-based plotting tool for CSV and SMV data files.
5
- License-Expression: MIT
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: pyqt6>=6.0
10
- Requires-Dist: matplotlib>=3.0
11
- Requires-Dist: numpy>=1.20
12
- Requires-Dist: pyqtdarktheme>=2.1.0
13
- Requires-Dist: scienceplots>=2.2.1
14
- Requires-Dist: python-can>=4.0
15
- Requires-Dist: cantools>=39.0
16
- Requires-Dist: asammdf>=7.0
17
- Dynamic: license-file
18
-
19
- # Toucan-Plot
20
-
21
- An interactive PyQt6 + Matplotlib plotting tool for CSV, SMV, and CAN bus log files.
22
-
23
- ![Python](https://img.shields.io/badge/python-3.11+-blue)
24
- ![License](https://img.shields.io/badge/license-MIT-green)
25
-
26
- ## Features
27
-
28
- ### Supported File Formats
29
-
30
- | Format | Description |
31
- |------------------|--------------------------------------------------------------|
32
- | `.csv` / `.smv` | Comma or semicolon delimited data (auto-detected) |
33
- | `.mf4` / `.mf4z` | ASAM MDF v4 measurement data (via asammdf) |
34
- | `.blf` | CAN bus binary log |
35
- | `.trc` | CAN bus trace log |
36
- | `.asc` | CAN bus ASCII log |
37
- | `.dbc` | CAN database for signal decoding (required with BLF/TRC/ASC) |
38
-
39
- - Auto-detects `Time` / `timestamp` columns as the default X axis
40
- - CAN signals are forward-fill interpolated and named as `Message.Signal`
41
-
42
- ### Plot Interaction
43
-
44
- - **Shared X axis** across all subplots with linked panning and zooming
45
- - **Secondary Y axis** support per series
46
- - **Double-click** a subplot to edit its series, or **right-click** for a context menu
47
- - **Measure cursors** — two draggable vertical cursors with a live delta table
48
- - **Expression evaluator** — create computed series from math expressions (e.g. `sin("series_A") + "series_B" * 2`)
49
- - **Multi-file merge** — overlay series from different files, each in its own tab
50
- - **X axis switching** — reassign the X column without losing subplot configuration
51
- - **Per-series properties** (label, color, linewidth, linestyle, marker) persisted across all redraws
52
- - Live mouse coordinates in the status bar
53
-
54
- ### Toolbar
55
-
56
- | Button | Action |
57
- |----------------|-----------------------------------------|
58
- | Home | Auto-fit all axes |
59
- | Back / Forward | Navigate view history |
60
- | Pan | Pan mode |
61
- | Zoom | Zoom rectangle mode |
62
- | Customize | Matplotlib per-curve property editor |
63
- | Save | Export figure to file |
64
- | Add subplot | Open the series selector |
65
- | Fit Y | Auto-fit Y axis keeping current X range |
66
- | Measure | Toggle measure cursors |
67
- | X axis | Select X axis column |
68
-
69
- ### Menus
70
-
71
- - **File → Open** (`Ctrl+O`) — open one or more files (multi-select)
72
- - **File → Merge** (`Ctrl+M`) — append files to the current session
73
- - **Style → Customize plot style** — preset, line mode, marker, text size, grid, legend
74
- - **Style → Theme** — Dark / Light (icons swap automatically)
75
-
76
- ### Right-Click Context Menu
77
-
78
- - Add subplot above / below
79
- - Delete subplot
80
- - Legend: show/hide, position (14 options), orientation, text size
81
- - Line style: plot / step
82
-
83
- ### Style Presets
84
-
85
- Four built-in presets: **Default**, **Style 1**, **IEEE**, **Other**.
86
-
87
- ## Installation
88
-
89
- ### From PyPI (recommended)
90
-
91
- Install the released package from PyPI:
92
-
93
- ```shell
94
- pip install toucan-plot
95
- ```
96
-
97
- ## Usage
98
-
99
- ```shell
100
- # Open a CSV file
101
- toucan-plot data.csv
102
-
103
- # Open an MF4 measurement file
104
- toucan-plot recording.mf4
105
-
106
- # Open a CAN log with DBC decoding
107
- toucan-plot recording.blf signals.dbc
108
-
109
- # Open multiple files (merge mode)
110
- toucan-plot file1.csv file2.csv
111
-
112
- # Launch without files
113
- toucan-plot
114
- ```
115
-
116
- ## Requirements
117
-
118
- - Python ≥ 3.11
119
- - PyQt6
120
- - Matplotlib
121
- - NumPy
122
- - pyqtdarktheme
123
- - SciencePlots
124
- - python-can (for CAN log files)
125
- - cantools (for DBC decoding)
126
- - asammdf (for MF4 files)
127
-
128
- ## License
129
-
130
- MIT
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,37 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- src/toucan_plot/__main__.py
5
- src/toucan_plot/main.py
6
- src/toucan_plot.egg-info/PKG-INFO
7
- src/toucan_plot.egg-info/SOURCES.txt
8
- src/toucan_plot.egg-info/dependency_links.txt
9
- src/toucan_plot.egg-info/entry_points.txt
10
- src/toucan_plot.egg-info/requires.txt
11
- src/toucan_plot.egg-info/top_level.txt
12
- src/toucan_plot/assets/adjustments-dark.svg
13
- src/toucan_plot/assets/adjustments-light.svg
14
- src/toucan_plot/assets/arrow-autofit-height-dark.svg
15
- src/toucan_plot/assets/arrow-autofit-height-light.svg
16
- src/toucan_plot/assets/arrow-narrow-left-dark.svg
17
- src/toucan_plot/assets/arrow-narrow-left-light.svg
18
- src/toucan_plot/assets/arrow-narrow-right-dark.svg
19
- src/toucan_plot/assets/arrow-narrow-right-light.svg
20
- src/toucan_plot/assets/device-floppy-dark.svg
21
- src/toucan_plot/assets/device-floppy-light.svg
22
- src/toucan_plot/assets/hand-stop-dark.svg
23
- src/toucan_plot/assets/hand-stop-light.svg
24
- src/toucan_plot/assets/home-dark.svg
25
- src/toucan_plot/assets/home-light.svg
26
- src/toucan_plot/assets/layout-grid-add-dark.svg
27
- src/toucan_plot/assets/layout-grid-add-light.svg
28
- src/toucan_plot/assets/math-function-dark.svg
29
- src/toucan_plot/assets/math-function-light.svg
30
- src/toucan_plot/assets/ruler-measure-dark.svg
31
- src/toucan_plot/assets/ruler-measure-light.svg
32
- src/toucan_plot/assets/zoom-dark.svg
33
- src/toucan_plot/assets/zoom-light.svg
34
- src/toucan_plot/assets/ico/toucan-plot.ico
35
- src/toucan_plot/utils/__init__.py
36
- src/toucan_plot/utils/loaders.py
37
- src/toucan_plot/utils/styles.py
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- toucan-plot = toucan_plot.main:main
@@ -1,8 +0,0 @@
1
- pyqt6>=6.0
2
- matplotlib>=3.0
3
- numpy>=1.20
4
- pyqtdarktheme>=2.1.0
5
- scienceplots>=2.2.1
6
- python-can>=4.0
7
- cantools>=39.0
8
- asammdf>=7.0
@@ -1 +0,0 @@
1
- toucan_plot