batplot 1.8.44__tar.gz → 1.8.46__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 (161) hide show
  1. {batplot-1.8.44 → batplot-1.8.46}/DEVELOPING.md +15 -1
  2. batplot-1.8.46/MANIFEST.in +4 -0
  3. {batplot-1.8.44 → batplot-1.8.46}/PKG-INFO +87 -5
  4. {batplot-1.8.44 → batplot-1.8.46}/README.md +86 -4
  5. {batplot-1.8.44 → batplot-1.8.46}/batplot/__init__.py +1 -1
  6. batplot-1.8.46/batplot/_mpl_backend.py +280 -0
  7. {batplot-1.8.44 → batplot-1.8.46}/batplot/args.py +73 -5
  8. {batplot-1.8.44 → batplot-1.8.46}/batplot/batch.py +261 -12
  9. {batplot-1.8.44 → batplot-1.8.46}/batplot/batplot.py +33 -74
  10. {batplot-1.8.44 → batplot-1.8.46}/batplot/canvas_interactive.py +6 -2
  11. {batplot-1.8.44 → batplot-1.8.46}/batplot/cli.py +12 -0
  12. batplot-1.8.46/batplot/cli_save.py +375 -0
  13. {batplot-1.8.44 → batplot-1.8.46}/batplot/color_utils.py +6 -21
  14. {batplot-1.8.44 → batplot-1.8.46}/batplot/converters.py +3 -0
  15. {batplot-1.8.44 → batplot-1.8.46}/batplot/data/CHANGELOG.md +8 -0
  16. batplot-1.8.46/batplot/data/latest_release_notes.json +7 -0
  17. {batplot-1.8.44 → batplot-1.8.46}/batplot/dev_upgrade.py +457 -142
  18. batplot-1.8.46/batplot/plot_modes/batch_session/__init__.py +5 -0
  19. batplot-1.8.46/batplot/plot_modes/batch_session/batch_commands.py +128 -0
  20. batplot-1.8.46/batplot/plot_modes/batch_session/batch_io.py +178 -0
  21. batplot-1.8.46/batplot/plot_modes/batch_session/common.py +139 -0
  22. batplot-1.8.46/batplot/plot_modes/batch_session/kinds.py +45 -0
  23. batplot-1.8.46/batplot/plot_modes/batch_session/load.py +204 -0
  24. batplot-1.8.46/batplot/plot_modes/batch_session/menu_cpc.py +291 -0
  25. batplot-1.8.46/batplot/plot_modes/batch_session/menu_ec.py +318 -0
  26. batplot-1.8.46/batplot/plot_modes/batch_session/menu_histo.py +407 -0
  27. batplot-1.8.46/batplot/plot_modes/batch_session/menu_operando.py +292 -0
  28. batplot-1.8.46/batplot/plot_modes/batch_session/menu_xy.py +429 -0
  29. batplot-1.8.46/batplot/plot_modes/batch_session/routing.py +77 -0
  30. batplot-1.8.46/batplot/plot_modes/batch_session/xy_batch_helpers.py +171 -0
  31. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/palettes.py +5 -3
  32. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/spines.py +9 -2
  33. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/colors.py +4 -3
  34. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/interactive.py +2 -2
  35. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/legend.py +2 -2
  36. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/routing.py +62 -43
  37. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/colors.py +17 -10
  38. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/dqdv_2d.py +1 -1
  39. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/interactive.py +2 -2
  40. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/routing.py +129 -105
  41. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/style.py +2 -2
  42. batplot-1.8.46/batplot/plot_modes/histo/__init__.py +5 -0
  43. batplot-1.8.46/batplot/plot_modes/histo/actions.py +436 -0
  44. batplot-1.8.46/batplot/plot_modes/histo/colors.py +200 -0
  45. batplot-1.8.46/batplot/plot_modes/histo/density_curve.py +152 -0
  46. batplot-1.8.46/batplot/plot_modes/histo/fonts.py +77 -0
  47. batplot-1.8.46/batplot/plot_modes/histo/interactive.py +502 -0
  48. batplot-1.8.46/batplot/plot_modes/histo/labels.py +101 -0
  49. batplot-1.8.46/batplot/plot_modes/histo/load.py +208 -0
  50. batplot-1.8.46/batplot/plot_modes/histo/plot.py +239 -0
  51. batplot-1.8.46/batplot/plot_modes/histo/routing.py +353 -0
  52. batplot-1.8.46/batplot/plot_modes/histo/session.py +61 -0
  53. batplot-1.8.46/batplot/plot_modes/histo/spines.py +291 -0
  54. batplot-1.8.46/batplot/plot_modes/histo/toggles.py +174 -0
  55. batplot-1.8.46/batplot/plot_modes/histo/wizard.py +188 -0
  56. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/colors.py +4 -5
  57. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/interactive.py +8 -6
  58. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/labels.py +1 -0
  59. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/plot.py +24 -12
  60. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/routing.py +42 -34
  61. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/style.py +2 -2
  62. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/session_routing.py +70 -56
  63. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/interactive.py +2 -2
  64. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/pipeline.py +59 -28
  65. {batplot-1.8.44 → batplot-1.8.46}/batplot/readers.py +26 -7
  66. {batplot-1.8.44 → batplot-1.8.46}/batplot/session.py +7 -7
  67. {batplot-1.8.44 → batplot-1.8.46}/batplot/version_check.py +8 -7
  68. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/PKG-INFO +87 -5
  69. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/SOURCES.txt +35 -0
  70. {batplot-1.8.44 → batplot-1.8.46}/pyproject.toml +8 -2
  71. batplot-1.8.46/tests/test_batch_session.py +241 -0
  72. batplot-1.8.46/tests/test_bruker_intensity.py +25 -0
  73. batplot-1.8.46/tests/test_cli_save.py +72 -0
  74. {batplot-1.8.44 → batplot-1.8.46}/tests/test_cli_smoke.py +42 -0
  75. {batplot-1.8.44 → batplot-1.8.46}/tests/test_cpc_roundtrip.py +9 -1
  76. {batplot-1.8.44 → batplot-1.8.46}/tests/test_dev_upgrade.py +108 -0
  77. {batplot-1.8.44 → batplot-1.8.46}/tests/test_ec_roundtrip.py +1 -1
  78. batplot-1.8.46/tests/test_histo.py +598 -0
  79. {batplot-1.8.44 → batplot-1.8.46}/tests/test_interactive_menu_smoke.py +2 -2
  80. batplot-1.8.46/tests/test_mpl_backend.py +143 -0
  81. {batplot-1.8.44 → batplot-1.8.46}/tests/test_operando_roundtrip.py +3 -3
  82. batplot-1.8.44/MANIFEST.in +0 -3
  83. {batplot-1.8.44 → batplot-1.8.46}/LICENSE +0 -0
  84. {batplot-1.8.44 → batplot-1.8.46}/NOTICE +0 -0
  85. {batplot-1.8.44 → batplot-1.8.46}/batplot/cif.py +0 -0
  86. {batplot-1.8.44 → batplot-1.8.46}/batplot/config.py +0 -0
  87. {batplot-1.8.44 → batplot-1.8.46}/batplot/ec_common.py +0 -0
  88. {batplot-1.8.44 → batplot-1.8.46}/batplot/modes.py +0 -0
  89. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/__init__.py +0 -0
  90. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/__init__.py +0 -0
  91. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/axis_state.py +0 -0
  92. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/files.py +0 -0
  93. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/fonts.py +0 -0
  94. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/interactive_state.py +0 -0
  95. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/menu_rendering.py +0 -0
  96. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/menus.py +0 -0
  97. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/smoothing.py +0 -0
  98. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/sources.py +0 -0
  99. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/terminal.py +0 -0
  100. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/common/title_offsets.py +0 -0
  101. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/__init__.py +0 -0
  102. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/actions.py +0 -0
  103. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/labels.py +0 -0
  104. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/menu.py +0 -0
  105. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/session.py +0 -0
  106. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/cpc/snapshots.py +0 -0
  107. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/__init__.py +0 -0
  108. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/actions.py +0 -0
  109. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/export.py +0 -0
  110. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/labels.py +0 -0
  111. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/legend.py +0 -0
  112. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/legend_order.py +0 -0
  113. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/line_style.py +0 -0
  114. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/menu.py +0 -0
  115. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/session.py +0 -0
  116. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/electrochem/spine_colors.py +0 -0
  117. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/__init__.py +0 -0
  118. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/actions.py +0 -0
  119. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/grid.py +0 -0
  120. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/ions_axis.py +0 -0
  121. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/layout.py +0 -0
  122. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/line_style.py +0 -0
  123. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/menu.py +0 -0
  124. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/peaks.py +0 -0
  125. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/session.py +0 -0
  126. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/operando/visibility.py +0 -0
  127. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/__init__.py +0 -0
  128. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/actions.py +0 -0
  129. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/arrange.py +0 -0
  130. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/axis_range.py +0 -0
  131. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/cif.py +0 -0
  132. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/colors.py +0 -0
  133. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/data_ops.py +0 -0
  134. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/derivative.py +0 -0
  135. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/game.py +0 -0
  136. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/labels.py +0 -0
  137. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/line_style.py +0 -0
  138. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/menu.py +0 -0
  139. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/peaks.py +0 -0
  140. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/session.py +0 -0
  141. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/smoothing.py +0 -0
  142. {batplot-1.8.44 → batplot-1.8.46}/batplot/plot_modes/xy/style.py +0 -0
  143. {batplot-1.8.44 → batplot-1.8.46}/batplot/plotting.py +0 -0
  144. {batplot-1.8.44 → batplot-1.8.46}/batplot/showcol.py +0 -0
  145. {batplot-1.8.44 → batplot-1.8.46}/batplot/style.py +0 -0
  146. {batplot-1.8.44 → batplot-1.8.46}/batplot/ui.py +0 -0
  147. {batplot-1.8.44 → batplot-1.8.46}/batplot/utils.py +0 -0
  148. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/dependency_links.txt +0 -0
  149. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/entry_points.txt +0 -0
  150. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/requires.txt +0 -0
  151. {batplot-1.8.44 → batplot-1.8.46}/batplot.egg-info/top_level.txt +0 -0
  152. {batplot-1.8.44 → batplot-1.8.46}/setup.cfg +0 -0
  153. {batplot-1.8.44 → batplot-1.8.46}/setup.py +0 -0
  154. {batplot-1.8.44 → batplot-1.8.46}/tests/test_color_utils.py +0 -0
  155. {batplot-1.8.44 → batplot-1.8.46}/tests/test_common_files.py +0 -0
  156. {batplot-1.8.44 → batplot-1.8.46}/tests/test_common_palettes.py +0 -0
  157. {batplot-1.8.44 → batplot-1.8.46}/tests/test_contracts.py +0 -0
  158. {batplot-1.8.44 → batplot-1.8.46}/tests/test_csv_readers.py +0 -0
  159. {batplot-1.8.44 → batplot-1.8.46}/tests/test_interactive_state.py +0 -0
  160. {batplot-1.8.44 → batplot-1.8.46}/tests/test_xy_modules.py +0 -0
  161. {batplot-1.8.44 → batplot-1.8.46}/tests/test_xy_roundtrip.py +0 -0
