Beispiel #1
0
    def axes(self, *args, **kwargs):
        """set axis properties

        Args:
            args: tuple of axes, either left out to apply to all axes or specify e.g. "x", "y"
            kwargs: dict, key-value where key can be any axis attribute set to value
        """
        which_axes = [0, 1, 2]
        if args:
            # remove unrequested axes
            if "x" not in args:
                which_axes.remove(0)
            if "y" not in args:
                which_axes.remove(1)
            if "z" not in args:
                which_axes.remove(2)


        for k, v in kwargs.items():
            # Apparently, does not work simply with class, potentially since attributes are only
            # added in __init__. Therefore, use one object instead
            if not hasattr(self._default_axes[0], k):
                get_logger().warning("Unknown attribute %s of AxisSpec", k)
                continue

            for i, ax in enumerate(self._default_axes):
                if i not in which_axes:
                    # only apply to requested
                    continue
                setattr(ax, k, v)
        if self._current_plot_spec:
            self._current_plot_spec.axes(*args, **kwargs)
Beispiel #2
0
 def legend(self, **kwargs):
     """set legend properties
     """
     for k, v in kwargs.items():
         if not hasattr(self._legend_spec, k):
             get_logger().warning("Unknown attribute %s of LegendSpec", k)
             continue
         setattr(self._legend_spec, k, v)
Beispiel #3
0
 def legend(self, **kwargs):
     """set legend properties
     """
     for k, v in kwargs.items():
         if not hasattr(self._default_legend, k):
             get_logger().warning("Unknown attribute %s of LegendSpec", k)
             continue
         setattr(self._default_legend, k, v)
     if self._current_plot_spec:
         self._current_plot_spec.legend(**kwargs)
Beispiel #4
0
    def __init__(self):

        # The parent FigureSpec this PlotSpec is embedded in
        self._parent_figure_spec = None

        # relative positioning in FigureSpec
        # (left, bottom, right, top)
        self._rel_coordinates = None

        # Tuple of row margins [0] = bottom, [1] = top
        self._row_margins = None
        # Tuple of column margins [0] = left, [1] = right
        self._column_margins = None

        # text to be added to a plot
        self._texts = []

        # legend properties
        self._legend_spec = LegendSpec()

        # AxisSpecs of the PlotSpec
        self._axes = [AxisSpec(), AxisSpec(), AxisSpec()]

        # LineSpecs
        self._lines = []

        # Potential PlotSpecs to share x- or y-axis with
        self._share_x = None
        self._share_y = None

        # title
        self._title = None

        # quickly refer to logger
        self.logger = get_logger()
Beispiel #5
0
def generate_styles(style_class, n_styles=1, **kwargs):
    """Generate a certain number of a given styles

    Args:
        style_class: class of object to be constructed
        n_styles: int number of styles to be created

    Returns:
        list of styles
    """
    # Generate number of requested styles
    styles = [style_class() for _ in range(n_styles)]
    # Get the defaults of this class
    defaults = deepcopy(style_class.defaults)

    for k in defaults:
        # Overwrite defaults by potential user input
        defaults[k] = kwargs.pop(k, defaults[k])

    for k, v in defaults.items():
        # Go through all style options
        for i, style in enumerate(styles):
            if not hasattr(style, k):
                get_logger().warning("Unknown attribute %s of style class %s",
                                     k, style_class.__name__)
                continue
            try:
                # Option might come as an iterable
                iter(v)
                chosen_style = v[i % len(v)]
            except TypeError:
                # if not, this is assumed to be the option
                chosen_style = v
            # now apply the option
            setattr(style, k, chosen_style)
    return styles
