CMS 3D CMS Logo

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