Source code for rootpy.plotting.canvas

"""
This module implements python classes which inherit from
and extend the functionality of the ROOT canvas classes.
"""
from __future__ import absolute_import

from .. import ROOT, QROOT, asrootpy
from .base import convert_color
from ..base import NamedObject
from ..context import invisible_canvas
from ..decorators import snake_case_methods
from ..memory.keepalive import keepalive

from array import array

__all__ = [
    'Pad',
    'Canvas',
]


class _PadBase(NamedObject):

    # https://sft.its.cern.ch/jira/browse/ROOT-9007
    # can remove this after 6.10/04
    EmitVA = None

    def cd(self, *args):
        pad = asrootpy(super(_PadBase, self).cd(*args))
        if pad and pad is not self:
            keepalive(self, pad)
        return pad

    def axes(self, ndim=1,
             xlimits=None, ylimits=None, zlimits=None,
             xbins=1, ybins=1, zbins=1):
        """
        Create and return axes on this pad
        """
        if xlimits is None:
            xlimits = (0, 1)
        if ylimits is None:
            ylimits = (0, 1)
        if zlimits is None:
            zlimits = (0, 1)
        if ndim == 1:
            from .hist import Hist
            hist = Hist(1, xlimits[0], xlimits[1])
        elif ndim == 2:
            from .hist import Hist2D
            hist = Hist2D(1, xlimits[0], xlimits[1],
                          1, ylimits[0], ylimits[1])
        elif ndim == 3:
            from .hist import Hist3D
            hist = Hist3D(1, xlimits[0], xlimits[1],
                          1, ylimits[0], ylimits[1],
                          1, zlimits[0], zlimits[1])
        else:
            raise ValueError("ndim must be 1, 2, or 3")
        with self:
            hist.Draw('AXIS')
        xaxis = hist.xaxis
        yaxis = hist.yaxis
        if isinstance(xbins, (list, tuple)):
            xbins = array('d', xbins)
        if hasattr(xbins, '__iter__'):
            xaxis.Set(len(xbins) - 1, xbins)
        else:
            xaxis.Set(xbins, *xlimits)
        if ndim > 1:
            if isinstance(ybins, (list, tuple)):
                ybins = array('d', ybins)
            if hasattr(ybins, '__iter__'):
                yaxis.Set(len(ybins) - 1, ybins)
            else:
                yaxis.Set(ybins, *ylimits)
        else:
            yaxis.limits = ylimits
            yaxis.range_user = ylimits
        if ndim > 1:
            zaxis = hist.zaxis
            if ndim == 3:
                if isinstance(zbins, (list, tuple)):
                    zbins = array('d', zbins)
                if hasattr(zbins, '__iter__'):
                    zaxis.Set(len(zbins) - 1, zbins)
                else:
                    zaxis.Set(zbins, *zlimits)
            else:
                zaxis.limits = zlimits
                zaxis.range_user = zlimits
            return xaxis, yaxis, zaxis
        return xaxis, yaxis

    @property
    def primitives(self):
        return asrootpy(self.GetListOfPrimitives())

    def find_all_primitives(self):
        """
        Recursively find all primities on a pad, even those hiding behind a
        GetListOfFunctions() of a primitive
        """
        # delayed import to avoid circular import
        from .utils import find_all_primitives
        return find_all_primitives(self)

    @property
    def canvas(self):
        return asrootpy(self.GetCanvas())

    @property
    def mother(self):
        return asrootpy(self.GetMother())

    @property
    def margin(self):
        return (self.GetLeftMargin(), self.GetRightMargin(),
                self.GetBottomMargin(), self.GetTopMargin())

    @margin.setter
    def margin(self, bounds):
        left, right, bottom, top = bounds
        super(_PadBase, self).SetMargin(left, right, bottom, top)

    @property
    def margin_pixels(self):
        left, right, bottom, top = self.margin
        width = self.width_pixels
        height = self.height_pixels
        return (int(left * width), int(right * width),
                int(bottom * height), int(top * height))

    @margin_pixels.setter
    def margin_pixels(self, bounds):
        left, right, bottom, top = bounds
        width = float(self.width_pixels)
        height = float(self.height_pixels)
        super(_PadBase, self).SetMargin(left / width, right / width,
                                        bottom / height, top / height)

    @property
    def range(self):
        x1, y1 = ROOT.Double(), ROOT.Double()
        x2, y2 = ROOT.Double(), ROOT.Double()
        super(_PadBase, self).GetRange(x1, y1, x2, y2)
        return x1, y1, x2, y2

    @range.setter
    def range(self, bounds):
        x1, y1, x2, y2 = bounds
        super(_PadBase, self).Range(x1, y1, x2, y2)

    @property
    def range_axis(self):
        x1, y1 = ROOT.Double(), ROOT.Double()
        x2, y2 = ROOT.Double(), ROOT.Double()
        super(_PadBase, self).GetRangeAxis(x1, y1, x2, y2)
        return x1, y1, x2, y2

    @range_axis.setter
    def range_axis(self, bounds):
        x1, y1, x2, y2 = bounds
        super(_PadBase, self).RangeAxis(x1, y1, x2, y2)

    def __enter__(self):
        self._prev_pad = ROOT.gPad
        self.cd()
        return self

    def __exit__(self, type, value, traceback):
        # similar to preserve_current_canvas in rootpy/context.py
        if self._prev_pad:
            self._prev_pad.cd()
        elif ROOT.gPad:
            # Put things back how they were before.
            with invisible_canvas():
                # This is a round-about way of resetting gPad to None.
                # No other technique I tried could do it.
                pass
        self._prev_pad = None
        return False


