CMS 3D CMS Logo

plotting.py
Go to the documentation of this file.
1 import os
2 import sys
3 import math
4 import copy
5 import array
6 import difflib
7 import collections
8 
9 import ROOT
10 ROOT.gROOT.SetBatch(True)
11 ROOT.PyConfig.IgnoreCommandLineOptions = True
12 
13 import html
14 
15 verbose=False
16 _ratioYTitle = "Ratio"
17 
18 def _setStyle():
19  _absoluteSize = True
20  if _absoluteSize:
21  font = 43
22  titleSize = 22
23  labelSize = 22
24  statSize = 14
25  else:
26  font = 42
27  titleSize = 0.05
28  labelSize = 0.05
29  statSize = 0.025
30 
31  ROOT.gROOT.SetStyle("Plain")
32  ROOT.gStyle.SetPadRightMargin(0.07)
33  ROOT.gStyle.SetPadLeftMargin(0.13)
34  ROOT.gStyle.SetTitleFont(font, "XYZ")
35  ROOT.gStyle.SetTitleSize(titleSize, "XYZ")
36  ROOT.gStyle.SetTitleOffset(1.2, "Y")
37  #ROOT.gStyle.SetTitleFontSize(0.05)
38  ROOT.gStyle.SetLabelFont(font, "XYZ")
39  ROOT.gStyle.SetLabelSize(labelSize, "XYZ")
40  ROOT.gStyle.SetTextSize(labelSize)
41  ROOT.gStyle.SetStatFont(font)
42  ROOT.gStyle.SetStatFontSize(statSize)
43 
44  ROOT.TGaxis.SetMaxDigits(4)
45 
46 def _getObject(tdirectory, name):
47  obj = tdirectory.Get(name)
48  if not obj:
49  if verbose:
50  print "Did not find {obj} from {dir}".format(obj=name, dir=tdirectory.GetPath())
51  return None
52  return obj
53 
54 def _getOrCreateObject(tdirectory, nameOrCreator):
55  if hasattr(nameOrCreator, "create"):
56  return nameOrCreator.create(tdirectory)
57  return _getObject(tdirectory, nameOrCreator)
58 
60  class FileNotExist: pass
62  class SubDirNotExist: pass
63 
64  @staticmethod
65  def codesToNone(code):
67  return None
68  return code
69 
70 def _getDirectoryDetailed(tfile, possibleDirs, subDir=None):
71  """Get TDirectory from TFile."""
72  if tfile is None:
74  for pdf in possibleDirs:
75  d = tfile.Get(pdf)
76  if d:
77  if subDir is not None:
78  # Pick associator if given
79  d = d.Get(subDir)
80  if d:
81  return d
82  else:
83  if verbose:
84  print "Did not find subdirectory '%s' from directory '%s' in file %s" % (subDir, pdf, tfile.GetName())
85 # if "Step" in subDir:
86 # raise Exception("Foo")
88  else:
89  return d
90  if verbose:
91  print "Did not find any of directories '%s' from file %s" % (",".join(possibleDirs), tfile.GetName())
93 
94 def _getDirectory(*args, **kwargs):
95  return GetDirectoryCode.codesToNone(_getDirectoryDetailed(*args, **kwargs))
96 
97 def _th1ToOrderedDict(th1, renameBin=None):
98  values = collections.OrderedDict()
99  for i in xrange(1, th1.GetNbinsX()+1):
100  binLabel = th1.GetXaxis().GetBinLabel(i)
101  if renameBin is not None:
102  binLabel = renameBin(binLabel)
103  values[binLabel] = (th1.GetBinContent(i), th1.GetBinError(i))
104  return values
105 
106 def _createCanvas(name, width, height):
107  # silence warning of deleting canvas with the same name
108  if not verbose:
109  backup = ROOT.gErrorIgnoreLevel
110  ROOT.gErrorIgnoreLevel = ROOT.kError
111  canvas = ROOT.TCanvas(name, name, width, height)
112  if not verbose:
113  ROOT.gErrorIgnoreLevel = backup
114  return canvas
115 
116 def _modifyPadForRatio(pad, ratioFactor):
117  pad.Divide(1, 2)
118 
119  divisionPoint = 1-1/ratioFactor
120 
121  topMargin = pad.GetTopMargin()
122  bottomMargin = pad.GetBottomMargin()
123  divisionPoint += (1-divisionPoint)*bottomMargin # correct for (almost-)zeroing bottom margin of pad1
124  divisionPointForPad1 = 1-( (1-divisionPoint) / (1-0.02) ) # then correct for the non-zero bottom margin, but for pad1 only
125 
126  # Set the lower point of the upper pad to divisionPoint
127  pad1 = pad.cd(1)
128  yup = 1.0
129  ylow = divisionPointForPad1
130  xup = 1.0
131  xlow = 0.0
132  pad1.SetPad(xlow, ylow, xup, yup)
133  pad1.SetFillStyle(4000) # transparent
134  pad1.SetBottomMargin(0.02) # need some bottom margin here for eps/pdf output (at least in ROOT 5.34)
135 
136  # Set the upper point of the lower pad to divisionPoint
137  pad2 = pad.cd(2)
138  yup = divisionPoint
139  ylow = 0.0
140  pad2.SetPad(xlow, ylow, xup, yup)
141  pad2.SetFillStyle(4000) # transparent
142  pad2.SetTopMargin(0.0)
143  pad2.SetBottomMargin(bottomMargin/(ratioFactor*divisionPoint))
144 
145 def _calculateRatios(histos, ratioUncertainty=False):
146  """Calculate the ratios for a list of histograms"""
147 
148  def _divideOrZero(numerator, denominator):
149  if denominator == 0:
150  return 0
151  return numerator/denominator
152 
153  def equal(a, b):
154  if a == 0. and b == 0.:
155  return True
156  return abs(a-b)/max(abs(a),abs(b)) < 1e-3
157 
158  def findBins(wrap, bins_xvalues):
159  ret = []
160  currBin = wrap.begin()
161  i = 0
162  while i < len(bins_xvalues) and currBin < wrap.end():
163  (xcenter, xlow, xhigh) = bins_xvalues[i]
164  xlowEdge = xcenter-xlow
165  xupEdge = xcenter+xhigh
166 
167  (curr_center, curr_low, curr_high) = wrap.xvalues(currBin)
168  curr_lowEdge = curr_center-curr_low
169  curr_upEdge = curr_center+curr_high
170 
171  if equal(xlowEdge, curr_lowEdge) and equal(xupEdge, curr_upEdge):
172  ret.append(currBin)
173  currBin += 1
174  i += 1
175  elif curr_upEdge <= xlowEdge:
176  currBin += 1
177  elif curr_lowEdge >= xupEdge:
178  ret.append(None)
179  i += 1
180  else:
181  ret.append(None)
182  currBin += 1
183  i += 1
184  if len(ret) != len(bins_xvalues):
185  ret.extend([None]*( len(bins_xvalues) - len(ret) ))
186  return ret
187 
188  # Define wrappers for TH1/TGraph/TGraph2D to have uniform interface
189  # TODO: having more global wrappers would make some things simpler also elsewhere in the code
190  class WrapTH1:
191  def __init__(self, th1, uncertainty):
192  self._th1 = th1
193  self._uncertainty = uncertainty
194 
195  xaxis = th1.GetXaxis()
196  xaxis_arr = xaxis.GetXbins()
197  if xaxis_arr.GetSize() > 0: # unequal binning
198  lst = [xaxis_arr[i] for i in xrange(0, xaxis_arr.GetSize())]
199  arr = array.array("d", lst)
200  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), arr)
201  else:
202  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), xaxis.GetXmin(), xaxis.GetXmax())
203  _copyStyle(th1, self._ratio)
204  self._ratio.SetStats(0)
205  self._ratio.SetLineColor(ROOT.kBlack)
206  self._ratio.SetLineWidth(1)
207  def draw(self, style=None):
208  st = style
209  if st is None:
210  if self._uncertainty:
211  st = "EP"
212  else:
213  st = "HIST P"
214  self._ratio.Draw("same "+st)
215  def begin(self):
216  return 1
217  def end(self):
218  return self._th1.GetNbinsX()+1
219  def xvalues(self, bin):
220  xval = self._th1.GetBinCenter(bin)
221  xlow = xval-self._th1.GetXaxis().GetBinLowEdge(bin)
222  xhigh = self._th1.GetXaxis().GetBinUpEdge(bin)-xval
223  return (xval, xlow, xhigh)
224  def yvalues(self, bin):
225  yval = self._th1.GetBinContent(bin)
226  yerr = self._th1.GetBinError(bin)
227  return (yval, yerr, yerr)
228  def y(self, bin):
229  return self._th1.GetBinContent(bin)
230  def divide(self, bin, scale):
231  self._ratio.SetBinContent(bin, _divideOrZero(self._th1.GetBinContent(bin), scale))
232  self._ratio.SetBinError(bin, _divideOrZero(self._th1.GetBinError(bin), scale))
233  def makeRatio(self):
234  pass
235  def getRatio(self):
236  return self._ratio
237 
238  class WrapTGraph:
239  def __init__(self, gr, uncertainty):
240  self._gr = gr
241  self._uncertainty = uncertainty
242  self._xvalues = []
243  self._xerrslow = []
244  self._xerrshigh = []
245  self._yvalues = []
246  self._yerrshigh = []
247  self._yerrslow = []
248  def draw(self, style=None):
249  if self._ratio is None:
250  return
251  st = style
252  if st is None:
253  if self._uncertainty:
254  st = "PZ"
255  else:
256  st = "PX"
257  self._ratio.Draw("same "+st)
258  def begin(self):
259  return 0
260  def end(self):
261  return self._gr.GetN()
262  def xvalues(self, bin):
263  return (self._gr.GetX()[bin], self._gr.GetErrorXlow(bin), self._gr.GetErrorXhigh(bin))
264  def yvalues(self, bin):
265  return (self._gr.GetY()[bin], self._gr.GetErrorYlow(bin), self._gr.GetErrorYhigh(bin))
266  def y(self, bin):
267  return self._gr.GetY()[bin]
268  def divide(self, bin, scale):
269  # Ignore bin if denominator is zero
270  if scale == 0:
271  return
272  # No more items in the numerator
273  if bin >= self._gr.GetN():
274  return
275  # denominator is missing an item
276  xvals = self.xvalues(bin)
277  xval = xvals[0]
278 
279  self._xvalues.append(xval)
280  self._xerrslow.append(xvals[1])
281  self._xerrshigh.append(xvals[2])
282  yvals = self.yvalues(bin)
283  self._yvalues.append(yvals[0] / scale)
284  if self._uncertainty:
285  self._yerrslow.append(yvals[1] / scale)
286  self._yerrshigh.append(yvals[2] / scale)
287  else:
288  self._yerrslow.append(0)
289  self._yerrshigh.append(0)
290  def makeRatio(self):
291  if len(self._xvalues) == 0:
292  self._ratio = None
293  return
294  self._ratio = ROOT.TGraphAsymmErrors(len(self._xvalues), array.array("d", self._xvalues), array.array("d", self._yvalues),
295  array.array("d", self._xerrslow), array.array("d", self._xerrshigh),
296  array.array("d", self._yerrslow), array.array("d", self._yerrshigh))
297  _copyStyle(self._gr, self._ratio)
298  def getRatio(self):
299  return self._ratio
300  class WrapTGraph2D(WrapTGraph):
301  def __init__(self, gr, uncertainty):
302  WrapTGraph.__init__(self, gr, uncertainty)
303  def xvalues(self, bin):
304  return (self._gr.GetX()[bin], self._gr.GetErrorX(bin), self._gr.GetErrorX(bin))
305  def yvalues(self, bin):
306  return (self._gr.GetY()[bin], self._gr.GetErrorY(bin), self._gr.GetErrorY(bin))
307 
308  def wrap(o):
309  if isinstance(o, ROOT.TH1):
310  return WrapTH1(o, ratioUncertainty)
311  elif isinstance(o, ROOT.TGraph):
312  return WrapTGraph(o, ratioUncertainty)
313  elif isinstance(o, ROOT.TGraph2D):
314  return WrapTGraph2D(o, ratioUncertainty)
315 
316  wrappers = [wrap(h) for h in histos]
317  ref = wrappers[0]
318 
319  wrappers_bins = []
320  ref_bins = [ref.xvalues(b) for b in xrange(ref.begin(), ref.end())]
321  for w in wrappers:
322  wrappers_bins.append(findBins(w, ref_bins))
323 
324  for i, bin in enumerate(xrange(ref.begin(), ref.end())):
325  (scale, ylow, yhigh) = ref.yvalues(bin)
326  for w, bins in zip(wrappers, wrappers_bins):
327  if bins[i] is None:
328  continue
329  w.divide(bins[i], scale)
330 
331  for w in wrappers:
332  w.makeRatio()
333 
334  return wrappers
335 
336 
337 def _getXmin(obj, limitToNonZeroContent=False):
338  if isinstance(obj, ROOT.TH1):
339  xaxis = obj.GetXaxis()
340  if limitToNonZeroContent:
341  for i in xrange(1, obj.GetNbinsX()+1):
342  if obj.GetBinContent(i) != 0:
343  return xaxis.GetBinLowEdge(i)
344  # None for all bins being zero
345  return None
346  else:
347  return xaxis.GetBinLowEdge(xaxis.GetFirst())
348  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
349  m = min([obj.GetX()[i] for i in xrange(0, obj.GetN())])
350  return m*0.9 if m > 0 else m*1.1
351  raise Exception("Unsupported type %s" % str(obj))
352 
353 def _getXmax(obj, limitToNonZeroContent=False):
354  if isinstance(obj, ROOT.TH1):
355  xaxis = obj.GetXaxis()
356  if limitToNonZeroContent:
357  for i in xrange(obj.GetNbinsX(), 0, -1):
358  if obj.GetBinContent(i) != 0:
359  return xaxis.GetBinUpEdge(i)
360  # None for all bins being zero
361  return None
362  else:
363  return xaxis.GetBinUpEdge(xaxis.GetLast())
364  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
365  m = max([obj.GetX()[i] for i in xrange(0, obj.GetN())])
366  return m*1.1 if m > 0 else m*0.9
367  raise Exception("Unsupported type %s" % str(obj))
368 
369 def _getYmin(obj, limitToNonZeroContent=False):
370  if isinstance(obj, ROOT.TH2):
371  yaxis = obj.GetYaxis()
372  return yaxis.GetBinLowEdge(yaxis.GetFirst())
373  elif isinstance(obj, ROOT.TH1):
374  if limitToNonZeroContent:
375  lst = [obj.GetBinContent(i) for i in xrange(1, obj.GetNbinsX()+1) if obj.GetBinContent(i) != 0 ]
376  return min(lst) if len(lst) != 0 else 0
377  else:
378  return obj.GetMinimum()
379  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
380  m = min([obj.GetY()[i] for i in xrange(0, obj.GetN())])
381  return m*0.9 if m > 0 else m*1.1
382  raise Exception("Unsupported type %s" % str(obj))
383 
384 def _getYmax(obj, limitToNonZeroContent=False):
385  if isinstance(obj, ROOT.TH2):
386  yaxis = obj.GetYaxis()
387  return yaxis.GetBinUpEdge(yaxis.GetLast())
388  elif isinstance(obj, ROOT.TH1):
389  if limitToNonZeroContent:
390  lst = [obj.GetBinContent(i) for i in xrange(1, obj.GetNbinsX()+1) if obj.GetBinContent(i) != 0 ]
391  return max(lst) if len(lst) != 0 else 0
392  else:
393  return obj.GetMaximum()
394  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
395  m = max([obj.GetY()[i] for i in xrange(0, obj.GetN())])
396  return m*1.1 if m > 0 else m*0.9
397  raise Exception("Unsupported type %s" % str(obj))
398 
400  return max([th1.GetBinContent(i)+th1.GetBinError(i) for i in xrange(1, th1.GetNbinsX()+1)])
401 
403  yvals = filter(lambda n: n>0, [th1.GetBinContent(i) for i in xrange(1, th1.GetNbinsX()+1)])
404  yvals.sort()
405  if len(yvals) == 0:
406  return th1.GetMinimum()
407  if len(yvals) == 1:
408  return yvals[0]
409 
410  # Define outlier as being x10 less than minimum of the 95 % of the non-zero largest values
411  ind_min = len(yvals)-1 - int(len(yvals)*0.95)
412  min_val = yvals[ind_min]
413  for i in xrange(0, ind_min):
414  if yvals[i] > 0.1*min_val:
415  return yvals[i]
416 
417  return min_val
418 
419 def _getYminMaxAroundMedian(obj, coverage, coverageRange=None):
420  inRange = lambda x: True
421  inRange2 = lambda xmin,xmax: True
422  if coverageRange:
423  inRange = lambda x: coverageRange[0] <= x <= coverageRange[1]
424  inRange2 = lambda xmin,xmax: coverageRange[0] <= xmin and xmax <= coverageRange[1]
425 
426  if isinstance(obj, ROOT.TH1):
427  yvals = [obj.GetBinContent(i) for i in xrange(1, obj.GetNbinsX()+1) if inRange2(obj.GetXaxis().GetBinLowEdge(i), obj.GetXaxis().GetBinUpEdge(i))]
428  yvals = filter(lambda x: x != 0, yvals)
429  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
430  yvals = [obj.GetY()[i] for i in xrange(0, obj.GetN()) if inRange(obj.GetX()[i])]
431  else:
432  raise Exception("Unsupported type %s" % str(obj))
433  if len(yvals) == 0:
434  return (0, 0)
435  if len(yvals) == 1:
436  return (yvals[0], yvals[0])
437  if len(yvals) == 2:
438  return (yvals[0], yvals[1])
439 
440  yvals.sort()
441  nvals = int(len(yvals)*coverage)
442  if nvals < 2:
443  # Take median and +- 1 values
444  if len(yvals) % 2 == 0:
445  half = len(yvals)/2
446  return ( yvals[half-1], yvals[half] )
447  else:
448  middle = len(yvals)/2
449  return ( yvals[middle-1], yvals[middle+1] )
450  ind_min = (len(yvals)-nvals)/2
451  ind_max = len(yvals)-1 - ind_min
452 
453  return (yvals[ind_min], yvals[ind_max])
454 
455 def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None):
456  """Find x-y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
457 
458  Arguments:
459  th1s -- List of TH1s
460  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
461 
462  Keyword arguments:
463  xmin -- Minimum x value; if None, take the minimum of TH1s
464  xmax -- Maximum x value; if None, take the maximum of TH1s
465  ymin -- Minimum y value; if None, take the minimum of TH1s
466  ymax -- Maximum y value; if None, take the maximum of TH1s
467  """
468 
469  (ymin, ymax) = _findBoundsY(th1s, ylog, ymin, ymax)
470 
471  if xmin is None or xmax is None or isinstance(xmin, list) or isinstance(max, list):
472  xmins = []
473  xmaxs = []
474  for th1 in th1s:
475  xmins.append(_getXmin(th1, limitToNonZeroContent=isinstance(xmin, list)))
476  xmaxs.append(_getXmax(th1, limitToNonZeroContent=isinstance(xmax, list)))
477 
478  # Filter out cases where histograms have zero content
479  xmins = filter(lambda h: h is not None, xmins)
480  xmaxs = filter(lambda h: h is not None, xmaxs)
481 
482  if xmin is None:
483  xmin = min(xmins)
484  elif isinstance(xmin, list):
485  if len(xmins) == 0: # all histograms zero
486  xmin = min(xmin)
487  if verbose:
488  print "Histogram is zero, using the smallest given value for xmin from", str(xmin)
489  else:
490  xm = min(xmins)
491  xmins_below = filter(lambda x: x<=xm, xmin)
492  if len(xmins_below) == 0:
493  xmin = min(xmin)
494  if xm < xmin:
495  if verbose:
496  print "Histogram minimum x %f is below all given xmin values %s, using the smallest one" % (xm, str(xmin))
497  else:
498  xmin = max(xmins_below)
499 
500  if xmax is None:
501  xmax = max(xmaxs)
502  elif isinstance(xmax, list):
503  if len(xmaxs) == 0: # all histograms zero
504  xmax = max(xmax)
505  if verbose:
506  print "Histogram is zero, using the smallest given value for xmax from", str(xmin)
507  else:
508  xm = max(xmaxs)
509  xmaxs_above = filter(lambda x: x>xm, xmax)
510  if len(xmaxs_above) == 0:
511  xmax = max(xmax)
512  if xm > xmax:
513  if verbose:
514  print "Histogram maximum x %f is above all given xmax values %s, using the maximum one" % (xm, str(xmax))
515  else:
516  xmax = min(xmaxs_above)
517 
518  for th1 in th1s:
519  th1.GetXaxis().SetRangeUser(xmin, xmax)
520 
521  return (xmin, ymin, xmax, ymax)
522 
523 def _findBoundsY(th1s, ylog, ymin=None, ymax=None, coverage=None, coverageRange=None):
524  """Find y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
525 
526  Arguments:
527  th1s -- List of TH1s
528  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
529 
530  Keyword arguments:
531  ymin -- Minimum y value; if None, take the minimum of TH1s
532  ymax -- Maximum y value; if None, take the maximum of TH1s
533  coverage -- If set, use only values within the 'coverage' part around the median are used for min/max (useful for ratio)
534  coverageRange -- If coverage and this are set, use only the x axis specified by an (xmin,xmax) pair for the coverage
535  """
536  if coverage is not None or isinstance(th1s[0], ROOT.TH2):
537  # the only use case for coverage for now is ratio, for which
538  # the scalings are not needed (actually harmful), so let's
539  # just ignore them if 'coverage' is set
540  #
541  # Also for TH2 do not adjust automatic y bounds
542  y_scale_max = lambda y: y
543  y_scale_min = lambda y: y
544  else:
545  if ylog:
546  y_scale_max = lambda y: y*1.5
547  else:
548  y_scale_max = lambda y: y*1.05
549  y_scale_min = lambda y: y*0.9 # assuming log
550 
551  if ymin is None or ymax is None or isinstance(ymin, list) or isinstance(ymax, list):
552  ymins = []
553  ymaxs = []
554  for th1 in th1s:
555  if coverage is not None:
556  (_ymin, _ymax) = _getYminMaxAroundMedian(th1, coverage, coverageRange)
557  else:
558  if ylog and isinstance(ymin, list):
559  _ymin = _getYminIgnoreOutlier(th1)
560  else:
561  _ymin = _getYmin(th1, limitToNonZeroContent=isinstance(ymin, list))
562  _ymax = _getYmax(th1, limitToNonZeroContent=isinstance(ymax, list))
563 # _ymax = _getYmaxWithError(th1)
564 
565  ymins.append(_ymin)
566  ymaxs.append(_ymax)
567 
568  if ymin is None:
569  ymin = min(ymins)
570  elif isinstance(ymin, list):
571  ym_unscaled = min(ymins)
572  ym = y_scale_min(ym_unscaled)
573  ymins_below = filter(lambda y: y<=ym, ymin)
574  if len(ymins_below) == 0:
575  ymin = min(ymin)
576  if ym_unscaled < ymin:
577  if verbose:
578  print "Histogram minimum y %f is below all given ymin values %s, using the smallest one" % (ym, str(ymin))
579  else:
580  ymin = max(ymins_below)
581 
582  if ymax is None:
583  # in case ymax is automatic, ymin is set by list, and the
584  # histograms are zero, ensure here that ymax > ymin
585  ymax = y_scale_max(max(ymaxs+[ymin]))
586  elif isinstance(ymax, list):
587  ym_unscaled = max(ymaxs)
588  ym = y_scale_max(ym_unscaled)
589  ymaxs_above = filter(lambda y: y>ym, ymax)
590  if len(ymaxs_above) == 0:
591  ymax = max(ymax)
592  if ym_unscaled > ymax:
593  if verbose:
594  print "Histogram maximum y %f is above all given ymax values %s, using the maximum one" % (ym_unscaled, str(ymax))
595  else:
596  ymax = min(ymaxs_above)
597 
598  for th1 in th1s:
599  th1.GetYaxis().SetRangeUser(ymin, ymax)
600 
601  return (ymin, ymax)
602 
603 def _th1RemoveEmptyBins(histos, xbinlabels):
604  binsToRemove = set()
605  for b in xrange(1, histos[0].GetNbinsX()+1):
606  binEmpty = True
607  for h in histos:
608  if h.GetBinContent(b) > 0:
609  binEmpty = False
610  break
611  if binEmpty:
612  binsToRemove.add(b)
613 
614  if len(binsToRemove) > 0:
615  # filter xbinlabels
616  xbinlab_new = []
617  for i in xrange(len(xbinlabels)):
618  if (i+1) not in binsToRemove:
619  xbinlab_new.append(xbinlabels[i])
620  xbinlabels = xbinlab_new
621 
622  # filter histogram bins
623  histos_new = []
624  for h in histos:
625  values = []
626  for b in xrange(1, h.GetNbinsX()+1):
627  if b not in binsToRemove:
628  values.append( (h.GetXaxis().GetBinLabel(b), h.GetBinContent(b), h.GetBinError(b)) )
629 
630  if len(values) > 0:
631  h_new = h.Clone(h.GetName()+"_empty")
632  h_new.SetBins(len(values), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(values))
633  for b, (l, v, e) in enumerate(values):
634  h_new.GetXaxis().SetBinLabel(b+1, l)
635  h_new.SetBinContent(b+1, v)
636  h_new.SetBinError(b+1, e)
637 
638  histos_new.append(h_new)
639  histos = histos_new
640 
641  return (histos, xbinlabels)
642 
643 def _th2RemoveEmptyBins(histos, xbinlabels, ybinlabels):
644  xbinsToRemove = set()
645  ybinsToRemove = set()
646  for ih, h in enumerate(histos):
647  for bx in xrange(1, h.GetNbinsX()+1):
648  binEmpty = True
649  for by in xrange(1, h.GetNbinsY()+1):
650  if h.GetBinContent(bx, by) > 0:
651  binEmpty = False
652  break
653  if binEmpty:
654  xbinsToRemove.add(bx)
655  elif ih > 0:
656  xbinsToRemove.discard(bx)
657 
658  for by in xrange(1, h.GetNbinsY()+1):
659  binEmpty = True
660  for bx in xrange(1, h.GetNbinsX()+1):
661  if h.GetBinContent(bx, by) > 0:
662  binEmpty = False
663  break
664  if binEmpty:
665  ybinsToRemove.add(by)
666  elif ih > 0:
667  ybinsToRemove.discard(by)
668 
669  if len(xbinsToRemove) > 0 or len(ybinsToRemove) > 0:
670  xbinlabels_new = []
671  xbins = []
672  for b in xrange(1, len(xbinlabels)+1):
673  if b not in xbinsToRemove:
674  xbinlabels_new.append(histos[0].GetXaxis().GetBinLabel(b))
675  xbins.append(b)
676  xbinlabels = xbinlabels_new
677  ybinlabels_new = []
678  ybins = []
679  for b in xrange(1, len(ybinlabels)+1):
680  if b not in ybinsToRemove:
681  ybinlabels.append(histos[0].GetYaxis().GetBinLabel(b))
682  ybins.append(b)
683  ybinlabels = xbinlabels_new
684 
685  histos_new = []
686  if len(xbinlabels) == 0 or len(ybinlabels) == 0:
687  return (histos_new, xbinlabels, ybinlabels)
688  for h in histos:
689  h_new = ROOT.TH2F(h.GetName()+"_empty", h.GetTitle(), len(xbinlabels),0,len(xbinlabels), len(ybinlabels),0,len(ybinlabels))
690  for b, l in enumerate(xbinlabels):
691  h_new.GetXaxis().SetBinLabel(b+1, l)
692  for b, l in enumerate(ybinlabels):
693  h_new.GetYaxis().SetBinLabel(b+1, l)
694 
695  for ix, bx in enumerate(xbins):
696  for iy, by in enumerate(ybins):
697  h_new.SetBinContent(ix+1, iy+1, h.GetBinContent(bx, by))
698  h_new.SetBinError(ix+1, iy+1, h.GetBinError(bx, by))
699  histos_new.append(h_new)
700  histos = histos_new
701  return (histos, xbinlabels, ybinlabels)
702 
703 def _mergeBinLabelsX(histos):
704  return _mergeBinLabels([[h.GetXaxis().GetBinLabel(i) for i in xrange(1, h.GetNbinsX()+1)] for h in histos])
705 
706 def _mergeBinLabelsY(histos):
707  return _mergeBinLabels([[h.GetYaxis().GetBinLabel(i) for i in xrange(1, h.GetNbinsY()+1)] for h in histos])
708 
709 def _mergeBinLabels(labelsAll):
710  labels_merged = labelsAll[0]
711  for labels in labelsAll[1:]:
712  diff = difflib.unified_diff(labels_merged, labels, n=max(len(labels_merged), len(labels)))
713  labels_merged = []
714  operation = []
715  for item in diff: # skip the "header" lines
716  if item[:2] == "@@":
717  break
718  for item in diff:
719  operation.append(item[0])
720  lab = item[1:]
721  if lab in labels_merged:
722  # pick the last addition of the bin
723  ind = labels_merged.index(lab)
724  if operation[ind] == "-" and operation[-1] == "+":
725  labels_merged.remove(lab)
726  del operation[ind] # to keep xbinlabels and operation indices in sync
727  elif operation[ind] == "+" and operation[-1] == "-":
728  del operation[-1] # to keep xbinlabels and operation indices in sync
729  continue
730  else:
731  raise Exception("This should never happen")
732  labels_merged.append(lab)
733  # unified_diff returns empty diff if labels_merged and labels are equal
734  # so if labels_merged is empty here, it can be just set to labels
735  if len(labels_merged) == 0:
736  labels_merged = labels
737 
738  return labels_merged
739 
740 def _th1IncludeOnlyBins(histos, xbinlabels):
741  histos_new = []
742  for h in histos:
743  h_new = h.Clone(h.GetName()+"_xbinlabels")
744  h_new.SetBins(len(xbinlabels), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(xbinlabels))
745  for i, label in enumerate(xbinlabels):
746  bin = h.GetXaxis().FindFixBin(label)
747  if bin >= 0:
748  h_new.SetBinContent(i+1, h.GetBinContent(bin))
749  h_new.SetBinError(i+1, h.GetBinError(bin))
750  else:
751  h_new.SetBinContent(i+1, 0)
752  h_new.SetBinError(i+1, 0)
753  histos_new.append(h_new)
754  return histos_new
755 
756 
757 class Subtract:
758  """Class for subtracting two histograms"""
759  def __init__(self, name, nameA, nameB, title=""):
760  """Constructor
761 
762  Arguments:
763  name -- String for name of the resulting histogram (A-B)
764  nameA -- String for A histogram
765  nameB -- String for B histogram
766 
767  Keyword arguments:
768  title -- String for a title of the resulting histogram (default "")
769 
770  Uncertainties are calculated with the assumption that B is a
771  subset of A, and the histograms contain event counts.
772  """
773  self._name = name
774  self._nameA = nameA
775  self._nameB = nameB
776  self._title = title
777 
778  def __str__(self):
779  """String representation, returns the name"""
780  return self._name
781 
782  def create(self, tdirectory):
783  """Create and return the fake+duplicate histogram from a TDirectory"""
784  histoA = _getObject(tdirectory, self._nameA)
785  histoB = _getObject(tdirectory, self._nameB)
786 
787  if not histoA or not histoB:
788  return None
789 
790  ret = histoA.Clone(self._name)
791  ret.SetTitle(self._title)
792 
793  # Disable canExtend if it is set, otherwise setting the
794  # overflow bin will extend instead, possibly causing weird
795  # effects downstream
796  ret.SetCanExtend(False)
797 
798  for i in xrange(0, histoA.GetNbinsX()+2): # include under- and overflow too
799  val = histoA.GetBinContent(i)-histoB.GetBinContent(i)
800  ret.SetBinContent(i, val)
801  ret.SetBinError(i, math.sqrt(val))
802 
803  return ret
804 
805 class Transform:
806  """Class to transform bin contents in an arbitrary way."""
807  def __init__(self, name, histo, func, title=""):
808  """Constructor.
809 
810  Argument:
811  name -- String for name of the resulting histogram
812  histo -- String for a source histogram (needs to be cumulative)
813  func -- Function to operate on the bin content
814  """
815  self._name = name
816  self._histo = histo
817  self._func = func
818  self._title = title
819 
820  def __str__(self):
821  """String representation, returns the name"""
822  return self._name
823 
824  def create(self, tdirectory):
825  """Create and return the transformed histogram from a TDirectory"""
826  histo = _getOrCreateObject(tdirectory, self._histo)
827  if not histo:
828  return None
829 
830  ret = histo.Clone(self._name)
831  ret.SetTitle(self._title)
832 
833  # Disable canExtend if it is set, otherwise setting the
834  # overflow bin will extend instead, possibly causing weird
835  # effects downstream
836  ret.SetCanExtend(False)
837 
838  for i in xrange(0, histo.GetNbinsX()+2):
839  ret.SetBinContent(i, self._func(histo.GetBinContent(i)))
840  return ret
841 
843  """Class to calculate the fake+duplicate rate"""
844  def __init__(self, name, assoc, dup, reco, title=""):
845  """Constructor.
846 
847  Arguments:
848  name -- String for the name of the resulting efficiency histogram
849  assoc -- String for the name of the "associated" histogram
850  dup -- String for the name of the "duplicates" histogram
851  reco -- String for the name of the "reco" (denominator) histogram
852 
853  Keyword arguments:
854  title -- String for a title of the resulting histogram (default "")
855 
856  The result is calculated as 1 - (assoc - dup) / reco
857  """
858  self._name = name
859  self._assoc = assoc
860  self._dup = dup
861  self._reco = reco
862  self._title = title
863 
864  def __str__(self):
865  """String representation, returns the name"""
866  return self._name
867 
868  def create(self, tdirectory):
869  """Create and return the fake+duplicate histogram from a TDirectory"""
870  # Get the numerator/denominator histograms
871  hassoc = _getObject(tdirectory, self._assoc)
872  hdup = _getObject(tdirectory, self._dup)
873  hreco = _getObject(tdirectory, self._reco)
874 
875  # Skip if any of them does not exist
876  if not hassoc or not hdup or not hreco:
877  return None
878 
879  hfakedup = hreco.Clone(self._name)
880  hfakedup.SetTitle(self._title)
881 
882  for i in xrange(1, hassoc.GetNbinsX()+1):
883  numerVal = hassoc.GetBinContent(i) - hdup.GetBinContent(i)
884  denomVal = hreco.GetBinContent(i)
885 
886  fakedupVal = (1 - numerVal / denomVal) if denomVal != 0.0 else 0.0
887  errVal = math.sqrt(fakedupVal*(1-fakedupVal)/denomVal) if (denomVal != 0.0 and fakedupVal <= 1) else 0.0
888 
889  hfakedup.SetBinContent(i, fakedupVal)
890  hfakedup.SetBinError(i, errVal)
891 
892  return hfakedup
893 
895  """Class for making a cut efficiency histograms.
896 
897  N after cut
898  eff = -----------
899  N total
900  """
901  def __init__(self, name, histo, title=""):
902  """Constructor
903 
904  Arguments:
905  name -- String for name of the resulting histogram
906  histo -- String for a source histogram (needs to be cumulative)
907  """
908  self._name = name
909  self._histo = histo
910  self._title = title
911 
912  def __str__(self):
913  """String representation, returns the name"""
914  return self._name
915 
916  def create(self, tdirectory):
917  """Create and return the cut efficiency histogram from a TDirectory"""
918  histo = _getOrCreateObject(tdirectory, self._histo)
919  if not histo:
920  return None
921 
922  # infer cumulative direction from the under/overflow bins
923  ascending = histo.GetBinContent(0) < histo.GetBinContent(histo.GetNbinsX())
924  if ascending:
925  n_tot = histo.GetBinContent(histo.GetNbinsX())
926  else:
927  n_tot = histo.GetBinContent(0)
928 
929  if n_tot == 0:
930  return histo
931 
932  ret = histo.Clone(self._name)
933  ret.SetTitle(self._title)
934 
935  # calculate efficiency
936  for i in xrange(1, histo.GetNbinsX()+1):
937  n = histo.GetBinContent(i)
938  val = n/n_tot
939  errVal = math.sqrt(val*(1-val)/n_tot)
940  ret.SetBinContent(i, val)
941  ret.SetBinError(i, errVal)
942  return ret
943 
945  """Class to create a histogram by aggregating bins of another histogram to a bin of the resulting histogram."""
946  def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None, ignoreMissingBins=False, minExistingBins=None, originalOrder=False, reorder=None):
947  """Constructor.
948 
949  Arguments:
950  name -- String for the name of the resulting histogram
951  histoName -- String for the name of the source histogram
952  mapping -- Dictionary for mapping the bins (see below)
953 
954  Keyword arguments:
955  normalizeTo -- Optional string of a bin label in the source histogram. If given, all bins of the resulting histogram are divided by the value of this bin.
956  scale -- Optional number for scaling the histogram (passed to ROOT.TH1.Scale())
957  renameBin -- Optional function (string -> string) to rename the bins of the input histogram
958  originalOrder -- Boolean for using the order of bins in the histogram (default False)
959  reorder -- Optional function to reorder the bins
960 
961  Mapping structure (mapping):
962 
963  Dictionary (you probably want to use collections.OrderedDict)
964  should be a mapping from the destination bin label to a list
965  of source bin labels ("dst -> [src]").
966  """
967  self._name = name
968  self._histoName = histoName
969  self._mapping = mapping
970  self._normalizeTo = normalizeTo
971  self._scale = scale
972  self._renameBin = renameBin
973  self._ignoreMissingBins = ignoreMissingBins
974  self._minExistingBins = minExistingBins
975  self._originalOrder = originalOrder
976  self._reorder = reorder
977  if self._originalOrder and self._reorder is not None:
978  raise Exception("reorder is not None and originalOrder is True, please set only one of them")
979 
980  def __str__(self):
981  """String representation, returns the name"""
982  return self._name
983 
984  def create(self, tdirectory):
985  """Create and return the histogram from a TDirectory"""
986  th1 = _getOrCreateObject(tdirectory, self._histoName)
987  if th1 is None:
988  return None
989 
990  binLabels = [""]*len(self._mapping)
991  binValues = [None]*len(self._mapping)
992 
993  # TH1 can't really be used as a map/dict, so convert it here:
994  values = _th1ToOrderedDict(th1, self._renameBin)
995 
996  binIndexOrder = [] # for reordering bins if self._originalOrder is True
997  for i, (key, labels) in enumerate(self._mapping.iteritems()):
998  sumTime = 0.
999  sumErrorSq = 0.
1000  nsum = 0
1001  for l in labels:
1002  try:
1003  sumTime += values[l][0]
1004  sumErrorSq += values[l][1]**2
1005  nsum += 1
1006  except KeyError:
1007  pass
1008 
1009  if nsum > 0:
1010  binValues[i] = (sumTime, math.sqrt(sumErrorSq))
1011  binLabels[i] = key
1012 
1013  ivalue = len(values)+1
1014  if len(labels) > 0:
1015  # first label doesn't necessarily exist (especially for
1016  # the iteration timing plots), so let's test them all
1017  for lab in labels:
1018  if lab in values:
1019  ivalue = values.keys().index(lab)
1020  break
1021  binIndexOrder.append( (ivalue, i) )
1022 
1023  if self._originalOrder:
1024  binIndexOrder.sort(key=lambda t: t[0])
1025  tmpVal = []
1026  tmpLab = []
1027  for i in xrange(0, len(binValues)):
1028  fromIndex = binIndexOrder[i][1]
1029  tmpVal.append(binValues[fromIndex])
1030  tmpLab.append(binLabels[fromIndex])
1031  binValues = tmpVal
1032  binLabels = tmpLab
1033  if self._reorder is not None:
1034  order = self._reorder(tdirectory, binLabels)
1035  binValues = [binValues[i] for i in order]
1036  binLabels = [binLabels[i] for i in order]
1037 
1038  if self._minExistingBins is not None and (len(binValues)-binValues.count(None)) < self._minExistingBins:
1039  return None
1040 
1041  if self._ignoreMissingBins:
1042  for i, val in enumerate(binValues):
1043  if val is None:
1044  binLabels[i] = None
1045  binValues = filter(lambda v: v is not None, binValues)
1046  binLabels = filter(lambda v: v is not None, binLabels)
1047  if len(binValues) == 0:
1048  return None
1049 
1050  result = ROOT.TH1F(self._name, self._name, len(binValues), 0, len(binValues))
1051  for i, (value, label) in enumerate(zip(binValues, binLabels)):
1052  if value is not None:
1053  result.SetBinContent(i+1, value[0])
1054  result.SetBinError(i+1, value[1])
1055  result.GetXaxis().SetBinLabel(i+1, label)
1056 
1057  if self._normalizeTo is not None:
1058  bin = th1.GetXaxis().FindBin(self._normalizeTo)
1059  if bin <= 0:
1060  print "Trying to normalize {name} to {binlabel}, which does not exist".format(name=self._name, binlabel=self._normalizeTo)
1061  sys.exit(1)
1062  value = th1.GetBinContent(bin)
1063  if value != 0:
1064  result.Scale(1/value)
1065 
1066  if self._scale is not None:
1067  result.Scale(self._scale)
1068 
1069  return result
1070 
1072  """Class to create a histogram by aggregaging integrals of another histoggrams."""
1073  def __init__(self, name, mapping, normalizeTo=None):
1074  """Constructor.
1075 
1076  Arguments:
1077  name -- String for the name of the resulting histogram
1078  mapping -- Dictionary for mapping the bin label to a histogram name
1079 
1080  Keyword arguments:
1081  normalizeTo -- Optional string for a histogram. If given, all bins of the resulting histograqm are divided by the integral of this histogram.
1082  """
1083  self._name = name
1084  self._mapping = mapping
1085  self._normalizeTo = normalizeTo
1086 
1087  def __str__(self):
1088  """String representation, returns the name"""
1089  return self._name
1090 
1091  def create(self, tdirectory):
1092  """Create and return the histogram from a TDirectory"""
1093  result = []
1094  for key, histoName in self._mapping.iteritems():
1095  th1 = _getObject(tdirectory, histoName)
1096  if th1 is None:
1097  continue
1098  result.append( (key, th1.Integral(0, th1.GetNbinsX()+1)) ) # include under- and overflow bins
1099  if len(result) == 0:
1100  return None
1101 
1102  res = ROOT.TH1F(self._name, self._name, len(result), 0, len(result))
1103 
1104  for i, (name, count) in enumerate(result):
1105  res.SetBinContent(i+1, count)
1106  res.GetXaxis().SetBinLabel(i+1, name)
1107 
1108  if self._normalizeTo is not None:
1109  th1 = _getObject(tdirectory, self._normalizeTo)
1110  if th1 is None:
1111  return None
1112  scale = th1.Integral(0, th1.GetNbinsX()+1)
1113  res.Scale(1/scale)
1114 
1115  return res
1116 
1117 class ROC:
1118  """Class to construct a ROC curve (e.g. efficiency vs. fake rate) from two histograms"""
1119  def __init__(self, name, xhistoName, yhistoName, zaxis=False):
1120  """Constructor.
1121 
1122  Arguments:
1123  name -- String for the name of the resulting histogram
1124  xhistoName -- String for the name of the x-axis histogram (or another "creator" object)
1125  yhistoName -- String for the name of the y-axis histogram (or another "creator" object)
1126 
1127  Keyword arguments:
1128  zaxis -- If set to True (default False), create a TGraph2D with z axis showing the cut value (recommended drawStyle 'pcolz')
1129  """
1130  self._name = name
1131  self._xhistoName = xhistoName
1132  self._yhistoName = yhistoName
1133  self._zaxis = zaxis
1134 
1135  def __str__(self):
1136  """String representation, returns the name"""
1137  return self._name
1138 
1139  def create(self, tdirectory):
1140  """Create and return the histogram from a TDirectory"""
1141  xhisto = _getOrCreateObject(tdirectory, self._xhistoName)
1142  yhisto = _getOrCreateObject(tdirectory, self._yhistoName);
1143  if xhisto is None or yhisto is None:
1144  return None
1145 
1146  x = []
1147  xerrup = []
1148  xerrdown = []
1149  y = []
1150  yerrup = []
1151  yerrdown = []
1152  z = []
1153 
1154  for i in xrange(1, xhisto.GetNbinsX()+1):
1155  x.append(xhisto.GetBinContent(i))
1156  xerrup.append(xhisto.GetBinError(i))
1157  xerrdown.append(xhisto.GetBinError(i))
1158 
1159  y.append(yhisto.GetBinContent(i))
1160  yerrup.append(yhisto.GetBinError(i))
1161  yerrdown.append(yhisto.GetBinError(i))
1162 
1163  z.append(xhisto.GetXaxis().GetBinUpEdge(i))
1164 
1165  # If either axis has only zeroes, no graph makes no point
1166  if x.count(0.0) == len(x) or y.count(0.0) == len(y):
1167  return None
1168 
1169  arr = lambda v: array.array("d", v)
1170  gr = None
1171  if self._zaxis:
1172  gr = ROOT.TGraph2D(len(x), arr(x), arr(y), arr(z))
1173  else:
1174  gr = ROOT.TGraphAsymmErrors(len(x), arr(x), arr(y), arr(xerrdown), arr(xerrup), arr(yerrdown), arr(yerrup))
1175  gr.SetTitle("")
1176  return gr
1177 
1178 
1179 # Plot styles
1180 _plotStylesColor = [4, 2, ROOT.kBlack, ROOT.kOrange+7, ROOT.kMagenta-3]
1181 _plotStylesMarker = [21, 20, 22, 34, 33]
1182 
1183 def _drawFrame(pad, bounds, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None, suffix=""):
1184  """Function to draw a frame
1185 
1186  Arguments:
1187  pad -- TPad to where the frame is drawn
1188  bounds -- List or 4-tuple for (xmin, ymin, xmax, ymax)
1189 
1190  Keyword arguments:
1191  xbinlabels -- Optional list of strings for x axis bin labels
1192  xbinlabelsize -- Optional number for the x axis bin label size
1193  xbinlabeloption -- Optional string for the x axis bin options (passed to ROOT.TH1.LabelsOption())
1194  suffix -- Optional string for a postfix of the frame name
1195  """
1196  if xbinlabels is None and ybinlabels is None:
1197  frame = pad.DrawFrame(*bounds)
1198  else:
1199  # Special form needed if want to set x axis bin labels
1200  nbins = len(xbinlabels)
1201  if ybinlabels is None:
1202  frame = ROOT.TH1F("hframe"+suffix, "", nbins, bounds[0], bounds[2])
1203  frame.SetMinimum(bounds[1])
1204  frame.SetMaximum(bounds[3])
1205  frame.GetYaxis().SetLimits(bounds[1], bounds[3])
1206  else:
1207  ybins = len(ybinlabels)
1208  frame = ROOT.TH2F("hframe"+suffix, "", nbins,bounds[0],bounds[2], ybins,bounds[1],bounds[3])
1209 
1210  frame.SetBit(ROOT.TH1.kNoStats)
1211  frame.SetBit(ROOT.kCanDelete)
1212  frame.Draw("")
1213 
1214  xaxis = frame.GetXaxis()
1215  for i in xrange(nbins):
1216  xaxis.SetBinLabel(i+1, xbinlabels[i])
1217  if xbinlabelsize is not None:
1218  xaxis.SetLabelSize(xbinlabelsize)
1219  if xbinlabeloption is not None:
1220  frame.LabelsOption(xbinlabeloption)
1221 
1222  if ybinlabels is not None:
1223  yaxis = frame.GetYaxis()
1224  for i, lab in enumerate(ybinlabels):
1225  yaxis.SetBinLabel(i+1, lab)
1226  if xbinlabelsize is not None:
1227  yaxis.SetLabelSize(xbinlabelsize)
1228  if xbinlabeloption is not None:
1229  frame.LabelsOption(xbinlabeloption, "Y")
1230 
1231  return frame
1232 
1233 class Frame:
1234  """Class for creating and managing a frame for a simple, one-pad plot"""
1235  def __init__(self, pad, bounds, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None):
1236  self._pad = pad
1237  self._frame = _drawFrame(pad, bounds, xbinlabels, xbinlabelsize, xbinlabeloption, ybinlabels)
1238 
1239  yoffsetFactor = 1
1240  xoffsetFactor = 1
1241  if nrows == 2:
1242  yoffsetFactor *= 2
1243  xoffsetFactor *= 2
1244  elif nrows >= 5:
1245  yoffsetFactor *= 1.5
1246  xoffsetFactor *= 1.5
1247  elif nrows >= 3:
1248  yoffsetFactor *= 4
1249  xoffsetFactor *= 3
1250 
1251  self._frame.GetYaxis().SetTitleOffset(self._frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
1252  self._frame.GetXaxis().SetTitleOffset(self._frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
1253 
1254 
1255  def setLogx(self, log):
1256  self._pad.SetLogx(log)
1257 
1258  def setLogy(self, log):
1259  self._pad.SetLogy(log)
1260 
1261  def setGridx(self, grid):
1262  self._pad.SetGridx(grid)
1263 
1264  def setGridy(self, grid):
1265  self._pad.SetGridy(grid)
1266 
1267  def adjustMarginLeft(self, adjust):
1268  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1269  # Need to redraw frame after adjusting the margin
1270  self._pad.cd()
1271  self._frame.Draw("")
1272 
1273  def adjustMarginRight(self, adjust):
1274  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1275  # Need to redraw frame after adjusting the margin
1276  self._pad.cd()
1277  self._frame.Draw("")
1278 
1279  def setTitle(self, title):
1280  self._frame.SetTitle(title)
1281 
1282  def setXTitle(self, title):
1283  self._frame.GetXaxis().SetTitle(title)
1284 
1285  def setXTitleSize(self, size):
1286  self._frame.GetXaxis().SetTitleSize(size)
1287 
1288  def setXTitleOffset(self, offset):
1289  self._frame.GetXaxis().SetTitleOffset(offset)
1290 
1291  def setXLabelSize(self, size):
1292  self._frame.GetXaxis().SetLabelSize(size)
1293 
1294  def setYTitle(self, title):
1295  self._frame.GetYaxis().SetTitle(title)
1296 
1297  def setYTitleSize(self, size):
1298  self._frame.GetYaxis().SetTitleSize(size)
1299 
1300  def setYTitleOffset(self, offset):
1301  self._frame.GetYaxis().SetTitleOffset(offset)
1302 
1303  def redrawAxis(self):
1304  self._pad.RedrawAxis()
1305 
1307  """Class for creating and managing a frame for a ratio plot with two subpads"""
1308  def __init__(self, pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ratioYTitle=_ratioYTitle):
1309  self._parentPad = pad
1310  self._pad = pad.cd(1)
1311  if xbinlabels is not None:
1312  self._frame = _drawFrame(self._pad, bounds, [""]*len(xbinlabels))
1313  else:
1314  self._frame = _drawFrame(self._pad, bounds)
1315  self._padRatio = pad.cd(2)
1316  self._frameRatio = _drawFrame(self._padRatio, ratioBounds, xbinlabels, xbinlabelsize, xbinlabeloption)
1317 
1318  self._frame.GetXaxis().SetLabelSize(0)
1319  self._frame.GetXaxis().SetTitleSize(0)
1320 
1321  yoffsetFactor = ratioFactor
1322  divisionPoint = 1-1/ratioFactor
1323  xoffsetFactor = 1/divisionPoint #* 0.6
1324 
1325  if nrows == 1:
1326  xoffsetFactor *= 0.6
1327  elif nrows == 2:
1328  yoffsetFactor *= 2
1329  xoffsetFactor *= 1.5
1330  elif nrows == 3:
1331  yoffsetFactor *= 4
1332  xoffsetFactor *= 2.3
1333  elif nrows >= 4:
1334  yoffsetFactor *= 5
1335  xoffsetFactor *= 3
1336 
1337  self._frame.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1338  self._frameRatio.GetYaxis().SetLabelSize(int(self._frameRatio.GetYaxis().GetLabelSize()*0.8))
1339  self._frameRatio.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1340  self._frameRatio.GetXaxis().SetTitleOffset(self._frameRatio.GetXaxis().GetTitleOffset()*xoffsetFactor)
1341 
1342  self._frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
1343 
1344  self._frameRatio.GetYaxis().SetTitle(ratioYTitle)
1345 
1346  def setLogx(self, log):
1347  self._pad.SetLogx(log)
1348  self._padRatio.SetLogx(log)
1349 
1350  def setLogy(self, log):
1351  self._pad.SetLogy(log)
1352 
1353  def setGridx(self, grid):
1354  self._pad.SetGridx(grid)
1355  self._padRatio.SetGridx(grid)
1356 
1357  def setGridy(self, grid):
1358  self._pad.SetGridy(grid)
1359  self._padRatio.SetGridy(grid)
1360 
1361  def adjustMarginLeft(self, adjust):
1362  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1363  self._padRatio.SetLeftMargin(self._padRatio.GetLeftMargin()+adjust)
1364  # Need to redraw frame after adjusting the margin
1365  self._pad.cd()
1366  self._frame.Draw("")
1367  self._padRatio.cd()
1368  self._frameRatio.Draw("")
1369 
1370  def adjustMarginRight(self, adjust):
1371  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1372  self._padRatio.SetRightMargin(self._padRatio.GetRightMargin()+adjust)
1373  # Need to redraw frames after adjusting the margin
1374  self._pad.cd()
1375  self._frame.Draw("")
1376  self._padRatio.cd()
1377  self._frameRatio.Draw("")
1378 
1379  def setTitle(self, title):
1380  self._frame.SetTitle(title)
1381 
1382  def setXTitle(self, title):
1383  self._frameRatio.GetXaxis().SetTitle(title)
1384 
1385  def setXTitleSize(self, size):
1386  self._frameRatio.GetXaxis().SetTitleSize(size)
1387 
1388  def setXTitleOffset(self, offset):
1389  self._frameRatio.GetXaxis().SetTitleOffset(offset)
1390 
1391  def setXLabelSize(self, size):
1392  self._frameRatio.GetXaxis().SetLabelSize(size)
1393 
1394  def setYTitle(self, title):
1395  self._frame.GetYaxis().SetTitle(title)
1396 
1397  def setYTitleRatio(self, title):
1398  self._frameRatio.GetYaxis().SetTitle(title)
1399 
1400  def setYTitleSize(self, size):
1401  self._frame.GetYaxis().SetTitleSize(size)
1402  self._frameRatio.GetYaxis().SetTitleSize(size)
1403 
1404  def setYTitleOffset(self, offset):
1405  self._frame.GetYaxis().SetTitleOffset(offset)
1406  self._frameRatio.GetYaxis().SetTitleOffset(offset)
1407 
1408  def redrawAxis(self):
1409  self._padRatio.RedrawAxis()
1410  self._pad.RedrawAxis()
1411 
1412  self._parentPad.cd()
1413 
1414  # pad to hide the lowest y axis label of the main pad
1415  xmin=0.065
1416  ymin=0.285
1417  xmax=0.128
1418  ymax=0.33
1419  self._coverPad = ROOT.TPad("coverpad", "coverpad", xmin, ymin, xmax, ymax)
1420  self._coverPad.SetBorderMode(0)
1421  self._coverPad.Draw()
1422 
1423  self._pad.cd()
1424  self._pad.Pop() # Move the first pad on top
1425 
1427  """Class for creating and managing a frame for a plot from TGraph2D"""
1428  def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
1429  self._pad = pad
1430  if ratioOrig:
1431  self._pad = pad.cd(1)
1432 
1433  # adjust margins because of not having the ratio, we want
1434  # the same bottom margin, so some algebra gives this
1435  (xlow, ylow, width, height) = (self._pad.GetXlowNDC(), self._pad.GetYlowNDC(), self._pad.GetWNDC(), self._pad.GetHNDC())
1436  xup = xlow+width
1437  yup = ylow+height
1438 
1439  bottomMargin = self._pad.GetBottomMargin()
1440  bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
1441 
1442  ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
1443  topMarginNew = self._pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
1444 
1445  self._pad.SetPad(xlow, ylowNew, xup, yup)
1446  self._pad.SetTopMargin(topMarginNew)
1447  self._pad.SetBottomMargin(bottomMarginNew)
1448 
1449  self._view = ROOT.TView.CreateView()
1450  self._view.SetRange(bounds[0], bounds[1], 0, bounds[2], bounds[3], 20) # 20 is from Harrison-Stetson, may need tuning?
1451  self._view.Top()
1452  self._view.ShowAxis()
1453 
1454  self._xtitleoffset = 1.8
1455  self._ytitleoffset = 2.3
1456 
1457  self._firstHisto = histos[0]
1458 
1459  def setLogx(self, log):
1460  pass
1461 
1462  def setLogy(self, log):
1463  pass
1464 
1465  def setGridx(self, grid):
1466  pass
1467 
1468  def setGridy(self, grid):
1469  pass
1470 
1471  def adjustMarginLeft(self, adjust):
1472  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1473  self._pad.cd()
1474 
1475  def adjustMarginRight(self, adjust):
1476  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1477  self._pad.cd()
1478 
1479  def setTitle(self, title):
1480  pass
1481 
1482  def setXTitle(self, title):
1483  self._xtitle = title
1484 
1485  def setXTitleSize(self, size):
1486  self._xtitlesize = size
1487 
1488  def setXTitleOffset(self, size):
1489  self._xtitleoffset = size
1490 
1491  def setXLabelSize(self, size):
1492  self._xlabelsize = size
1493 
1494  def setYTitle(self, title):
1495  self._ytitle = title
1496 
1497  def setYTitleSize(self, size):
1498  self._ytitlesize = size
1499 
1500  def setYTitleOffset(self, offset):
1501  self._ytitleoffset = offset
1502 
1503  def setZTitle(self, title):
1504  self._firstHisto.GetZaxis().SetTitle(title)
1505 
1506  def setZTitleOffset(self, offset):
1507  self._firstHisto.GetZaxis().SetTitleOffset(offset)
1508 
1509  def redrawAxis(self):
1510  # Disabling and enabled the 3D rulers somehow magically moves the axes to their proper places
1511  ROOT.TAxis3D.ToggleRulers()
1512  ROOT.TAxis3D.ToggleRulers()
1513  axis = ROOT.TAxis3D.GetPadAxis()
1514  axis.SetLabelColor(ROOT.kBlack);
1515  axis.SetAxisColor(ROOT.kBlack);
1516 
1517  axis.GetXaxis().SetTitleOffset(self._xtitleoffset)
1518  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
1519 
1520  if hasattr(self, "_xtitle"):
1521  axis.GetXaxis().SetTitle(self._xtitle)
1522  if hasattr(self, "_xtitlesize"):
1523  axis.GetXaxis().SetTitleSize(self._xtitlesize)
1524  if hasattr(self, "_xlabelsize"):
1525  axis.GetXaxis().SetLabelSize(self._labelsize)
1526  if hasattr(self, "_ytitle"):
1527  axis.GetYaxis().SetTitle(self._ytitle)
1528  if hasattr(self, "_ytitlesize"):
1529  axis.GetYaxis().SetTitleSize(self._ytitlesize)
1530  if hasattr(self, "_ytitleoffset"):
1531  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
1532 
1533 class PlotText:
1534  """Abstraction on top of TLatex"""
1535  def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None):
1536  """Constructor.
1537 
1538  Arguments:
1539  x -- X coordinate of the text (in NDC)
1540  y -- Y coordinate of the text (in NDC)
1541  text -- String to draw
1542  size -- Size of text (None for the default value, taken from gStyle)
1543  bold -- Should the text be bold?
1544  align -- Alignment of text (left, center, right)
1545  color -- Color of the text
1546  font -- Specify font explicitly
1547  """
1548  self._x = x
1549  self._y = y
1550  self._text = text
1551 
1552  self._l = ROOT.TLatex()
1553  self._l.SetNDC()
1554  if not bold:
1555  self._l.SetTextFont(self._l.GetTextFont()-20) # bold -> normal
1556  if font is not None:
1557  self._l.SetTextFont(font)
1558  if size is not None:
1559  self._l.SetTextSize(size)
1560  if isinstance(align, basestring):
1561  if align.lower() == "left":
1562  self._l.SetTextAlign(11)
1563  elif align.lower() == "center":
1564  self._l.SetTextAlign(21)
1565  elif align.lower() == "right":
1566  self._l.SetTextAlign(31)
1567  else:
1568  raise Exception("Error: Invalid option '%s' for text alignment! Options are: 'left', 'center', 'right'."%align)
1569  else:
1570  self._l.SetTextAlign(align)
1571  self._l.SetTextColor(color)
1572 
1573  def Draw(self, options=None):
1574  """Draw the text to the current TPad.
1575 
1576  Arguments:
1577  options -- For interface compatibility, ignored
1578 
1579  Provides interface compatible with ROOT's drawable objects.
1580  """
1581  self._l.DrawLatex(self._x, self._y, self._text)
1582 
1583 
1585  """Class for drawing text and a background box."""
1586  def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, **kwargs):
1587  """Constructor
1588 
1589  Arguments:
1590  xmin -- X min coordinate of the box (NDC)
1591  ymin -- Y min coordinate of the box (NDC) (if None, deduced automatically)
1592  xmax -- X max coordinate of the box (NDC)
1593  ymax -- Y max coordinate of the box (NDC)
1594  lineheight -- Line height
1595  fillColor -- Fill color of the box
1596  transparent -- Should the box be transparent? (in practive the TPave is not created)
1597 
1598  Keyword arguments are forwarded to constructor of PlotText
1599  """
1600  # ROOT.TPave Set/GetX1NDC() etc don't seem to work as expected.
1601  self._xmin = xmin
1602  self._xmax = xmax
1603  self._ymin = ymin
1604  self._ymax = ymax
1605  self._lineheight = lineheight
1606  self._fillColor = fillColor
1607  self._transparent = transparent
1608  self._texts = []
1609  self._textArgs = {}
1610  self._textArgs.update(kwargs)
1611 
1612  self._currenty = ymax
1613 
1614  def addText(self, text):
1615  """Add text to current position"""
1616  self._currenty -= self._lineheight
1617  self._texts.append(PlotText(self._xmin+0.01, self._currenty, text, **self._textArgs))
1618 
1619  def width(self):
1620  return self._xmax-self._xmin
1621 
1622  def move(self, dx=0, dy=0, dw=0, dh=0):
1623  """Move the box and the contained text objects
1624 
1625  Arguments:
1626  dx -- Movement in x (positive is to right)
1627  dy -- Movement in y (positive is to up)
1628  dw -- Increment of width (negative to decrease width)
1629  dh -- Increment of height (negative to decrease height)
1630 
1631  dx and dy affect to both box and text objects, dw and dh
1632  affect the box only.
1633  """
1634  self._xmin += dx
1635  self._xmax += dx
1636  if self._ymin is not None:
1637  self._ymin += dy
1638  self._ymax += dy
1639 
1640  self._xmax += dw
1641  if self._ymin is not None:
1642  self._ymin -= dh
1643 
1644  for t in self._texts:
1645  t._x += dx
1646  t._y += dy
1647 
1648  def Draw(self, options=""):
1649  """Draw the box and the text to the current TPad.
1650 
1651  Arguments:
1652  options -- Forwarded to ROOT.TPave.Draw(), and the Draw() of the contained objects
1653  """
1654  if not self._transparent:
1655  ymin = self.ymin
1656  if ymin is None:
1657  ymin = self.currenty - 0.01
1658  self._pave = ROOT.TPave(self.xmin, self.ymin, self.xmax, self.ymax, 0, "NDC")
1659  self._pave.SetFillColor(self.fillColor)
1660  self._pave.Draw(options)
1661  for t in self._texts:
1662  t.Draw(options)
1663 
1664 def _copyStyle(src, dst):
1665  properties = []
1666  if hasattr(src, "GetLineColor") and hasattr(dst, "SetLineColor"):
1667  properties.extend(["LineColor", "LineStyle", "LineWidth"])
1668  if hasattr(src, "GetFillColor") and hasattr(dst, "SetFillColor"):
1669  properties.extend(["FillColor", "FillStyle"])
1670  if hasattr(src, "GetMarkerColor") and hasattr(dst, "SetMarkerColor"):
1671  properties.extend(["MarkerColor", "MarkerSize", "MarkerStyle"])
1672 
1673  for prop in properties:
1674  getattr(dst, "Set"+prop)(getattr(src, "Get"+prop)())
1675 
1677  """Denotes an empty place in a group."""
1678  def __init__(self):
1679  pass
1680 
1681  def getName(self):
1682  return None
1683 
1685  return False
1686 
1687  def create(self, *args, **kwargs):
1688  pass
1689 
1690  def isEmpty(self):
1691  return True
1692 
1694  return 0
1695 
1696 class Plot:
1697  """Represents one plot, comparing one or more histograms."""
1698  def __init__(self, name, **kwargs):
1699  """ Constructor.
1700 
1701  Arguments:
1702  name -- String for name of the plot, or Efficiency object
1703 
1704  Keyword arguments:
1705  fallback -- Dictionary for specifying fallback (default None)
1706  outname -- String for an output name of the plot (default None for the same as 'name')
1707  title -- String for a title of the plot (default None)
1708  xtitle -- String for x axis title (default None)
1709  xtitlesize -- Float for x axis title size (default None)
1710  xtitleoffset -- Float for x axis title offset (default None)
1711  xlabelsize -- Float for x axis label size (default None)
1712  ytitle -- String for y axis title (default None)
1713  ytitlesize -- Float for y axis title size (default None)
1714  ytitleoffset -- Float for y axis title offset (default None)
1715  ztitle -- String for z axis title (default None)
1716  ztitleoffset -- Float for z axis title offset (default None)
1717  xmin -- Float for x axis minimum (default None, i.e. automatic)
1718  xmax -- Float for x axis maximum (default None, i.e. automatic)
1719  ymin -- Float for y axis minimum (default 0)
1720  ymax -- Float for y axis maximum (default None, i.e. automatic)
1721  xlog -- Bool for x axis log status (default False)
1722  ylog -- Bool for y axis log status (default False)
1723  xgrid -- Bool for x axis grid status (default True)
1724  ygrid -- Bool for y axis grid status (default True)
1725  stat -- Draw stat box? (default False)
1726  fit -- Do gaussian fit? (default False)
1727  statx -- Stat box x coordinate (default 0.65)
1728  staty -- Stat box y coordinate (default 0.8)
1729  statyadjust -- List of floats for stat box y coordinate adjustments (default None)
1730  normalizeToUnitArea -- Normalize histograms to unit area? (default False)
1731  normalizeToNumberOfEvents -- Normalize histograms to number of events? If yes, the PlotFolder needs 'numberOfEventsHistogram' set to a histogram filled once per event (default False)
1732  profileX -- Take histograms via ProfileX()? (default False)
1733  fitSlicesY -- Take histograms via FitSlicesY() (default False)
1734  rebinX -- rebin x axis (default None)
1735  scale -- Scale histograms by a number (default None)
1736  xbinlabels -- List of x axis bin labels (if given, default None)
1737  xbinlabelsize -- Size of x axis bin labels (default None)
1738  xbinlabeloption -- Option string for x axis bin labels (default None)
1739  removeEmptyBins -- Bool for removing empty bins, but only if histogram has bin labels (default False)
1740  printBins -- Bool for printing bin values, but only if histogram has bin labels (default False)
1741  drawStyle -- If "hist", draw as line instead of points (default None)
1742  drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
1743  lineWidth -- If drawStyle=="hist", the width of line (default 2)
1744  legendDx -- Float for moving TLegend in x direction for separate=True (default None)
1745  legendDy -- Float for moving TLegend in y direction for separate=True (default None)
1746  legendDw -- Float for changing TLegend width for separate=True (default None)
1747  legendDh -- Float for changing TLegend height for separate=True (default None)
1748  legend -- Bool to enable/disable legend (default True)
1749  adjustMarginLeft -- Float for adjusting left margin (default None)
1750  adjustMarginRight -- Float for adjusting right margin (default None)
1751  ratio -- Possibility to disable ratio for this particular plot (default None)
1752  ratioYmin -- Float for y axis minimum in ratio pad (default: list of values)
1753  ratioYmax -- Float for y axis maximum in ratio pad (default: list of values)
1754  ratioFit -- Fit straight line in ratio? (default None)
1755  ratioUncertainty -- Plot uncertainties on ratio? (default True)
1756  ratioCoverageXrange -- Range of x axis values (xmin,xmax) to limit the automatic ratio y axis range calculation to (default None for disabled)
1757  histogramModifier -- Function to be called in create() to modify the histograms (default None)
1758  """
1759  self._name = name
1760 
1761  def _set(attr, default):
1762  setattr(self, "_"+attr, kwargs.get(attr, default))
1763 
1764  _set("fallback", None)
1765  _set("outname", None)
1766 
1767  _set("title", None)
1768  _set("xtitle", None)
1769  _set("xtitlesize", None)
1770  _set("xtitleoffset", None)
1771  _set("xlabelsize", None)
1772  _set("ytitle", None)
1773  _set("ytitlesize", None)
1774  _set("ytitleoffset", None)
1775  _set("ztitle", None)
1776  _set("ztitleoffset", None)
1777 
1778  _set("xmin", None)
1779  _set("xmax", None)
1780  _set("ymin", 0.)
1781  _set("ymax", None)
1782 
1783  _set("xlog", False)
1784  _set("ylog", False)
1785  _set("xgrid", True)
1786  _set("ygrid", True)
1787 
1788  _set("stat", False)
1789  _set("fit", False)
1790 
1791  _set("statx", 0.65)
1792  _set("staty", 0.8)
1793  _set("statyadjust", None)
1794 
1795  _set("normalizeToUnitArea", False)
1796  _set("normalizeToNumberOfEvents", False)
1797  _set("profileX", False)
1798  _set("fitSlicesY", False)
1799  _set("rebinX", None)
1800 
1801  _set("scale", None)
1802  _set("xbinlabels", None)
1803  _set("xbinlabelsize", None)
1804  _set("xbinlabeloption", None)
1805  _set("removeEmptyBins", False)
1806  _set("printBins", False)
1807 
1808  _set("drawStyle", "EP")
1809  _set("drawCommand", None)
1810  _set("lineWidth", 2)
1811 
1812  _set("legendDx", None)
1813  _set("legendDy", None)
1814  _set("legendDw", None)
1815  _set("legendDh", None)
1816  _set("legend", True)
1817 
1818  _set("adjustMarginLeft", None)
1819  _set("adjustMarginRight", None)
1820 
1821  _set("ratio", None)
1822  _set("ratioYmin", [0, 0.2, 0.5, 0.7, 0.8, 0.9, 0.95])
1823  _set("ratioYmax", [1.05, 1.1, 1.2, 1.3, 1.5, 1.8, 2, 2.5, 3, 4, 5])
1824  _set("ratioFit", None)
1825  _set("ratioUncertainty", True)
1826  _set("ratioCoverageXrange", None)
1827 
1828  _set("histogramModifier", None)
1829 
1830  self._histograms = []
1831 
1832  def setProperties(self, **kwargs):
1833  for name, value in kwargs.iteritems():
1834  if not hasattr(self, "_"+name):
1835  raise Exception("No attribute '%s'" % name)
1836  setattr(self, "_"+name, value)
1837 
1838  def clone(self, **kwargs):
1839  if not self.isEmpty():
1840  raise Exception("Plot can be cloned only before histograms have been created")
1841  cl = copy.copy(self)
1842  cl.setProperties(**kwargs)
1843  return cl
1844 
1846  """Return number of existing histograms."""
1847  return len(filter(lambda h: h is not None, self._histograms))
1848 
1849  def isEmpty(self):
1850  """Return true if there are no histograms created for the plot"""
1851  return self.getNumberOfHistograms() == 0
1852 
1853  def isTGraph2D(self):
1854  for h in self._histograms:
1855  if isinstance(h, ROOT.TGraph2D):
1856  return True
1857  return False
1858 
1859  def isRatio(self, ratio):
1860  if self._ratio is None:
1861  return ratio
1862  return ratio and self._ratio
1863 
1864  def getName(self):
1865  if self._outname is not None:
1866  return self._outname
1867  if isinstance(self._name, list):
1868  return str(self._name[0])
1869  else:
1870  return str(self._name)
1871 
1873  """Return true if the ratio uncertainty should be drawn"""
1874  return self._ratioUncertainty
1875 
1876  def _createOne(self, name, index, tdir, nevents):
1877  """Create one histogram from a TDirectory."""
1878  if tdir == None:
1879  return None
1880 
1881  # If name is a list, pick the name by the index
1882  if isinstance(name, list):
1883  name = name[index]
1884 
1885  h = _getOrCreateObject(tdir, name)
1886  if h is not None and self._normalizeToNumberOfEvents and nevents is not None and nevents != 0:
1887  h.Scale(1.0/nevents)
1888  return h
1889 
1890  def create(self, tdirNEvents, requireAllHistograms=False):
1891  """Create histograms from list of TDirectories"""
1892  self._histograms = [self._createOne(self._name, i, tdirNEvent[0], tdirNEvent[1]) for i, tdirNEvent in enumerate(tdirNEvents)]
1893 
1894  if self._fallback is not None:
1895  profileX = [self._profileX]*len(self._histograms)
1896  for i in xrange(0, len(self._histograms)):
1897  if self._histograms[i] is None:
1898  self._histograms[i] = self._createOne(self._fallback["name"], i, tdirNEvents[i][0], tdirNEvents[i][1])
1899  profileX[i] = self._fallback.get("profileX", self._profileX)
1900 
1901  if self._histogramModifier is not None:
1902  self._histograms = self._histogramModifier(self._histograms)
1903 
1904  if len(self._histograms) > len(_plotStylesColor):
1905  raise Exception("More histograms (%d) than there are plot styles (%d) defined. Please define more plot styles in this file" % (len(self._histograms), len(_plotStylesColor)))
1906 
1907  # Modify histograms here in case self._name returns numbers
1908  # and self._histogramModifier creates the histograms from
1909  # these numbers
1910  def _modifyHisto(th1, profileX):
1911  if th1 is None:
1912  return None
1913 
1914  if profileX:
1915  th1 = th1.ProfileX()
1916 
1917  if self._fitSlicesY:
1918  ROOT.TH1.AddDirectory(True)
1919  th1.FitSlicesY()
1920  th1 = ROOT.gDirectory.Get(th1.GetName()+"_2")
1921  th1.SetDirectory(None)
1922  #th1.SetName(th1.GetName()+"_ref")
1923  ROOT.TH1.AddDirectory(False)
1924 
1925  if self._title is not None:
1926  th1.SetTitle(self._title)
1927 
1928  if self._scale is not None:
1929  th1.Scale(self._scale)
1930 
1931  return th1
1932 
1933  if self._fallback is not None:
1934  self._histograms = map(_modifyHisto, self._histograms, profileX)
1935  else:
1936  self._histograms = map(lambda h: _modifyHisto(h, self._profileX), self._histograms)
1937  if requireAllHistograms and None in self._histograms:
1938  self._histograms = [None]*len(self._histograms)
1939 
1940  def _setStats(self, histos, startingX, startingY):
1941  """Set stats box."""
1942  if not self._stat:
1943  for h in histos:
1944  if h is not None and hasattr(h, "SetStats"):
1945  h.SetStats(0)
1946  return
1947 
1948  def _doStats(h, col, dy):
1949  if h is None:
1950  return
1951  h.SetStats(True)
1952 
1953  if self._fit and h.GetEntries() > 0.5:
1954  h.Fit("gaus", "Q")
1955  f = h.GetListOfFunctions().FindObject("gaus")
1956  if f == None:
1957  h.SetStats(0)
1958  return
1959  f.SetLineColor(col)
1960  f.SetLineWidth(1)
1961  h.Draw()
1962  ROOT.gPad.Update()
1963  st = h.GetListOfFunctions().FindObject("stats")
1964  if self._fit:
1965  st.SetOptFit(0o010)
1966  st.SetOptStat(1001)
1967  st.SetX1NDC(startingX)
1968  st.SetX2NDC(startingX+0.3)
1969  st.SetY1NDC(startingY+dy)
1970  st.SetY2NDC(startingY+dy+0.15)
1971  st.SetTextColor(col)
1972 
1973  dy = 0.0
1974  for i, h in enumerate(histos):
1975  if self._statyadjust is not None and i < len(self._statyadjust):
1976  dy += self._statyadjust[i]
1977 
1978  _doStats(h, _plotStylesColor[i], dy)
1979  dy -= 0.19
1980 
1981  def _normalize(self):
1982  """Normalise histograms to unit area"""
1983 
1984  for h in self._histograms:
1985  if h is None:
1986  continue
1987  i = h.Integral()
1988  if i == 0:
1989  continue
1990  if h.GetSumw2().fN <= 0: # to suppress warning
1991  h.Sumw2()
1992  h.Scale(1.0/i)
1993 
1994  def draw(self, pad, ratio, ratioFactor, nrows):
1995  """Draw the histograms using values for a given algorithm."""
1996 # if len(self._histograms) == 0:
1997 # print "No histograms for plot {name}".format(name=self._name)
1998 # return
1999 
2000  isTGraph2D = self.isTGraph2D()
2001  if isTGraph2D:
2002  # Ratios for the TGraph2Ds is not that interesting
2003  ratioOrig = ratio
2004  ratio = False
2005 
2006  if self._normalizeToUnitArea:
2007  self._normalize()
2008 
2009  if self._rebinX is not None:
2010  for h in self._histograms:
2011  h.Rebin(self._rebinX)
2012 
2013  def _styleMarker(h, msty, col):
2014  h.SetMarkerStyle(msty)
2015  h.SetMarkerColor(col)
2016  h.SetMarkerSize(0.7)
2017  h.SetLineColor(1)
2018  h.SetLineWidth(1)
2019 
2020  def _styleHist(h, msty, col):
2021  _styleMarker(h, msty, col)
2022  h.SetLineColor(col)
2023  h.SetLineWidth(self._lineWidth)
2024 
2025  # Use marker or hist style
2026  style = _styleMarker
2027  if "hist" in self._drawStyle.lower():
2028  style = _styleHist
2029  if len(self._histograms) > 0 and isinstance(self._histograms[0], ROOT.TGraph):
2030  if "l" in self._drawStyle.lower():
2031  style = _styleHist
2032 
2033  # Apply style to histograms, filter out Nones
2034  histos = []
2035  for i, h in enumerate(self._histograms):
2036  if h is None:
2037  continue
2038  style(h, _plotStylesMarker[i], _plotStylesColor[i])
2039  histos.append(h)
2040  if len(histos) == 0:
2041  if verbose:
2042  print "No histograms for plot {name}".format(name=self.getName())
2043  return
2044 
2045  # Extract x bin labels, make sure that only bins with same
2046  # label are compared with each other
2047  histosHaveBinLabels = len(histos[0].GetXaxis().GetBinLabel(1)) > 0
2048  xbinlabels = self._xbinlabels
2049  ybinlabels = None
2050  if xbinlabels is None:
2051  if histosHaveBinLabels:
2052  xbinlabels = _mergeBinLabelsX(histos)
2053  if isinstance(histos[0], ROOT.TH2):
2054  ybinlabels = _mergeBinLabelsY(histos)
2055 
2056  if len(histos) > 1: # don't bother if only one histogram
2057  # doing this for TH2 is pending for use case, for now there is only 1 histogram/plot for TH2
2058  histos = _th1IncludeOnlyBins(histos, xbinlabels)
2059  self._tmp_histos = histos # need to keep these in memory too ...
2060 
2061  # Remove empty bins, but only if histograms have bin labels
2062  if self._removeEmptyBins and histosHaveBinLabels:
2063  # at this point, all histograms have been "equalized" by their x binning and labels
2064  # therefore remove bins which are empty in all histograms
2065  if isinstance(histos[0], ROOT.TH2):
2066  (histos, xbinlabels, ybinlabels) = _th2RemoveEmptyBins(histos, xbinlabels, ybinlabels)
2067  else:
2068  (histos, xbinlabels) = _th1RemoveEmptyBins(histos, xbinlabels)
2069  self._tmp_histos = histos # need to keep these in memory too ...
2070  if len(histos) == 0:
2071  if verbose:
2072  print "No histograms with non-empty bins for plot {name}".format(name=self.getName())
2073  return
2074 
2075  if self._printBins and histosHaveBinLabels:
2076  print "####################"
2077  print self._name
2078  width = max([len(l) for l in xbinlabels])
2079  tmp = "%%-%ds " % width
2080  for b in xrange(1, histos[0].GetNbinsX()+1):
2081  s = tmp % xbinlabels[b-1]
2082  for h in histos:
2083  s += "%.3f " % h.GetBinContent(b)
2084  print s
2085  print
2086 
2087  bounds = _findBounds(histos, self._ylog,
2088  xmin=self._xmin, xmax=self._xmax,
2089  ymin=self._ymin, ymax=self._ymax)
2090 
2091  # need to keep these in memory
2094 
2095  if ratio:
2096  self._ratios = _calculateRatios(histos, self._ratioUncertainty) # need to keep these in memory too ...
2097  ratioHistos = filter(lambda h: h is not None, [r.getRatio() for r in self._ratios[1:]])
2098 
2099  if len(ratioHistos) > 0:
2100  ratioBoundsY = _findBoundsY(ratioHistos, ylog=False, ymin=self._ratioYmin, ymax=self._ratioYmax, coverage=0.68, coverageRange=self._ratioCoverageXrange)
2101  else:
2102  ratioBoundsY = (0.9, 1,1) # hardcoded default in absence of valid ratio calculations
2103 
2104  if self._ratioFit is not None:
2105  for i, rh in enumerate(ratioHistos):
2106  tf_line = ROOT.TF1("line%d"%i, "[0]+x*[1]")
2107  tf_line.SetRange(self._ratioFit["rangemin"], self._ratioFit["rangemax"])
2108  fitres = rh.Fit(tf_line, "RINSQ")
2109  tf_line.SetLineColor(rh.GetMarkerColor())
2110  tf_line.SetLineWidth(2)
2111  self._ratioAdditional.append(tf_line)
2112  box = PlotTextBox(xmin=self._ratioFit.get("boxXmin", 0.14), ymin=None, # None for automatix
2113  xmax=self._ratioFit.get("boxXmax", 0.35), ymax=self._ratioFit.get("boxYmax", 0.09),
2114  color=rh.GetMarkerColor(), font=43, size=11, lineheight=0.02)
2115  box.move(dx=(box.width()+0.01)*i)
2116  #box.addText("Const: %.4f" % fitres.Parameter(0))
2117  #box.addText("Slope: %.4f" % fitres.Parameter(1))
2118  box.addText("Const: %.4f#pm%.4f" % (fitres.Parameter(0), fitres.ParError(0)))
2119  box.addText("Slope: %.4f#pm%.4f" % (fitres.Parameter(1), fitres.ParError(1)))
2120  self._mainAdditional.append(box)
2121 
2122 
2123  # Create bounds before stats in order to have the
2124  # SetRangeUser() calls made before the fit
2125  #
2126  # stats is better to be called before frame, otherwise get
2127  # mess in the plot (that frame creation cleans up)
2128  if ratio:
2129  pad.cd(1)
2130  self._setStats(histos, self._statx, self._staty)
2131 
2132  # Create frame
2133  if isTGraph2D:
2134  frame = FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
2135  else:
2136  if ratio:
2137  ratioBounds = (bounds[0], ratioBoundsY[0], bounds[2], ratioBoundsY[1])
2138  frame = FrameRatio(pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
2139  else:
2140  frame = Frame(pad, bounds, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption, ybinlabels=ybinlabels)
2141 
2142  # Set log and grid
2143  frame.setLogx(self._xlog)
2144  frame.setLogy(self._ylog)
2145  frame.setGridx(self._xgrid)
2146  frame.setGridy(self._ygrid)
2147 
2148  # Construct draw option string
2149  opt = "sames" # s for statbox or something?
2150  ds = ""
2151  if self._drawStyle is not None:
2152  ds = self._drawStyle
2153  if self._drawCommand is not None:
2154  ds = self._drawCommand
2155  if len(ds) > 0:
2156  opt += " "+ds
2157 
2158  # Set properties of frame
2159  frame.setTitle(histos[0].GetTitle())
2160  if self._xtitle is not None:
2161  frame.setXTitle(self._xtitle)
2162  if self._xtitlesize is not None:
2163  frame.setXTitleSize(self._xtitlesize)
2164  if self._xtitleoffset is not None:
2165  frame.setXTitleOffset(self._xtitleoffset)
2166  if self._xlabelsize is not None:
2167  frame.setXLabelSize(self._xlabelsize)
2168  if self._ytitle is not None:
2169  frame.setYTitle(self._ytitle)
2170  if self._ytitlesize is not None:
2171  frame.setYTitleSize(self._ytitlesize)
2172  if self._ytitleoffset is not None:
2173  frame.setYTitleOffset(self._ytitleoffset)
2174  if self._ztitle is not None:
2175  frame.setZTitle(self._ztitle)
2176  if self._ztitleoffset is not None:
2177  frame.setZTitleOffset(self._ztitleoffset)
2178  if self._adjustMarginLeft is not None:
2179  frame.adjustMarginLeft(self._adjustMarginLeft)
2180  if self._adjustMarginRight is not None:
2181  frame.adjustMarginRight(self._adjustMarginRight)
2182  elif "z" in opt:
2183  frame.adjustMarginLeft(0.03)
2184  frame.adjustMarginRight(0.08)
2185 
2186  # Draw histograms
2187  if ratio:
2188  frame._pad.cd()
2189 
2190  for h in histos:
2191  h.Draw(opt)
2192 
2193  for addl in self._mainAdditional:
2194  addl.Draw("same")
2195 
2196  # Draw ratios
2197  if ratio and len(histos) > 0:
2198  frame._padRatio.cd()
2199  firstRatio = self._ratios[0].getRatio()
2200  if self._ratioUncertainty and firstRatio is not None:
2201  firstRatio.SetFillStyle(1001)
2202  firstRatio.SetFillColor(ROOT.kGray)
2203  firstRatio.SetLineColor(ROOT.kGray)
2204  firstRatio.SetMarkerColor(ROOT.kGray)
2205  firstRatio.SetMarkerSize(0)
2206  self._ratios[0].draw("E2")
2207  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
2208  for r in self._ratios[1:]:
2209  r.draw()
2210 
2211  for addl in self._ratioAdditional:
2212  addl.Draw("same")
2213 
2214  frame.redrawAxis()
2215  self._frame = frame # keep the frame in memory for sure
2216 
2217  def addToLegend(self, legend, legendLabels, denomUncertainty):
2218  """Add histograms to a legend.
2219 
2220  Arguments:
2221  legend -- TLegend
2222  legendLabels -- List of strings for the legend labels
2223  """
2224  first = denomUncertainty
2225  for h, label in zip(self._histograms, legendLabels):
2226  if h is None:
2227  first = False
2228  continue
2229  if first:
2230  self._forLegend = h.Clone()
2231  self._forLegend.SetFillStyle(1001)
2232  self._forLegend.SetFillColor(ROOT.kGray)
2233  entry = legend.AddEntry(self._forLegend, label, "lpf")
2234  first = False
2235  else:
2236  legend.AddEntry(h, label, "LP")
2237 
2239  """Group of plots, results a TCanvas"""
2240  def __init__(self, name, plots, **kwargs):
2241  """Constructor.
2242 
2243  Arguments:
2244  name -- String for name of the TCanvas, used also as the basename of the picture files
2245  plots -- List of Plot objects
2246 
2247  Keyword arguments:
2248  ncols -- Number of columns (default 2)
2249  legendDx -- Float for moving TLegend in x direction (default None)
2250  legendDy -- Float for moving TLegend in y direction (default None)
2251  legendDw -- Float for changing TLegend width (default None)
2252  legendDh -- Float for changing TLegend height (default None)
2253  legend -- Bool for disabling legend (default True for legend being enabled)
2254  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
2255  onlyForPileup -- Plots this group only for pileup samples
2256  """
2257  super(PlotGroup, self).__init__()
2258 
2259  self._name = name
2260  self._plots = plots
2261 
2262  def _set(attr, default):
2263  setattr(self, "_"+attr, kwargs.get(attr, default))
2264 
2265  _set("ncols", 2)
2266 
2267  _set("legendDx", None)
2268  _set("legendDy", None)
2269  _set("legendDw", None)
2270  _set("legendDh", None)
2271  _set("legend", True)
2272 
2273  _set("overrideLegendLabels", None)
2274 
2275  _set("onlyForPileup", False)
2276 
2277  self._ratioFactor = 1.25
2278 
2279  def setProperties(self, **kwargs):
2280  for name, value in kwargs.iteritems():
2281  if not hasattr(self, "_"+name):
2282  raise Exception("No attribute '%s'" % name)
2283  setattr(self, "_"+name, value)
2284 
2285  def getName(self):
2286  return self._name
2287 
2288  def getPlots(self):
2289  return self._plots
2290 
2291  def remove(self, name):
2292  for i, plot in enumerate(self._plots):
2293  if plot.getName() == name:
2294  del self._plots[i]
2295  return
2296  raise Exception("Did not find Plot '%s' from PlotGroup '%s'" % (name, self._name))
2297 
2298  def clear(self):
2299  self._plots = []
2300 
2301  def append(self, plot):
2302  self._plots.append(plot)
2303 
2304  def getPlot(self, name):
2305  for plot in self._plots:
2306  if plot.getName() == name:
2307  return plot
2308  raise Exception("No Plot named '%s'" % name)
2309 
2310  def onlyForPileup(self):
2311  """Return True if the PlotGroup is intended only for pileup samples"""
2312  return self._onlyForPileup
2313 
2314  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2315  """Create histograms from a list of TDirectories.
2316 
2317  Arguments:
2318  tdirectoryNEvents -- List of (TDirectory, nevents) pairs
2319  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2320  """
2321  for plot in self._plots:
2322  plot.create(tdirectoryNEvents, requireAllHistograms)
2323 
2324  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2325  """Draw the histograms using values for a given algorithm.
2326 
2327  Arguments:
2328  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
2329  prefix -- Optional string for file name prefix (default None)
2330  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2331  saveFormat -- String specifying the plot format (default '.pdf')
2332  ratio -- Add ratio to the plot (default True)
2333  directory -- Directory where to save the file (default "")
2334  """
2335 
2336  if self._overrideLegendLabels is not None:
2337  legendLabels = self._overrideLegendLabels
2338 
2339  # Do not draw the group if it would be empty
2340  onlyEmptyPlots = True
2341  for plot in self._plots:
2342  if not plot.isEmpty():
2343  onlyEmptyPlots = False
2344  break
2345  if onlyEmptyPlots:
2346  return []
2347 
2348  if separate:
2349  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio, directory)
2350 
2351  cwidth = 500*self._ncols
2352  nrows = int((len(self._plots)+self._ncols-1)/self._ncols) # this should work also for odd n
2353  cheight = 500 * nrows
2354 
2355  if ratio:
2356  cheight = int(cheight*self._ratioFactor)
2357 
2358  canvas = _createCanvas(self._name, cwidth, cheight)
2359 
2360  canvas.Divide(self._ncols, nrows)
2361  if ratio:
2362  for i, plot in enumerate(self._plots):
2363  pad = canvas.cd(i+1)
2364  self._modifyPadForRatio(pad)
2365 
2366  # Draw plots to canvas
2367  for i, plot in enumerate(self._plots):
2368  pad = canvas.cd(i+1)
2369  if not plot.isEmpty():
2370  plot.draw(pad, ratio, self._ratioFactor, nrows)
2371 
2372  # Setup legend
2373  canvas.cd()
2374  if len(self._plots) <= 4:
2375  lx1 = 0.2
2376  lx2 = 0.9
2377  ly1 = 0.48
2378  ly2 = 0.53
2379  else:
2380  lx1 = 0.1
2381  lx2 = 0.9
2382  ly1 = 0.64
2383  ly2 = 0.67
2384  if self._legendDx is not None:
2385  lx1 += self._legendDx
2386  lx2 += self._legendDx
2387  if self._legendDy is not None:
2388  ly1 += self._legendDy
2389  ly2 += self._legendDy
2390  if self._legendDw is not None:
2391  lx2 += self._legendDw
2392  if self._legendDh is not None:
2393  ly1 -= self._legendDh
2394  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
2395  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
2396  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2397  denomUncertainty=(ratio and denomUnc))
2398 
2399  return self._save(canvas, saveFormat, prefix=prefix, directory=directory)
2400 
2401  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory):
2402  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2403  width = 500
2404  height = 500
2405 
2406  canvas = _createCanvas(self._name+"Single", width, height)
2407  canvasRatio = _createCanvas(self._name+"SingleRatio", width, int(height*self._ratioFactor))
2408 
2409  # from TDRStyle
2410  for c in [canvas, canvasRatio]:
2411  c.SetTopMargin(0.05)
2412  c.SetBottomMargin(0.13)
2413  c.SetLeftMargin(0.16)
2414  c.SetRightMargin(0.05)
2415 
2416  lx1def = 0.6
2417  lx2def = 0.95
2418  ly1def = 0.85
2419  ly2def = 0.95
2420 
2421  ret = []
2422 
2423  for plot in self._plots:
2424  if plot.isEmpty():
2425  continue
2426 
2427  ratioForThisPlot = plot.isRatio(ratio)
2428  c = canvas
2429  if ratioForThisPlot:
2430  c = canvasRatio
2431  c.cd()
2432  self._modifyPadForRatio(c)
2433 
2434  # Draw plot to canvas
2435  c.cd()
2436  plot.draw(c, ratioForThisPlot, self._ratioFactor, 1)
2437 
2438  if plot._legend:
2439  # Setup legend
2440  lx1 = lx1def
2441  lx2 = lx2def
2442  ly1 = ly1def
2443  ly2 = ly2def
2444 
2445  if plot._legendDx is not None:
2446  lx1 += plot._legendDx
2447  lx2 += plot._legendDx
2448  if plot._legendDy is not None:
2449  ly1 += plot._legendDy
2450  ly2 += plot._legendDy
2451  if plot._legendDw is not None:
2452  lx2 += plot._legendDw
2453  if plot._legendDh is not None:
2454  ly1 -= plot._legendDh
2455 
2456  c.cd()
2457  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2458  denomUncertainty=(ratioForThisPlot and plot.drawRatioUncertainty))
2459 
2460  ret.extend(self._save(c, saveFormat, prefix=prefix, postfix="_"+plot.getName(), single=True, directory=directory))
2461  return ret
2462 
2463  def _modifyPadForRatio(self, pad):
2464  """Internal method to set divide a pad to two for ratio plots"""
2465  _modifyPadForRatio(pad, self._ratioFactor)
2466 
2467  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2468  if not self._legend:
2469  return None
2470 
2471  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2472  l.SetTextSize(textSize)
2473  l.SetLineColor(1)
2474  l.SetLineWidth(1)
2475  l.SetLineStyle(1)
2476  l.SetFillColor(0)
2477  l.SetMargin(0.07)
2478 
2479  plot.addToLegend(l, legendLabels, denomUncertainty)
2480  l.Draw()
2481  return l
2482 
2483  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory=""):
2484  # Save the canvas to file and clear
2485  name = self._name
2486  if prefix is not None:
2487  name = prefix+name
2488  if postfix is not None:
2489  name = name+postfix
2490  name = os.path.join(directory, name)
2491 
2492  if not verbose: # silence saved file printout
2493  backup = ROOT.gErrorIgnoreLevel
2494  ROOT.gErrorIgnoreLevel = ROOT.kWarning
2495  canvas.SaveAs(name+saveFormat)
2496  if not verbose:
2497  ROOT.gErrorIgnoreLevel = backup
2498 
2499  if single:
2500  canvas.Clear()
2501  canvas.SetLogx(False)
2502  canvas.SetLogy(False)
2503  else:
2504  canvas.Clear("D") # keep subpads
2505 
2506  return [name+saveFormat]
2507 
2509  """Resembles DQM GUI's "On side" layout.
2510 
2511  Like PlotGroup, but has only a description of a single plot. The
2512  plot is drawn separately for each file. Useful for 2D histograms."""
2513 
2514  def __init__(self, name, plot, ncols=2, onlyForPileup=False):
2515  super(PlotOnSideGroup, self).__init__(name, [], ncols=ncols, legend=False, onlyForPileup=onlyForPileup)
2516  self._plot = plot
2517  self._plot.setProperties(ratio=False)
2518 
2519  def append(self, *args, **kwargs):
2520  raise Exception("PlotOnSideGroup.append() is not implemented")
2521 
2522  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2523  self._plots = []
2524  for element in tdirectoryNEvents:
2525  pl = self._plot.clone()
2526  pl.create([element], requireAllHistograms)
2527  self._plots.append(pl)
2528 
2529  def draw(self, *args, **kwargs):
2530  kargs = copy.copy(kwargs)
2531  kargs["ratio"] = False
2532  return super(PlotOnSideGroup, self).draw(*args, **kargs)
2533 
2535 
2536  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2537  def __init__(self, *plotGroups, **kwargs):
2538  """Constructor.
2539 
2540  Arguments:
2541  plotGroups -- List of PlotGroup objects
2542 
2543  Keyword arguments
2544  loopSubFolders -- Should the subfolders be looped over? (default: True)
2545  onlyForPileup -- Plots this folder only for pileup samples
2546  onlyForElectron -- Plots this folder only for electron samples
2547  onlyForConversion -- Plots this folder only for conversion samples
2548  onlyForBHadron -- Plots this folder only for B-hadron samples
2549  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2550  page -- Optional string for the page in HTML generatin
2551  section -- Optional string for the section within a page in HTML generation
2552  numberOfEventsHistogram -- Optional path to histogram filled once per event. Needed if there are any plots normalized by number of events. Path is relative to "possibleDqmFolders".
2553  """
2554  self._plotGroups = list(plotGroups)
2555  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
2556  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
2557  self._onlyForElectron = kwargs.pop("onlyForElectron", False)
2558  self._onlyForConversion = kwargs.pop("onlyForConversion", False)
2559  self._onlyForBHadron = kwargs.pop("onlyForBHadron", False)
2560  self._purpose = kwargs.pop("purpose", None)
2561  self._page = kwargs.pop("page", None)
2562  self._section = kwargs.pop("section", None)
2563  self._numberOfEventsHistogram = kwargs.pop("numberOfEventsHistogram", None)
2564  if len(kwargs) > 0:
2565  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
2566 
2567  def loopSubFolders(self):
2568  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2569  return self._loopSubFolders
2570 
2571  def onlyForPileup(self):
2572  """Return True if the folder is intended only for pileup samples"""
2573  return self._onlyForPileup
2574 
2575  def onlyForElectron(self):
2576  return self._onlyForElectron
2577 
2579  return self._onlyForConversion
2580 
2581  def onlyForBHadron(self):
2582  return self._onlyForBHadron
2583 
2584  def getPurpose(self):
2585  return self._purpose
2586 
2587  def getPage(self):
2588  return self._page
2589 
2590  def getSection(self):
2591  return self._section
2592 
2594  return self._numberOfEventsHistogram
2595 
2596  def append(self, plotGroup):
2597  self._plotGroups.append(plotGroup)
2598 
2599  def set(self, plotGroups):
2600  self._plotGroups = plotGroups
2601 
2602  def getPlotGroups(self):
2603  return self._plotGroups
2604 
2605  def getPlotGroup(self, name):
2606  for pg in self._plotGroups:
2607  if pg.getName() == name:
2608  return pg
2609  raise Exception("No PlotGroup named '%s'" % name)
2610 
2611  def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2612  """Create histograms from a list of TFiles.
2613 
2614  Arguments:
2615  dirsNEvents -- List of (TDirectory, nevents) pairs
2616  labels -- List of strings for legend labels corresponding the files
2617  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2618  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2619  """
2620 
2621  if len(dirsNEvents) != len(labels):
2622  raise Exception("len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2623 
2624  self._labels = labels
2625 
2626  for pg in self._plotGroups:
2627  if pg.onlyForPileup() and not isPileupSample:
2628  continue
2629  pg.create(dirsNEvents, requireAllHistograms)
2630 
2631  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2632  """Draw and save all plots using settings of a given algorithm.
2633 
2634  Arguments:
2635  prefix -- Optional string for file name prefix (default None)
2636  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2637  saveFormat -- String specifying the plot format (default '.pdf')
2638  ratio -- Add ratio to the plot (default True)
2639  directory -- Directory where to save the file (default "")
2640  """
2641  ret = []
2642 
2643  for pg in self._plotGroups:
2644  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio, directory=directory))
2645  return ret
2646 
2647 
2648  # These are to be overridden by derived classes for customisation
2649  def translateSubFolder(self, dqmSubFolderName):
2650  """Method called to (possibly) translate a subfolder name to more 'readable' form
2651 
2652  The implementation in this (base) class just returns the
2653  argument. The idea is that a deriving class might want to do
2654  something more complex (like trackingPlots.TrackingPlotFolder
2655  does)
2656  """
2657  return dqmSubFolderName
2658 
2659  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
2660  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2661  ret = ""
2662  if plotFolderName != "":
2663  ret += "_"+plotFolderName
2664  if translatedDqmSubFolder is not None:
2665  ret += "_"+translatedDqmSubFolder
2666  yield ret
2667 
2668  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
2669  """Return True if this subfolder should be processed
2670 
2671  Arguments:
2672  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2673  translatedDqmSubFolder -- Return value of translateSubFolder
2674  """
2675  return translatedDqmSubFolder in limitOnlyTo
2676 
2678  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2679  def __init__(self, subfolder, translated):
2680  self.subfolder = subfolder
2681  self.translated = translated
2682 
2683  def equalTo(self, other):
2684  """Equality is defined by the 'translated' name"""
2685  return self.translated == other.translated
2686 
2688  """Plotter for one DQM folder.
2689 
2690  This class is supposed to be instantiated by the Plotter class (or
2691  PlotterItem, to be more specific), and not used directly by the
2692  user.
2693  """
2694  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2695  """
2696  Constructor
2697 
2698  Arguments:
2699  name -- Name of the folder (is used in the output directory naming)
2700  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2701  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2702  plotFolder -- PlotFolder object
2703  fallbackNames -- List of names for backward compatibility (can be empty). These are used only by validation.Validation (class responsible of the release validation workflow) in case the reference file pointed by 'name' does not exist.
2704  fallbackDqmSubFolders -- List of dicts of (string->string) for mapping the subfolder names found in the first file to another names. Use case is comparing files that have different iteration naming convention.
2705  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2706  """
2707  self._name = name
2708  self._possibleDqmFolders = possibleDqmFolders
2709  self._plotFolder = plotFolder
2710  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
2711  if dqmSubFolders is None:
2712  self._dqmSubFolders = None
2713  else:
2714  # Match the subfolders between files in case the lists differ
2715  # equality is by the 'translated' name
2716  subfolders = {}
2717  for sf_list in dqmSubFolders:
2718  for sf in sf_list:
2719  sf_translated = self._plotFolder.translateSubFolder(sf)
2720  if sf_translated is not None and not sf_translated in subfolders:
2721  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
2722 
2723  self._dqmSubFolders = subfolders.values()
2724  self._dqmSubFolders.sort(key=lambda sf: sf.subfolder)
2725 
2726  self._fallbackNames = fallbackNames
2727  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2728  self._tableCreators = tableCreators
2729 
2730  def getName(self):
2731  return self._name
2732 
2733  def getPurpose(self):
2734  return self._plotFolder.getPurpose()
2735 
2736  def getPage(self):
2737  return self._plotFolder.getPage()
2738 
2739  def getSection(self):
2740  return self._plotFolder.getSection()
2741 
2742  def onlyForPileup(self):
2743  return self._plotFolder.onlyForPileup()
2744 
2745  def onlyForElectron(self):
2746  return self._plotFolder.onlyForElectron()
2747 
2749  return self._plotFolder.onlyForConversion()
2750 
2751  def onlyForBHadron(self):
2752  return self._plotFolder.onlyForBHadron()
2753 
2755  return self._possibleDqmFolders
2756 
2757  def getDQMSubFolders(self, limitOnlyTo=None):
2758  """Get list of subfolders, possibly limiting to some of them.
2759 
2760  Keyword arguments:
2761  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2762  """
2763 
2764  if self._dqmSubFolders is None:
2765  return [None]
2766 
2767  if limitOnlyTo is None:
2768  return self._dqmSubFolders
2769 
2770  return filter(lambda s: self._plotFolder.limitSubFolder(limitOnlyTo, s.translated), self._dqmSubFolders)
2771 
2772  def getTableCreators(self):
2773  return self._tableCreators
2774 
2775  def getSelectionNameIterator(self, dqmSubFolder):
2776  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2777  for name in [self._name]+self._fallbackNames:
2778  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2779  yield selname
2780 
2781  def getSelectionName(self, dqmSubFolder):
2782  return next(self.getSelectionNameIterator(dqmSubFolder))
2783 
2784  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2785  """Create histograms from a list of TFiles.
2786  Arguments:
2787  files -- List of TFiles
2788  labels -- List of strings for legend labels corresponding the files
2789  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2790  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2791  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2792  """
2793 
2794  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2795  neventsHisto = self._plotFolder.getNumberOfEventsHistogram()
2796  dirsNEvents = []
2797 
2798  for tfile in files:
2799  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2800  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2802  for fallbackFunc in self._fallbackDqmSubFolders:
2803  fallback = fallbackFunc(subfolder)
2804  if fallback is not None:
2805  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2806  if ret is not GetDirectoryCode.SubDirNotExist:
2807  break
2808  d = GetDirectoryCode.codesToNone(ret)
2809  nev = None
2810  if neventsHisto is not None and tfile is not None:
2811  hnev = _getObject(tfile, neventsHisto)
2812  if hnev is not None:
2813  nev = hnev.GetEntries()
2814  dirsNEvents.append( (d, nev) )
2815 
2816  self._plotFolder.create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2817 
2818  def draw(self, *args, **kwargs):
2819  """Draw and save all plots using settings of a given algorithm."""
2820  return self._plotFolder.draw(*args, **kwargs)
2821 
2822 
2824  """Instance of plotter that knows the directory content, holds many folders."""
2825  def __init__(self, folders):
2826  self._plotterFolders = filter(lambda f: f is not None, folders)
2827 
2828  def iterFolders(self, limitSubFoldersOnlyTo=None):
2829  for plotterFolder in self._plotterFolders:
2830  limitOnlyTo = None
2831  if limitSubFoldersOnlyTo is not None:
2832  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2833 
2834  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2835  yield plotterFolder, dqmSubFolder
2836 
2837 # Helper for Plotter
2839  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2840  """ Constructor
2841 
2842  Arguments:
2843  name -- Name of the folder (is used in the output directory naming)
2844  possibleDirs -- List of strings for possible directories of histograms in TFiles
2845  plotFolder -- PlotFolder object
2846 
2847  Keyword arguments
2848  fallbackNames -- Optional list of names for backward compatibility. These are used only by validation.Validation (class responsible of the release validation workflow) in case the reference file pointed by 'name' does not exist.
2849  fallbackDqmSubFolders -- Optional list of functions for (string->string) mapping the subfolder names found in the first file to another names (function should return None for no mapping). Use case is comparing files that have different iteration naming convention.
2850  """
2851  self._name = name
2852  self._possibleDirs = possibleDirs
2853  self._plotFolder = plotFolder
2854  self._fallbackNames = fallbackNames
2855  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2856  self._tableCreators = []
2857 
2858  def getName(self):
2859  return self._name
2860 
2861  def getPlotFolder(self):
2862  return self._plotFolder
2863 
2864  def appendTableCreator(self, tc):
2865  self._tableCreators.append(tc)
2866 
2867  def readDirs(self, files):
2868  """Read available subfolders from the files
2869 
2870  Arguments:
2871  files -- List of strings for paths to files, or list of TFiles
2872 
2873  For each file, loop over 'possibleDirs', and read the
2874  subfolders of first one that exists.
2875 
2876  Returns a PlotterFolder if at least one file for which one of
2877  'possibleDirs' exists. Otherwise, return None to signal that
2878  there is nothing available for this PlotFolder.
2879  """
2880  subFolders = None
2881  if self._plotFolder.loopSubFolders():
2882  subFolders = []
2883  possibleDirFound = False
2884  for fname in files:
2885  if fname is None:
2886  continue
2887 
2888  isOpenFile = isinstance(fname, ROOT.TFile)
2889  if isOpenFile:
2890  tfile = fname
2891  else:
2892  tfile = ROOT.TFile.Open(fname)
2893  for pd in self._possibleDirs:
2894  d = tfile.Get(pd)
2895  if d:
2896  possibleDirFound = True
2897  if subFolders is not None:
2898  subf = []
2899  for key in d.GetListOfKeys():
2900  if isinstance(key.ReadObj(), ROOT.TDirectory):
2901  subf.append(key.GetName())
2902  subFolders.append(subf)
2903  break
2904 
2905  if not isOpenFile:
2906  tfile.Close()
2907 
2908  if not possibleDirFound:
2909  return None
2910 
2911  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2912 
2914  def __init__(self, possibleDirs, tableCreator):
2915  self._possibleDirs = possibleDirs
2916  self._tableCreator = tableCreator
2917 
2918  def create(self, openFiles, legendLabels, dqmSubFolder):
2919  if isinstance(dqmSubFolder, list):
2920  if len(dqmSubFolder) != len(openFiles):
2921  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2922  else:
2923  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2924  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2925 
2926  tbl = []
2927  for f, sf in zip(openFiles, dqmSubFolder):
2928  data = None
2929  tdir = _getDirectory(f, self._possibleDirs, sf)
2930  if tdir is not None:
2931  data = self._tableCreator.create(tdir)
2932  tbl.append(data)
2933 
2934  # Check if we have any content
2935  allNones = True
2936  colLen = 0
2937  for col in tbl:
2938  if col is not None:
2939  allNones = False
2940  colLen = len(col)
2941  break
2942  if allNones:
2943  return None
2944 
2945  # Replace all None columns with lists of column length
2946  for i in xrange(len(tbl)):
2947  if tbl[i] is None:
2948  tbl[i] = [None]*colLen
2949 
2950  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2951  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2952 
2953 class Plotter:
2954  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2955  def __init__(self):
2956  self._plots = []
2957  _setStyle()
2958  ROOT.TH1.AddDirectory(False)
2959 
2960  def append(self, *args, **kwargs):
2961  """Append a plot folder to the plotter.
2962 
2963  All arguments are forwarded to the constructor of PlotterItem.
2964  """
2965  self._plots.append(PlotterItem(*args, **kwargs))
2966 
2967  def appendTable(self, attachToFolder, *args, **kwargs):
2968  for plotterItem in self._plots:
2969  if plotterItem.getName() == attachToFolder:
2970  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2971  return
2972  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2973 
2974  def clear(self):
2975  """Remove all plot folders and tables"""
2976  self._plots = []
2977 
2979  return [item.getName() for item in self._plots]
2980 
2981  def getPlotFolders(self):
2982  return [item.getPlotFolder() for item in self._plots]
2983 
2984  def getPlotFolder(self, name):
2985  for item in self._plots:
2986  if item.getName() == name:
2987  return item.getPlotFolder()
2988  raise Exception("No PlotFolder named '%s'" % name)
2989 
2990  def readDirs(self, *files):
2991  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
2992  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def remove(self, name)
Definition: plotting.py:2291
def create(self, tdirectory)
Definition: plotting.py:868
def iterSelectionName(self, plotFolderName, translatedDqmSubFolder)
Definition: plotting.py:2659
def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None)
Definition: plotting.py:1535
def getName(self)
Definition: plotting.py:1681
def onlyForPileup(self)
Definition: plotting.py:2571
def __init__(self)
Definition: plotting.py:1678
def append(self, plotGroup)
Definition: plotting.py:2596
def getName(self)
Definition: plotting.py:2285
def _setStyle()
Definition: plotting.py:18
def _getYmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:384
def loopSubFolders(self)
Definition: plotting.py:2567
def _copyStyle(src, dst)
Definition: plotting.py:1664
def __init__(self, name, plots, kwargs)
Definition: plotting.py:2240
def __init__(self, name, nameA, nameB, title="")
Definition: plotting.py:759
def getPossibleDQMFolders(self)
Definition: plotting.py:2754
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1471
def _findBoundsY(th1s, ylog, ymin=None, ymax=None, coverage=None, coverageRange=None)
Definition: plotting.py:523
def set(self, plotGroups)
Definition: plotting.py:2599
def setXTitle(self, title)
Definition: plotting.py:1382
def setLogx(self, log)
Definition: plotting.py:1346
def readDirs(self, files)
Definition: plotting.py:2990
def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory="")
Definition: plotting.py:2324
def setXLabelSize(self, size)
Definition: plotting.py:1491
def __init__(self, name, histo, func, title="")
Definition: plotting.py:807
def setGridx(self, grid)
Definition: plotting.py:1465
def _getOrCreateObject(tdirectory, nameOrCreator)
Definition: plotting.py:54
def iterFolders(self, limitSubFoldersOnlyTo=None)
Definition: plotting.py:2828
def redrawAxis(self)
Definition: plotting.py:1408
def setYTitleSize(self, size)
Definition: plotting.py:1497
def setXLabelSize(self, size)
Definition: plotting.py:1391
def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None)
Definition: plotting.py:455
def _modifyPadForRatio(self, pad)
Definition: plotting.py:2463
def Draw(self, options="")
Definition: plotting.py:1648
def equalTo(self, other)
Definition: plotting.py:2683
def __init__(self, name, xhistoName, yhistoName, zaxis=False)
Definition: plotting.py:1119
def __init__(self, pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ratioYTitle=_ratioYTitle)
Definition: plotting.py:1308
def adjustMarginRight(self, adjust)
Definition: plotting.py:1273
def addText(self, text)
Definition: plotting.py:1614
def __init__(self, possibleDirs, tableCreator)
Definition: plotting.py:2914
def getTableCreators(self)
Definition: plotting.py:2772
def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, kwargs)
Definition: plotting.py:1586
def _getYmin(obj, limitToNonZeroContent=False)
Definition: plotting.py:369
def create(self, args, kwargs)
Definition: plotting.py:1687
def setXTitleSize(self, size)
Definition: plotting.py:1285
def create(self, tdirectory)
Definition: plotting.py:1139
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1361
def clear(self)
Definition: plotting.py:2298
def setXLabelSize(self, size)
Definition: plotting.py:1291
def _getYminIgnoreOutlier(th1)
Definition: plotting.py:402
def divide(outfile, dest, numer, denom)
Definition: rootmath.py:291
def append(self, args, kwargs)
Definition: plotting.py:2960
def getDQMSubFolders(self, limitOnlyTo=None)
Definition: plotting.py:2757
def _getXmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:353
def append(self, plot)
Definition: plotting.py:2301
def setGridx(self, grid)
Definition: plotting.py:1353
def setLogy(self, log)
Definition: plotting.py:1462
def setGridy(self, grid)
Definition: plotting.py:1264
def drawRatioUncertainty(self)
Definition: plotting.py:1684
def setZTitle(self, title)
Definition: plotting.py:1503
def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor)
Definition: plotting.py:1428
def setZTitleOffset(self, offset)
Definition: plotting.py:1506
def setXTitleSize(self, size)
Definition: plotting.py:1485
def getNumberOfHistograms(self)
Definition: plotting.py:1845
def create(self, tdirectoryNEvents, requireAllHistograms=False)
Definition: plotting.py:2314
bool equal(const T &first, const T &second)
Definition: Equal.h:34
def move(self, dx=0, dy=0, dw=0, dh=0)
Definition: plotting.py:1622
def __init__(self, name, kwargs)
Definition: plotting.py:1698
def getPlotGroups(self)
Definition: plotting.py:2602
def create(self, openFiles, legendLabels, dqmSubFolder)
Definition: plotting.py:2918
def _calculateRatios(histos, ratioUncertainty=False)
Definition: plotting.py:145
def appendTable(self, attachToFolder, args, kwargs)
Definition: plotting.py:2967
def setYTitleOffset(self, offset)
Definition: plotting.py:1300
def setYTitle(self, title)
Definition: plotting.py:1394
def clear(self)
Definition: plotting.py:2974
def create(self, tdirectory)
Definition: plotting.py:824
def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory="")
Definition: plotting.py:2483
def setYTitleSize(self, size)
Definition: plotting.py:1297
def onlyForElectron(self)
Definition: plotting.py:2575
def draw(name, histos, styles=_defaultStyles, legendLabels=[], kwargs)
def _setStats(self, histos, startingX, startingY)
Definition: plotting.py:1940
def setLogy(self, log)
Definition: plotting.py:1258
def getSelectionName(self, dqmSubFolder)
Definition: plotting.py:2781
def _getDirectory(args, kwargs)
Definition: plotting.py:94
def setYTitle(self, title)
Definition: plotting.py:1294
def Draw(self, options=None)
Definition: plotting.py:1573
def draw(self, pad, ratio, ratioFactor, nrows)
Definition: plotting.py:1994
Definition: style.py:1
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
def setTitle(self, title)
Definition: plotting.py:1379
def setXTitle(self, title)
Definition: plotting.py:1282
def getPlots(self)
Definition: plotting.py:2288
def create(self, tdirectory)
Definition: plotting.py:782
def isRatio(self, ratio)
Definition: plotting.py:1859
def setGridx(self, grid)
Definition: plotting.py:1261
def readDirs(self, files)
Definition: plotting.py:2867
def _mergeBinLabelsY(histos)
Definition: plotting.py:706
def getPlotFolder(self)
Definition: plotting.py:2861
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def adjustMarginRight(self, adjust)
Definition: plotting.py:1475
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1267
def onlyForPileup(self)
Definition: plotting.py:2310
#define end
Definition: vmac.h:37
def getPlot(self, name)
Definition: plotting.py:2304
def setProperties(self, kwargs)
Definition: plotting.py:2279
def __str__(self)
Definition: plotting.py:778
T min(T a, T b)
Definition: MathUtil.h:58
def addToLegend(self, legend, legendLabels, denomUncertainty)
Definition: plotting.py:2217
def _getYmaxWithError(th1)
Definition: plotting.py:399
def setXTitleOffset(self, offset)
Definition: plotting.py:1288
def _getDirectoryDetailed(tfile, possibleDirs, subDir=None)
Definition: plotting.py:70
def setXTitleOffset(self, size)
Definition: plotting.py:1488
def draw(self, args, kwargs)
Definition: plotting.py:2529
def create(self, tdirectory)
Definition: plotting.py:984
def __init__(self, pad, bounds, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None)
Definition: plotting.py:1235
def onlyForConversion(self)
Definition: plotting.py:2578
def _getYminMaxAroundMedian(obj, coverage, coverageRange=None)
Definition: plotting.py:419
def getPlotFolder(self, name)
Definition: plotting.py:2984
def setYTitleOffset(self, offset)
Definition: plotting.py:1500
def setLogy(self, log)
Definition: plotting.py:1350
def __init__(self, name, histo, title="")
Definition: plotting.py:901
def __str__(self)
Definition: plotting.py:820
def onlyForConversion(self)
Definition: plotting.py:2748
def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder)
Definition: plotting.py:2668
def setXTitleSize(self, size)
Definition: plotting.py:1385
def setYTitleRatio(self, title)
Definition: plotting.py:1397
def drawRatioUncertainty(self)
Definition: plotting.py:1872
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False)
Definition: plotting.py:2611
def setGridy(self, grid)
Definition: plotting.py:1468
def getNumberOfHistograms(self)
Definition: plotting.py:1693
def __init__(self, name, assoc, dup, reco, title="")
Definition: plotting.py:844
def onlyForBHadron(self)
Definition: plotting.py:2751
def adjustMarginRight(self, adjust)
Definition: plotting.py:1370
def getPlotFolders(self)
Definition: plotting.py:2981
def __init__(self, subfolder, translated)
Definition: plotting.py:2679
def create(self, tdirectory)
Definition: plotting.py:916
def setXTitleOffset(self, offset)
Definition: plotting.py:1388
def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None, ignoreMissingBins=False, minExistingBins=None, originalOrder=False, reorder=None)
Definition: plotting.py:946
def __init__(self, name, mapping, normalizeTo=None)
Definition: plotting.py:1073
def _normalize(self)
Definition: plotting.py:1981
def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True)
Definition: plotting.py:2467
def translateSubFolder(self, dqmSubFolderName)
Definition: plotting.py:2649
def __str__(self)
Definition: plotting.py:1135
def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory)
Definition: plotting.py:2401
def __init__(self, folders)
Definition: plotting.py:2825
def setTitle(self, title)
Definition: plotting.py:1279
def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators)
Definition: plotting.py:2694
def setXTitle(self, title)
Definition: plotting.py:1482
def setGridy(self, grid)
Definition: plotting.py:1357
#define begin
Definition: vmac.h:30
def setYTitleSize(self, size)
Definition: plotting.py:1400
def setYTitle(self, title)
Definition: plotting.py:1494
def _th1RemoveEmptyBins(histos, xbinlabels)
Definition: plotting.py:603
def onlyForBHadron(self)
Definition: plotting.py:2581
def __init__(self)
Definition: plotting.py:2955
def isEmpty(self)
Definition: plotting.py:1849
def _getXmin(obj, limitToNonZeroContent=False)
Definition: plotting.py:337
def onlyForElectron(self)
Definition: plotting.py:2745
def append(self, args, kwargs)
Definition: plotting.py:2519
def create(self, tdirNEvents, requireAllHistograms=False)
Definition: plotting.py:1890
def getPlotGroup(self, name)
Definition: plotting.py:2605
def getPage(self)
Definition: plotting.py:2587
def _drawFrame(pad, bounds, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None, suffix="")
Definition: plotting.py:1183
def appendTableCreator(self, tc)
Definition: plotting.py:2864
def isTGraph2D(self)
Definition: plotting.py:1853
def _modifyPadForRatio(pad, ratioFactor)
Definition: plotting.py:116
def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False)
Definition: plotting.py:2784
def create(self, tdirectoryNEvents, requireAllHistograms=False)
Definition: plotting.py:2522
def _createCanvas(name, width, height)
Definition: plotting.py:106
def setYTitleOffset(self, offset)
Definition: plotting.py:1404
def redrawAxis(self)
Definition: plotting.py:1303
auto wrap(F iFunc) -> decltype(iFunc())
def setLogx(self, log)
Definition: plotting.py:1255
def onlyForPileup(self)
Definition: plotting.py:2742
def clone(self, kwargs)
Definition: plotting.py:1838
def __init__(self, name, plot, ncols=2, onlyForPileup=False)
Definition: plotting.py:2514
def getPlotFolderNames(self)
Definition: plotting.py:2978
def getNumberOfEventsHistogram(self)
Definition: plotting.py:2593
def _mergeBinLabels(labelsAll)
Definition: plotting.py:709
def getPurpose(self)
Definition: plotting.py:2584
def __init__(self, plotGroups, kwargs)
Definition: plotting.py:2537
def setLogx(self, log)
Definition: plotting.py:1459
def draw(self, args, kwargs)
Definition: plotting.py:2818
def _th1ToOrderedDict(th1, renameBin=None)
Definition: plotting.py:97
def isEmpty(self)
Definition: plotting.py:1690
def _th2RemoveEmptyBins(histos, xbinlabels, ybinlabels)
Definition: plotting.py:643
def getSelectionNameIterator(self, dqmSubFolder)
Definition: plotting.py:2775
def getName(self)
Definition: plotting.py:1864
def create(self, tdirectory)
Definition: plotting.py:1091
def setProperties(self, kwargs)
Definition: plotting.py:1832
def _mergeBinLabelsX(histos)
Definition: plotting.py:703
def _createOne(self, name, index, tdir, nevents)
Definition: plotting.py:1876
def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory="")
Definition: plotting.py:2631
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run
def setTitle(self, title)
Definition: plotting.py:1479
def _th1IncludeOnlyBins(histos, xbinlabels)
Definition: plotting.py:740
def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[])
Definition: plotting.py:2839
def _getObject(tdirectory, name)
Definition: plotting.py:46
def getSection(self)
Definition: plotting.py:2590