CMS 3D CMS Logo

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