@@ -429,9 +429,23 @@ Before changing release behavior:
429
429
 
430
430
  1. Read `batplot/dev_upgrade.py`.
431
431
  2. Add or update tests in `tests/test_dev_upgrade.py`.
432
- 3. Confirm future files are included through package discovery or manifest rules.
432
+ 3. Confirm future files are included automatically:
433
+ - Python modules: `packages.find include = ["batplot*"]` plus
434
+ `validate_distribution_contents()` (every `batplot/**/*.py` must be in the wheel).
435
+ - Package data: drop files into `batplot/data/` (except `USER_MANUAL.md`);
436
+ `--dev-upgrade` runs `sync_pyproject_package_data()` to refresh
437
+ `[tool.setuptools.package-data]`.
438
+ - Git: `_git_stage_release_snapshot()` stages tracked updates and all untracked
439
+ non-ignored paths (no hand-maintained file list).
433
440
  4. Keep GitHub/PyPI conflict handling explicit and testable.
434
441
 
442
+ Canonical version files checked on every release:
443
+
444
+ - `batplot/__init__.py` (`__version__`)
445
+ - `pyproject.toml` (`[project] version`)
446
+ - `CITATION.cff` (`version: v…`)
447
+ - `batplot/data/latest_release_notes.json` (`version` field)
448
+
435
449
  ## Cross-Platform Checklist