Beispiel #6
0
    def __init__(self, n_cols, n_rows, height_ratios=None, width_ratios=None, **kwargs):

        # construct unique name
        self.name = f"{FigureSpec.FIGURE_NAME_BASE}_{FigureSpec.N_FIGURES}"
        FigureSpec.N_FIGURES += 1

        # Size in pixels (width, height)
        self.size = kwargs.pop("size", (300, 300))

        # number of columns and rows
        self.n_cols = n_cols
        self.n_rows = n_rows

        # construct height and width ratios of rows and columns
        self.__make_height_ratios(height_ratios)
        self.__make_width_ratios(width_ratios)

        # construct margins of rows and columns
        self.__make_row_margins(kwargs.pop("row_margin", 0.05))
        self.__make_column_margins(kwargs.pop("column_margin", 0.05))

        # remember which cells are taken already
        self.cells_taken = []

        # store all PlotSpecs
        self._plot_specs = []

        # set current PlotSpec internally to be able to provide some proxy methods
        self._current_plot_spec = None

        # default axes settings
        self._default_axes = [AxisSpec(), AxisSpec(), AxisSpec()]

        # default legend settings
        self._default_legend = LegendSpec()

        # quickly refer to logger
        self.logger = get_logger()

        # automatically define a plot if only one cell
        if self.n_cols == 1 and self.n_rows == 1:
            self.define_plot(0,0)
