batplot 1.8.5__tar.gz → 1.8.8__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 (83) hide show
  1. {batplot-1.8.5/batplot.egg-info → batplot-1.8.8}/PKG-INFO +1 -1
  2. {batplot-1.8.5 → batplot-1.8.8}/batplot/__init__.py +1 -1
  3. {batplot-1.8.5 → batplot-1.8.8}/batplot/batplot.py +145 -55
  4. {batplot-1.8.5 → batplot-1.8.8}/batplot/cpc_interactive.py +286 -140
  5. {batplot-1.8.5 → batplot-1.8.8}/batplot/electrochem_interactive.py +57 -44
  6. {batplot-1.8.5 → batplot-1.8.8}/batplot/interactive.py +216 -2
  7. {batplot-1.8.5 → batplot-1.8.8}/batplot/modes.py +12 -11
  8. {batplot-1.8.5 → batplot-1.8.8}/batplot/operando_ec_interactive.py +158 -14
  9. {batplot-1.8.5 → batplot-1.8.8}/batplot/session.py +61 -9
  10. {batplot-1.8.5 → batplot-1.8.8}/batplot/style.py +109 -19
  11. {batplot-1.8.5 → batplot-1.8.8/batplot.egg-info}/PKG-INFO +1 -1
  12. {batplot-1.8.5 → batplot-1.8.8}/pyproject.toml +1 -1
  13. {batplot-1.8.5 → batplot-1.8.8}/LICENSE +0 -0
  14. {batplot-1.8.5 → batplot-1.8.8}/MANIFEST.in +0 -0
  15. {batplot-1.8.5 → batplot-1.8.8}/README.md +0 -0
  16. {batplot-1.8.5 → batplot-1.8.8}/USER_MANUAL.md +0 -0
  17. {batplot-1.8.5 → batplot-1.8.8}/batplot/args.py +0 -0
  18. {batplot-1.8.5 → batplot-1.8.8}/batplot/batch.py +0 -0
  19. {batplot-1.8.5 → batplot-1.8.8}/batplot/cif.py +0 -0
  20. {batplot-1.8.5 → batplot-1.8.8}/batplot/cli.py +0 -0
  21. {batplot-1.8.5 → batplot-1.8.8}/batplot/color_utils.py +0 -0
  22. {batplot-1.8.5 → batplot-1.8.8}/batplot/config.py +0 -0
  23. {batplot-1.8.5 → batplot-1.8.8}/batplot/converters.py +0 -0
  24. {batplot-1.8.5 → batplot-1.8.8}/batplot/data/USER_MANUAL.md +0 -0
  25. {batplot-1.8.5 → batplot-1.8.8}/batplot/manual.py +0 -0
  26. {batplot-1.8.5 → batplot-1.8.8}/batplot/operando.py +0 -0
  27. {batplot-1.8.5 → batplot-1.8.8}/batplot/plotting.py +0 -0
  28. {batplot-1.8.5 → batplot-1.8.8}/batplot/readers.py +0 -0
  29. {batplot-1.8.5 → batplot-1.8.8}/batplot/ui.py +0 -0
  30. {batplot-1.8.5 → batplot-1.8.8}/batplot/utils.py +0 -0
  31. {batplot-1.8.5 → batplot-1.8.8}/batplot/version_check.py +0 -0
  32. {batplot-1.8.5 → batplot-1.8.8}/batplot.egg-info/SOURCES.txt +0 -0
  33. {batplot-1.8.5 → batplot-1.8.8}/batplot.egg-info/dependency_links.txt +0 -0
  34. {batplot-1.8.5 → batplot-1.8.8}/batplot.egg-info/entry_points.txt +0 -0
  35. {batplot-1.8.5 → batplot-1.8.8}/batplot.egg-info/requires.txt +0 -0
  36. {batplot-1.8.5 → batplot-1.8.8}/batplot.egg-info/top_level.txt +0 -0
  37. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/__init__.py +0 -0
  38. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/args.py +0 -0
  39. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/batch.py +0 -0
  40. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/batplot.py +0 -0
  41. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/cif.py +0 -0
  42. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/cli.py +0 -0
  43. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/color_utils.py +0 -0
  44. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/config.py +0 -0
  45. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/converters.py +0 -0
  46. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/cpc_interactive.py +0 -0
  47. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/electrochem_interactive.py +0 -0
  48. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/interactive.py +0 -0
  49. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/modes.py +0 -0
  50. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/operando.py +0 -0
  51. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/operando_ec_interactive.py +0 -0
  52. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/plotting.py +0 -0
  53. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/readers.py +0 -0
  54. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/session.py +0 -0
  55. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/style.py +0 -0
  56. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/ui.py +0 -0
  57. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/utils.py +0 -0
  58. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251121_223043/version_check.py +0 -0
  59. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/__init__.py +0 -0
  60. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/args.py +0 -0
  61. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/batch.py +0 -0
  62. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/batplot.py +0 -0
  63. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/cif.py +0 -0
  64. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/cli.py +0 -0
  65. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/color_utils.py +0 -0
  66. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/config.py +0 -0
  67. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/converters.py +0 -0
  68. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/cpc_interactive.py +0 -0
  69. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/electrochem_interactive.py +0 -0
  70. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/interactive.py +0 -0
  71. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/manual.py +0 -0
  72. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/modes.py +0 -0
  73. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/operando.py +0 -0
  74. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/operando_ec_interactive.py +0 -0
  75. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/plotting.py +0 -0
  76. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/readers.py +0 -0
  77. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/session.py +0 -0
  78. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/style.py +0 -0
  79. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/ui.py +0 -0
  80. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/utils.py +0 -0
  81. {batplot-1.8.5 → batplot-1.8.8}/batplot_backup_20251221_101150/version_check.py +0 -0
  82. {batplot-1.8.5 → batplot-1.8.8}/setup.cfg +0 -0
  83. {batplot-1.8.5 → batplot-1.8.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.8.5
3
+ Version: 1.8.8
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
@@ -1,5 +1,5 @@
1
1
  """batplot: Interactive plotting for battery data visualization."""
2
2
 
3
- __version__ = "1.8.5"
3
+ __version__ = "1.8.8"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -422,7 +422,10 @@ def batplot_main() -> int:
422
422
  # Ensure font and canvas settings match GC/dQdV
423
423
  _plt.rcParams.update({
424
424
  'font.family': 'sans-serif',
425
- 'font.sans-serif': ['Arial', 'DejaVu Sans', 'Helvetica', 'STIXGeneral', 'Liberation Sans', 'Arial Unicode MS'],
425
+ # Prefer DejaVu Sans first because it has good Unicode
426
+ # coverage (including subscript/superscript digits), then
427
+ # fall back to other common sans-serif fonts.
428
+ 'font.sans-serif': ['DejaVu Sans', 'Arial', 'Helvetica', 'STIXGeneral', 'Liberation Sans', 'Arial Unicode MS'],
426
429
  'mathtext.fontset': 'dejavusans',
427
430
  'font.size': 16
428
431
  })
@@ -616,8 +619,10 @@ def batplot_main() -> int:
616
619
  # Set global default font
617
620
  plt.rcParams.update({
618
621
  'font.family': 'sans-serif',
619
- 'font.sans-serif': ['Arial', 'DejaVu Sans', 'Helvetica', 'STIXGeneral', 'Liberation Sans', 'Arial Unicode MS'],
620
- 'mathtext.fontset': 'dejavusans', # keeps math consistent with Arial-like sans
622
+ # Use DejaVu Sans first to ensure good Unicode coverage (subscripts,
623
+ # superscripts, Greek, etc.), then fall back to other common fonts.
624
+ 'font.sans-serif': ['DejaVu Sans', 'Arial', 'Helvetica', 'STIXGeneral', 'Liberation Sans', 'Arial Unicode MS'],
625
+ 'mathtext.fontset': 'dejavusans', # keeps math consistent with sans-serif
621
626
  'font.size': 16
622
627
  })
623
628
 
@@ -1165,11 +1170,9 @@ def batplot_main() -> int:
1165
1170
  print(f"Skipped {file_basename}: unsupported format (must be .csv, .xlsx, or .mpt)")
1166
1171
  continue
1167
1172
 
1168
- # Assign colors: distinct hue for each file
1173
+ # Assign colors: distinct hue per file
1169
1174
  capacity_color = capacity_colors[file_idx % len(capacity_colors)]
1170
1175
  efficiency_color = efficiency_colors[file_idx % len(efficiency_colors)]
1171
- # Generate discharge color using the same function as interactive menu
1172
- discharge_color = _generate_similar_color(capacity_color)
1173
1176
 
1174
1177
  file_data.append({
1175
1178
  'filename': file_basename,
@@ -1179,7 +1182,6 @@ def batplot_main() -> int:
1179
1182
  'cap_discharge': cap_discharge,
1180
1183
  'eff': eff,
1181
1184
  'color': capacity_color,
1182
- 'discharge_color': discharge_color,
1183
1185
  'eff_color': efficiency_color,
1184
1186
  'visible': True
1185
1187
  })
@@ -1207,7 +1209,7 @@ def batplot_main() -> int:
1207
1209
  cap_charge = file_info['cap_charge']
1208
1210
  cap_discharge = file_info['cap_discharge']
1209
1211
  eff = file_info['eff']
1210
- color = file_info['color'] # Warm color for capacity
1212
+ color = file_info['color'] # Base color for capacity (both charge/discharge)
1211
1213
  eff_color = file_info['eff_color'] # Cold color for efficiency
1212
1214
  label = file_info['filename']
1213
1215
 
@@ -1221,16 +1223,30 @@ def batplot_main() -> int:
1221
1223
  label_dch = f'{label} (Dch)'
1222
1224
  label_eff = f'{label} (Eff)'
1223
1225
 
1224
- # Use stored discharge color if available, otherwise generate it
1225
- if 'discharge_color' in file_info and file_info['discharge_color'] is not None:
1226
- discharge_color = file_info['discharge_color']
1227
- else:
1228
- discharge_color = _generate_similar_color(color)
1229
-
1230
- sc_charge = ax.scatter(cyc_nums, cap_charge, color=color, label=label_chg,
1231
- s=32, zorder=3, alpha=0.8, marker='o')
1232
- sc_discharge = ax.scatter(cyc_nums, cap_discharge, color=discharge_color, label=label_dch,
1233
- s=32, zorder=3, alpha=0.8, marker='s')
1226
+ # Capacity curves: same color, different fill style
1227
+ # - Charge: filled square
1228
+ # - Discharge: hollow square (edge only)
1229
+ sc_charge = ax.scatter(
1230
+ cyc_nums,
1231
+ cap_charge,
1232
+ label=label_chg,
1233
+ s=32,
1234
+ zorder=3,
1235
+ alpha=0.8,
1236
+ marker='s',
1237
+ color=color,
1238
+ )
1239
+ sc_discharge = ax.scatter(
1240
+ cyc_nums,
1241
+ cap_discharge,
1242
+ label=label_dch,
1243
+ s=32,
1244
+ zorder=3,
1245
+ alpha=0.8,
1246
+ marker='s',
1247
+ facecolor='none',
1248
+ edgecolor=color,
1249
+ )
1234
1250
  sc_eff = ax2.scatter(cyc_nums, eff, color=eff_color, marker='^', label=label_eff,
1235
1251
  s=40, alpha=0.7, zorder=3)
1236
1252
 
@@ -1247,27 +1263,25 @@ def batplot_main() -> int:
1247
1263
  h1, l1 = ax.get_legend_handles_labels()
1248
1264
  h2, l2 = ax2.get_legend_handles_labels()
1249
1265
  combined_handles = h1 + h2
1250
- leg = ax.legend(
1251
- combined_handles, l1 + l2,
1252
- loc='best',
1253
- frameon=False,
1254
- handlelength=1.0,
1255
- handletextpad=0.35,
1256
- labelspacing=0.25,
1257
- borderaxespad=0.5,
1258
- borderpad=0.3,
1259
- columnspacing=0.6,
1260
- labelcolor='linecolor',
1261
- )
1262
- if leg is not None:
1263
- try:
1264
- leg.set_frame_on(False)
1265
- leg.set_edgecolor('none')
1266
- leg.set_facecolor('none')
1267
- except Exception:
1268
- pass
1269
- except Exception:
1270
- pass
1266
+ combined_labels = l1 + l2
1267
+ if combined_handles:
1268
+ # Don't use labelcolor='linecolor' for scatter plots with hollow markers
1269
+ # as it causes issues with color extraction
1270
+ leg = ax.legend(
1271
+ combined_handles, combined_labels,
1272
+ loc='best',
1273
+ frameon=False,
1274
+ handlelength=1.0,
1275
+ handletextpad=0.35,
1276
+ labelspacing=0.25,
1277
+ borderaxespad=0.5,
1278
+ borderpad=0.3,
1279
+ columnspacing=0.6,
1280
+ )
1281
+ except Exception as e:
1282
+ print(f"Warning: Could not create CPC legend: {e}")
1283
+ import traceback
1284
+ traceback.print_exc()
1271
1285
 
1272
1286
  # Adjust layout to ensure top and bottom labels/titles are visible
1273
1287
  fig.subplots_adjust(left=0.12, right=0.88, top=0.88, bottom=0.15)
@@ -1842,6 +1856,11 @@ def batplot_main() -> int:
1842
1856
  _plt.show(block=False)
1843
1857
  except Exception:
1844
1858
  pass
1859
+ # Seed last-session path so 'os' overwrite command is available immediately
1860
+ try:
1861
+ fig._last_session_save_path = os.path.abspath(sess_path)
1862
+ except Exception:
1863
+ pass
1845
1864
  try:
1846
1865
  source_list = list(getattr(fig, '_bp_source_paths', []) or [])
1847
1866
  sess_abs = os.path.abspath(sess_path)
@@ -1877,6 +1896,11 @@ def batplot_main() -> int:
1877
1896
  _plt.show(block=False)
1878
1897
  except Exception:
1879
1898
  pass
1899
+ # Seed last-session path so 'os' overwrite command is available immediately
1900
+ try:
1901
+ fig2._last_session_save_path = os.path.abspath(sess_path)
1902
+ except Exception:
1903
+ pass
1880
1904
  try:
1881
1905
  if operando_ec_interactive_menu is not None:
1882
1906
  operando_ec_interactive_menu(fig2, ax2, im2, cbar2, ec_ax2)
@@ -1905,6 +1929,11 @@ def batplot_main() -> int:
1905
1929
  _plt.show(block=False)
1906
1930
  except Exception:
1907
1931
  pass
1932
+ # Seed last-session path so 'os' overwrite command is available immediately
1933
+ try:
1934
+ fig_c._last_session_save_path = os.path.abspath(sess_path)
1935
+ except Exception:
1936
+ pass
1908
1937
  try:
1909
1938
  if cpc_interactive_menu is not None:
1910
1939
  cpc_interactive_menu(fig_c, ax_c, ax2_c, sc_c, sc_d, sc_e, file_data=file_data)
@@ -2207,6 +2236,14 @@ def batplot_main() -> int:
2207
2236
  fig._tick_lengths['minor'] = minor_len
2208
2237
  except Exception:
2209
2238
  pass
2239
+ # Tick direction restore (t submenu)
2240
+ try:
2241
+ tick_direction = sess.get('tick_direction', 'out')
2242
+ if tick_direction:
2243
+ setattr(fig, '_tick_direction', tick_direction)
2244
+ ax.tick_params(axis='both', which='both', direction=tick_direction)
2245
+ except Exception:
2246
+ pass
2210
2247
 
2211
2248
  # Restore WASD state (spine, ticks, labels, title visibility for all 4 sides)
2212
2249
  try:
@@ -2675,8 +2712,24 @@ def batplot_main() -> int:
2675
2712
  offset = 0.0
2676
2713
  direction = -1 if args.stack else 1 # stack downward
2677
2714
  if args.interactive:
2715
+ # Interactive: keep a reasonably compact default size so the window
2716
+ # fits well on most screens; margins are handled by the menu logic.
2678
2717
  plt.ion()
2679
- fig, ax = plt.subplots(figsize=(8, 6))
2718
+ figsize = (8, 6)
2719
+ else:
2720
+ # Non-interactive (no --i): use a slightly larger canvas so that labels,
2721
+ # titles, legends, and CIF ticks are not clipped even with long filenames.
2722
+ # The size (9, 6.4) keeps a similar aspect ratio but with a bit more room
2723
+ # than the interactive default while still fitting comfortably on screen.
2724
+ figsize = (9.5, 6.4)
2725
+ fig, ax = plt.subplots(figsize=figsize)
2726
+
2727
+ # Set consistent margins for all modes.
2728
+ # This prevents labels/titles from being cut off at the edges.
2729
+ try:
2730
+ fig.subplots_adjust(left=0.125, right=0.9, top=0.88, bottom=0.11)
2731
+ except Exception:
2732
+ pass
2680
2733
 
2681
2734
  y_data_list = []
2682
2735
  x_data_list = []
@@ -2710,7 +2763,17 @@ def batplot_main() -> int:
2710
2763
  any_cif = any(f.lower().endswith(".cif") for f in args.files)
2711
2764
  non_cif_count = sum(0 if f.lower().endswith('.cif') else 1 for f in args.files)
2712
2765
  cif_only = any_cif and non_cif_count == 0
2713
- any_lambda = any(":" in f for f in args.files) or args.wl is not None
2766
+ # Check for wavelength parameters (file:wl), but exclude Windows drive letters (C:\...)
2767
+ def has_wavelength_param(f):
2768
+ if ":" not in f:
2769
+ return False
2770
+ # Check if it's a Windows path (single letter followed by :\ or :/)
2771
+ if len(f) >= 2 and f[1] == ':' and len(f[0]) == 1 and f[0].isalpha():
2772
+ # This is a Windows drive letter, check after the drive path
2773
+ # Look for additional colons beyond the drive letter
2774
+ return ":" in f[2:]
2775
+ return True
2776
+ any_lambda = any(has_wavelength_param(f) for f in args.files) or args.wl is not None
2714
2777
 
2715
2778
  # Incompatibilities (no mixing of fundamentally different axis domains)
2716
2779
  if sum(bool(x) for x in (any_gr, any_nor, any_chik, any_chir, (any_qye or any_lambda or any_cif))) > 1:
@@ -2730,7 +2793,8 @@ def batplot_main() -> int:
2730
2793
  elif any_txt:
2731
2794
  # .txt is generic, require --xaxis
2732
2795
  if args.xaxis:
2733
- axis_mode = args.xaxis
2796
+ # Normalize case: 'q' or 'Q' → 'Q' (uppercase), everything else lowercase
2797
+ axis_mode = "Q" if args.xaxis.upper() == "Q" else args.xaxis.lower()
2734
2798
  else:
2735
2799
  raise ValueError("Unknown file type. Use: batplot file.txt --xaxis [Q|2theta|r|k|energy|rft] or batplot -h for help.")
2736
2800
  elif any_lambda or any_cif:
@@ -2741,7 +2805,8 @@ def batplot_main() -> int:
2741
2805
  # CIF files are in Q space
2742
2806
  axis_mode = "Q"
2743
2807
  elif args.xaxis:
2744
- axis_mode = args.xaxis
2808
+ # Normalize case: 'q' or 'Q' → 'Q' (uppercase), everything else lowercase
2809
+ axis_mode = "Q" if args.xaxis.upper() == "Q" else args.xaxis.lower()
2745
2810
  else:
2746
2811
  raise ValueError("Unknown file type. Use: batplot file.csv --xaxis [Q|2theta|r|k|energy|rft] or batplot -h for help.")
2747
2812
 
@@ -2841,8 +2906,16 @@ def batplot_main() -> int:
2841
2906
 
2842
2907
  # Use data_files instead of args.files for processing
2843
2908
  for idx_file, file_entry in enumerate(data_files):
2909
+ # Handle Windows paths (C:\...) vs wavelength parameters (file:wl)
2910
+ # On Windows, check if first part is a single letter (drive letter)
2844
2911
  parts = file_entry.split(":")
2845
- fname = parts[0]
2912
+ if len(parts) > 1 and len(parts[0]) == 1 and parts[0].isalpha():
2913
+ # Windows drive letter detected (e.g., "C" from "C:\path")
2914
+ # Rejoin the first two parts as the filename
2915
+ fname = parts[0] + ":" + parts[1]
2916
+ parts = [fname] + parts[2:] # Reconstruct parts with full Windows path
2917
+ else:
2918
+ fname = parts[0]
2846
2919
  # Parse wavelength parameters: file:wl1 or file:wl1:wl2 or file.cif:wl
2847
2920
  wavelength_file = None
2848
2921
  original_wavelength = None # First wavelength (for Q conversion)
@@ -3532,19 +3605,36 @@ def batplot_main() -> int:
3532
3605
  ax._cif_hover_cid = cid
3533
3606
 
3534
3607
  if cif_tick_series:
3535
- # Auto-assign distinct colors if all are default 'k'
3608
+ # Auto-assign distinct colors for CIF tick series.
3609
+ # For multiple CIF series:
3610
+ # - If <= 10 files, use 'tab10' but in a re-ordered sequence to
3611
+ # maximize visual separation between adjacent colors.
3612
+ # - If > 10 files, use 'viridis' with evenly spaced samples.
3613
+ #
3614
+ # This overrides any previous per-series color so that the requested
3615
+ # colormap behavior is always enforced.
3536
3616
  if len(cif_tick_series) > 1:
3537
- if all(c[-1] == 'k' for c in cif_tick_series):
3538
- try:
3539
- cmap_name = 'tab10' if len(cif_tick_series) <= 10 else 'hsv'
3540
- cmap = plt.get_cmap(cmap_name)
3617
+ try:
3618
+ n_cif = len(cif_tick_series)
3619
+ if n_cif <= 10:
3620
+ tab10 = plt.get_cmap('tab10').colors
3621
+ # Reorder indices for more distinct neighboring colors
3622
+ order = [0, 3, 6, 1, 4, 7, 2, 5, 8, 9]
3541
3623
  new_series = []
3542
- for i,(lab,fname,peaksQ,wl,qmax_sim,col) in enumerate(cif_tick_series):
3543
- color = cmap(i / max(1,(len(cif_tick_series)-1)))
3544
- new_series.append((lab,fname,peaksQ,wl,qmax_sim,color))
3545
- cif_tick_series[:] = new_series
3546
- except Exception:
3547
- pass
3624
+ for i, (lab, fname, peaksQ, wl, qmax_sim, col) in enumerate(cif_tick_series):
3625
+ idx = order[i] if i < len(order) else i % len(tab10)
3626
+ color = tab10[idx]
3627
+ new_series.append((lab, fname, peaksQ, wl, qmax_sim, color))
3628
+ else:
3629
+ cmap = plt.get_cmap('viridis')
3630
+ positions = np.linspace(0.0, 1.0, n_cif)
3631
+ new_series = []
3632
+ for (pos, (lab, fname, peaksQ, wl, qmax_sim, col)) in zip(positions, cif_tick_series):
3633
+ color = cmap(pos)
3634
+ new_series.append((lab, fname, peaksQ, wl, qmax_sim, color))
3635
+ cif_tick_series[:] = new_series
3636
+ except Exception:
3637
+ pass
3548
3638
  if use_2th:
3549
3639
  _ensure_wavelength_for_2theta()
3550
3640
  draw_cif_ticks()