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
@@ -0,0 +1,228 @@
1
+ """Plotting helpers for batplot.
2
+
3
+ This module contains utility functions for positioning and styling plot labels.
4
+ Labels are the text annotations that identify each curve (e.g., file names).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import List
10
+ import numpy as np
11
+
12
+
13
+ def update_labels(ax, y_data_list: List, label_text_objects: List, stack_mode: bool, stack_label_at_bottom: bool = False):
14
+ """
15
+ Update positions and colors of curve labels on a plot.
16
+
17
+ HOW LABEL POSITIONING WORKS:
18
+ ---------------------------
19
+ Labels identify which curve is which (e.g., "file1.xy", "file2.xy").
20
+ This function positions them intelligently based on the plot mode:
21
+
22
+ STACK MODE (stack_mode=True):
23
+ -----------------------------
24
+ When curves are stacked vertically (--stack flag), each label is placed
25
+ next to its corresponding curve at the right edge of the plot.
26
+
27
+ Positioning:
28
+ - X position: Right edge of plot (x_max in data coordinates)
29
+ - Y position:
30
+ * If stack_label_at_bottom=False: At curve maximum (top of curve)
31
+ * If stack_label_at_bottom=True: At curve minimum + 10% offset (bottom of curve)
32
+
33
+ Coordinate system: Data coordinates (matches curve data)
34
+ Example: If curve goes from y=0 to y=100, label is at (x_max, 100) or (x_max, 10)
35
+
36
+ NORMAL MODE (stack_mode=False):
37
+ --------------------------------
38
+ When curves are overlaid (not stacked), labels form a vertical list
39
+ at the right edge of the plot.
40
+
41
+ Positioning:
42
+ - X position: Right edge (1.0 in axes coordinates = 100% of plot width)
43
+ - Y position: Evenly spaced vertical list
44
+ * If stack_label_at_bottom=False: Top-right, spacing downward
45
+ * If stack_label_at_bottom=True: Bottom-right, spacing upward
46
+
47
+ Coordinate system: Axes coordinates (0.0 to 1.0, independent of data range)
48
+ Example: First label at (1.0, 0.98), second at (1.0, 0.90), etc.
49
+
50
+ COLOR MATCHING:
51
+ --------------
52
+ Each label's text color is set to match its corresponding curve color.
53
+ This makes it easy to identify which label belongs to which curve.
54
+
55
+ Args:
56
+ ax: Matplotlib axes object containing the plot
57
+ y_data_list: List of y-data arrays, one per curve
58
+ Used to find curve min/max for stack mode positioning
59
+ label_text_objects: List of matplotlib Text objects (the labels)
60
+ stack_mode: True if curves are stacked, False if overlaid
61
+ stack_label_at_bottom: If True, place labels at bottom (for stack mode)
62
+ or bottom-right (for normal mode)
63
+ """
64
+ # Early return if no labels to update
65
+ if not label_text_objects:
66
+ return
67
+
68
+ # ====================================================================
69
+ # STACK MODE: Labels positioned next to each curve
70
+ # ====================================================================
71
+ # In stack mode, curves are separated vertically. Each label is placed
72
+ # at the right edge of the plot, aligned with its corresponding curve.
73
+ fig = getattr(ax, 'figure', None)
74
+ label_anchor_left = bool(getattr(fig, '_label_anchor_left', False)) if fig is not None else False
75
+
76
+ # ====================================================================
77
+ if stack_mode:
78
+ # Get plot edges in data coordinates
79
+ x_min, x_max = ax.get_xlim()
80
+ x_pos = x_min if label_anchor_left else x_max
81
+ ha = 'left' if label_anchor_left else 'right'
82
+
83
+ # Position each label next to its curve
84
+ for i, txt in enumerate(label_text_objects):
85
+ # Check if we have data for this curve
86
+ if i < len(y_data_list) and len(y_data_list[i]) > 0:
87
+ # We have actual data, use curve min/max
88
+ if stack_label_at_bottom:
89
+ # Place label at bottom of curve with small upward offset
90
+ # This prevents label from being hidden at the very bottom
91
+ y_min = float(np.min(y_data_list[i])) # Bottom of curve
92
+ y_max = float(np.max(y_data_list[i])) # Top of curve
93
+ y_range = y_max - y_min
94
+ # Position 10% above minimum (small offset for visibility)
95
+ y_pos_curve = y_min + (0.1 * y_range)
96
+ else:
97
+ # Place label at top of curve (default)
98
+ y_pos_curve = float(np.max(y_data_list[i]))
99
+ else:
100
+ # No data available, use plot limits as fallback
101
+ if stack_label_at_bottom:
102
+ y_lim_min = ax.get_ylim()[0] # Bottom of plot
103
+ y_lim_max = ax.get_ylim()[1] # Top of plot
104
+ y_lim_range = y_lim_max - y_lim_min
105
+ # Position 10% above bottom of plot
106
+ y_pos_curve = y_lim_min + (0.1 * y_lim_range)
107
+ else:
108
+ y_pos_curve = ax.get_ylim()[1] # Top of plot
109
+
110
+ # Set coordinate system to data coordinates (matches curve positions)
111
+ # transData means positions are in the same units as the plot data
112
+ txt.set_transform(ax.transData)
113
+
114
+ # Set label position (right edge, aligned with curve)
115
+ txt.set_position((x_pos, y_pos_curve))
116
+ txt.set_ha(ha)
117
+ txt.set_va('bottom' if stack_label_at_bottom else 'top')
118
+
119
+ # Set label color to match curve color (makes identification easier)
120
+ try:
121
+ if i < len(ax.lines):
122
+ # Get color from corresponding line object
123
+ txt.set_color(ax.lines[i].get_color())
124
+ except Exception:
125
+ # If color matching fails, keep default color
126
+ pass
127
+ # ====================================================================
128
+ # NORMAL MODE: Labels form vertical list at right edge
129
+ # ====================================================================
130
+ # In normal mode (overlaid curves), labels are stacked vertically
131
+ # at the right edge of the plot. They use axes coordinates (0.0 to 1.0)
132
+ # so they stay in the same position even if data range changes.
133
+ # ====================================================================
134
+ else:
135
+ n = len(label_text_objects)
136
+
137
+ # Padding from edges (in axes coordinates, where 0.0 = bottom, 1.0 = top)
138
+ top_pad = 0.02 # 2% padding from top
139
+ bottom_pad = 0.05 # 5% padding from bottom (more to avoid x-axis labels)
140
+
141
+ # Calculate spacing between labels
142
+ # Formula: Distribute 90% of vertical space evenly among labels
143
+ # Clamp between 0.025 (minimum) and 0.08 (maximum) for readability
144
+ spacing = min(0.08, max(0.025, 0.90 / max(n, 1)))
145
+
146
+ if stack_label_at_bottom:
147
+ # ============================================================
148
+ # BOTTOM-RIGHT POSITIONING
149
+ # ============================================================
150
+ # Labels start at bottom and stack upward.
151
+ # Useful when top of plot is crowded or you want labels near data.
152
+ # ============================================================
153
+
154
+ # Calculate available vertical space
155
+ available_space = 1.0 - bottom_pad - top_pad
156
+
157
+ # Calculate total height needed for all labels
158
+ total_height = (n - 1) * spacing if n > 1 else 0
159
+
160
+ # If labels would extend beyond top, compress spacing
161
+ # This ensures all labels fit even with many curves
162
+ if total_height > available_space:
163
+ spacing = available_space / max(n - 1, 1)
164
+
165
+ # Start from bottom and stack upward
166
+ start_y = bottom_pad
167
+ for i, txt in enumerate(label_text_objects):
168
+ y_pos = start_y + i * spacing # Each label higher than previous
169
+
170
+ # Ensure we stay within bounds (safety check)
171
+ if y_pos > 1.0 - top_pad:
172
+ y_pos = 1.0 - top_pad
173
+
174
+ # Use axes coordinates (0.0 to 1.0, independent of data)
175
+ # This keeps labels in same position even if data range changes
176
+ txt.set_transform(ax.transAxes)
177
+
178
+ # Position at right edge (1.0 = 100% of plot width)
179
+ txt.set_position((0.0 if label_anchor_left else 1.0, y_pos))
180
+ txt.set_ha('left' if label_anchor_left else 'right')
181
+ txt.set_va('bottom')
182
+
183
+ # Match label color to curve color
184
+ try:
185
+ if i < len(ax.lines):
186
+ txt.set_color(ax.lines[i].get_color())
187
+ except Exception:
188
+ pass
189
+ else:
190
+ # ============================================================
191
+ # TOP-RIGHT POSITIONING (DEFAULT)
192
+ # ============================================================
193
+ # Labels start at top and stack downward.
194
+ # This is the default behavior and works well for most plots.
195
+ # ============================================================
196
+
197
+ # Start from top and stack downward
198
+ start_y = 1.0 - top_pad # Start just below top edge
199
+ for i, txt in enumerate(label_text_objects):
200
+ y_pos = start_y - i * spacing # Each label lower than previous
201
+
202
+ # Ensure we stay within bounds (safety check)
203
+ if y_pos < top_pad:
204
+ y_pos = top_pad
205
+
206
+ # Use axes coordinates (0.0 to 1.0, independent of data)
207
+ txt.set_transform(ax.transAxes)
208
+
209
+ # Position at right edge (1.0 = 100% of plot width)
210
+ txt.set_position((0.0 if label_anchor_left else 1.0, y_pos))
211
+ txt.set_ha('left' if label_anchor_left else 'right')
212
+ txt.set_va('top')
213
+
214
+ # Match label color to curve color
215
+ try:
216
+ if i < len(ax.lines):
217
+ txt.set_color(ax.lines[i].get_color())
218
+ except Exception:
219
+ pass
220
+
221
+ # ====================================================================
222
+ # REDRAW PLOT
223
+ # ====================================================================
224
+ # After updating label positions, tell matplotlib to redraw the plot.
225
+ # draw_idle() schedules a redraw when the GUI is ready (more efficient
226
+ # than immediate draw()).
227
+ # ====================================================================
228
+ ax.figure.canvas.draw_idle()