436
450
 
437
451
  Use this checklist before finishing code changes:
@@ -0,0 +1,4 @@
1
+ include README.md LICENSE DEVELOPING.md
2
+ exclude batplot/data/USER_MANUAL.md
3
+ include batplot/data/CHANGELOG.md
4
+ include batplot/data/latest_release_notes.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.8.44
3
+ Version: 1.8.46
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
@@ -64,9 +64,10 @@ With a single line of command to easily plot publication-ready plots with custom
64
64
  - **Electrochemistry Plot**: Galvanostatic cycling (GC), cyclic voltammetry (CV), differential capacity (dQdV), capacity per cycle (CPC) with multi-file support
65
65
  - **1D XY plot**: Designed for XRD, PDF, XAS (XANES/EXAFS) but also support other types
66
66
  - **Operando Contour plot**: Correlate in-situ characterizations (XRD/PDF/XAS) with electrochemical data
67
+ - **Histogram mode**: Column histograms from tabular `.csv`/`.txt` data (e.g. particle-size lists)
67
68
  - **Interactive plotting**: Real-time editing customized for each type of plottings
68
69
  - **Session Persistence**: Save and reload complete plot states with `.pkl` files
69
- - **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files
70
+ - **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files (histogram: `.bpsh`)
70
71
  - **Batch Processing**: Export each file separately with `--all`
