batplot 1.7.25__py3-none-any.whl → 1.7.27__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.
- batplot/__init__.py +1 -1
- batplot/batplot.py +62 -31
- batplot/color_utils.py +13 -6
- batplot/cpc_interactive.py +515 -320
- batplot/electrochem_interactive.py +24 -2
- batplot/interactive.py +12 -4
- batplot/operando.py +3 -3
- batplot/operando_ec_interactive.py +22 -1
- batplot/session.py +29 -3
- batplot/style.py +4 -0
- batplot/ui.py +13 -29
- batplot/utils.py +48 -0
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/METADATA +1 -1
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/RECORD +18 -18
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/WHEEL +0 -0
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/entry_points.txt +0 -0
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.7.25.dist-info → batplot-1.7.27.dist-info}/top_level.txt +0 -0
|
@@ -1684,6 +1684,17 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1684
1684
|
if not already_confirmed and os.path.exists(target):
|
|
1685
1685
|
target = _confirm_overwrite(target)
|
|
1686
1686
|
if target:
|
|
1687
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
1688
|
+
from .utils import ensure_exact_case_filename
|
|
1689
|
+
target = ensure_exact_case_filename(target)
|
|
1690
|
+
|
|
1691
|
+
# Save current legend position before export (savefig can change layout)
|
|
1692
|
+
saved_legend_pos = None
|
|
1693
|
+
try:
|
|
1694
|
+
saved_legend_pos = getattr(fig, '_ec_legend_xy_in', None)
|
|
1695
|
+
except Exception:
|
|
1696
|
+
pass
|
|
1697
|
+
|
|
1687
1698
|
# If exporting SVG, make background transparent for PowerPoint
|
|
1688
1699
|
_, ext2 = os.path.splitext(target)
|
|
1689
1700
|
ext2 = ext2.lower()
|
|
@@ -1727,6 +1738,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1727
1738
|
fig.savefig(target, bbox_inches='tight')
|
|
1728
1739
|
print(f"Exported figure to {target}")
|
|
1729
1740
|
fig._last_figure_export_path = target
|
|
1741
|
+
|
|
1742
|
+
# Restore legend position after savefig (which may have changed layout)
|
|
1743
|
+
if saved_legend_pos is not None:
|
|
1744
|
+
try:
|
|
1745
|
+
fig._ec_legend_xy_in = saved_legend_pos
|
|
1746
|
+
_rebuild_legend(ax)
|
|
1747
|
+
fig.canvas.draw_idle()
|
|
1748
|
+
except Exception:
|
|
1749
|
+
pass
|
|
1730
1750
|
except Exception as e:
|
|
1731
1751
|
print(f"Export failed: {e}")
|
|
1732
1752
|
except Exception as e:
|
|
@@ -3283,8 +3303,10 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3283
3303
|
continue
|
|
3284
3304
|
elif key == 'f':
|
|
3285
3305
|
# Font submenu with numbered options
|
|
3306
|
+
cur_family = plt.rcParams.get('font.sans-serif', [''])[0]
|
|
3307
|
+
cur_size = plt.rcParams.get('font.size', None)
|
|
3286
3308
|
while True:
|
|
3287
|
-
print("\nFont menu: f=font family, s=size, q=back")
|
|
3309
|
+
print(f"\nFont menu (current: family='{cur_family}', size={cur_size}): f=font family, s=size, q=back")
|
|
3288
3310
|
sub = input("Font> ").strip().lower()
|
|
3289
3311
|
if not sub:
|
|
3290
3312
|
continue
|
|
@@ -3298,7 +3320,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3298
3320
|
for i, font in enumerate(fonts, 1):
|
|
3299
3321
|
print(f" {i}: {font}")
|
|
3300
3322
|
print("Or enter custom font name directly.")
|
|
3301
|
-
choice = input("Font family (number or name): ").strip()
|
|
3323
|
+
choice = input(f"Font family (current: '{cur_family}', number or name): ").strip()
|
|
3302
3324
|
if not choice:
|
|
3303
3325
|
continue
|
|
3304
3326
|
# Check if it's a number
|
batplot/interactive.py
CHANGED
|
@@ -963,7 +963,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
963
963
|
txt = ax.text(1.0, 1.0, "",
|
|
964
964
|
ha='right', va='bottom',
|
|
965
965
|
transform=ax.transAxes,
|
|
966
|
-
fontsize=max(9, int(0.6 * plt.rcParams.get('font.size',
|
|
966
|
+
fontsize=max(9, int(0.6 * plt.rcParams.get('font.size', 16))),
|
|
967
967
|
color='0.15',
|
|
968
968
|
bbox=dict(boxstyle='round,pad=0.25', fc='white', ec='0.7', alpha=0.8))
|
|
969
969
|
|
|
@@ -3130,15 +3130,18 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3130
3130
|
except Exception as e:
|
|
3131
3131
|
print(f"Error setting widths: {e}")
|
|
3132
3132
|
elif key == 'f':
|
|
3133
|
+
cur_family = plt.rcParams.get('font.sans-serif', [''])[0]
|
|
3134
|
+
cur_size = plt.rcParams.get('font.size', None)
|
|
3133
3135
|
while True:
|
|
3134
|
-
subkey = input(colorize_prompt("Font submenu (s=size, f=family, q=return
|
|
3136
|
+
subkey = input(colorize_prompt(f"Font submenu (current: family='{cur_family}', size={cur_size}) - s=size, f=family, q=return: ")).strip().lower()
|
|
3135
3137
|
if subkey == 'q':
|
|
3136
3138
|
break
|
|
3137
3139
|
if subkey == '':
|
|
3138
3140
|
continue
|
|
3139
3141
|
if subkey == 's':
|
|
3140
3142
|
try:
|
|
3141
|
-
|
|
3143
|
+
cur_size = plt.rcParams.get('font.size', None)
|
|
3144
|
+
fs = input(f"Enter new font size (current: {cur_size}, q=cancel): ").strip()
|
|
3142
3145
|
if not fs or fs.lower() == 'q':
|
|
3143
3146
|
print("Canceled.")
|
|
3144
3147
|
else:
|
|
@@ -3153,13 +3156,14 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3153
3156
|
print(f"Error changing font size: {e}")
|
|
3154
3157
|
elif subkey == 'f':
|
|
3155
3158
|
try:
|
|
3159
|
+
cur_family = plt.rcParams.get('font.sans-serif', [''])[0]
|
|
3156
3160
|
print("Common publication fonts:")
|
|
3157
3161
|
print(" 1) Arial")
|
|
3158
3162
|
print(" 2) Helvetica")
|
|
3159
3163
|
print(" 3) Times New Roman")
|
|
3160
3164
|
print(" 4) STIXGeneral")
|
|
3161
3165
|
print(" 5) DejaVu Sans")
|
|
3162
|
-
ft_raw = input("Enter font number or family name (q=cancel): ").strip()
|
|
3166
|
+
ft_raw = input(f"Enter font number or family name (current: '{cur_family}', q=cancel): ").strip()
|
|
3163
3167
|
if not ft_raw or ft_raw.lower() == 'q':
|
|
3164
3168
|
print("Canceled.")
|
|
3165
3169
|
else:
|
|
@@ -3705,6 +3709,10 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3705
3709
|
if not export_target:
|
|
3706
3710
|
print("Export canceled.")
|
|
3707
3711
|
else:
|
|
3712
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
3713
|
+
from .utils import ensure_exact_case_filename
|
|
3714
|
+
export_target = ensure_exact_case_filename(export_target)
|
|
3715
|
+
|
|
3708
3716
|
# Temporarily remove numbering for export
|
|
3709
3717
|
for i, txt in enumerate(label_text_objects):
|
|
3710
3718
|
txt.set_text(labels[i])
|
batplot/operando.py
CHANGED
|
@@ -386,7 +386,7 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
|
|
|
386
386
|
x_data, y_data, current_mA, x_label, y_label = result
|
|
387
387
|
# For EC-Lab files: x_label='Time (h)', y_label='Voltage (V)'
|
|
388
388
|
# For simple files: x_label could be 'Time(h)', 'time', etc.
|
|
389
|
-
# EC-Lab
|
|
389
|
+
# EC-Lab files: read_mpt_file already converts time from seconds to hours
|
|
390
390
|
# operando plots with voltage on X-axis and time on Y-axis
|
|
391
391
|
|
|
392
392
|
# Check if labels indicate time/voltage data (flexible matching)
|
|
@@ -400,8 +400,8 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
|
|
|
400
400
|
is_time_voltage = (has_time_in_x or has_time_in_y) and (has_voltage_in_x or has_voltage_in_y)
|
|
401
401
|
|
|
402
402
|
if x_label == 'Time (h)' and y_label == 'Voltage (V)':
|
|
403
|
-
# EC-Lab file:
|
|
404
|
-
time_h = np.asarray(x_data, float)
|
|
403
|
+
# EC-Lab file: time is already in hours from read_mpt_file, just swap axes
|
|
404
|
+
time_h = np.asarray(x_data, float) # Already in hours, no conversion needed
|
|
405
405
|
voltage_v = np.asarray(y_data, float)
|
|
406
406
|
x_data = voltage_v
|
|
407
407
|
y_data = time_h
|
|
@@ -1553,7 +1553,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1553
1553
|
# Create text annotations for coordinates
|
|
1554
1554
|
coord_text = fig.text(0.02, 0.98, '', transform=fig.transFigure,
|
|
1555
1555
|
verticalalignment='top',
|
|
1556
|
-
fontsize=max(9, int(0.6 * mpl_plt.rcParams.get('font.size',
|
|
1556
|
+
fontsize=max(9, int(0.6 * mpl_plt.rcParams.get('font.size', 16))),
|
|
1557
1557
|
color='0.15',
|
|
1558
1558
|
bbox=dict(boxstyle='round,pad=0.25', fc='white', ec='0.7', alpha=0.8))
|
|
1559
1559
|
except Exception as e:
|
|
@@ -1765,6 +1765,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1765
1765
|
target = _co(target)
|
|
1766
1766
|
if not target:
|
|
1767
1767
|
print_menu(); continue
|
|
1768
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
1769
|
+
from .utils import ensure_exact_case_filename
|
|
1770
|
+
target = ensure_exact_case_filename(target)
|
|
1771
|
+
|
|
1768
1772
|
_, ext = os.path.splitext(target)
|
|
1769
1773
|
if ext.lower() == '.svg':
|
|
1770
1774
|
try:
|
|
@@ -2052,6 +2056,19 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2052
2056
|
print_menu(); continue
|
|
2053
2057
|
dump_operando_session(target, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
2054
2058
|
fig._last_session_save_path = target
|
|
2059
|
+
# Show the actual filename that was saved (in case of case differences on macOS)
|
|
2060
|
+
actual_name = os.path.basename(target)
|
|
2061
|
+
if os.path.exists(target):
|
|
2062
|
+
# Get the actual filename as stored on disk (for case-sensitive display)
|
|
2063
|
+
try:
|
|
2064
|
+
dir_files = os.listdir(folder)
|
|
2065
|
+
for f in dir_files:
|
|
2066
|
+
if f.lower() == actual_name.lower():
|
|
2067
|
+
actual_name = f
|
|
2068
|
+
break
|
|
2069
|
+
except Exception:
|
|
2070
|
+
pass
|
|
2071
|
+
print(f"Operando session saved to {os.path.join(folder, actual_name)}")
|
|
2055
2072
|
except Exception as e:
|
|
2056
2073
|
print(f"Save failed: {e}")
|
|
2057
2074
|
print_menu(); continue
|
|
@@ -3777,6 +3794,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3777
3794
|
if yn != 'y':
|
|
3778
3795
|
target = None
|
|
3779
3796
|
if target:
|
|
3797
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
3798
|
+
from .utils import ensure_exact_case_filename
|
|
3799
|
+
target = ensure_exact_case_filename(target)
|
|
3800
|
+
|
|
3780
3801
|
with open(target, 'w', encoding='utf-8') as f:
|
|
3781
3802
|
json.dump(cfg, f, indent=2)
|
|
3782
3803
|
print(f"Exported style to {target}")
|
batplot/session.py
CHANGED
|
@@ -519,6 +519,10 @@ def dump_session(
|
|
|
519
519
|
if not target:
|
|
520
520
|
print("Session save canceled.")
|
|
521
521
|
return
|
|
522
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
523
|
+
from .utils import ensure_exact_case_filename
|
|
524
|
+
target = ensure_exact_case_filename(target)
|
|
525
|
+
|
|
522
526
|
with open(target, 'wb') as f:
|
|
523
527
|
pickle.dump(sess, f)
|
|
524
528
|
print(f"Session saved to {target}")
|
|
@@ -663,6 +667,16 @@ def dump_operando_session(
|
|
|
663
667
|
# Capture operando WASD state, spines, and tick widths
|
|
664
668
|
op_wasd_state = _capture_wasd_state(ax)
|
|
665
669
|
op_spines, op_ticks = _capture_spine_tick_widths(ax)
|
|
670
|
+
|
|
671
|
+
# Capture operando title offsets
|
|
672
|
+
op_title_offsets = {
|
|
673
|
+
'top_y': float(getattr(ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
674
|
+
'top_x': float(getattr(ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
675
|
+
'bottom_y': float(getattr(ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
676
|
+
'left_x': float(getattr(ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
677
|
+
'right_x': float(getattr(ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
678
|
+
'right_y': float(getattr(ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
679
|
+
}
|
|
666
680
|
|
|
667
681
|
# EC panel (optional)
|
|
668
682
|
ec_state = None
|
|
@@ -783,6 +797,10 @@ def dump_operando_session(
|
|
|
783
797
|
if not target:
|
|
784
798
|
print("Session save canceled.")
|
|
785
799
|
return
|
|
800
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
801
|
+
from .utils import ensure_exact_case_filename
|
|
802
|
+
target = ensure_exact_case_filename(target)
|
|
803
|
+
|
|
786
804
|
with open(target, 'wb') as f:
|
|
787
805
|
pickle.dump(sess, f)
|
|
788
806
|
print(f"Operando session saved to {target}")
|
|
@@ -2868,15 +2886,23 @@ def load_cpc_session(filename: str):
|
|
|
2868
2886
|
fw, fh = fig.get_size_inches()
|
|
2869
2887
|
fx = 0.5 + float(xy_in[0]) / float(fw)
|
|
2870
2888
|
fy = 0.5 + float(xy_in[1]) / float(fh)
|
|
2871
|
-
|
|
2889
|
+
# Use same spacing parameters as _legend_no_frame for consistent legend appearance
|
|
2890
|
+
leg = ax.legend(H, L, loc='center', bbox_to_anchor=(fx, fy), bbox_transform=fig.transFigure,
|
|
2891
|
+
handlelength=1.0, handletextpad=0.35, labelspacing=0.25,
|
|
2892
|
+
borderaxespad=0.5, borderpad=0.3, columnspacing=0.6,
|
|
2893
|
+
labelcolor='linecolor', frameon=False)
|
|
2872
2894
|
# persist inches on fig for interactive menu
|
|
2873
2895
|
try:
|
|
2874
2896
|
fig._cpc_legend_xy_in = (float(xy_in[0]), float(xy_in[1]))
|
|
2875
2897
|
except Exception:
|
|
2876
2898
|
pass
|
|
2877
2899
|
else:
|
|
2878
|
-
|
|
2879
|
-
|
|
2900
|
+
# Use same spacing parameters as _legend_no_frame for consistent legend appearance
|
|
2901
|
+
leg = ax.legend(H, L, loc='best',
|
|
2902
|
+
handlelength=1.0, handletextpad=0.35, labelspacing=0.25,
|
|
2903
|
+
borderaxespad=0.5, borderpad=0.3, columnspacing=0.6,
|
|
2904
|
+
labelcolor='linecolor', frameon=False)
|
|
2905
|
+
# Ensure legend frame is off (redundant but safe)
|
|
2880
2906
|
try:
|
|
2881
2907
|
if leg is not None:
|
|
2882
2908
|
leg.set_frame_on(False)
|
batplot/style.py
CHANGED
|
@@ -787,6 +787,10 @@ def export_style_config(
|
|
|
787
787
|
print("Style export canceled.")
|
|
788
788
|
return None
|
|
789
789
|
|
|
790
|
+
# Ensure exact case is preserved (important for macOS case-insensitive filesystem)
|
|
791
|
+
from .utils import ensure_exact_case_filename
|
|
792
|
+
target_path = ensure_exact_case_filename(target_path)
|
|
793
|
+
|
|
790
794
|
with open(target_path, "w", encoding="utf-8") as f:
|
|
791
795
|
json.dump(cfg, f, indent=2)
|
|
792
796
|
print(f"Exported style to {target_path}")
|
batplot/ui.py
CHANGED
|
@@ -611,7 +611,7 @@ def resize_plot_frame(fig, ax, y_data_list: List, label_text_objects: List, args
|
|
|
611
611
|
ax_bbox = ax.get_position()
|
|
612
612
|
cur_ax_w_in = ax_bbox.width * fig_w_in
|
|
613
613
|
cur_ax_h_in = ax_bbox.height * fig_h_in
|
|
614
|
-
print(f"Current canvas
|
|
614
|
+
print(f"Current canvas: {fig_w_in:.2f} x {fig_h_in:.2f} in")
|
|
615
615
|
print(f"Current plot frame: {cur_ax_w_in:.2f} x {cur_ax_h_in:.2f} in (W x H)")
|
|
616
616
|
try:
|
|
617
617
|
spec = input("Enter new plot frame size (e.g. '6 4', '6x4', 'w=6 h=4', 'scale=1.2', single width, q=back): ").strip().lower()
|
|
@@ -651,16 +651,9 @@ def resize_plot_frame(fig, ax, y_data_list: List, label_text_objects: List, args
|
|
|
651
651
|
print("Could not parse specification.")
|
|
652
652
|
continue
|
|
653
653
|
req_w_in, req_h_in = new_w_in, new_h_in
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if new_w_in > max_w_in:
|
|
658
|
-
print(f"Requested width {new_w_in:.2f} exceeds max {max_w_in:.2f}; clamped.")
|
|
659
|
-
new_w_in = max_w_in
|
|
660
|
-
if new_h_in > max_h_in:
|
|
661
|
-
print(f"Requested height {new_h_in:.2f} exceeds max {max_h_in:.2f}; clamped.")
|
|
662
|
-
new_h_in = max_h_in
|
|
663
|
-
min_ax_in = 0.25
|
|
654
|
+
# Apply exact requested size without any clamping
|
|
655
|
+
# Only enforce minimum size to prevent division by zero
|
|
656
|
+
min_ax_in = 0.01
|
|
664
657
|
new_w_in = max(min_ax_in, new_w_in)
|
|
665
658
|
new_h_in = max(min_ax_in, new_h_in)
|
|
666
659
|
tol = 1e-3
|
|
@@ -676,33 +669,24 @@ def resize_plot_frame(fig, ax, y_data_list: List, label_text_objects: List, args
|
|
|
676
669
|
lm, bm, rm, tm = fig._last_user_margins
|
|
677
670
|
fig.subplots_adjust(left=lm, bottom=bm, right=rm, top=tm)
|
|
678
671
|
update_labels_func(ax, y_data_list, label_text_objects, args.stack)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
continue
|
|
672
|
+
fig.canvas.draw_idle()
|
|
673
|
+
print(f"Plot frame unchanged ({new_w_in:.2f} x {new_h_in:.2f} in); layout preserved.")
|
|
674
|
+
continue
|
|
683
675
|
left = (1 - w_frac) / 2
|
|
684
676
|
right = left + w_frac
|
|
685
677
|
bottom = (1 - h_frac) / 2
|
|
686
678
|
top = bottom + h_frac
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
right = min(1 - min_margin_frac, right)
|
|
690
|
-
top = min(1 - min_margin_frac, top)
|
|
691
|
-
if right - left < 0.05 or top - bottom < 0.05:
|
|
692
|
-
print("Requested frame too small after safety clamps; aborting.")
|
|
693
|
-
else:
|
|
694
|
-
fig.subplots_adjust(left=left, right=right, bottom=bottom, top=top)
|
|
679
|
+
# Apply exact size without margin clamping
|
|
680
|
+
fig.subplots_adjust(left=left, right=right, bottom=bottom, top=top)
|
|
695
681
|
update_labels_func(ax, y_data_list, label_text_objects, args.stack)
|
|
696
|
-
|
|
682
|
+
# Store the final size (exactly as requested, no text visibility adjustments)
|
|
697
683
|
sp = fig.subplotpars
|
|
698
|
-
fig._last_user_axes_inches = (
|
|
684
|
+
fig._last_user_axes_inches = (new_w_in, new_h_in)
|
|
699
685
|
fig._last_user_margins = (sp.left, sp.bottom, sp.right, sp.top)
|
|
700
686
|
final_w_in = (sp.right - sp.left) * fig_w_in
|
|
701
687
|
final_h_in = (sp.top - sp.bottom) * fig_h_in
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
else:
|
|
705
|
-
print(f"Plot frame set to {final_w_in:.2f} x {final_h_in:.2f} in inside fixed canvas {fig_w_in:.2f} x {fig_h_in:.2f} in.")
|
|
688
|
+
# Show the requested size (which is what was applied)
|
|
689
|
+
print(f"Plot frame set to {req_w_in:.2f} x {req_h_in:.2f} in inside canvas {fig_w_in:.2f} x {fig_h_in:.2f} in.")
|
|
706
690
|
except KeyboardInterrupt:
|
|
707
691
|
print("Canceled.")
|
|
708
692
|
return
|
batplot/utils.py
CHANGED
|
@@ -777,6 +777,54 @@ def choose_save_path(file_paths: list, purpose: str = "saving") -> Optional[str]
|
|
|
777
777
|
return os.getcwd()
|
|
778
778
|
|
|
779
779
|
|
|
780
|
+
def ensure_exact_case_filename(target_path: str) -> str:
|
|
781
|
+
"""Ensure a file is saved with the exact case specified, even on case-insensitive filesystems.
|
|
782
|
+
|
|
783
|
+
This function handles case-insensitive filesystems (macOS, Windows) by ensuring that
|
|
784
|
+
if a file exists with different case, it is removed first so the new file can be created
|
|
785
|
+
with the exact case specified by the user.
|
|
786
|
+
|
|
787
|
+
On case-sensitive filesystems (Linux, Unix), this function is safe but has no effect
|
|
788
|
+
since files with different case are treated as different files.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
target_path: The desired file path with exact case
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
The same path (for compatibility)
|
|
795
|
+
"""
|
|
796
|
+
folder = os.path.dirname(target_path)
|
|
797
|
+
desired_basename = os.path.basename(target_path)
|
|
798
|
+
|
|
799
|
+
if not folder or not desired_basename:
|
|
800
|
+
return target_path
|
|
801
|
+
|
|
802
|
+
try:
|
|
803
|
+
# Check if file already exists with exact case
|
|
804
|
+
if os.path.exists(target_path):
|
|
805
|
+
# Check if the actual filename on disk matches the desired case
|
|
806
|
+
existing_files = os.listdir(folder)
|
|
807
|
+
for existing_file in existing_files:
|
|
808
|
+
# If same name (case-insensitive) but different case, we need to fix it
|
|
809
|
+
if existing_file.lower() == desired_basename.lower() and existing_file != desired_basename:
|
|
810
|
+
existing_path = os.path.join(folder, existing_file)
|
|
811
|
+
# Delete the existing file with wrong case
|
|
812
|
+
# This is safe on case-insensitive filesystems and has no effect on case-sensitive ones
|
|
813
|
+
try:
|
|
814
|
+
if os.path.exists(existing_path):
|
|
815
|
+
os.remove(existing_path)
|
|
816
|
+
except Exception:
|
|
817
|
+
# Ignore errors (e.g., permission issues, file in use)
|
|
818
|
+
pass
|
|
819
|
+
break
|
|
820
|
+
except Exception:
|
|
821
|
+
# If we can't check/list the directory, just return the path as-is
|
|
822
|
+
# This is safe and ensures we don't break on permission errors
|
|
823
|
+
pass
|
|
824
|
+
|
|
825
|
+
return target_path
|
|
826
|
+
|
|
827
|
+
|
|
780
828
|
def _normalize_extension(ext: str) -> str:
|
|
781
829
|
if not ext:
|
|
782
830
|
return ext
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.27
|
|
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,28 +1,28 @@
|
|
|
1
|
-
batplot/__init__.py,sha256=
|
|
1
|
+
batplot/__init__.py,sha256=goLxYJdJ8EH5VFCsIujxppuLBaTaNllrKqArLH1V3bw,119
|
|
2
2
|
batplot/args.py,sha256=6g1eHVsycDSAenwrXuCTcyuXCy_-30zy1lWtdguBG2s,34983
|
|
3
3
|
batplot/batch.py,sha256=YQ7obCIqLCObwDbM7TXpOBh7g7BO95wZNsa2Fy84c6o,53858
|
|
4
|
-
batplot/batplot.py,sha256=
|
|
4
|
+
batplot/batplot.py,sha256=40lU1nY1NqeAOpzNG_vLF_L34COKhiA19pMpbvA3SJc,171885
|
|
5
5
|
batplot/cif.py,sha256=JfHwNf3SHrcpALc_F5NjJmQ3lg71MBRSaIUJjGYPTx8,30120
|
|
6
6
|
batplot/cli.py,sha256=ScDb2je8VQ0mz_z0SLCHEigiTuFPY5pb1snnzCouKms,5828
|
|
7
|
-
batplot/color_utils.py,sha256=
|
|
7
|
+
batplot/color_utils.py,sha256=7InQLVo1XTg7sgAbltM2KeDSFJgr787YEaV9vJbIoWY,20460
|
|
8
8
|
batplot/config.py,sha256=6nGY7fKN4T5KZUGQS2ArUBgEkLAL0j37XwG5SCVQgKA,6420
|
|
9
9
|
batplot/converters.py,sha256=rR2WMPM0nR5E3eZI3gWbaJf_AfbdQx3urVSbJmZXNzo,8237
|
|
10
|
-
batplot/cpc_interactive.py,sha256=
|
|
11
|
-
batplot/electrochem_interactive.py,sha256=
|
|
12
|
-
batplot/interactive.py,sha256=
|
|
10
|
+
batplot/cpc_interactive.py,sha256=7J_aj9fYXqjTWJWM7QqfyYrQcJCtFErH6mCsluXhzj4,232888
|
|
11
|
+
batplot/electrochem_interactive.py,sha256=W1bWrA8sqpVvYyZC6T5Y8IWQcNGgjRoVJtBWHrMdIcY,218567
|
|
12
|
+
batplot/interactive.py,sha256=cU7i6S2MRUjUA0DwCchIdk8vmnBgpcsZuBPBVsispUI,204281
|
|
13
13
|
batplot/manual.py,sha256=pbRI6G4Pm12pOW8LrOLWWu7IEOtqWN3tRHtgge50LlA,11556
|
|
14
14
|
batplot/modes.py,sha256=qE2OsOQQKhwOWene5zxJeuuewTrZxubtahQuz5je7ok,37252
|
|
15
|
-
batplot/operando.py,sha256=
|
|
16
|
-
batplot/operando_ec_interactive.py,sha256=
|
|
15
|
+
batplot/operando.py,sha256=1i1kb6YKEVXFFKfLWKXr5MwA2hOOQQoVSnT3rgpnWG8,28176
|
|
16
|
+
batplot/operando_ec_interactive.py,sha256=8LxZrebFSrgn8vK8IG0L_DP1i-uoRXQOGO2z5kGVR3M,294374
|
|
17
17
|
batplot/plotting.py,sha256=hG2_EdDhF1Qpn1XfZKdCQ5-w_m9gUYFbr804UQ5QjsU,10841
|
|
18
18
|
batplot/readers.py,sha256=kAI0AvYrdfGRZkvADJ4riN96IWtrH24aAoZpBtONTbw,112960
|
|
19
|
-
batplot/session.py,sha256=
|
|
20
|
-
batplot/style.py,sha256=
|
|
21
|
-
batplot/ui.py,sha256=
|
|
22
|
-
batplot/utils.py,sha256=
|
|
19
|
+
batplot/session.py,sha256=xwEcCM0r_gehJdNZdSUT4Kj5joN9rZ7WsAReetcNFiw,133244
|
|
20
|
+
batplot/style.py,sha256=ig1ozX4dhEsXf5JKaPZOvgVS3CWx-BTFSc3vfAH3Y-E,62274
|
|
21
|
+
batplot/ui.py,sha256=ifpbK74juUzLMCt-sJGVaWtpDb1NMRJzs2YyiwwafzY,35302
|
|
22
|
+
batplot/utils.py,sha256=VtCShOPD4Z_cVODBINclfFdD12pLdSe1av9ZAnE27zY,36043
|
|
23
23
|
batplot/version_check.py,sha256=OG4LuHo5-rSqLLHQo5nWbX9lbNq6NyxRdvVUUcJRBqQ,6219
|
|
24
24
|
batplot/data/USER_MANUAL.md,sha256=VYPvNZt3Fy8Z4Izr2FnQBw9vEaFTPkybhHDnF-OuKws,17694
|
|
25
|
-
batplot-1.7.
|
|
25
|
+
batplot-1.7.27.dist-info/licenses/LICENSE,sha256=2PAnHeCiTfgI7aKZLWr0G56HI9fGKQ0CEbQ02H-yExQ,1065
|
|
26
26
|
batplot_backup_20251121_223043/__init__.py,sha256=3s2DUQuTbWs65hoN9cQQ8IiJbaFJY8fNxiCpwRBYoOA,118
|
|
27
27
|
batplot_backup_20251121_223043/args.py,sha256=OH-h84QhN-IhMS8sPAsSEqccHD3wpeMgmXa_fqv5xtg,21215
|
|
28
28
|
batplot_backup_20251121_223043/batch.py,sha256=oI7PONJyciHDOqNPq-8fnOQMyn9CpAdVznKaEdsy0ig,48650
|
|
@@ -45,8 +45,8 @@ batplot_backup_20251121_223043/style.py,sha256=xg-tj6bEbFUVjjxYMokiLehS4tSfKanLI
|
|
|
45
45
|
batplot_backup_20251121_223043/ui.py,sha256=K0XZWyiuBRNkFod9mgZyJ9CLN78GR1-hh6EznnIb5S8,31208
|
|
46
46
|
batplot_backup_20251121_223043/utils.py,sha256=jydA0JxsCWWAudXEwSjlxTG17y2F8U6hIAukAzi1P0g,32526
|
|
47
47
|
batplot_backup_20251121_223043/version_check.py,sha256=vlHkGkgUJcD_Z4KZmwonxZvKZh0MwHLaBSxaLPc66AQ,4555
|
|
48
|
-
batplot-1.7.
|
|
49
|
-
batplot-1.7.
|
|
50
|
-
batplot-1.7.
|
|
51
|
-
batplot-1.7.
|
|
52
|
-
batplot-1.7.
|
|
48
|
+
batplot-1.7.27.dist-info/METADATA,sha256=wdu3Ad4Go1vIqXzFiwwMKrDkNvYW_y_tCi4jvAXiHt8,7407
|
|
49
|
+
batplot-1.7.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
batplot-1.7.27.dist-info/entry_points.txt,sha256=73GgH3Zs-qGIvgiyQLgGsSW-ryOwPPKHveOW6TDIR5Q,82
|
|
51
|
+
batplot-1.7.27.dist-info/top_level.txt,sha256=CgqK4RpsYnUFAcqO4bLOnEhCoPY4IPEGLPkiDlzLIxg,39
|
|
52
|
+
batplot-1.7.27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|