quantized-lab 0.8.1__tar.gz → 0.10.0__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 (240) hide show
  1. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/PKG-INFO +1 -1
  2. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/pyproject.toml +1 -1
  3. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/__init__.py +1 -1
  4. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure.py +17 -5
  5. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_break.py +10 -4
  6. quantized_lab-0.10.0/src/quantized/calc/figure_labels.py +159 -0
  7. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_overrides.py +25 -1
  8. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_page.py +4 -0
  9. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_scale.py +4 -1
  10. quantized_lab-0.10.0/src/quantized/calc/figure_shapes.py +153 -0
  11. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_ticks.py +31 -2
  12. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_bumps.py +30 -4
  13. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_scan.py +2 -7
  14. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fitting.py +17 -0
  15. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/curve_style_color.py +13 -8
  16. quantized_lab-0.10.0/src/quantized/io/origin_project/fidelity.py +192 -0
  17. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_layers.py +2 -1
  18. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures.py +13 -3
  19. quantized_lab-0.10.0/src/quantized/io/origin_project/graph_preview.py +156 -0
  20. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_curves.py +87 -22
  21. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/templates.py +5 -3
  22. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_figures.py +6 -0
  23. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_page.py +2 -0
  24. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/fitting.py +20 -3
  25. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/parsers.py +31 -6
  26. quantized_lab-0.8.1/src/quantized/web/assets/index-BiZzN7J6.css → quantized_lab-0.10.0/src/quantized/web/assets/index-DkU851uO.css +1 -1
  27. quantized_lab-0.10.0/src/quantized/web/assets/index-N-btoovo.js +27 -0
  28. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/index.html +2 -2
  29. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/tools/origin_compare/README.md +1 -1
  30. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/tools/visual/README.md +44 -5
  31. quantized_lab-0.8.1/src/quantized/calc/figure_labels.py +0 -62
  32. quantized_lab-0.8.1/src/quantized/web/assets/index-BHmmCL-x.js +0 -27
  33. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/.gitignore +0 -0
  34. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/LICENSE +0 -0
  35. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/NOTICE +0 -0
  36. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/README.md +0 -0
  37. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/__main__.py +0 -0
  38. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/api.py +0 -0
  39. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/app.py +0 -0
  40. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/__init__.py +0 -0
  41. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/_clipfit.py +0 -0
  42. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/_natural_neighbor.py +0 -0
  43. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/aggregate.py +0 -0
  44. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/backgrounds.py +0 -0
  45. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/baseline.py +0 -0
  46. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/batch_fit.py +0 -0
  47. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/constants.py +0 -0
  48. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/corrections.py +0 -0
  49. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/crystallography.py +0 -0
  50. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/diffusion.py +0 -0
  51. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/electrical.py +0 -0
  52. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/electrochemistry.py +0 -0
  53. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/element_data.json +0 -0
  54. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/element_data.py +0 -0
  55. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/errors.py +0 -0
  56. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_categorical.py +0 -0
  57. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_corner.py +0 -0
  58. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_facets.py +0 -0
  59. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_field.py +0 -0
  60. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_hitmap.py +0 -0
  61. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_map.py +0 -0
  62. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_statplots.py +0 -0
  63. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_styles.py +0 -0
  64. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_ternary.py +0 -0
  65. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_autoguess.py +0 -0
  66. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_bootstrap.py +0 -0
  67. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_constraints.py +0 -0
  68. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_equation.py +0 -0
  69. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_findxy.py +0 -0
  70. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_models.py +0 -0
  71. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_models_special.py +0 -0
  72. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_odr.py +0 -0
  73. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_stats.py +0 -0
  74. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/formula.py +0 -0
  75. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/global_curve_fit.py +0 -0
  76. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/global_fit.py +0 -0
  77. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/interp2d.py +0 -0
  78. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/linecut.py +0 -0
  79. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/magnetic.py +0 -0
  80. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/magnetometry.py +0 -0
  81. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/map.py +0 -0
  82. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/mcmc.py +0 -0
  83. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/optics.py +0 -0
  84. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/pawley.py +0 -0
  85. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_batch.py +0 -0
  86. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_fit.py +0 -0
  87. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_integrate.py +0 -0
  88. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_multifit.py +0 -0
  89. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_track.py +0 -0
  90. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peaks.py +0 -0
  91. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peakshapes.py +0 -0
  92. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/plotting.py +0 -0
  93. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/processing.py +0 -0
  94. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/qspace.py +0 -0
  95. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reductions.py +0 -0
  96. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reductions_fft.py +0 -0
  97. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/refl_sld_presets.json +0 -0
  98. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reflectivity.py +0 -0
  99. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/registry.py +0 -0
  100. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/relaxation.py +0 -0
  101. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/report.py +0 -0
  102. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/report_emit.py +0 -0
  103. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/resample.py +0 -0
  104. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/rsm.py +0 -0
  105. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/rsm_analyze.py +0 -0
  106. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/semiconductor.py +0 -0
  107. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/sld.py +0 -0
  108. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/sld_formula.py +0 -0
  109. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/spectral.py +0 -0
  110. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/statplots.py +0 -0
  111. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats.py +0 -0
  112. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova2.py +0 -0
  113. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova_ext.py +0 -0
  114. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_dist.py +0 -0
  115. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_glm.py +0 -0
  116. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_multivar.py +0 -0
  117. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_roc.py +0 -0
  118. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_survival.py +0 -0
  119. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_tests.py +0 -0
  120. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/substrates.py +0 -0
  121. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/superconductor.py +0 -0
  122. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/surface_fit.py +0 -0
  123. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/surface_models.py +0 -0
  124. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/thermal.py +0 -0
  125. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/thin_film.py +0 -0
  126. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/unit_convert.py +0 -0
  127. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/units.py +0 -0
  128. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/vacuum.py +0 -0
  129. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/xray.py +0 -0
  130. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/cli.py +0 -0
  131. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/datastruct.py +0 -0
  132. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/__init__.py +0 -0
  133. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_hdf5_layout.py +0 -0
  134. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_jcamp_asdf.py +0 -0
  135. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_xrdml_scan.py +0 -0
  136. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/base.py +0 -0
  137. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/bruker_brml.py +0 -0
  138. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/bruker_raw.py +0 -0
  139. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/cif.py +0 -0
  140. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/consolidated.py +0 -0
  141. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/delimited.py +0 -0
  142. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/excel.py +0 -0
  143. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/hdf5.py +0 -0
  144. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/import_filters.py +0 -0
  145. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/import_preview.py +0 -0
  146. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/jcamp.py +0 -0
  147. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/lakeshore.py +0 -0
  148. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/ncnr.py +0 -0
  149. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/netcdf.py +0 -0
  150. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/opus.py +0 -0
  151. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin.py +0 -0
  152. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_com.py +0 -0
  153. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/__init__.py +0 -0
  154. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
  155. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/container.py +0 -0
  156. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
  157. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_text.py +0 -0
  158. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures_opju.py +0 -0
  159. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/notes.py +0 -0
  160. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj.py +0 -0
  161. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
  162. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju.py +0 -0
  163. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
  164. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
  165. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
  166. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
  167. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
  168. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
  169. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_text.py +0 -0
  170. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
  171. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
  172. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/preview.py +0 -0
  173. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree.py +0 -0
  174. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
  175. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows.py +0 -0
  176. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
  177. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer.py +0 -0
  178. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
  179. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/qd.py +0 -0
  180. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/refl1d.py +0 -0
  181. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/registry.py +0 -0
  182. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/report_export.py +0 -0
  183. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/rigaku.py +0 -0
  184. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/sims.py +0 -0
  185. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/spc.py +0 -0
  186. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/xrd_csv.py +0 -0
  187. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/xrdml.py +0 -0
  188. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/jobs.py +0 -0
  189. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/__init__.py +0 -0
  190. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/contract.py +0 -0
  191. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/loader.py +0 -0
  192. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/steps.py +0 -0
  193. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/__init__.py +0 -0
  194. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_bookcache.py +0 -0
  195. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_export_common.py +0 -0
  196. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_payload.py +0 -0
  197. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_uploadcache.py +0 -0
  198. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/aggregate.py +0 -0
  199. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/baseline.py +0 -0
  200. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/books.py +0 -0
  201. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/calc.py +0 -0
  202. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/corrections.py +0 -0
  203. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/crystallography.py +0 -0
  204. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/diffusion.py +0 -0
  205. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/electrical.py +0 -0
  206. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/electrochemistry.py +0 -0
  207. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export.py +0 -0
  208. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_facets.py +0 -0
  209. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/fitting_bumps.py +0 -0
  210. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/import_template.py +0 -0
  211. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/import_wizard.py +0 -0
  212. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/jobs_api.py +0 -0
  213. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/magnetic.py +0 -0
  214. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/magnetometry.py +0 -0
  215. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/optics.py +0 -0
  216. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/peaks.py +0 -0
  217. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/plot.py +0 -0
  218. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reductions.py +0 -0
  219. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reference.py +0 -0
  220. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reflectivity.py +0 -0
  221. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/report_export.py +0 -0
  222. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/rsm.py +0 -0
  223. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/samples.py +0 -0
  224. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/semiconductor.py +0 -0
  225. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/sld.py +0 -0
  226. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/spectral.py +0 -0
  227. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/statplots.py +0 -0
  228. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/stats.py +0 -0
  229. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/stats_design.py +0 -0
  230. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/substrates.py +0 -0
  231. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/superconductor.py +0 -0
  232. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/thermal.py +0 -0
  233. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/thin_film.py +0 -0
  234. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/vacuum.py +0 -0
  235. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/xray.py +0 -0
  236. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/samples/demo_vsm.csv +0 -0
  237. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/server_launch.py +0 -0
  238. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  239. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  240. {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/loading.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantized-lab
3
- Version: 0.8.1
3
+ Version: 0.10.0
4
4
  Summary: Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines.
5
5
  Project-URL: Homepage, https://github.com/pquarterman17/quantized
6
6
  Project-URL: Documentation, https://github.com/pquarterman17/quantized/wiki
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quantized-lab"
3
- version = "0.8.1"
3
+ version = "0.10.0"
4
4
  description = "Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -7,5 +7,5 @@ Layered, enforced architecture (see CLAUDE.md):
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- __version__ = "0.8.1"
10
+ __version__ = "0.10.0"
11
11
  __all__ = ["__version__"]
@@ -27,7 +27,7 @@ from quantized.calc.figure_labels import safe_mathtext_label # noqa: E402
27
27
  from quantized.calc.figure_overrides import _apply_overrides, _validate_overrides # noqa: E402
28
28
  from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
29
29
  from quantized.calc.figure_styles import FigureStyle, figure_style # noqa: E402
30
- from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
30
+ from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
31
31
 
32
32
  __all__ = ["draw_series_axes", "render_figure", "style_rc"]
33
33
 
@@ -177,6 +177,8 @@ def draw_series_axes(
177
177
  series_styles: Sequence[Mapping[str, Any] | None] | None = None,
178
178
  x_fmt: Mapping[str, Any] | None = None,
179
179
  y_fmt: Mapping[str, Any] | None = None,
180
+ x_step: float | None = None,
181
+ y_step: float | None = None,
180
182
  ) -> list[Any]:
181
183
  """Plot ``series`` into an EXISTING Axes: lines, scales, labels, spines,
182
184
  legend, grid, and the per-figure override sweep (:func:`_apply_overrides`).
@@ -221,8 +223,11 @@ def draw_series_axes(
221
223
  (line,) = ax.plot(xv, yv, label=label, **kw)
222
224
  _apply_fill(ax, xv, yv, series, i, spec, line.get_color())
223
225
  artists.append(line)
224
- apply_axis_scale(ax, "x", resolve_axis_scale(x_scale, x_log))
225
- apply_axis_scale(ax, "y", resolve_axis_scale(y_scale, y_log))
226
+ resolved_x_scale = resolve_axis_scale(x_scale, x_log)
227
+ resolved_y_scale = resolve_axis_scale(y_scale, y_log)
228
+ apply_axis_scale(ax, "x", resolved_x_scale)
229
+ apply_axis_scale(ax, "y", resolved_y_scale)
230
+ apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
226
231
  apply_tick_formats(ax, x_fmt, y_fmt)
227
232
  if title:
228
233
  ax.set_title(title)
@@ -242,9 +247,10 @@ def draw_series_axes(
242
247
  loc=st.legend_location,
243
248
  )
244
249
  if st.grid_alpha > 0:
245
- ax.grid(True, alpha=st.grid_alpha)
250
+ ax.grid(True, which="major", alpha=st.grid_alpha)
251
+ ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
246
252
  else:
247
- ax.grid(False)
253
+ ax.grid(False, which="both")
248
254
  _apply_overrides(fig, ax, st, ov, n_series=len(series))
249
255
  return artists
250
256
 
@@ -270,6 +276,8 @@ def _render_impl(
270
276
  collect_map: bool = False,
271
277
  x_fmt: Mapping[str, Any] | None = None,
272
278
  y_fmt: Mapping[str, Any] | None = None,
279
+ x_step: float | None = None,
280
+ y_step: float | None = None,
273
281
  ) -> bytes | dict[str, Any]:
274
282
  """Render ``series`` (each ``(label, y)``) against ``x`` to image bytes.
275
283
 
@@ -352,6 +360,8 @@ def _render_impl(
352
360
  series_styles=series_styles,
353
361
  x_fmt=x_fmt,
354
362
  y_fmt=y_fmt,
363
+ x_step=x_step,
364
+ y_step=y_step,
355
365
  )
356
366
  fig, ax = plt.subplots(figsize=figsize)
357
367
  try:
@@ -372,6 +382,8 @@ def _render_impl(
372
382
  series_styles=series_styles,
373
383
  x_fmt=x_fmt,
374
384
  y_fmt=y_fmt,
385
+ x_step=x_step,
386
+ y_step=y_step,
375
387
  )
376
388
  if not ov.get("margins"):
377
389
  fig.tight_layout()
@@ -40,7 +40,7 @@ from numpy.typing import ArrayLike, NDArray # noqa: E402
40
40
 
41
41
  from quantized.calc.figure import _plot_kwargs # noqa: E402
42
42
  from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
43
- from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
43
+ from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
44
44
 
45
45
  __all__ = ["render_breaks_impl"]
46
46
 
@@ -65,6 +65,8 @@ def render_breaks_impl(
65
65
  series_styles: Sequence[Mapping[str, Any] | None] | None,
66
66
  x_fmt: Mapping[str, Any] | None = None,
67
67
  y_fmt: Mapping[str, Any] | None = None,
68
+ x_step: float | None = None,
69
+ y_step: float | None = None,
68
70
  ) -> bytes:
69
71
  """Render ``series`` against ``x`` with the x-axis elided over each
70
72
  ``[lo, hi]`` pair in ``breaks`` (already sorted/validated non-overlapping
@@ -100,8 +102,11 @@ def render_breaks_impl(
100
102
  ax.plot(x, np.asarray(y, dtype=float), label=label, **kw)
101
103
  lo, hi = bounds[i]
102
104
  ax.set_xlim(lo, hi)
103
- apply_axis_scale(ax, "x", resolve_axis_scale(x_scale, x_log))
104
- apply_axis_scale(ax, "y", resolve_axis_scale(y_scale, y_log))
105
+ resolved_x_scale = resolve_axis_scale(x_scale, x_log)
106
+ resolved_y_scale = resolve_axis_scale(y_scale, y_log)
107
+ apply_axis_scale(ax, "x", resolved_x_scale)
108
+ apply_axis_scale(ax, "y", resolved_y_scale)
109
+ apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
105
110
  apply_tick_formats(ax, x_fmt, y_fmt)
106
111
  if i == 0:
107
112
  handles, labels_out = ax.get_legend_handles_labels()
@@ -113,7 +118,8 @@ def render_breaks_impl(
113
118
  if not st.box_on:
114
119
  ax.spines["top"].set_visible(False)
115
120
  if st.grid_alpha > 0:
116
- ax.grid(True, alpha=st.grid_alpha)
121
+ ax.grid(True, which="major", alpha=st.grid_alpha)
122
+ ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
117
123
 
118
124
  # Diagonal break glyphs (matplotlib's standard broken-axis recipe):
119
125
  # short strokes angled across each seam, on both the outgoing panel's
@@ -0,0 +1,159 @@
1
+ """Mathtext label guard for figure export (GOTO #5 rich-text labels).
2
+
3
+ Labels flow from the UI as plain text with optional ``$...$`` mathtext
4
+ regions (the frontend's ``lib/richtext`` micro-syntax is a strict subset of
5
+ matplotlib mathtext, so passthrough is the rendering mechanism). matplotlib
6
+ parses math regions at DRAW time, which means an invalid label would raise
7
+ inside ``fig.savefig`` and turn an export into a 500. This module is the
8
+ shared chokepoint every ``calc.figure*`` renderer routes its user-supplied
9
+ strings through: it pre-validates with matplotlib's own mathtext parser and,
10
+ on failure, escapes the dollar signs so the label renders as literal text
11
+ (matplotlib renders ``\\$`` as a plain ``$``) -- an invalid label must NEVER
12
+ error an export.
13
+
14
+ Screen/export WYSIWYG (bug-hunt fix): the frontend's parser
15
+ (``frontend/src/lib/richtext.ts``) only accepts a STRICT SUBSET of
16
+ mathtext -- Greek letters, the symbol/relation/arrow commands in
17
+ ``SUPPORTED_MATHTEXT_COMMANDS`` (``\\AA \\times \\leq \\approx \\infty
18
+ \\rightarrow`` ...), the structural commands ``\\frac \\sqrt`` (MAIN #28),
19
+ ``\\, \\mathrm \\mathit``, and literal Unicode -- and rejects everything else
20
+ (e.g. ``\\hat``, ``\\vec``, ``\\overline``) as invalid markup, rendering it
21
+ literally on screen. Raw matplotlib accepts a much larger command set, so
22
+ without this gate a label like ``$\\vec{v}$`` would render literal text
23
+ ("Invalid markup") on screen but an actual arrow-accented vector in the
24
+ PDF/SVG export -- a WYSIWYG violation. `_uses_only_supported_commands`
25
+ enumerates the SAME command set the frontend parser accepts and rejects
26
+ (falls back to literal) any ``$...$`` region using a command outside it,
27
+ BEFORE the matplotlib trial parse below ever runs. Keep
28
+ `SUPPORTED_MATHTEXT_COMMANDS` in sync with richtext.ts's
29
+ ``GREEK``/``SYMBOLS``/``BIGOPS`` tables + ``mathrm``/``mathit``/``frac``/
30
+ ``sqrt`` handling.
31
+
32
+ Pure layer: string in -> string out. matplotlib is imported lazily (same
33
+ convention as the figure modules -- the heavy import is paid only on export).
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import re
39
+ from functools import lru_cache
40
+ from typing import Any
41
+
42
+ __all__ = ["safe_mathtext_label", "SUPPORTED_MATHTEXT_COMMANDS"]
43
+
44
+ # A "$" not preceded by a backslash -- matplotlib's own math-region rule
45
+ # (matplotlib.cbook.is_math_text counts these; an odd count means the string
46
+ # is rendered as literal text, dollars visible, no mathtext at all).
47
+ _UNESCAPED_DOLLAR = re.compile(r"(?<!\\)\$")
48
+
49
+ # A backslash command name (`\foo`) inside a math region. `\,` (thin space)
50
+ # is handled separately by richtext.ts before its name-based dispatch and
51
+ # has no letters, so it never matches this and is implicitly always allowed.
52
+ _COMMAND = re.compile(r"\\([a-zA-Z]+)")
53
+
54
+ # Mirrors frontend/src/lib/richtext.ts's `GREEK` table: lowercase (+ \var*
55
+ # variants) render italic, uppercase upright -- irrelevant here, we only
56
+ # need the NAME set, not the styling.
57
+ _GREEK_COMMANDS = frozenset(
58
+ {
59
+ "alpha", "beta", "gamma", "delta", "epsilon", "varepsilon", "zeta",
60
+ "eta", "theta", "vartheta", "iota", "kappa", "lambda", "mu", "nu",
61
+ "xi", "pi", "rho", "sigma", "varsigma", "tau", "upsilon", "phi",
62
+ "varphi", "chi", "psi", "omega",
63
+ "Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon",
64
+ "Phi", "Psi", "Omega",
65
+ }
66
+ )
67
+ # Mirrors richtext.ts's `SYMBOLS` table (non-Greek symbol commands): base
68
+ # symbols + relations, analysis/geometry glyphs, and arrows. Each is verified
69
+ # to parse in matplotlib mathtext (tools probe), so a label using them renders
70
+ # identically on screen (richtext.ts glyph) and in export (mathtext glyph).
71
+ _SYMBOL_COMMANDS = frozenset(
72
+ {
73
+ "AA", "circ", "times", "cdot", "pm", "mp", "div", "prime",
74
+ # relations
75
+ "leq", "geq", "neq", "approx", "equiv", "sim", "propto", "ll", "gg",
76
+ # analysis / geometry
77
+ "infty", "partial", "nabla", "perp", "parallel", "angle",
78
+ "cdots", "ldots", "dots",
79
+ # arrows
80
+ "rightarrow", "to", "leftarrow", "leftrightarrow", "Rightarrow",
81
+ }
82
+ )
83
+ # Mirrors richtext.ts's `\mathrm{...}` / `\mathit{...}` style-group handling.
84
+ _STYLE_COMMANDS = frozenset({"mathrm", "mathit"})
85
+ # Mirrors richtext.ts's structural nodes: `\frac{a}{b}`, `\sqrt{x}`,
86
+ # `\sqrt[n]{x}` (MAIN #28). matplotlib renders these natively; the frontend
87
+ # canvas + DOM renderers draw the matching stacked/radical layout.
88
+ _STRUCTURE_COMMANDS = frozenset({"frac", "sqrt"})
89
+ # Mirrors richtext.ts's `BIGOPS` table (large operators). matplotlib stacks
90
+ # \sum/\prod limits over/under and side-scripts \int/\oint inline -- the
91
+ # frontend canvas renderer matches that limits placement.
92
+ _BIGOP_COMMANDS = frozenset({"sum", "prod", "int", "oint"})
93
+
94
+ SUPPORTED_MATHTEXT_COMMANDS = (
95
+ _GREEK_COMMANDS
96
+ | _SYMBOL_COMMANDS
97
+ | _STYLE_COMMANDS
98
+ | _STRUCTURE_COMMANDS
99
+ | _BIGOP_COMMANDS
100
+ )
101
+
102
+
103
+ def _uses_only_supported_commands(label: str) -> bool:
104
+ """True when every backslash command inside every ``$...$`` region of
105
+ `label` is in `SUPPORTED_MATHTEXT_COMMANDS`. Text OUTSIDE `$...$` is
106
+ never scanned -- a stray backslash there is the caller's problem, not
107
+ this gate's (and richtext.ts itself only ever considers commands inside
108
+ math regions). Assumes a balanced (even) unescaped-`$` count, as
109
+ guaranteed by `safe_mathtext_label` calling this only after that check.
110
+ """
111
+ segments = _UNESCAPED_DOLLAR.split(label)
112
+ for i, segment in enumerate(segments):
113
+ if i % 2 == 0:
114
+ continue # literal text outside $...$
115
+ for m in _COMMAND.finditer(segment):
116
+ if m.group(1) not in SUPPORTED_MATHTEXT_COMMANDS:
117
+ return False
118
+ return True
119
+
120
+
121
+ @lru_cache(maxsize=1)
122
+ def _parser() -> Any:
123
+ """The mathtext trial parser (cached -- construction is not free)."""
124
+ from matplotlib.mathtext import MathTextParser
125
+
126
+ return MathTextParser("agg")
127
+
128
+
129
+ def safe_mathtext_label(label: str) -> str:
130
+ """Return ``label`` unchanged when it renders safely; else de-math it.
131
+
132
+ - No ``$`` at all: fast path, returned as-is (byte-identical to today).
133
+ - Odd count of unescaped ``$``: matplotlib already treats the whole
134
+ string as literal text -- returned as-is.
135
+ - Balanced ``$...$`` regions using only commands the frontend's
136
+ richtext.ts subset also accepts (`SUPPORTED_MATHTEXT_COMMANDS`), AND
137
+ that trial-parse successfully with matplotlib's mathtext parser ->
138
+ unchanged (mathtext renders it at export, matching the screen).
139
+ Otherwise (an out-of-subset command, e.g. ``\\frac``/``\\sqrt``/
140
+ ``\\sum``, OR a parse failure even within the subset, e.g. unbalanced
141
+ braces) -> every unescaped ``$`` is escaped to ``\\$`` so matplotlib
142
+ renders the literal string (dollars visible) instead of raising at
143
+ savefig time OR silently rendering richer math than the screen ever
144
+ showed.
145
+ """
146
+ if not label or "$" not in label:
147
+ return label
148
+ n_math = len(_UNESCAPED_DOLLAR.findall(label))
149
+ if n_math == 0 or n_math % 2 == 1:
150
+ # Zero unescaped $ (all \$-escaped) or an odd count: matplotlib
151
+ # already renders the whole string as literal text -- leave it be.
152
+ return label
153
+ if not _uses_only_supported_commands(label):
154
+ return _UNESCAPED_DOLLAR.sub(r"\\$", label)
155
+ try:
156
+ _parser().parse(label)
157
+ except Exception: # ANY parse failure means "render literal", never raise
158
+ return _UNESCAPED_DOLLAR.sub(r"\\$", label)
159
+ return label
@@ -17,6 +17,7 @@ from collections.abc import Mapping
17
17
  from typing import Any
18
18
 
19
19
  from quantized.calc.figure_labels import safe_mathtext_label
20
+ from quantized.calc.figure_shapes import _apply_shapes, _validate_shapes
20
21
 
21
22
  __all__ = ["_apply_overrides", "_validate_overrides"]
22
23
 
@@ -63,6 +64,7 @@ def _validate_overrides(ov: Mapping[str, Any]) -> None:
63
64
  if prev_hi is not None and lo < prev_hi:
64
65
  raise ValueError("x_breaks entries must be sorted and non-overlapping")
65
66
  prev_hi = hi
67
+ _validate_shapes(ov.get("shapes"))
66
68
 
67
69
 
68
70
  def _apply_overrides(
@@ -116,7 +118,7 @@ def _apply_overrides(
116
118
  )
117
119
 
118
120
  if "grid" in ov:
119
- ax.grid(bool(ov["grid"]), alpha=st.grid_alpha or 0.3)
121
+ ax.grid(bool(ov["grid"]), which="both", alpha=st.grid_alpha or 0.3)
120
122
 
121
123
  for ann in ov.get("annotations", []):
122
124
  # MAIN #18: a per-annotation `size` (the pointer tool's corner-handle
@@ -142,6 +144,23 @@ def _apply_overrides(
142
144
  # pixel-identical placement between screen and export.
143
145
  ann_kw["xycoords"] = "figure fraction"
144
146
  y = 1.0 - y
147
+ frame = ann.get("frame")
148
+ if frame:
149
+ # MAIN #27 "text box": a bbox behind the annotation text. `pad`
150
+ # (screen CSS px around the measured text) has no exact
151
+ # matplotlib equivalent (`boxstyle="square,pad=N"`'s pad is in
152
+ # font-size units, not px) -- divided by a fixed factor so the
153
+ # box reads as padded rather than pixel-identical, the same
154
+ # "Origin-parity-in-spirit, not pixel-identical" caveat the
155
+ # page-anchor y-flip above documents. fc/ec default to a plain
156
+ # white-card-on-black-border look (independent of the screen's
157
+ # own dark-canvas ink default -- publication export is white-bg).
158
+ ann_kw["bbox"] = dict(
159
+ boxstyle=f"square,pad={float(frame.get('pad', 4)) / 4.0}",
160
+ fc=frame.get("fill") or "white",
161
+ ec=frame.get("stroke") or "black",
162
+ alpha=frame.get("opacity", 1.0),
163
+ )
145
164
  ax.annotate(
146
165
  safe_mathtext_label(str(ann.get("text", ""))),
147
166
  xy=(x, y),
@@ -157,3 +176,8 @@ def _apply_overrides(
157
176
  top=None if margins.get("top") is None else 1.0 - float(margins["top"]),
158
177
  bottom=margins.get("bottom"),
159
178
  )
179
+
180
+ # MAIN #27: shapes paint LAST -- on top of everything else this sweep
181
+ # drew (annotations included), matching export intent: shapes mark up
182
+ # the finished figure.
183
+ _apply_shapes(fig, ax, ov.get("shapes"))
@@ -115,6 +115,8 @@ class PagePanel:
115
115
  # (a figure page composes several independently-configured plot views).
116
116
  x_fmt: Mapping[str, Any] | None = None
117
117
  y_fmt: Mapping[str, Any] | None = None
118
+ x_step: float | None = None
119
+ y_step: float | None = None
118
120
  series_styles: Sequence[Mapping[str, Any] | None] | None = None
119
121
  overrides: Mapping[str, Any] | None = None
120
122
  label: str | None = None
@@ -256,6 +258,8 @@ def render_figure_page(
256
258
  series_styles=p.series_styles,
257
259
  x_fmt=p.x_fmt,
258
260
  y_fmt=p.y_fmt,
261
+ x_step=p.x_step,
262
+ y_step=p.y_step,
259
263
  )
260
264
  text = p.label if p.label is not None else panel_label(idx, label_format)
261
265
  _place_label(ax, safe_mathtext_label(text), label_pos, st)
@@ -21,7 +21,7 @@ from __future__ import annotations
21
21
  from typing import Any
22
22
 
23
23
  import numpy as np
24
- from matplotlib.ticker import Locator
24
+ from matplotlib.ticker import Locator, LogLocator, NullFormatter
25
25
  from numpy.typing import NDArray
26
26
 
27
27
  __all__ = ["apply_axis_scale", "reciprocal_tick_values", "resolve_axis_scale"]
@@ -115,6 +115,9 @@ def apply_axis_scale(ax: Any, axis: str, scale: str) -> None:
115
115
  set_scale = ax.set_xscale if axis == "x" else ax.set_yscale
116
116
  if scale == "log":
117
117
  set_scale("log")
118
+ target = ax.xaxis if axis == "x" else ax.yaxis
119
+ target.set_minor_locator(LogLocator(base=10.0, subs=tuple(range(2, 10))))
120
+ target.set_minor_formatter(NullFormatter())
118
121
  return
119
122
  if scale == "reciprocal":
120
123
  set_scale("function", functions=(_reciprocal, _reciprocal))
@@ -0,0 +1,153 @@
1
+ """Drawn-shape overrides (MAIN #27) -- export parity for the screen's
2
+ ``lib/uplotShapes.ts``. Split out of ``figure_overrides.py`` purely to stay
3
+ under each module's line-count discipline (same reasoning as
4
+ ``figure_labels.py`` / ``figure_break.py``) -- the behavioural contract is
5
+ still ``figure_overrides``'s; ``_apply_overrides`` calls ``_apply_shapes``
6
+ as its last step, AFTER the annotation sweep, so shapes paint on TOP of
7
+ everything else the override sweep drew (annotations included) -- matching
8
+ export intent: shapes mark up the finished figure. Pure layer: mapping in ->
9
+ mutates the passed matplotlib Figure/Axes, no return value.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Mapping, Sequence
15
+ from typing import Any, Literal
16
+
17
+ from matplotlib.lines import Line2D
18
+ from matplotlib.patches import Ellipse, FancyArrowPatch, Rectangle
19
+
20
+ __all__ = ["_apply_shapes", "_validate_shapes"]
21
+
22
+ _SHAPE_KINDS = frozenset({"arrow", "line", "rect", "ellipse"})
23
+
24
+ # Default whole-shape opacity (fill AND stroke, ONE knob) mirroring the
25
+ # screen's `defaultShapeOpacity` (lib/uplotShapes.ts): 1 for line/arrow, 0.35
26
+ # for rect/ellipse (a freshly drawn box reads as visibly translucent OVER
27
+ # the data it marks).
28
+ _DEFAULT_OPACITY = {"arrow": 1.0, "line": 1.0, "rect": 0.35, "ellipse": 0.35}
29
+ _DEFAULT_WIDTH = 1.5
30
+ _DEFAULT_STROKE = "black"
31
+
32
+
33
+ def _validate_shapes(shapes: Sequence[Mapping[str, Any]] | None) -> None:
34
+ """Raise ``ValueError`` on a malformed shape (bad kind, non-finite
35
+ coords); a shape with an unknown/missing key inside is otherwise
36
+ tolerant (unknown keys ignored), mirroring ``_validate_overrides``."""
37
+ if not shapes:
38
+ return
39
+ for sh in shapes:
40
+ kind = sh.get("kind")
41
+ if kind not in _SHAPE_KINDS:
42
+ raise ValueError(f"shape kind must be one of {sorted(_SHAPE_KINDS)}")
43
+ for key in ("x1", "y1", "x2", "y2"):
44
+ v = sh.get(key)
45
+ if not isinstance(v, (int, float)) or isinstance(v, bool):
46
+ raise ValueError(f"shape {key} must be a number")
47
+
48
+
49
+ def _shape_endpoints(
50
+ sh: Mapping[str, Any], ax: Any, fig: Any
51
+ ) -> tuple[tuple[float, float], tuple[float, float], Any]:
52
+ """(p1, p2, transform) for one shape, resolving its anchor -- DATA (the
53
+ default: ``ax.transData``, applied implicitly by omitting ``transform``
54
+ and adding via ``ax.add_patch``/``ax.add_line``) or PAGE (explicit
55
+ ``fig.transFigure``, Y FLIPPED -- canvas y grows downward, matplotlib
56
+ figure fraction grows upward -- the SAME convention
57
+ ``_apply_overrides``'s page-anchored annotation uses)."""
58
+ x1, y1 = float(sh["x1"]), float(sh["y1"])
59
+ x2, y2 = float(sh["x2"]), float(sh["y2"])
60
+ if sh.get("anchor") == "page":
61
+ return (x1, 1.0 - y1), (x2, 1.0 - y2), fig.transFigure
62
+ return (x1, y1), (x2, y2), ax.transData
63
+
64
+
65
+ def _apply_shapes(fig: Any, ax: Any, shapes: Sequence[Mapping[str, Any]] | None) -> None:
66
+ """Draw MAIN #27 shapes (arrow/line/rect/ellipse). Arrows use
67
+ ``FancyArrowPatch``; plain lines ``Line2D``; rect/ellipse the matching
68
+ matplotlib patch with ``alpha`` -- all added via ``ax.add_patch``/
69
+ ``ax.add_line`` (data-anchored, the default -- no explicit ``transform``
70
+ needed there; matplotlib resolves an artist added this way against
71
+ ``ax.transData``) or with an EXPLICIT ``transform=fig.transFigure`` for
72
+ a page-anchored shape (``ax.add_patch``/``add_line`` never override an
73
+ already-set transform -- verified empirically, see the porting notes).
74
+ """
75
+ if not shapes:
76
+ return
77
+ for sh in shapes:
78
+ kind = sh.get("kind")
79
+ if kind not in _SHAPE_KINDS:
80
+ continue
81
+ p1, p2, transform = _shape_endpoints(sh, ax, fig)
82
+ stroke = sh.get("stroke") or _DEFAULT_STROKE
83
+ # `is not None` (not `or`): an explicit `width: 0` is falsy in Python
84
+ # and `... or _DEFAULT_WIDTH` was silently replacing it with the
85
+ # default (1.5) -- unlike the correct `opacity` idiom two lines below.
86
+ width = float(sh["width"]) if sh.get("width") is not None else _DEFAULT_WIDTH
87
+ dash: Literal["-", "--"] = "--" if sh.get("dash") else "-"
88
+ opacity = sh.get("opacity")
89
+ opacity = float(opacity) if opacity is not None else _DEFAULT_OPACITY[kind]
90
+ fill = sh.get("fill") or stroke
91
+ patch: Any
92
+
93
+ if kind == "arrow":
94
+ patch = FancyArrowPatch(
95
+ p1,
96
+ p2,
97
+ transform=transform,
98
+ arrowstyle="-|>",
99
+ mutation_scale=12 + width * 4,
100
+ # matplotlib's FancyArrowPatch default shrinkA/shrinkB=2pt
101
+ # insets BOTH ends from the given points (meant for
102
+ # "annotate near, not touching, an object") -- WYSIWYG with
103
+ # the screen's canvas draw (no shrink at all) needs the
104
+ # endpoints to land EXACTLY where dragged.
105
+ shrinkA=0,
106
+ shrinkB=0,
107
+ color=stroke,
108
+ linewidth=width,
109
+ linestyle=dash,
110
+ alpha=opacity,
111
+ )
112
+ ax.add_patch(patch)
113
+ elif kind == "line":
114
+ line = Line2D(
115
+ [p1[0], p2[0]],
116
+ [p1[1], p2[1]],
117
+ transform=transform,
118
+ color=stroke,
119
+ linewidth=width,
120
+ linestyle=dash,
121
+ alpha=opacity,
122
+ )
123
+ ax.add_line(line)
124
+ elif kind == "rect":
125
+ x0, x1v = min(p1[0], p2[0]), max(p1[0], p2[0])
126
+ y0, y1v = min(p1[1], p2[1]), max(p1[1], p2[1])
127
+ patch = Rectangle(
128
+ (x0, y0),
129
+ x1v - x0,
130
+ y1v - y0,
131
+ transform=transform,
132
+ edgecolor=stroke,
133
+ facecolor=fill,
134
+ linewidth=width,
135
+ linestyle=dash,
136
+ alpha=opacity,
137
+ )
138
+ ax.add_patch(patch)
139
+ else: # ellipse
140
+ cx, cy = (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2
141
+ w, h = abs(p2[0] - p1[0]), abs(p2[1] - p1[1])
142
+ patch = Ellipse(
143
+ (cx, cy),
144
+ w,
145
+ h,
146
+ transform=transform,
147
+ edgecolor=stroke,
148
+ facecolor=fill,
149
+ linewidth=width,
150
+ linestyle=dash,
151
+ alpha=opacity,
152
+ )
153
+ ax.add_patch(patch)
@@ -36,9 +36,9 @@ import math
36
36
  from collections.abc import Mapping
37
37
  from typing import Any
38
38
 
39
- from matplotlib.ticker import Formatter
39
+ from matplotlib.ticker import Formatter, MultipleLocator
40
40
 
41
- __all__ = ["apply_tick_formats", "axis_tick_formatter"]
41
+ __all__ = ["apply_tick_formats", "apply_tick_steps", "axis_tick_formatter"]
42
42
 
43
43
  _MODES = ("fixed", "sci", "eng")
44
44
 
@@ -215,3 +215,32 @@ def apply_tick_formats(
215
215
  yf = axis_tick_formatter(y_fmt)
216
216
  if yf is not None:
217
217
  ax.yaxis.set_major_formatter(yf)
218
+
219
+
220
+ def apply_tick_steps(
221
+ ax: Any,
222
+ x_step: float | None,
223
+ y_step: float | None,
224
+ x_scale: str,
225
+ y_scale: str,
226
+ ) -> None:
227
+ """Apply saved major-tick increments to linear axes only.
228
+
229
+ Origin's decoded ``from/to/step`` triples carry a linear increment. Log
230
+ axes retain their scale-specific locator; reciprocal axes have their own
231
+ locator. Invalid or absent increments leave matplotlib's locator intact.
232
+ ``MultipleLocator`` anchors ticks at integer multiples of the step, the
233
+ same convention as the interactive ``fixedLinearAxisSplits`` helper.
234
+ """
235
+ def apply_one(axis: Any, limits: tuple[float, float], step: float | None) -> None:
236
+ if step is None or not math.isfinite(step) or step <= 0:
237
+ return
238
+ span = abs(float(limits[1]) - float(limits[0]))
239
+ if not math.isfinite(span) or span / step > 1000:
240
+ return
241
+ axis.set_major_locator(MultipleLocator(float(step)))
242
+
243
+ if x_scale == "linear":
244
+ apply_one(ax.xaxis, ax.get_xlim(), x_step)
245
+ if y_scale == "linear":
246
+ apply_one(ax.yaxis, ax.get_ylim(), y_step)
@@ -39,6 +39,12 @@ AbortFn = Callable[[], bool]
39
39
  #: Supported bumps engine ids (bumps.fitters FITTERS ids).
40
40
  BUMPS_ENGINES = ("amoeba", "lm", "de", "dream")
41
41
 
42
+ #: Gelman-Rubin R-hat at/above which DREAM chains are considered NOT converged
43
+ #: (the widely-used 1.1 threshold; < ~1.01 is "well mixed"). Surfaced so the UI
44
+ #: never presents posterior intervals as trustworthy when the chains haven't
45
+ #: mixed (audit P1 #2).
46
+ RHAT_THRESHOLD = 1.1
47
+
42
48
  _INSTALL_HINT = (
43
49
  "bumps is not installed - the optional fit engine needs "
44
50
  "'pip install quantized[bumps]' (or: uv sync --extra bumps)"
@@ -154,9 +160,11 @@ def fit_bumps(
154
160
  saved equation-builder models) or a raw ``f(x, p)`` callable. ``engine``
155
161
  is one of ``BUMPS_ENGINES``: amoeba / lm / de report Hessian-derived
156
162
  uncertainties (``uncertainty_kind='hessian'``); dream samples the
157
- posterior (``uncertainty_kind='posterior'``) and additionally returns
158
- per-parameter medians and central 68% intervals (plus the raw draw
159
- matrix when ``return_samples`` — corner-plot food).
163
+ posterior (``uncertainty_kind='posterior'``) and additionally returns, in
164
+ ``result['posterior']``, per-parameter medians, central 68% intervals,
165
+ ``n_draws``, ``nChains``, and the convergence diagnostics ``rHat`` (per
166
+ parameter), ``rHatMax``, and ``converged`` (Gelman-Rubin, audit P1 #2) —
167
+ plus the raw draw matrix when ``return_samples`` (corner-plot food).
160
168
 
161
169
  ``samples`` / ``burn`` / ``pop`` tune dream only. ``progress_callback``
162
170
  (fraction in [0, 1)) and ``abort_check`` (True -> stop sampling early)
@@ -243,15 +251,33 @@ def fit_bumps(
243
251
  }
244
252
 
245
253
  if engine == "dream":
246
- draw = driver.fitter.state.draw()
254
+ state = driver.fitter.state
255
+ draw = state.draw()
247
256
  pts = np.asarray(draw.points, dtype=float)[:, order]
248
257
  lo68, med, hi68 = (
249
258
  np.asarray(np.percentile(pts, q, axis=0), dtype=float) for q in (16.0, 50.0, 84.0)
250
259
  )
260
+ # Convergence diagnostics (audit P1 #2): the Gelman-Rubin potential
261
+ # scale reduction factor (R-hat) per parameter, so posterior intervals
262
+ # are never presented as trustworthy when the chains haven't mixed.
263
+ # (bumps also warns "Did not converge!" via a separate K-S test; R-hat
264
+ # is the standard, interpretable metric. bumps exposes no clean
265
+ # effective-sample-size, so ESS isn't faked from cross-chain draws.)
266
+ try:
267
+ rhat_all = np.asarray(state.gelman(), dtype=float)
268
+ rhat = [float(rhat_all[i]) for i in order]
269
+ rhat_max = float(np.max(rhat_all)) if rhat_all.size else float("nan")
270
+ except Exception: # noqa: BLE001 — aborted/degenerate state (cf. stderr above)
271
+ rhat = [float("nan")] * n_params
272
+ rhat_max = float("nan")
251
273
  result["posterior"] = {
252
274
  "medians": [float(v) for v in med],
253
275
  "interval68": [[float(a), float(b)] for a, b in zip(lo68, hi68, strict=True)],
254
276
  "n_draws": int(pts.shape[0]),
277
+ "rHat": rhat,
278
+ "rHatMax": rhat_max,
279
+ "converged": bool(math.isfinite(rhat_max) and rhat_max < RHAT_THRESHOLD),
280
+ "nChains": int(getattr(state, "Npop", pop * n_params)),
255
281
  }
256
282
  if return_samples:
257
283
  result["samples"] = pts