[docs]@snake_case_methods class Pad(_PadBase, QROOT.TPad): _ROOT = QROOT.TPad def __init__(self, xlow, ylow, xup, yup, color=-1, bordersize=-1, bordermode=-2, name=None, title=None): color = convert_color(color, 'root') super(Pad, self).__init__(xlow, ylow, xup, yup, color, bordersize, bordermode, name=name, title=title) def Draw(self, *args): ret = super(Pad, self).Draw(*args) canvas = self.GetCanvas() keepalive(canvas, self) return ret @property def width(self): return self.GetWNDC() @property def height(self): return self.GetHNDC() @property def width_pixels(self): mother = self.mother canvas = self.canvas w = self.GetWNDC() while mother is not canvas: w *= mother.GetWNDC() mother = mother.mother return int(w * mother.width) @property def height_pixels(self): mother = self.mother canvas = self.canvas h = self.GetHNDC() while mother is not canvas: h *= mother.GetHNDC() mother = mother.mother return int(h * mother.height)
[docs]@snake_case_methods class Canvas(_PadBase, QROOT.TCanvas): _ROOT = QROOT.TCanvas def __init__(self, width=None, height=None, x=None, y=None, name=None, title=None, size_includes_decorations=False): # The following line will trigger finalSetup and start the graphics # thread if not started already style = ROOT.gStyle if width is None: width = style.GetCanvasDefW() if height is None: height = style.GetCanvasDefH() if x is None: x = style.GetCanvasDefX() if y is None: y = style.GetCanvasDefY() super(Canvas, self).__init__(x, y, width, height, name=name, title=title) if not size_includes_decorations: # Canvas dimensions include the window manager's decorations by # default in vanilla ROOT. I think this is a bad default. # Since in the most common case I don't care about the window # decorations, the default will be to set the dimensions of the # paintable area of the canvas. if self.IsBatch(): self.SetCanvasSize(width, height) else: self.SetWindowSize(width + (width - self.GetWw()), height + (height - self.GetWh())) self.size_includes_decorations = size_includes_decorations @property def width(self): return self.GetWw() @width.setter def width(self, value): value = int(value) if self.IsBatch(): self.SetCanvasSize(value, self.GetWh()) else: curr_height = self.GetWh() self.SetWindowSize(value, curr_height) if not getattr(self, 'size_includes_decorations', False): self.SetWindowSize(value + (value - self.GetWw()), curr_height + (curr_height - self.GetWh())) @property def width_pixels(self): return self.GetWw() @width_pixels.setter def width_pixels(self, value): self.width = value @property def height(self): return self.GetWh() @height.setter def height(self, value): value = int(value) if self.IsBatch(): self.SetCanvasSize(self.GetWw(), value) else: curr_width = self.GetWw() self.SetWindowSize(curr_width, value) if not getattr(self, 'size_includes_decorations', False): self.SetWindowSize(curr_width + (curr_width - self.GetWw()), value + (value - self.GetWh())) @property def height_pixels(self): return self.GetWh() @height_pixels.setter def height_pixels(self, value): self.height = value