batplot 1.8.55__tar.gz → 1.8.57__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 (349) hide show
  1. {batplot-1.8.55 → batplot-1.8.57}/PKG-INFO +4 -3
  2. {batplot-1.8.55 → batplot-1.8.57}/README.md +3 -2
  3. {batplot-1.8.55 → batplot-1.8.57}/batplot/__init__.py +1 -1
  4. {batplot-1.8.55 → batplot-1.8.57}/batplot/args.py +1 -0
  5. {batplot-1.8.55 → batplot-1.8.57}/batplot/data/CHANGELOG.md +8 -0
  6. {batplot-1.8.55 → batplot-1.8.57}/batplot/data/latest_release_notes.json +1 -1
  7. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/ec_batch_helpers.py +34 -0
  8. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_ec.py +70 -0
  9. batplot-1.8.57/batplot/plot_modes/electrochem/dqdv_norm.py +419 -0
  10. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/interactive.py +25 -0
  11. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/menu.py +2 -0
  12. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/session.py +40 -0
  13. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/smoothing_menu.py +17 -0
  14. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/style.py +10 -0
  15. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/style_apply.py +26 -0
  16. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/undo_state.py +43 -2
  17. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/PKG-INFO +4 -3
  18. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/SOURCES.txt +2 -0
  19. {batplot-1.8.55 → batplot-1.8.57}/pyproject.toml +1 -1
  20. batplot-1.8.57/tests/test_dqdv_normalize.py +169 -0
  21. {batplot-1.8.55 → batplot-1.8.57}/DEVELOPING.md +0 -0
  22. {batplot-1.8.55 → batplot-1.8.57}/LICENSE +0 -0
  23. {batplot-1.8.55 → batplot-1.8.57}/MANIFEST.in +0 -0
  24. {batplot-1.8.55 → batplot-1.8.57}/NOTICE +0 -0
  25. {batplot-1.8.55 → batplot-1.8.57}/batplot/_mpl_backend.py +0 -0
  26. {batplot-1.8.55 → batplot-1.8.57}/batplot/batch.py +0 -0
  27. {batplot-1.8.55 → batplot-1.8.57}/batplot/batplot.py +0 -0
  28. {batplot-1.8.55 → batplot-1.8.57}/batplot/canvas_interactive.py +0 -0
  29. {batplot-1.8.55 → batplot-1.8.57}/batplot/cif.py +0 -0
  30. {batplot-1.8.55 → batplot-1.8.57}/batplot/cli.py +0 -0
  31. {batplot-1.8.55 → batplot-1.8.57}/batplot/cli_save.py +0 -0
  32. {batplot-1.8.55 → batplot-1.8.57}/batplot/color_utils.py +0 -0
  33. {batplot-1.8.55 → batplot-1.8.57}/batplot/config.py +0 -0
  34. {batplot-1.8.55 → batplot-1.8.57}/batplot/converters.py +0 -0
  35. {batplot-1.8.55 → batplot-1.8.57}/batplot/dev_upgrade.py +0 -0
  36. {batplot-1.8.55 → batplot-1.8.57}/batplot/ec_common.py +0 -0
  37. {batplot-1.8.55 → batplot-1.8.57}/batplot/modes.py +0 -0
  38. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/__init__.py +0 -0
  39. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/__init__.py +0 -0
  40. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_commands.py +0 -0
  41. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_crosshair.py +0 -0
  42. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_figure_io.py +0 -0
  43. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_geom_helpers.py +0 -0
  44. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_io.py +0 -0
  45. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_menu_helpers.py +0 -0
  46. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_menu_io.py +0 -0
  47. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_panel_state.py +0 -0
  48. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/batch_scoped_sync.py +0 -0
  49. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/common.py +0 -0
  50. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/cpc_batch_helpers.py +0 -0
  51. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/dqdv_2d_batch_helpers.py +0 -0
  52. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/histo_batch_helpers.py +0 -0
  53. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/kinds.py +0 -0
  54. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/load.py +0 -0
  55. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_cpc.py +0 -0
  56. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_dqdv_2d.py +0 -0
  57. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_histo.py +0 -0
  58. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_operando.py +0 -0
  59. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/menu_xy.py +0 -0
  60. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/operando_batch_helpers.py +0 -0
  61. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/routing.py +0 -0
  62. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/batch_session/xy_batch_helpers.py +0 -0
  63. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/__init__.py +0 -0
  64. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/axis_state.py +0 -0
  65. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/batch_font.py +0 -0
  66. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/color_menu_help.py +0 -0
  67. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/crosshair_export.py +0 -0
  68. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/curve_look_help.py +0 -0
  69. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/files.py +0 -0
  70. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/font_extras.py +0 -0
  71. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/fonts.py +0 -0
  72. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/interactive_state.py +0 -0
  73. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/layout_compat.py +0 -0
  74. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/line_dash.py +0 -0
  75. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/menu_rendering.py +0 -0
  76. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/menus.py +0 -0
  77. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/overview_metrics.py +0 -0
  78. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/palettes.py +0 -0
  79. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/session_data_guarantee.py +0 -0
  80. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/session_helpers.py +0 -0
  81. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/session_key_smoke.py +0 -0
  82. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/size_spec.py +0 -0
  83. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/smoothing.py +0 -0
  84. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/sources.py +0 -0
  85. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/spines.py +0 -0
  86. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/state_capture.py +0 -0
  87. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/style_routing.py +0 -0
  88. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/terminal.py +0 -0
  89. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/common/title_offsets.py +0 -0
  90. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/__init__.py +0 -0
  91. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/actions.py +0 -0
  92. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/add_file.py +0 -0
  93. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/colors.py +0 -0
  94. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/interactive.py +0 -0
  95. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/labels.py +0 -0
  96. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/legend.py +0 -0
  97. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/legend_order.py +0 -0
  98. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/load.py +0 -0
  99. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/menu.py +0 -0
  100. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/overview.py +0 -0
  101. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/panel_menus.py +0 -0
  102. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/routing.py +0 -0
  103. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/session.py +0 -0
  104. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/snapshots.py +0 -0
  105. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/style.py +0 -0
  106. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/cpc/wasd_menu.py +0 -0
  107. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/__init__.py +0 -0
  108. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/actions.py +0 -0
  109. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/capacity_cum.py +0 -0
  110. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/colors.py +0 -0
  111. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/dqdv_2d.py +0 -0
  112. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/dual_axis_menu.py +0 -0
  113. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/export.py +0 -0
  114. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/labels.py +0 -0
  115. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/legend.py +0 -0
  116. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/legend_order.py +0 -0
  117. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/line_style.py +0 -0
  118. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/overview.py +0 -0
  119. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/routing.py +0 -0
  120. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/electrochem/spine_colors.py +0 -0
  121. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/__init__.py +0 -0
  122. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/actions.py +0 -0
  123. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/colors.py +0 -0
  124. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/density_curve.py +0 -0
  125. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/fonts.py +0 -0
  126. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/interactive.py +0 -0
  127. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/labels.py +0 -0
  128. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/line_style.py +0 -0
  129. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/load.py +0 -0
  130. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/plot.py +0 -0
  131. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/routing.py +0 -0
  132. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/session.py +0 -0
  133. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/spines.py +0 -0
  134. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/toggles.py +0 -0
  135. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/wizard.py +0 -0
  136. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/histo/y_range.py +0 -0
  137. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/__init__.py +0 -0
  138. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/actions.py +0 -0
  139. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/axes_limits_menu.py +0 -0
  140. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/axis_units.py +0 -0
  141. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/cif_menu.py +0 -0
  142. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/colors.py +0 -0
  143. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/grid.py +0 -0
  144. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/intensity_menu.py +0 -0
  145. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/interactive.py +0 -0
  146. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/ions_axis.py +0 -0
  147. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/labels.py +0 -0
  148. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/layout.py +0 -0
  149. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/layout_menu.py +0 -0
  150. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/line_style.py +0 -0
  151. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/menu.py +0 -0
  152. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/peaks.py +0 -0
  153. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/plot.py +0 -0
  154. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/routing.py +0 -0
  155. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/session.py +0 -0
  156. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/spine_colors.py +0 -0
  157. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/style.py +0 -0
  158. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/style_apply.py +0 -0
  159. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/undo_state.py +0 -0
  160. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/operando/visibility.py +0 -0
  161. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/session_routing.py +0 -0
  162. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/__init__.py +0 -0
  163. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/actions.py +0 -0
  164. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/arrange.py +0 -0
  165. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/axis_range.py +0 -0
  166. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/axis_units.py +0 -0
  167. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/cif.py +0 -0
  168. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/colors.py +0 -0
  169. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/data_ops.py +0 -0
  170. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/derivative.py +0 -0
  171. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/full_data.py +0 -0
  172. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/game.py +0 -0
  173. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/interactive.py +0 -0
  174. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/labels.py +0 -0
  175. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/line_style.py +0 -0
  176. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/menu.py +0 -0
  177. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/offset_menu.py +0 -0
  178. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/peaks.py +0 -0
  179. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/pipeline.py +0 -0
  180. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/session.py +0 -0
  181. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/smoothing.py +0 -0
  182. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/spines.py +0 -0
  183. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/style.py +0 -0
  184. {batplot-1.8.55 → batplot-1.8.57}/batplot/plot_modes/xy/undo_state.py +0 -0
  185. {batplot-1.8.55 → batplot-1.8.57}/batplot/plotting.py +0 -0
  186. {batplot-1.8.55 → batplot-1.8.57}/batplot/readers.py +0 -0
  187. {batplot-1.8.55 → batplot-1.8.57}/batplot/readers_common.py +0 -0
  188. {batplot-1.8.55 → batplot-1.8.57}/batplot/readers_ec.py +0 -0
  189. {batplot-1.8.55 → batplot-1.8.57}/batplot/readers_xrd.py +0 -0
  190. {batplot-1.8.55 → batplot-1.8.57}/batplot/screen_color.py +0 -0
  191. {batplot-1.8.55 → batplot-1.8.57}/batplot/session.py +0 -0
  192. {batplot-1.8.55 → batplot-1.8.57}/batplot/showcol.py +0 -0
  193. {batplot-1.8.55 → batplot-1.8.57}/batplot/strip_header.py +0 -0
  194. {batplot-1.8.55 → batplot-1.8.57}/batplot/style.py +0 -0
  195. {batplot-1.8.55 → batplot-1.8.57}/batplot/ui.py +0 -0
  196. {batplot-1.8.55 → batplot-1.8.57}/batplot/utils.py +0 -0
  197. {batplot-1.8.55 → batplot-1.8.57}/batplot/version_check.py +0 -0
  198. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/dependency_links.txt +0 -0
  199. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/entry_points.txt +0 -0
  200. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/requires.txt +0 -0
  201. {batplot-1.8.55 → batplot-1.8.57}/batplot.egg-info/top_level.txt +0 -0
  202. {batplot-1.8.55 → batplot-1.8.57}/setup.cfg +0 -0
  203. {batplot-1.8.55 → batplot-1.8.57}/setup.py +0 -0
  204. {batplot-1.8.55 → batplot-1.8.57}/tests/test_all_menus_smoke.py +0 -0
  205. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_all_modes_sync.py +0 -0
  206. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_cpc_key_isolation.py +0 -0
  207. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_crosshair.py +0 -0
  208. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_dqdv_key_isolation.py +0 -0
  209. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_ec_cycles_status.py +0 -0
  210. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_font_menu.py +0 -0
  211. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_gc_key_isolation.py +0 -0
  212. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_geom_menu.py +0 -0
  213. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_geometry_pisb_keys.py +0 -0
  214. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_histo_key_isolation.py +0 -0
  215. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_menu_parity.py +0 -0
  216. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_operando_key_isolation.py +0 -0
  217. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_panel_status.py +0 -0
  218. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_pisb_contract.py +0 -0
  219. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_scoped_sync_modes.py +0 -0
  220. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_session.py +0 -0
  221. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_spine_sync.py +0 -0
  222. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_style_pisb_keys.py +0 -0
  223. {batplot-1.8.55 → batplot-1.8.57}/tests/test_batch_xy_key_isolation.py +0 -0
  224. {batplot-1.8.55 → batplot-1.8.57}/tests/test_bc_new_fields_defaults.py +0 -0
  225. {batplot-1.8.55 → batplot-1.8.57}/tests/test_bruker_intensity.py +0 -0
  226. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cif_tick_crystallography.py +0 -0
  227. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cli_all_flags.py +0 -0
  228. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cli_save.py +0 -0
  229. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cli_smoke.py +0 -0
  230. {batplot-1.8.55 → batplot-1.8.57}/tests/test_color_display_hex.py +0 -0
  231. {batplot-1.8.55 → batplot-1.8.57}/tests/test_color_listing_visible.py +0 -0
  232. {batplot-1.8.55 → batplot-1.8.57}/tests/test_color_utils.py +0 -0
  233. {batplot-1.8.55 → batplot-1.8.57}/tests/test_common_files.py +0 -0
  234. {batplot-1.8.55 → batplot-1.8.57}/tests/test_common_palettes.py +0 -0
  235. {batplot-1.8.55 → batplot-1.8.57}/tests/test_contracts.py +0 -0
  236. {batplot-1.8.55 → batplot-1.8.57}/tests/test_convert_xrd.py +0 -0
  237. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cpc_add_file.py +0 -0
  238. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cpc_canvas_undo.py +0 -0
  239. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cpc_legend_order.py +0 -0
  240. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cpc_roundtrip.py +0 -0
  241. {batplot-1.8.55 → batplot-1.8.57}/tests/test_critical_stability_fixes.py +0 -0
  242. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cross_os_deeper.py +0 -0
  243. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cross_os_oe_paths.py +0 -0
  244. {batplot-1.8.55 → batplot-1.8.57}/tests/test_cross_platform_parity.py +0 -0
  245. {batplot-1.8.55 → batplot-1.8.57}/tests/test_crosshair_export.py +0 -0
  246. {batplot-1.8.55 → batplot-1.8.57}/tests/test_csv_readers.py +0 -0
  247. {batplot-1.8.55 → batplot-1.8.57}/tests/test_curve_look_help.py +0 -0
  248. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_noninteractive_bugs.py +0 -0
  249. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_residual_pisb_gates.py +0 -0
  250. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round10_bc_safe.py +0 -0
  251. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round11_title_fidelity.py +0 -0
  252. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round12_commercial_gates.py +0 -0
  253. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round13_final_key_audit.py +0 -0
  254. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round3_pisb_gates.py +0 -0
  255. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round4_pisb_gates.py +0 -0
  256. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round5_pisb_gates.py +0 -0
  257. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round6_session_fidelity.py +0 -0
  258. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round7_root_helpers.py +0 -0
  259. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round8_root_helpers.py +0 -0
  260. {batplot-1.8.55 → batplot-1.8.57}/tests/test_deep_round9_root_helpers.py +0 -0
  261. {batplot-1.8.55 → batplot-1.8.57}/tests/test_default_sizes_and_oe_persistence.py +0 -0
  262. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dev_upgrade.py +0 -0
  263. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dqdv_contour_session_keys.py +0 -0
  264. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dqdv_smooth_status.py +0 -0
  265. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dual_axis_menu_audit.py +0 -0
  266. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dual_mode_pisb.py +0 -0
  267. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dual_pisb_hard_gates.py +0 -0
  268. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dual_tk_a_coupling.py +0 -0
  269. {batplot-1.8.55 → batplot-1.8.57}/tests/test_dual_top_wasd_both_layers.py +0 -0
  270. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_cpc_geometry_pisb_keys.py +0 -0
  271. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_cpc_style_pisb_keys.py +0 -0
  272. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_cycle_identity_roundtrip.py +0 -0
  273. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_cycles_three_color_modes.py +0 -0
  274. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_multifile_color_menu.py +0 -0
  275. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_old_session_three_color_modes.py +0 -0
  276. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_roundtrip.py +0 -0
  277. {batplot-1.8.55 → batplot-1.8.57}/tests/test_ec_session_bc_new_keys.py +0 -0
  278. {batplot-1.8.55 → batplot-1.8.57}/tests/test_every_key_behavioral_walk.py +0 -0
  279. {batplot-1.8.55 → batplot-1.8.57}/tests/test_file_dialogs_os.py +0 -0
  280. {batplot-1.8.55 → batplot-1.8.57}/tests/test_final_audit_residuals.py +0 -0
  281. {batplot-1.8.55 → batplot-1.8.57}/tests/test_full_menu_audit_gaps.py +0 -0
  282. {batplot-1.8.55 → batplot-1.8.57}/tests/test_gc_cumulative.py +0 -0
  283. {batplot-1.8.55 → batplot-1.8.57}/tests/test_geom_size_highlight.py +0 -0
  284. {batplot-1.8.55 → batplot-1.8.57}/tests/test_hard_check_twin_and_batch.py +0 -0
  285. {batplot-1.8.55 → batplot-1.8.57}/tests/test_histo.py +0 -0
  286. {batplot-1.8.55 → batplot-1.8.57}/tests/test_histo_geometry_pisb_keys.py +0 -0
  287. {batplot-1.8.55 → batplot-1.8.57}/tests/test_histo_interactive_imports.py +0 -0
  288. {batplot-1.8.55 → batplot-1.8.57}/tests/test_histo_style_pisb_keys.py +0 -0
  289. {batplot-1.8.55 → batplot-1.8.57}/tests/test_imk_stderr_guard.py +0 -0
  290. {batplot-1.8.55 → batplot-1.8.57}/tests/test_interactive_handler_intent.py +0 -0
  291. {batplot-1.8.55 → batplot-1.8.57}/tests/test_interactive_menu_smoke.py +0 -0
  292. {batplot-1.8.55 → batplot-1.8.57}/tests/test_interactive_state.py +0 -0
  293. {batplot-1.8.55 → batplot-1.8.57}/tests/test_label_shortcuts_math_help.py +0 -0
  294. {batplot-1.8.55 → batplot-1.8.57}/tests/test_line_dash_styles.py +0 -0
  295. {batplot-1.8.55 → batplot-1.8.57}/tests/test_line_submenu_pisb_widths.py +0 -0
  296. {batplot-1.8.55 → batplot-1.8.57}/tests/test_menu_handler_imports.py +0 -0
  297. {batplot-1.8.55 → batplot-1.8.57}/tests/test_menu_prompt_order.py +0 -0
  298. {batplot-1.8.55 → batplot-1.8.57}/tests/test_menu_rendering_presentation.py +0 -0
  299. {batplot-1.8.55 → batplot-1.8.57}/tests/test_mpl_backend.py +0 -0
  300. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_axis_units.py +0 -0
  301. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_batch_menu.py +0 -0
  302. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_cif_add.py +0 -0
  303. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_cif_color.py +0 -0
  304. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_geometry_pisb_keys.py +0 -0
  305. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_layout_menu.py +0 -0
  306. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_peaks_axis_units.py +0 -0
  307. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_roundtrip.py +0 -0
  308. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_spine_isolation.py +0 -0
  309. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_style_pisb_keys.py +0 -0
  310. {batplot-1.8.55 → batplot-1.8.57}/tests/test_operando_submenu_smoke.py +0 -0
  311. {batplot-1.8.55 → batplot-1.8.57}/tests/test_overview_metrics.py +0 -0
  312. {batplot-1.8.55 → batplot-1.8.57}/tests/test_pi_axis_units_persistence.py +0 -0
  313. {batplot-1.8.55 → batplot-1.8.57}/tests/test_pisb_deep_all_modes.py +0 -0
  314. {batplot-1.8.55 → batplot-1.8.57}/tests/test_quit_confirm_export_save.py +0 -0
  315. {batplot-1.8.55 → batplot-1.8.57}/tests/test_recent_axis_names.py +0 -0
  316. {batplot-1.8.55 → batplot-1.8.57}/tests/test_screen_color.py +0 -0
  317. {batplot-1.8.55 → batplot-1.8.57}/tests/test_screen_color_pisb.py +0 -0
  318. {batplot-1.8.55 → batplot-1.8.57}/tests/test_secondary_spine_colors.py +0 -0
  319. {batplot-1.8.55 → batplot-1.8.57}/tests/test_session_backward_compat.py +0 -0
  320. {batplot-1.8.55 → batplot-1.8.57}/tests/test_session_font_restore.py +0 -0
  321. {batplot-1.8.55 → batplot-1.8.57}/tests/test_session_full_data_guarantee.py +0 -0
  322. {batplot-1.8.55 → batplot-1.8.57}/tests/test_session_save_integrity.py +0 -0
  323. {batplot-1.8.55 → batplot-1.8.57}/tests/test_session_style_restore_parity.py +0 -0
  324. {batplot-1.8.55 → batplot-1.8.57}/tests/test_spine_colors_pisb.py +0 -0
  325. {batplot-1.8.55 → batplot-1.8.57}/tests/test_spine_wasd_figure_help.py +0 -0
  326. {batplot-1.8.55 → batplot-1.8.57}/tests/test_spine_wasd_pisb_all_modes.py +0 -0
  327. {batplot-1.8.55 → batplot-1.8.57}/tests/test_stack_rearrange_tick_spacing.py +0 -0
  328. {batplot-1.8.55 → batplot-1.8.57}/tests/test_state_capture.py +0 -0
  329. {batplot-1.8.55 → batplot-1.8.57}/tests/test_strip_header.py +0 -0
  330. {batplot-1.8.55 → batplot-1.8.57}/tests/test_stupid_input_hardening.py +0 -0
  331. {batplot-1.8.55 → batplot-1.8.57}/tests/test_style_import_integrity.py +0 -0
  332. {batplot-1.8.55 → batplot-1.8.57}/tests/test_style_layout_compat.py +0 -0
  333. {batplot-1.8.55 → batplot-1.8.57}/tests/test_style_routing.py +0 -0
  334. {batplot-1.8.55 → batplot-1.8.57}/tests/test_tick_display_color_contract.py +0 -0
  335. {batplot-1.8.55 → batplot-1.8.57}/tests/test_tick_visibility_save_truth.py +0 -0
  336. {batplot-1.8.55 → batplot-1.8.57}/tests/test_undo_deep_all_modes.py +0 -0
  337. {batplot-1.8.55 → batplot-1.8.57}/tests/test_undo_stack_integrity.py +0 -0
  338. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xrd_crosshair_bragg.py +0 -0
  339. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_axis_units.py +0 -0
  340. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_bps_bpsg_key_matrix.py +0 -0
  341. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_cif_add.py +0 -0
  342. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_full_data_retention.py +0 -0
  343. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_generic_xy_default.py +0 -0
  344. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_geometry_pisb_keys.py +0 -0
  345. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_modules.py +0 -0
  346. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_offset_menu.py +0 -0
  347. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_roundtrip.py +0 -0
  348. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_source_files.py +0 -0
  349. {batplot-1.8.55 → batplot-1.8.57}/tests/test_xy_style_pisb_keys.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.8.55
