quantized-lab 0.10.0__tar.gz → 0.11.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.
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/.gitignore +7 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/PKG-INFO +1 -1
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/pyproject.toml +1 -1
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/__init__.py +1 -1
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/app.py +4 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure.py +60 -22
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_categorical.py +39 -22
- quantized_lab-0.11.0/src/quantized/calc/figure_facets.py +359 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_overrides.py +26 -3
- quantized_lab-0.11.0/src/quantized/calc/figure_page.py +449 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_ticks.py +22 -1
- quantized_lab-0.11.0/src/quantized/calc/figure_y2.py +266 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fitting.py +12 -0
- quantized_lab-0.11.0/src/quantized/calc/plotting.py +278 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/semiconductor.py +11 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/datastruct.py +22 -3
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/delimited.py +53 -2
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/fidelity.py +2 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/figure_layers.py +11 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/figure_text.py +36 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/figures_opju.py +2 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_figure_text.py +6 -0
- quantized_lab-0.11.0/src/quantized/io/sqlite_query.py +145 -0
- quantized_lab-0.11.0/src/quantized/routes/database.py +62 -0
- quantized_lab-0.11.0/src/quantized/routes/export_figures.py +467 -0
- quantized_lab-0.11.0/src/quantized/routes/export_figures_aux.py +227 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/export_page.py +25 -6
- quantized_lab-0.11.0/src/quantized/web/assets/index-CQDio8Fq.css +1 -0
- quantized_lab-0.11.0/src/quantized/web/assets/index-jFkYmKQl.js +27 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/web/index.html +2 -2
- quantized_lab-0.10.0/src/quantized/calc/figure_facets.py +0 -127
- quantized_lab-0.10.0/src/quantized/calc/figure_page.py +0 -270
- quantized_lab-0.10.0/src/quantized/calc/plotting.py +0 -147
- quantized_lab-0.10.0/src/quantized/routes/export_figures.py +0 -477
- quantized_lab-0.10.0/src/quantized/web/assets/index-DkU851uO.css +0 -1
- quantized_lab-0.10.0/src/quantized/web/assets/index-N-btoovo.js +0 -27
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/LICENSE +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/NOTICE +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/README.md +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/__main__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/api.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/__init__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/_clipfit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/_natural_neighbor.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/aggregate.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/backgrounds.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/baseline.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/batch_fit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/constants.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/corrections.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/crystallography.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/diffusion.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/electrical.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/electrochemistry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/element_data.json +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/element_data.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/errors.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_break.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_corner.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_field.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_hitmap.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_labels.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_map.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_scale.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_shapes.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_statplots.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_styles.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/figure_ternary.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_autoguess.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_bootstrap.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_bumps.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_constraints.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_equation.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_findxy.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_models.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_models_special.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_odr.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_scan.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/fit_stats.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/formula.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/global_curve_fit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/global_fit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/interp2d.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/linecut.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/magnetic.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/magnetometry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/map.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/mcmc.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/optics.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/pawley.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peak_batch.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peak_fit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peak_integrate.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peak_multifit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peak_track.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peaks.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/peakshapes.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/processing.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/qspace.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/reductions.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/reductions_fft.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/refl_sld_presets.json +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/reflectivity.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/registry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/relaxation.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/report.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/report_emit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/resample.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/rsm.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/rsm_analyze.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/sld.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/sld_formula.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/spectral.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/statplots.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_anova2.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_anova_ext.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_dist.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_glm.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_multivar.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_roc.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_survival.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/stats_tests.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/substrates.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/superconductor.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/surface_fit.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/surface_models.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/thermal.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/thin_film.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/unit_convert.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/units.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/vacuum.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/calc/xray.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/cli.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/__init__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/_hdf5_layout.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/_jcamp_asdf.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/_xrdml_scan.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/base.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/bruker_brml.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/bruker_raw.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/cif.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/consolidated.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/excel.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/hdf5.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/import_filters.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/import_preview.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/jcamp.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/lakeshore.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/ncnr.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/netcdf.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/opus.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_com.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/__init__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/container.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/curve_style_color.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/figures.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/graph_preview.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/notes.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opj.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opj_curves.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/preview.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/templates.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/tree.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/windows.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/writer.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/qd.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/refl1d.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/registry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/report_export.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/rigaku.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/sims.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/spc.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/xrd_csv.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/io/xrdml.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/jobs.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/plugins/__init__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/plugins/contract.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/plugins/loader.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/plugins/steps.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/__init__.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/_bookcache.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/_export_common.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/_payload.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/_uploadcache.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/aggregate.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/baseline.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/books.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/calc.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/corrections.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/crystallography.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/diffusion.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/electrical.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/electrochemistry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/export.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/export_facets.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/fitting.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/fitting_bumps.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/import_template.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/import_wizard.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/jobs_api.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/magnetic.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/magnetometry.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/optics.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/parsers.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/peaks.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/plot.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/reductions.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/reference.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/reflectivity.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/report_export.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/rsm.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/samples.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/semiconductor.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/sld.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/spectral.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/statplots.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/stats.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/stats_design.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/substrates.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/superconductor.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/thermal.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/thin_film.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/vacuum.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/routes/xray.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/samples/demo_vsm.csv +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/server_launch.py +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/src/quantized/web/loading.html +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/tools/origin_compare/README.md +0 -0
- {quantized_lab-0.10.0 → quantized_lab-0.11.0}/tools/visual/README.md +0 -0
|
@@ -16,6 +16,13 @@ node_modules/
|
|
|
16
16
|
frontend/dist/
|
|
17
17
|
*.tsbuildinfo
|
|
18
18
|
|
|
19
|
+
# ── Playwright E2E (GUI_INTERACTION_PLAN #15) ──────────────────────────
|
|
20
|
+
frontend/test-results/
|
|
21
|
+
frontend/playwright-report/
|
|
22
|
+
frontend/e2e/test-results/
|
|
23
|
+
frontend/e2e/playwright-report/
|
|
24
|
+
frontend/blob-report/
|
|
25
|
+
|
|
19
26
|
# ── Tauri / desktop ───────────────────────────────────────────────────
|
|
20
27
|
src-tauri/target/
|
|
21
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantized-lab
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.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.
|
|
3
|
+
version = "0.11.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"
|
|
@@ -25,12 +25,14 @@ from quantized.routes import (
|
|
|
25
25
|
calc,
|
|
26
26
|
corrections,
|
|
27
27
|
crystallography,
|
|
28
|
+
database,
|
|
28
29
|
diffusion,
|
|
29
30
|
electrical,
|
|
30
31
|
electrochemistry,
|
|
31
32
|
export,
|
|
32
33
|
export_facets,
|
|
33
34
|
export_figures,
|
|
35
|
+
export_figures_aux,
|
|
34
36
|
export_page,
|
|
35
37
|
fitting,
|
|
36
38
|
fitting_bumps,
|
|
@@ -146,6 +148,7 @@ def create_app() -> FastAPI:
|
|
|
146
148
|
application.include_router(import_template.router)
|
|
147
149
|
application.include_router(plot.router)
|
|
148
150
|
application.include_router(corrections.router)
|
|
151
|
+
application.include_router(database.router)
|
|
149
152
|
application.include_router(fitting.router)
|
|
150
153
|
application.include_router(fitting_bumps.router)
|
|
151
154
|
application.include_router(jobs_api.router)
|
|
@@ -156,6 +159,7 @@ def create_app() -> FastAPI:
|
|
|
156
159
|
application.include_router(reference.router)
|
|
157
160
|
application.include_router(export.router)
|
|
158
161
|
application.include_router(export_figures.router)
|
|
162
|
+
application.include_router(export_figures_aux.router)
|
|
159
163
|
application.include_router(export_facets.router)
|
|
160
164
|
application.include_router(export_page.router)
|
|
161
165
|
application.include_router(report_export.router)
|
|
@@ -32,7 +32,7 @@ from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps #
|
|
|
32
32
|
__all__ = ["draw_series_axes", "render_figure", "style_rc"]
|
|
33
33
|
|
|
34
34
|
_FORMATS = ("pdf", "svg", "png", "tiff")
|
|
35
|
-
_LINESTYLE = {"solid": "-", "dashed": "--", "dotted": ":"}
|
|
35
|
+
_LINESTYLE = {"solid": "-", "dashed": "--", "dotted": ":", "none": "none"}
|
|
36
36
|
# Fixed fill translucency for MAIN #13 (fill under/between curves) — matches
|
|
37
37
|
# the screen side's `uplotFill.ts` FILL_ALPHA_PCT (25%) so an exported figure
|
|
38
38
|
# reads the same as its on-screen counterpart.
|
|
@@ -278,6 +278,11 @@ def _render_impl(
|
|
|
278
278
|
y_fmt: Mapping[str, Any] | None = None,
|
|
279
279
|
x_step: float | None = None,
|
|
280
280
|
y_step: float | None = None,
|
|
281
|
+
y2_mask: Sequence[bool] | None = None,
|
|
282
|
+
y2_label: str = "",
|
|
283
|
+
y2_scale: str | None = None,
|
|
284
|
+
y2_fmt: Mapping[str, Any] | None = None,
|
|
285
|
+
y2_step: float | None = None,
|
|
281
286
|
) -> bytes | dict[str, Any]:
|
|
282
287
|
"""Render ``series`` (each ``(label, y)``) against ``x`` to image bytes.
|
|
283
288
|
|
|
@@ -303,7 +308,17 @@ def _render_impl(
|
|
|
303
308
|
(MAIN #12) select linear/log/reciprocal; ``x_log``/``y_log`` are the
|
|
304
309
|
back-compat boolean fallback (see :func:`draw_series_axes`'s doc).
|
|
305
310
|
``x_fmt``/``y_fmt`` (MAIN #24) are the tick-label number format -- see
|
|
306
|
-
:func:`draw_series_axes`'s doc.
|
|
311
|
+
:func:`draw_series_axes`'s doc. ``y2_mask`` (parallel to ``series``,
|
|
312
|
+
``True`` marks a channel drawn on a secondary/right Y axis) dispatches to
|
|
313
|
+
:func:`quantized.calc.figure_y2.render_with_secondary_axis` -- a real
|
|
314
|
+
``Axes.twinx()`` -- instead of ``draw_series_axes``'s single axes;
|
|
315
|
+
``None``/all-``False`` (the default) is today's single-axis behaviour,
|
|
316
|
+
byte-identical. ``y2_label``/``y2_scale``/``y2_fmt``/``y2_step`` mirror
|
|
317
|
+
their primary-axis counterparts but apply only to the secondary axis;
|
|
318
|
+
a fixed secondary range rides ``overrides["y2_lim"]`` (see
|
|
319
|
+
``figure_y2.render_with_secondary_axis``'s doc). Not compatible with
|
|
320
|
+
``x_breaks`` (raises ``ValueError``) -- a broken figure has several
|
|
321
|
+
axes already and gains no coherent secondary-axis meaning.
|
|
307
322
|
"""
|
|
308
323
|
if fmt not in _FORMATS:
|
|
309
324
|
raise ValueError(f"fmt must be one of {_FORMATS}")
|
|
@@ -315,11 +330,16 @@ def _render_impl(
|
|
|
315
330
|
title = safe_mathtext_label(title)
|
|
316
331
|
x_label = safe_mathtext_label(x_label)
|
|
317
332
|
y_label = safe_mathtext_label(y_label)
|
|
333
|
+
y2_label = safe_mathtext_label(y2_label)
|
|
318
334
|
series = [(safe_mathtext_label(label), y) for label, y in series]
|
|
319
335
|
st = figure_style(style)
|
|
320
336
|
ov = dict(overrides or {})
|
|
321
337
|
_validate_overrides(ov)
|
|
322
338
|
resolved_dpi = int(dpi) if dpi is not None else int(st.dpi)
|
|
339
|
+
y2_mask_list = list(y2_mask) if y2_mask is not None else [False] * len(series)
|
|
340
|
+
if len(y2_mask_list) != len(series):
|
|
341
|
+
raise ValueError("y2_mask must have the same length as series")
|
|
342
|
+
has_y2 = any(y2_mask_list)
|
|
323
343
|
|
|
324
344
|
# rc_context scopes typography to this render (see style_rc). (matplotlib's
|
|
325
345
|
# RcParams Literal-key type is impractical with the dynamic font.<generic>
|
|
@@ -329,6 +349,8 @@ def _render_impl(
|
|
|
329
349
|
|
|
330
350
|
xv: NDArray[np.float64] = np.asarray(x, dtype=float)
|
|
331
351
|
x_breaks = ov.get("x_breaks")
|
|
352
|
+
if has_y2 and x_breaks:
|
|
353
|
+
raise ValueError("y2_keys is not supported together with x_breaks")
|
|
332
354
|
with matplotlib.rc_context(rc): # type: ignore[arg-type]
|
|
333
355
|
# Manual axis breaks (gap #21): a distinct, twinned-panel rendering
|
|
334
356
|
# path — not compatible with the hit-map collector (collect_map is
|
|
@@ -365,26 +387,42 @@ def _render_impl(
|
|
|
365
387
|
)
|
|
366
388
|
fig, ax = plt.subplots(figsize=figsize)
|
|
367
389
|
try:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
390
|
+
if has_y2:
|
|
391
|
+
# Lazy import: mirrors figure_break's own lazy import above —
|
|
392
|
+
# keeps this module's own top-level import list light, and
|
|
393
|
+
# the twinx orchestration out of the 500-line ceiling here.
|
|
394
|
+
from quantized.calc.figure_y2 import render_with_secondary_axis
|
|
395
|
+
|
|
396
|
+
artists = render_with_secondary_axis(
|
|
397
|
+
fig, ax, xv, series, series_styles, y2_mask_list,
|
|
398
|
+
st=st, ov=ov, x_log=x_log, y_log=y_log,
|
|
399
|
+
x_scale=x_scale, y_scale=y_scale,
|
|
400
|
+
title=title, x_label=x_label, y_label=y_label,
|
|
401
|
+
x_fmt=x_fmt, y_fmt=y_fmt, x_step=x_step, y_step=y_step,
|
|
402
|
+
y2_label=y2_label, y2_scale=y2_scale,
|
|
403
|
+
y2_fmt=y2_fmt, y2_step=y2_step,
|
|
404
|
+
)
|
|
405
|
+
else:
|
|
406
|
+
artists = draw_series_axes(
|
|
407
|
+
fig,
|
|
408
|
+
ax,
|
|
409
|
+
xv,
|
|
410
|
+
series,
|
|
411
|
+
st=st,
|
|
412
|
+
ov=ov,
|
|
413
|
+
x_log=x_log,
|
|
414
|
+
y_log=y_log,
|
|
415
|
+
x_scale=x_scale,
|
|
416
|
+
y_scale=y_scale,
|
|
417
|
+
title=title,
|
|
418
|
+
x_label=x_label,
|
|
419
|
+
y_label=y_label,
|
|
420
|
+
series_styles=series_styles,
|
|
421
|
+
x_fmt=x_fmt,
|
|
422
|
+
y_fmt=y_fmt,
|
|
423
|
+
x_step=x_step,
|
|
424
|
+
y_step=y_step,
|
|
425
|
+
)
|
|
388
426
|
if not ov.get("margins"):
|
|
389
427
|
fig.tight_layout()
|
|
390
428
|
if collect_map:
|
|
@@ -28,6 +28,44 @@ __all__ = ["render_categorical_figure"]
|
|
|
28
28
|
_FORMATS = ("pdf", "svg", "png", "tiff")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _draw_categorical_bars(
|
|
32
|
+
ax: Any,
|
|
33
|
+
groups: list[str],
|
|
34
|
+
series: list[str],
|
|
35
|
+
vals: Any,
|
|
36
|
+
errs: Any | None,
|
|
37
|
+
stacked: bool,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Draw one grouped/stacked bar panel into `ax` — shared by the flat
|
|
40
|
+
single-panel path below and `figure_facets.render_categorical_facets_figure`
|
|
41
|
+
(GUI_INTERACTION #12 slice 4b), so a faceted panel matches the flat
|
|
42
|
+
export exactly. Caller applies title/axis-labels/spines/legend/grid —
|
|
43
|
+
this function only draws the bars + category ticks + zero baseline."""
|
|
44
|
+
n_groups, n_series = len(groups), len(series)
|
|
45
|
+
x = np.arange(n_groups, dtype=float)
|
|
46
|
+
if stacked:
|
|
47
|
+
bottom = np.zeros(n_groups)
|
|
48
|
+
for si in range(n_series):
|
|
49
|
+
yerr = errs[:, si] if errs is not None and si == n_series - 1 else None
|
|
50
|
+
ax.bar(
|
|
51
|
+
x, vals[:, si], 0.68, bottom=bottom, yerr=yerr, capsize=3,
|
|
52
|
+
label=series[si],
|
|
53
|
+
)
|
|
54
|
+
bottom = bottom + np.nan_to_num(vals[:, si])
|
|
55
|
+
else:
|
|
56
|
+
width = 0.8 / n_series
|
|
57
|
+
for si in range(n_series):
|
|
58
|
+
offset = (si - (n_series - 1) / 2) * width
|
|
59
|
+
yerr = errs[:, si] if errs is not None else None
|
|
60
|
+
ax.bar(
|
|
61
|
+
x + offset, vals[:, si], width * 0.85, yerr=yerr, capsize=3,
|
|
62
|
+
label=series[si],
|
|
63
|
+
)
|
|
64
|
+
ax.set_xticks(x)
|
|
65
|
+
ax.set_xticklabels(groups)
|
|
66
|
+
ax.axhline(0, color="0.3", linewidth=0.8) # baseline, visible for mixed-sign data
|
|
67
|
+
|
|
68
|
+
|
|
31
69
|
def _to_matrix(
|
|
32
70
|
values: list[list[float]], n_groups: int, n_series: int, name: str
|
|
33
71
|
) -> np.ndarray:
|
|
@@ -111,28 +149,7 @@ def render_categorical_figure(
|
|
|
111
149
|
with matplotlib.rc_context(rc): # type: ignore[arg-type]
|
|
112
150
|
fig, ax = plt.subplots(figsize=figsize)
|
|
113
151
|
try:
|
|
114
|
-
|
|
115
|
-
if stacked:
|
|
116
|
-
bottom = np.zeros(n_groups)
|
|
117
|
-
for si in range(n_series):
|
|
118
|
-
yerr = errs[:, si] if errs is not None and si == n_series - 1 else None
|
|
119
|
-
ax.bar(
|
|
120
|
-
x, vals[:, si], 0.68, bottom=bottom, yerr=yerr, capsize=3,
|
|
121
|
-
label=series[si],
|
|
122
|
-
)
|
|
123
|
-
bottom = bottom + np.nan_to_num(vals[:, si])
|
|
124
|
-
else:
|
|
125
|
-
width = 0.8 / n_series
|
|
126
|
-
for si in range(n_series):
|
|
127
|
-
offset = (si - (n_series - 1) / 2) * width
|
|
128
|
-
yerr = errs[:, si] if errs is not None else None
|
|
129
|
-
ax.bar(
|
|
130
|
-
x + offset, vals[:, si], width * 0.85, yerr=yerr, capsize=3,
|
|
131
|
-
label=series[si],
|
|
132
|
-
)
|
|
133
|
-
ax.set_xticks(x)
|
|
134
|
-
ax.set_xticklabels(groups)
|
|
135
|
-
ax.axhline(0, color="0.3", linewidth=0.8) # baseline, visible for mixed-sign data
|
|
152
|
+
_draw_categorical_bars(ax, groups, series, vals, errs, stacked)
|
|
136
153
|
if title:
|
|
137
154
|
ax.set_title(title)
|
|
138
155
|
if x_label:
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""Publication rendering for faceted (small-multiples) plots.
|
|
2
|
+
|
|
3
|
+
ORIGIN_GAP_PLAN #21 (export half, faceting) + GUI_INTERACTION #12 slice 4b
|
|
4
|
+
(stat-stage faceted export). Pure layer: N independently pre-split panels
|
|
5
|
+
sharing scales/labels -> image bytes out. ``render_facets_figure`` mirrors
|
|
6
|
+
the interactive xy facet splitter (frontend ``lib/facet.facetPayloads``):
|
|
7
|
+
each panel is already the (label, x, series) data for one categorical level.
|
|
8
|
+
``render_stat_facets_figure``/``render_categorical_facets_figure`` mirror
|
|
9
|
+
the interactive StatStage's OWN facet grid (``useStatStage``'s
|
|
10
|
+
``drawFacets``) for box/violin and bar mode respectively — reusing
|
|
11
|
+
``figure_statplots``/``figure_categorical``'s per-panel draw functions so a
|
|
12
|
+
single facet renders byte-identically to that module's flat single-panel
|
|
13
|
+
export. All three share this module's grid-layout helpers
|
|
14
|
+
(``_grid_shape``/``_new_grid_figure``): the SAME ``ceil(sqrt(n))``-column,
|
|
15
|
+
auto-wrapping-row layout the screen's CSS grid uses
|
|
16
|
+
(``Math.ceil(Math.sqrt(n)))`` columns — see ``StatStage.tsx``/
|
|
17
|
+
``GraphPreview.tsx``), so an exported faceted figure tiles identically to
|
|
18
|
+
what's on screen.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from io import BytesIO
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import matplotlib
|
|
27
|
+
|
|
28
|
+
matplotlib.use("Agg") # headless
|
|
29
|
+
|
|
30
|
+
import matplotlib.pyplot as plt # noqa: E402
|
|
31
|
+
import numpy as np # noqa: E402
|
|
32
|
+
|
|
33
|
+
from quantized.calc.figure import _plot_kwargs # noqa: E402
|
|
34
|
+
from quantized.calc.figure_labels import safe_mathtext_label # noqa: E402
|
|
35
|
+
from quantized.calc.figure_styles import figure_style # noqa: E402
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"render_facets_figure",
|
|
39
|
+
"render_stat_facets_figure",
|
|
40
|
+
"render_categorical_facets_figure",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
_FORMATS = ("pdf", "svg", "png", "tiff")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _grid_shape(n: int) -> tuple[int, int]:
|
|
47
|
+
"""(rows, cols) for `n` panels — the SAME `ceil(sqrt(n))`-column layout
|
|
48
|
+
`render_facets_figure` below already uses, and the screen's own CSS grid
|
|
49
|
+
formula (`Math.ceil(Math.sqrt(n))` columns, auto-wrapping rows —
|
|
50
|
+
`StatStage.tsx`/`GraphPreview.tsx`), so every faceted export tiles
|
|
51
|
+
identically to what's on screen."""
|
|
52
|
+
cols = int(np.ceil(np.sqrt(n)))
|
|
53
|
+
rows = int(np.ceil(n / cols))
|
|
54
|
+
return rows, cols
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _new_grid_figure(n: int, figsize: tuple[float, float]) -> tuple[Any, list[Any]]:
|
|
58
|
+
"""A fresh `_grid_shape(n)` subplot grid; returns the figure and its axes
|
|
59
|
+
flattened + trimmed to exactly `n` (unused trailing cells past `n` are
|
|
60
|
+
hidden, matching `render_facets_figure`'s own convention below)."""
|
|
61
|
+
rows, cols = _grid_shape(n)
|
|
62
|
+
fig, axes_grid = plt.subplots(rows, cols, figsize=figsize, squeeze=False)
|
|
63
|
+
flat = [ax for row in axes_grid for ax in row]
|
|
64
|
+
for j in range(n, len(flat)):
|
|
65
|
+
flat[j].set_visible(False)
|
|
66
|
+
return fig, flat[:n]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def render_facets_figure(
|
|
70
|
+
panels: list[dict[str, Any]],
|
|
71
|
+
*,
|
|
72
|
+
x_log: bool = False,
|
|
73
|
+
y_log: bool = False,
|
|
74
|
+
title: str = "",
|
|
75
|
+
x_label: str = "",
|
|
76
|
+
y_label: str = "",
|
|
77
|
+
fmt: str = "pdf",
|
|
78
|
+
style: str = "default",
|
|
79
|
+
width_in: float | None = None,
|
|
80
|
+
height_in: float | None = None,
|
|
81
|
+
dpi: int = 200,
|
|
82
|
+
) -> bytes:
|
|
83
|
+
"""Render one small-multiples panel per facet level.
|
|
84
|
+
|
|
85
|
+
Each ``panels[i]`` is ``{"label": str, "x": [...], "series": [{"label":
|
|
86
|
+
str, "y": [...]}]}`` — a pre-split slice of data for one categorical
|
|
87
|
+
level (see the frontend's ``lib/facet.facetPayloads``). Panels tile into
|
|
88
|
+
as-square-as-possible rows/columns and share x/y scales (``sharex`` /
|
|
89
|
+
``sharey``) so magnitudes stay comparable across levels; each panel
|
|
90
|
+
carries its own facet-level title, and unused trailing grid cells (when
|
|
91
|
+
the panel count isn't a perfect rows*cols rectangle) are hidden.
|
|
92
|
+
"""
|
|
93
|
+
if fmt not in _FORMATS:
|
|
94
|
+
raise ValueError(f"fmt must be one of {_FORMATS}")
|
|
95
|
+
if not panels:
|
|
96
|
+
raise ValueError("panels must be non-empty")
|
|
97
|
+
# Rich-text labels (GOTO #5): de-math INVALID $...$ so savefig never raises.
|
|
98
|
+
title = safe_mathtext_label(title)
|
|
99
|
+
x_label = safe_mathtext_label(x_label)
|
|
100
|
+
y_label = safe_mathtext_label(y_label)
|
|
101
|
+
|
|
102
|
+
st = figure_style(style)
|
|
103
|
+
n = len(panels)
|
|
104
|
+
cols = int(np.ceil(np.sqrt(n)))
|
|
105
|
+
rows = int(np.ceil(n / cols))
|
|
106
|
+
figsize = (
|
|
107
|
+
width_in or st.fig_width_in * cols * 0.8,
|
|
108
|
+
height_in or st.fig_height_in * rows * 0.8,
|
|
109
|
+
)
|
|
110
|
+
fallback = "DejaVu Serif" if st.font_generic == "serif" else "DejaVu Sans"
|
|
111
|
+
rc: dict[str, Any] = {
|
|
112
|
+
"font.family": st.font_generic,
|
|
113
|
+
f"font.{st.font_generic}": [st.font_name, fallback],
|
|
114
|
+
"font.size": st.font_size,
|
|
115
|
+
"axes.labelsize": st.font_size,
|
|
116
|
+
"axes.titlesize": st.font_size,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
with matplotlib.rc_context(rc): # type: ignore[arg-type]
|
|
120
|
+
fig, axes_grid = plt.subplots(
|
|
121
|
+
rows, cols, figsize=figsize, sharex=True, sharey=True, squeeze=False,
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
flat = [ax for row in axes_grid for ax in row]
|
|
125
|
+
for i, panel in enumerate(panels):
|
|
126
|
+
ax = flat[i]
|
|
127
|
+
x = np.asarray(panel.get("x", []), dtype=float)
|
|
128
|
+
series = panel.get("series", [])
|
|
129
|
+
for si, s in enumerate(series):
|
|
130
|
+
kw = _plot_kwargs(st.line_width, st.marker_size, None)
|
|
131
|
+
ax.plot(
|
|
132
|
+
x, np.asarray(s.get("y", []), dtype=float),
|
|
133
|
+
label=safe_mathtext_label(str(s.get("label", f"s{si}"))), **kw,
|
|
134
|
+
)
|
|
135
|
+
ax.set_title(
|
|
136
|
+
safe_mathtext_label(str(panel.get("label", ""))), fontsize=st.font_size
|
|
137
|
+
)
|
|
138
|
+
if x_log:
|
|
139
|
+
ax.set_xscale("log")
|
|
140
|
+
if y_log:
|
|
141
|
+
ax.set_yscale("log")
|
|
142
|
+
if not st.box_on:
|
|
143
|
+
ax.spines["top"].set_visible(False)
|
|
144
|
+
ax.spines["right"].set_visible(False)
|
|
145
|
+
if st.grid_alpha > 0:
|
|
146
|
+
ax.grid(True, alpha=st.grid_alpha)
|
|
147
|
+
if len(series) > 1:
|
|
148
|
+
ax.legend(fontsize=max(6.0, st.legend_font_size - 2), frameon=st.legend_box)
|
|
149
|
+
for j in range(n, len(flat)):
|
|
150
|
+
flat[j].set_visible(False)
|
|
151
|
+
|
|
152
|
+
if title:
|
|
153
|
+
fig.suptitle(title)
|
|
154
|
+
if x_label:
|
|
155
|
+
fig.supxlabel(x_label)
|
|
156
|
+
if y_label:
|
|
157
|
+
fig.supylabel(y_label)
|
|
158
|
+
fig.tight_layout()
|
|
159
|
+
buf = BytesIO()
|
|
160
|
+
fig.savefig(buf, format=fmt, dpi=dpi)
|
|
161
|
+
return buf.getvalue()
|
|
162
|
+
finally:
|
|
163
|
+
plt.close(fig)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def render_stat_facets_figure(
|
|
167
|
+
panels: list[dict[str, Any]],
|
|
168
|
+
*,
|
|
169
|
+
default_kind: str,
|
|
170
|
+
dist: str = "norm",
|
|
171
|
+
bins: str | int = "fd",
|
|
172
|
+
fit: str | None = None,
|
|
173
|
+
title: str = "",
|
|
174
|
+
x_label: str = "",
|
|
175
|
+
y_label: str = "",
|
|
176
|
+
fmt: str = "pdf",
|
|
177
|
+
style: str = "default",
|
|
178
|
+
width_in: float | None = None,
|
|
179
|
+
height_in: float | None = None,
|
|
180
|
+
dpi: int | None = None,
|
|
181
|
+
) -> bytes:
|
|
182
|
+
"""Faceted box/violin export (GUI_INTERACTION #12 slice 4b, StatStage's
|
|
183
|
+
"facet by" grid). Each ``panels[i]`` is ``{"label": str, "kind": "box" |
|
|
184
|
+
"violin" (optional), "data": [[...], ...], "labels": [...] | None}``.
|
|
185
|
+
|
|
186
|
+
``kind`` is per-facet MODE FIDELITY: the interactive StatStage computes
|
|
187
|
+
each facet slice independently (``useStatStage.computeFacetGroupDraws``)
|
|
188
|
+
and a violin slice whose own ``/api/statplots/violin`` call failed
|
|
189
|
+
degrades to a box plot for JUST that slice (never fabricating a KDE) —
|
|
190
|
+
omitting ``kind`` falls back to ``default_kind`` (today's degrade-free
|
|
191
|
+
case, every panel the same requested kind), while an explicit per-panel
|
|
192
|
+
``kind`` reproduces a mixed grid exactly as the screen showed it.
|
|
193
|
+
|
|
194
|
+
Reuses ``figure_statplots._draw_statplot`` for each panel so a single
|
|
195
|
+
facet renders byte-identically to that module's flat single-panel path.
|
|
196
|
+
"""
|
|
197
|
+
from quantized.calc.figure_statplots import _GROUPED, _draw_statplot
|
|
198
|
+
|
|
199
|
+
if fmt not in _FORMATS:
|
|
200
|
+
raise ValueError(f"fmt must be one of {_FORMATS}")
|
|
201
|
+
if not panels:
|
|
202
|
+
raise ValueError("panels must be non-empty")
|
|
203
|
+
title = safe_mathtext_label(title)
|
|
204
|
+
x_label = safe_mathtext_label(x_label)
|
|
205
|
+
y_label = safe_mathtext_label(y_label)
|
|
206
|
+
|
|
207
|
+
st = figure_style(style)
|
|
208
|
+
resolved_dpi = int(dpi) if dpi is not None else int(st.dpi)
|
|
209
|
+
n = len(panels)
|
|
210
|
+
rows, cols = _grid_shape(n)
|
|
211
|
+
figsize = (
|
|
212
|
+
width_in or st.fig_width_in * cols * 0.8,
|
|
213
|
+
height_in or st.fig_height_in * rows * 0.8,
|
|
214
|
+
)
|
|
215
|
+
fallback = "DejaVu Serif" if st.font_generic == "serif" else "DejaVu Sans"
|
|
216
|
+
rc: dict[str, Any] = {
|
|
217
|
+
"font.family": st.font_generic,
|
|
218
|
+
f"font.{st.font_generic}": [st.font_name, fallback],
|
|
219
|
+
"font.size": st.font_size,
|
|
220
|
+
"axes.labelsize": st.font_size,
|
|
221
|
+
"axes.titlesize": st.font_size,
|
|
222
|
+
"xtick.top": st.box_on,
|
|
223
|
+
"ytick.right": st.box_on,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
prepared: list[tuple[str, str, Any, list[str] | None]] = []
|
|
227
|
+
for p in panels:
|
|
228
|
+
label = safe_mathtext_label(str(p.get("label", "")))
|
|
229
|
+
kind = p.get("kind") or default_kind
|
|
230
|
+
if kind not in _GROUPED:
|
|
231
|
+
raise ValueError(f"facet kind must be one of {_GROUPED}")
|
|
232
|
+
data = p.get("data")
|
|
233
|
+
if not isinstance(data, list) or not data:
|
|
234
|
+
raise ValueError(f"facet {label!r} needs a non-empty list of groups")
|
|
235
|
+
flabels = p.get("labels")
|
|
236
|
+
flabels = [safe_mathtext_label(str(g)) for g in flabels] if flabels else flabels
|
|
237
|
+
prepared.append((label, kind, data, flabels))
|
|
238
|
+
|
|
239
|
+
with matplotlib.rc_context(rc): # type: ignore[arg-type]
|
|
240
|
+
fig, axes = _new_grid_figure(n, figsize)
|
|
241
|
+
try:
|
|
242
|
+
for ax, (label, kind, data, flabels) in zip(axes, prepared, strict=True):
|
|
243
|
+
_draw_statplot(ax, kind, data, flabels, dist, bins, fit, st)
|
|
244
|
+
ax.set_title(label, fontsize=st.font_size)
|
|
245
|
+
if not st.box_on:
|
|
246
|
+
ax.spines["top"].set_visible(False)
|
|
247
|
+
ax.spines["right"].set_visible(False)
|
|
248
|
+
if title:
|
|
249
|
+
fig.suptitle(title)
|
|
250
|
+
if x_label:
|
|
251
|
+
fig.supxlabel(x_label)
|
|
252
|
+
if y_label:
|
|
253
|
+
fig.supylabel(y_label)
|
|
254
|
+
fig.tight_layout()
|
|
255
|
+
buf = BytesIO()
|
|
256
|
+
fig.savefig(buf, format=fmt, dpi=resolved_dpi)
|
|
257
|
+
return buf.getvalue()
|
|
258
|
+
finally:
|
|
259
|
+
plt.close(fig)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def render_categorical_facets_figure(
|
|
263
|
+
panels: list[dict[str, Any]],
|
|
264
|
+
*,
|
|
265
|
+
stacked: bool = False,
|
|
266
|
+
title: str = "",
|
|
267
|
+
x_label: str = "",
|
|
268
|
+
y_label: str = "",
|
|
269
|
+
fmt: str = "pdf",
|
|
270
|
+
style: str = "default",
|
|
271
|
+
width_in: float | None = None,
|
|
272
|
+
height_in: float | None = None,
|
|
273
|
+
dpi: int = 200,
|
|
274
|
+
) -> bytes:
|
|
275
|
+
"""Faceted grouped/stacked bar export (GUI_INTERACTION #12 slice 4b,
|
|
276
|
+
StatStage's bar-mode "facet by" grid). Each ``panels[i]`` is
|
|
277
|
+
``{"label": str, "groups": [...], "series": [...], "values": [[...]],
|
|
278
|
+
"errors": [[...]] | None}`` — a SELF-CONTAINED category x series matrix
|
|
279
|
+
(a facet's category set can differ slice to slice when a grouped-by
|
|
280
|
+
column level is absent from that slice, so panels never share one
|
|
281
|
+
``groups`` list). Reuses ``figure_categorical._draw_categorical_bars``
|
|
282
|
+
so a single panel matches that module's flat single-panel export.
|
|
283
|
+
|
|
284
|
+
``series`` is assumed consistent across panels (the same plotted
|
|
285
|
+
channels every slice draws), so ONE legend on the first panel documents
|
|
286
|
+
the whole figure rather than repeating it in every small cell.
|
|
287
|
+
"""
|
|
288
|
+
from quantized.calc.figure_categorical import (
|
|
289
|
+
_draw_categorical_bars,
|
|
290
|
+
_to_error_matrix,
|
|
291
|
+
_to_matrix,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if fmt not in _FORMATS:
|
|
295
|
+
raise ValueError(f"fmt must be one of {_FORMATS}")
|
|
296
|
+
if not panels:
|
|
297
|
+
raise ValueError("panels must be non-empty")
|
|
298
|
+
title = safe_mathtext_label(title)
|
|
299
|
+
x_label = safe_mathtext_label(x_label)
|
|
300
|
+
y_label = safe_mathtext_label(y_label)
|
|
301
|
+
|
|
302
|
+
st = figure_style(style)
|
|
303
|
+
n = len(panels)
|
|
304
|
+
rows, cols = _grid_shape(n)
|
|
305
|
+
figsize = (
|
|
306
|
+
width_in or st.fig_width_in * cols * 0.8,
|
|
307
|
+
height_in or st.fig_height_in * rows * 0.8,
|
|
308
|
+
)
|
|
309
|
+
fallback = "DejaVu Serif" if st.font_generic == "serif" else "DejaVu Sans"
|
|
310
|
+
rc: dict[str, Any] = {
|
|
311
|
+
"font.family": st.font_generic,
|
|
312
|
+
f"font.{st.font_generic}": [st.font_name, fallback],
|
|
313
|
+
"font.size": st.font_size,
|
|
314
|
+
"axes.labelsize": st.font_size,
|
|
315
|
+
"axes.titlesize": st.font_size,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
prepared = []
|
|
319
|
+
for p in panels:
|
|
320
|
+
label = safe_mathtext_label(str(p.get("label", "")))
|
|
321
|
+
groups = [safe_mathtext_label(str(g)) for g in p.get("groups", [])]
|
|
322
|
+
series = [safe_mathtext_label(str(s)) for s in p.get("series", [])]
|
|
323
|
+
if not groups:
|
|
324
|
+
raise ValueError(f"facet {label!r} needs a non-empty groups list")
|
|
325
|
+
if not series:
|
|
326
|
+
raise ValueError(f"facet {label!r} needs a non-empty series list")
|
|
327
|
+
vals = _to_matrix(p.get("values", []), len(groups), len(series), f"facet {label!r} values")
|
|
328
|
+
errs = _to_error_matrix(p.get("errors"), len(groups), len(series))
|
|
329
|
+
prepared.append((label, groups, series, vals, errs))
|
|
330
|
+
|
|
331
|
+
with matplotlib.rc_context(rc): # type: ignore[arg-type]
|
|
332
|
+
fig, axes = _new_grid_figure(n, figsize)
|
|
333
|
+
try:
|
|
334
|
+
for ax, (label, groups, series, vals, errs) in zip(axes, prepared, strict=True):
|
|
335
|
+
_draw_categorical_bars(ax, groups, series, vals, errs, stacked)
|
|
336
|
+
ax.set_title(label, fontsize=st.font_size)
|
|
337
|
+
if not st.box_on:
|
|
338
|
+
ax.spines["top"].set_visible(False)
|
|
339
|
+
ax.spines["right"].set_visible(False)
|
|
340
|
+
if st.grid_alpha > 0:
|
|
341
|
+
ax.grid(True, alpha=st.grid_alpha, axis="y")
|
|
342
|
+
first_series = prepared[0][2]
|
|
343
|
+
if len(first_series) > 1:
|
|
344
|
+
axes[0].legend(
|
|
345
|
+
frameon=st.legend_box, fontsize=max(6.0, st.legend_font_size - 2),
|
|
346
|
+
loc=st.legend_location,
|
|
347
|
+
)
|
|
348
|
+
if title:
|
|
349
|
+
fig.suptitle(title)
|
|
350
|
+
if x_label:
|
|
351
|
+
fig.supxlabel(x_label)
|
|
352
|
+
if y_label:
|
|
353
|
+
fig.supylabel(y_label)
|
|
354
|
+
fig.tight_layout()
|
|
355
|
+
buf = BytesIO()
|
|
356
|
+
fig.savefig(buf, format=fmt, dpi=dpi)
|
|
357
|
+
return buf.getvalue()
|
|
358
|
+
finally:
|
|
359
|
+
plt.close(fig)
|