├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------