3
+ Version: 1.8.57
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
@@ -85,7 +85,7 @@ pip install batplot
85
85
  ## Quick Start
86
86
 
87
87
  - User manual: https://chem-plot.github.io/batplot/
88
- - Tutorial: https://drive.google.com/file/d/1NTFJWNBbWW4mgz0H5ZelGjuOBWxoFgkr/view
88
+ - Tutorial videos: https://www.youtube.com/watch?v=gnX1ytqlV4k&list=PLaJaXAfs1irk
89
89
  - Demo files: https://github.com/chem-plot/batplot/blob/main/batplot_demo_files.zip
90
90
 
91
91
  ---
@@ -491,7 +491,8 @@ With `--interactive`:
491
491
 
492
492
  ## Help & Documentation
493
493
 
494
- Illustrated user manual (GitHub Pages): https://chem-plot.github.io/batplot/
494
+ - Illustrated user manual: https://chem-plot.github.io/batplot/
495
+ - Tutorial videos: https://www.youtube.com/watch?v=gnX1ytqlV4k&list=PLaJaXAfs1irk
495
496
 
496
497
  ```bash
497
498
  batplot --h # General help
@@ -29,7 +29,7 @@ pip install batplot
29
29
  ## Quick Start
30
30
 
31
31
  - User manual: https://chem-plot.github.io/batplot/
32
- - Tutorial: https://drive.google.com/file/d/1NTFJWNBbWW4mgz0H5ZelGjuOBWxoFgkr/view
32
+ - Tutorial videos: https://www.youtube.com/watch?v=gnX1ytqlV4k&list=PLaJaXAfs1irk
33
33
  - Demo files: https://github.com/chem-plot/batplot/blob/main/batplot_demo_files.zip
34
34
 
35
35
  ---
@@ -435,7 +435,8 @@ With `--interactive`:
435
435
 
436
436
  ## Help & Documentation
437
437
 
438
- Illustrated user manual (GitHub Pages): https://chem-plot.github.io/batplot/
438
+ - Illustrated user manual: https://chem-plot.github.io/batplot/
439
+ - Tutorial videos: https://www.youtube.com/watch?v=gnX1ytqlV4k&list=PLaJaXAfs1irk
439
440
 
440
441
  ```bash
