├── new_docs ├── docs │ ├── baseline │ ├── CONTRIBUTING.md │ ├── CONTRIBUTING_DOC.md │ ├── .htaccess │ ├── mplhep.png │ ├── _static │ │ ├── mathjax.js │ │ └── custom_css.css │ ├── gallery │ │ ├── model_examples_pull.md │ │ ├── 1d_comparison_pull.md │ │ ├── model_all_comparisons.md │ │ ├── model_examples_stacked.md │ │ ├── model_examples_unstacked.md │ │ ├── 1d_comparison_asymmetry.md │ │ ├── 1d_comparison_difference.md │ │ ├── 1d_comparison_efficiency.md │ │ ├── 1d_comparison_split_ratio.md │ │ ├── 1d_comparison_only_efficiency.md │ │ ├── model_examples_stacked_unstacked.md │ │ ├── 1d_comparison_ratio.md │ │ ├── model_examples_pull_no_model_unc.md │ │ ├── 1d_comparison_relative_difference.md │ │ ├── model_all_comparisons_no_model_unc.md │ │ ├── model_with_stacked_and_unstacked_function_components.md │ │ ├── model_with_stacked_and_unstacked_histograms_components.md │ │ └── ratio_data_vs_model_with_stacked_and_unstacked_function_components.md │ ├── svg_utils.py │ ├── resize_svg.py │ ├── stylesheets │ │ ├── neoteroi-spantable.css │ │ └── neoteroi-cards.css │ ├── api.md │ ├── guide_advanced_template.md │ ├── guide.md │ └── gallery.md ├── requirements.txt ├── mkdocs_norender.yml └── docs_hooks.py ├── binder └── postBuild ├── install.sh ├── img ├── style0.png ├── style1.png ├── style2.png ├── Example1.png ├── Example2.png └── FontComp.png ├── docs ├── source │ ├── guide.rst │ ├── _static │ │ └── mplhep.png │ ├── gallery │ │ ├── index.rst │ │ ├── labels.rst │ │ └── styles.rst │ ├── index.rst │ ├── api.rst │ └── install.rst ├── requirements.txt ├── Makefile └── make.bat ├── tests ├── baseline │ ├── test_log.png │ ├── test_simple.png │ ├── test_histplot.png │ ├── test_pub_loc.png │ ├── test_simple2d.png │ ├── test_hist2dplot.png │ ├── test_histplot_w2.png │ ├── test_inputs_bh.png │ ├── test_label_loc.png │ ├── test_log_mpl39.png │ ├── test_onebin_hist.png │ ├── test_simple_xerr.png │ ├── test_square_cbar.png │ ├── test_style_alice.png │ ├── test_style_atlas.png │ ├── test_style_cms.png │ ├── test_style_dune.png │ ├── test_style_dune1.png │ ├── test_style_lhcb.png │ ├── test_style_lhcb2.png │ ├── 1d_comparison_pull.png │ ├── test_histplot_bar.png │ ├── test_histplot_flow.png │ ├── test_histplot_real.png │ ├── test_inputs_basic.png │ ├── test_inputs_bh_cat.png │ ├── test_inputs_uproot.png │ ├── test_labeltext_loc.png │ ├── test_style_cmstex.png │ ├── test_style_dunetex.png │ ├── test_style_roottex.png │ ├── test_yscale_legend.png │ ├── 1d_comparison_ratio.png │ ├── model_all_comparisons.png │ ├── model_examples_pull.png │ ├── test_dune_label_loc.png │ ├── test_hist2dplot_flow.png │ ├── test_hist_visual_fill.png │ ├── test_histplot_density.png │ ├── test_histplot_kwargs.png │ ├── test_histplot_stack.png │ ├── test_histplot_types.png │ ├── test_style_atlastex.png │ ├── test_style_dunetex1.png │ ├── test_style_lhcbtex1.png │ ├── test_style_lhcbtex2.png │ ├── test_style_plothist.png │ ├── 1d_comparison_asymmetry.png │ ├── model_examples_stacked.png │ ├── test_add_text_placement.png │ ├── test_hist_visual_single.png │ ├── test_histplot_multiple.png │ ├── test_histplot_sort_None.png │ ├── test_histplot_type_flow.png │ ├── test_histplot_w2_scipy.png │ ├── test_histplot_xoffsets.png │ ├── 1d_comparison_difference.png │ ├── 1d_comparison_efficiency.png │ ├── 1d_comparison_split_ratio.png │ ├── model_examples_unstacked.png │ ├── test_append_text_multiline.png │ ├── test_hist2dplot_cbar_False.png │ ├── test_hist2dplot_cbar_True.png │ ├── test_hist_visual_multiple.png │ ├── test_histplot_sort_label.png │ ├── test_histplot_sort_label_r.png │ ├── test_histplot_sort_sort5.png │ ├── test_histplot_sort_yield.png │ ├── test_histplot_sort_yield_r.png │ ├── test_histplot_uproot_flow.png │ ├── test_histplot_w2_methods.png │ ├── test_histplot_w2_noscipy.png │ ├── test_yscale_anchored_text.png │ ├── test_add_text_placement_any.png │ ├── test_add_text_placement_asym.png │ ├── test_hist2dplot_inputs_nobin.png │ ├── test_style_dune_logo_colors.png │ ├── 1d_comparison_only_efficiency.png │ ├── test_exp_text_scilocator_adjust.png │ ├── test_hist2dplot_cbar_subplots.png │ ├── test_hist2dplot_custom_labels.png │ ├── test_yscale_mpl_magic_add_text.png │ ├── 1d_comparison_relative_difference.png │ ├── model_examples_pull_no_model_unc.png │ ├── model_examples_stacked_unstacked.png │ ├── test_append_text_alignment_large.png │ ├── test_exp_label_scilocator_adjust.png │ ├── test_histplot_hist_flow_variances.png │ ├── test_histplot_w2_poisson_handling.png │ ├── test_yscale_legend_comprehensive.png │ ├── model_all_comparisons_no_model_unc.png │ ├── test_append_text_alignment_x-small.png │ ├── test_append_text_placement_default.png │ ├── test_hist2dplot_hist_all_flow_hint.png │ ├── test_hist2dplot_hist_all_flow_show.png │ ├── test_histplot_hist_flow_no_variances.png │ ├── test_append_text_placement_tex_gyre_heros.png │ ├── test_yscale_anchored_text_comprehensive.png │ ├── model_with_stacked_and_unstacked_function_components.png │ ├── model_with_stacked_and_unstacked_histograms_components.png │ └── ratio_data_vs_model_with_stacked_and_unstacked_function_components.png ├── from_issues │ └── baseline │ │ ├── test_hist_visual_fill.png │ │ ├── test_hist_visual_single.png │ │ └── test_hist_visual_multiple.png ├── copilot │ ├── baseline │ │ ├── test_hist2dplot_cbarpos_top_visual.png │ │ └── test_hist2dplot_cbarpos_bottom_visual.png │ └── test_hist2dplot_cbarpos.py ├── test_notebooks.py ├── test_layouts.py ├── conftest.py ├── test_examples_data_model.py ├── test_examples_1d_histograms.py ├── test_make_plottable_histogram.py ├── test_inputs.py ├── helpers.py ├── test_dune.py ├── test_styles_latex.py └── test_mock.py ├── src └── mplhep │ ├── _version.pyi │ ├── _tools.py │ ├── comp.py │ ├── styles │ ├── alice.py │ ├── plothist.py │ ├── cms.py │ ├── __init__.py │ └── dune.py │ ├── exp_alice.py │ ├── _compat.py │ ├── exp_cms.py │ ├── error_estimation.py │ ├── exp_dune.py │ ├── exp_lhcb.py │ ├── __init__.py │ └── exp_atlas.py ├── .bumpversion.cfg ├── .semantic.yml ├── .github ├── dependabot.yml ├── workflows │ ├── branch-sync.yml │ ├── cd.yml │ ├── ci-pages.yml │ ├── ci.yml │ └── ci_latex.yml ├── copilot-instructions.md └── copilot-setup-steps.yml ├── .readthedocs.yml ├── examples ├── 1d_histograms │ ├── 1d_comparison_only_efficiency.py │ ├── 1d_comparison_pull.py │ ├── 1d_comparison_ratio.py │ ├── 1d_comparison_asymmetry.py │ ├── 1d_comparison_split_ratio.py │ ├── 1d_comparison_efficiency.py │ ├── 1d_comparison_relative_difference.py │ └── 1d_comparison_difference.py └── model_ex │ ├── model_with_stacked_and_unstacked_function_components.py │ ├── model_examples_pull.py │ ├── model_with_stacked_and_unstacked_histograms_components.py │ ├── model_examples_unstacked.py │ ├── ratio_data_vs_model_with_stacked_and_unstacked_function_components.py │ ├── model_examples_pull_no_model_unc.py │ ├── model_examples_stacked_unstacked.py │ ├── model_examples_stacked.py │ ├── model_all_comparisons.py │ └── model_all_comparisons_no_model_unc.py ├── LICENSE ├── CITATION.cff ├── .pre-commit-config.yaml ├── .gitignore ├── .tools └── git-cleanup.sh ├── CONTRIBUTING.md └── noxfile.py /new_docs/docs/baseline: -------------------------------------------------------------------------------- 1 | ../../tests/baseline -------------------------------------------------------------------------------- /new_docs/docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /new_docs/docs/CONTRIBUTING_DOC.md: -------------------------------------------------------------------------------- 1 | ../CONTRIBUTING_DOC.md -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | python -m pip install --upgrade .[complete] 2 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | python -m pip install --upgrade --editable ".[all]" 2 | -------------------------------------------------------------------------------- /img/style0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/style0.png -------------------------------------------------------------------------------- /img/style1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/style1.png -------------------------------------------------------------------------------- /img/style2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/style2.png -------------------------------------------------------------------------------- /img/Example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/Example1.png -------------------------------------------------------------------------------- /img/Example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/Example2.png -------------------------------------------------------------------------------- /img/FontComp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/img/FontComp.png -------------------------------------------------------------------------------- /new_docs/docs/.htaccess: -------------------------------------------------------------------------------- 1 | # Allow all access 2 | Order allow,deny 3 | Allow from all 4 | -------------------------------------------------------------------------------- /docs/source/guide.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Guide 3 | ===== 4 | 5 | To be filled from example notebooks. 6 | -------------------------------------------------------------------------------- /new_docs/docs/mplhep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/new_docs/docs/mplhep.png -------------------------------------------------------------------------------- /tests/baseline/test_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_log.png -------------------------------------------------------------------------------- /docs/source/_static/mplhep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/docs/source/_static/mplhep.png -------------------------------------------------------------------------------- /src/mplhep/_version.pyi: -------------------------------------------------------------------------------- 1 | version: str 2 | version_tuple: tuple[int, int, int] | tuple[int, int, int, str, str] 3 | -------------------------------------------------------------------------------- /tests/baseline/test_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_simple.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot.png -------------------------------------------------------------------------------- /tests/baseline/test_pub_loc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_pub_loc.png -------------------------------------------------------------------------------- /tests/baseline/test_simple2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_simple2d.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_w2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_w2.png -------------------------------------------------------------------------------- /tests/baseline/test_inputs_bh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_inputs_bh.png -------------------------------------------------------------------------------- /tests/baseline/test_label_loc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_label_loc.png -------------------------------------------------------------------------------- /tests/baseline/test_log_mpl39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_log_mpl39.png -------------------------------------------------------------------------------- /tests/baseline/test_onebin_hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_onebin_hist.png -------------------------------------------------------------------------------- /tests/baseline/test_simple_xerr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_simple_xerr.png -------------------------------------------------------------------------------- /tests/baseline/test_square_cbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_square_cbar.png -------------------------------------------------------------------------------- /tests/baseline/test_style_alice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_alice.png -------------------------------------------------------------------------------- /tests/baseline/test_style_atlas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_atlas.png -------------------------------------------------------------------------------- /tests/baseline/test_style_cms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_cms.png -------------------------------------------------------------------------------- /tests/baseline/test_style_dune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_dune.png -------------------------------------------------------------------------------- /tests/baseline/test_style_dune1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_dune1.png -------------------------------------------------------------------------------- /tests/baseline/test_style_lhcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_lhcb.png -------------------------------------------------------------------------------- /tests/baseline/test_style_lhcb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_lhcb2.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_pull.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_bar.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_flow.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_real.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_real.png -------------------------------------------------------------------------------- /tests/baseline/test_inputs_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_inputs_basic.png -------------------------------------------------------------------------------- /tests/baseline/test_inputs_bh_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_inputs_bh_cat.png -------------------------------------------------------------------------------- /tests/baseline/test_inputs_uproot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_inputs_uproot.png -------------------------------------------------------------------------------- /tests/baseline/test_labeltext_loc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_labeltext_loc.png -------------------------------------------------------------------------------- /tests/baseline/test_style_cmstex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_cmstex.png -------------------------------------------------------------------------------- /tests/baseline/test_style_dunetex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_dunetex.png -------------------------------------------------------------------------------- /tests/baseline/test_style_roottex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_roottex.png -------------------------------------------------------------------------------- /tests/baseline/test_yscale_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_yscale_legend.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_ratio.png -------------------------------------------------------------------------------- /tests/baseline/model_all_comparisons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_all_comparisons.png -------------------------------------------------------------------------------- /tests/baseline/model_examples_pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_examples_pull.png -------------------------------------------------------------------------------- /tests/baseline/test_dune_label_loc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_dune_label_loc.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_flow.png -------------------------------------------------------------------------------- /tests/baseline/test_hist_visual_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist_visual_fill.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_density.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_density.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_kwargs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_kwargs.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_stack.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_types.png -------------------------------------------------------------------------------- /tests/baseline/test_style_atlastex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_atlastex.png -------------------------------------------------------------------------------- /tests/baseline/test_style_dunetex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_dunetex1.png -------------------------------------------------------------------------------- /tests/baseline/test_style_lhcbtex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_lhcbtex1.png -------------------------------------------------------------------------------- /tests/baseline/test_style_lhcbtex2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_lhcbtex2.png -------------------------------------------------------------------------------- /tests/baseline/test_style_plothist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_plothist.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_asymmetry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_asymmetry.png -------------------------------------------------------------------------------- /tests/baseline/model_examples_stacked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_examples_stacked.png -------------------------------------------------------------------------------- /tests/baseline/test_add_text_placement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_add_text_placement.png -------------------------------------------------------------------------------- /tests/baseline/test_hist_visual_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist_visual_single.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_multiple.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_None.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_None.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_type_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_type_flow.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_w2_scipy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_w2_scipy.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_xoffsets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_xoffsets.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_difference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_difference.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_efficiency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_efficiency.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_split_ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_split_ratio.png -------------------------------------------------------------------------------- /tests/baseline/model_examples_unstacked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_examples_unstacked.png -------------------------------------------------------------------------------- /tests/baseline/test_append_text_multiline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_append_text_multiline.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_cbar_False.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_cbar_False.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_cbar_True.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_cbar_True.png -------------------------------------------------------------------------------- /tests/baseline/test_hist_visual_multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist_visual_multiple.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_label.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_label_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_label_r.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_sort5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_sort5.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_yield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_yield.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_sort_yield_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_sort_yield_r.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_uproot_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_uproot_flow.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_w2_methods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_w2_methods.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_w2_noscipy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_w2_noscipy.png -------------------------------------------------------------------------------- /tests/baseline/test_yscale_anchored_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_yscale_anchored_text.png -------------------------------------------------------------------------------- /tests/baseline/test_add_text_placement_any.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_add_text_placement_any.png -------------------------------------------------------------------------------- /tests/baseline/test_add_text_placement_asym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_add_text_placement_asym.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_inputs_nobin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_inputs_nobin.png -------------------------------------------------------------------------------- /tests/baseline/test_style_dune_logo_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_style_dune_logo_colors.png -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_only_efficiency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_only_efficiency.png -------------------------------------------------------------------------------- /tests/baseline/test_exp_text_scilocator_adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_exp_text_scilocator_adjust.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_cbar_subplots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_cbar_subplots.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_custom_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_custom_labels.png -------------------------------------------------------------------------------- /tests/baseline/test_yscale_mpl_magic_add_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_yscale_mpl_magic_add_text.png -------------------------------------------------------------------------------- /docs/source/gallery/index.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Gallery 3 | =========== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | styles 9 | labels 10 | -------------------------------------------------------------------------------- /tests/baseline/1d_comparison_relative_difference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/1d_comparison_relative_difference.png -------------------------------------------------------------------------------- /tests/baseline/model_examples_pull_no_model_unc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_examples_pull_no_model_unc.png -------------------------------------------------------------------------------- /tests/baseline/model_examples_stacked_unstacked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_examples_stacked_unstacked.png -------------------------------------------------------------------------------- /tests/baseline/test_append_text_alignment_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_append_text_alignment_large.png -------------------------------------------------------------------------------- /tests/baseline/test_exp_label_scilocator_adjust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_exp_label_scilocator_adjust.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_hist_flow_variances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_hist_flow_variances.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_w2_poisson_handling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_w2_poisson_handling.png -------------------------------------------------------------------------------- /tests/baseline/test_yscale_legend_comprehensive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_yscale_legend_comprehensive.png -------------------------------------------------------------------------------- /tests/from_issues/baseline/test_hist_visual_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/from_issues/baseline/test_hist_visual_fill.png -------------------------------------------------------------------------------- /tests/baseline/model_all_comparisons_no_model_unc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_all_comparisons_no_model_unc.png -------------------------------------------------------------------------------- /tests/baseline/test_append_text_alignment_x-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_append_text_alignment_x-small.png -------------------------------------------------------------------------------- /tests/baseline/test_append_text_placement_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_append_text_placement_default.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_hist_all_flow_hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_hist_all_flow_hint.png -------------------------------------------------------------------------------- /tests/baseline/test_hist2dplot_hist_all_flow_show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_hist2dplot_hist_all_flow_show.png -------------------------------------------------------------------------------- /tests/baseline/test_histplot_hist_flow_no_variances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_histplot_hist_flow_no_variances.png -------------------------------------------------------------------------------- /tests/from_issues/baseline/test_hist_visual_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/from_issues/baseline/test_hist_visual_single.png -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.3.15 3 | commit = True 4 | tag = True 5 | allow_dirty = True 6 | 7 | [bumpversion:file:src/mplhep/.VERSION] 8 | -------------------------------------------------------------------------------- /tests/from_issues/baseline/test_hist_visual_multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/from_issues/baseline/test_hist_visual_multiple.png -------------------------------------------------------------------------------- /tests/baseline/test_append_text_placement_tex_gyre_heros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_append_text_placement_tex_gyre_heros.png -------------------------------------------------------------------------------- /tests/baseline/test_yscale_anchored_text_comprehensive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/test_yscale_anchored_text_comprehensive.png -------------------------------------------------------------------------------- /tests/copilot/baseline/test_hist2dplot_cbarpos_top_visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/copilot/baseline/test_hist2dplot_cbarpos_top_visual.png -------------------------------------------------------------------------------- /new_docs/requirements.txt: -------------------------------------------------------------------------------- 1 | docstring-inheritance 2 | griffe-inherited-docstrings 3 | markdown-exec 4 | mkdocs-matplotlib @ git+https://github.com/andrzejnovak/mkdocs-matplotlib.git 5 | -------------------------------------------------------------------------------- /tests/copilot/baseline/test_hist2dplot_cbarpos_bottom_visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/copilot/baseline/test_hist2dplot_cbarpos_bottom_visual.png -------------------------------------------------------------------------------- /tests/baseline/model_with_stacked_and_unstacked_function_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_with_stacked_and_unstacked_function_components.png -------------------------------------------------------------------------------- /tests/baseline/model_with_stacked_and_unstacked_histograms_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/model_with_stacked_and_unstacked_histograms_components.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter-sphinx 2 | matplotlib>=3.2 3 | mplhep_data 4 | numpy>=1.16.0 5 | packaging 6 | pydata_sphinx_theme 7 | pyyaml 8 | sphinx 9 | sphinxcontrib-images 10 | uhi>=0.2.0 11 | -------------------------------------------------------------------------------- /tests/baseline/ratio_data_vs_model_with_stacked_and_unstacked_function_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-hep/mplhep/HEAD/tests/baseline/ratio_data_vs_model_with_stacked_and_unstacked_function_components.png -------------------------------------------------------------------------------- /.semantic.yml: -------------------------------------------------------------------------------- 1 | types: 2 | - feat 3 | - fix 4 | - docs 5 | - style 6 | - refactor 7 | - deprecate 8 | - perf 9 | - test 10 | - build 11 | - ci 12 | - chore 13 | - revert 14 | - pre-commit.ci 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | groups: 9 | actions: 10 | patterns: 11 | - "*" 12 | -------------------------------------------------------------------------------- /new_docs/docs/_static/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [['$', '$'], ['\\(', '\\)']], 4 | displayMath: [['$$', '$$'], ['\\[', '\\]']], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: 'tex2jax_ignore', 10 | processHtmlClass: 'tex2jax_process' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/mplhep/_tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | 5 | 6 | class Config(argparse.Namespace): 7 | def clear(self): 8 | for key, value in dict(self._get_kwargs()).items(): 9 | if isinstance(value, Config): 10 | value.clear() 11 | else: 12 | setattr(self, key, None) 13 | -------------------------------------------------------------------------------- /tests/test_notebooks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | 6 | # import papermill as pm 7 | import pytest 8 | 9 | os.environ["RUNNING_PYTEST"] = "true" 10 | 11 | 12 | @pytest.fixture 13 | def common_kwargs(tmpdir): 14 | outputnb = tmpdir.join("output.ipynb") 15 | return { 16 | "output_path": str(outputnb), 17 | "kernel_name": f"python{sys.version_info.major}", 18 | } 19 | 20 | 21 | # def test_examples(common_kwargs): 22 | # pm.execute_notebook("examples/Examples.ipynb", **common_kwargs) 23 | -------------------------------------------------------------------------------- /.github/workflows/branch-sync.yml: -------------------------------------------------------------------------------- 1 | name: sync branches to main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | sync-branch: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Checkout main 14 | uses: actions/checkout@v6 15 | 16 | - name: Push updates to master 17 | uses: ad-m/github-push-action@master 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | repository: scikit-hep/mplhep 21 | branch: master 22 | force: true 23 | -------------------------------------------------------------------------------- /tests/test_layouts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | import matplotlib.pyplot as plt 6 | import pytest 7 | 8 | os.environ["RUNNING_PYTEST"] = "true" 9 | 10 | import mplhep as mh 11 | 12 | """ 13 | To test run: 14 | python pytest -r sa --mpl --mpl-results-path=pytest_results 15 | 16 | When adding new tests, run: 17 | python pytest -r sa --mpl --mpl-generate-path=tests/baseline 18 | """ 19 | 20 | plt.switch_backend("Agg") 21 | 22 | 23 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 24 | def test_square_cbar(): 25 | fig, ax = plt.subplots() 26 | ax = mh.make_square_add_cbar(ax=ax) 27 | return fig 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = mplhep 8 | SOURCEDIR = source 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_examples_pull.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model Examples Pull 3 | --- 4 | 5 | ![Model Examples Pull](../baseline/model_examples_pull.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_examples_pull.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_examples_pull.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_examples_pull.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_examples_pull.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_pull.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Pull 3 | --- 4 | 5 | ![1D Comparison Pull](../baseline/1d_comparison_pull.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_pull.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_pull.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_pull.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_pull.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_all_comparisons.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model All Comparisons 3 | --- 4 | 5 | ![Model All Comparisons](../baseline/model_all_comparisons.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_all_comparisons.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_all_comparisons.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_all_comparisons.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_all_comparisons.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_examples_stacked.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model Examples Stacked 3 | --- 4 | 5 | ![Model Examples Stacked](../baseline/model_examples_stacked.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_examples_stacked.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_examples_stacked.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_examples_stacked.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_examples_stacked.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_examples_unstacked.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model Examples Unstacked 3 | --- 4 | 5 | ![Model Examples Unstacked](../baseline/model_examples_unstacked.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_examples_unstacked.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_examples_unstacked.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_examples_unstacked.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_examples_unstacked.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/svg_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append(".") # Ensure resize_svg can be imported from the same directory 4 | from io import StringIO 5 | 6 | import resize_svg 7 | 8 | 9 | def save_figure_as_resized_svg(fig, width_percentage=50): 10 | """ 11 | Save a matplotlib figure as SVG, resize it to a percentage width, and return the result. 12 | 13 | Args: 14 | fig: The matplotlib Figure object 15 | width_percentage (float): The desired width as a percentage (default 50) 16 | 17 | Returns: 18 | str: The resized SVG content 19 | """ 20 | buffer = StringIO() 21 | fig.savefig(buffer, format="svg", dpi=300, bbox_inches="tight", pad_inches=0.2) 22 | return resize_svg.resize_svg_to_percentage(buffer.getvalue(), width_percentage) 23 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_asymmetry.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Asymmetry 3 | --- 4 | 5 | ![1D Comparison Asymmetry](../baseline/1d_comparison_asymmetry.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_asymmetry.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_asymmetry.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_asymmetry.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_asymmetry.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_difference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Difference 3 | --- 4 | 5 | ![1D Comparison Difference](../baseline/1d_comparison_difference.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_difference.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_difference.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_difference.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_difference.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_efficiency.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Efficiency 3 | --- 4 | 5 | ![1D Comparison Efficiency](../baseline/1d_comparison_efficiency.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_efficiency.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_efficiency.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_efficiency.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_efficiency.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_split_ratio.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Split Ratio 3 | --- 4 | 5 | ![1D Comparison Split Ratio](../baseline/1d_comparison_split_ratio.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_split_ratio.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_split_ratio.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_split_ratio.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_split_ratio.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_only_efficiency.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Only Efficiency 3 | --- 4 | 5 | ![1D Comparison Efficiency](../baseline/1d_comparison_only_efficiency.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_only_efficiency.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_only_efficiency.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_only_efficiency.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_only_efficiency.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | release: 10 | types: 11 | - published 12 | 13 | jobs: 14 | dist: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: hynek/build-and-inspect-python-package@v2 22 | 23 | publish: 24 | needs: [dist] 25 | environment: release 26 | permissions: 27 | id-token: write 28 | runs-on: ubuntu-latest 29 | if: github.event_name == 'release' && github.event.action == 'published' 30 | steps: 31 | - uses: actions/download-artifact@v6 32 | with: 33 | name: Packages 34 | path: dist 35 | 36 | - uses: pypa/gh-action-pypi-publish@release/v1 37 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_examples_stacked_unstacked.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model Examples Stacked Unstacked 3 | --- 4 | 5 | ![Model Examples Stacked Unstacked](../baseline/model_examples_stacked_unstacked.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_examples_stacked_unstacked.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_examples_stacked_unstacked.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_examples_stacked_unstacked.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_examples_stacked_unstacked.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_ratio.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Ratio 3 | --- 4 | 5 | This example shows how to plot the comparison between two 1D histograms using the ratio. 6 | 7 | ![1D Comparison Ratio](../baseline/1d_comparison_ratio.png) 8 | 9 | ??? imports "Imports" 10 | ```python 11 | --8<-- 12 | examples/1d_histograms/1d_comparison_ratio.py:imports 13 | --8<-- 14 | ``` 15 | 16 | ??? setup "Setup" 17 | ```python 18 | --8<-- 19 | examples/1d_histograms/1d_comparison_ratio.py:setup 20 | --8<-- 21 | ``` 22 | 23 | !!! tip "Code" 24 | ```python 25 | --8<-- 26 | examples/1d_histograms/1d_comparison_ratio.py:plot_body 27 | --8<-- 28 | ``` 29 | 30 | ??? code "Full code" 31 | ```python 32 | --8<-- 33 | examples/1d_histograms/1d_comparison_ratio.py:full_code 34 | --8<-- 35 | ``` 36 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_examples_pull_no_model_unc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model Examples Pull No Model Uncertainty 3 | --- 4 | 5 | ![Model Examples Pull No Model Uncertainty](../baseline/model_examples_pull_no_model_unc.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_examples_pull_no_model_unc.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_examples_pull_no_model_unc.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_examples_pull_no_model_unc.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_examples_pull_no_model_unc.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/1d_comparison_relative_difference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1D Comparison Relative Difference 3 | --- 4 | 5 | ![1D Comparison Relative Difference](../baseline/1d_comparison_relative_difference.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/1d_histograms/1d_comparison_relative_difference.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/1d_histograms/1d_comparison_relative_difference.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/1d_histograms/1d_comparison_relative_difference.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/1d_histograms/1d_comparison_relative_difference.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_all_comparisons_no_model_unc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model All Comparisons No Model Uncertainty 3 | --- 4 | 5 | ![Model All Comparisons No Model Uncertainty](../baseline/model_all_comparisons_no_model_unc.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_all_comparisons_no_model_unc.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_all_comparisons_no_model_unc.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_all_comparisons_no_model_unc.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_all_comparisons_no_model_unc.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the version of Python and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | #sphinx: 15 | # configuration: docs/source/conf.py 16 | 17 | # Build documentation with MkDocs 18 | mkdocs: 19 | configuration: new_docs/mkdocs.yml 20 | 21 | # Optionally build your docs in additional formats such as PDF and ePub 22 | #formats: all 23 | 24 | # Optionally set the version of Python and requirements required to build your docs 25 | python: 26 | install: 27 | - method: pip 28 | path: . 29 | extra_requirements: 30 | - all 31 | - requirements: new_docs/requirements.txt 32 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_with_stacked_and_unstacked_function_components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model With Stacked And Unstacked Function Components 3 | --- 4 | 5 | ![Model With Stacked And Unstacked Function Components](../baseline/model_with_stacked_and_unstacked_function_components.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_with_stacked_and_unstacked_function_components.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_with_stacked_and_unstacked_function_components.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_with_stacked_and_unstacked_function_components.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_with_stacked_and_unstacked_function_components.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/model_with_stacked_and_unstacked_histograms_components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Model With Stacked And Unstacked Histograms Components 3 | --- 4 | 5 | ![Model With Stacked And Unstacked Histograms Components](../baseline/model_with_stacked_and_unstacked_histograms_components.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_only_efficiency.py: -------------------------------------------------------------------------------- 1 | """ 2 | Comparison 3 | ========== 4 | 5 | Plot the comparison between two 1D histograms. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import matplotlib.pyplot as plt 12 | import numpy as np 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Generate dummy data - sample is subset of total 21 | x_total = np.random.normal(0.4, 0.1, 10000) 22 | x_sample = x_total[:7500] # 75% subset 23 | 24 | # Create and fill histograms 25 | h_sample = hist.new.Regular(50, 0, 1).Weight().fill(x_sample) 26 | h_total = hist.new.Regular(50, 0, 1).Weight().fill(x_total) 27 | # --8<-- [end:setup] 28 | 29 | # --8<-- [start:plot_body] 30 | fig, ax = plt.subplots() 31 | mh.comp.comparison(h_sample, h_total, ax=ax, xlabel="Variable", comparison="efficiency") 32 | 33 | # --8<-- [end:plot_body] 34 | # --8<-- [end:full_code] 35 | fig.savefig("1d_comparison_only_efficiency.svg", bbox_inches="tight") 36 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_pull.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pull 3 | ==== 4 | 5 | Compare two 1D histograms using the pull method. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h1, 31 | h2, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_1$", 35 | h2_label=r"$h_2$", 36 | comparison="pull", # <--- 37 | ) 38 | 39 | # --8<-- [end:plot_body] 40 | # --8<-- [end:full_code] 41 | fig.savefig("1d_comparison_pull.png", bbox_inches="tight") 42 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_ratio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ratio 3 | ===== 4 | 5 | Compare two 1D histograms using the ratio [h1/h2]. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h1, 31 | h2, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_1$", 35 | h2_label=r"$h_2$", 36 | comparison="ratio", # <-- 37 | ) 38 | 39 | # --8<-- [end:plot_body] 40 | # --8<-- [end:full_code] 41 | fig.savefig("1d_comparison_ratio.png", bbox_inches="tight") 42 | -------------------------------------------------------------------------------- /new_docs/docs/gallery/ratio_data_vs_model_with_stacked_and_unstacked_function_components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ratio Data Vs Model With Stacked And Unstacked Function Components 3 | --- 4 | 5 | ![Ratio Data Vs Model With Stacked And Unstacked Function Components](../baseline/ratio_data_vs_model_with_stacked_and_unstacked_function_components.png) 6 | 7 | ??? imports "Imports" 8 | ```python 9 | --8<-- 10 | examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py:imports 11 | --8<-- 12 | ``` 13 | 14 | ??? setup "Setup" 15 | ```python 16 | --8<-- 17 | examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py:setup 18 | --8<-- 19 | ``` 20 | 21 | !!! tip "Code" 22 | ```python 23 | --8<-- 24 | examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py:plot_body 25 | --8<-- 26 | ``` 27 | 28 | ??? code "Full code" 29 | ```python 30 | --8<-- 31 | examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py:full_code 32 | --8<-- 33 | ``` 34 | -------------------------------------------------------------------------------- /src/mplhep/comp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Comparison/compound plotting module. 3 | 4 | This module provides functions to create comparison plots, such as ratio plots, 5 | data vs. model comparisons, and more. 6 | """ 7 | 8 | from .comparison_plotters import ( 9 | comparison, 10 | data_model, 11 | hists, 12 | ) 13 | 14 | # Alias functions for plothist consistency 15 | plot_comparison = comparison 16 | plot_data_model_comparison = data_model 17 | plot_two_hist_comparison = hists 18 | 19 | from .comparison_functions import ( 20 | get_asymmetry, 21 | get_comparison, 22 | get_difference, 23 | get_efficiency, 24 | get_pull, 25 | get_ratio, 26 | ) 27 | 28 | # Alias the module name itself for discoverability 29 | compare = __import__(__name__) 30 | 31 | __all__ = [ 32 | "hists", 33 | "plot_two_hist_comparison", 34 | "comparison", 35 | "plot_comparison", 36 | "data_model", 37 | "plot_data_model_comparison", 38 | "compare", 39 | "get_ratio", 40 | "get_difference", 41 | "get_efficiency", 42 | "get_pull", 43 | "get_comparison", 44 | "get_asymmetry", 45 | ] 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrzej Novak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_asymmetry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Asymmetry 3 | ========= 4 | 5 | Compare two 1D histograms using the asymmetry comparison [(h1-h2)/(h1+h2)]. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h1, 31 | h2, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_1$", 35 | h2_label=r"$h_2$", 36 | comparison="asymmetry", # <-- 37 | ) 38 | # --8<-- [end:plot_body] 39 | # --8<-- [end:full_code] 40 | fig.savefig("1d_comparison_asymmetry.svg", bbox_inches="tight") 41 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_split_ratio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ratio, split errors 3 | =================== 4 | 5 | Compare two 1D histograms using the ratio [h1/h2] method and split the errors. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h1, 31 | h2, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_1$", 35 | h2_label=r"$h_2$", 36 | comparison="split_ratio", 37 | ) 38 | 39 | # --8<-- [end:plot_body] 40 | # --8<-- [end:full_code] 41 | fig.savefig("1d_comparison_split_ratio.svg", bbox_inches="tight") 42 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_efficiency.py: -------------------------------------------------------------------------------- 1 | """ 2 | Efficiency 3 | ========== 4 | 5 | Compare the ratio between two histograms h1 and h2 when the entries of h1 are a subset of the entries of h2. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data - sample is subset of total 20 | x_total = np.random.normal(0.4, 0.1, 10000) 21 | x_sample = x_total[:7500] # 75% subset 22 | 23 | # Create and fill histograms 24 | h_sample = hist.new.Regular(50, 0, 1).Weight().fill(x_sample) 25 | h_total = hist.new.Regular(50, 0, 1).Weight().fill(x_total) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h_sample, 31 | h_total, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_{Sample}$", 35 | h2_label=r"$h_{Total}$", 36 | comparison="efficiency", # <-- 37 | ) 38 | 39 | # --8<-- [end:plot_body] 40 | # --8<-- [end:full_code] 41 | fig.savefig("1d_comparison_efficiency.png", bbox_inches="tight") 42 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_relative_difference.py: -------------------------------------------------------------------------------- 1 | """ 2 | Relative difference 3 | =================== 4 | 5 | Compare two 1D histograms using the relative difference [(h1-h2)/h2]. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | # --8<-- [start:plot_body] 29 | fig, ax_main, ax_comparison = mh.comp.hists( 30 | h1, 31 | h2, 32 | xlabel="Variable", 33 | ylabel="Entries", 34 | h1_label=r"$h_1$", 35 | h2_label=r"$h_2$", 36 | comparison="relative_difference", # <-- 37 | ) 38 | 39 | # --8<-- [end:plot_body] 40 | # --8<-- [end:full_code] 41 | fig.savefig("1d_comparison_relative_difference.svg", bbox_inches="tight") 42 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. mplhep documentation master file, created by 2 | sphinx-quickstart on Sun Apr 12 14:08:38 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root ``toctree`` directive. 5 | 6 | 7 | ====== 8 | mplhep 9 | ====== 10 | 11 | A `matplotlib `__ wrapper for easy plotting required in 12 | high energy physics (HEP). Primarily "prebinned" 1D & 2D histograms and matplotlib 13 | `style-sheets `__ 14 | carrying recommended plotting styles of large LHC experiments - ATLAS, CMS & LHCb. 15 | This project is published `on GitHub `__. 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | :caption: Getting Started 20 | 21 | install 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | :caption: User Guide 26 | 27 | guide 28 | 29 | Work-in-progress 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | :caption: Gallery 34 | 35 | gallery/index 36 | 37 | .. toctree:: 38 | :maxdepth: 2 39 | :caption: Reference 40 | 41 | api 42 | 43 | Indices and tables 44 | ================== 45 | * :ref:`genindex` 46 | * :ref:`modindex` 47 | * :ref:`search` 48 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import subprocess 3 | 4 | import matplotlib.pyplot as plt 5 | import pytest 6 | 7 | 8 | def _has_latex(): 9 | """Check if LaTeX is available on the system.""" 10 | if not shutil.which("latex"): 11 | return False 12 | try: 13 | subprocess.run( 14 | ["latex", "--version"], 15 | check=True, 16 | capture_output=True, 17 | timeout=5, 18 | ) 19 | except ( 20 | subprocess.CalledProcessError, 21 | subprocess.TimeoutExpired, 22 | FileNotFoundError, 23 | ): 24 | return False 25 | return True 26 | 27 | 28 | def pytest_collection_modifyitems(config, items): # noqa: ARG001 29 | """Skip LaTeX tests if LaTeX is not installed.""" 30 | if _has_latex(): 31 | return 32 | 33 | skip_latex = pytest.mark.skip(reason="LaTeX not installed") 34 | for item in items: 35 | if "latex" in item.keywords: 36 | item.add_marker(skip_latex) 37 | 38 | 39 | @pytest.fixture(autouse=True) 40 | def clear_mplhep_rcparams(): 41 | """Clear matplotlib rcParams before and after each test.""" 42 | 43 | plt.rcParams.update(plt.rcParamsDefault) 44 | yield 45 | plt.rcParams.update(plt.rcParamsDefault) 46 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | doi: "10.5281/zenodo.3766157" 4 | date-released: "2020-04-25" 5 | authors: 6 | - family-names: "Novak" 7 | given-names: "Andrzej" 8 | affiliation: "Massachusetts Institute of Technology" 9 | orcid: "https://orcid.org/0000-0002-0389-5896" 10 | - family-names: "Schreiner" 11 | given-names: "Henry" 12 | affiliation: "Princeton University" 13 | orcid: "https://orcid.org/0000-0002-7833-783X" 14 | - family-names: "Feickert" 15 | given-names: "Matthew" 16 | affiliation: "University of Wisconsin-Madison" 17 | orcid: "https://orcid.org/0000-0003-4124-7862" 18 | - family-names: "Eschle" 19 | given-names: "Jonas" 20 | affiliation: "CERN" 21 | orcid: "https://orcid.org/0000-0002-7312-3699" 22 | - family-names: "Fillinger" 23 | given-names: "Tristan" 24 | affiliation: "KEK" 25 | orcid: "https://orcid.org/0000-0001-9795-7412" 26 | - family-names: "Praz" 27 | given-names: "Cyrille" 28 | affiliation: "HEPVS" 29 | orcid: "https://orcid.org/0000-0002-6154-885X" 30 | title: "mplhep" 31 | url: "https://github.com/scikit-hep/mplhep" 32 | keywords: 33 | - python 34 | - physics 35 | - matplotlib 36 | - visualization 37 | - plotting 38 | - scikit-hep 39 | license: "MIT" 40 | -------------------------------------------------------------------------------- /new_docs/docs/resize_svg.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def resize_svg_to_percentage(svg_content, width_percentage=50): 5 | """ 6 | Resize SVG to a percentage of its container width, maintaining aspect ratio. 7 | 8 | Args: 9 | svg_content (str): The SVG content as a string 10 | width_percentage (float): The desired width as a percentage (default 50) 11 | 12 | Returns: 13 | str: The resized SVG content 14 | """ 15 | width_match = re.search(r'width="([^"]*)"', svg_content) 16 | height_match = re.search(r'height="([^"]*)"', svg_content) 17 | if width_match and height_match: 18 | w_str = width_match.group(1) 19 | h_str = height_match.group(1) 20 | # Extract numeric values 21 | w_val = float(re.match(r"([\d.]+)", w_str).group(1)) 22 | h_val = float(re.match(r"([\d.]+)", h_str).group(1)) 23 | # Calculate aspect ratio 24 | ratio = h_val / w_val 25 | # Set width to percentage and height proportionally 26 | new_w = f"{width_percentage}%" 27 | new_h = f"{width_percentage * ratio}%" 28 | svg_content = svg_content.replace(f'width="{w_str}"', f'width="{new_w}"') 29 | svg_content = svg_content.replace(f'height="{h_str}"', f'height="{new_h}"') 30 | return svg_content 31 | -------------------------------------------------------------------------------- /examples/1d_histograms/1d_comparison_difference.py: -------------------------------------------------------------------------------- 1 | """ 2 | Difference 3 | ========== 4 | 5 | Compare two 1D histograms using the difference [h1-h2]. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | 13 | import mplhep as mh 14 | 15 | np.random.seed(42) 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Generate dummy data 20 | x1 = np.r_[np.random.normal(0.4, 0.1, 5000), np.random.normal(0.7, 0.1, 5000)] 21 | x2 = np.r_[np.random.normal(0.4, 0.1, 1000), np.random.normal(0.7, 0.11, 7000)] 22 | 23 | # Create and fill histograms 24 | h1 = hist.new.Regular(50, 0, 1).Weight().fill(x1) 25 | h2 = hist.new.Regular(50, 0, 1).Weight().fill(x2) 26 | # --8<-- [end:setup] 27 | 28 | 29 | # --8<-- [start:plot_body] 30 | fig, ax_main, ax_comparison = mh.comp.hists( 31 | h1, 32 | h2, 33 | xlabel="Variable", 34 | ylabel="Entries", 35 | h1_label=r"$h_1$", 36 | h2_label=r"$h_2$", 37 | comparison="difference", # <-- 38 | ) 39 | 40 | mh.add_text( 41 | "Comparison of two hist with difference plot", 42 | ax=ax_main, 43 | fontsize="small", 44 | loc="over left", 45 | ) 46 | mh.add_text("Difference ax", ax=ax_comparison, loc="over right", fontsize="small") 47 | 48 | # --8<-- [end:plot_body] 49 | # --8<-- [end:full_code] 50 | fig.savefig("1d_comparison_difference.png", bbox_inches="tight") 51 | -------------------------------------------------------------------------------- /new_docs/mkdocs_norender.yml: -------------------------------------------------------------------------------- 1 | # MkDocs configuration for fast development builds without figure rendering 2 | # This configuration inherits from mkdocs.yml but disables mkdocs_matplotlib plugin 3 | # Use: mkdocs serve -f mkdocs_norender.yml 4 | 5 | INHERIT: mkdocs.yml 6 | 7 | # Override plugins to exclude mkdocs_matplotlib 8 | plugins: 9 | - search 10 | - minify 11 | - git-revision-date-localized 12 | # mkdocs_matplotlib is DISABLED for fast builds 13 | - mkdocstrings: 14 | handlers: 15 | python: 16 | paths: [/home/anovak/software/mplhep/src] 17 | options: 18 | docstring_style: numpy 19 | docstring_section_style: table 20 | members_order: source 21 | show_signature: true 22 | separate_signature: true 23 | show_signature_annotations: true 24 | show_source: true 25 | show_overloads: false 26 | show_symbol_type_heading: true 27 | show_symbol_type_toc: true 28 | show_docstring_attributes: true 29 | show_docstring_functions: true 30 | show_docstring_modules: true 31 | show_docstring_examples: false 32 | show_object_full_path: true 33 | extensions: 34 | - griffe_inherited_docstrings 35 | - git-committers: 36 | repository: scikit-hep/mplhep 37 | branch: main 38 | -------------------------------------------------------------------------------- /src/mplhep/styles/alice.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import matplotlib as mpl 4 | 5 | ALICE = { 6 | "lines.linewidth": 1, 7 | "font.family": "sans-serif", 8 | "font.sans-serif": ["TeX Gyre Heros", "Helvetica", "Arial"], 9 | "font.size": 14, 10 | "mathtext.fontset": "stixsans", 11 | "mathtext.default": "rm", 12 | # figure layout 13 | "figure.figsize": (12.0, 9.0), 14 | "figure.facecolor": "white", 15 | "figure.subplot.bottom": 0.16, 16 | "figure.subplot.top": 0.93, 17 | "figure.subplot.left": 0.16, 18 | "figure.subplot.right": 0.95, 19 | # axes 20 | "axes.labelsize": 32, 21 | "axes.labelpad": 24, 22 | "xtick.top": True, 23 | "xtick.labelsize": 25, 24 | "xtick.major.size": 10, 25 | "xtick.minor.size": 5, 26 | "xtick.direction": "in", 27 | "xtick.minor.visible": True, 28 | "ytick.right": True, 29 | "ytick.labelsize": 25, 30 | "ytick.major.size": 14, 31 | "ytick.minor.size": 7, 32 | "ytick.direction": "in", 33 | "ytick.minor.visible": True, 34 | "lines.markersize": 8, 35 | # legend 36 | "legend.loc": "best", 37 | "legend.numpoints": 1, 38 | "legend.fontsize": 28, 39 | "legend.labelspacing": 0.3, 40 | "legend.frameon": False, 41 | "xaxis.labellocation": "right", 42 | "yaxis.labellocation": "top", 43 | } 44 | 45 | # Filter extra (labellocation) items if needed 46 | ALICE = {k: v for k, v in ALICE.items() if k in mpl.rcParams} 47 | -------------------------------------------------------------------------------- /examples/model_ex/model_with_stacked_and_unstacked_function_components.py: -------------------------------------------------------------------------------- 1 | """ 2 | Model with stacked and unstacked functional components 3 | ====================================================== 4 | 5 | Plot a model with stacked and unstacked functional components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | from scipy.stats import norm 11 | 12 | import mplhep as mh 13 | 14 | # --8<-- [end:imports] 15 | 16 | 17 | # --8<-- [start:setup] 18 | # Define model function components 19 | def f_signal(x): 20 | return 600 * norm.pdf(x, loc=0.2, scale=3) 21 | 22 | 23 | def f_background1(x): 24 | return 1000 * norm.pdf(x, loc=-1.5, scale=4) 25 | 26 | 27 | def f_background2(x): 28 | return 3000 * norm.pdf(x, loc=-3.2, scale=1.2) 29 | 30 | 31 | # --8<-- [end:setup] 32 | 33 | # --8<-- [start:plot_body] 34 | fig, ax = mh.model( 35 | stacked_components=[f_background1, f_background2], 36 | stacked_labels=["c0", "c1"], 37 | unstacked_components=[f_signal], 38 | unstacked_labels=["Signal"], 39 | unstacked_colors=["black"], 40 | xlabel="Observable", 41 | ylabel="f(Observable)", 42 | model_sum_kwargs={"show": True, "label": "Model", "color": "navy"}, 43 | function_range=(-9, 12), 44 | ) 45 | 46 | mh.add_text("Model made of functions", ax=ax, loc="over left", fontsize="small") 47 | # --8<-- [end:plot_body] 48 | 49 | # --8<-- [end:full_code] 50 | fig.savefig( 51 | "model_with_stacked_and_unstacked_function_components.svg", bbox_inches="tight" 52 | ) 53 | -------------------------------------------------------------------------------- /tests/test_examples_data_model.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | 4 | import matplotlib.pyplot as plt 5 | import pytest 6 | 7 | # Add the tests directory to sys.path to find helpers 8 | tests_dir = Path(__file__).parent 9 | if str(tests_dir) not in sys.path: 10 | sys.path.insert(0, str(tests_dir)) 11 | 12 | from helpers import run_script_and_get_object 13 | 14 | import mplhep 15 | 16 | mpl_image_compare_kwargs = { 17 | "baseline_dir": "baseline", 18 | "savefig_kwargs": {"bbox_inches": "tight"}, 19 | "style": mplhep.style.plothist, 20 | "deterministic": True, 21 | } 22 | 23 | script_dir = Path(mplhep.__file__).parent / ".." / ".." / "examples" / "model_ex" 24 | 25 | current_module = sys.modules[__name__] 26 | 27 | 28 | @pytest.fixture(autouse=True) 29 | def close_all_figures(): 30 | """Automatically close all figures after each test.""" 31 | yield 32 | plt.close("all") 33 | 34 | 35 | for script_path in script_dir.glob("*.py"): 36 | filename = f"{script_path.stem}.png" 37 | test_name = f"test_{script_path.stem}" 38 | 39 | def make_test(script_path=script_path): 40 | @pytest.mark.mpl_image_compare( 41 | filename=f"{script_path.stem}.png", **mpl_image_compare_kwargs 42 | ) 43 | def func_test(): 44 | return run_script_and_get_object(script_path, "fig") 45 | 46 | return func_test 47 | 48 | test_func = make_test() 49 | test_func.__name__ = test_name 50 | setattr(current_module, test_name, test_func) 51 | -------------------------------------------------------------------------------- /tests/test_examples_1d_histograms.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | 4 | import matplotlib.pyplot as plt 5 | import pytest 6 | 7 | # Add the tests directory to sys.path to find helpers 8 | tests_dir = Path(__file__).parent 9 | if str(tests_dir) not in sys.path: 10 | sys.path.insert(0, str(tests_dir)) 11 | 12 | from helpers import run_script_and_get_object 13 | 14 | import mplhep 15 | 16 | mpl_image_compare_kwargs = { 17 | "baseline_dir": "baseline", 18 | "savefig_kwargs": {"bbox_inches": "tight"}, 19 | "style": mplhep.style.plothist, 20 | "deterministic": True, 21 | } 22 | 23 | 24 | script_dir = Path(mplhep.__file__).parent / ".." / ".." / "examples" / "1d_histograms" 25 | 26 | current_module = sys.modules[__name__] 27 | 28 | 29 | @pytest.fixture(autouse=True) 30 | def close_all_figures(): 31 | """Automatically close all figures after each test.""" 32 | yield 33 | plt.close("all") 34 | 35 | 36 | for script_path in script_dir.glob("*.py"): 37 | filename = f"{script_path.stem}.png" 38 | test_name = f"test_{script_path.stem}" 39 | 40 | def make_test(script_path=script_path): 41 | @pytest.mark.mpl_image_compare( 42 | filename=f"{script_path.stem}.png", **mpl_image_compare_kwargs 43 | ) 44 | def func_test(): 45 | return run_script_and_get_object(script_path, "fig") 46 | 47 | return func_test 48 | 49 | test_func = make_test() 50 | test_func.__name__ = test_name 51 | setattr(current_module, test_name, test_func) 52 | -------------------------------------------------------------------------------- /new_docs/docs/stylesheets/neoteroi-spantable.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Extra CSS file recommended for MkDocs and neoteroi.spantable extension. 3 | * 4 | * https://github.com/Neoteroi/mkdocs-plugins 5 | **/ 6 | .span-table-wrapper table { 7 | border-collapse: collapse; 8 | margin-bottom: 2rem; 9 | border-radius: 0.1rem; 10 | } 11 | 12 | .span-table td, 13 | .span-table th { 14 | padding: 0.2rem; 15 | background-color: var(--md-default-bg-color); 16 | font-size: 0.64rem; 17 | max-width: 100%; 18 | overflow: auto; 19 | touch-action: auto; 20 | border-top: 0.05rem solid var(--md-typeset-table-color); 21 | padding: 0.9375em 1.25em; 22 | vertical-align: top; 23 | } 24 | 25 | .span-table tr:first-child td { 26 | font-weight: 700; 27 | min-width: 5rem; 28 | padding: 0.9375em 1.25em; 29 | vertical-align: top; 30 | } 31 | 32 | .span-table td:first-child { 33 | border-left: 0.05rem solid var(--md-typeset-table-color); 34 | } 35 | 36 | .span-table td:last-child { 37 | border-right: 0.05rem solid var(--md-typeset-table-color); 38 | } 39 | 40 | .span-table tr:last-child { 41 | border-bottom: 0.05rem solid var(--md-typeset-table-color); 42 | } 43 | 44 | .span-table [colspan], 45 | .span-table [rowspan] { 46 | font-weight: bold; 47 | border: 0.05rem solid var(--md-typeset-table-color); 48 | } 49 | 50 | .span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]), 51 | .span-table td[colspan]:hover, 52 | .span-table td[rowspan]:hover { 53 | background-color: rgba(0, 0, 0, 0.035); 54 | box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset; 55 | transition: background-color 125ms; 56 | } 57 | -------------------------------------------------------------------------------- /new_docs/docs_hooks.py: -------------------------------------------------------------------------------- 1 | """MkDocs hooks for documentation generation. 2 | 3 | This module provides hooks for MkDocs to run before building the documentation. 4 | """ 5 | 6 | import contextlib 7 | import subprocess 8 | import sys 9 | from pathlib import Path 10 | 11 | 12 | def on_pre_build(config): # noqa: ARG001 13 | """Run before the build process starts. 14 | 15 | This hook runs the generation scripts to create style-specific 16 | guide files and the guide overview from templates. 17 | 18 | Parameters 19 | ---------- 20 | config : MkDocsConfig 21 | The MkDocs configuration object (unused) 22 | """ 23 | docs_root = Path(__file__).parent 24 | 25 | # Run the style guides generation script 26 | style_guides_script = docs_root / "generate_style_guides.py" 27 | if style_guides_script.exists(): 28 | with contextlib.suppress(subprocess.CalledProcessError): 29 | subprocess.run( 30 | [sys.executable, str(style_guides_script)], 31 | cwd=docs_root, 32 | capture_output=True, 33 | text=True, 34 | check=True, 35 | ) 36 | 37 | # Run the guide overview generation script 38 | guide_overview_script = docs_root / "generate_guide_overview.py" 39 | if guide_overview_script.exists(): 40 | with contextlib.suppress(subprocess.CalledProcessError): 41 | subprocess.run( 42 | [sys.executable, str(guide_overview_script)], 43 | cwd=docs_root, 44 | capture_output=True, 45 | text=True, 46 | check=True, 47 | ) 48 | -------------------------------------------------------------------------------- /examples/model_ex/model_examples_pull.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pull plot 3 | ========= 4 | 5 | Compare data and model with pulls. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 2000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 500), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | # --8<-- [end:setup] 44 | 45 | # --8<-- [start:plot_body] 46 | fig, ax_main, ax_comparison = mh.comp.data_model( 47 | data_hist=data_hist, 48 | stacked_components=background_hists, 49 | stacked_labels=["c0", "c1", "c2"], 50 | stacked_colors=sns.color_palette("cubehelix", 3), 51 | xlabel=r"$m_{\ell\ell}\,\,[TeV/c^2]$", 52 | ylabel="Candidates per 0.42 $TeV/c^2$", 53 | comparison="pull", 54 | ) 55 | # --8<-- [end:plot_body] 56 | 57 | # --8<-- [end:full_code] 58 | fig.savefig("model_examples_pull.svg", bbox_inches="tight") 59 | -------------------------------------------------------------------------------- /examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py: -------------------------------------------------------------------------------- 1 | """ 2 | Model with stacked and unstacked components 3 | =========================================== 4 | 5 | Plot a model with stacked and unstacked components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create background histograms 21 | background_hists = [ 22 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 23 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 24 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 25 | ] 26 | 27 | # Create signal histogram 28 | signal_hist = hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 0.5, 500)) 29 | # --8<-- [end:setup] 30 | 31 | # --8<-- [start:plot_body] 32 | fig, ax = mh.model( 33 | stacked_components=background_hists, 34 | stacked_labels=["c0", "c1", "c2"], 35 | stacked_colors=sns.color_palette("cubehelix", 3), 36 | unstacked_components=[signal_hist], 37 | unstacked_labels=["Signal"], 38 | unstacked_colors=["black"], 39 | unstacked_kwargs_list=[{"linestyle": "dotted"}], 40 | xlabel="Observable", 41 | ylabel="Entries", 42 | model_sum_kwargs={"show": True, "label": "Model", "color": "navy"}, 43 | model_uncertainty_label="Stat. unc.", 44 | ) 45 | 46 | mh.add_text("Model made of histograms", ax=ax, loc="over left") 47 | # --8<-- [end:plot_body] 48 | 49 | # --8<-- [end:full_code] 50 | fig.savefig( 51 | "model_with_stacked_and_unstacked_histograms_components.svg", 52 | bbox_inches="tight", 53 | ) 54 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | ## General Copilot Instructions 2 | 3 | - Commit messages and PR names should follow Conventional Commits specification (https://www.conventionalcommits.org/en/v1.0.0/) 4 | - Use present tense ("add feature" not "added feature") 5 | 6 | ## Folder Structure 7 | 8 | - `/src/mplhep`: Contains the source code for project. 9 | - `/tests`: Contains tests. 10 | - `/docs`: Contains documentation for the project, including API specifications and user guides. 11 | - `/examples`: Is a joint folder for examples which are parts of both documentation and tests. 12 | 13 | ## Coding Standards 14 | 15 | - Use single quotes for strings. Such that printed statements containing strings can use double quotes. 16 | - Follow PEP 8 guidelines for Python code. 17 | - Write docstrings for all public functions and classes using the NumPy/SciPy style. 18 | - Follow ruff configuration in pyproject.toml for linting and formatting 19 | - Use type hints where appropriate (mypy configuration available) 20 | - Run pre-commit hooks before committing 21 | 22 | 23 | ## Testing Guidelines 24 | 25 | - For new features, add tests to the `tests/copilot` directory 26 | - When resolving issues, write tests into `tests/from_issues` directory 27 | - Use pytest-mpl for matplotlib-based tests that require visual comparison (most cases) 28 | - If you think it's useful, you can run the test suite with `python -m pytest -r sa --mpl --mpl-results-path=pytest_results -n 4` before submitting changes 29 | - Intentionally changed behaviours might require a new baseline generated as `pytest --mpl-generate-path=tests/baseline` if visual outputs change 30 | 31 | 32 | ## Project-Specific Guidelines 33 | 34 | - Keep backward compatibility in mind for public APIs 35 | - Consider optional dependencies vs core requirements carefully 36 | -------------------------------------------------------------------------------- /src/mplhep/exp_alice.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import inspect 4 | 5 | import mplhep 6 | 7 | from . import label as label_base 8 | from ._compat import copy_doc 9 | from ._deprecate import deprecate_parameter 10 | 11 | # Log styles 12 | from .styles import alice as style 13 | 14 | __all__ = ("label", "style", "text") 15 | 16 | 17 | @copy_doc(label_base.exp_text) 18 | def text(text="", **kwargs): 19 | for key, value in dict(mplhep.rcParams.text._get_kwargs()).items(): 20 | if ( 21 | value is not None 22 | and key not in kwargs 23 | and key in inspect.getfullargspec(label_base.exp_text).kwonlyargs 24 | ): 25 | kwargs.setdefault(key, value) 26 | kwargs.setdefault("fontsize", 28) 27 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 28 | kwargs.setdefault("loc", 1) 29 | kwargs.setdefault("exp", "ALICE") 30 | return label_base.exp_text(text=text, **kwargs) 31 | 32 | 33 | @deprecate_parameter("label", reason='Use `text="..."` instead.', warn_once=False) 34 | @copy_doc(label_base.exp_label) 35 | def label(text=None, label=None, **kwargs): 36 | for key, value in dict(mplhep.rcParams.label._get_kwargs()).items(): 37 | if ( 38 | value is not None 39 | and key not in kwargs 40 | and key in inspect.getfullargspec(label_base.exp_label).kwonlyargs 41 | ): 42 | kwargs.setdefault(key, value) 43 | kwargs.setdefault("fontsize", 28) 44 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 45 | kwargs.setdefault("loc", 1) 46 | kwargs.setdefault("exp", "ALICE") 47 | kwargs.setdefault("rlabel", "") 48 | if text is not None: 49 | kwargs["text"] = text 50 | if label is not None: 51 | kwargs["text"] = label 52 | return label_base.exp_label(**kwargs) 53 | -------------------------------------------------------------------------------- /examples/model_ex/model_examples_unstacked.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data vs model with unstacked components 3 | ===================================== 4 | 5 | Plot data and a model with unstacked components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 3000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 500), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | # --8<-- [end:setup] 44 | 45 | # --8<-- [start:plot_body] 46 | fig, ax_main, ax_comparison = mh.comp.data_model( 47 | data_hist=data_hist, 48 | unstacked_components=background_hists, 49 | unstacked_labels=["c0", "c1", "c2"], 50 | unstacked_colors=sns.color_palette("cubehelix", 3), 51 | xlabel="Observable", 52 | ylabel="Entries", 53 | model_sum_kwargs={"label": "Sum(hists)", "color": "navy"}, 54 | comparison_ylim=[0.5, 1.5], 55 | ) 56 | # --8<-- [end:plot_body] 57 | 58 | # --8<-- [end:full_code] 59 | fig.savefig("model_examples_unstacked.svg", bbox_inches="tight") 60 | -------------------------------------------------------------------------------- /examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data vs functional model 3 | ======================== 4 | 5 | Compare data and model with stacked and unstacked functional components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | from scipy.stats import norm 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | 20 | # --8<-- [start:setup] 21 | # Create data histogram 22 | data_hist = hist.new.Regular(50, -8, 8).Weight() 23 | data_hist.fill( 24 | np.concatenate( 25 | [ 26 | np.random.normal(0, 2, 3500), 27 | np.random.normal(-3, 1, 2000), 28 | np.random.normal(5, 0.5, 200), 29 | ] 30 | ) 31 | ) 32 | _binwidth = data_hist.axes[0].widths[0] 33 | 34 | 35 | # Define model function components 36 | def f_signal(x): 37 | return 200 * _binwidth * norm.pdf(x, loc=5, scale=0.5) 38 | 39 | 40 | def f_background1(x): 41 | return 3500 * _binwidth * norm.pdf(x, loc=0, scale=2) 42 | 43 | 44 | def f_background2(x): 45 | return 2000 * _binwidth * norm.pdf(x, loc=-3, scale=1) 46 | 47 | 48 | # --8<-- [end:setup] 49 | 50 | # --8<-- [start:plot_body] 51 | fig, ax_main, ax_comparison = mh.comp.data_model( 52 | data_hist=data_hist, 53 | stacked_components=[f_background1, f_background2], 54 | stacked_labels=["c0", "c1"], 55 | unstacked_components=[f_signal], 56 | unstacked_labels=["Signal"], 57 | unstacked_colors=["#8EBA42"], 58 | xlabel="Observable", 59 | ylabel="Entries", 60 | model_sum_kwargs={"show": True, "label": "Model", "color": "navy"}, 61 | comparison="pull", 62 | ) 63 | # --8<-- [end:plot_body] 64 | 65 | # --8<-- [end:full_code] 66 | fig.savefig( 67 | "ratio_data_vs_model_with_stacked_and_unstacked_function_components.svg", 68 | bbox_inches="tight", 69 | ) 70 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate" 3 | 4 | repos: 5 | - repo: https://github.com/compilerla/conventional-pre-commit 6 | rev: v4.3.0 7 | hooks: 8 | - id: conventional-pre-commit 9 | stages: [commit-msg] 10 | args: [--verbose] 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: v6.0.0 13 | hooks: 14 | - id: check-added-large-files 15 | exclude: '\.ipynb$' 16 | - id: check-case-conflict 17 | - id: check-merge-conflict 18 | - id: check-symlinks 19 | - id: check-yaml 20 | - id: debug-statements 21 | - id: end-of-file-fixer 22 | - id: mixed-line-ending 23 | - id: requirements-txt-fixer 24 | - id: trailing-whitespace 25 | 26 | - repo: https://github.com/astral-sh/ruff-pre-commit 27 | # Ruff version. 28 | rev: v0.14.9 29 | hooks: 30 | # Run the linter. 31 | - id: ruff-check 32 | args: [ --fix, --show-fixes ] 33 | exclude: '\.ipynb$' 34 | # Run the formatter. 35 | - id: ruff-format 36 | exclude: '\.ipynb$' 37 | 38 | - repo: https://github.com/pre-commit/mirrors-mypy 39 | rev: v1.19.1 40 | hooks: 41 | - id: mypy 42 | files: src 43 | additional_dependencies: ["uhi", "numpy", "matplotlib>3.4"] 44 | args: [--show-error-codes] 45 | 46 | - repo: https://github.com/codespell-project/codespell 47 | rev: "v2.4.1" 48 | hooks: 49 | - id: codespell 50 | args: ["-L", "hist,heros,hep", "--uri-ignore-words-list", "*"] 51 | exclude: '^examples/Examples\.ipynb$|^new_docs/site/|\.ipynb$' 52 | 53 | - repo: https://github.com/pre-commit/pygrep-hooks 54 | rev: "v1.10.0" 55 | hooks: 56 | - id: python-check-blanket-noqa 57 | - id: python-check-blanket-type-ignore 58 | - id: python-no-log-warn 59 | - id: python-no-eval 60 | - id: python-use-type-annotations 61 | - id: rst-backticks 62 | - id: rst-directive-colons 63 | - id: rst-inline-touching-normal 64 | -------------------------------------------------------------------------------- /examples/model_ex/model_examples_pull_no_model_unc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pull plot, no model uncertainty 3 | =============================== 4 | 5 | Compare data and model with pulls, without model uncertainty. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 2000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 500), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | # --8<-- [end:setup] 44 | 45 | # --8<-- [start:plot_body] 46 | fig, ax_main, ax_comparison = mh.comp.data_model( 47 | data_hist=data_hist, 48 | stacked_components=background_hists, 49 | stacked_labels=["c0", "c1", "c2"], 50 | stacked_colors=sns.color_palette("cubehelix", 3), 51 | xlabel=r"$m_{\ell\ell}\,\,[TeV/c^2]$", 52 | ylabel=r"Hits in the LMN per $4.2\times 10^{-1}\,\,eV/c^2$", 53 | comparison="pull", 54 | model_uncertainty=False, # <-- 55 | ) 56 | # --8<-- [end:plot_body] 57 | 58 | # --8<-- [end:full_code] 59 | fig.savefig("model_examples_pull_no_model_unc.svg", bbox_inches="tight") 60 | -------------------------------------------------------------------------------- /new_docs/docs/_static/custom_css.css: -------------------------------------------------------------------------------- 1 | /* Custom CSS for mplhep docs */ 2 | 3 | .md-header__topic { 4 | font-weight: 600; 5 | } 6 | 7 | .md-nav__link--active { 8 | font-weight: 600; 9 | } 10 | 11 | /* MathJax styling */ 12 | .math { 13 | font-size: 1.1em; 14 | } 15 | 16 | /* Code block styling */ 17 | .highlight pre { 18 | border-radius: 0.3rem; 19 | } 20 | 21 | /* Admonition styling */ 22 | .md-typeset .admonition { 23 | border-radius: 0.3rem; 24 | } 25 | 26 | .md-typeset .admonition.imports, 27 | .md-typeset details.imports { 28 | border-color: rgb(25, 118, 210); 29 | } 30 | .md-typeset .imports > .admonition-title, 31 | .md-typeset .imports > summary { 32 | background-color: rgba(25, 118, 210, 0.1); 33 | } 34 | .md-typeset .imports > .admonition-title::before, 35 | .md-typeset .imports > summary::before { 36 | background-color: rgb(25, 118, 210); 37 | -webkit-mask-image: var(--md-admonition-icon--imports); 38 | mask-image: var(--md-admonition-icon--imports); 39 | } 40 | 41 | .md-typeset .admonition.setup, 42 | .md-typeset details.setup { 43 | border-color: rgb(76, 175, 80); 44 | } 45 | .md-typeset .setup > .admonition-title, 46 | .md-typeset .setup > summary { 47 | background-color: rgba(76, 175, 80, 0.1); 48 | } 49 | .md-typeset .setup > .admonition-title::before, 50 | .md-typeset .setup > summary::before { 51 | background-color: rgb(76, 175, 80); 52 | -webkit-mask-image: var(--md-admonition-icon--setup); 53 | mask-image: var(--md-admonition-icon--setup); 54 | } 55 | 56 | .md-typeset .admonition.code, 57 | .md-typeset details.code { 58 | border-color: rgb(156, 39, 176); 59 | } 60 | .md-typeset .code > .admonition-title, 61 | .md-typeset .code > summary { 62 | background-color: rgba(156, 39, 176, 0.1); 63 | } 64 | .md-typeset .code > .admonition-title::before, 65 | .md-typeset .code > summary::before { 66 | background-color: rgb(156, 39, 176); 67 | -webkit-mask-image: var(--md-admonition-icon--code); 68 | mask-image: var(--md-admonition-icon--code); 69 | } 70 | -------------------------------------------------------------------------------- /src/mplhep/styles/plothist.py: -------------------------------------------------------------------------------- 1 | from cycler import cycler 2 | 3 | plothist = { 4 | # General Settings 5 | "figure.figsize": (6, 4), 6 | "figure.facecolor": "white", 7 | # "text.color" : .1, 8 | # Fonts 9 | "font.size": 15, 10 | "xtick.labelsize": 15, 11 | "ytick.labelsize": 15, 12 | "axes.labelsize": 18, 13 | "legend.fontsize": 12, 14 | # "axes.labelcolor" : .1, 15 | "text.usetex": False, 16 | "font.family": ["serif", "sans-serif"], 17 | "font.serif": ["Latin Modern Math", "Latin Modern Roman"], 18 | "font.sans-serif": ["Latin Modern Sans"], 19 | # Text in math mode 20 | "mathtext.fontset": "cm", 21 | "mathtext.default": "regular", 22 | "legend.frameon": False, 23 | "legend.framealpha": 0.5, 24 | # Ticks 25 | "xtick.direction": "in", 26 | "ytick.direction": "in", 27 | # "xtick.color" : .1, 28 | # "ytick.color" : .1, 29 | "xtick.minor.visible": True, 30 | "ytick.minor.visible": True, 31 | "ytick.left": True, 32 | "ytick.right": False, 33 | "xtick.top": False, 34 | "xtick.bottom": True, 35 | "xtick.major.size": 6, 36 | "ytick.major.size": 6, 37 | "xtick.minor.size": 3, 38 | "ytick.minor.size": 3, 39 | "xtick.major.pad": 8.0, 40 | "ytick.major.pad": 5.0, 41 | "axes.axisbelow": False, 42 | "image.cmap": "viridis", 43 | "grid.linestyle": "-", 44 | "lines.solid_capstyle": "round", 45 | "axes.grid": False, 46 | "axes.facecolor": "white", 47 | # "axes.edgecolor" : .0, 48 | "axes.linewidth": 1, 49 | "axes.formatter.limits": (-4, 4), 50 | "axes.prop_cycle": cycler( 51 | "color", 52 | ["#348ABD", "#E24A33", "#988ED5", "#777777", "#FBC15E", "#8EBA42", "#FFB5B8"], 53 | ), 54 | "axes.formatter.use_mathtext": True, 55 | "axes.formatter.useoffset": False, 56 | "axes.labelpad": 6, 57 | # "grid.color" : .8, 58 | "errorbar.capsize": 0, 59 | # "svg.hashsalt" : 8311311 60 | } 61 | -------------------------------------------------------------------------------- /examples/model_ex/model_examples_stacked_unstacked.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data vs model with stacked and unstacked components 3 | =================================================== 4 | 5 | Plot data and a model with stacked and unstacked components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 3000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 500), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | # --8<-- [end:setup] 44 | 45 | # --8<-- [start:plot_body] 46 | fig, ax_main, ax_comparison = mh.comp.data_model( 47 | data_hist=data_hist, 48 | stacked_components=background_hists[:2], 49 | stacked_labels=["c0", "c1"], 50 | stacked_colors=sns.color_palette("cubehelix", 3)[:2], 51 | unstacked_components=background_hists[2:], 52 | unstacked_labels=["c2"], 53 | unstacked_colors=[sns.color_palette("cubehelix", 3)[2]], 54 | xlabel="Observable", 55 | ylabel="Entries", 56 | model_sum_kwargs={"show": True, "label": "Model", "color": "navy"}, 57 | comparison_ylim=(0.5, 1.5), 58 | ) 59 | # --8<-- [end:plot_body] 60 | 61 | # --8<-- [end:full_code] 62 | fig.savefig("model_examples_stacked_unstacked.svg", bbox_inches="tight") 63 | -------------------------------------------------------------------------------- /src/mplhep/_compat.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | from typing import Any, Callable, TypeVar 5 | 6 | if sys.version_info >= (3, 10): 7 | from typing import ParamSpec, TypeAlias 8 | else: 9 | from typing_extensions import ParamSpec, TypeAlias 10 | 11 | T = TypeVar("T") 12 | P = ParamSpec("P") 13 | WrappedFuncDeco: TypeAlias = Callable[[Callable[..., Any]], Callable[..., Any]] 14 | 15 | 16 | def copy_doc( 17 | copy_func: Callable[..., Any], 18 | ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: 19 | """Copies the doc string of the given function to another. 20 | This function is intended to be used as a decorator. 21 | 22 | .. code-block:: python3 23 | 24 | def foo(): 25 | '''This is a foo doc string''' 26 | ... 27 | 28 | @copy_doc(foo) 29 | def bar(): 30 | ... 31 | """ 32 | 33 | def wrapped(func: Callable[..., Any]) -> Callable[..., Any]: 34 | func.__doc__ = copy_func.__doc__ 35 | return func 36 | 37 | return wrapped 38 | 39 | 40 | class DocstringCopier: 41 | """A docstring copying mechanism that works with both Sphinx and MkDocs.""" 42 | 43 | def copy(self, source_func: Callable[..., Any]) -> Callable[..., Any]: 44 | """Decorator to copy docstring from source function to target function.""" 45 | 46 | def decorator(target_func: Callable[..., Any]) -> Callable[..., Any]: 47 | # Copy the docstring at decoration time 48 | target_func.__doc__ = source_func.__doc__ 49 | return target_func 50 | 51 | return decorator 52 | 53 | 54 | def filter_deprecated(obj): 55 | """Filter out objects that have __deprecated__ attribute set to True. 56 | 57 | This function is used by MkDocs to automatically exclude deprecated functions 58 | from the API documentation. 59 | """ 60 | return not getattr(obj, "__deprecated__", False) 61 | 62 | 63 | # Create a singleton instance for backward compatibility 64 | docstring = DocstringCopier() 65 | 66 | __all__ = ("docstring", "copy_doc", "filter_deprecated") 67 | -------------------------------------------------------------------------------- /examples/model_ex/model_examples_stacked.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data vs model with stacked components 3 | ===================================== 4 | 5 | Plot data and a model with stacked components. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 3000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 500), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | 44 | # Create signal histogram (not part of the model) 45 | signal_hist = hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-1, 0.5, 400)) 46 | # --8<-- [end:setup] 47 | 48 | # --8<-- [start:plot_body] 49 | fig, ax_main, ax_comparison = mh.comp.data_model( 50 | data_hist=data_hist, 51 | stacked_components=background_hists, 52 | stacked_labels=["c0", "c1", "c2"], 53 | stacked_colors=sns.color_palette("cubehelix", 3), 54 | xlabel="Observable", 55 | ylabel="Entries", 56 | ) 57 | 58 | # Signal histogram not part of the model and therefore not included in the comparison 59 | mh.histplot(signal_hist, ax=ax_main, color="red", label="Signal", histtype="step") 60 | ax_main.legend() 61 | # --8<-- [end:plot_body] 62 | 63 | # --8<-- [end:full_code] 64 | fig.savefig("model_examples_stacked.svg", bbox_inches="tight") 65 | -------------------------------------------------------------------------------- /.github/copilot-setup-steps.yml: -------------------------------------------------------------------------------- 1 | name: "Copilot Setup Steps" 2 | 3 | # Automatically run the setup steps when they are changed to allow for easy validation, and 4 | # allow manual testing through the repository's "Actions" tab 5 | on: 6 | workflow_dispatch: 7 | push: 8 | paths: 9 | - .github/workflows/copilot-setup-steps.yml 10 | pull_request: 11 | paths: 12 | - .github/workflows/copilot-setup-steps.yml 13 | 14 | jobs: 15 | # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. 16 | copilot-setup-steps: 17 | runs-on: ubuntu-latest 18 | 19 | # Set the permissions to the lowest permissions possible needed for your steps. 20 | # Copilot will be given its own token for its operations. 21 | permissions: 22 | # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. 23 | contents: read 24 | 25 | # You can define any steps you want, and they will run before the agent starts. 26 | # If you do not check out your code, Copilot will do this for you. 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v5 30 | 31 | - name: Setup uv 32 | uses: astral-sh/setup-uv@v5 33 | with: 34 | version: "latest" 35 | python-version: ${{ matrix.python-version }} 36 | enable-cache: true 37 | cache-dependency-glob: "**/pyproject.toml" 38 | 39 | - name: Requirements check 40 | run: uv pip list 41 | 42 | - name: Install core fonts 43 | if: runner.os == 'Linux' 44 | run: | 45 | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections 46 | sudo apt-get install ttf-mscorefonts-installer 47 | 48 | - name: Install package 49 | run: | 50 | uv pip install -e ".[all]" 51 | uv pip install pytest-github-actions-annotate-failures 52 | uv pip list 53 | -------------------------------------------------------------------------------- /.github/workflows/ci-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 16 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 17 | concurrency: 18 | group: "pages" 19 | cancel-in-progress: false 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v6 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v5 32 | 33 | - name: Setup uv 34 | uses: astral-sh/setup-uv@v7 35 | with: 36 | activate-environment: true 37 | version: "latest" 38 | python-version: "3.11" 39 | enable-cache: true 40 | 41 | - name: Install core fonts 42 | run: | 43 | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections 44 | sudo apt-get install ttf-mscorefonts-installer 45 | 46 | - name: Install package and documentation dependencies 47 | run: | 48 | uv pip install -e ".[all]" 49 | uv pip install markdown-exec griffe-inherited-docstrings docstring-inheritance 50 | uv pip install git+https://github.com/andrzejnovak/mkdocs-matplotlib.git 51 | uv pip list 52 | 53 | - name: Build documentation 54 | run: | 55 | cd new_docs 56 | mkdocs build --clean 57 | 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v4 60 | with: 61 | path: ./new_docs/site 62 | 63 | deploy: 64 | runs-on: ubuntu-latest 65 | needs: build 66 | steps: 67 | - name: Deploy to GitHub Pages 68 | id: deployment 69 | uses: actions/deploy-pages@v4 70 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | API reference 3 | ============= 4 | 5 | Plotting functions 6 | ================== 7 | 8 | Primary functions. 9 | 10 | .. autofunction:: mplhep.histplot 11 | 12 | .. autofunction:: mplhep.hist2dplot 13 | 14 | .. autofunction:: mplhep.funcplot 15 | 16 | .. # List all modules when appropriately privatized 17 | .. automodule:: mplhep.plot 18 | :members: 19 | 20 | 21 | Text functions 22 | ======================== 23 | 24 | Functions to annotate figures in a convenient way. Typically ``append_text`` can be used to place an additional artist after ``add_text``. 25 | 26 | .. autofunction:: mplhep.label.add_text 27 | 28 | .. autofunction:: mplhep.label.append_text 29 | 30 | 31 | Experiment label helpers 32 | ======================== 33 | 34 | Experiment specific helpers. 35 | 36 | For the effects of the ``label`` method, see also the gallery examples in :ref:`gallery-labels`. 37 | 38 | 39 | .. autofunction:: mplhep.cms.label 40 | 41 | .. autofunction:: mplhep.cms.text 42 | 43 | .. autofunction:: mplhep.atlas.label 44 | 45 | .. autofunction:: mplhep.atlas.text 46 | 47 | .. autofunction:: mplhep.lhcb.label 48 | 49 | .. autofunction:: mplhep.lhcb.text 50 | 51 | .. autofunction:: mplhep.alice.label 52 | 53 | .. autofunction:: mplhep.alice.text 54 | 55 | .. autofunction:: mplhep.dune.label 56 | 57 | .. autofunction:: mplhep.dune.text 58 | 59 | 60 | Axes helpers 61 | ============ 62 | 63 | Use all helpers together 64 | 65 | .. autofunction:: mplhep.mpl_magic 66 | 67 | or one by one. 68 | 69 | .. autofunction:: mplhep.plot.ylow 70 | 71 | .. autofunction:: mplhep.plot.yscale_legend 72 | 73 | .. autofunction:: mplhep.plot.yscale_anchored_text 74 | 75 | 76 | Figure helpers 77 | ============== 78 | 79 | .. autofunction:: mplhep.append_axes 80 | 81 | .. autofunction:: mplhep.box_aspect 82 | 83 | .. autofunction:: mplhep.make_square_add_cbar 84 | 85 | .. autofunction:: mplhep.rescale_to_axessize 86 | 87 | Legend helpers 88 | ============== 89 | 90 | .. autofunction:: mplhep.sort_legend() 91 | 92 | 93 | Styles 94 | ================== 95 | 96 | See the :ref:`gallery-styles` section for an overview of the available styles. 97 | -------------------------------------------------------------------------------- /src/mplhep/exp_cms.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import inspect 4 | 5 | from matplotlib import rcParams 6 | 7 | import mplhep 8 | 9 | from . import label as label_base 10 | from ._compat import copy_doc 11 | from ._deprecate import deprecate_parameter 12 | 13 | # Log styles 14 | from .styles import cms as style 15 | 16 | # import mplhep._deprecate as deprecate 17 | 18 | __all__ = ("label", "style", "text") 19 | 20 | 21 | @copy_doc(label_base.exp_text) 22 | def text(text="", **kwargs): 23 | for key, value in dict(mplhep.rcParams.text._get_kwargs()).items(): 24 | if ( 25 | value is not None 26 | and key not in kwargs 27 | and key in inspect.getfullargspec(label_base.exp_text).kwonlyargs 28 | ): 29 | kwargs.setdefault(key, value) 30 | kwargs.setdefault("fontstyle", ("normal", "italic", "normal", "normal")) 31 | kwargs.setdefault("exp", "CMS") 32 | if text is not None: 33 | kwargs["text"] = text 34 | return label_base.exp_text(**kwargs) 35 | 36 | 37 | @deprecate_parameter("label", reason='Use `text="..."` instead.', warn_once=False) 38 | @copy_doc(label_base.exp_label) 39 | def label(text=None, label=None, **kwargs): # noqa: ARG001 40 | for key, value in dict(mplhep.rcParams.label._get_kwargs()).items(): 41 | if ( 42 | value is not None 43 | and key not in kwargs 44 | and key in inspect.getfullargspec(label_base.exp_label).kwonlyargs 45 | ): 46 | kwargs.setdefault(key, value) 47 | kwargs.setdefault( 48 | "fontsize", 49 | ( 50 | rcParams["font.size"] * 1.3, 51 | rcParams["font.size"], 52 | rcParams["font.size"] / 1.1, 53 | rcParams["font.size"] / 1.3, 54 | ), 55 | ) 56 | kwargs.setdefault("fontstyle", ("normal", "italic", "normal", "normal")) 57 | kwargs.setdefault("exp", "CMS") 58 | if text is not None: 59 | kwargs["text"] = text 60 | return label_base.exp_label(**kwargs) 61 | 62 | 63 | # Deprecation example 64 | # @deprecate.deprecate("Naming convention is changing. Use ``mplhep.cms.label``.") 65 | # def cmslabel(*args, **kwargs): 66 | # return _cms_label(*args, **kwargs) 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific 2 | src/mplhep/_version.py 3 | *.root 4 | result_images/ 5 | pytest_results/ 6 | test/ 7 | pytest_results/ 8 | .nox/ 9 | .vscode/ 10 | new_docs/site/ 11 | uv.lock 12 | new_docs/docs/guide_advanced.md 13 | new_docs/docs/guide_comparisons.md 14 | new_docs/docs/guide_styling.md 15 | new_docs/docs/guide_basic_plotting.md 16 | new_docs/docs/guide_utilities.md 17 | 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | MANIFEST 44 | 45 | # PyInstaller 46 | # Usually these files are written by a python script from a template 47 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 48 | *.manifest 49 | *.spec 50 | 51 | # Installer logs 52 | pip-log.txt 53 | pip-delete-this-directory.txt 54 | 55 | # Unit test / coverage reports 56 | htmlcov/ 57 | .tox/ 58 | .coverage 59 | .coverage.* 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | *.cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | flow_th1.root 67 | result_images/ 68 | 69 | # Translations 70 | *.mo 71 | *.pot 72 | 73 | # Django stuff: 74 | *.log 75 | local_settings.py 76 | db.sqlite3 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # pyenv 95 | .python-version 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | 125 | # PyCharm 126 | .idea/ 127 | /docs/source/_generated/ 128 | /tmp/ 129 | /docs/source/_static/_generated/ 130 | -------------------------------------------------------------------------------- /.tools/git-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # To use make set as a git alias 3 | # git config --global alias.cleanup '!sh .tools/git-cleanup.sh' 4 | # Usage: 5 | # git cleanup local 6 | # git cleanup remote 7 | # --dryrun to show which branches would be deleted 8 | # --unmerged when running "remote" to remove unmerged branches too 9 | 10 | 11 | lstring="To delete local branches manually you could use: 12 | git branch -d " 13 | rstring="To delete remote branches manually you could use: 14 | git push --delete origin " 15 | 16 | #### Opt Parsing 17 | DRYRUN=0 18 | for i in "$@" ; do [ $i = "--dryrun" ] && DRYRUN=1 && break ; done 19 | 20 | UNMERGED=0 21 | for i in "$@" ; do [ $i = "--unmerged" ] && UNMERGED=1 && break ; done 22 | MERGED=$((1-$UNMERGED)) 23 | #### 24 | 25 | if [ $USER != anovak ] && [ $USER != novak ]; then 26 | echo "Don't do that. If you insist, change lines 23-26 in .tools/git-cleanup.sh" 27 | exit 1 28 | fi 29 | 30 | if [ "$1" = "local" ]; then 31 | echo $([ $DRYRUN = 1 ] && echo "Would" || echo "Will") "delete the following local branches:" 32 | git branch $([ $MERGED = 1 ] && echo "--merged" || echo "") | egrep -v "(^\*|master|main|dev)" 33 | echo $lstring 34 | 35 | if [ $DRYRUN = 0 ]; then 36 | read -r -p "Are you sure? [y/n]" CONT 37 | if [ "$CONT" = "y" ] || [ -z $CONT ]; then 38 | git branch $([ $MERGED = 1 ] && echo "--merged" || echo "") | egrep -v "(^\*|master|main|dev)" | xargs git branch $([ $MERGED = 1 ] && echo "-d" || echo "-D") 39 | else 40 | echo 41 | echo "No action taken"; 42 | echo 43 | fi 44 | 45 | fi 46 | elif [ "$1" = "remote" ]; then 47 | echo $([ $DRYRUN = 1 ] && echo "Would" || echo "Will") "delete the following remote branches:" 48 | git branch -r $([ $MERGED = 1 ] && echo "--merged" || echo "") | egrep -v "(^\*|master|main|dev)" 49 | echo $lstring 50 | 51 | if [ $DRYRUN = 0 ]; then 52 | read -r -p "Are you sure? [y/n]" CONT 53 | if [ "$CONT" = "y" ] || [ -z $CONT ]; then 54 | git branch -r $([ $MERGED = 1 ] && echo "--merged" || echo "") | egrep -v "(^\*|master|main|dev)" | awk -F/ '{print $NF}' | xargs -n 1 git push --delete origin 55 | else 56 | echo 57 | echo "No action taken"; 58 | echo 59 | fi 60 | fi 61 | else 62 | echo "git cleanup: $1 is not a valid git-cleanup option. See 'git cleanup --help'." 63 | echo $lstring 64 | echo $rstring 65 | 66 | fi 67 | -------------------------------------------------------------------------------- /new_docs/docs/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 10 4 | --- 5 | 6 | # API reference 7 | 8 | ## Top Level 9 | 10 | ::: mplhep 11 | options: 12 | members: 13 | - histplot 14 | - hist2dplot 15 | - funcplot 16 | - hist 17 | - add_text 18 | - append_text 19 | show_root_heading: true 20 | show_source: false 21 | 22 | ## Plotting 23 | 24 | ::: mplhep.plot 25 | options: 26 | members: 27 | - histplot 28 | - hist2dplot 29 | - model 30 | - funcplot 31 | - hist 32 | show_root_heading: true 33 | show_source: false 34 | 35 | ::: mplhep.comp 36 | heading_level: 2 37 | options: 38 | show_root_heading: true 39 | show_source: false 40 | 41 | ## Labeling 42 | 43 | ::: mplhep.label 44 | options: 45 | filters: 46 | - "mplhep._compat.filter_deprecated" 47 | heading_level: 2 48 | options: 49 | show_root_heading: true 50 | show_source: false 51 | 52 | ## Utilities 53 | 54 | ::: mplhep.utils 55 | options: 56 | show_root_heading: true 57 | show_source: false 58 | 59 | ## Experiment styling 60 | 61 | ::: mplhep.style 62 | heading_level: 2 63 | options: 64 | show_root_heading: true 65 | show_source: false 66 | 67 | ::: mplhep.atlas 68 | options: 69 | heading_level: 2 70 | members: 71 | - label 72 | - text 73 | filters: 74 | - "mplhep._compat.filter_deprecated" 75 | show_root_heading: true 76 | show_source: false 77 | 78 | ::: mplhep.cms 79 | options: 80 | heading_level: 2 81 | members: 82 | - label 83 | - text 84 | show_root_heading: true 85 | show_source: false 86 | 87 | ::: mplhep.lhcb 88 | options: 89 | heading_level: 2 90 | members: 91 | - label 92 | - text 93 | show_root_heading: true 94 | show_source: false 95 | 96 | ::: mplhep.alice 97 | options: 98 | heading_level: 2 99 | members: 100 | - label 101 | - text 102 | show_root_heading: true 103 | show_source: false 104 | 105 | ::: mplhep.dune 106 | options: 107 | heading_level: 2 108 | members: 109 | - label 110 | - text 111 | show_root_heading: true 112 | show_source: false 113 | -------------------------------------------------------------------------------- /src/mplhep/error_estimation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import warnings 4 | 5 | import numpy as np 6 | import scipy.stats 7 | 8 | _coverage1sd = scipy.stats.norm.cdf(1) - scipy.stats.norm.cdf(-1) 9 | 10 | 11 | def poisson_interval(sumw, sumw2, coverage=_coverage1sd): 12 | """Frequentist coverage interval for Poisson-distributed observations 13 | Parameters 14 | ---------- 15 | sumw : numpy.ndarray 16 | Sum of weights vector 17 | sumw2 : numpy.ndarray 18 | Sum weights squared vector 19 | coverage : float, optional 20 | Central coverage interval, defaults to 68% 21 | Calculates the so-called 'Garwood' interval, 22 | c.f. https://www.ine.pt/revstat/pdf/rs120203.pdf or 23 | http://ms.mcmaster.ca/peter/s743/poissonalpha.html 24 | For weighted data, this approximates the observed count by ``sumw**2/sumw2``, which 25 | effectively scales the unweighted poisson interval by the average weight. 26 | This may not be the optimal solution: see https://arxiv.org/pdf/1309.1287.pdf for a 27 | proper treatment. When a bin is zero, the scale of the nearest nonzero bin is 28 | substituted to scale the nominal upper bound. 29 | If all bins zero, a warning is generated and interval is set to ``sumw``. 30 | # Taken from Coffea 31 | """ # codespell:ignore 32 | scale = np.empty_like(sumw) 33 | scale[sumw != 0] = sumw2[sumw != 0] / sumw[sumw != 0] 34 | if np.sum(sumw == 0) > 0: 35 | missing = np.where(sumw == 0) 36 | available = np.nonzero(sumw) 37 | if len(available[0]) == 0: 38 | warnings.warn( 39 | "All sumw are zero! Cannot compute meaningful error bars", 40 | RuntimeWarning, 41 | stacklevel=2, 42 | ) 43 | return np.vstack([sumw, sumw]) 44 | nearest = np.sum( 45 | [np.subtract.outer(d, d0) ** 2 for d, d0 in zip(available, missing)] 46 | ).argmin(axis=0) 47 | argnearest = tuple(dim[nearest] for dim in available) 48 | scale[missing] = scale[argnearest] 49 | counts = sumw / scale 50 | lo = scale * scipy.stats.chi2.ppf((1 - coverage) / 2, 2 * counts) / 2.0 51 | hi = scale * scipy.stats.chi2.ppf((1 + coverage) / 2, 2 * (counts + 1)) / 2.0 52 | interval = np.array([lo, hi]) 53 | interval[np.isnan(interval)] = 0.0 # chi2.ppf produces nan for counts=0 54 | return interval 55 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | .. _installing-mplhep: 2 | 3 | Install 4 | ================= 5 | 6 | Quick start 7 | ----------- 8 | If you are already familiar with python environment and have ran ``pip install mplhep``, 9 | primary functionality can be accessed as follows: 10 | 11 | .. code-block:: python 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | import mplhep as mh 16 | 17 | # Load style sheet 18 | plt.style.use(mh.style.CMS) # or ATLAS/LHCb2 19 | 20 | h, bins = np.histogram(np.random.random(1000)) 21 | fig, ax = plt.subplots() 22 | mh.histplot(h, bins) 23 | 24 | Will feature binder 25 | 26 | .. 27 | To try mplhep now, without installing anything, you can experiment with our 28 | `hosted tutorial notebooks `_. 29 | 30 | Platform support 31 | ---------------- 32 | mplhep is a python package distributed via `PyPI `_. 33 | Python version 3.6 or newer is preferred, but a semi-maintained version of mplhep will run in python 2.7 or newer. 34 | 35 | .. note:: Python 2 end-of-life is Jan. 1, 2020. All major scientific python packages will no longer provide support for python 2 by that date: https://python3statement.org/ 36 | 37 | All functional features in each supported python version are routinely tested. 38 | You can see the python version you have installed by typing the following at the command prompt: 39 | 40 | >>> python --version 41 | 42 | or, in some cases, if both python 2 and 3 are available, you can find the python 3 version via: 43 | 44 | >>> python3 --version 45 | 46 | Install mplhep 47 | -------------- 48 | To install mplhep you likely want to place it where your matplotlib installation is: 49 | 50 | - default ``pip install mplhep`` installs system-wide or whichever virtual environment is currently sourced 51 | - if you do not have administrator permissions, install as local user with ``pip install --user mplhep`` 52 | - if you prefer to not place mplhep in your global environment, you can set up a `virtual environment `_, and use the venv-provided pip 53 | - if you use `Conda `_, simply activate the environment you wish to use and install via the conda-provided pip. 54 | - it will soon be possible to install from conda-forge ``conda install mplhep`` 55 | 56 | To update a previously installed mplhep to a newer version, use: ``pip install --upgrade mplhep``. 57 | -------------------------------------------------------------------------------- /src/mplhep/exp_dune.py: -------------------------------------------------------------------------------- 1 | # Taken with a lot of inspiration from https://github.com/DUNE/dune_plot_style 2 | # Many thanks to the authors for their work! 3 | 4 | from __future__ import annotations 5 | 6 | import inspect 7 | 8 | from matplotlib import rcParams 9 | 10 | import mplhep 11 | 12 | from . import label as label_base 13 | from ._compat import copy_doc 14 | from ._deprecate import deprecate_parameter 15 | 16 | # Import styles 17 | from .styles import dune as style 18 | 19 | __all__ = ("label", "style", "text") 20 | 21 | 22 | @copy_doc(label_base.exp_text) 23 | def text(text="", **kwargs): 24 | """Add DUNE experiment text to a plot.""" 25 | for key, value in dict(mplhep.rcParams.text._get_kwargs()).items(): 26 | if ( 27 | value is not None 28 | and key not in kwargs 29 | and key in inspect.getfullargspec(label_base.exp_text).kwonlyargs 30 | ): 31 | kwargs.setdefault(key, value) 32 | kwargs.setdefault( 33 | "fontsize", 34 | ( 35 | rcParams["font.size"] * 1.3, 36 | rcParams["font.size"], 37 | rcParams["font.size"] * 0.95, 38 | rcParams["font.size"] / 1.3, 39 | ), 40 | ) 41 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 42 | kwargs.setdefault("exp", "DUNE") 43 | return label_base.exp_text(text=text, **kwargs) 44 | 45 | 46 | @deprecate_parameter("label", reason='Use `text="..."` instead.', warn_once=False) 47 | @copy_doc(label_base.exp_label) 48 | def label(text=None, label=None, **kwargs): 49 | """Add DUNE experiment label to a plot.""" 50 | for key, value in dict(mplhep.rcParams.label._get_kwargs()).items(): 51 | if ( 52 | value is not None 53 | and key not in kwargs 54 | and key in inspect.getfullargspec(label_base.exp_label).kwonlyargs 55 | ): 56 | kwargs.setdefault(key, value) 57 | kwargs.setdefault( 58 | "fontsize", 59 | ( 60 | rcParams["font.size"] * 1.3, 61 | rcParams["font.size"], 62 | rcParams["font.size"] * 0.95, 63 | rcParams["font.size"] / 1.3, 64 | ), 65 | ) 66 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 67 | kwargs.setdefault("exp", "DUNE") 68 | kwargs.setdefault("loc", 0) 69 | if text is not None: 70 | kwargs["text"] = text 71 | if label is not None: 72 | kwargs["text"] = label 73 | return label_base.exp_label(**kwargs) 74 | -------------------------------------------------------------------------------- /tests/test_make_plottable_histogram.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import boost_histogram as bh 4 | import hist 5 | import numpy as np 6 | import pytest 7 | import uproot 8 | 9 | from mplhep import EnhancedPlottableHistogram, make_plottable_histogram 10 | 11 | 12 | @pytest.fixture 13 | def data(): 14 | """ 15 | Pytest fixture that returns a dummy data list to be reused across multiple tests. 16 | """ 17 | return [0.5] + [1.1, 1.9] + [2.1, 2.5, 2.9] # noqa: RUF005 18 | 19 | 20 | @pytest.fixture 21 | def basic_hist(): 22 | edges = np.array([[0, 1], [1, 2], [2, 3]]) 23 | values = np.array([1.0, 2.0, 3.0]) 24 | variances = values 25 | return EnhancedPlottableHistogram(values, edges=edges, variances=variances) 26 | 27 | 28 | def test_make_plottable_histogram_from_numpy_hist(basic_hist, data): 29 | basic_hist.set_variances(None) # Numpy histograms do not have variances 30 | 31 | bins = [0, 1, 2, 3] 32 | h_numpy = np.histogram(data, bins=bins) 33 | h_plottable = make_plottable_histogram(h_numpy) 34 | 35 | assert h_plottable == basic_hist 36 | 37 | 38 | def test_make_plottable_histogram_from_boost_hist(basic_hist, data): 39 | bins = bh.axis.Regular(3, 0, 3) 40 | h_boost_histogram = bh.Histogram(bins) 41 | h_boost_histogram.fill(data) 42 | h_plottable = make_plottable_histogram(h_boost_histogram) 43 | 44 | assert h_plottable == basic_hist 45 | 46 | 47 | def test_make_plottable_histogram_from_hist(basic_hist, data): 48 | bins = hist.axis.Regular(3, 0, 3) 49 | h_hist = hist.Hist(bins) 50 | h_hist.fill(data) 51 | h_plottable = make_plottable_histogram(h_hist) 52 | 53 | assert h_plottable == basic_hist 54 | 55 | 56 | def test_make_plottable_histogram_from_uproot(basic_hist, data): 57 | filename = "_temp_test_make_plottable_histogram_from_uproot.root" 58 | bins = hist.axis.Regular(3, 0, 3) 59 | h = hist.Hist(bins) 60 | h.fill(data) 61 | 62 | with uproot.recreate(filename) as f: 63 | f["test_hist"] = h 64 | 65 | with uproot.open(filename) as f: 66 | h_uproot = f["test_hist"] 67 | 68 | h_plottable = make_plottable_histogram(h_uproot) 69 | 70 | os.remove(filename) 71 | 72 | assert h_plottable == basic_hist 73 | 74 | 75 | def test_make_plottable_histogram_from_root_hist(basic_hist, data): 76 | ROOT = pytest.importorskip("ROOT") 77 | 78 | h_root = ROOT.TH1F("h1", "h1", 3, 0.0, 3.0) 79 | for value in data: 80 | h_root.Fill(value) 81 | 82 | h_plottable = make_plottable_histogram(h_root) 83 | 84 | assert h_plottable == basic_hist 85 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - 'master*' 8 | pull_request: 9 | # Run daily at 0:01 UTC 10 | schedule: 11 | - cron: '1 0 * * *' 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | 19 | test: 20 | name: "🐍 ${{ matrix.python-version }} • ${{ matrix.runs-on }}" 21 | runs-on: ${{ matrix.runs-on }} 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | runs-on: [ ubuntu-latest ] 26 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 27 | include: # testing the last "reasonable supported" version 28 | - runs-on: macOS-15-intel 29 | python-version: "3.9" 30 | - runs-on: macOS-latest 31 | python-version: "3.9" 32 | - runs-on: windows-latest 33 | python-version: "3.9" 34 | 35 | steps: 36 | - uses: actions/checkout@v6 37 | 38 | - name: Setup uv 39 | uses: astral-sh/setup-uv@v7 40 | with: 41 | activate-environment: true 42 | version: "latest" 43 | python-version: ${{ matrix.python-version }} 44 | enable-cache: true 45 | cache-dependency-glob: "**/pyproject.toml" 46 | 47 | - name: Requirements check 48 | run: uv pip list 49 | 50 | - name: Install core fonts 51 | if: runner.os == 'Linux' 52 | run: | 53 | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections 54 | sudo apt-get install ttf-mscorefonts-installer 55 | 56 | - name: Install package for user # check that this installs all dependencies properly 57 | run: uv pip install -e . 58 | 59 | - name: Test import to check for dependencies 60 | run: python -c "import mplhep; from mplhep import *" 61 | 62 | - name: Install package for testing 63 | run: | 64 | uv pip install -e ".[all]" 65 | uv pip install pytest-github-actions-annotate-failures 66 | uv pip install pytest-xdist 67 | uv pip list 68 | 69 | - name: Test with pytest 70 | run: | 71 | python -m pytest -r sa --mpl --mpl-results-path=pytest_results -n 4 --benchmark-disable 72 | 73 | - name: Upload pytest test results 74 | uses: actions/upload-artifact@v5 75 | if: failure() 76 | with: 77 | name: pytest_results-${{ matrix.python-version }}-${{ matrix.runs-on }} 78 | retention-days: 3 79 | path: pytest_results 80 | -------------------------------------------------------------------------------- /tests/test_inputs.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | import pytest 8 | 9 | os.environ["RUNNING_PYTEST"] = "true" 10 | 11 | import boost_histogram as bh 12 | import uproot 13 | from skhep_testdata import data_path 14 | 15 | import mplhep as mh 16 | 17 | """ 18 | To test run: 19 | pytest --mpl 20 | 21 | When adding new tests, run: 22 | pytest --mpl-generate-path=tests/baseline 23 | """ 24 | 25 | plt.switch_backend("Agg") 26 | 27 | 28 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 29 | def test_inputs_basic(): 30 | np.random.seed(0) 31 | H = np.histogram(np.random.normal(5, 2, 1000), bins=np.arange(0, 10, 1)) 32 | h, bins = H 33 | 34 | fig, ax = plt.subplots() 35 | mh.histplot(H, label="tuple", ls="--") 36 | H = (h * 2, bins) 37 | mh.histplot(H, label="unwrap", ls=":") 38 | mh.histplot(h * 3, bins, label="split") 39 | 40 | return fig 41 | 42 | 43 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 44 | def test_inputs_uproot(): 45 | fname = data_path("uproot-hepdata-example.root") 46 | f = uproot.open(fname) 47 | 48 | fig, axs = plt.subplots(1, 2, figsize=(14, 5)) 49 | TH1, TH2 = f["hpx"], f["hpxpy"] 50 | mh.histplot(TH1, yerr=False, ax=axs[0]) 51 | mh.hist2dplot(TH2, ax=axs[1], cbar=False) 52 | 53 | return fig 54 | 55 | 56 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 57 | def test_inputs_bh(): 58 | np.random.seed(0) 59 | 60 | hist2d = bh.Histogram(bh.axis.Regular(10, 0.0, 1.0), bh.axis.Regular(10, 0, 1)) 61 | hist2d.fill(np.random.normal(0.5, 0.2, 1000), np.random.normal(0.5, 0.2, 1000)) 62 | 63 | fig, axs = plt.subplots(1, 2, figsize=(14, 5)) 64 | mh.histplot(hist2d.project(0), yerr=False, ax=axs[0]) 65 | mh.hist2dplot(hist2d, labels=True, cbar=False, ax=axs[1]) 66 | 67 | return fig 68 | 69 | 70 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 71 | def test_inputs_bh_cat(): 72 | np.random.seed(0) 73 | 74 | hist2d = bh.Histogram( 75 | bh.axis.IntCategory(range(10)), bh.axis.StrCategory("", growth=True) 76 | ) 77 | 78 | x = np.round(np.random.normal(5, 3, 1000)).astype(int) 79 | A, Z = np.array(["A", "Z"]).view("int32") 80 | y = list( 81 | np.random.randint(low=A, high=Z, size=1000, dtype="int32").view(f"U{1000}")[0] 82 | ) 83 | hist2d.fill(x, y) 84 | 85 | fig, axs = plt.subplots(1, 2, figsize=(14, 5)) 86 | mh.histplot(hist2d.project(0), yerr=False, ax=axs[0]) 87 | mh.hist2dplot(hist2d, cbar=False, ax=axs[1]) 88 | 89 | return fig 90 | -------------------------------------------------------------------------------- /new_docs/docs/stylesheets/neoteroi-cards.css: -------------------------------------------------------------------------------- 1 | .nt-cards.nt-grid { 2 | display: grid; 3 | grid-auto-columns: 1fr; 4 | gap: 0.5rem; 5 | max-width: 100vw; 6 | overflow-x: auto; 7 | padding: 1px; 8 | } 9 | .nt-cards.nt-grid.cols-1 { 10 | grid-template-columns: repeat(1, 1fr); 11 | } 12 | .nt-cards.nt-grid.cols-2 { 13 | grid-template-columns: repeat(2, 1fr); 14 | } 15 | .nt-cards.nt-grid.cols-3 { 16 | grid-template-columns: repeat(3, 1fr); 17 | } 18 | .nt-cards.nt-grid.cols-4 { 19 | grid-template-columns: repeat(4, 1fr); 20 | } 21 | .nt-cards.nt-grid.cols-5 { 22 | grid-template-columns: repeat(5, 1fr); 23 | } 24 | .nt-cards.nt-grid.cols-6 { 25 | grid-template-columns: repeat(6, 1fr); 26 | } 27 | 28 | @media only screen and (max-width: 400px) { 29 | .nt-cards.nt-grid { 30 | grid-template-columns: repeat(1, 1fr) !important; 31 | } 32 | } 33 | .nt-card { 34 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); 35 | } 36 | .nt-card:hover { 37 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22); 38 | } 39 | 40 | [data-md-color-scheme=slate] .nt-card { 41 | box-shadow: 0 2px 2px 0 rgba(4, 40, 33, 0.14), 0 3px 1px -2px rgba(40, 86, 94, 0.47), 0 1px 5px 0 rgba(139, 252, 255, 0.64); 42 | } 43 | [data-md-color-scheme=slate] .nt-card:hover { 44 | box-shadow: 0 2px 2px 0 rgba(0, 255, 206, 0.14), 0 3px 1px -2px rgba(33, 156, 177, 0.47), 0 1px 5px 0 rgba(96, 251, 255, 0.64); 45 | } 46 | 47 | .nt-card > a { 48 | color: var(--md-default-fg-color); 49 | } 50 | 51 | .nt-card > a > div { 52 | cursor: pointer; 53 | } 54 | 55 | .nt-card { 56 | padding: 5px; 57 | margin-bottom: 0.5rem; 58 | } 59 | 60 | .nt-card-title { 61 | font-size: 1rem; 62 | font-weight: bold; 63 | margin: 4px 0 8px 0; 64 | line-height: 22px; 65 | } 66 | 67 | .nt-card-content { 68 | padding: 0.4rem 0.8rem 0.8rem 0.8rem; 69 | } 70 | 71 | .nt-card-text { 72 | font-size: 14px; 73 | padding: 0; 74 | margin: 0; 75 | } 76 | 77 | .nt-card .nt-card-image { 78 | text-align: center; 79 | border-radius: 2px; 80 | background-position: center center; 81 | background-size: cover; 82 | background-repeat: no-repeat; 83 | min-height: 120px; 84 | } 85 | 86 | .nt-card .nt-card-image.tags img { 87 | margin-top: 12px; 88 | } 89 | 90 | .nt-card .nt-card-image img { 91 | height: 105px; 92 | margin-top: 5px; 93 | } 94 | 95 | .nt-card a:hover, 96 | .nt-card a:focus { 97 | color: var(--md-accent-fg-color); 98 | } 99 | 100 | .nt-card h2 { 101 | margin: 0; 102 | } 103 | -------------------------------------------------------------------------------- /new_docs/docs/guide_advanced_template.md: -------------------------------------------------------------------------------- 1 | # To go further 2 | 3 | This section covers advanced mplhep features and utilities.. 4 | 5 | ??? tip "Prerequisites" 6 | Throughout this guide the following codeblock is assumed. 7 | ```python 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | import hist 11 | np.random.seed(42) 12 | import mplhep as mh 13 | # mh.style.use('') 14 | ``` 15 | 16 | ## Working with UHI Histograms 17 | 18 | mplhep fully supports the [Unified Histogram Interface](https://uhi.readthedocs.io/), making it compatible with modern histogram libraries: 19 | 20 | ```python 21 | import hist 22 | import boost_histogram as bh 23 | 24 | # Using hist library 25 | h = hist.Hist(hist.axis.Regular(50, -3, 3, name='x', label='Observable [GeV]')) 26 | h.fill(data) 27 | mh.histplot(h) # Automatically uses axis labels 28 | 29 | # Using boost_histogram 30 | h_boost = bh.Histogram(bh.axis.Regular(50, -3, 3)) 31 | h_boost.fill(data) 32 | mh.histplot(h_boost) 33 | 34 | # From ROOT files via uproot 35 | import uproot 36 | file = uproot.open('data.root') 37 | h_root = file['histogram_name'] 38 | mh.histplot(h_root) 39 | ``` 40 | 41 | ## Best Practices 42 | 43 | ### Consistent Binning 44 | 45 | When plotting multiple histograms together, ensure they share the same binning: 46 | 47 | ```python 48 | # Define bins once 49 | bins = np.linspace(-4, 4, 41) 50 | 51 | # Use same bins for all histograms 52 | h1 = np.histogram(data1, bins=bins) 53 | h2 = np.histogram(data2, bins=bins) 54 | 55 | mh.histplot([h1[0], h2[0]], bins=bins) 56 | ``` 57 | 58 | ### Error Representation 59 | 60 | Always show uncertainties for data: 61 | 62 | ```python 63 | # For data points 64 | mh.histplot(data_hist, bins=bins, yerr=True, histtype='errorbar', label='Data') 65 | 66 | # For Monte Carlo, consider filled uncertainty bands 67 | mh.histplot(mc_hist, bins=bins, histtype='fill', alpha=0.3, label='MC') 68 | ``` 69 | 70 | ### Axis Labels with Units 71 | 72 | Include units in axis labels following HEP conventions: 73 | 74 | ```python 75 | ax.set_xlabel('Mass [GeV]') 76 | ax.set_ylabel('Events / 5 GeV') # Include bin width 77 | ``` 78 | 79 | ### Using Legends 80 | 81 | Place legends appropriately and use clear labels: 82 | 83 | ```python 84 | mh.histplot([h1, h2], bins=bins, label=['Signal', 'Background']) 85 | ax.legend(loc='upper right', frameon=False) 86 | ``` 87 | 88 | ### Style Consistency 89 | 90 | Apply styles at the beginning of your script, not in functions: 91 | 92 | ```python 93 | # At the top of your script 94 | import mplhep as mh 95 | mh.style.use('CMS') 96 | 97 | # Then create all plots 98 | # ... 99 | ``` 100 | -------------------------------------------------------------------------------- /examples/model_ex/model_all_comparisons.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data/model comparisons 3 | ====================== 4 | 5 | All supported comparisons between data and model. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | np.random.seed(42) 17 | # --8<-- [end:imports] 18 | 19 | # --8<-- [start:setup] 20 | # Create data histogram with mixed components 21 | data_hist = hist.new.Regular(50, -8, 8).Weight() 22 | data_hist.fill( 23 | np.concatenate( 24 | [ 25 | np.random.normal(0, 2, 3000), 26 | np.random.normal(-3, 0.8, 1500), 27 | np.random.normal(-2, 1.5, 1200), 28 | np.random.normal(0, 0.5, 300), 29 | ] 30 | ) 31 | ) 32 | 33 | # Create background component histograms 34 | background_hists = [ 35 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(0, 2, 3500)), 36 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-3, 0.8, 1800)), 37 | hist.new.Regular(50, -8, 8).Weight().fill(np.random.normal(-2, 1.5, 1400)), 38 | ] 39 | 40 | # Scale backgrounds to match data 41 | scale = data_hist.sum().value / sum(background_hists).sum().value 42 | background_hists = [scale * h for h in background_hists] 43 | # --8<-- [end:setup] 44 | 45 | # --8<-- [start:plot_body] 46 | fig, axes = mh.subplots(nrows=6, hspace=0.3) 47 | 48 | mh.comp.data_model( 49 | data_hist=data_hist, 50 | stacked_components=background_hists, 51 | stacked_labels=["c0", "c1", "c2"], 52 | stacked_colors=sns.color_palette("cubehelix", 3), 53 | xlabel="", 54 | ylabel="Entries", 55 | comparison="ratio", 56 | fig=fig, 57 | ax_main=axes[0], 58 | ax_comparison=axes[1], 59 | ) 60 | 61 | mh.add_text( 62 | r"Multiple data-model comparisons, $\mathbf{with}$ model uncertainty", 63 | ax=axes[0], 64 | loc="over left", 65 | fontsize="small", 66 | ) 67 | mh.add_text( 68 | r' $\mathbf{→}$ comparison = "ratio"', ax=axes[1], loc="over left", fontsize=13 69 | ) 70 | 71 | # Add remaining comparison types 72 | for k, comp in enumerate( 73 | ["split_ratio", "pull", "relative_difference", "difference"], start=2 74 | ): 75 | mh.comp.comparison( 76 | data_hist, 77 | sum(background_hists), 78 | ax=axes[k], 79 | comparison=comp, 80 | xlabel="", 81 | h1_label="Data", 82 | h2_label="MC", 83 | h1_w2method="poisson", 84 | ) 85 | mh.add_text( 86 | rf' $\mathbf{{→}}$ comparison = "{comp}"', 87 | ax=axes[k], 88 | fontsize=13, 89 | loc="over left", 90 | ) 91 | mh.set_fitting_ylabel_fontsize(axes[k]) 92 | 93 | axes[-1].set_xlabel("Observable") 94 | # --8<-- [end:plot_body] 95 | 96 | # --8<-- [end:full_code] 97 | fig.savefig("model_all_comparisons.svg", bbox_inches="tight") 98 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to mplhep 2 | 3 | We are happy to accept contributions to `mplhep` via Pull Requests to the GitHub repo. To get started fork the repo. 4 | 5 | ## Bug Reports 6 | 7 | Please open an [issue](https://github.com/scikit-hep/mplhep/issues). 8 | 9 | ## Installing the development environment 10 | 11 | ```bash 12 | python -m pip install --upgrade --editable ".[all]" 13 | ``` 14 | Also conveniently accessible as `bash install.sh`. 15 | 16 | ## Pull Requests 17 | 18 | ### Pull Requests Procedure 19 | 20 | If you would like to make a pull request please: 21 | 22 | 1. Make a fork of the project 23 | 2. Clone your fork locally 24 | 3. Install `prek` and the project's `pre-commit` hooks 25 | 4. Test your changes with `pytest` 26 | 5. Commit your changes to a feature branch of your fork, push to your branch 27 | 6. Make a PR 28 | 29 | ### Running the tests 30 | 31 | You can run the unit tests (which should be fast!) via the following command: 32 | 33 | **With pytest** 34 | 35 | ```bash 36 | pytest --mpl --ignore=tests/test_notebooks.py 37 | ``` 38 | 39 | Note: This ignores the notebook tests (which are run via [papermill](https://github.com/nteract/papermill)) and run somewhat slow. 40 | 41 | Make sure to run the complete suite before submitting a PR 42 | 43 | ```bash 44 | python -m pytest -r sa --mpl --mpl-results-path=pytest_results -n 4 45 | ``` 46 | 47 | **With nox** 48 | 49 | ```bash 50 | nox -s tests 51 | ``` 52 | 53 | ### Making a pull request 54 | 55 | We follow [Conventional Commit](https://www.conventionalcommits.org/) for commit messages and PR titles. Since we merge PR's using squash commits, it's fine if the final commit messages (proposed in the PR body) follow this convention. 56 | 57 | ### Generating Reference Visuals 58 | 59 | If you modified expected outcomes of the tests. New baseline visuals can be generated using this command: 60 | 61 | **With pytest** 62 | 63 | ```bash 64 | python -m pytest -r sa --mpl -n 4 --mpl-generate-path=tests/baseline 65 | ``` 66 | 67 | **With nox** 68 | 69 | ```bash 70 | nox -s generate_examples_figures 71 | ``` 72 | 73 | Only include the actually modified baseline images in your PR! Running `git add -a` and the like will sometimes result in including images which are visually identically but not the same bit-wise. 74 | 75 | ### Linting and Formatting 76 | 77 | We use `prek` to manage code formatting and linting. Make sure to run it before committing your changes: 78 | 79 | **With prek** 80 | 81 | ```bash 82 | prek run --all-files 83 | ``` 84 | 85 | **With nox** 86 | 87 | ```bash 88 | nox -s lint 89 | ``` 90 | 91 | ## Contributing to the documentation 92 | 93 | The documentation is built using [MkDocs](https://www.mkdocs.org/) and the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. To contribute to the documentation, please look at the [`new_docs/CONTRIBUTING_DOC.md`](CONTRIBUTING_DOC.md) file for instructions. 94 | -------------------------------------------------------------------------------- /src/mplhep/exp_lhcb.py: -------------------------------------------------------------------------------- 1 | """LHCb-like plot styles 2 | 3 | All of these styles resemble the LHCb plotting style; however it is an approximation and 4 | not an official style. The style `LHCb` should improve over time by inputs and may be changed in the future in favor of 5 | a style that resembles morethe actual LHCb style. 6 | 7 | To use a specific style, use `LHCb1`, `LHCb2` etc. as they won't change in the future. 8 | 9 | Notes on LHCb2 style: 10 | 11 | An updated version of `LHCb` that includes minor ticks by default on and on all axes as well as improved legends and 12 | larger minus sign (by using unicode). 13 | 14 | Contributed and adjusted by Jonas Eschle 15 | based on the works of Kevin Dungs, Tim Head, Thomas Schietinger, 16 | Andrew Powell, Chris Parkes, Elena Graverini 17 | and Niels Tuning 18 | """ 19 | 20 | from __future__ import annotations 21 | 22 | import inspect 23 | 24 | import mplhep 25 | from mplhep import label as label_base 26 | 27 | from ._compat import copy_doc 28 | from ._deprecate import deprecate_parameter 29 | 30 | # from .label import lumitext as _base_lumitext 31 | from .styles import lhcb as style 32 | 33 | __all__ = ("label", "style", "text") 34 | 35 | 36 | @copy_doc(label_base.exp_text) 37 | def text(text="", **kwargs): 38 | for key, value in dict(mplhep.rcParams.text._get_kwargs()).items(): 39 | if ( 40 | value is not None 41 | and key not in kwargs 42 | and key in inspect.getfullargspec(label_base.exp_text).kwonlyargs 43 | ): 44 | kwargs.setdefault(key, value) 45 | kwargs.setdefault("fontsize", (28 * 1.3, 28, 28 * 0.95, 28 / 1.3)) 46 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 47 | kwargs.setdefault("fontweight", ("normal", "normal", "normal", "normal")) 48 | kwargs.setdefault("fontname", "Times New Roman") 49 | kwargs.setdefault("loc", 1) 50 | kwargs.setdefault("exp", "LHCb") 51 | return label_base.exp_text(text=text, **kwargs) 52 | 53 | 54 | @deprecate_parameter( 55 | "label", reason='Use `text="..."` instead.', warn_once=False, removed=True 56 | ) 57 | @copy_doc(label_base.exp_label) 58 | def label(text=None, label=None, **kwargs): # noqa: ARG001 59 | for key, value in dict(mplhep.rcParams.label._get_kwargs()).items(): 60 | if ( 61 | value is not None 62 | and key not in kwargs 63 | and key in inspect.getfullargspec(label_base.exp_label).kwonlyargs 64 | ): 65 | kwargs.setdefault(key, value) 66 | kwargs.setdefault("fontsize", (28 * 1.3, 28, 28 * 0.95, 28 / 1.3)) 67 | kwargs.setdefault("fontstyle", ("normal", "normal", "normal", "normal")) 68 | kwargs.setdefault("fontweight", ("normal", "normal", "normal", "normal")) 69 | kwargs.setdefault("fontname", "Times New Roman") 70 | kwargs.setdefault("loc", 1) 71 | kwargs.setdefault("exp", "LHCb") 72 | if text is not None: 73 | kwargs["text"] = text 74 | return label_base.exp_label(**kwargs) 75 | 76 | 77 | # WIP copilot dont remove 78 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import runpy 4 | import sys 5 | from pathlib import Path 6 | from typing import Any 7 | 8 | import matplotlib as mpl 9 | import matplotlib.figure 10 | import matplotlib.pyplot as plt 11 | import pytest 12 | 13 | 14 | def with_benchmark(func): 15 | """ 16 | Decorator that automatically creates a benchmark version of a test function. 17 | 18 | Usage: 19 | @with_benchmark 20 | @pytest.mark.mpl_image_compare(style="default", remove_text=True) 21 | def test_simple(): 22 | # your plotting code 23 | return fig 24 | 25 | This creates both: 26 | - test_simple() - for image comparison 27 | - test_simple_benchmark() - for performance benchmarking 28 | """ 29 | 30 | # Create the benchmark version 31 | @pytest.mark.benchmark 32 | def benchmark_test(benchmark): 33 | def run_test(): 34 | fig = func() 35 | if fig is not None: 36 | plt.close(fig) # Clean up figures to prevent memory issues 37 | 38 | benchmark(run_test) 39 | 40 | # Set the benchmark function name and add to the current module 41 | benchmark_name = func.__name__ + "_benchmark" 42 | benchmark_test.__name__ = benchmark_name 43 | benchmark_test.__doc__ = f"Benchmark version of {func.__name__}" 44 | 45 | # Get the module where the decorator is being used 46 | caller_frame = sys._getframe(1) 47 | caller_globals = caller_frame.f_globals 48 | 49 | # Add the benchmark test to the caller's globals so pytest can find it 50 | caller_globals[benchmark_name] = benchmark_test 51 | 52 | # Return the original function unchanged 53 | return func 54 | 55 | 56 | def run_script_and_get_object(script_path_str: str, name: str) -> Any | None: 57 | """ 58 | Runs a Python script from a given file path, temporarily disables saving of matplotlib figures 59 | to suppress file output, and retrieves a variable from the script's global namespace by name. 60 | The retrieved variable can be of any type. 61 | 62 | Typically used with name="fig" to get the figure object created in the script. 63 | 64 | Parameters: 65 | script_path_str (str): Path to the Python script file to execute. 66 | name (str): Name of the variable in the script's global namespace to retrieve. 67 | 68 | Returns: 69 | Any or None: The specified variable from the script's global namespace if present; 70 | otherwise, None. 71 | """ 72 | script_path = Path(script_path_str).resolve() 73 | 74 | if not script_path.is_file(): 75 | msg = f"Script file not found: {script_path_str}" 76 | raise FileNotFoundError(msg) 77 | original_savefig = mpl.figure.Figure.savefig 78 | 79 | def suppressed_savefig(*args, **kwargs): 80 | pass 81 | 82 | mpl.figure.Figure.savefig = suppressed_savefig # type: ignore[method-assign] 83 | 84 | try: 85 | globals_dict = runpy.run_path(str(script_path)) 86 | finally: 87 | mpl.figure.Figure.savefig = original_savefig # type: ignore[method-assign] 88 | 89 | return globals_dict.get(name) 90 | -------------------------------------------------------------------------------- /docs/source/gallery/labels.rst: -------------------------------------------------------------------------------- 1 | .. _gallery-labels: 2 | 3 | Labels 4 | =========== 5 | 6 | This gallery contains examples of how to add labels to plots using the ``mplhep`` package. 7 | 8 | The following plots show the same data with different label positions, using the experiment specific 9 | label methods such as :py:func:`~mplhep.cms.label()` and the position argument ``loc`` to place the label. 10 | 11 | 12 | Position 0 13 | ------------------- 14 | 15 | .. image:: ../_static/_generated/ATLAS/fill/pos0.png 16 | :width: 45% 17 | 18 | .. image:: ../_static/_generated/ATLASAlt/fill/pos0.png 19 | :width: 45% 20 | 21 | .. image:: ../_static/_generated/LHCb1/fill/pos0.png 22 | :width: 45% 23 | 24 | .. image:: ../_static/_generated/LHCb2/fill/pos0.png 25 | :width: 45% 26 | 27 | .. image:: ../_static/_generated/CMS/fill/pos0.png 28 | :width: 45% 29 | 30 | .. image:: ../_static/_generated/DUNE/fill/pos0.png 31 | :width: 45% 32 | 33 | Position 1 34 | ------------------- 35 | 36 | .. image:: ../_static/_generated/ATLAS/fill/pos1.png 37 | :width: 45% 38 | 39 | .. image:: ../_static/_generated/ATLASAlt/fill/pos1.png 40 | :width: 45% 41 | 42 | .. image:: ../_static/_generated/LHCb1/fill/pos1.png 43 | :width: 45% 44 | 45 | .. image:: ../_static/_generated/LHCb2/fill/pos1.png 46 | :width: 45% 47 | 48 | .. image:: ../_static/_generated/CMS/fill/pos1.png 49 | :width: 45% 50 | 51 | .. image:: ../_static/_generated/DUNE/fill/pos1.png 52 | :width: 45% 53 | 54 | Position 2 55 | ------------------- 56 | 57 | .. image:: ../_static/_generated/ATLAS/fill/pos2.png 58 | :width: 45% 59 | 60 | .. image:: ../_static/_generated/ATLASAlt/fill/pos2.png 61 | :width: 45% 62 | 63 | .. image:: ../_static/_generated/LHCb1/fill/pos2.png 64 | :width: 45% 65 | 66 | .. image:: ../_static/_generated/LHCb2/fill/pos2.png 67 | :width: 45% 68 | 69 | .. image:: ../_static/_generated/CMS/fill/pos2.png 70 | :width: 45% 71 | 72 | .. image:: ../_static/_generated/DUNE/fill/pos2.png 73 | :width: 45% 74 | 75 | Position 3 76 | ------------------- 77 | 78 | .. image:: ../_static/_generated/ATLAS/fill/pos3.png 79 | :width: 45% 80 | 81 | .. image:: ../_static/_generated/ATLASAlt/fill/pos3.png 82 | :width: 45% 83 | 84 | .. image:: ../_static/_generated/LHCb1/fill/pos3.png 85 | :width: 45% 86 | 87 | .. image:: ../_static/_generated/LHCb2/fill/pos3.png 88 | :width: 45% 89 | 90 | .. image:: ../_static/_generated/CMS/fill/pos3.png 91 | :width: 45% 92 | 93 | .. image:: ../_static/_generated/DUNE/fill/pos3.png 94 | :width: 45% 95 | 96 | Position 4 97 | ------------------- 98 | 99 | .. image:: ../_static/_generated/ATLAS/fill/pos4.png 100 | :width: 45% 101 | 102 | .. image:: ../_static/_generated/ATLASAlt/fill/pos4.png 103 | :width: 45% 104 | 105 | .. image:: ../_static/_generated/LHCb1/fill/pos4.png 106 | :width: 45% 107 | 108 | .. image:: ../_static/_generated/LHCb2/fill/pos4.png 109 | :width: 45% 110 | 111 | .. image:: ../_static/_generated/CMS/fill/pos4.png 112 | :width: 45% 113 | 114 | .. image:: ../_static/_generated/DUNE/fill/pos4.png 115 | :width: 45% 116 | -------------------------------------------------------------------------------- /new_docs/docs/guide.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | Welcome to the mplhep user guide! This documentation is organized into focused sections to help you find what you need quickly. 4 | 5 | ???+ tip "Prerequisites" 6 | Throughout this guide the following codeblock is assumed. 7 | ```python 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | import hist 11 | np.random.seed(42) 12 | import mplhep as mh 13 | mh.style.use('') 14 | ``` 15 | 16 | ### [**Histogram Plotting**](guide_basic_plotting.md) 17 | Functions for 1D and 2D pre-binned histograms with support for the [Unified Histogram Interface (UHI)](https://uhi.readthedocs.io/). 18 | 19 | - [1D Histogram Plotting](guide_basic_plotting.md#1d-histogram-plotting) 20 | - [Supported Input Formats](guide_basic_plotting.md#supported-input-formats) 21 | - [Histogram Styles](guide_basic_plotting.md#histogram-styles) 22 | - [Multiple Histograms](guide_basic_plotting.md#multiple-histograms) 23 | - [Error Bars](guide_basic_plotting.md#error-bars) 24 | - [Normalization Options](guide_basic_plotting.md#normalization-options) 25 | - [2D Histograms](guide_basic_plotting.md#2d-histograms) 26 | 27 | ### [**Comparison Plotting**](guide_comparisons.md) 28 | High-level functions for data-model comparisons. 29 | 30 | - [Compare two histograms](guide_comparisons.md#comparing-two-histograms) 31 | - [Data-MC comparison](guide_comparisons.md#data-mc-comparison) 32 | - [Showcasing more options](guide_comparisons.md#showcasing-more-options) 33 | - [Additional examples](guide_comparisons.md#additional-examples) 34 | 35 | ### [**Styling**](guide_styling.md) 36 | Official styles for ATLAS, CMS, LHCb, ALICE, and DUNE experiments. 37 | 38 | - [Setting experiment styles](guide_styling.md#setting-experiment-styles) 39 | - [Setting experiment labels](guide_styling.md#setting-experiment-labels) 40 | - [Configuring experiment labels](guide_styling.md#configuring-experiment-labels) 41 | 42 | ### [**Utilities**](guide_utilities.md) 43 | Utility functions and experiment-specific label formatters. 44 | 45 | - [Text placement](guide_advanced.md#text-placement) 46 | - [Subplot creation](guide_utilities.md#subplot-creation) 47 | - [Save variations](guide_utilities.md#save-variations) 48 | - [Fit y-label](guide_utilities.md#fit-y-label) 49 | - [mpl_magic](guide_utilities.md#mpl_magic) 50 | - [Axes manipulation](guide_utilities.md#axes-manipulation) 51 | - [plt.hist wrapper](guide_utilities.md#hist-wrapper) 52 | 53 | ### [**To go further**](guide_advanced.md) 54 | UHI integration and best practices. 55 | 56 | - [Working with UHI Histograms](guide_advanced.md#working-with-uhi-histograms) 57 | - [Best practices](guide_advanced.md#best-practices) 58 | 59 | ## More Info 60 | 61 | - **[API Reference](api.md)** - Detailed documentation of all functions and parameters 62 | - **[Gallery](gallery.md)** - Visual examples of all plot types 63 | - **[Contributing](CONTRIBUTING.md)** - Information on development and testing 64 | 65 | ## Getting Help 66 | 67 | - Check the [GitHub repository](https://github.com/scikit-hep/mplhep) for issues 68 | - Ask questions in the [scikit-hep discussions](https://github.com/scikit-hep/mplhep/discussions) 69 | -------------------------------------------------------------------------------- /src/mplhep/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Primary namespace for mplhep. Holds primary plotting and most common functions. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import os 8 | 9 | import matplotlib.font_manager as fm 10 | import mplhep_data 11 | 12 | # Get styles directly, also available within experiment helpers. 13 | # Get helper functions 14 | from . import comp, label, plot 15 | from . import exp_alice as alice 16 | from . import exp_atlas as atlas 17 | from . import exp_cms as cms 18 | from . import exp_dune as dune 19 | from . import exp_lhcb as lhcb 20 | from . import styles as style 21 | from ._tools import Config 22 | from ._utils import ( 23 | EnhancedPlottableHistogram, 24 | _check_counting_histogram, 25 | ) 26 | from ._utils import ( 27 | _get_plottables as get_plottables, 28 | ) 29 | from ._utils import ( 30 | _make_plottable_histogram as make_plottable_histogram, 31 | ) 32 | from ._version import version as __version__ # noqa: F401 33 | from .label import add_text, append_text, save_variations, savelabels 34 | from .plot import ( 35 | funcplot, 36 | hist, 37 | hist2dplot, 38 | histplot, 39 | model, 40 | ) 41 | 42 | plot_model = model 43 | 44 | plot_hist = histplot 45 | from .styles import set_style 46 | from .utils import ( 47 | append_axes, 48 | box_aspect, 49 | make_square_add_cbar, 50 | merge_legend_handles_labels, 51 | mpl_magic, 52 | rescale_to_axessize, 53 | set_fitting_ylabel_fontsize, 54 | set_ylow, 55 | sort_legend, 56 | subplots, 57 | yscale_anchored_text, 58 | yscale_legend, 59 | ) 60 | 61 | # Configs 62 | rcParams = Config( 63 | label=Config( 64 | text=None, 65 | data=None, 66 | year=None, 67 | lumi=None, 68 | llabel=None, 69 | rlabel=None, 70 | ), 71 | text=Config( 72 | text=None, 73 | ), 74 | ) 75 | 76 | path = os.path.abspath(__file__) 77 | font_path = os.path.join(os.path.dirname(mplhep_data.__file__), "fonts") 78 | font_files = fm.findSystemFonts(fontpaths=font_path) 79 | for font in font_files: 80 | fm.fontManager.addfont(font) 81 | 82 | 83 | # Log submodules 84 | __all__ = [ 85 | "EnhancedPlottableHistogram", 86 | "_check_counting_histogram", 87 | "add_text", 88 | "add_text", 89 | "alice", 90 | "append_axes", 91 | "append_text", 92 | "atlas", 93 | "box_aspect", 94 | "cms", 95 | "comp", 96 | "dune", 97 | "funcplot", 98 | "get_plottables", 99 | "hist", 100 | "hist2dplot", 101 | # Log plot functions 102 | "histplot", 103 | "label", 104 | "lhcb", 105 | "make_plottable_histogram", 106 | "make_square_add_cbar", 107 | "merge_legend_handles_labels", 108 | "mpl_magic", 109 | "plot", 110 | "model", 111 | "plot_hist", 112 | "plot_model", 113 | "rescale_to_axessize", 114 | "save_variations", 115 | "savelabels", 116 | "set_fitting_ylabel_fontsize", 117 | "set_style", 118 | "set_ylow", 119 | "sort_legend", 120 | "style", 121 | "yscale_anchored_text", 122 | "yscale_legend", 123 | "subplots", 124 | ] 125 | -------------------------------------------------------------------------------- /src/mplhep/styles/cms.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import matplotlib as mpl 4 | from cycler import cycler 5 | 6 | # PRL figsize / Elsevier figsize, Nature is somewhere in between 7 | # single column width - 86 mm (3.386in) / 90 mm (3.543in) 8 | # double column width - 172 mm (6.772in) / 180 mm (7.087in) 9 | # For now size to 10 10 | 11 | cmap_petroff = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] 12 | 13 | CMS = { 14 | "axes.prop_cycle": cycler("color", cmap_petroff), 15 | "font.sans-serif": ["TeX Gyre Heros", "Helvetica", "Arial"], 16 | "font.family": "sans-serif", 17 | "mathtext.fontset": "custom", 18 | "mathtext.rm": "TeX Gyre Heros", 19 | "mathtext.bf": "TeX Gyre Heros:bold", 20 | "mathtext.sf": "TeX Gyre Heros", 21 | "mathtext.it": "TeX Gyre Heros:italic", 22 | "mathtext.tt": "TeX Gyre Heros", 23 | "mathtext.cal": "TeX Gyre Heros", 24 | "mathtext.default": "regular", 25 | "figure.figsize": (10.0, 10.0), 26 | "font.size": 26, 27 | "axes.labelsize": "medium", 28 | "axes.unicode_minus": False, 29 | "xtick.labelsize": "small", 30 | "ytick.labelsize": "small", 31 | "legend.fontsize": "small", 32 | "legend.handlelength": 1.5, 33 | "legend.borderpad": 0.5, 34 | "legend.frameon": False, 35 | "xtick.direction": "in", 36 | "xtick.major.size": 12, 37 | "xtick.minor.size": 6, 38 | "xtick.major.pad": 6, 39 | "xtick.top": True, 40 | "xtick.major.top": True, 41 | "xtick.major.bottom": True, 42 | "xtick.minor.top": True, 43 | "xtick.minor.bottom": True, 44 | "xtick.minor.visible": True, 45 | "ytick.direction": "in", 46 | "ytick.major.size": 12, 47 | "ytick.minor.size": 6.0, 48 | "ytick.right": True, 49 | "ytick.major.left": True, 50 | "ytick.major.right": True, 51 | "ytick.minor.left": True, 52 | "ytick.minor.right": True, 53 | "ytick.minor.visible": True, 54 | "grid.alpha": 0.8, 55 | "grid.linestyle": ":", 56 | "axes.linewidth": 2, 57 | "savefig.transparent": False, 58 | "xaxis.labellocation": "right", 59 | "yaxis.labellocation": "top", 60 | } 61 | 62 | # Filter extra (labellocation) items if needed 63 | CMS = {k: v for k, v in CMS.items() if k in mpl.rcParams} 64 | 65 | CMSTex = { 66 | **CMS, 67 | "text.usetex": True, 68 | "text.latex.preamble": "\n".join( 69 | [ 70 | r"\usepackage[LGR,T1]{fontenc}", 71 | r"\usepackage{tgheros}", 72 | r"\renewcommand{\familydefault}{\sfdefault}", 73 | r"\renewcommand{\rmdefault}{\sfdefault}", 74 | r"\usepackage{amsmath}", 75 | r"\usepackage[symbolgreek,symbolmax]{mathastext}", 76 | r"\usepackage{siunitx}", 77 | r"\setlength{\parindent}{0pt}", 78 | r"\def\mathdefault{}", 79 | ] 80 | ), 81 | } 82 | 83 | ROOT = CMS # Leave as default 84 | # ROOT = DeprecDict( 85 | # CMS, message="'ROOT' style dict is deprecated, please use 'CMS' or other" 86 | # "experiment specific stytle instead" 87 | # ) 88 | ROOTTex = CMSTex 89 | # ROOTTex = DeprecDict( 90 | # CMSTex, message="'ROOTTex' style dict is deprecated, please use 'CMSTex' or other" 91 | # "experiment specific stytle instead" 92 | # ) 93 | -------------------------------------------------------------------------------- /tests/test_dune.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | 6 | import matplotlib.pyplot as plt 7 | import pytest 8 | from matplotlib.testing.decorators import check_figures_equal 9 | 10 | os.environ["RUNNING_PYTEST"] = "true" 11 | 12 | import mplhep as mh 13 | 14 | plt.switch_backend("Agg") 15 | 16 | 17 | @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") 18 | @check_figures_equal(extensions=["pdf"]) 19 | def test_dune_style_variants(fig_test, fig_ref): 20 | plt.rcParams.update(plt.rcParamsDefault) 21 | 22 | mh.rcParams.clear() 23 | plt.style.use(mh.style.DUNE1) 24 | fig_ref.subplots() 25 | 26 | mh.rcParams.clear() 27 | mh.style.use(mh.style.DUNE1) 28 | fig_test.subplots() 29 | 30 | 31 | @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") 32 | @check_figures_equal(extensions=["pdf"]) 33 | def test_dune_style_str_alias(fig_test, fig_ref): 34 | plt.rcParams.update(plt.rcParamsDefault) 35 | 36 | mh.rcParams.clear() 37 | plt.style.use(mh.style.DUNE1) 38 | fig_ref.subplots() 39 | 40 | mh.rcParams.clear() 41 | mh.style.use("DUNE") 42 | fig_test.subplots() 43 | 44 | 45 | @pytest.mark.latex 46 | @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") 47 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 48 | def test_style_dunetex(): 49 | plt.rcParams.update(plt.rcParamsDefault) 50 | 51 | plt.style.use(mh.style.DUNETex) 52 | fig, ax = plt.subplots() 53 | mh.dune.label(text="Preliminary") 54 | 55 | return fig 56 | 57 | 58 | @pytest.mark.latex 59 | @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") 60 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 61 | def test_style_dunetex1(): 62 | plt.rcParams.update(plt.rcParamsDefault) 63 | 64 | plt.style.use(mh.style.DUNETex1) 65 | fig, ax = plt.subplots() 66 | mh.dune.label(text="Preliminary") 67 | 68 | return fig 69 | 70 | 71 | @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") 72 | @check_figures_equal(extensions=["pdf"]) 73 | @pytest.mark.parametrize( 74 | ("style", "str_alias"), 75 | [ 76 | (mh.style.DUNE, "DUNE"), 77 | (mh.style.DUNE1, "DUNE1"), 78 | pytest.param( 79 | mh.style.DUNETex, 80 | "DUNETex", 81 | marks=pytest.mark.latex, 82 | ), 83 | pytest.param( 84 | mh.style.DUNETex1, 85 | "DUNETex1", 86 | marks=pytest.mark.latex, 87 | ), 88 | ], 89 | ids=["DUNE", "DUNE1", "DUNETex", "DUNETex1"], 90 | ) 91 | def test_dune_style_string_aliases(fig_test, fig_ref, style, str_alias): 92 | """Test that string aliases work for all DUNE style variants.""" 93 | plt.rcParams.update(plt.rcParamsDefault) 94 | 95 | mh.rcParams.clear() 96 | plt.style.use(style) 97 | fig_ref.subplots() 98 | 99 | mh.rcParams.clear() 100 | mh.style.use(str_alias) 101 | fig_test.subplots() 102 | 103 | 104 | @pytest.mark.mpl_image_compare(style="default") 105 | def test_dune_label_loc(): 106 | fig, axs = plt.subplots(1, 4, figsize=(16, 4)) 107 | for i, ax in enumerate(axs.flatten()): 108 | mh.dune.label(text="Preliminary", loc=i, ax=ax, lumi=50, data=True) 109 | ax.set_title(f"loc={i}") 110 | return fig 111 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | 5 | import nox 6 | 7 | nox.options.sessions = ["lint", "tests"] 8 | nox.needs_version = ">=2025.2.9" 9 | nox.options.default_venv_backend = "uv|venv" 10 | 11 | PYTHON_ALL_VERSIONS = ["3.9", "3.13"] 12 | 13 | 14 | @nox.session(reuse_venv=True) 15 | def lint(session: nox.Session) -> None: 16 | """ 17 | Run the linter. 18 | """ 19 | session.install("prek") 20 | session.run("prek", "run", "--all-files", *session.posargs) 21 | 22 | 23 | @nox.session(python=PYTHON_ALL_VERSIONS) 24 | def tests(session: nox.Session) -> None: 25 | """ 26 | Run the unit and regular tests. 27 | """ 28 | pyproject = nox.project.load_toml("pyproject.toml") 29 | session.install("-e", ".") 30 | session.install(*pyproject["project"]["optional-dependencies"]["test"]) 31 | session.run("pytest", "--mpl", "-n", "auto", *session.posargs) 32 | 33 | 34 | @nox.session(reuse_venv=True) 35 | def generate_examples_figures(session: nox.Session) -> None: 36 | """ 37 | Generate the example figures. Pass "-- tests/test_examples_*.py" to run only the relevant tests. 38 | """ 39 | pyproject = nox.project.load_toml("pyproject.toml") 40 | session.install("-e", ".") 41 | session.install(*pyproject["project"]["optional-dependencies"]["test"]) 42 | session.run( 43 | "pytest", 44 | "--mpl-generate-path=tests/baseline", 45 | *session.posargs, 46 | ) 47 | 48 | 49 | @nox.session(venv_backend="conda", reuse_venv=True) 50 | def root_tests(session): 51 | """ 52 | Test ROOT histograms. Note: a conda installation is needed to run this test. 53 | """ 54 | pyproject = nox.project.load_toml("pyproject.toml") 55 | session.conda_install("--channel=conda-forge", "ROOT") 56 | session.install("-e", ".") 57 | session.install(*pyproject["project"]["optional-dependencies"]["test"]) 58 | session.run("pytest", "tests/test_make_plottable_histogram.py", *session.posargs) 59 | 60 | 61 | @nox.session(reuse_venv=True) 62 | def docs(session: nox.Session) -> None: 63 | """ 64 | Build the documentation. 65 | """ 66 | parser = argparse.ArgumentParser() 67 | parser.add_argument( 68 | "--serve", 69 | action="store_true", 70 | help="Serve the docs locally. By default, the docs are built instead.", 71 | ) 72 | parser.add_argument( 73 | "--fast", 74 | action="store_true", 75 | help="Build docs without generating the codeblock examples.", 76 | ) 77 | parser.add_argument( 78 | "--port", 79 | type=int, 80 | default=8000, 81 | help="Port to serve the docs on if --serve is used. Default is 8000.", 82 | ) 83 | args, posargs = parser.parse_known_args(session.posargs) 84 | 85 | pyproject = nox.project.load_toml("pyproject.toml") 86 | session.install("-e", ".") 87 | session.install(*pyproject["project"]["optional-dependencies"]["docs"]) 88 | session.install(*pyproject["project"]["optional-dependencies"]["test"]) 89 | session.chdir("new_docs") 90 | session.install("-r", "requirements.txt") 91 | 92 | cmd = ["mkdocs"] 93 | 94 | if args.serve: 95 | cmd.extend(["serve", "--dev-addr", f"127.0.0.1:{args.port}"]) 96 | else: 97 | cmd.append("build") 98 | 99 | if args.fast: 100 | cmd.extend(["-f", "mkdocs_norender.yml"]) 101 | 102 | session.run(*cmd, *posargs) 103 | -------------------------------------------------------------------------------- /src/mplhep/exp_atlas.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import inspect 4 | 5 | import matplotlib as mpl 6 | from matplotlib import rcParams 7 | 8 | import mplhep 9 | 10 | from . import label as label_base 11 | from ._deprecate import deprecate, deprecate_parameter 12 | 13 | # Log styles 14 | from .styles import atlas as style 15 | 16 | __all__ = ("label", "style", "text") 17 | 18 | 19 | def text(text="", **kwargs): 20 | for key, value in dict(mplhep.rcParams.text._get_kwargs()).items(): 21 | if ( 22 | value is not None 23 | and key not in kwargs 24 | and key in inspect.getfullargspec(label_base.exp_text).kwonlyargs 25 | ): 26 | kwargs.setdefault(key, value) 27 | kwargs.setdefault( 28 | "fontsize", 29 | ( 30 | rcParams["font.size"] * 1.3, 31 | rcParams["font.size"] * 1.2, 32 | rcParams["font.size"], 33 | rcParams["font.size"] / 1.3, 34 | ), 35 | ) 36 | kwargs.setdefault("fontstyle", ("italic", "normal", "italic", "normal")) 37 | kwargs.setdefault("loc", 4) 38 | kwargs.setdefault("exp", "ATLAS") 39 | return label_base.exp_text(text=text, **kwargs) 40 | 41 | 42 | @deprecate_parameter("label", reason='Use `text="..."` instead.', warn_once=False) 43 | def label(text=None, **kwargs): 44 | for key, value in dict(mplhep.rcParams.label._get_kwargs()).items(): 45 | if ( 46 | value is not None 47 | and key not in kwargs 48 | and key in inspect.getfullargspec(label_base.exp_label).kwonlyargs 49 | ): 50 | kwargs.setdefault(key, value) 51 | kwargs.setdefault( 52 | "fontsize", 53 | ( 54 | rcParams["font.size"] * 1.3, 55 | rcParams["font.size"] * 1.2, 56 | rcParams["font.size"], 57 | rcParams["font.size"] / 1.3, 58 | ), 59 | ) 60 | kwargs.setdefault("fontstyle", ("italic", "normal", "italic", "normal")) 61 | kwargs.setdefault("loc", 4) 62 | kwargs.setdefault("exp", "ATLAS") 63 | if text is not None: 64 | kwargs["text"] = text 65 | return label_base.exp_label(**kwargs) 66 | 67 | 68 | @deprecate(reason="Use `ax.set_xlabel(...)` directly instead.", warn_once=False) 69 | def set_xlabel(label, ax=None, *args, **kwargs): 70 | """ 71 | Set x label in ATLAS style (right aligned). 72 | 73 | Additional parameters are passed through to `ax.set_xlabel`. 74 | 75 | Parameters 76 | ---------- 77 | label : str 78 | Label (LaTeX permitted) 79 | ax : mpl.axes.Axes, optional 80 | Axes to set x label on 81 | """ 82 | ax = ax or mpl.pyplot.gca() 83 | # update settings 84 | kwargs.update({"ha": "right", "va": "top", "x": 1.0}) 85 | ax.set_xlabel(label, *args, **kwargs) 86 | 87 | 88 | @deprecate(reason="Use `ax.set_xlabel(...)` directly instead.", warn_once=False) 89 | def set_ylabel(label, ax=None, *args, **kwargs): 90 | """ 91 | Set y label in ATLAS style (top aligned). 92 | 93 | Additional parameters are passed through to ``ax.set_ylabel``. 94 | 95 | Parameters 96 | ---------- 97 | label : str 98 | Label (LaTeX permitted) 99 | ax : mpl.axes.Axes, optional 100 | Axes to set y label on 101 | """ 102 | ax = ax or mpl.pyplot.gca() 103 | kwargs.update({"ha": "right", "va": "bottom", "y": 1.0}) 104 | ax.set_ylabel(label, *args, **kwargs) 105 | -------------------------------------------------------------------------------- /new_docs/docs/gallery.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Gallery 5 | 6 | ## 1D Histogram Comparisons 7 | 8 | ::cards:: cols=4 9 | 10 | - title: 11 | content: Asymmetry comparison 12 | image: baseline/1d_comparison_asymmetry.png 13 | url: gallery/1d_comparison_asymmetry.md 14 | 15 | - title: 16 | content: Difference comparison 17 | image: baseline/1d_comparison_difference.png 18 | url: gallery/1d_comparison_difference.md 19 | 20 | - title: 21 | content: Efficiency comparison 22 | image: baseline/1d_comparison_efficiency.png 23 | url: gallery/1d_comparison_efficiency.md 24 | 25 | - title: 26 | content: Only efficiency comparison 27 | image: baseline/1d_comparison_only_efficiency.png 28 | url: gallery/1d_comparison_only_efficiency.md 29 | 30 | - title: 31 | content: Pull comparison 32 | image: baseline/1d_comparison_pull.png 33 | url: gallery/1d_comparison_pull.md 34 | 35 | - title: 36 | content: Ratio comparison 37 | image: baseline/1d_comparison_ratio.png 38 | url: gallery/1d_comparison_ratio.md 39 | 40 | - title: 41 | content: Relative difference comparison 42 | image: baseline/1d_comparison_relative_difference.png 43 | url: gallery/1d_comparison_relative_difference.md 44 | 45 | - title: 46 | content: Split ratio comparison 47 | image: baseline/1d_comparison_split_ratio.png 48 | url: gallery/1d_comparison_split_ratio.md 49 | 50 | ::/cards:: 51 | 52 | ## Model Comparisons 53 | 54 | ::cards:: cols=4 55 | 56 | - title: 57 | content: Model all comparisons 58 | image: baseline/model_all_comparisons.png 59 | url: gallery/model_all_comparisons.md 60 | 61 | - title: 62 | content: Model all comparisons no model uncertainty 63 | image: baseline/model_all_comparisons_no_model_unc.png 64 | url: gallery/model_all_comparisons_no_model_unc.md 65 | 66 | - title: 67 | content: Model examples pull 68 | image: baseline/model_examples_pull.png 69 | url: gallery/model_examples_pull.md 70 | 71 | - title: 72 | content: Model examples pull no model uncertainty 73 | image: baseline/model_examples_pull_no_model_unc.png 74 | url: gallery/model_examples_pull_no_model_unc.md 75 | 76 | - title: 77 | content: Model examples stacked 78 | image: baseline/model_examples_stacked.png 79 | url: gallery/model_examples_stacked.md 80 | 81 | - title: 82 | content: Model examples stacked unstacked 83 | image: baseline/model_examples_stacked_unstacked.png 84 | url: gallery/model_examples_stacked_unstacked.md 85 | 86 | - title: 87 | content: Model examples unstacked 88 | image: baseline/model_examples_unstacked.png 89 | url: gallery/model_examples_unstacked.md 90 | 91 | - title: 92 | content: Model with stacked and unstacked function components 93 | image: baseline/model_with_stacked_and_unstacked_function_components.png 94 | url: gallery/model_with_stacked_and_unstacked_function_components.md 95 | 96 | - title: 97 | content: Model with stacked and unstacked histograms components 98 | image: baseline/model_with_stacked_and_unstacked_histograms_components.png 99 | url: gallery/model_with_stacked_and_unstacked_histograms_components.md 100 | 101 | - title: 102 | content: Ratio data vs model with stacked and unstacked function components 103 | image: baseline/ratio_data_vs_model_with_stacked_and_unstacked_function_components.png 104 | url: gallery/ratio_data_vs_model_with_stacked_and_unstacked_function_components.md 105 | 106 | ::/cards:: 107 | -------------------------------------------------------------------------------- /tests/copilot/test_hist2dplot_cbarpos.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for hist2dplot cbarpos parameter with matplotlib >= 3.6. 3 | 4 | This test addresses the bug where using cbarpos="top" or cbarpos="bottom" 5 | causes an AttributeError due to matplotlib 3.6+ making groupers immutable. 6 | The fix uses ax.sharex() instead of the deprecated .join() method. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | import os 12 | 13 | os.environ["RUNNING_PYTEST"] = "true" 14 | 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | import pytest 18 | 19 | plt.switch_backend("Agg") 20 | 21 | import mplhep as mh 22 | 23 | 24 | @pytest.fixture 25 | def sample_2d_data(): 26 | """Create sample 2D histogram data.""" 27 | np.random.seed(42) 28 | xedges = np.linspace(0, 10, 51) 29 | yedges = np.linspace(0, 10, 51) 30 | H = np.random.normal(size=(50, 50)) 31 | return H, xedges, yedges 32 | 33 | 34 | def test_hist2dplot_cbarpos_top(sample_2d_data): 35 | """Test hist2dplot with cbarpos='top'.""" 36 | H, xedges, yedges = sample_2d_data 37 | fig, ax = plt.subplots() 38 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="top", ax=ax) 39 | plt.close(fig) 40 | 41 | 42 | def test_hist2dplot_cbarpos_bottom(sample_2d_data): 43 | """Test hist2dplot with cbarpos='bottom'.""" 44 | H, xedges, yedges = sample_2d_data 45 | fig, ax = plt.subplots() 46 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="bottom", ax=ax) 47 | plt.close(fig) 48 | 49 | 50 | def test_hist2dplot_cbarpos_left(sample_2d_data): 51 | """Test hist2dplot with cbarpos='left'.""" 52 | H, xedges, yedges = sample_2d_data 53 | fig, ax = plt.subplots() 54 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="left", ax=ax) 55 | plt.close(fig) 56 | 57 | 58 | def test_hist2dplot_cbarpos_right(sample_2d_data): 59 | """Test hist2dplot with cbarpos='right'.""" 60 | H, xedges, yedges = sample_2d_data 61 | fig, ax = plt.subplots() 62 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="right", ax=ax) 63 | plt.close(fig) 64 | 65 | 66 | def test_hist2dplot_cbarpos_all_positions(sample_2d_data): 67 | """Test hist2dplot with all cbarpos positions in one test.""" 68 | H, xedges, yedges = sample_2d_data 69 | positions = ["top", "bottom", "left", "right"] 70 | 71 | for pos in positions: 72 | fig, ax = plt.subplots() 73 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos=pos, ax=ax) 74 | plt.close(fig) 75 | 76 | 77 | @pytest.mark.mpl_image_compare(style="default", remove_text=True, tolerance=10) 78 | def test_hist2dplot_cbarpos_top_visual(): 79 | """Visual comparison test for cbarpos='top'.""" 80 | np.random.seed(42) 81 | xedges = np.linspace(0, 10, 21) 82 | yedges = np.linspace(0, 10, 21) 83 | x = np.random.normal(5, 2, 1000) 84 | y = np.random.normal(5, 2, 1000) 85 | H, _, _ = np.histogram2d(x, y, bins=(xedges, yedges)) 86 | 87 | fig, ax = plt.subplots(figsize=(8, 7)) 88 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="top", ax=ax) 89 | 90 | return fig 91 | 92 | 93 | @pytest.mark.mpl_image_compare(style="default", remove_text=True, tolerance=10) 94 | def test_hist2dplot_cbarpos_bottom_visual(): 95 | """Visual comparison test for cbarpos='bottom'.""" 96 | np.random.seed(42) 97 | xedges = np.linspace(0, 10, 21) 98 | yedges = np.linspace(0, 10, 21) 99 | x = np.random.normal(5, 2, 1000) 100 | y = np.random.normal(5, 2, 1000) 101 | H, _, _ = np.histogram2d(x, y, bins=(xedges, yedges)) 102 | 103 | fig, ax = plt.subplots(figsize=(8, 7)) 104 | mh.hist2dplot(H, xedges, yedges, cbar=True, cbarpos="bottom", ax=ax) 105 | 106 | return fig 107 | -------------------------------------------------------------------------------- /.github/workflows/ci_latex.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow for testing LaTeX-dependent styles 2 | # 3 | # This workflow tests matplotlib styles that require LaTeX to be installed: 4 | # - CMSTex, ROOTTex (CMS experiment) 5 | # - DUNETex, DUNETex1 (DUNE experiment) 6 | # - ATLASTex (ATLAS experiment) 7 | # - LHCbTex1, LHCbTex2 (LHCb experiment) 8 | # 9 | # These tests are separated from the main CI workflow because: 10 | # 1. LaTeX installation adds ~500MB and several minutes to CI time 11 | # 2. LaTeX rendering can be environment-dependent 12 | # 3. Most PRs don't affect LaTeX-dependent features 13 | # 14 | # The workflow runs: 15 | # - On pull requests that modify style files or LaTeX tests 16 | # - Weekly (every Monday) to catch regressions 17 | # - Manually via workflow_dispatch 18 | 19 | name: CI LaTeX 20 | 21 | on: 22 | workflow_dispatch: 23 | pull_request: 24 | paths: 25 | - 'src/mplhep/styles/**' 26 | - 'tests/test_styles_latex.py' 27 | - 'tests/test_dune.py' 28 | - '.github/workflows/ci_latex.yml' 29 | # Run weekly on Monday at 0:00 UTC 30 | schedule: 31 | - cron: '0 0 * * 1' 32 | 33 | concurrency: 34 | group: ${{ github.workflow }}-${{ github.ref }} 35 | cancel-in-progress: true 36 | 37 | jobs: 38 | 39 | test-latex: 40 | name: "LaTeX Tests • Python ${{ matrix.python-version }}" 41 | runs-on: ubuntu-latest 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | python-version: ['3.12'] 46 | 47 | steps: 48 | - uses: actions/checkout@v6 49 | 50 | - name: Setup uv 51 | uses: astral-sh/setup-uv@v7 52 | with: 53 | activate-environment: true 54 | version: 'latest' 55 | python-version: ${{ matrix.python-version }} 56 | enable-cache: true 57 | cache-dependency-glob: '**/pyproject.toml' 58 | 59 | - name: Install core fonts 60 | run: | 61 | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections 62 | sudo apt-get install ttf-mscorefonts-installer 63 | 64 | - name: Install LaTeX packages 65 | run: | 66 | sudo apt-get update 67 | sudo apt-get install -y texlive-latex-extra texlive-fonts-recommended texlive-science tex-gyre dvipng cm-super 68 | 69 | - name: Install package 70 | run: | 71 | uv pip install -e '.[all]' 72 | uv pip install pytest-github-actions-annotate-failures 73 | uv pip install pytest-xdist 74 | uv pip list 75 | 76 | - name: Verify LaTeX installation 77 | run: | 78 | which latex 79 | latex --version 80 | 81 | - name: Test LaTeX styles with pytest 82 | run: | 83 | python -m pytest tests/test_styles_latex.py -m latex -r sa --mpl --mpl-results-path=pytest_results_latex -n 4 --benchmark-disable 84 | 85 | - name: Test DUNE LaTeX styles with pytest 86 | run: | 87 | python -m pytest tests/test_dune.py::test_style_dunetex -m latex -r sa --mpl --mpl-results-path=pytest_results_dune_tex 88 | python -m pytest tests/test_dune.py::test_style_dunetex1 -m latex -r sa --mpl --mpl-results-path=pytest_results_dune_tex 89 | python -m pytest tests/test_dune.py::test_dune_style_string_aliases -m latex -r sa --mpl --mpl-results-path=pytest_results_dune_tex 90 | 91 | - name: Upload pytest test results 92 | uses: actions/upload-artifact@v5 93 | if: failure() 94 | with: 95 | name: pytest_results_latex-${{ matrix.python-version }} 96 | retention-days: 7 97 | path: | 98 | pytest_results_latex 99 | pytest_results_dune_tex 100 | -------------------------------------------------------------------------------- /src/mplhep/styles/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Styling module. 3 | 4 | This module provides matplotlib stylesheets for various HEP experiments. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import sys 10 | 11 | from matplotlib.pyplot import style as plt_style 12 | 13 | import mplhep._deprecate as deprecate 14 | 15 | # Short cut to all styles 16 | from .alice import ALICE 17 | from .atlas import ATLAS, ATLAS1, ATLAS2, ATLASAlt, ATLASTex 18 | from .cms import CMS, ROOT, CMSTex, ROOTTex 19 | from .dune import DUNE, DUNE1, DUNETex, DUNETex1 20 | from .lhcb import LHCb, LHCb1, LHCb2, LHCbTex, LHCbTex1, LHCbTex2 21 | from .plothist import plothist 22 | 23 | __all__ = ( 24 | "ALICE", 25 | "ATLAS", 26 | "CMS", 27 | "plothist", 28 | "ROOT", 29 | "ATLAS1", 30 | "ATLAS2", 31 | "ATLASAlt", 32 | "ATLASTex", 33 | "CMSTex", 34 | "DUNE1", 35 | "DUNETex1", 36 | "DUNE", 37 | "DUNETex", 38 | "LHCb", 39 | "LHCb1", 40 | "LHCb2", 41 | "LHCbTex", 42 | "LHCbTex1", 43 | "LHCbTex2", 44 | "ROOTTex", 45 | "fabiola", 46 | "fira", 47 | "firamath", 48 | "set_style", 49 | "use", 50 | ) 51 | 52 | 53 | __style_aliases__ = ( 54 | "ALICE", 55 | "ATLAS", 56 | "CMS", 57 | "plothist", 58 | "ROOT", 59 | "ATLAS1", 60 | "ATLAS2", 61 | "ATLASAlt", 62 | "ATLASTex", 63 | "CMSTex", 64 | "DUNE1", 65 | "DUNETex1", 66 | "DUNE", 67 | "DUNETex", 68 | "LHCb", 69 | "LHCb1", 70 | "LHCb2", 71 | "LHCbTex", 72 | "LHCbTex1", 73 | "LHCbTex2", 74 | "ROOTTex", 75 | "fabiola", 76 | "fira", 77 | "firamath", 78 | ) 79 | 80 | 81 | @deprecate.deprecate( 82 | "Naming convention is changing to match mpl. Use ``mplhep.style.use()``." 83 | ) 84 | def set_style(styles=None): 85 | use(styles) 86 | 87 | 88 | def use(styles=None): 89 | """ 90 | Set the experiment specific plotting style 91 | 92 | Example: 93 | 94 | >>> import mplhep as mh 95 | >>> mh.style.use("ATLAS") 96 | >>> mh.style.use(mh.style.CMS) 97 | 98 | Parameters 99 | ---------- 100 | styles : str or mplhep.style or dict or None 101 | The experiment style. Will understand a dictionary 102 | of rcParams, a mplhep style or its string alias. 103 | Pass ``None`` to reset to mpl defaults. 104 | """ 105 | 106 | if styles is None: 107 | return plt_style.use("default") 108 | if not isinstance(styles, list): 109 | styles = [styles] 110 | 111 | # passed in experiment mplhep.style dict or str alias 112 | _passed_aliases = [style for style in styles if not isinstance(style, dict)] 113 | if len(_passed_aliases) > 1: 114 | error_msg = ( 115 | 'Can only pass in one style alias at a time, but can modify settings eg. `use(["CMS", {"font.size":25}])`. ' 116 | f"Got {', '.join(_passed_aliases)}" 117 | ) 118 | raise ValueError(error_msg) 119 | if ( 120 | len(_passed_aliases) == 1 121 | and _passed_aliases[0] not in sys.modules[__name__].__dict__ 122 | ): 123 | error_msg = f"Unknown style alias: {_passed_aliases[0]}. Choose from {list(__style_aliases__)}" 124 | raise ValueError(error_msg) 125 | styles = [ 126 | style if isinstance(style, dict) else getattr(sys.modules[__name__], f"{style}") 127 | for style in styles 128 | ] 129 | 130 | plt_style.use(styles) 131 | return None 132 | 133 | 134 | fira = {"font.sans-serif": "Fira Sans"} 135 | 136 | firamath = { 137 | "mathtext.fontset": "custom", 138 | "mathtext.rm": "Fira Math:regular", 139 | "mathtext.bf": "Fira Math:medium", 140 | "mathtext.sf": "Fira Math", 141 | "mathtext.it": "Fira Math:regular:italic", 142 | "mathtext.tt": "Fira Mono", 143 | } 144 | 145 | fabiola = { 146 | "font.sans-serif": "Comic Sans MS", 147 | } 148 | -------------------------------------------------------------------------------- /tests/test_styles_latex.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | 6 | import matplotlib.pyplot as plt 7 | import pytest 8 | from matplotlib.testing.decorators import check_figures_equal 9 | 10 | os.environ["RUNNING_PYTEST"] = "true" 11 | 12 | import mplhep as mh 13 | 14 | """ 15 | Tests for LaTeX-dependent styles (CMSTex, DUNETex, ATLASTex, LHCbTex, etc.) 16 | 17 | To test run: 18 | pytest tests/test_styles_latex.py --mpl 19 | 20 | When adding new tests, run: 21 | pytest tests/test_styles_latex.py --mpl-generate-path=tests/baseline 22 | """ 23 | 24 | plt.switch_backend("Agg") 25 | 26 | pytestmark = [ 27 | pytest.mark.latex, 28 | pytest.mark.skipif(sys.platform != "linux", reason="Linux only"), 29 | ] 30 | 31 | 32 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 33 | def test_style_cmstex(): 34 | plt.rcParams.update(plt.rcParamsDefault) 35 | 36 | plt.style.use(mh.style.CMSTex) 37 | fig, ax = plt.subplots() 38 | mh.cms.label("Preliminary") 39 | 40 | return fig 41 | 42 | 43 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 44 | def test_style_roottex(): 45 | plt.rcParams.update(plt.rcParamsDefault) 46 | 47 | plt.style.use(mh.style.ROOTTex) 48 | fig, ax = plt.subplots() 49 | ax.set_xlabel(r"$p_T$ [GeV]") 50 | ax.set_ylabel(r"Events / 10 GeV") 51 | 52 | return fig 53 | 54 | 55 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 56 | def test_style_dunetex(): 57 | plt.rcParams.update(plt.rcParamsDefault) 58 | 59 | plt.style.use(mh.style.DUNETex) 60 | fig, ax = plt.subplots() 61 | mh.dune.label(text="Preliminary") 62 | 63 | return fig 64 | 65 | 66 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 67 | def test_style_dunetex1(): 68 | plt.rcParams.update(plt.rcParamsDefault) 69 | 70 | plt.style.use(mh.style.DUNETex1) 71 | fig, ax = plt.subplots() 72 | mh.dune.label(text="Preliminary") 73 | 74 | return fig 75 | 76 | 77 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 78 | def test_style_atlastex(): 79 | plt.rcParams.update(plt.rcParamsDefault) 80 | 81 | plt.style.use(mh.style.ATLASTex) 82 | fig, ax = plt.subplots() 83 | mh.atlas.label(text="Preliminary") 84 | 85 | return fig 86 | 87 | 88 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 89 | def test_style_lhcbtex1(): 90 | plt.rcParams.update(plt.rcParamsDefault) 91 | 92 | plt.style.use([mh.style.LHCbTex1, {"figure.autolayout": False}]) 93 | fig, ax = plt.subplots() 94 | mh.lhcb.label("Preliminary") 95 | 96 | return fig 97 | 98 | 99 | @pytest.mark.mpl_image_compare(style="default", remove_text=False) 100 | def test_style_lhcbtex2(): 101 | plt.rcParams.update(plt.rcParamsDefault) 102 | 103 | plt.style.use([mh.style.LHCbTex2, {"figure.autolayout": False}]) 104 | fig, ax = plt.subplots() 105 | mh.lhcb.label("Preliminary") 106 | 107 | return fig 108 | 109 | 110 | @check_figures_equal(extensions=["pdf"]) 111 | @pytest.mark.parametrize( 112 | ("mplhep_style", "str_alias"), 113 | [ 114 | (mh.style.CMSTex, "CMSTex"), 115 | (mh.style.ROOTTex, "ROOTTex"), 116 | (mh.style.DUNETex, "DUNETex"), 117 | (mh.style.DUNETex1, "DUNETex1"), 118 | (mh.style.ATLASTex, "ATLASTex"), 119 | (mh.style.LHCbTex1, "LHCbTex1"), 120 | (mh.style.LHCbTex2, "LHCbTex2"), 121 | ], 122 | ids=[ 123 | "CMSTex", 124 | "ROOTTex", 125 | "DUNETex", 126 | "DUNETex1", 127 | "ATLASTex", 128 | "LHCbTex1", 129 | "LHCbTex2", 130 | ], 131 | ) 132 | def test_latex_style_str_alias(fig_test, fig_ref, mplhep_style, str_alias): 133 | """Test that string aliases work for all LaTeX style variants.""" 134 | plt.rcParams.update(plt.rcParamsDefault) 135 | 136 | mh.rcParams.clear() 137 | plt.style.use(mplhep_style) 138 | fig_ref.subplots() 139 | 140 | mh.rcParams.clear() 141 | mh.style.use(str_alias) 142 | fig_test.subplots() 143 | -------------------------------------------------------------------------------- /tests/test_mock.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from types import SimpleNamespace 4 | 5 | import matplotlib.lines 6 | import matplotlib.pyplot # noqa: ICN001 7 | import numpy as np 8 | import pytest 9 | from pytest import approx 10 | 11 | import mplhep as mh 12 | 13 | 14 | @pytest.fixture 15 | def mock_matplotlib(mocker): 16 | fig = mocker.Mock(spec=matplotlib.pyplot.Figure) 17 | ax = mocker.Mock(spec=matplotlib.pyplot.Axes) 18 | ax.figure = mocker.MagicMock(spec=matplotlib.pyplot.Figure) 19 | ax.figure.dpi_scale_trans = mocker.MagicMock() 20 | ax.get_window_extent.return_value = mocker.MagicMock() 21 | line2d = mocker.Mock(name="step", spec=matplotlib.lines.Line2D) 22 | line2d.get_color.return_value = "current-color" 23 | # errorbar_cont = mocker.Mock(name="err_cont", spec=matplotlib.container.ErrorbarContainer) 24 | # errorbar_cont.sticky_edges = mocker.Mock(name="edges") 25 | ax.step.return_value = (line2d,) 26 | ax.plot.return_value = (line2d,) 27 | ax.errorbar.return_value = (line2d,) 28 | 29 | # Mock the _get_lines attribute 30 | _get_lines = mocker.Mock() 31 | _get_lines.get_next_color.return_value = "next-color" 32 | ax._get_lines = _get_lines 33 | 34 | mpl = mocker.patch("matplotlib.pyplot", autospec=True) 35 | mocker.patch("matplotlib.pyplot.subplots", return_value=(fig, ax)) 36 | 37 | return SimpleNamespace(fig=fig, ax=ax, line2d=line2d, mpl=mpl) 38 | 39 | 40 | def test_simple(mock_matplotlib): 41 | ax = mock_matplotlib.ax 42 | 43 | h = [1, 3, 2] 44 | bins = [0, 1, 2, 3] 45 | mh.histplot(h, bins, yerr=True, label="X", ax=ax) 46 | 47 | assert len(ax.mock_calls) == 13 48 | 49 | ax.stairs.assert_called_once_with( 50 | values=approx([1.0, 3.0, 2.0]), 51 | edges=approx([0.0, 1.0, 2.0, 3.0]), 52 | baseline=0, 53 | label=None, 54 | linewidth=1.5, 55 | color="next-color", 56 | ) 57 | 58 | assert ax.errorbar.call_count == 2 59 | ax.errorbar.assert_any_call( 60 | approx([]), 61 | approx([]), 62 | yerr=1, 63 | xerr=None, 64 | linestyle="-", 65 | color=ax.stairs().get_edgecolor(), 66 | label="X", 67 | ) 68 | 69 | ax.errorbar.assert_any_call( 70 | x=approx([0.5, 1.5, 2.5]), 71 | y=approx([1, 3, 2]), 72 | yerr=[ 73 | approx([0.82724622, 1.63270469, 1.29181456]), 74 | approx([2.29952656, 2.91818583, 2.63785962]), 75 | ], 76 | color="next-color", 77 | linestyle="none", 78 | linewidth=1.5, 79 | ) 80 | 81 | 82 | def test_histplot_real(mock_matplotlib): 83 | np.random.seed(0) 84 | h, bins = np.histogram(np.random.normal(10, 3, 1000), bins=10) 85 | 86 | ax = mock_matplotlib.ax 87 | a, b, c = h, h * 2, np.random.poisson(h * 3) 88 | 89 | mh.histplot([a, b, c], bins=bins, ax=ax, yerr=True, label=["MC1", "MC2", "Data"]) 90 | ax.legend() 91 | ax.set_title("Raw") 92 | assert len(ax.mock_calls) == 27 93 | 94 | ax.reset_mock() 95 | 96 | mh.histplot([a, b], bins=bins, ax=ax, stack=True, label=["MC1", "MC2"]) 97 | mh.histplot([c], bins=bins, ax=ax, yerr=True, histtype="errorbar", label="Data") 98 | ax.legend() 99 | ax.set_title("Data/MC") 100 | assert len(ax.mock_calls) == 20 101 | ax.reset_mock() 102 | 103 | mh.histplot( 104 | [a, b], bins=bins, ax=ax, stack=True, label=["MC1", "MC2"], binwnorm=[2, 1] 105 | ) 106 | mh.histplot( 107 | c, 108 | bins=bins, 109 | ax=ax, 110 | yerr=True, 111 | histtype="errorbar", 112 | label="Data", 113 | binwnorm=1, 114 | ) 115 | ax.legend() 116 | ax.set_title("Data/MC binwnorm") 117 | assert len(ax.mock_calls) == 20 118 | ax.reset_mock() 119 | 120 | mh.histplot( 121 | [a, b], bins=bins, ax=ax, stack=True, label=["MC1", "MC2"], density=True 122 | ) 123 | mh.histplot( 124 | c, 125 | bins=bins, 126 | ax=ax, 127 | yerr=True, 128 | histtype="errorbar", 129 | label="Data", 130 | density=True, 131 | ) 132 | ax.legend() 133 | ax.set_title("Data/MC Density") 134 | assert len(ax.mock_calls) == 20 135 | -------------------------------------------------------------------------------- /examples/model_ex/model_all_comparisons_no_model_unc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data/model comparisons, no model uncertainty 3 | ============================================ 4 | 5 | All supported comparisons between data and model, without model uncertainty. 6 | """ 7 | 8 | # --8<-- [start:full_code] 9 | # --8<-- [start:imports] 10 | import hist 11 | import numpy as np 12 | import seaborn as sns 13 | 14 | import mplhep as mh 15 | 16 | # --8<-- [end:imports] 17 | 18 | # --8<-- [start:setup] 19 | # Set seed for reproducible demo 20 | np.random.seed(42) 21 | 22 | # Create demo histograms directly 23 | bins = np.linspace(-9, 12, 51) 24 | 25 | # Background components with different shapes 26 | bkg1_data = np.random.normal(0, 2.5, 4000) # Broad background 27 | bkg2_data = np.random.normal(3, 1.2, 2000) # Narrower peak 28 | bkg3_data = np.random.normal(-1, 1.8, 1500) # Another component 29 | 30 | # Data = backgrounds + signal peak + some deficit 31 | data_data = np.concatenate( 32 | [ 33 | np.random.normal(0, 2.5, 3500), # Less background here 34 | np.random.normal(3, 1.2, 1800), # Similar background 35 | np.random.normal(-1, 1.8, 1400), # Similar background 36 | np.random.normal(0, 0.8, 500), # Clear signal peak 37 | np.random.normal(-3, 0.5, 200), # Some deficit here (under-predicted) 38 | ] 39 | ) 40 | 41 | # Create histograms 42 | data_hist = hist.new.Regular(50, -8, 8).Weight().fill(data_data) 43 | 44 | background_hists = [ 45 | hist.new.Regular(50, -8, 8).Weight().fill(bkg1_data), 46 | hist.new.Regular(50, -8, 8).Weight().fill(bkg2_data), 47 | hist.new.Regular(50, -8, 8).Weight().fill(bkg3_data), 48 | ] 49 | 50 | # Scale backgrounds to match data 51 | total_bkg = sum(background_hists).sum().value 52 | data_total = data_hist.sum().value 53 | scale_factor = data_total / total_bkg 54 | background_hists = [scale_factor * h for h in background_hists] 55 | 56 | # Labels and colors 57 | background_labels = ["Broad BG", "Peak BG", "Offset BG"] 58 | background_colors = sns.color_palette("cubehelix", 3) 59 | 60 | # Scale backgrounds to match data 61 | total_bkg = sum(background_hists).sum().value 62 | data_total = data_hist.sum().value 63 | scale_factor = data_total / total_bkg 64 | background_hists = [scale_factor * h for h in background_hists] 65 | 66 | # Labels and colors 67 | background_labels = ["Background 1", "Background 2", "Background 3"] 68 | background_colors = sns.color_palette("cubehelix", 3) 69 | # --8<-- [end:setup] 70 | 71 | # --8<-- [start:plot_body] 72 | # Create comparison plots 73 | fig, axes = mh.subplots(nrows=6, hspace=0.3) 74 | 75 | background_sum = sum(background_hists) 76 | 77 | mh.comp.data_model( 78 | data_hist=data_hist, 79 | stacked_components=background_hists, 80 | stacked_labels=background_labels, 81 | stacked_colors=background_colors, 82 | xlabel="", 83 | ylabel="Entries", 84 | model_uncertainty=False, # <-- 85 | comparison="ratio", 86 | fig=fig, 87 | ax_main=axes[0], 88 | ax_comparison=axes[1], 89 | ) 90 | 91 | mh.add_text( 92 | r"Multiple data-model comparisons, $\mathbf{without}$ model uncertainty", 93 | ax=axes[0], 94 | loc="over left", 95 | fontsize="small", 96 | ) 97 | mh.add_text( 98 | r' $\mathbf{→}$ comparison = "ratio"', ax=axes[1], loc="over left", fontsize=13 99 | ) 100 | 101 | for k_comp, comp in enumerate( 102 | ["split_ratio", "pull", "relative_difference", "difference"], start=2 103 | ): 104 | ax_comparison = axes[k_comp] 105 | 106 | # Copy the original histogram and set the uncertainties of the copy to 0. 107 | background_sum_copy = background_sum.copy() 108 | background_sum_copy[:] = np.c_[ 109 | background_sum_copy.values(), np.zeros_like(background_sum_copy.values()) 110 | ] 111 | 112 | mh.comp.comparison( 113 | data_hist, 114 | background_sum_copy, 115 | ax=ax_comparison, 116 | comparison=comp, 117 | xlabel="", 118 | h1_label="Data", 119 | h2_label="MC", 120 | h1_w2method="poisson", 121 | ) 122 | if comp == "pull": 123 | # Since the uncertainties of the model are neglected, the pull label is "(Data - MC)/sigma_Data" 124 | ax_comparison.set_ylabel(r"$\frac{Data-MC}{\sigma_{Data}}$") 125 | mh.add_text( 126 | rf' $\mathbf{{→}}$ comparison = "{comp}"', 127 | ax=ax_comparison, 128 | loc="over left", 129 | fontsize=13, 130 | ) 131 | mh.set_fitting_ylabel_fontsize(ax_comparison) 132 | 133 | axes[-1].set_xlabel("Observable") 134 | # --8<-- [end:plot_body] 135 | 136 | # --8<-- [end:full_code] 137 | fig.savefig("model_all_comparisons_no_model_unc.svg", bbox_inches="tight") 138 | -------------------------------------------------------------------------------- /docs/source/gallery/styles.rst: -------------------------------------------------------------------------------- 1 | .. _gallery-styles: 2 | 3 | 4 | Styles 5 | =========== 6 | 7 | This page provides a gallery of available styles in mplhep. The styles are 8 | sorted by the experiments they are associated with. 9 | 10 | Styles can be used by calling :py:func:`mplhep.style.use(style)` with ``style`` 11 | on of the available styles in :py:mod:`mplhep.style`. In this gallery, only the most used 12 | and actively maintained styles are shown. 13 | 14 | The following plots are generated using :py:func:`~mplhep.histplot` and :py:func:`plt.plot(...)` 15 | to plot the fit of a Gaussian distribution (for the signal peak) and an exponential 16 | distribution (for the background) on top of a histogram. 17 | 18 | All plots have additionally a legend, axis labels, and a title as well as 19 | 20 | - the text "Preliminary" at position 0 (see :ref:`gallery-labels` for more positions), using the experiment specific function if available (or none otherwise), 21 | - the year 2016 22 | - a luminosity of 9 :math:`fb^{-1}` 23 | - ``data=True`` to show that it's not simulation 24 | 25 | to illustrate the visual appearance of the styles. 26 | 27 | ATLAS 28 | ------------ 29 | 30 | ATLAS has two `recommended styles `__. 31 | The main recommendation, ``ATLAS`` (or ``ATLAS2``, based on `this work `__) provides 7 colors, 32 | with Vermilion, the first color in the palette, recommended for signal. In the case of large signals, white can also be used. 33 | 34 | .. image:: ../../_static/_generated/ATLAS2/fill/pos0.png 35 | :width: 45% 36 | 37 | .. image:: ../../_static/_generated/ATLAS2/step/pos0.png 38 | :width: 45% 39 | 40 | .. image:: ../../_static/_generated/ATLAS2/errorbar/pos0.png 41 | :width: 45% 42 | 43 | .. image:: ../../_static/_generated/ATLAS2/band/pos0.png 44 | :width: 45% 45 | 46 | For plots that require large numbers of colors, the ``ATLAS1`` palette is provided with 10 colors `based on this paper `__. 47 | 48 | .. image:: ../../_static/_generated/ATLAS1/fill/pos0.png 49 | :width: 45% 50 | 51 | .. image:: ../../_static/_generated/ATLAS1/step/pos0.png 52 | :width: 45% 53 | 54 | .. image:: ../../_static/_generated/ATLAS1/errorbar/pos0.png 55 | :width: 45% 56 | 57 | .. image:: ../../_static/_generated/ATLAS1/band/pos0.png 58 | :width: 45% 59 | 60 | CMS 61 | ------------ 62 | 63 | .. image:: ../_static/_generated/CMS/fill/pos0.png 64 | :width: 45% 65 | 66 | .. image:: ../_static/_generated/CMS/step/pos0.png 67 | :width: 45% 68 | 69 | .. image:: ../_static/_generated/CMS/errorbar/pos0.png 70 | :width: 45% 71 | 72 | .. image:: ../_static/_generated/CMS/band/pos0.png 73 | :width: 45% 74 | 75 | LHCb 76 | ------------ 77 | 78 | LHCb has two styles, the older one, :py:obj:`~mplhep.style.LHCb1`, and the newer one, 79 | :py:obj:`~mplhep.style.LHCb2`. 80 | 81 | 82 | LHCb1 style (old) 83 | 84 | .. image:: ../_static/_generated/LHCb1/fill/pos0.png 85 | :width: 45% 86 | 87 | .. image:: ../_static/_generated/LHCb1/step/pos0.png 88 | :width: 45% 89 | 90 | .. image:: ../_static/_generated/LHCb1/errorbar/pos0.png 91 | :width: 45% 92 | 93 | .. image:: ../_static/_generated/LHCb1/band/pos0.png 94 | :width: 45% 95 | 96 | LHCb2 style 97 | 98 | .. image:: ../_static/_generated/LHCb2/fill/pos0.png 99 | :width: 45% 100 | 101 | .. image:: ../_static/_generated/LHCb2/step/pos0.png 102 | :width: 45% 103 | 104 | .. image:: ../_static/_generated/LHCb2/errorbar/pos0.png 105 | :width: 45% 106 | 107 | .. image:: ../_static/_generated/LHCb2/band/pos0.png 108 | :width: 45% 109 | 110 | 111 | ALICE 112 | ------------ 113 | 114 | ALICE style 115 | 116 | .. image:: ../_static/_generated/ALICE/fill/pos0.png 117 | :width: 45% 118 | 119 | .. image:: ../_static/_generated/ALICE/step/pos0.png 120 | :width: 45% 121 | 122 | .. image:: ../_static/_generated/ALICE/errorbar/pos0.png 123 | :width: 45% 124 | 125 | .. image:: ../_static/_generated/ALICE/band/pos0.png 126 | :width: 45% 127 | 128 | DUNE 129 | ------------ 130 | 131 | DUNE neutrino experiment style. DUNE points to the newest style, currently only DUNE1 is available. 132 | If you want to use the newest style, in case of updates, use :py:obj:`~mplhep.style.DUNE`, if you want to make sure that backwards compatibility is kept, use :py:obj:`~mplhep.style.DUNE1`. 133 | 134 | .. image:: ../_static/_generated/DUNE1/fill/pos0.png 135 | :width: 45% 136 | 137 | .. image:: ../_static/_generated/DUNE1/step/pos0.png 138 | :width: 45% 139 | 140 | .. image:: ../_static/_generated/DUNE1/errorbar/pos0.png 141 | :width: 45% 142 | 143 | .. image:: ../_static/_generated/DUNE1/band/pos0.png 144 | :width: 45% 145 | -------------------------------------------------------------------------------- /src/mplhep/styles/dune.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import copy 4 | 5 | import matplotlib as mpl 6 | from cycler import cycler 7 | 8 | # DUNE style based on dune_plot_style package 9 | DUNE1 = { 10 | # Font configuration 11 | "font.sans-serif": [ 12 | "TeX Gyre Heros", # Better, free Helvetica-like font with math support 13 | "Helvetica", 14 | "Helvetica Neue", 15 | "Nimbus Sans L", 16 | "Liberation Sans", 17 | "Arial", 18 | "FreeSans", 19 | ], 20 | "font.family": "sans-serif", 21 | "mathtext.fontset": "custom", 22 | "mathtext.rm": "TeX Gyre Heros", 23 | "mathtext.bf": "TeX Gyre Heros:bold", 24 | "mathtext.sf": "TeX Gyre Heros", 25 | "mathtext.it": "TeX Gyre Heros:italic", 26 | "mathtext.tt": "TeX Gyre Heros", 27 | "mathtext.cal": "TeX Gyre Heros", 28 | "mathtext.default": "regular", 29 | # Figure configuration 30 | "figure.figsize": (10, 10), 31 | "figure.facecolor": "white", 32 | "figure.dpi": 100, 33 | "figure.autolayout": True, 34 | # Text properties 35 | "text.hinting_factor": 8, 36 | "font.size": 22, 37 | # Axes properties 38 | "axes.facecolor": "white", 39 | "axes.edgecolor": "black", 40 | "axes.grid": False, 41 | "axes.linewidth": 1.5, 42 | "axes.labelsize": "medium", 43 | "axes.titlesize": 36, 44 | # "axes.labelpad": 8, # Better spacing between labels and axes 45 | # "axes.formatter.limits": "-3, 4", # Better scientific notation limits 46 | "axes.formatter.use_mathtext": True, 47 | "axes.unicode_minus": False, # Use ASCII minus for better compatibility 48 | "axes.xmargin": 0.0, # Small margin for better plot bounds 49 | # "axes.ymargin": 0.0, 50 | # Enhanced color cycle - DUNE logo colors first, then accessibility-optimized sequence 51 | "axes.prop_cycle": cycler( 52 | "color", 53 | [ 54 | "#000000", # Black 55 | "#D55E00", # DUNE Orange (primary) 56 | "#56B4E9", # DUNE Sky Blue (primary) 57 | "#E69F00", # DUNE Yellow/Gold (primary) 58 | "#009E73", # Green (accessible) 59 | "#CC79A7", # Pink (accessible) 60 | "#0072B2", # Blue (accessible) 61 | "#F0E442", # Bright Yellow (accessible) 62 | ], 63 | ), 64 | # Line properties 65 | "lines.linewidth": 2.0, 66 | "lines.markersize": 8, 67 | # Patch properties (histograms) 68 | "patch.linewidth": 1.5, 69 | "patch.facecolor": "blue", 70 | "patch.edgecolor": "black", 71 | "patch.antialiased": True, 72 | # Image properties - Color Vision Deficiency friendly 73 | "image.cmap": "cividis", 74 | "image.aspect": "auto", 75 | # Grid properties - Improved styling when enabled 76 | "grid.color": "#b2b2b2", 77 | "grid.linestyle": ":", # Dotted style for less visual interference 78 | "grid.linewidth": 0.5, 79 | "grid.alpha": 0.8, 80 | # Legend properties 81 | "legend.fontsize": 12, 82 | "legend.title_fontsize": "large", 83 | "legend.frameon": False, 84 | "legend.handlelength": 2.0, 85 | "legend.borderpad": 0.8, 86 | "legend.columnspacing": 1.0, 87 | "legend.labelspacing": 0.5, 88 | # Automatically choose the best location 89 | "legend.loc": "best", 90 | # Enhanced tick properties 91 | "xtick.color": "black", 92 | "xtick.direction": "in", 93 | "xtick.labelsize": "small", 94 | "xtick.major.size": 10, 95 | "xtick.minor.size": 5, 96 | "xtick.major.pad": 6, 97 | "xtick.minor.visible": True, 98 | "xtick.top": True, 99 | "xtick.bottom": True, 100 | "xtick.major.top": True, 101 | "xtick.major.bottom": True, 102 | "xtick.minor.top": True, 103 | "xtick.minor.bottom": True, 104 | "ytick.color": "black", 105 | "ytick.direction": "in", 106 | "ytick.labelsize": "small", 107 | "ytick.major.size": 10, 108 | "ytick.minor.size": 5, 109 | "ytick.major.pad": 6, 110 | "ytick.minor.visible": True, 111 | "ytick.right": True, 112 | "ytick.left": True, 113 | "ytick.major.left": True, 114 | "ytick.major.right": True, 115 | "ytick.minor.left": True, 116 | "ytick.minor.right": True, 117 | # Enhanced axis label positioning (like other HEP experiments) 118 | "xaxis.labellocation": "right", 119 | "yaxis.labellocation": "top", 120 | # Save figure properties 121 | "savefig.transparent": False, 122 | "savefig.bbox": "tight", 123 | # "savefig.pad_inches": 0.1, 124 | } 125 | 126 | # Filter extra items if needed 127 | DUNE1 = {k: v for k, v in DUNE1.items() if k in mpl.rcParams} 128 | 129 | # Add a tex variant 130 | DUNETex1 = { 131 | **DUNE1, 132 | "text.usetex": True, 133 | "text.latex.preamble": "\n".join( 134 | [ 135 | r"\usepackage[LGR,T1]{fontenc}", 136 | r"\usepackage{tgheros}", 137 | r"\renewcommand{\familydefault}{\sfdefault}", 138 | r"\renewcommand{\rmdefault}{\sfdefault}", 139 | r"\usepackage{amsmath}", 140 | r"\usepackage[symbolgreek,symbolmax]{mathastext}", 141 | r"\usepackage{physics}", 142 | r"\usepackage{siunitx}", 143 | r"\setlength{\parindent}{0pt}", 144 | r"\def\mathdefault{}", 145 | ] 146 | ), 147 | } 148 | 149 | # moving targets 150 | DUNE = copy.deepcopy(DUNE1) 151 | DUNETex = copy.deepcopy(DUNETex1) 152 | --------------------------------------------------------------------------------