batplot 1.8.0__py3-none-any.whl → 1.8.2__py3-none-any.whl

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.

Potentially problematic release.


This version of batplot might be problematic. Click here for more details.

Files changed (42) hide show
  1. batplot/__init__.py +1 -1
  2. batplot/args.py +5 -3
  3. batplot/batplot.py +44 -4
  4. batplot/cpc_interactive.py +96 -3
  5. batplot/electrochem_interactive.py +28 -0
  6. batplot/interactive.py +18 -2
  7. batplot/modes.py +12 -12
  8. batplot/operando.py +2 -0
  9. batplot/operando_ec_interactive.py +112 -11
  10. batplot/session.py +35 -1
  11. batplot/utils.py +40 -0
  12. batplot/version_check.py +85 -6
  13. {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/METADATA +1 -1
  14. batplot-1.8.2.dist-info/RECORD +75 -0
  15. {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/top_level.txt +1 -0
  16. batplot_backup_20251221_101150/__init__.py +5 -0
  17. batplot_backup_20251221_101150/args.py +625 -0
  18. batplot_backup_20251221_101150/batch.py +1176 -0
  19. batplot_backup_20251221_101150/batplot.py +3589 -0
  20. batplot_backup_20251221_101150/cif.py +823 -0
  21. batplot_backup_20251221_101150/cli.py +149 -0
  22. batplot_backup_20251221_101150/color_utils.py +547 -0
  23. batplot_backup_20251221_101150/config.py +198 -0
  24. batplot_backup_20251221_101150/converters.py +204 -0
  25. batplot_backup_20251221_101150/cpc_interactive.py +4409 -0
  26. batplot_backup_20251221_101150/electrochem_interactive.py +4520 -0
  27. batplot_backup_20251221_101150/interactive.py +3894 -0
  28. batplot_backup_20251221_101150/manual.py +323 -0
  29. batplot_backup_20251221_101150/modes.py +799 -0
  30. batplot_backup_20251221_101150/operando.py +603 -0
  31. batplot_backup_20251221_101150/operando_ec_interactive.py +5487 -0
  32. batplot_backup_20251221_101150/plotting.py +228 -0
  33. batplot_backup_20251221_101150/readers.py +2607 -0
  34. batplot_backup_20251221_101150/session.py +2951 -0
  35. batplot_backup_20251221_101150/style.py +1441 -0
  36. batplot_backup_20251221_101150/ui.py +790 -0
  37. batplot_backup_20251221_101150/utils.py +1046 -0
  38. batplot_backup_20251221_101150/version_check.py +253 -0
  39. batplot-1.8.0.dist-info/RECORD +0 -52
  40. {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/WHEEL +0 -0
  41. {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/entry_points.txt +0 -0
  42. {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -39,7 +39,7 @@ from .color_utils import (
39
39
  resolve_color_token,
40
40
  manage_user_colors,
41
41
  )
42
- from .utils import choose_style_file
42
+ from .utils import choose_style_file, convert_label_shortcuts
43
43
 
44
44
 
45
45
  class _FilterIMKWarning:
@@ -376,6 +376,71 @@ def _update_custom_colorbar(cbar_ax, im=None, label=None, label_mode=None):
376
376
  _draw_custom_colorbar(cbar_ax, im, label, label_mode)
377
377
 
378
378
 
379
+ def _safe_set_clim(im, vmin, vmax):
380
+ """Safely set color limits without triggering matplotlib colorbar callbacks.
381
+
382
+ This wrapper around im.set_clim() prevents the NotImplementedError: cannot remove artist
383
+ error when using custom colorbars. The issue occurs because matplotlib's callback system
384
+ tries to update the colorbar when set_clim() is called, but our custom colorbar drawing
385
+ has already cleared the axes, causing the update to fail.
386
+
387
+ This function suppresses the callback traceback by redirecting stderr to a null device
388
+ during the set_clim() call, preventing matplotlib's callback system from printing
389
+ tracebacks to the terminal.
390
+
391
+ Args:
392
+ im: AxesImage object
393
+ vmin: Minimum value for color scale
394
+ vmax: Maximum value for color scale
395
+ """
396
+ import sys
397
+ import os
398
+ from io import StringIO
399
+
400
+ # Create a null device for stderr redirection
401
+ class NullDevice:
402
+ def write(self, s):
403
+ pass
404
+ def flush(self):
405
+ pass
406
+ def close(self):
407
+ pass
408
+
409
+ # Suppress matplotlib's exception printing by redirecting stderr AND excepthook
410
+ old_stderr = sys.stderr
411
+ old_excepthook = sys.excepthook
412
+ null_dev = NullDevice()
413
+
414
+ # Create a no-op excepthook that suppresses all exceptions
415
+ def suppress_excepthook(exc_type, exc_value, exc_traceback):
416
+ # Only suppress if it's the specific error we're looking for
417
+ if exc_type == NotImplementedError and 'cannot remove artist' in str(exc_value).lower():
418
+ return # Suppress this specific exception
419
+ # For any other exception, use the original handler
420
+ old_excepthook(exc_type, exc_value, exc_traceback)
421
+
422
+ sys.stderr = null_dev
423
+ sys.excepthook = suppress_excepthook
424
+
425
+ try:
426
+ # Set the color limits - matplotlib's callback will try to print traceback
427
+ # but both stderr and excepthook are suppressed
428
+ im.set_clim(vmin, vmax)
429
+ # The operation succeeds; any tracebacks are suppressed
430
+ except NotImplementedError:
431
+ # Suppress the specific error - color limits were still updated successfully
432
+ pass
433
+ except Exception:
434
+ # For any other unexpected error, restore handlers and re-raise
435
+ sys.stderr = old_stderr
436
+ sys.excepthook = old_excepthook
437
+ raise
438
+ finally:
439
+ # Always restore both stderr and excepthook
440
+ sys.stderr = old_stderr
441
+ sys.excepthook = old_excepthook
442
+
443
+
379
444
  def _detach_mpl_colorbar_callbacks(cbar, im) -> None:
380
445
  """Detach a Matplotlib Colorbar from its mappable callbacks.
381
446
 
@@ -737,7 +802,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
737
802
 
738
803
  if intensity_max > intensity_min:
739
804
  # Update color limits
740
- im.set_clim(intensity_min, intensity_max)
805
+ _safe_set_clim(im, intensity_min, intensity_max)
741
806
 
742
807
  # Update colorbar if available
743
808
  try:
@@ -1111,7 +1176,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
1111
1176
  clim = im.get_clim()
1112
1177
  except Exception:
1113
1178
  clim = None
1114
- cmap_name = getattr(im.get_cmap(), 'name', None)
1179
+ # Get colormap name: first check if we stored it explicitly, otherwise try to get from colormap object
1180
+ cmap_name = getattr(im, '_operando_cmap_name', None)
1181
+ if cmap_name is None:
1182
+ cmap_name = getattr(im.get_cmap(), 'name', None)
1115
1183
  # EC mode and caches (only if ec_ax exists)
1116
1184
  if ec_ax is not None:
1117
1185
  mode = getattr(ec_ax, '_ec_y_mode', 'time')
@@ -1325,12 +1393,15 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
1325
1393
  cbar.solids = None
1326
1394
  except Exception:
1327
1395
  pass
1328
- lo, hi = snap['clim']; im.set_clim(float(lo), float(hi))
1396
+ lo, hi = snap['clim']; _safe_set_clim(im, float(lo), float(hi))
1329
1397
  except Exception:
1330
1398
  pass
1331
1399
  try:
1332
1400
  if snap.get('cmap'):
1333
- im.set_cmap(snap['cmap'])
1401
+ cmap_name = snap['cmap']
1402
+ im.set_cmap(cmap_name)
1403
+ # Store the colormap name explicitly so it can be retrieved reliably when saving
1404
+ setattr(im, '_operando_cmap_name', cmap_name)
1334
1405
  if cbar is not None:
1335
1406
  _update_custom_colorbar(cbar.ax, im)
1336
1407
  except Exception:
@@ -2023,8 +2094,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
2023
2094
  continue
2024
2095
  except ValueError:
2025
2096
  print("Invalid input. Enter a number.")
2097
+ continue
2026
2098
  except Exception as e:
2027
2099
  print(f"Error: {e}")
2100
+ continue
2028
2101
  elif sub == 'e':
2029
2102
  if ec_ax is None:
2030
2103
  print("EC panel not available.")
@@ -2042,10 +2115,13 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
2042
2115
  continue
2043
2116
  except ValueError:
2044
2117
  print("Invalid input. Enter a number.")
2118
+ continue
2045
2119
  except Exception as e:
2046
2120
  print(f"Error: {e}")
2121
+ continue
2047
2122
  else:
2048
2123
  print("Invalid choice.")
2124
+ continue
2049
2125
  elif choice != 'q':
2050
2126
  print("Invalid choice")
2051
2127
  else:
@@ -2107,10 +2183,13 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
2107
2183
  continue
2108
2184
  except ValueError:
2109
2185
  print("Invalid input. Enter a number.")
2186
+ continue
2110
2187
  except Exception as e:
2111
2188
  print(f"Error: {e}")
2189
+ continue
2112
2190
  else:
2113
2191
  print("Invalid choice.")
2192
+ continue
2114
2193
  elif choice != 'q':
2115
2194
  print("Invalid choice")
2116
2195
  fig.canvas.draw_idle()
@@ -3280,7 +3359,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3280
3359
  print("Invalid value, ignored.")
3281
3360
  continue
3282
3361
  _snapshot("operando-intensity-range")
3283
- im.set_clim(cur[0], new_upper)
3362
+ _safe_set_clim(im, cur[0], new_upper)
3284
3363
  try:
3285
3364
  if cbar is not None:
3286
3365
  _update_custom_colorbar(cbar.ax, im)
@@ -3306,7 +3385,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3306
3385
  print("Invalid value, ignored.")
3307
3386
  continue
3308
3387
  _snapshot("operando-intensity-range")
3309
- im.set_clim(new_lower, cur[1])
3388
+ _safe_set_clim(im, new_lower, cur[1])
3310
3389
  try:
3311
3390
  if cbar is not None:
3312
3391
  _update_custom_colorbar(cbar.ax, im)
@@ -3320,7 +3399,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3320
3399
  if line.lower() == 'a':
3321
3400
  # Apply auto-normalization to visible data
3322
3401
  if auto_available:
3323
- im.set_clim(auto_lo, auto_hi)
3402
+ _safe_set_clim(im, auto_lo, auto_hi)
3324
3403
  try:
3325
3404
  if cbar is not None:
3326
3405
  _update_custom_colorbar(cbar.ax, im)
@@ -3332,7 +3411,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3332
3411
  print("Auto-fit unavailable: no finite data in visible area")
3333
3412
  else:
3334
3413
  lo, hi = map(float, line.split())
3335
- im.set_clim(lo, hi)
3414
+ _safe_set_clim(im, lo, hi)
3336
3415
  try:
3337
3416
  if cbar is not None:
3338
3417
  _update_custom_colorbar(cbar.ax, im)
@@ -3381,6 +3460,15 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3381
3460
  print_menu()
3382
3461
  elif cmd == 'oc':
3383
3462
  # Change operando colormap (perceptually uniform suggestions)
3463
+ # Show current colormap if one is applied
3464
+ try:
3465
+ current_cmap = getattr(im, '_operando_cmap_name', None)
3466
+ if current_cmap is None:
3467
+ current_cmap = getattr(im.get_cmap(), 'name', None)
3468
+ if current_cmap:
3469
+ print(f"Current operando colormap: {current_cmap}")
3470
+ except Exception:
3471
+ pass
3384
3472
  def _refresh_available():
3385
3473
  return set(name.lower() for name in plt.colormaps())
3386
3474
  available = _refresh_available()
@@ -3453,6 +3541,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3453
3541
  if reversed_choice:
3454
3542
  palette_obj = palette_obj.reversed()
3455
3543
  im.set_cmap(palette_obj)
3544
+ # Store the colormap name explicitly so it can be retrieved reliably when saving
3545
+ setattr(im, '_operando_cmap_name', choice)
3456
3546
  try:
3457
3547
  # Update custom colorbar with new colormap
3458
3548
  if cbar is not None:
@@ -3489,7 +3579,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3489
3579
  cb_w_in, cb_gap_in, ec_gap_in, ec_w_in, ax_w_in, ax_h_in = _ensure_fixed_params(fig, ax, cbar.ax, ec_ax)
3490
3580
  fam = plt.rcParams.get('font.sans-serif', [''])[0]
3491
3581
  fsize = plt.rcParams.get('font.size', None)
3492
- cmap_name = getattr(im.get_cmap(), 'name', None)
3582
+ # Get colormap name: first check if we stored it explicitly, otherwise try to get from colormap object
3583
+ cmap_name = getattr(im, '_operando_cmap_name', None)
3584
+ if cmap_name is None:
3585
+ cmap_name = getattr(im.get_cmap(), 'name', None)
3493
3586
  cb_vis = bool(cbar.ax.get_visible())
3494
3587
  ec_vis = bool(ec_ax.get_visible()) if ec_ax is not None else None
3495
3588
  cb_label_text = str(getattr(cbar.ax, '_colorbar_label', cbar.ax.get_ylabel() or 'Intensity'))
@@ -4056,6 +4149,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4056
4149
  if cmap:
4057
4150
  try:
4058
4151
  im.set_cmap(cmap)
4152
+ # Store the colormap name explicitly so it can be retrieved reliably when saving
4153
+ setattr(im, '_operando_cmap_name', cmap)
4059
4154
  if cbar is not None:
4060
4155
  _update_custom_colorbar(cbar.ax, im)
4061
4156
  except Exception:
@@ -4329,7 +4424,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4329
4424
  try:
4330
4425
  intensity_range = op.get('intensity_range')
4331
4426
  if intensity_range and isinstance(intensity_range, (list, tuple)) and len(intensity_range) == 2:
4332
- im.set_clim(float(intensity_range[0]), float(intensity_range[1]))
4427
+ _safe_set_clim(im, float(intensity_range[0]), float(intensity_range[1]))
4333
4428
  print(f"Applied intensity range: {intensity_range[0]:.4g} to {intensity_range[1]:.4g}")
4334
4429
  except Exception as e:
4335
4430
  print(f"Warning: Could not apply intensity range: {e}")
@@ -4623,6 +4718,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4623
4718
  print("Tip: Use LaTeX/mathtext for special characters:")
4624
4719
  print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
4625
4720
  print(" Bullet: $\\bullet$ → • | Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
4721
+ print(" Shortcuts: g{super(-1)} → g$^{\\mathrm{-1}}$ | Li{sub(2)}O → Li$_{\\mathrm{2}}$O")
4626
4722
  while True:
4627
4723
  sub = _safe_input("or> ").strip().lower()
4628
4724
  if not sub:
@@ -4633,6 +4729,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4633
4729
  cur = ax.get_xlabel() or ''
4634
4730
  lab = _safe_input(f"New operando X label (blank=cancel, current='{cur}'): ")
4635
4731
  if lab:
4732
+ lab = convert_label_shortcuts(lab)
4636
4733
  _snapshot("rename-op-x")
4637
4734
  try:
4638
4735
  ax.set_xlabel(lab)
@@ -4646,6 +4743,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4646
4743
  cur = ax.get_ylabel() or ''
4647
4744
  lab = _safe_input(f"New operando Y label (blank=cancel, current='{cur}'): ")
4648
4745
  if lab:
4746
+ lab = convert_label_shortcuts(lab)
4649
4747
  _snapshot("rename-op-y")
4650
4748
  try:
4651
4749
  ax.set_ylabel(lab)
@@ -4675,6 +4773,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4675
4773
  print("Tip: Use LaTeX/mathtext for special characters:")
4676
4774
  print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
4677
4775
  print(" Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
4776
+ print(" Shortcuts: g{super(-1)} → g$^{\\mathrm{-1}}$ | Li{sub(2)}O → Li$_{\\mathrm{2}}$O")
4678
4777
  while True:
4679
4778
  sub = _safe_input("er> ").strip().lower()
4680
4779
  if not sub:
@@ -4685,6 +4784,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4685
4784
  cur = ec_ax.get_xlabel() or ''
4686
4785
  lab = _safe_input(f"New EC X label (blank=cancel, current='{cur}'): ")
4687
4786
  if lab:
4787
+ lab = convert_label_shortcuts(lab)
4688
4788
  _snapshot("rename-ec-x")
4689
4789
  try:
4690
4790
  ec_ax.set_xlabel(lab)
@@ -4697,6 +4797,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
4697
4797
  cur = ec_ax.get_ylabel() or ''
4698
4798
  lab = _safe_input(f"New EC Y label (blank=cancel, current='{cur}'): ")
4699
4799
  if lab:
4800
+ lab = convert_label_shortcuts(lab)
4700
4801
  _snapshot("rename-ec-y")
4701
4802
  try:
4702
4803
  ec_ax.set_ylabel(lab)
batplot/session.py CHANGED
@@ -578,7 +578,10 @@ def dump_operando_session(
578
578
  # Use masked arrays to preserve NaNs if present
579
579
  data = _np.array(arr) # preserves mask where possible
580
580
  extent = tuple(map(float, im.get_extent())) if hasattr(im, 'get_extent') else None
581
- cmap_name = getattr(im.get_cmap(), 'name', None)
581
+ # Get colormap name: first check if we stored it explicitly, otherwise try to get from colormap object
582
+ cmap_name = getattr(im, '_operando_cmap_name', None)
583
+ if cmap_name is None:
584
+ cmap_name = getattr(im.get_cmap(), 'name', None)
582
585
  clim = tuple(map(float, im.get_clim())) if hasattr(im, 'get_clim') else None
583
586
  origin = getattr(im, 'origin', 'upper')
584
587
  interpolation = getattr(im, 'get_interpolation', lambda: None)() or 'nearest'
@@ -870,6 +873,8 @@ def load_operando_session(filename: str):
870
873
  cmap_name = 'viridis'
871
874
  im = ax.imshow(arr, aspect='auto', origin=op.get('origin', 'upper'), extent=extent,
872
875
  cmap=cmap_name, interpolation=op.get('interpolation', 'nearest'))
876
+ # Store the colormap name explicitly so it can be retrieved reliably when saving
877
+ setattr(im, '_operando_cmap_name', cmap_name)
873
878
  if op.get('clim'):
874
879
  try:
875
880
  im.set_clim(*op['clim'])
@@ -1315,6 +1320,18 @@ def load_operando_session(filename: str):
1315
1320
  elif ec_h_offset is not None:
1316
1321
  # EC panel doesn't exist but offset was saved - ignore it
1317
1322
  pass
1323
+
1324
+ # Apply layout with loaded offsets to ensure visual position matches saved position
1325
+ # This must happen after all offsets and geometry parameters are set
1326
+ try:
1327
+ from .operando_ec_interactive import _apply_group_layout_inches, _ensure_fixed_params
1328
+ # Get current geometry parameters (which should match what was just loaded)
1329
+ cb_w_i, cb_gap_i, ec_gap_i, ec_w_i, ax_w_i, ax_h_i = _ensure_fixed_params(fig, ax, cbar_ax, ec_ax)
1330
+ # Apply layout with loaded offsets (offsets are already set as attributes above)
1331
+ _apply_group_layout_inches(fig, ax, cbar_ax, ec_ax, ax_w_i, ax_h_i, cb_w_i, cb_gap_i, ec_gap_i, ec_w_i)
1332
+ except Exception:
1333
+ # If layout application fails, continue - better to have a slightly wrong layout than crash
1334
+ pass
1318
1335
  except Exception:
1319
1336
  pass
1320
1337
 
@@ -2811,6 +2828,23 @@ def load_cpc_session(filename: str):
2811
2828
  ax2.yaxis.set_minor_locator(AutoMinorLocator())
2812
2829
  ax2.yaxis.set_minor_formatter(NullFormatter())
2813
2830
  ax2.tick_params(axis='y', which='minor', right=True)
2831
+ # Store tick_state on axes for interactive menu
2832
+ tick_state = {}
2833
+ for side_key, prefix in [('top', 't'), ('bottom', 'b'), ('left', 'l'), ('right', 'r')]:
2834
+ s = wasd_state.get(side_key, {})
2835
+ tick_state[f'{prefix}_ticks'] = bool(s.get('ticks', side_key in ('bottom', 'left')))
2836
+ tick_state[f'{prefix}_labels'] = bool(s.get('labels', side_key in ('bottom', 'left')))
2837
+ tick_state[f'm{prefix}x' if prefix in 'tb' else f'm{prefix}y'] = bool(s.get('minor', False))
2838
+ # Legacy keys
2839
+ tick_state['bx'] = tick_state.get('b_ticks', True)
2840
+ tick_state['tx'] = tick_state.get('t_ticks', False)
2841
+ tick_state['ly'] = tick_state.get('l_ticks', True)
2842
+ tick_state['ry'] = tick_state.get('r_ticks', True) # CPC has right axis
2843
+ tick_state['mbx'] = tick_state.get('mbx', False)
2844
+ tick_state['mtx'] = tick_state.get('mtx', False)
2845
+ tick_state['mly'] = tick_state.get('mly', False)
2846
+ tick_state['mry'] = tick_state.get('mry', False)
2847
+ ax._saved_tick_state = tick_state
2814
2848
  except Exception:
2815
2849
  pass
2816
2850
 
batplot/utils.py CHANGED
@@ -540,6 +540,46 @@ def list_files_in_subdirectory(extensions: tuple, file_type: str, base_path: str
540
540
  return sorted(files, key=lambda x: x[0])
541
541
 
542
542
 
543
+ def convert_label_shortcuts(text: str) -> str:
544
+ """Convert shortcut syntax to LaTeX format for labels.
545
+
546
+ Converts {super(...)} and {sub(...)} shortcuts to LaTeX superscript/subscript format.
547
+ This allows easier input of mathematical notation without typing full LaTeX.
548
+
549
+ Args:
550
+ text: Label text that may contain {super(...)} or {sub(...)} shortcuts
551
+
552
+ Returns:
553
+ Text with shortcuts converted to LaTeX format (uses \\mathrm{} to prevent italic rendering).
554
+
555
+ Examples:
556
+ >>> convert_label_shortcuts("g{super(-1)}")
557
+ "g$^{\\mathrm{-1}}$"
558
+ >>> convert_label_shortcuts("Li{sub(2)}FeSeO")
559
+ "Li$_{\\mathrm{2}}$FeSeO"
560
+ >>> convert_label_shortcuts("H{sub(2)}O")
561
+ "H$_{\\mathrm{2}}$O"
562
+ """
563
+ if not text:
564
+ return text
565
+
566
+ import re
567
+
568
+ # Convert {super(...)} to $^{\mathrm{...}}$ to prevent italic rendering
569
+ # Pattern matches {super(anything inside)}
570
+ # Use \mathrm{} to ensure non-italic rendering unless explicitly specified
571
+ # Need to escape backslashes in replacement string for LaTeX commands
572
+ text = re.sub(r'\{super\(([^)]+)\)\}', r'$^{\\mathrm{\1}}$', text)
573
+
574
+ # Convert {sub(...)} to $_{\mathrm{...}}$ to prevent italic rendering
575
+ # Pattern matches {sub(anything inside)}
576
+ # Use \mathrm{} to ensure non-italic rendering unless explicitly specified
577
+ # Need to escape backslashes in replacement string for LaTeX commands
578
+ text = re.sub(r'\{sub\(([^)]+)\)\}', r'$_{\\mathrm{\1}}$', text)
579
+
580
+ return text
581
+
582
+
543
583
  def normalize_label_text(text: str) -> str:
544
584
  """Normalize axis label text for proper matplotlib rendering.
545
585
 
batplot/version_check.py CHANGED
@@ -53,6 +53,54 @@ import time
53
53
  from pathlib import Path
54
54
  from typing import Optional, Tuple
55
55
 
56
+ # ====================================================================================
57
+ # UPDATE INFO CONFIGURATION
58
+ # ====================================================================================
59
+ # Edit this section to customize update notification messages and add update info.
60
+ #
61
+ # HOW TO USE:
62
+ # ----------
63
+ # When releasing a new version, edit the UPDATE_INFO dictionary below to include
64
+ # information about what's new or important in the update. This information will
65
+ # be displayed to users when they run batplot and a newer version is available.
66
+ #
67
+ # EXAMPLE:
68
+ # --------
69
+ # UPDATE_INFO = {
70
+ # 'custom_message': "This update includes important bug fixes and new features.",
71
+ # 'update_notes': [
72
+ # "- Fixed colormap preservation issue in session files",
73
+ # "- Improved legend positioning when toggling visibility",
74
+ # "- Added superscript/subscript shortcuts for labels",
75
+ # "- Enhanced version check notifications"
76
+ # ],
77
+ # 'show_update_notes': True,
78
+ # }
79
+ #
80
+ # To disable custom messages, set 'custom_message' to None.
81
+ # To disable update notes, set 'update_notes' to None or an empty list [].
82
+ # ====================================================================================
83
+
84
+ UPDATE_INFO = {
85
+ # Custom message to include in update notification
86
+ # Set to None or empty string to disable
87
+ # This will be displayed as an additional line in the update message box
88
+ 'custom_message': "This update includes important bug fixes", # Example: "This update includes important bug fixes."
89
+
90
+ # Additional notes about the update (list of strings)
91
+ # Set to None or empty list [] to disable
92
+ # Each item in the list will be displayed as a separate line
93
+ 'update_notes': None, # Example: ["- Fixed colormap preservation issue", "- Improved legend positioning"]
94
+
95
+ # Whether to show update notes if provided
96
+ # Set to False to hide update notes even if they are defined
97
+ 'show_update_notes': True,
98
+ }
99
+
100
+ # ====================================================================================
101
+ # END OF UPDATE INFO CONFIGURATION
102
+ # ====================================================================================
103
+
56
104
 
57
105
  def get_cache_file() -> Path:
58
106
  """Get the path to the version check cache file."""
@@ -160,12 +208,43 @@ def _print_update_message(current: str, latest: str) -> None:
160
208
  current: Current version
161
209
  latest: Latest available version
162
210
  """
163
- print(f"\n\033[93m╭{'─' * 68}╮\033[0m")
164
- print(f"\033[93m│\033[0m \033[1mA new version of batplot is available!\033[0m" + " " * 31 + "\033[93m│\033[0m")
165
- print(f"\033[93m│\033[0m Current: \033[91m{current}\033[0m → Latest: \033[92m{latest}\033[0m" + " " * (42 - len(current) - len(latest)) + "\033[93m│\033[0m")
166
- print(f"\033[93m│\033[0m Update with: \033[96mpip install --upgrade batplot\033[0m" + " " * 20 + "\033[93m│\033[0m")
167
- print(f"\033[93m│\033[0m To disable this check: \033[96mexport BATPLOT_NO_VERSION_CHECK=1\033[0m" + " " * 7 + "\033[93m│\033[0m")
168
- print(f"\033[93m╰{'─' * 68}╯\033[0m\n")
211
+ # Calculate box width (minimum 68, expand if needed for longer messages)
212
+ box_width = 68
213
+ custom_msg = UPDATE_INFO.get('Fixed some bugs')
214
+ update_notes = UPDATE_INFO.get('update_notes')
215
+ show_notes = UPDATE_INFO.get('show_update_notes', True)
216
+
217
+ # Calculate required width based on content
218
+ max_line_len = 68 # Default minimum width
219
+ if custom_msg:
220
+ max_line_len = max(max_line_len, len(custom_msg) + 4)
221
+ if update_notes and show_notes:
222
+ for note in update_notes:
223
+ max_line_len = max(max_line_len, len(note) + 4)
224
+ # Ensure box width is at least the calculated width
225
+ box_width = max(68, min(max_line_len, 100)) # Cap at 100 for readability
226
+
227
+ print(f"\n\033[93m╭{'─' * box_width}╮\033[0m")
228
+ print(f"\033[93m│\033[0m \033[1mA new version of batplot is available!\033[0m" + " " * max(0, box_width - 34) + "\033[93m│\033[0m")
229
+ print(f"\033[93m│\033[0m Current: \033[91m{current}\033[0m → Latest: \033[92m{latest}\033[0m" + " " * max(0, box_width - 20 - len(current) - len(latest)) + "\033[93m│\033[0m")
230
+
231
+ # Add custom message if provided
232
+ if custom_msg and custom_msg.strip():
233
+ # Truncate if too long to fit in box
234
+ msg = custom_msg[:box_width - 6] if len(custom_msg) > box_width - 6 else custom_msg
235
+ print(f"\033[93m│\033[0m {msg}" + " " * max(0, box_width - len(msg) - 4) + "\033[93m│\033[0m")
236
+
237
+ # Add update notes if provided
238
+ if update_notes and show_notes and isinstance(update_notes, list):
239
+ for note in update_notes:
240
+ if note and note.strip():
241
+ # Truncate if too long to fit in box
242
+ note_text = note[:box_width - 6] if len(note) > box_width - 6 else note
243
+ print(f"\033[93m│\033[0m {note_text}" + " " * max(0, box_width - len(note_text) - 4) + "\033[93m│\033[0m")
244
+
245
+ print(f"\033[93m│\033[0m Update with: \033[96mpip install --upgrade batplot\033[0m" + " " * max(0, box_width - 34) + "\033[93m│\033[0m")
246
+ print(f"\033[93m│\033[0m To disable this check: \033[96mexport BATPLOT_NO_VERSION_CHECK=1\033[0m" + " " * max(0, box_width - 45) + "\033[93m│\033[0m")
247
+ print(f"\033[93m╰{'─' * box_width}╯\033[0m\n")
169
248
 
170
249
 
171
250
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.8.0
3
+ Version: 1.8.2
4
4
  Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
5
5
  Author-email: Tian Dai <tianda@uio.no>
6
6
  License: MIT License
@@ -0,0 +1,75 @@
1
+ batplot/__init__.py,sha256=cmYE6SKId-NqANj2k5EUEXFUnzP58FNBG7XRF6CLfgQ,118
2
+ batplot/args.py,sha256=mrDjMURp_OQnXrAwl9WnE_FW4HJu940O7NmL9QWQnD4,35189
3
+ batplot/batch.py,sha256=YQ7obCIqLCObwDbM7TXpOBh7g7BO95wZNsa2Fy84c6o,53858
4
+ batplot/batplot.py,sha256=Avb0Txs-VaG1UU-bL-7HWnZf_8RS_n-KTnZz9O_3GPA,174374
5
+ batplot/cif.py,sha256=JfHwNf3SHrcpALc_F5NjJmQ3lg71MBRSaIUJjGYPTx8,30120
6
+ batplot/cli.py,sha256=ScDb2je8VQ0mz_z0SLCHEigiTuFPY5pb1snnzCouKms,5828
7
+ batplot/color_utils.py,sha256=7InQLVo1XTg7sgAbltM2KeDSFJgr787YEaV9vJbIoWY,20460
8
+ batplot/config.py,sha256=6nGY7fKN4T5KZUGQS2ArUBgEkLAL0j37XwG5SCVQgKA,6420
9
+ batplot/converters.py,sha256=rR2WMPM0nR5E3eZI3gWbaJf_AfbdQx3urVSbJmZXNzo,8237
10
+ batplot/cpc_interactive.py,sha256=r70RVu75QAPEWkK_bR4c3MWDXNSwHYxOAYGEVf0TgUg,238755
11
+ batplot/electrochem_interactive.py,sha256=ti7V8BoAxUk4BD_vDRKAu5ydlHMl75htLvdVYFUUVsw,221778
12
+ batplot/interactive.py,sha256=2hQrvV-7VEz0cscIb3Bc7qQ9MpRxP4hjZ6raEnpl5yg,206779
13
+ batplot/manual.py,sha256=pbRI6G4Pm12pOW8LrOLWWu7IEOtqWN3tRHtgge50LlA,11556
14
+ batplot/modes.py,sha256=qE2OsOQQKhwOWene5zxJeuuewTrZxubtahQuz5je7ok,37252
15
+ batplot/operando.py,sha256=p2Ug1mFUQxaU702cTBGgJKb3_v1C2p3LLUwfXaVBpPY,28311
16
+ batplot/operando_ec_interactive.py,sha256=8GQ47-I8SLTS88sFEk8m3vDxFEjSfD3hao62Qke7SxA,305137
17
+ batplot/plotting.py,sha256=hG2_EdDhF1Qpn1XfZKdCQ5-w_m9gUYFbr804UQ5QjsU,10841
18
+ batplot/readers.py,sha256=kAI0AvYrdfGRZkvADJ4riN96IWtrH24aAoZpBtONTbw,112960
19
+ batplot/session.py,sha256=WuBZR-HKDr4VlqjwynyK8gCkx6xLoHzNaGBH948p9jU,135638
20
+ batplot/style.py,sha256=ig1ozX4dhEsXf5JKaPZOvgVS3CWx-BTFSc3vfAH3Y-E,62274
21
+ batplot/ui.py,sha256=ifpbK74juUzLMCt-sJGVaWtpDb1NMRJzs2YyiwwafzY,35302
22
+ batplot/utils.py,sha256=LY2-Axr3DsQMTxuXe48vSjrLJKEnkzkZjdSFdQizbpg,37599
23
+ batplot/version_check.py,sha256=--U_74DKgHbGtVdBsg9DfUJ10S5-OMXT-rzaYjK0JBc,9997
24
+ batplot/data/USER_MANUAL.md,sha256=VYPvNZt3Fy8Z4Izr2FnQBw9vEaFTPkybhHDnF-OuKws,17694
25
+ batplot-1.8.2.dist-info/licenses/LICENSE,sha256=2PAnHeCiTfgI7aKZLWr0G56HI9fGKQ0CEbQ02H-yExQ,1065
26
+ batplot_backup_20251121_223043/__init__.py,sha256=3s2DUQuTbWs65hoN9cQQ8IiJbaFJY8fNxiCpwRBYoOA,118
27
+ batplot_backup_20251121_223043/args.py,sha256=OH-h84QhN-IhMS8sPAsSEqccHD3wpeMgmXa_fqv5xtg,21215
28
+ batplot_backup_20251121_223043/batch.py,sha256=oI7PONJyciHDOqNPq-8fnOQMyn9CpAdVznKaEdsy0ig,48650
29
+ batplot_backup_20251121_223043/batplot.py,sha256=cJOqfqEWd3vW4prlt0i2Ih6BqopnjFiRP_fAxCMHcds,157883
30
+ batplot_backup_20251121_223043/cif.py,sha256=MeMHq7GvFwrFwpASD4XkqbHVBiBrJxmG112IPThfbZo,17564
31
+ batplot_backup_20251121_223043/cli.py,sha256=lq-0MSCHouGm1qXMYFlI2YMwAMZZSPQxhfMrR8-vdTM,2622
32
+ batplot_backup_20251121_223043/color_utils.py,sha256=jQzsR1Q2cfn7luIdK5aMW4BZhIKb5Q_Q9NfmiUk0AwA,8467
33
+ batplot_backup_20251121_223043/config.py,sha256=MKtmMiQrY9WmghEpcdyBwpiT-Xl3SwLVZTt0DhuB7Yc,1939
34
+ batplot_backup_20251121_223043/converters.py,sha256=O7zvCNTztrXXSxJtm2GZkseYt4agidA6URSCugh953I,3090
35
+ batplot_backup_20251121_223043/cpc_interactive.py,sha256=Z8Sil20F8ibhZUFm1s2oKWGr_tDFwbPBCvEzfb3xADQ,157886
36
+ batplot_backup_20251121_223043/electrochem_interactive.py,sha256=FxFuq7945tIZJWWxdXkh8ZGzyCV5u8khVIr9bFCtKTw,173915
37
+ batplot_backup_20251121_223043/interactive.py,sha256=dAntE1WkAC2pf50wK-jOLmO-t3BOt9NgtgrFmyM_0ac,181928
38
+ batplot_backup_20251121_223043/modes.py,sha256=s4eUHfbl-uORt9kR7Rl56xvirMkPXSsU_dVVv8PrOSE,24698
39
+ batplot_backup_20251121_223043/operando.py,sha256=SABx1BQguoXXsG5PFAbp_CMvAz9vDA3JKRa4lcKGCm0,22980
40
+ batplot_backup_20251121_223043/operando_ec_interactive.py,sha256=k9_r70OKqclJQUOT3c_W2DxXfkF9qVRCcUxo-QuTwsI,261415
41
+ batplot_backup_20251121_223043/plotting.py,sha256=fwzcdKQcHZDamtJj33zfs--Kwc81KoMhn91EQ8Vkeq8,4157
42
+ batplot_backup_20251121_223043/readers.py,sha256=O1CbGE5sedFn1TvCAvIB39Y6zCqcBt3ehvIkf0b6rYw,78339
43
+ batplot_backup_20251121_223043/session.py,sha256=CzzDZw_7rb8r2seVZZbP6dLoDl8nhhViKgB-qaT5-5s,111705
44
+ batplot_backup_20251121_223043/style.py,sha256=xg-tj6bEbFUVjjxYMokiLehS4tSfKanLIQKtly3cAP0,51318
45
+ batplot_backup_20251121_223043/ui.py,sha256=K0XZWyiuBRNkFod9mgZyJ9CLN78GR1-hh6EznnIb5S8,31208
46
+ batplot_backup_20251121_223043/utils.py,sha256=jydA0JxsCWWAudXEwSjlxTG17y2F8U6hIAukAzi1P0g,32526
47
+ batplot_backup_20251121_223043/version_check.py,sha256=vlHkGkgUJcD_Z4KZmwonxZvKZh0MwHLaBSxaLPc66AQ,4555
48
+ batplot_backup_20251221_101150/__init__.py,sha256=AjJgj7OxrIM-uNCR6JvGnRjpVNZgCUbmj0VE_BpFFF8,118
49
+ batplot_backup_20251221_101150/args.py,sha256=mrDjMURp_OQnXrAwl9WnE_FW4HJu940O7NmL9QWQnD4,35189
50
+ batplot_backup_20251221_101150/batch.py,sha256=YQ7obCIqLCObwDbM7TXpOBh7g7BO95wZNsa2Fy84c6o,53858
51
+ batplot_backup_20251221_101150/batplot.py,sha256=40lU1nY1NqeAOpzNG_vLF_L34COKhiA19pMpbvA3SJc,171885
52
+ batplot_backup_20251221_101150/cif.py,sha256=JfHwNf3SHrcpALc_F5NjJmQ3lg71MBRSaIUJjGYPTx8,30120
53
+ batplot_backup_20251221_101150/cli.py,sha256=ScDb2je8VQ0mz_z0SLCHEigiTuFPY5pb1snnzCouKms,5828
54
+ batplot_backup_20251221_101150/color_utils.py,sha256=7InQLVo1XTg7sgAbltM2KeDSFJgr787YEaV9vJbIoWY,20460
55
+ batplot_backup_20251221_101150/config.py,sha256=6nGY7fKN4T5KZUGQS2ArUBgEkLAL0j37XwG5SCVQgKA,6420
56
+ batplot_backup_20251221_101150/converters.py,sha256=rR2WMPM0nR5E3eZI3gWbaJf_AfbdQx3urVSbJmZXNzo,8237
57
+ batplot_backup_20251221_101150/cpc_interactive.py,sha256=HrrjaB8-CNYUitgl5zWMNvWQLZfxyFAtpSm67qoi-nE,238235
58
+ batplot_backup_20251221_101150/electrochem_interactive.py,sha256=ti7V8BoAxUk4BD_vDRKAu5ydlHMl75htLvdVYFUUVsw,221778
59
+ batplot_backup_20251221_101150/interactive.py,sha256=uerVR-56g2Ur8qDZ-cXffPbpYMQXEXiMNXCxyWZZ8k0,206259
60
+ batplot_backup_20251221_101150/manual.py,sha256=pbRI6G4Pm12pOW8LrOLWWu7IEOtqWN3tRHtgge50LlA,11556
61
+ batplot_backup_20251221_101150/modes.py,sha256=Utfal5IaV8rfoNyNFziUZpqRlpZAWJdiTc45DY-FJE8,37300
62
+ batplot_backup_20251221_101150/operando.py,sha256=p2Ug1mFUQxaU702cTBGgJKb3_v1C2p3LLUwfXaVBpPY,28311
63
+ batplot_backup_20251221_101150/operando_ec_interactive.py,sha256=TMB6rDpeolX0CgE2V7tWC24ffJrnbJomQSnTsTd8CNQ,305121
64
+ batplot_backup_20251221_101150/plotting.py,sha256=hG2_EdDhF1Qpn1XfZKdCQ5-w_m9gUYFbr804UQ5QjsU,10841
65
+ batplot_backup_20251221_101150/readers.py,sha256=kAI0AvYrdfGRZkvADJ4riN96IWtrH24aAoZpBtONTbw,112960
66
+ batplot_backup_20251221_101150/session.py,sha256=05JsVi0ygMzOxVRRZ4klhE5Eh6eE6QxKR8p7_j6slBI,134429
67
+ batplot_backup_20251221_101150/style.py,sha256=ig1ozX4dhEsXf5JKaPZOvgVS3CWx-BTFSc3vfAH3Y-E,62274
68
+ batplot_backup_20251221_101150/ui.py,sha256=ifpbK74juUzLMCt-sJGVaWtpDb1NMRJzs2YyiwwafzY,35302
69
+ batplot_backup_20251221_101150/utils.py,sha256=LY2-Axr3DsQMTxuXe48vSjrLJKEnkzkZjdSFdQizbpg,37599
70
+ batplot_backup_20251221_101150/version_check.py,sha256=ztTHwqgWd8OlS9PLLY5A_TabWxBASDA_-5yyN15PZC8,9996
71
+ batplot-1.8.2.dist-info/METADATA,sha256=ZNkBWCjEFRJVKuq1FtQ9JCovRWnEm3lmqWs8eYsvMzU,7406
72
+ batplot-1.8.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ batplot-1.8.2.dist-info/entry_points.txt,sha256=73GgH3Zs-qGIvgiyQLgGsSW-ryOwPPKHveOW6TDIR5Q,82
74
+ batplot-1.8.2.dist-info/top_level.txt,sha256=Z5Q4sAiT_FDqZqhlLsYn9avRTuFAEEf3AVfkswxOb18,70
75
+ batplot-1.8.2.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  batplot
2
2
  batplot_backup_20251121_223043
3
+ batplot_backup_20251221_101150
@@ -0,0 +1,5 @@
1
+ """batplot: Interactive plotting for battery data visualization."""
2
+
3
+ __version__ = "1.8.1"
4
+
5
+ __all__ = ["__version__"]