71
72
  - **Column preview**: `--showcol` prints numbered columns, header names when found, and the first 10 values per column (CSV, Excel, text, .mpt, .brml, Bruker .raw, etc.)
72
73
 
@@ -190,6 +191,21 @@ batplot file1.xy file2.xy --stack --i
190
191
  batplot allfiles --xaxis 2theta --xrange 15 75 --i
191
192
  ```
192
193
 
194
+ ### Session save without interactive menu (`--save`)
195
+
196
+ ```bash
197
+ # Single file: default .pkl name = data file stem; choose save folder when prompted
198
+ batplot pattern.xye --xaxis 2theta --save
199
+
200
+ # Batch: one session per file (default names), folder chosen once
201
+ batplot --all --xaxis 2theta --xrange 10 80 --save
202
+
203
+ # Combined plot (allfiles, operando, multi-file GC/CPC): you must name the session
204
+ batplot allfiles --xaxis q --save
205
+ batplot --operando --wl 0.25 --save
206
+ batplot file1.csv file2.csv --gc --save
207
+ ```
208
+
193
209
  ---
194
210
 
195
211
  ## Electrochemistry Mode
@@ -296,16 +312,80 @@ Operando column selection:
296
312
 
297
313
  ---
298
314
 
315
+ ## Histogram Mode
316
+
317
+ Histogram mode plots a single numeric column from tabular `.csv` or `.txt` files. It is designed for particle-size distributions and similar column data exported from spreadsheets or image-analysis tools.
318
+
319
+ ### Basic usage
320
+
321
+ ```bash
322
+ # Interactive wizard + styling menu
323
+ batplot sizes.csv --histo --i
324
+
325
+ # Non-interactive: column, range, bins, save figure
326
+ batplot data.txt --histo --histocol Length --xrange 0 16 --binwidth 1 --out hist.png
327
+
328
+ # Preview columns before choosing
329
+ batplot --showcol sizes.csv
330
+ ```
331
+
332
+ ### Startup wizard (`--i`)
333
+
334
+ 1. Choose the column to histogram (numbered list with preview)
335
+ 2. Set histogram range (`xmin xmax` or `auto`)
336
+ 3. Set bin width or `bins=N`
337
+
338
+ ### Batch export
339
+
340
+ Export each CSV/TXT file in the folder as a separate figure under `Figures/`:
341
+
342
+ ```bash
343
+ batplot --all --histo --histocol Length
344
+ batplot allfiles --histo --histocol 7 --binwidth 1
345
+ batplot histo_folder/ --histo --histocol Length
346
+
347
+ # Apply a shared histogram style (.bpsh)
348
+ batplot --all mystyle.bpsh --histo --histocol Length
349
+ ```
350
+
351
+ `--histocol` is required for batch export (column number or header name).
352
+
353
+ ### Batch interactive editing
354
+
355
+ Edit two or more histograms together (sync colors, fonts, export):
356
+
357
+ ```bash
358
+ batplot allfiles --histo --i
359
+ ```
360
+
361
+ If `--histocol` is omitted, the wizard runs on the first file and the same column/bin layout is reused for the rest.
362
+
363
+ ### Flags
364
+
365
+ | Flag | Description |
366
+ |------|-------------|
367
+ | `--histo` | Launch histogram mode |
368
+ | `--histocol N` | Column to histogram (1-indexed or header name) |
369
+ | `--xrange A B` | Histogram display range |
370
+ | `--binwidth W` | Width of each bin |
371
+ | `--bins N` | Number of equal-width bins |
372
+ | `--all` | Batch export each CSV/TXT file (requires `--histocol`) |
373
+ | `allfiles` | Expand all CSV/TXT in folder (batch export or `--i` batch edit) |
374
+
375
+ Style files: `.bpsh` (export with `p` in the interactive menu).
376
+
377
+ ---
378
+
299
379
  ## Plotting multiple files
300
380
 
301
381
  ```bash
302
382
  # All XY files in current directory on same figure
303
- batplot allfiles
304
- batplot allfiles --stack --i
383
+ batplot allfiles --xaxis 2theta --i
384
+ batplot allfiles --stack --gc --i
305
385
 
306
386
  # Only specific extension (natural-sorted)
307
387
  batplot allxyfiles
308
- batplot "/path/to/data" allnorfiles --i
388
+ batplot "/path/to/data" allnorfiles --xaxis energy --i
309
389
 
310
390
  # Explicit file list
311
391
  batplot file1.xye file2.qye structure.cif:1.54 --stack --i
