diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7b7357a..fda2756c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.0.1 hooks: - id: check-added-large-files - id: check-case-conflict @@ -23,7 +23,7 @@ repos: args: ["--include-version-classifiers", "--max-py-version=3.11"] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort args: ["-a", "from __future__ import annotations"] diff --git a/src/mplhep/plot.py b/src/mplhep/plot.py index 60695ed5..3b48ec57 100644 --- a/src/mplhep/plot.py +++ b/src/mplhep/plot.py @@ -7,10 +7,11 @@ import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np +from matplotlib import patches from matplotlib.offsetbox import AnchoredText from matplotlib.transforms import Bbox from mpl_toolkits.axes_grid1 import axes_size, make_axes_locatable -import scipy.stats + from .utils import ( Plottable, get_histogram_axes_title, @@ -131,7 +132,7 @@ def histplot( Attempts to draw x-axis ticks coinciding with bin boundaries if feasible. ax : matplotlib.axes.Axes, optional Axes object (if None, last one is fetched or one is created) - flow : str, optional {None, "show", "sum"} + flow : str, optional {None, "show", "sum", "hint"} Whether plot the under/overflow bin. If "show", add additional under/overflow bin. If "sum", add the under/overflow bin content to first/last bin. **kwargs : Keyword arguments passed to underlying matplotlib functions - @@ -153,6 +154,10 @@ def histplot( _err_message = f"Select 'histtype' from: {_allowed_histtype}" assert histtype in _allowed_histtype, _err_message + # hist overflow check + if flow is not None and "Hist" not in str(type(H)): + raise TypeError(f"{flow} option only works for Hist object") + # Convert 1/0 etc to real bools stack = bool(stack) density = bool(density) @@ -181,9 +186,9 @@ def histplot( plottables = [] final_bins = np.array( [ - final_bins[0] - (final_bins[1] - final_bins[0]) * 5, + final_bins[0] - (final_bins[1] - final_bins[0]) * 2, *final_bins, - final_bins[-1] + (final_bins[1] - final_bins[0]) * 5, + final_bins[-1] + (final_bins[1] - final_bins[0]) * 2, ] ) for h in hists: @@ -429,6 +434,7 @@ def iterable_not_string(arg): # Add sticky edges for autoscale _artist.sticky_edges.y.append(0) + if xtick_labels is None: if binticks: _slice = int(round(float(len(final_bins)) / len(ax.get_xticks()))) + 1 @@ -449,25 +455,9 @@ def iterable_not_string(arg): underflow = underflow + h.view(flow=True)["value"][0] overflow = overflow + h.view(flow=True)["value"][-1] if underflow > 0.0: - ax.text( - 0.85, - 0.65, - "underflow", - horizontalalignment="center", - verticalalignment="center", - transform=ax.transAxes, - ) - print("underflow bin:", underflow) + ax.axvspan(final_bins[0], final_bins[1], facecolor="red", alpha=0.5) if overflow > 0.0: - ax.text( - 0.85, - 0.5, - "overflow", - horizontalalignment="center", - verticalalignment="center", - transform=ax.transAxes, - ) - print("overflow bin:", overflow) + ax.axvspan(final_bins[-2], final_bins[-1], facecolor="red", alpha=0.5) return return_artists @@ -526,8 +516,8 @@ def hist2dplot( Colorbar maximum. ax : matplotlib.axes.Axes, optional Axes object (if None, last one is fetched or one is created) - flow : str, optional {None, "show", "sum"} - Whether plot the under/overflow bin. If "show", add additional under/overflow bin. If "sum", add the under/overflow bin content to first/last bin. + flow : str, optional {None, "show", "sum","hint"} + Whether plot the under/overflow bin. If "show", add additional under/overflow bin. If "sum", add the under/overflow bin content to first/last bin. "hint" would highlight the bins with under/overflow contents **kwargs : Keyword arguments passed to underlying matplotlib function - pcolormesh. @@ -544,29 +534,32 @@ def hist2dplot( if not isinstance(ax, plt.Axes): raise ValueError("ax must be a matplotlib Axes object") + # hist overflow check + if flow is not None and "Hist" not in str(type(H)): + raise TypeError(f"{flow} option only works for Hist object") + hist = hist_object_handler(H, xbins, ybins) # TODO: use Histogram everywhere H = hist.values() xbins, xtick_labels = get_plottable_protocol_bins(hist.axes[0]) ybins, ytick_labels = get_plottable_protocol_bins(hist.axes[1]) - # Show under/overflow bins - # "show": Add additional bin with 5 times bin width + # "show": Add additional bin with 2 times bin width if flow == "show": H = hist.view(flow=True)["value"] xbins = np.array( [ - xbins[0] - (xbins[1] - xbins[0]) * 5, + xbins[0] - (xbins[1] - xbins[0]) * 2, *xbins, - xbins[-1] + (xbins[1] - xbins[0]) * 5, + xbins[-1] + (xbins[1] - xbins[0]) * 2, ] ) ybins = np.array( [ - ybins[0] - (ybins[1] - ybins[0]) * 5, + ybins[0] - (ybins[1] - ybins[0]) * 2, *ybins, - ybins[-1] + (ybins[1] - ybins[0]) * 5, + ybins[-1] + (ybins[1] - ybins[0]) * 2, ] ) if flow == "sum": @@ -632,23 +625,46 @@ def hist2dplot( plt.sca(ax) if flow == "hint": if hist.view(flow=True)["value"][0, 0] > 0.0: - print( - "underflow for x & underflow for y:", - hist.view(flow=True)["value"][0, 0], + ret1 = patches.Rectangle( + (xbins[0], ybins[0]), + width=xbins[1] - xbins[0], + height=ybins[1] - ybins[0], + fc="none", + ec="r", + lw=2, ) + ax.add_patch(ret1) + if hist.view(flow=True)["value"][-1, 0] > 0.0: - print( - "overflow for x & underflow for y:", - hist.view(flow=True)["value"][-1, 0], + ret2 = patches.Rectangle( + (xbins[-2], ybins[0]), + width=xbins[1] - xbins[0], + height=ybins[1] - ybins[0], + fc="none", + ec="r", + lw=2, ) + ax.add_patch(ret2) if hist.view(flow=True)["value"][0, -1] > 0.0: - print( - "underflow for x & overflow for y:", hist.view(flow=True)["value"][0, 0] + ret3 = patches.Rectangle( + (xbins[0], ybins[-2]), + width=xbins[1] - xbins[0], + height=ybins[1] - ybins[0], + fc="none", + ec="r", + lw=2, ) + ax.add_patch(ret3) if hist.view(flow=True)["value"][-1, -1] > 0.0: - print( - "overflow for x & overflow for y:", hist.view(flow=True)["value"][-1, 0] + ret4 = patches.Rectangle( + (xbins[-2], ybins[-2]), + width=xbins[1] - xbins[0], + height=ybins[1] - ybins[0], + fc="none", + ec="r", + lw=2, ) + ax.add_patch(ret4) _labels: np.ndarray | None = None if isinstance(labels, bool): _labels = H if labels else None @@ -683,320 +699,8 @@ def hist2dplot( return ColormeshArtists(pc, cb_obj, text_artists) -### copy functions coffea.hist.plotratio https://github.com/CoffeaTeam/coffea/blob/master/coffea/hist/plot.py to boost-hist -################ -## ratio plot ## -################ -_coverage1sd = scipy.stats.norm.cdf(1) - scipy.stats.norm.cdf(-1) - - -def plotratio( - num, - denom, - ax=None, - clear=True, - flow=None, - xerr=False, - error_opts={}, - denom_fill_opts={}, - guide_opts={}, - unc="num", - label=None, - ext_denom_error=None, -): - """Create a ratio plot, dividing two compatible histograms - Parameters - ---------- - num : Hist - Numerator, a single-axis histogram - denom : Hist - Denominator, a single-axis histogram - ax : matplotlib.axes.Axes, optional - Axes object (if None, one is created) - clear : bool, optional - Whether to clear Axes before drawing (if passed); if False, this function will skip drawing the legend - flow : str, optional {None, "show", "sum"} - Whether plot the under/overflow bin. If "show", add additional under/overflow bin. If "sum", add the under/overflow bin content to first/last bin. - xerr: bool, optional - If true, then error bars are drawn for x-axis to indicate the size of the bin. - error_opts : dict, optional - A dictionary of options to pass to the matplotlib - `ax.errorbar `_ call - internal to this function. Leave blank for defaults. Some special options are interpreted by - this function and not passed to matplotlib: 'emarker' (default: '') specifies the marker type - to place at cap of the errorbar. - denom_fill_opts : dict, optional - A dictionary of options to pass to the matplotlib - `ax.fill_between `_ call - internal to this function, filling the denominator uncertainty band. Leave blank for defaults. - guide_opts : dict, optional - A dictionary of options to pass to the matplotlib - `ax.axhline `_ call - internal to this function, to plot a horizontal guide line at ratio of 1. Leave blank for defaults. - unc : str, optional - Uncertainty calculation option: 'clopper-pearson' interval for efficiencies; 'poisson-ratio' interval - for ratio of poisson distributions; 'num' poisson interval of numerator scaled by denominator value - (common for data/mc, for better or worse). - label : str, optional - Associate a label to this entry (note: y axis label set by ``num.label``) - ext_denom_error: list of np.array[error_up,error_down], optional - External MC errors not stored in the original histogram - Returns - ------- - ax : matplotlib.axes.Axes - A matplotlib `Axes `_ object - """ - if ax is None: - fig, ax = plt.subplots(1, 1) - else: - if not isinstance(ax, plt.Axes): - raise ValueError("ax must be a matplotlib Axes object") - if clear: - ax.clear() - if not compatible(num, denom): - raise ValueError( - "numerator and denominator histograms have incompatible axis definitions" - ) - if len(num.axes) > 1: - raise ValueError("plotratio() can only support one-dimensional histograms") - if error_opts is None and denom_fill_opts is None and guide_opts is None: - error_opts = {} - denom_fill_opts = {} - - axis = num.axes[0] - - ax.set_xlabel(axis.label) - ax.set_ylabel(num.label) - edges = axis.edges - if flow == "show": - edges = np.array( - [ - edges[0] - (edges[1] - edges[0]) * 5, - *edges, - edges[-1] + (edges[1] - edges[0]) * 5, - ] - ) - centers = (edges[1:] + edges[:-1]) / 2 - ranges = (edges[1:] - edges[:-1]) / 2 if xerr else None - sumw_num, sumw2_num = num.values(), num.variances() - sumw_denom, sumw2_denom = denom.values(), denom.variances() - - if flow == "show": - sumw_num, sumw2_num = ( - num.view(flow=True)["value"], - num.view(flow=True)["variance"], - ) - sumw_denom, sumw2_denom = ( - denom.view(flow=True)["value"], - denom.view(flow=True)["variance"], - ) - elif flow == "sum": - sumw_num[0], sumw2_num[0] = ( - sumw_num[0] + num.view(flow=True)["value"][0], - sumw2_num[0] + num.view(flow=True)["value"][0], - ) - sumw_num[-1], sumw2_num[-1] = ( - sumw_num[-1] + num.view(flow=True)["value"][-1], - sumw2_num[-1] + num.view(flow=True)["value"][-1], - ) - sumw_denom[0], sumw2_denom[0] = ( - sumw_denom[0] + denom.view(flow=True)["value"][0], - sumw2_denom[0] + denom.view(flow=True)["value"][0], - ) - sumw_denom[-1], sumw2_denom[-1] = ( - sumw_denom[-1] + denom.view(flow=True)["value"][-1], - sumw2_denom[-1] + denom.view(flow=True)["value"][-1], - ) - else: - sumw_num, sumw2_num = num.values(), num.variances() - sumw_denom, sumw2_denom = denom.values(), denom.variances() - rsumw = sumw_num / sumw_denom - if unc == "clopper-pearson": - rsumw_err = np.abs(clopper_pearson_interval(sumw_num, sumw_denom) - rsumw) - elif unc == "poisson-ratio": - # poisson ratio n/m is equivalent to binomial n/(n+m) - rsumw_err = np.abs( - clopper_pearson_interval(sumw_num, sumw_num + sumw_denom) - rsumw - ) - elif unc == "num": - rsumw_err = np.abs(poisson_interval(rsumw, sumw2_num / sumw_denom**2) - rsumw) - elif unc == "efficiency": - rsumw_err = np.abs( - normal_interval(sumw_num, sumw_denom, sumw2_num, sumw2_denom) - ) - else: - raise ValueError("Unrecognized uncertainty option: %r" % unc) - - ## if additional uncertainties - if ext_denom_error is not None: - if denom_fill_opts is {}: - print("suggest to use different style for additional error") - if np.shape(rsumw_err) != np.shape(ext_denom_error / sumw_denom): - raise ValueError("Imcompatible error length") - rsumw_err = np.sqrt(rsumw_err**2 + (ext_denom_error / sumw_denom) ** 2) - - if error_opts is not None: - opts = { - "label": label, - "linestyle": "none", - "lw": 1, - "marker": "o", - "color": "k", - } - opts.update(error_opts) - emarker = opts.pop("emarker", "") - errbar = ax.errorbar(x=centers, y=rsumw, xerr=ranges, yerr=rsumw_err, **opts) - plt.setp(errbar[1], "marker", emarker) - if denom_fill_opts is not None: - unity = np.ones_like(sumw_denom) - denom_unc = poisson_interval(unity, sumw2_denom / sumw_denom**2) - opts = { - "hatch": "////", - "facecolor": "none", - "lw": 0, - "color": "k", - "alpha": 0.4, - } - if ext_denom_error is not None: - denom_unc[0] = ( - unity[0] - - np.sqrt( - (denom_unc - unity) ** 2 + (ext_denom_error / sumw_denom) ** 2 - )[0] - ) - denom_unc[1] = ( - unity[1] - + np.sqrt( - (denom_unc - unity) ** 2 + (ext_denom_error / sumw_denom) ** 2 - )[1] - ) - opts = denom_fill_opts - ax.stairs(denom_unc[0], edges=edges, baseline=denom_unc[1], **opts) - if guide_opts is not None: - opts = {"linestyle": "--", "color": (0, 0, 0, 0.5), "linewidth": 1} - opts.update(guide_opts) - if clear is not False: - ax.axhline(1.0, **opts) - - if clear: - ax.autoscale(axis="x", tight=True) - ax.set_ylim(0, None) - - return ax - - ############################################# # Utils -def compatible(self, other): - """Checks if this histogram is compatible with another, i.e. they have identical binning""" - if len(self.axes) != len(other.axes): - return False - if set(self.axes.name) != set(other.axes.name): - return False - # if not all(d1 == d2 for d1, d2 in zip(self.dense_axes(), other.dense_axes())): - # return False - return True - - -def poisson_interval(sumw, sumw2, coverage=_coverage1sd): - """Frequentist coverage interval for Poisson-distributed observations - Parameters - ---------- - sumw : numpy.ndarray - Sum of weights vector - sumw2 : numpy.ndarray - Sum weights squared vector - coverage : float, optional - Central coverage interval, defaults to 68% - Calculates the so-called 'Garwood' interval, - c.f. https://www.ine.pt/revstat/pdf/rs120203.pdf or - http://ms.mcmaster.ca/peter/s743/poissonalpha.html - For weighted data, this approximates the observed count by ``sumw**2/sumw2``, which - effectively scales the unweighted poisson interval by the average weight. - This may not be the optimal solution: see https://arxiv.org/pdf/1309.1287.pdf for a proper treatment. - When a bin is zero, the scale of the nearest nonzero bin is substituted to scale the nominal upper bound. - If all bins zero, a warning is generated and interval is set to ``sumw``. - """ - scale = np.empty_like(sumw) - scale[sumw != 0] = sumw2[sumw != 0] / sumw[sumw != 0] - if np.sum(sumw == 0) > 0: - missing = np.where(sumw == 0) - available = np.nonzero(sumw) - if len(available[0]) == 0: - warnings.warn( - "All sumw are zero! Cannot compute meaningful error bars", - RuntimeWarning, - ) - return np.vstack([sumw, sumw]) - nearest = sum( - [np.subtract.outer(d, d0) ** 2 for d, d0 in zip(available, missing)] - ).argmin(axis=0) - argnearest = tuple(dim[nearest] for dim in available) - scale[missing] = scale[argnearest] - counts = sumw / scale - lo = scale * scipy.stats.chi2.ppf((1 - coverage) / 2, 2 * counts) / 2.0 - hi = scale * scipy.stats.chi2.ppf((1 + coverage) / 2, 2 * (counts + 1)) / 2.0 - interval = np.array([lo, hi]) - interval[interval == np.nan] = 0.0 # chi2.ppf produces nan for counts=0 - return interval - - -def normal_interval(pw, tw, pw2, tw2, coverage=_coverage1sd): - """Compute errors based on the expansion of pass/(pass + fail), possibly weighted - Parameters - ---------- - pw : np.ndarray - Numerator, or number of (weighted) successes, vectorized - tw : np.ndarray - Denominator or number of (weighted) trials, vectorized - pw2 : np.ndarray - Numerator sum of weights squared, vectorized - tw2 : np.ndarray - Denominator sum of weights squared, vectorized - coverage : float, optional - Central coverage interval, defaults to 68% - c.f. https://root.cern.ch/doc/master/TEfficiency_8cxx_source.html#l02515 - """ - - eff = pw / tw - - variance = (pw2 * (1 - 2 * eff) + tw2 * eff**2) / (tw**2) - sigma = np.sqrt(variance) - - prob = 0.5 * (1 - coverage) - delta = np.zeros_like(sigma) - delta[sigma != 0] = scipy.stats.norm.ppf(prob, scale=sigma[sigma != 0]) - - lo = eff - np.minimum(eff + delta, np.ones_like(eff)) - hi = np.maximum(eff - delta, np.zeros_like(eff)) - eff - - return np.array([lo, hi]) - - -def clopper_pearson_interval(num, denom, coverage=_coverage1sd): - """Compute Clopper-Pearson coverage interval for a binomial distribution - Parameters - ---------- - num : numpy.ndarray - Numerator, or number of successes, vectorized - denom : numpy.ndarray - Denominator or number of trials, vectorized - coverage : float, optional - Central coverage interval, defaults to 68% - c.f. http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval - """ - if np.any(num > denom): - raise ValueError( - "Found numerator larger than denominator while calculating binomial uncertainty" - ) - lo = scipy.stats.beta.ppf((1 - coverage) / 2, num, denom - num + 1) - hi = scipy.stats.beta.ppf((1 + coverage) / 2, num + 1, denom - num) - interval = np.array([lo, hi]) - interval[:, num == 0.0] = 0.0 - interval[1, num == denom] = 1.0 - return interval - - def overlap(ax, bbox, get_vertices=False): """ Find overlap of bbox for drawn elements an axes. diff --git a/tests/baseline/test_hist2dplot.png b/tests/baseline/test_hist2dplot.png index 975bce0e..4c513b7a 100644 Binary files a/tests/baseline/test_hist2dplot.png and b/tests/baseline/test_hist2dplot.png differ diff --git a/tests/baseline/test_hist2dplot_cbar_False.png b/tests/baseline/test_hist2dplot_cbar_False.png index 975bce0e..4c513b7a 100644 Binary files a/tests/baseline/test_hist2dplot_cbar_False.png and b/tests/baseline/test_hist2dplot_cbar_False.png differ diff --git a/tests/baseline/test_hist2dplot_cbar_True.png b/tests/baseline/test_hist2dplot_cbar_True.png index c9e407b5..eda3b263 100644 Binary files a/tests/baseline/test_hist2dplot_cbar_True.png and b/tests/baseline/test_hist2dplot_cbar_True.png differ diff --git a/tests/baseline/test_hist2dplot_cbar_subplots.png b/tests/baseline/test_hist2dplot_cbar_subplots.png index 234afefe..34246667 100644 Binary files a/tests/baseline/test_hist2dplot_cbar_subplots.png and b/tests/baseline/test_hist2dplot_cbar_subplots.png differ diff --git a/tests/baseline/test_hist2dplot_custom_labels.png b/tests/baseline/test_hist2dplot_custom_labels.png index 8baf0ab1..7891e558 100644 Binary files a/tests/baseline/test_hist2dplot_custom_labels.png and b/tests/baseline/test_hist2dplot_custom_labels.png differ diff --git a/tests/baseline/test_hist2dplot_flow.png b/tests/baseline/test_hist2dplot_flow.png new file mode 100644 index 00000000..d6ea0895 Binary files /dev/null and b/tests/baseline/test_hist2dplot_flow.png differ diff --git a/tests/baseline/test_hist2dplot_inputs_nobin.png b/tests/baseline/test_hist2dplot_inputs_nobin.png index 0b9cb46c..076d6172 100644 Binary files a/tests/baseline/test_hist2dplot_inputs_nobin.png and b/tests/baseline/test_hist2dplot_inputs_nobin.png differ diff --git a/tests/baseline/test_histplot.png b/tests/baseline/test_histplot.png index 31a39bdb..53be91bd 100644 Binary files a/tests/baseline/test_histplot.png and b/tests/baseline/test_histplot.png differ diff --git a/tests/baseline/test_histplot_density.png b/tests/baseline/test_histplot_density.png index d5360b79..e28ea7e3 100644 Binary files a/tests/baseline/test_histplot_density.png and b/tests/baseline/test_histplot_density.png differ diff --git a/tests/baseline/test_histplot_flow.png b/tests/baseline/test_histplot_flow.png new file mode 100644 index 00000000..11971b38 Binary files /dev/null and b/tests/baseline/test_histplot_flow.png differ diff --git a/tests/baseline/test_histplot_kwargs.png b/tests/baseline/test_histplot_kwargs.png index dcbb5e51..a54605db 100644 Binary files a/tests/baseline/test_histplot_kwargs.png and b/tests/baseline/test_histplot_kwargs.png differ diff --git a/tests/baseline/test_histplot_multiple.png b/tests/baseline/test_histplot_multiple.png index 7562b9e0..0d4c8322 100644 Binary files a/tests/baseline/test_histplot_multiple.png and b/tests/baseline/test_histplot_multiple.png differ diff --git a/tests/baseline/test_histplot_real.png b/tests/baseline/test_histplot_real.png index 079aaadc..783a35f1 100644 Binary files a/tests/baseline/test_histplot_real.png and b/tests/baseline/test_histplot_real.png differ diff --git a/tests/baseline/test_histplot_stack.png b/tests/baseline/test_histplot_stack.png index af1f4b93..1a902d3e 100644 Binary files a/tests/baseline/test_histplot_stack.png and b/tests/baseline/test_histplot_stack.png differ diff --git a/tests/baseline/test_histplot_types.png b/tests/baseline/test_histplot_types.png index 42f41ec0..cd07d5d1 100644 Binary files a/tests/baseline/test_histplot_types.png and b/tests/baseline/test_histplot_types.png differ diff --git a/tests/baseline/test_histplot_w2.png b/tests/baseline/test_histplot_w2.png index 58f4f96f..4bd019c0 100644 Binary files a/tests/baseline/test_histplot_w2.png and b/tests/baseline/test_histplot_w2.png differ diff --git a/tests/baseline/test_inputs_basic.png b/tests/baseline/test_inputs_basic.png index c9a029b4..bdb91559 100644 Binary files a/tests/baseline/test_inputs_basic.png and b/tests/baseline/test_inputs_basic.png differ diff --git a/tests/baseline/test_inputs_bh.png b/tests/baseline/test_inputs_bh.png index f71cb724..c7700654 100644 Binary files a/tests/baseline/test_inputs_bh.png and b/tests/baseline/test_inputs_bh.png differ diff --git a/tests/baseline/test_inputs_bh_cat.png b/tests/baseline/test_inputs_bh_cat.png index 000494c9..9c6a72be 100644 Binary files a/tests/baseline/test_inputs_bh_cat.png and b/tests/baseline/test_inputs_bh_cat.png differ diff --git a/tests/baseline/test_inputs_uproot.png b/tests/baseline/test_inputs_uproot.png index 5b544d84..4830d7fb 100644 Binary files a/tests/baseline/test_inputs_uproot.png and b/tests/baseline/test_inputs_uproot.png differ diff --git a/tests/baseline/test_label_loc.png b/tests/baseline/test_label_loc.png index bceb6ebd..3ec48bd7 100644 Binary files a/tests/baseline/test_label_loc.png and b/tests/baseline/test_label_loc.png differ diff --git a/tests/baseline/test_labeltext_loc.png b/tests/baseline/test_labeltext_loc.png index 96d77fd0..00dda305 100644 Binary files a/tests/baseline/test_labeltext_loc.png and b/tests/baseline/test_labeltext_loc.png differ diff --git a/tests/baseline/test_log.png b/tests/baseline/test_log.png index 3dc64ad0..5654318d 100644 Binary files a/tests/baseline/test_log.png and b/tests/baseline/test_log.png differ diff --git a/tests/baseline/test_simple.png b/tests/baseline/test_simple.png index 286a3213..fc7e2692 100644 Binary files a/tests/baseline/test_simple.png and b/tests/baseline/test_simple.png differ diff --git a/tests/baseline/test_simple2d.png b/tests/baseline/test_simple2d.png index 57d5fe52..268bd16a 100644 Binary files a/tests/baseline/test_simple2d.png and b/tests/baseline/test_simple2d.png differ diff --git a/tests/baseline/test_simple_xerr.png b/tests/baseline/test_simple_xerr.png index 6d913f34..58e6eeb6 100644 Binary files a/tests/baseline/test_simple_xerr.png and b/tests/baseline/test_simple_xerr.png differ diff --git a/tests/baseline/test_style_alice.png b/tests/baseline/test_style_alice.png index 2ae81019..30662d86 100644 Binary files a/tests/baseline/test_style_alice.png and b/tests/baseline/test_style_alice.png differ diff --git a/tests/baseline/test_style_atlas.png b/tests/baseline/test_style_atlas.png index 167f0971..91b33e04 100644 Binary files a/tests/baseline/test_style_atlas.png and b/tests/baseline/test_style_atlas.png differ diff --git a/tests/baseline/test_style_cms.png b/tests/baseline/test_style_cms.png index 47abb815..76fa2ed7 100644 Binary files a/tests/baseline/test_style_cms.png and b/tests/baseline/test_style_cms.png differ diff --git a/tests/baseline/test_style_lhcb.png b/tests/baseline/test_style_lhcb.png index 7485aa57..6f7c8f67 100644 Binary files a/tests/baseline/test_style_lhcb.png and b/tests/baseline/test_style_lhcb.png differ diff --git a/tests/baseline/test_style_lhcb2.png b/tests/baseline/test_style_lhcb2.png index 9200a542..6dab473e 100644 Binary files a/tests/baseline/test_style_lhcb2.png and b/tests/baseline/test_style_lhcb2.png differ diff --git a/tests/test_basic.py b/tests/test_basic.py index 1b9423a0..edd98232 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -2,6 +2,7 @@ import os +import hist import matplotlib.pyplot as plt import numpy as np import pytest @@ -110,6 +111,27 @@ def test_histplot_density(): return fig +@pytest.mark.mpl_image_compare(style="default") +def test_histplot_flow(): + np.random.seed(0) + h = hist.Hist(hist.axis.Regular(20, 5, 15, name="x"), hist.storage.Weight()) + h.fill(np.random.normal(10, 3, 400)) + fig, axs = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 10)) + axs = axs.flatten() + + hep.histplot(h, ax=axs[0], flow="hint") + hep.histplot(h, ax=axs[1], flow="None") + hep.histplot(h, ax=axs[2], flow="show") + hep.histplot(h, ax=axs[3], flow="sum") + + axs[0].set_title("Default", fontsize=18) + axs[1].set_title("None", fontsize=18) + axs[2].set_title("Show", fontsize=18) + axs[3].set_title("Sum", fontsize=18) + fig.subplots_adjust(hspace=0.1, wspace=0.1) + return fig + + @pytest.mark.mpl_image_compare(style="default", remove_text=True) def test_histplot_multiple(): np.random.seed(0) @@ -175,6 +197,32 @@ def test_hist2dplot(): return fig +@pytest.mark.mpl_image_compare(style="default") +def test_hist2dplot_flow(): + np.random.seed(0) + h = hist.Hist( + hist.axis.Regular(20, 5, 15, name="x"), + hist.axis.Regular(20, -5, 5, name="y"), + hist.storage.Weight(), + ) + h.fill(np.random.normal(10, 3, 400), np.random.normal(0, 4, 400)) + fig, axs = plt.subplots(2, 2, figsize=(10, 10)) + axs = axs.flatten() + + hep.hist2dplot(h, ax=axs[0], flow="hint") + hep.hist2dplot(h, ax=axs[1], flow="None") + hep.hist2dplot(h, ax=axs[2], flow="show") + hep.hist2dplot(h, ax=axs[3], flow="sum") + + axs[0].set_title("Default", fontsize=18) + axs[1].set_title("None", fontsize=18) + axs[2].set_title("Show", fontsize=18) + axs[3].set_title("Sum", fontsize=18) + fig.subplots_adjust(hspace=0.1, wspace=0.1) + + return fig + + @pytest.mark.mpl_image_compare(style="default", remove_text=True) def test_hist2dplot_inputs_nobin(): np.random.seed(0)