441
442
  batplot --h # General help
@@ -7,7 +7,7 @@ import importlib.abc
7
7
  import importlib.machinery
8
8
  import sys
9
9
 
10
- __version__ = "1.8.55"
10
+ __version__ = "1.8.57"
11
11
 
12
12
 
13
13
  _LEGACY_MODULE_ALIASES = {
@@ -201,6 +201,7 @@ def _print_general_help() -> None:
201
201
  " Personal page: https://www.mn.uio.no/kjemi/english/people/aca/tianda/\n"
202
202
  " GitHub: https://github.com/chem-plot/batplot\n"
203
203
  " User manual: https://chem-plot.github.io/batplot/\n"
204
+ " Tutorial videos: https://www.youtube.com/watch?v=gnX1ytqlV4k&list=PLaJaXAfs1irk\n"
204
205
  " Kindly cite Tian's github page if the plot is used for publication\n"
205
206
  )
206
207
  _print_help(msg)
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.56] - 2026-09-25
4
+ - Add normalization suppor for dQdV mode
5
+
6
+
7
+ ## [1.8.57] - 2026-09-25
8
+ - Bug fixes
9
+
10
+
3
11
  ## [1.8.55] - 2026-09-15
4
12
  - Bug fixes
5
13
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.55",
2
+ "version": "1.8.57",
3
3
  "custom_message": "- Bug fixes",