@@ -351,6 +431,7 @@ With `--interactive`:
351
431
  - **Styling**: Line widths, markers, fonts
352
432
  - **Axes**: Labels, limits, ticks, spine styles
353
433
  - **Export**: Sessions (`.pkl`), styles (`.bps`/`.bpsg`), high-res images. Colors persist via `p` (print style), `i` (import), `s` (save session), `b` (undo)
434
+ - **Non-interactive session save**: `--save` (without `--i`) prompts for folder and filename—the same `.pkl` sessions as interactive `s`, with default names for single-file and `--all` batch runs
354
435
  - **Live Preview**: All changes update in real-time
355
436
 
356
437
  ---
@@ -362,6 +443,7 @@ batplot --h # General help
362
443
  batplot --h xy # XY mode guide
363
444
  batplot --h ec # Electrochemistry guide
364
445
  batplot --h op # Operando guide
446
+ batplot --h histo # Histogram mode guide
365
447
  batplot --v # Version and release notes
366
448
  batplot --m # Open illustrated manual
367
449
  ```
@@ -10,9 +10,10 @@ With a single line of command to easily plot publication-ready plots with custom
10
10
  - **Electrochemistry Plot**: Galvanostatic cycling (GC), cyclic voltammetry (CV), differential capacity (dQdV), capacity per cycle (CPC) with multi-file support
11
11
  - **1D XY plot**: Designed for XRD, PDF, XAS (XANES/EXAFS) but also support other types
12
12
  - **Operando Contour plot**: Correlate in-situ characterizations (XRD/PDF/XAS) with electrochemical data
13
+ - **Histogram mode**: Column histograms from tabular `.csv`/`.txt` data (e.g. particle-size lists)
13
14
  - **Interactive plotting**: Real-time editing customized for each type of plottings
14
15
  - **Session Persistence**: Save and reload complete plot states with `.pkl` files
15
- - **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files
16
+ - **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files (histogram: `.bpsh`)
16
17
  - **Batch Processing**: Export each file separately with `--all`
17
18
  - **Column preview**: `--showcol` prints numbered columns, header names when found, and the first 10 values per column (CSV, Excel, text, .mpt, .brml, Bruker .raw, etc.)
18
19
 
@@ -136,6 +137,21 @@ batplot file1.xy file2.xy --stack --i
136
137
  batplot allfiles --xaxis 2theta --xrange 15 75 --i
137
138
  ```
138
139
 
140
+ ### Session save without interactive menu (`--save`)
141
+
142
+ ```bash
143
+ # Single file: default .pkl name = data file stem; choose save folder when prompted
144
+ batplot pattern.xye --xaxis 2theta --save
145
+
146
+ # Batch: one session per file (default names), folder chosen once
147
+ batplot --all --xaxis 2theta --xrange 10 80 --save
148
+
149
+ # Combined plot (allfiles, operando, multi-file GC/CPC): you must name the session
150
+ batplot allfiles --xaxis q --save
151
+ batplot --operando --wl 0.25 --save
152
+ batplot file1.csv file2.csv --gc --save
153
+ ```
154
+
139
155
  ---
140
156
 
141
157
  ## Electrochemistry Mode
@@ -242,16 +258,80 @@ Operando column selection:
242
258
 
243
259
  ---
244
260
 
261
+ ## Histogram Mode
262
+
263
+ Histogram mode plots a single numeric column from tabular `.csv` or `.txt` files. It is designed for particle-size distributions and similar column data exported from spreadsheets or image-analysis tools.
264
+
265
+ ### Basic usage
266
+
267
+ ```bash
268
+ # Interactive wizard + styling menu
269
+ batplot sizes.csv --histo --i
270
+
271
+ # Non-interactive: column, range, bins, save figure
272
+ batplot data.txt --histo --histocol Length --xrange 0 16 --binwidth 1 --out hist.png
273
+
274
+ # Preview columns before choosing
275
+ batplot --showcol sizes.csv
276
+ ```
277
+
278
+ ### Startup wizard (`--i`)
279
+
280
+ 1. Choose the column to histogram (numbered list with preview)
281
+ 2. Set histogram range (`xmin xmax` or `auto`)
282
+ 3. Set bin width or `bins=N`
283
+
284
+ ### Batch export
285
+
286
+ Export each CSV/TXT file in the folder as a separate figure under `Figures/`:
287
+
288
+ ```bash
289
+ batplot --all --histo --histocol Length
290
+ batplot allfiles --histo --histocol 7 --binwidth 1
291
+ batplot histo_folder/ --histo --histocol Length
292
+
293
+ # Apply a shared histogram style (.bpsh)
294
+ batplot --all mystyle.bpsh --histo --histocol Length
295
+ ```
296
+
297
+ `--histocol` is required for batch export (column number or header name).
298
+
299
+ ### Batch interactive editing
300
+
301
+ Edit two or more histograms together (sync colors, fonts, export):
302
+
303
+ ```bash
304
+ batplot allfiles --histo --i
305
+ ```
306
+
307
+ If `--histocol` is omitted, the wizard runs on the first file and the same column/bin layout is reused for the rest.
308
+
309
+ ### Flags
310
+
311
+ | Flag | Description |
312
+ |------|-------------|
313
+ | `--histo` | Launch histogram mode |
314
+ | `--histocol N` | Column to histogram (1-indexed or header name) |
315
+ | `--xrange A B` | Histogram display range |
316
+ | `--binwidth W` | Width of each bin |
317
+ | `--bins N` | Number of equal-width bins |
318
+ | `--all` | Batch export each CSV/TXT file (requires `--histocol`) |
319
+ | `allfiles` | Expand all CSV/TXT in folder (batch export or `--i` batch edit) |
320
+
321
+ Style files: `.bpsh` (export with `p` in the interactive menu).
322
+
323
+ ---
324
+
245
325
  ## Plotting multiple files
