Source code for MPF.pad

import collections

import ROOT
from .commonHelpers.logger import logger
logger = logger.getChild(__name__)

from . import pyrootHelpers as PH
from .histograms import HistogramStack, getHM
from .line import Line, CutLine
from . import globalStyle as gst
from .labels import scaleYPosTopMargin

[docs]class Pad(ROOT.TPad): def __init__(self, name, size=(0,0,1,1), logy=False, xTitle=None, yTitle= None, xTitleOffset=1, yTitleOffset=gst.yTitleOffset, yMinimum=None, yMaximum=None, yAxisNDivisions=None, gridy=None, removeXLabels=False, setNoExponent=False, xTitleScale=1, yTitleScale=1, xAxisLabelsOption=None, insideTopMargin=None, debugText=[], customTexts=None): self.xlow, self.ylow, self.xhigh, self.yhigh = size super(Pad, self).__init__(name, name, self.xlow, self.ylow, self.xhigh, self.yhigh) self.Draw() # do we need that here? self.drawables = [] # containes all objects on which .draw() will be called self.logy = logy self.xTitle = xTitle self.xTitleOffset = xTitleOffset self.yTitle = yTitle self.yTitleOffset = yTitleOffset self.debugText = debugText if customTexts is not None: self.customTexts = customTexts else: self.customTexts = [] self.yMinimum = yMinimum self.yMaximum = yMaximum self.yAxisNDivisions = yAxisNDivisions self.gridy = gridy self.removeXLabels = removeXLabels self.setNoExponent = setNoExponent self.xTitleScale=xTitleScale self.yTitleScale=yTitleScale self.insideTopMargin = insideTopMargin self.xAxisLabelsOption = xAxisLabelsOption self.histogramsMinimum = None self.histogramsMaximum = None self.legend = None self.referenceHist = None # In case there is no histogram assigned to the plot the axis are determined from this histogram self.projection = False self.relSize = 1
[docs] def getHistograms(self): for db in self.drawables: if issubclass(type(db), ROOT.TH1): yield db if issubclass(type(db), ROOT.THStack): yield db
[docs] def draw(self, rangeErrors=False, printOverflowBins=False): logger.debug('drawing {}'.format(self)) logger.debug('drawables: {}'.format(self.drawables)) self.cd() if self.gridy is not None: logger.debug("Setting Gridy!") self.SetGridy(self.gridy) # Force recalculation of min and max self.setMinMax() if not self.logy: self.SetLogy(0) else: self.SetLogy(1) if self.referenceHist is not None: logger.debug("Adding reference hist {}".format(self.referenceHist)) self.drawables.append(self.referenceHist) self.drawables.sort(key=lambda db: db.zIndex) # decorate histograms and set min/max for hist in self.getHistograms(): logger.debug("Call SetMinimum for {} (setting to {})".format(hist, self.getMinimum())) hist.SetMinimum(self.getMinimum()) hist.minimum = self.getMinimum() logger.debug("Call SetMaximum for {} (setting to {})".format(hist, self.getMaximum())) hist.SetMaximum(self.getMaximum()) hist.maximum = self.getMaximum() self.decorateDrawable(hist) # draw the first histogram firstDrawn = None for hist in self.getHistograms(): hist.firstDraw() firstDrawn = hist break # draw the rest for drawable in self.drawables: drawable.minimum = self.getMinimum() drawable.maximum = self.getMaximum() drawable.logy = self.logy if not drawable is firstDrawn: logger.debug("Drawing {} to pad {}".format(drawable, self.GetName())) drawable.draw(drawString='same') self.printOverflow(printOverflowBins) self.drawText() if self.legend is not None: self.legend.draw() self.reDrawAxes() # Puts axis ticks above backgrounds self.Draw() self.Modified() self.Update()
[docs] def setMinMax(self): """Set Minimum and maximum calculated from all histogram-like objects""" self.histogramsMinimum, self.histogramsMaximum = PH.getMinMaxWithErrors(*self.getHistograms(), maximumWithErrors=gst.maximumWithErrors, minimumWithErrors=gst.minimumWithErrors) logger.debug("Determined Minimum: {}".format(self.histogramsMinimum)) logger.debug("Determined Maximum: {}".format(self.histogramsMaximum))
[docs] def getHistogramsMaximum(self, errors=False): """Get the maximum of all histogram-like objects""" if self.histogramsMaximum is None: self.setMinMax() return self.histogramsMaximum
[docs] def getHistogramsMinimum(self): """Get the minimum of all histogram-like objects""" if self.histogramsMinimum is None: self.setMinMax() return self.histogramsMinimum
[docs] def getMinimum(self, errors=False): """Get the minimum which is going to be used for the actual plot""" if self.yMinimum is not None: logger.debug("Using custom minimum {}".format(self.yMinimum)) return self.yMinimum minimum = self.getHistogramsMinimum() preliminary_maximum = self.getHistogramsMaximum() if minimum is None: minimum = 0. if minimum > 0: if self.logy: minimum = 0.1*minimum else: minimum = 0 if minimum <= 0 and self.logy: logger.debug("Log scale - using {} instead of {} as Minimum".format(gst.defaultLogyYmin, minimum)) minimum = gst.defaultLogyYmin if minimum >= preliminary_maximum: logger.warning("Minimum might become larger as maximum (globalStyle.defaultLogyYmin = {})" "- setting to 10% of (unscaled) maximum: {}" .format(gst.defaultLogyYmin, 0.1*preliminary_maximum)) minimum = 0.1*preliminary_maximum logger.debug("Returning minimum: {}".format(minimum)) return minimum
[docs] def getMaximum(self, errors=False): """Get the maximum which is going to be used for the actual plot""" if self.yMaximum is not None: logger.debug("Using custom maximum {}".format(self.yMaximum)) return self.yMaximum minimum = self.getMinimum() maximum = self.getHistogramsMaximum() if self.insideTopMargin is not None: returnmax = PH.calculateMarginMaximum(self.insideTopMargin/self.GetAbsHNDC(), minimum, maximum, logy=self.logy) else: if not self.logy: returnmax = 1.5*maximum else: returnmax = 5*maximum if maximum < minimum: raise ValueError("Maximum got smaller than minimum! - Something is wrong") if self.logy and returnmax <= 0: logger.warning("Maximum <= 0! Returning : {}".format(2*minimum)) return 2*minimum else: logger.debug("determined Maximum: {}".format(returnmax)) return returnmax
[docs] def printOverflow(self, printOverflowBins): threshold = 5 if printOverflowBins: for histogram in self.getHistograms(): try: if histogram.underflow()/histogram.Integral() > threshold/100.: self.debugText.append('uflow {}, {:.1e}'.format(histogram.legend, histogram.underflow())) except ZeroDivisionError: if histogram.underflow() > 0.0: self.debugText.append('uflow {}, {:.1e}'.format(histogram.legend, histogram.underflow())) try: if histogram.overflow()/histogram.Integral() > threshold/100.: self.debugText.append('oflow {}, {:.1e}'.format(histogram.legend, histogram.overflow())) except ZeroDivisionError: if histogram.overflow() > 0.0: self.debugText.append('oflow {}, {:.1e}'.format(histogram.legend, histogram.overflow()))
[docs] def drawText(self): yspacing = 0.06 yup = 0.95 for i, line in enumerate(self.debugText): l = ROOT.TLatex() l.SetNDC() l.SetTextColor(ROOT.kBlack) l.DrawLatex(0.2,yup-i*yspacing, "{}".format(line)) for xpos, ypos, text in self.customTexts: l = ROOT.TLatex() l.SetNDC() l.SetTextColor(ROOT.kBlack) l.SetTextFont(gst.customTextFont) l.SetTextSize(gst.customTextSize) l.DrawLatex(xpos, scaleYPosTopMargin(ypos), "{}".format(text))
[docs] def reDrawAxes(self): self.RedrawAxis() self.RedrawAxis("g")
[docs] def getXTitle(self): """ algorithm to get our xTitle for the pad """ # if explicitly set use this one if self.xTitle is not None: return self.xTitle candidates = collections.OrderedDict() for d in self.drawables: try: title = d.getXTitle() if title is not None: candidates[title] = 1 except AttributeError as e1: logger.debug(e1) candidates = list(candidates.keys()) logger.debug("X-Title candidates: {}".format(candidates)) if len(candidates) == 1: return candidates[0] elif len(candidates) == 0: logger.debug("no xTitle found for {}".format(self)) return '' else: logger.warn("multiple xTitles found: {} - taking first one".format(candidates)) return candidates[0]
[docs] def getYTitle(self): """ algorithm to get our yTitle for the pad Currently - this is exactly the same function as for the xTitle - maybe generalise this """ # if explicitly set use this one if self.yTitle is not None: return self.yTitle candidates = collections.OrderedDict() for d in self.drawables: try: title = d.getYTitle() if title is not None: candidates[title] = 1 except AttributeError as e1: logger.debug(e1) candidates = list(candidates.keys()) logger.debug("Y-Title candidates: {}".format(candidates)) if len(candidates) == 1: return candidates[0] elif len(candidates) == 0: logger.debug("no yTitle found for {}".format(self)) return '' else: logger.warn("multiple yTitles found: {} - taking first one".format(candidates)) return candidates[0]
# def getRange(self): # try: # return self.rangeDown, self.rangeUp # except AttributeError: # for drawable in self.drawables: # try: # self.rangeDown = drawable.GetXaxis().GetBinLowEdge(1) # self.rangeUp = drawable.GetXaxis().GetBinUpEdge(drawable.GetNbinsX()) # return self.rangeDown, self.rangeUp # except (AttributeError, ReferenceError): # pass
[docs] def addReferenceHist(self, hist): refHist = hist.Clone(next(PH.tempNames)) refHist.Reset() refHist.SetDirectory(0) # refHist.Fill(1,1) self.referenceHist = getHM(refHist) self.referenceHist.cloneAttributes(self.referenceHist, hist) self.referenceHist.zIndex = -100 self.referenceHist.style = "axis"
[docs] def addVertLine(self, cutValue, **kwargs): line = CutLine(cutValue, self.getMinimum(), cutValue, 0.8*self.getMaximum(), **kwargs) self.drawables.append(line)
[docs] def decorateDrawable(self, drawable): if issubclass(type(drawable), ROOT.THStack): # Take axis hist of HistogramStack xaxis = drawable.axisHist.GetXaxis() yaxis = drawable.axisHist.GetYaxis() else: xaxis = drawable.GetXaxis() yaxis = drawable.GetYaxis() if xaxis == None: raise AttributeError('no axis found for {}',format(drawable)) xaxis.SetTitle(self.getXTitle()) yaxis.SetTitle(self.getYTitle()) if not self.yAxisNDivisions is None: # ToDo: Check how this interferes with SetRangeUser yaxis.SetNdivisions(self.yAxisNDivisions, True) if self.setNoExponent: yaxis.SetNoExponent(True) for axis in [xaxis, yaxis]: if gst.labelFont is not None: axis.SetLabelFont(gst.labelFont) axis.SetTitleFont(gst.labelFont) if gst.labelFontSize is not None: if axis == xaxis: titleScale = self.xTitleScale elif axis == yaxis: titleScale = self.yTitleScale else: titleScale = 1. axis.SetLabelSize(gst.labelFontSize) axis.SetTitleSize(gst.labelFontSize*titleScale) if self.xAxisLabelsOption is not None: xaxis.LabelsOption(self.xAxisLabelsOption) # fucking order matters! xaxis.SetTitleOffset(self.xTitleOffset/self.yTitleScale) yaxis.SetTitleOffset(self.yTitleOffset/self.yTitleScale) if self.removeXLabels: xaxis.SetLabelSize(0) xaxis.SetTitleSize(0)
# if issubclass(type(drawable), ROOT.TGraph): # # histo = drawable.GetHistogram() # # histo.GetXaxis().SetRangeUser(*self.getRange()) # # zaxis = histo.GetZaxis() # pass # else: # zaxis = drawable.GetZaxis() # try: # drawable.GetXaxis().SetRangeUser(*self.getRange()) # except ReferenceError: # pass