def find_boundaries(
        objects,
        x_low=None,
        x_up=None,
        y_low=None,
        y_up=None,
        z_low=None,
        z_up=None,  # pylint: disable=unused-argument, too-many-branches, too-many-statements
        x_log=False,
        y_log=False,
        z_log=False,
        reserve_ndc_top=None,
        reserve_ndc_bottom=None,
        x_force_limits=False,
        y_force_limits=False,
        y_account_for_errors=True):
    """Find boundaries for any ROOT objects

    Args:
        histo: TH1 1D histogram to derive boundaries for
        x_low: float or None (fix to given float if given) otherwise derive from histogram
        x_up: float or None (fix to given float if given) otherwise derive from histogram
        y_low: float or None (fix to given float if given) otherwise derive from histogram
        y_up: float or None (fix to given float if given) otherwise derive from histogram
        x_log: bool whether or not x-axis is log scale
        y_log: bool whether or not y-axis is log scale
        reserve_ndc_top: float or None, specify whether or not some space should be reserved
                         for the legend at the top
        reserve_ndc_bottom: float or None, specify whether or not some space should be reserved
                            for the legend at the bottom
        y_force_limits: bool to really force the limits as set by the user regardless of having a
                        potential overlapping legend
    Returns:
        float, float, float, float (derived boundaries)

    """

    #TODO This has to be revised (code duplication, ~brute force implementation)

    if x_up is not None and x_low is not None and x_up < x_low:
        # At this point there are numbers for sure.
        # If specified by the user and in case wrong way round, fix it
        get_logger().warning("Minimum (%f) is larger than maximum (%f) on x-axis. " \
        "Fix it by swapping numbers", x_low, x_up)
        x_low, x_up = (x_up, x_low)

    if y_up is not None and y_low is not None and y_up < y_low:
        # At this point there are numbers for sure.
        # If specified by the user and in case wrong way round, fix it
        get_logger().warning("Minimum (%f) is larger than maximum (%f) on y-axis. " \
        "Fix it by swapping numbers", y_low, y_up)
        y_low, y_up = (y_up, y_low)

    if z_up is not None and z_low is not None and z_up < z_low:
        # At this point there are numbers for sure.
        # If specified by the user and in case wrong way round, fix it
        get_logger().warning("Minimum (%f) is larger than maximum (%f) on z-axis. " \
        "Fix it by swapping numbers", z_low, z_up)
        z_low, z_up = (z_up, z_low)

    # set largest/lowest float values
    x_low_new = sys.float_info.max
    x_up_new = sys.float_info.min
    y_low_new = sys.float_info.max
    y_up_new = sys.float_info.min
    z_low_new = sys.float_info.max
    z_up_new = sys.float_info.min

    # set largest/lowest float values for a second search to find boundaries
    # closest to object's contents
    x_low_new_no_user = sys.float_info.max
    x_up_new_no_user = sys.float_info.min
    y_low_new_no_user = sys.float_info.max
    y_up_new_no_user = sys.float_info.min
    z_low_new_no_user = sys.float_info.max
    z_up_new_no_user = sys.float_info.min

    # whether or not adjust y-limits
    adjust_y_limits = True

    for obj in objects:
        # for each 1D ROOT object find user and non-user-specific boundaries
        if not (is_1d(obj) or is_2d(obj)):
            get_logger().warning("Cannot derive limits for object's class %s",
                                 obj.__class__.__name__)
            continue
        if isinstance(obj, ROOT_OBJECTS_HIST_1D):
            x_low_est, x_up_est, y_low_est, y_up_est = \
            find_boundaries_TH1(obj, x_low, x_up, y_low, y_up, x_log, y_log,
                                y_account_for_errors=y_account_for_errors)
            x_low_est_no_user, x_up_est_no_user, y_low_est_no_user, y_up_est_no_user = \
            find_boundaries_TH1(obj, x_log=x_log, y_log=y_log,
                                y_account_for_errors=y_account_for_errors)
        elif isinstance(obj, TF1) and not isinstance(obj, ROOT_OBJECTS_NOT_1D):
            x_low_est, x_up_est, y_low_est, y_up_est = \
            find_boundaries_TF1(obj, x_low, x_up, y_low, y_up, x_log, y_log)
            x_low_est_no_user, x_up_est_no_user, y_low_est_no_user, y_up_est_no_user = \
            find_boundaries_TF1(obj, x_log=x_log, y_log=y_log)
        elif isinstance(obj, TGraph):
            x_low_est, x_up_est, y_low_est, y_up_est = \
            find_boundaries_TGraph(obj, x_low, x_up, y_low, y_up, x_log, y_log)
            if x_low_est is None:
                continue
            x_low_est_no_user, x_up_est_no_user, y_low_est_no_user, y_up_est_no_user = \
            find_boundaries_TGraph(obj, x_log=x_log, y_log=y_log)
        elif isinstance(obj, ROOT_OBJECTS_HIST_2D):
            x_low_est, x_up_est, y_low_est, y_up_est, z_low_est, z_up_est = \
            find_boundaries_TH2(obj, x_low, x_up, y_low, y_up, z_low, z_up, x_log, y_log, z_log)
            x_low_est_no_user, x_up_est_no_user, y_low_est_no_user, y_up_est_no_user, z_low_est_no_user, z_up_est_no_user = \
            find_boundaries_TH2(obj, x_log=x_log, y_log=y_log, z_log=z_log) # pylint: disable=line-too-long
            # update boundaries for user-specific settings
            z_up_new = max(z_up_est, z_up_new)
            z_low_new = min(z_low_est, z_low_new)
            # update boundaries for user-independent settings
            z_up_new_no_user = max(z_up_est_no_user, z_up_new_no_user)
            z_low_new_no_user = min(z_low_est_no_user, z_low_new_no_user)
            adjust_y_limits = False
        elif isinstance(obj, TEfficiency):
            get_logger().warning(
                "Finding boundaries for TEfficiency not yet implemented")
            continue
        else:
            get_logger().warning("Unknown class %s", obj.__class__.__name__)
            continue

        # update boundaries for user-specific settings
        x_up_new = max(x_up_est, x_up_new)
        x_low_new = min(x_low_est, x_low_new)
        y_up_new = max(y_up_est, y_up_new)
        y_low_new = min(y_low_est, y_low_new)

        # update boundaries for user-independent settings
        x_up_new_no_user = max(x_up_est_no_user, x_up_new_no_user)
        x_low_new_no_user = min(x_low_est_no_user, x_low_new_no_user)
        y_up_new_no_user = max(y_up_est_no_user, y_up_new_no_user)
        y_low_new_no_user = min(y_low_est_no_user, y_low_new_no_user)

    if y_up is not None and y_up_new < y_up_new_no_user and not y_force_limits:
        # only do it if y-limits are not forced
        get_logger().warning("The upper y-limit was chosen to be %f which is however too small " \
        "to fit the plots. It is adjusted to the least maximum value of %f.",
        y_up_new, y_up_new_no_user)

        y_up_new = y_up_new_no_user

    if y_low is not None and y_low_new > y_low_new_no_user and not y_force_limits:
        # only do it if y-limits are not forced
        get_logger().warning("The lower y-limit was chosen to be %f which is however too large " \
        "to fit the plots. It is adjusted to the least maximum value of %f.",
        y_low_new, y_low_new_no_user)

        y_low_new = y_low_new_no_user

    if y_log:
        if y_low_new <= 0:
            y_low_new = MIN_LOG_SCALE
        if y_low_new_no_user <= 0:
            y_low_new_no_user = MIN_LOG_SCALE

    # Adjust a bit top and bottom otherwise maxima and minima will exactly touch the x-axis
    if adjust_y_limits:
        if y_log:
            y_diff = log10(y_up_new) - log10(y_low_new)
        else:
            y_diff = y_up_new - y_low_new

        if y_low is None and reserve_ndc_bottom is None:
            if y_log:
                y_low_new = 10**(log10(y_low_new) - y_diff * 0.1)
            else:
                y_low_new -= 0.1 * y_diff

        if y_up is None and reserve_ndc_top is None:
            if y_log:
                y_up_new = 10**(log10(y_up_new) + y_diff * 0.1)
            else:
                y_up_new += 0.1 * y_diff

    # Now force the limits if requested
    if x_force_limits and x_low is not None and x_up is not None:
        x_low_new = x_low
        x_up_new = x_up

    if y_force_limits and y_low is not None and y_up is not None:
        y_low_new = y_low
        y_up_new = y_up

    # if z_force_limits and z_low is not None and z_up is not None:
    #     z_low_new = z_low
    #     z_up_new = z_up

    if z_low_new <= 0 and z_log:
        # If not compatible with log scale, force it to be
        # Can happen if fixed by user - but incompatible -
        # and at the same time log scale is requested
        get_logger().warning("Have to set z-minimum to something larger than 0 since log-scale " \
        "is requested. Set value was %f and reset value is now %f", z_low_new, MIN_LOG_SCALE)
        z_low_new = MIN_LOG_SCALE

    if y_low_new <= 0 and y_log:
        # If not compatible with log scale, force it to be
        # Can happen if fixed by user - but incompatible -
        # and at the same time log scale is requested
        get_logger().warning("Have to set y-minimum to something larger than 0 since log-scale " \
        "is requested. Set value was %f and reset value is now %f", y_low_new, MIN_LOG_SCALE)
        y_low_new = MIN_LOG_SCALE

    if x_low_new <= 0 and x_log:
        # If not compatible with log scale, force it to be
        # Can happen if fixed by user - but incompatible -
        # and at the same time log scale is requested
        get_logger().warning("Have to set x-minimum to something larger than 0 since log-scale " \
        "is requested. Set value was %f and reset value is now %f", x_low_new, MIN_LOG_SCALE)
        x_low_new = MIN_LOG_SCALE

    # compute what we need for the legend
    if reserve_ndc_top and not y_force_limits:
        if y_log:
            y_diff = log10(y_up_new) - log10(y_low_new)
            y_diff_up_user_no_user = log10(y_up_new) - log10(y_up_new_no_user)
        else:
            y_diff = y_up_new - y_low_new
            y_diff_up_user_no_user = y_up_new - y_up_new_no_user
        if y_diff_up_user_no_user / y_diff < reserve_ndc_top:
            get_logger().info("Add space to fit legend")
            y_diff_with_legend = y_diff / (1 - reserve_ndc_top)
            if y_log:
                y_up_new = 10**(log10(y_low_new) + y_diff_with_legend +
                                0.1 * y_diff)
            else:
                y_diff_with_legend = y_diff / (1 - reserve_ndc_top)
                y_up_new = y_low_new + y_diff_with_legend + 0.1 * y_diff

    elif reserve_ndc_bottom and not y_force_limits:
        if y_log:
            y_diff = log10(y_up_new) - log10(y_low_new)
            y_diff_low_user_no_user = log10(y_low_new_no_user) - log10(
                y_low_new)
        else:
            y_diff = y_up_new - y_low_new
            y_diff_low_user_no_user = y_low_new_no_user - y_low_new
        if y_diff_low_user_no_user / y_diff < reserve_ndc_bottom:
            get_logger().info("Add space to fit legend")
            y_diff_with_legend = y_diff / (1 - reserve_ndc_bottom)
            if y_log:
                y_low_new = 10**(log10(y_up_new) - y_diff_with_legend -
                                 0.1 * y_diff)
            else:
                y_diff_with_legend = y_diff / (1 - reserve_ndc_bottom)
                y_low_new = y_up_new - y_diff_with_legend - 0.1 * y_diff

    return x_low_new, x_up_new, y_low_new, y_up_new, z_low_new, z_up_new