246
326
 
247
327
  ```bash
248
328
  # All XY files in current directory on same figure
249
- batplot allfiles
250
- batplot allfiles --stack --i
329
+ batplot allfiles --xaxis 2theta --i
330
+ batplot allfiles --stack --gc --i
251
331
 
252
332
  # Only specific extension (natural-sorted)
253
333
  batplot allxyfiles
254
- batplot "/path/to/data" allnorfiles --i
334
+ batplot "/path/to/data" allnorfiles --xaxis energy --i
255
335
 
256
336
  # Explicit file list
257
337
  batplot file1.xye file2.qye structure.cif:1.54 --stack --i
@@ -297,6 +377,7 @@ With `--interactive`:
297
377
  - **Styling**: Line widths, markers, fonts
298
378
  - **Axes**: Labels, limits, ticks, spine styles
299
379
  - **Export**: Sessions (`.pkl`), styles (`.bps`/`.bpsg`), high-res images. Colors persist via `p` (print style), `i` (import), `s` (save session), `b` (undo)
380
+ - **Non-interactive session save**: `--save` (without `--i`) prompts for folder and filename—the same `.pkl` sessions as interactive `s`, with default names for single-file and `--all` batch runs
300
381
  - **Live Preview**: All changes update in real-time
301
382
 
302
383
  ---
@@ -308,6 +389,7 @@ batplot --h # General help
308
389
  batplot --h xy # XY mode guide
309
390
  batplot --h ec # Electrochemistry guide
310
391
  batplot --h op # Operando guide
392
+ batplot --h histo # Histogram mode guide
311
393
  batplot --v # Version and release notes
312
394
  batplot --m # Open illustrated manual
313
395
  ```
@@ -7,7 +7,7 @@ import importlib.abc
7
7
  import importlib.machinery
8
8
  import sys
9
9
 
10
- __version__ = "1.8.44"
10
+ __version__ = "1.8.46"
11
11
 
12
12
 