4
4
  "update_notes": [
5
5
  "- Bug fixes"
@@ -55,6 +55,7 @@ __all__ = [
55
55
  "apply_ec_line_chrome_only",
56
56
  "apply_ec_scoped_sync",
57
57
  "apply_ec_smooth_only",
58
+ "apply_ec_norm_only",
58
59
  "apply_ec_spine_colors_only",
59
60
  "apply_ec_wasd_chrome_only",
60
61
  "default_ec_tick_state",
@@ -526,6 +527,39 @@ def apply_ec_smooth_only(panel: EcPanel, cfg: dict, *, silent: bool = True) -> b
526
527
  return True
527
528
 
528
529
 
530
+ def apply_ec_norm_only(panel: EcPanel, cfg: dict, *, silent: bool = True) -> bool:
531
+ """Batch ``nm`` (dQdV): sync normalize settings and re-apply (not colors / WASD)."""
532
+ from ..electrochem.dqdv_norm import apply_dqdv_norm, reset_dqdv_norm
533
+
534
+ cfg = dict(cfg) if isinstance(cfg, dict) else {}
535
+ norm = cfg.get("_dqdv_norm_settings")
536
+ if not isinstance(norm, dict):
537
+ return False
538
+ file_data, cycle_lines, is_multi = ec_normalize_file_data(panel)
539
+ try:
540
+ if bool(norm.get("enabled")):
541
+ apply_dqdv_norm(
542
+ panel.fig,
543
+ file_data=file_data if is_multi else None,
544
+ cycle_lines=cycle_lines,
545
+ method=norm.get("method"),
546
+ visible_only=False,
547
+ ax=panel.ax,
548
+ )
549
+ else:
550
+ reset_dqdv_norm(
551
+ panel.fig,
552
+ file_data=file_data if is_multi else None,
553
+ cycle_lines=cycle_lines,
554
+ ax=panel.ax,
555
+ )
556
+ except Exception as exc:
557
+ if not silent:
558
+ print(f"dQ/dV normalize sync failed for {getattr(panel, 'path', '?')}: {exc}")
559
+ return False
560
+ return True
561
+
562
+
529
563
  def ec_panel_is_dqdv(panel: EcPanel) -> bool:
530
564
  """True when this EC panel is a dQ/dV (differential capacity) session."""
531
565
  ax = getattr(panel, "ax", None)
@@ -82,6 +82,7 @@ from .ec_batch_helpers import (
82
82
  apply_ec_legend_only,
83
83
  apply_ec_line_chrome_only,
84
84
  apply_ec_smooth_only,
85
+ apply_ec_norm_only,
85
86
  apply_ec_spine_colors_only,
86
87
  apply_ec_wasd_chrome_only,
87
88
  default_ec_tick_state,
@@ -103,6 +104,7 @@ from .ec_batch_helpers import (
103
104
  from ..electrochem.interactive import _apply_stored_smooth_settings
104
105
  from ..electrochem.legend_order import run_ec_legend_order_menu
105
106
  from ..electrochem.smoothing_menu import run_dqdv_smoothing_menu
107
+ from ..electrochem.dqdv_norm import run_dqdv_norm_menu
106
108
  from .load import EcPanel
107
109
 
108
110
 
@@ -140,6 +142,8 @@ def _print_ec_batch_menu(panels: List[EcPanel]) -> None:
140
142
  ]
141
143
  if is_dqdv:
142
144
  col1.insert(2, "sm: smooth")
145
+ # Batch: normalize each panel (multi-file overlay or peer single-file panels).
146
+ col1.insert(3, "nm: normalize")
143
147
  if _ec_batch_has_multi_file(panels):
144
148
  # Match interactive: only list file visibility when multi-file
145
149
  col1.insert(-1, "v: show/hide files")
@@ -389,10 +393,76 @@ def run_ec_batch_menu(panels: List[EcPanel]) -> None:
389
393
  safe_input=safe_input,
390
394
  colorize_menu=_colorize_menu,
391
395
  colorize_prompt=colorize_prompt,
396
+ ax=ref.ax,
392
397
  ),
393
398
  )
394
399
  continue
395
400
 
401
+ if cmd == "nm":
402
+ if not is_dqdv:
403
+ print("'nm' is only available in batch dQ/dV mode.")
404
+ continue
405
+ file_data, cycle_lines, is_multi_file, print_file_list = _ref_menu_context(ref)
406
+ # Interactive nm requires multi-file on the ref panel; for single-file
407
+ # batch peers, apply normalize directly then sync settings.
408
+ def _edit_nm() -> None:
409
+ if is_multi_file and file_data and len(file_data) >= 2:
410
+ run_dqdv_norm_menu(
411
+ fig=ref.fig,
412
+ ax=ref.ax,
413
+ cycle_lines=cycle_lines,
414
+ file_data=file_data,
415
+ current_file_idx=0,
416
+ all_cycles=ec_all_cycles(cycle_lines, file_data),
417
+ is_dqdv=True,
418
+ is_multi_file=True,
419
+ menu_title="",
420
+ canvas_mode=False,
421
+ print_menu=lambda *_a, **_k: None,
422
+ print_file_list=print_file_list,
423
+ push_state=noop_snapshot,
424
+ safe_input=safe_input,
425
+ colorize_menu=_colorize_menu,
426
+ colorize_prompt=colorize_prompt,
427
+ )
428
+ else:
429
+ from ..electrochem.dqdv_norm import (
430
+ apply_dqdv_norm,
431
+ format_dqdv_norm_status,
432
+ reset_dqdv_norm,
433
+ )
434
+
435
+ print("\n" + format_dqdv_norm_status(ref.fig))
436
+ print(" a: normalize this panel | r: reset | q: back")
437
+ sub = safe_input(colorize_prompt("Normalize (a/r/q): ")).strip().lower()
438
+ if sub in ("a", "all"):
439
+ apply_dqdv_norm(
440
+ ref.fig,
441
+ file_data=None,
442
+ cycle_lines=cycle_lines,
443
+ ax=ref.ax,
444
+ )
445
+ print("Normalized.")
446
+ elif sub == "r":
447
+ reset_dqdv_norm(
448
+ ref.fig,
449
+ file_data=None,
450
+ cycle_lines=cycle_lines,
451
+ ax=ref.ax,
452
+ )
453
+ print("Normalize reset.")
454
+
455
+ edit_ref_then_sync(
456
+ ref,
457
+ panels,
458
+ undo=undo,
459
+ capture_panel=_capture_panel,
460
+ apply_cfg=apply_ec_norm_only,
461
+ draw_all=lambda: draw_panels(panels),
462
+ edit_fn=_edit_nm,
463
+ )
464
+ continue
465
+
396
466
  if cmd == "t":
397
467
  run_ec_batch_spine_menu(
398
468
  ref,
@@ -0,0 +1,419 @@
1
+ """Multi-file dQ/dV Y-normalization (instrument-agnostic).
2
+
3
+ Works on already-plotted ``Line2D`` data after Neware / BioLogic / CS-B /
4
+ numerical loaders. Default method ``max_abs`` scales each file so
5
+ ``max(|y|) == 1`` across that file's charge+discharge curves — useful when
6
+ overlaying absolute ``dQ/dV`` with specific ``dQm/dV`` exports.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Dict, Iterable, List, Optional, Sequence
12
+
13
+ import numpy as np
14
+
15
+
16
+ _NORM_ATTR_PRE = "_dqdv_pre_norm_ydata"
17
+ _NORM_ATTR_APPLIED = "_dqdv_norm_applied"
18
+ _NORM_ATTR_SCALE = "_dqdv_norm_scale"
19
+
20
+ DEFAULT_METHOD = "max_abs"
21
+
22
+
23
+ def default_dqdv_norm_settings() -> Dict[str, Any]:
24
+ return {
25
+ "enabled": False,
26
+ "method": DEFAULT_METHOD,
27
+ # Parallel to file_data when multi-file; empty when off.
28
+ "file_scales": [],
29
+ }
30
+
31
+
32
+ def get_dqdv_norm_settings(fig) -> Dict[str, Any]:
33
+ raw = getattr(fig, "_dqdv_norm_settings", None)
34
+ if not isinstance(raw, dict):
35
+ return default_dqdv_norm_settings()
36
+ out = default_dqdv_norm_settings()
37
+ out["enabled"] = bool(raw.get("enabled", False))
38
+ method = raw.get("method") or DEFAULT_METHOD
39
+ out["method"] = str(method) if method else DEFAULT_METHOD
40
+ scales = raw.get("file_scales")
41
+ if isinstance(scales, (list, tuple)):
42
+ try:
43
+ out["file_scales"] = [float(x) for x in scales]
44
+ except (TypeError, ValueError):
45
+ out["file_scales"] = []
46
+ return out
47
+
48
+
49
+ def set_dqdv_norm_settings(fig, settings: Optional[dict]) -> None:
50
+ if not isinstance(settings, dict) or not settings:
51
+ fig._dqdv_norm_settings = default_dqdv_norm_settings()
52
+ return
53
+ fig._dqdv_norm_settings = {
54
+ "enabled": bool(settings.get("enabled", False)),
55
+ "method": str(settings.get("method") or DEFAULT_METHOD),
56
+ "file_scales": list(settings.get("file_scales") or []),
57
+ }
58
+
59
+
60
+ def format_dqdv_norm_status(fig) -> str:
61
+ cfg = get_dqdv_norm_settings(fig)
62
+ if not cfg.get("enabled"):
63
+ return "Current: normalize off"
64
+ method = cfg.get("method") or DEFAULT_METHOD
65
+ scales = cfg.get("file_scales") or []
66
+ if scales:
67
+ bits = ", ".join(f"{i + 1}:{s:.4g}" for i, s in enumerate(scales))
68
+ return f"Current: normalize on ({method}); scales [{bits}]"
69
+ return f"Current: normalize on ({method})"
70
+
71
+
72
+ def _iter_lines(cycle_lines) -> Iterable[Any]:
73
+ if not isinstance(cycle_lines, dict):
74
+ return
75
+ for _cyc, parts in cycle_lines.items():
76
+ if isinstance(parts, dict):
77
+ for role in ("charge", "discharge"):
78
+ ln = parts.get(role)
79
+ if ln is not None:
80
+ yield ln
81
+ elif parts is not None:
82
+ yield parts
83
+
84
+
85
+ def _clear_line_norm_attrs(ln) -> None:
86
+ for attr in (_NORM_ATTR_PRE, _NORM_ATTR_APPLIED, _NORM_ATTR_SCALE):
87
+ if hasattr(ln, attr):
88
+ try:
89
+ delattr(ln, attr)
90
+ except Exception:
91
+ pass
92
+
93
+
94
+ def clear_norm_attrs_on_cycle_lines(cycle_lines) -> None:
95
+ for ln in _iter_lines(cycle_lines):
96
+ _clear_line_norm_attrs(ln)
97
+
98
+
99
+ def _restore_pre_norm_y(ln) -> bool:
100
+ if not bool(getattr(ln, _NORM_ATTR_APPLIED, False)):
101
+ return False
102
+ pre = getattr(ln, _NORM_ATTR_PRE, None)
103
+ if pre is None:
104
+ return False
105
+ try:
106
+ ln.set_ydata(np.asarray(pre, float))
107
+ ln._dqdv_norm_applied = False
108
+ return True
109
+ except Exception:
110
+ return False
111
+
112
+
113
+ def _peak_abs(y: np.ndarray) -> float:
114
+ if y.size == 0:
115
+ return 0.0
116
+ finite = y[np.isfinite(y)]
117
+ if finite.size == 0:
118
+ return 0.0
119
+ return float(np.max(np.abs(finite)))
120
+
121
+
122
+ def _normalize_cycle_lines(cycle_lines, *, method: str = DEFAULT_METHOD) -> float:
123
+ """Normalize one file's curves in place. Returns the scale factor used."""
124
+ lines = list(_iter_lines(cycle_lines))
125
+ if not lines:
126
+ return 1.0
127
+ for ln in lines:
128
+ _restore_pre_norm_y(ln)
129
+ bases: List[np.ndarray] = []
130
+ for ln in lines:
131
+ y = np.asarray(ln.get_ydata(), float).copy()
132
+ setattr(ln, _NORM_ATTR_PRE, y)
133
+ bases.append(y)
134
+ if method not in (DEFAULT_METHOD, "max_abs"):
135
+ method = DEFAULT_METHOD
136
+ peak = 0.0
137
+ for y in bases:
138
+ peak = max(peak, _peak_abs(y))
139
+ scale = (1.0 / peak) if peak > 0.0 else 1.0
140
+ for ln, y in zip(lines, bases):
141
+ try:
142
+ ln.set_ydata(y * scale)
143
+ setattr(ln, _NORM_ATTR_APPLIED, True)
144
+ setattr(ln, _NORM_ATTR_SCALE, float(scale))
145
+ except Exception:
146
+ pass
147
+ return float(scale)
148
+
149
+
150
+ def reset_cycle_lines_norm(cycle_lines) -> int:
151
+ """Restore pre-norm Y on one file's curves. Returns restored line count."""
152
+ n = 0
153
+ for ln in _iter_lines(cycle_lines):
154
+ if _restore_pre_norm_y(ln):
155
+ n += 1
156
+ _clear_line_norm_attrs(ln)
157
+ return n
158
+
159
+
160
+ def _targets_from_file_data(
161
+ file_data: Optional[Sequence[dict]],
162
+ cycle_lines,
163
+ file_indices: Optional[Sequence[int]],
164
+ *,
165
+ visible_only: bool,
166
+ ) -> List[tuple]:
167
+ """Return list of (file_index_or_None, cycle_lines)."""
168
+ if file_data and len(file_data) > 0:
169
+ out = []
170
+ if file_indices is None:
171
+ idxs = list(range(len(file_data)))
172
+ else:
173
+ idxs = [int(i) for i in file_indices]
174
+ for i in idxs:
175
+ if i < 0 or i >= len(file_data):
176
+ continue
177
+ f = file_data[i]
178
+ if visible_only and not bool(f.get("visible", True)):
179
+ continue
180
+ cl = f.get("cycle_lines")
181
+ if isinstance(cl, dict):
182
+ out.append((i, cl))
183
+ return out
184
+ if isinstance(cycle_lines, dict):
185
+ return [(None, cycle_lines)]
186
+ return []
187
+
188
+
189
+ def apply_dqdv_norm(
190
+ fig,
191
+ *,
192
+ file_data: Optional[Sequence[dict]] = None,
193
+ cycle_lines=None,
194
+ file_indices: Optional[Sequence[int]] = None,
195
+ method: Optional[str] = None,
196
+ visible_only: bool = True,
197
+ update_ylabel: bool = True,
198
+ ax=None,
199
+ ) -> Dict[str, Any]:
200
+ """Apply per-file max_|y| normalization. Returns updated settings dict."""
201
+ cfg = get_dqdv_norm_settings(fig)
202
+ use_method = str(method or cfg.get("method") or DEFAULT_METHOD)
203
+ targets = _targets_from_file_data(
204
+ file_data, cycle_lines, file_indices, visible_only=visible_only
205
+ )
206
+ if not targets:
207
+ return cfg
208
+
209
+ n_files = len(file_data) if file_data else 1
210
+ scales = list(cfg.get("file_scales") or [])
211
+ while len(scales) < n_files:
212
+ scales.append(1.0)
213
+ if file_data:
214
+ scales = scales[:n_files]
215
+
216
+ for idx, cl in targets:
217
+ scale = _normalize_cycle_lines(cl, method=use_method)
218
+ if idx is not None and 0 <= idx < len(scales):
219
+ scales[idx] = scale
220
+ elif idx is None:
221
+ scales = [scale]
222
+
223
+ cfg = {
224
+ "enabled": True,
225
+ "method": use_method,
226
+ "file_scales": scales if file_data else [scales[0] if scales else 1.0],
227
+ }
228
+ set_dqdv_norm_settings(fig, cfg)
229
+
230
+ if update_ylabel and ax is not None:
231
+ try:
232
+ if not hasattr(ax, "_dqdv_ylabel_before_norm"):
233
+ ax._dqdv_ylabel_before_norm = ax.get_ylabel()
234
+ ax.set_ylabel("normalized dQ/dV (a.u.)")
235
+ ax._stored_ylabel = "normalized dQ/dV (a.u.)"
236
+ except Exception:
237
+ pass
238
+ try:
239
+ fig.canvas.draw_idle()
240
+ except Exception:
241
+ pass
242
+ return cfg
243
+
244
+
245
+ def reset_dqdv_norm(
246
+ fig,
247
+ *,
248
+ file_data: Optional[Sequence[dict]] = None,
249
+ cycle_lines=None,
250
+ file_indices: Optional[Sequence[int]] = None,
251
+ restore_ylabel: bool = True,
252
+ ax=None,
253
+ ) -> int:
254
+ """Restore pre-norm Y. Returns number of restored lines."""
255
+ targets = _targets_from_file_data(
256
+ file_data, cycle_lines, file_indices, visible_only=False
257
+ )
258
+ n = 0
259
+ for _idx, cl in targets:
260
+ n += reset_cycle_lines_norm(cl)
261
+ set_dqdv_norm_settings(fig, default_dqdv_norm_settings())
262
+ if restore_ylabel and ax is not None:
263
+ try:
264
+ prev = getattr(ax, "_dqdv_ylabel_before_norm", None)
265
+ if isinstance(prev, str):
266
+ ax.set_ylabel(prev)
267
+ ax._stored_ylabel = prev
268
+ if hasattr(ax, "_dqdv_ylabel_before_norm"):
269
+ delattr(ax, "_dqdv_ylabel_before_norm")
270
+ except Exception:
271
+ pass
272
+ try:
273
+ fig.canvas.draw_idle()
274
+ except Exception:
275
+ pass
276
+ return n
277
+
278
+
279
+ def reapply_dqdv_norm_after_data_change(
280
+ fig,
281
+ *,
282
+ file_data: Optional[Sequence[dict]] = None,
283
+ cycle_lines=None,
284
+ ax=None,
285
+ ) -> None:
286
+ """After smooth/filter changes Y, rebase and re-apply if normalize is on."""
287
+ cfg = get_dqdv_norm_settings(fig)
288
+ if not cfg.get("enabled"):
289
+ return
290
+ targets = _targets_from_file_data(
291
+ file_data, cycle_lines, None, visible_only=False
292
+ )
293
+ for _idx, cl in targets:
294
+ clear_norm_attrs_on_cycle_lines(cl)
295
+ apply_dqdv_norm(
296
+ fig,
297
+ file_data=file_data,
298
+ cycle_lines=cycle_lines,
299
+ file_indices=None,
300
+ method=cfg.get("method"),
301
+ visible_only=False,
302
+ update_ylabel=True,
303
+ ax=ax,
304
+ )
305
+
306
+
307
+ def run_dqdv_norm_menu(
308
+ *,
309
+ fig,
310
+ ax,
311
+ cycle_lines,
312
+ file_data,
313
+ current_file_idx,
314
+ all_cycles,
315
+ is_dqdv,
316
+ is_multi_file,
317
+ menu_title,
318
+ canvas_mode,
319
+ print_menu,
320
+ print_file_list,
321
+ push_state,
322
+ safe_input,
323
+ colorize_menu,
324
+ colorize_prompt,
325
+ ) -> None:
326
+ """Interactive ``nm`` submenu (multi-file dQ/dV only)."""
327
+ if not is_dqdv:
328
+ print("Normalize is only available in dQ/dV mode.")
329
+ print_menu(len(all_cycles), is_dqdv, fig, is_multi_file, menu_title, canvas_mode)
330
+ return
331
+ if not is_multi_file or not file_data or len(file_data) < 2:
332
+ print(
333
+ "Normalize is for multi-file dQ/dV overlays "
334
+ "(compare different instruments/files)."
335
+ )
336
+ print_menu(len(all_cycles), is_dqdv, fig, is_multi_file, menu_title, canvas_mode)
337
+ return
338
+
339
+ while True:
340
+ print("\n\033[1mdQ/dV normalize (per-file max |Y| → 1)\033[0m")
341
+ print(format_dqdv_norm_status(fig))
342
+ print("Works on plotted curves (Neware absolute / specific, BioLogic, …).")
343
+ print("Commands:")
344
+ print(" " + colorize_menu("a: normalize all visible files"))
345
+ print(" " + colorize_menu("f: normalize selected file number(s)"))
346
+ print(" " + colorize_menu("r: reset normalize (restore pre-norm Y)"))
347
+ print(" " + colorize_menu("q: back to main menu"))
348
+ sub = safe_input(colorize_prompt("Normalize command (a/f/r/q): ")).strip().lower()
349
+ if not sub:
350
+ continue
351
+ if sub == "q":
352
+ break
353
+ if sub == "r":
354
+ push_state("dqdv-norm-reset")
355
+ n = reset_dqdv_norm(
356
+ fig, file_data=file_data, cycle_lines=cycle_lines, ax=ax
357
+ )
358
+ if n:
359
+ print(f"Reset normalize on {n} curve(s).")
360
+ else:
361
+ print("No normalized curves to reset.")
362
+ continue
363
+ if sub in ("a", "all"):
364
+ push_state("dqdv-norm-apply")
365
+ cfg = apply_dqdv_norm(
366
+ fig,
367
+ file_data=file_data,
368
+ cycle_lines=cycle_lines,
369
+ file_indices=None,
370
+ visible_only=True,
371
+ ax=ax,
372
+ )
373
+ n_vis = sum(1 for f in file_data if f.get("visible", True))
374
+ print(f"Normalized {n_vis} visible file(s) ({cfg.get('method')}).")
375
+ continue
376
+ if sub == "f":
377
+ print_file_list(file_data, current_file_idx)
378
+ choice = safe_input(
379
+ f"File numbers (1-{len(file_data)}, space-separated) or q=cancel: "
380
+ ).strip().lower()
381
+ if not choice or choice == "q":
382
+ continue
383
+ try:
384
+ idxs = [int(x) - 1 for x in choice.replace(",", " ").split()]
385
+ except ValueError:
386
+ print("Invalid input.")
387
+ continue
388
+ if any(i < 0 or i >= len(file_data) for i in idxs):
389
+ print("Invalid file number.")
390
+ continue
391
+ push_state("dqdv-norm-apply")
392
+ cfg = apply_dqdv_norm(
393
+ fig,
394
+ file_data=file_data,
395
+ cycle_lines=cycle_lines,
396
+ file_indices=idxs,
397
+ visible_only=False,
398
+ ax=ax,
399
+ )
400
+ print(f"Normalized file(s) {[i + 1 for i in idxs]} ({cfg.get('method')}).")
401
+ continue
402
+ print("Unknown command.")
403
+
404
+ print_menu(len(all_cycles), is_dqdv, fig, is_multi_file, menu_title, canvas_mode)
405
+ print_file_list(file_data, current_file_idx)
406
+
407
+
408
+ __all__ = [
409
+ "DEFAULT_METHOD",
410
+ "apply_dqdv_norm",
411
+ "clear_norm_attrs_on_cycle_lines",
412
+ "default_dqdv_norm_settings",
413
+ "format_dqdv_norm_status",
414
+ "get_dqdv_norm_settings",
415
+ "reapply_dqdv_norm_after_data_change",
416
+ "reset_dqdv_norm",
417
+ "run_dqdv_norm_menu",
418
+ "set_dqdv_norm_settings",
419
+ ]