13
13
  _LEGACY_MODULE_ALIASES = {
@@ -0,0 +1,280 @@
1
+ """Matplotlib backend selection for batplot CLI and interactive sessions.
2
+
3
+ Imported early from :mod:`batplot.cli` so pytest/CI on Windows never fall back
4
+ to Tk when ``MPLBACKEND=Agg`` is set explicitly in the environment.
5
+
6
+ When the user has not set ``MPLBACKEND``, we start on Agg for safe imports but
7
+ may switch to a GUI backend before opening interactive windows (``.pkl`` reload,
8
+ ``--gc --i``, ``--canvas``, etc.).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ import os
15
+ import sys
16
+
17
+ _USER_SET_MPLBACKEND = "MPLBACKEND" in os.environ
18
+ _DEFAULT_BACKEND = os.environ.get("MPLBACKEND", "Agg")
19
+
20
+ import matplotlib as _mpl
21
+
22
+ _mpl.use(_DEFAULT_BACKEND, force=True)
23
+
24
+ _INTERACTIVE_BACKENDS = frozenset(
25
+ {
26
+ "macosx",
27
+ "tkagg",
28
+ "qt5agg",
29
+ "qt4agg",
30
+ "qtagg",
31
+ "wxagg",
32
+ "gtk3agg",
33
+ "gtk4agg",
34
+ "wx",
35
+ "qt",
36
+ "gtk",
37
+ "gtk3",
38
+ "gtk4",
39
+ }
40
+ )
41
+
42
+
43
+ def _is_noninteractive_backend(name: str | None) -> bool:
44
+ if not isinstance(name, str):
45
+ return False
46
+ low = name.lower()
47
+ if low in _INTERACTIVE_BACKENDS:
48
+ return False
49
+ return ("agg" in low) or ("inline" in low) or (low in {"pdf", "ps", "svg", "template"})
50
+
51
+
52
+ def is_interactive_backend() -> bool:
53
+ """Return True when the active Matplotlib backend can open a GUI window."""
54
+ try:
55
+ return not _is_noninteractive_backend(_mpl.get_backend())
56
+ except Exception:
57
+ return False
58
+
59
+
60
+ def _headless_context() -> bool:
61
+ """True when batplot must stay on a non-interactive backend (CI/pytest)."""
62
+ if os.environ.get("BATPLOT_HEADLESS", "").lower() in ("1", "true", "yes"):
63
+ return True
64
+ if os.environ.get("CI", "").lower() in ("1", "true", "yes"):
65
+ return True
66
+ if os.environ.get("PYTEST_CURRENT_TEST"):
67
+ return True
68
+ if "pytest" in sys.modules:
69
+ return True
70
+ return False
71
+
72
+
73
+ def running_headless() -> bool:
74
+ """True under pytest, CI, or when ``BATPLOT_HEADLESS`` is set."""
75
+ return _headless_context()
76
+
77
+
78
+ def wants_interactive_window(args) -> bool:
79
+ """Return True when this CLI invocation should open an interactive figure."""
80
+ if args is None:
81
+ return False
82
+ if getattr(args, "all", None) is not None:
83
+ return False
84
+ files = getattr(args, "files", None) or []
85
+ if any(str(f).lower().endswith(".pkl") for f in files):
86
+ return True
87
+ if getattr(args, "canvas", False):
88
+ return True
89
+ if getattr(args, "interactive", False):
90
+ return True
91
+ # Headless export / session save: --out, --savefig, or --save (without --i).
92
+ if getattr(args, "savefig", False) or getattr(args, "out", None):
93
+ return False
94
+ if getattr(args, "save", False) and not getattr(args, "interactive", False):
95
+ return False
96
+ for attr in ("operando", "contour", "gc", "cv", "dqdv", "cpc", "epc", "histo"):
97
+ if getattr(args, attr, False):
98
+ return True
99
+ # Default XY / multi-file diffraction view (no --out, no --all).
100
+ if files:
101
+ return True
102
+ return False
103
+
104
+
105
+ def _should_respect_env_agg() -> bool:
106
+ """Keep Agg when the environment requested it and we are in headless context."""
107
+ if not _USER_SET_MPLBACKEND:
108
+ return False
109
+ env_be = os.environ.get("MPLBACKEND")
110
+ if not env_be or not _is_noninteractive_backend(env_be):
111
+ return False
112
+ return _headless_context()
113
+
114
+
115
+ def _has_qt_bindings() -> bool:
116
+ for mod in ("PyQt6", "PyQt5", "PySide6", "PySide2"):
117
+ if importlib.util.find_spec(mod) is not None:
118
+ return True
119
+ return False
120
+
121
+
122
+ def _gui_backend_order() -> list[str]:
123
+ if sys.platform == "darwin":
124
+ return ["MacOSX", "TkAgg", "QtAgg"]
125
+ if sys.platform.startswith("win"):
126
+ return ["TkAgg", "QtAgg"]
127
+ return ["TkAgg", "QtAgg", "Gtk4Agg", "Gtk3Agg"]
128
+
129
+
130
+ def _backend_can_be_tried(name: str) -> bool:
131
+ if name == "TkAgg":
132
+ return importlib.util.find_spec("tkinter") is not None
133
+ if name == "QtAgg":
134
+ return _has_qt_bindings()
135
+ return True
136
+
137
+
138
+ def ensure_gui_backend(args=None) -> bool:
139
+ """Switch from Agg to a GUI backend when an interactive window is expected.
140
+
141
+ Returns True when the active backend is interactive (or was switched to one).
142
+ Honors ``MPLBACKEND=Agg`` only under pytest/CI; interactive CLI use (``--i``,
143
+ ``.pkl`` reload) overrides an inherited Agg default from conda/shell profiles.
144
+ """
145
+ if args is not None and not wants_interactive_window(args):
146
+ return is_interactive_backend()
147
+
148
+ if _should_respect_env_agg():
149
+ return False
150
+
151
+ if _USER_SET_MPLBACKEND:
152
+ env_be = os.environ.get("MPLBACKEND")
153
+ if env_be and not _is_noninteractive_backend(env_be):
154
+ return True
155
+
156
+ if is_interactive_backend():
157
+ return True
158
+
159
+ for cand in _gui_backend_order():
160
+ if not _backend_can_be_tried(cand):
161
+ continue
162
+ try:
163
+ _mpl.use(cand, force=True)
164
+ if is_interactive_backend():
165
+ return True
166
+ except Exception:
167
+ continue
168
+ return False
169
+
170
+
171
+ def _one_line_backend_workaround() -> str:
172
+ """Return a copy-paste command prefix for the current platform."""
173
+ if sys.platform == "darwin":
174
+ return "MPLBACKEND=MacOSX batplot ..."
175
+ if sys.platform.startswith("win"):
176
+ return 'set MPLBACKEND=TkAgg (cmd) or $env:MPLBACKEND="TkAgg" (PowerShell), then batplot ...'
177
+ return "MPLBACKEND=TkAgg batplot ..."
178
+
179
+
180
+ def _print_interactive_backend_help(context: str = "interactive menu") -> None:
181
+ """User-facing recovery steps when no GUI window can be opened."""
182
+ try:
183
+ backend = _mpl.get_backend()
184
+ except Exception:
185
+ backend = "unknown"
186
+ try:
187
+ from . import __version__ as _bp_version
188
+ except Exception:
189
+ _bp_version = "latest"
190
+
191
+ print()
192
+ print("Interactive plotting needs a display window, but batplot could not open one.")
193
+ print(f"(Matplotlib backend: {backend})")
194
+ if context:
195
+ print(f"This affects: {context}.")
196
+ print()
197
+ print("Try these steps, in order:")
198
+ print(" 1. Upgrade batplot (fixes most cases):")
199
+ print(" pip install --upgrade batplot")
200
+ print(f" You need the latest batplot release (installed: {_bp_version}).")
201
+ print(" 2. If you cannot upgrade yet, force a GUI backend for this session:")
202
+ print(f" {_one_line_backend_workaround()}")
203
+ print(" 3. To save a figure without the interactive menu:")
204
+ print(" batplot ... --out figure.png (omit --i)")
205
+ print()
206
+ print("Advanced: if MPLBACKEND=Agg is set in your shell or conda env, unset it or override")
207
+ print("as in step 2. On headless servers (SSH with no display), use --out instead of --i.")
208
+ print()
209
+
210
+
211
+ def warn_if_noninteractive(context: str = "interactive menu") -> bool:
212
+ """Print a helpful message when the backend cannot show a window."""
213
+ if is_interactive_backend():
214
+ return True
215
+ _print_interactive_backend_help(context)
216
+ return False
217
+
218
+
219
+ def require_interactive_display(
220
+ args=None,
221
+ *,
222
+ context: str = "interactive menu",
223
+ export_hint: str | None = None,
224
+ ) -> bool:
225
+ """Ensure a GUI backend, warn if unavailable, return True when display is possible."""
226
+ ensure_gui_backend(args)
227
+ if is_interactive_backend():
228
+ return True
229
+ warn_if_noninteractive(context)
230
+ return False
231
+
232
+
233
+ def show_figure_if_possible(args=None, *, block: bool = True) -> bool:
234
+ """Show the current figure when the backend supports windows.
235
+
236
+ Defaults to ``block=True`` so the window stays open until the user closes it.
237
+ Non-interactive view mode (no ``--i``) relies on this; ``block=False`` would
238
+ return immediately and the process would exit before the figure is visible.
239
+ """
240
+ ensure_gui_backend(args)
241
+ if not is_interactive_backend():
242
+ if args is not None and not (getattr(args, "savefig", False) or getattr(args, "out", None)):
243
+ print("No display window available. Save the figure with: batplot ... --out figure.png")
244
+ return False
245
+ import matplotlib.pyplot as plt
246
+
247
+ plt.show(block=block)
248
+ return True
249
+
250
+
251
+ def prime_interactive_figure(fig=None) -> None:
252
+ """Prepare a matplotlib window before an interactive menu (non-blocking)."""
253
+ import matplotlib.pyplot as plt
254
+
255
+ try:
256
+ plt.ion()
257
+ except Exception:
258
+ pass
259
+ if fig is not None:
260
+ try:
261
+ fig.canvas.draw_idle()
262
+ fig.canvas.flush_events()
263
+ except Exception:
264
+ pass
265
+ try:
266
+ plt.show(block=False)
267
+ except Exception:
268
+ pass
269
+
270
+
271
+ def hold_figure_open() -> None:
272
+ """Keep the current figure window open until the user closes it."""
273
+ if not is_interactive_backend():
274
+ return
275
+ import matplotlib.pyplot as plt
276
+
277
+ try:
278
+ plt.show()
279
+ except Exception:
280
+ pass