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 == 'Default':
2162  frame.setXTitle( histos[0].GetXaxis().GetTitle() )
2163  elif self._xtitle is not None:
2164  frame.setXTitle(self._xtitle)
2165  if self._xtitlesize is not None:
2166  frame.setXTitleSize(self._xtitlesize)
2167  if self._xtitleoffset is not None:
2168  frame.setXTitleOffset(self._xtitleoffset)
2169  if self._xlabelsize is not None:
2170  frame.setXLabelSize(self._xlabelsize)
2171  if self._ytitle == 'Default':
2172  frame.setYTitle( histos[0].GetYaxis().GetTitle() )
2173  elif self._ytitle is not None:
2174  frame.setYTitle(self._ytitle)
2175  if self._ytitlesize is not None:
2176  frame.setYTitleSize(self._ytitlesize)
2177  if self._ytitleoffset is not None:
2178  frame.setYTitleOffset(self._ytitleoffset)
2179  if self._ztitle is not None:
2180  frame.setZTitle(self._ztitle)
2181  if self._ztitleoffset is not None:
2182  frame.setZTitleOffset(self._ztitleoffset)
2183  if self._adjustMarginLeft is not None:
2184  frame.adjustMarginLeft(self._adjustMarginLeft)
2185  if self._adjustMarginRight is not None:
2186  frame.adjustMarginRight(self._adjustMarginRight)
2187  elif "z" in opt:
2188  frame.adjustMarginLeft(0.03)
2189  frame.adjustMarginRight(0.08)
2190 
2191  # Draw histograms
2192  if ratio:
2193  frame._pad.cd()
2194 
2195  for i, h in enumerate(histos):
2196  o = opt
2197  if isTGraph2D and i == 0:
2198  o = o.replace("sames", "")
2199  h.Draw(o)
2200 
2201  for addl in self._mainAdditional:
2202  addl.Draw("same")
2203 
2204  # Draw ratios
2205  if ratio and len(histos) > 0:
2206  frame._padRatio.cd()
2207  firstRatio = self._ratios[0].getRatio()
2208  if self._ratioUncertainty and firstRatio is not None:
2209  firstRatio.SetFillStyle(1001)
2210  firstRatio.SetFillColor(ROOT.kGray)
2211  firstRatio.SetLineColor(ROOT.kGray)
2212  firstRatio.SetMarkerColor(ROOT.kGray)
2213  firstRatio.SetMarkerSize(0)
2214  self._ratios[0].draw("E2")
2215  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
2216  for r in self._ratios[1:]:
2217  r.draw()
2218 
2219  for addl in self._ratioAdditional:
2220  addl.Draw("same")
2221 
2222  frame.redrawAxis()
2223  self._frame = frame # keep the frame in memory for sure
2224 
2225  def addToLegend(self, legend, legendLabels, denomUncertainty):
2226  """Add histograms to a legend.
2227 
2228  Arguments:
2229  legend -- TLegend
2230  legendLabels -- List of strings for the legend labels
2231  """
2232  first = denomUncertainty
2233  for h, label in zip(self._histograms, legendLabels):
2234  if h is None:
2235  first = False
2236  continue
2237  if first:
2238  self._forLegend = h.Clone()
2239  self._forLegend.SetFillStyle(1001)
2240  self._forLegend.SetFillColor(ROOT.kGray)
2241  entry = legend.AddEntry(self._forLegend, label, "lpf")
2242  first = False
2243  else:
2244  legend.AddEntry(h, label, "LP")
2245 
2247  """Group of plots, results a TCanvas"""
2248  def __init__(self, name, plots, **kwargs):
2249  """Constructor.
2250 
2251  Arguments:
2252  name -- String for name of the TCanvas, used also as the basename of the picture files
2253  plots -- List of Plot objects
2254 
2255  Keyword arguments:
2256  ncols -- Number of columns (default 2)
2257  legendDx -- Float for moving TLegend in x direction (default None)
2258  legendDy -- Float for moving TLegend in y direction (default None)
2259  legendDw -- Float for changing TLegend width (default None)
2260  legendDh -- Float for changing TLegend height (default None)
2261  legend -- Bool for disabling legend (default True for legend being enabled)
2262  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
2263  onlyForPileup -- Plots this group only for pileup samples
2264  """
2265  super(PlotGroup, self).__init__()
2266 
2267  self._name = name
2268  self._plots = plots
2269 
2270  def _set(attr, default):
2271  setattr(self, "_"+attr, kwargs.get(attr, default))
2272 
2273  _set("ncols", 2)
2274 
2275  _set("legendDx", None)
2276  _set("legendDy", None)
2277  _set("legendDw", None)
2278  _set("legendDh", None)
2279  _set("legend", True)
2280 
2281  _set("overrideLegendLabels", None)
2282 
2283  _set("onlyForPileup", False)
2284 
2285  self._ratioFactor = 1.25
2286 
2287  def setProperties(self, **kwargs):
2288  for name, value in six.iteritems(kwargs):
2289  if not hasattr(self, "_"+name):
2290  raise Exception("No attribute '%s'" % name)
2291  setattr(self, "_"+name, value)
2292 
2293  def getName(self):
2294  return self._name
2295 
2296  def getPlots(self):
2297  return self._plots
2298 
2299  def remove(self, name):
2300  for i, plot in enumerate(self._plots):
2301  if plot.getName() == name:
2302  del self._plots[i]
2303  return
2304  raise Exception("Did not find Plot '%s' from PlotGroup '%s'" % (name, self._name))
2305 
2306  def clear(self):
2307  self._plots = []
2308 
2309  def append(self, plot):
2310  self._plots.append(plot)
2311 
2312  def getPlot(self, name):
2313  for plot in self._plots:
2314  if plot.getName() == name:
2315  return plot
2316  raise Exception("No Plot named '%s'" % name)
2317 
2318  def onlyForPileup(self):
2319  """Return True if the PlotGroup is intended only for pileup samples"""
2320  return self._onlyForPileup
2321 
2322  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2323  """Create histograms from a list of TDirectories.
2324 
2325  Arguments:
2326  tdirectoryNEvents -- List of (TDirectory, nevents) pairs
2327  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2328  """
2329  for plot in self._plots:
2330  plot.create(tdirectoryNEvents, requireAllHistograms)
2331 
2332  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2333  """Draw the histograms using values for a given algorithm.
2334 
2335  Arguments:
2336  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
2337  prefix -- Optional string for file name prefix (default None)
2338  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2339  saveFormat -- String specifying the plot format (default '.pdf')
2340  ratio -- Add ratio to the plot (default True)
2341  directory -- Directory where to save the file (default "")
2342  """
2343 
2344  if self._overrideLegendLabels is not None:
2345  legendLabels = self._overrideLegendLabels
2346 
2347  # Do not draw the group if it would be empty
2348  onlyEmptyPlots = True
2349  for plot in self._plots:
2350  if not plot.isEmpty():
2351  onlyEmptyPlots = False
2352  break
2353  if onlyEmptyPlots:
2354  return []
2355 
2356  if separate:
2357  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio, directory)
2358 
2359  cwidth = 500*self._ncols
2360  nrows = int((len(self._plots)+self._ncols-1)/self._ncols) # this should work also for odd n
2361  cheight = 500 * nrows
2362 
2363  if ratio:
2364  cheight = int(cheight*self._ratioFactor)
2365 
2366  canvas = _createCanvas(self._name, cwidth, cheight)
2367 
2368  canvas.Divide(self._ncols, nrows)
2369  if ratio:
2370  for i, plot in enumerate(self._plots):
2371  pad = canvas.cd(i+1)
2372  self._modifyPadForRatio(pad)
2373 
2374  # Draw plots to canvas
2375  for i, plot in enumerate(self._plots):
2376  pad = canvas.cd(i+1)
2377  if not plot.isEmpty():
2378  plot.draw(pad, ratio, self._ratioFactor, nrows)
2379 
2380  # Setup legend
2381  canvas.cd()
2382  if len(self._plots) <= 4:
2383  lx1 = 0.2
2384  lx2 = 0.9
2385  ly1 = 0.48
2386  ly2 = 0.53
2387  else:
2388  lx1 = 0.1
2389  lx2 = 0.9
2390  ly1 = 0.64
2391  ly2 = 0.67
2392  if self._legendDx is not None:
2393  lx1 += self._legendDx
2394  lx2 += self._legendDx
2395  if self._legendDy is not None:
2396  ly1 += self._legendDy
2397  ly2 += self._legendDy
2398  if self._legendDw is not None:
2399  lx2 += self._legendDw
2400  if self._legendDh is not None:
2401  ly1 -= self._legendDh
2402  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
2403  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
2404  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2405  denomUncertainty=(ratio and denomUnc))
2406 
2407  return self._save(canvas, saveFormat, prefix=prefix, directory=directory)
2408 
2409  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory):
2410  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2411  width = 500
2412  height = 500
2413 
2414  canvas = _createCanvas(self._name+"Single", width, height)
2415  canvasRatio = _createCanvas(self._name+"SingleRatio", width, int(height*self._ratioFactor))
2416 
2417  # from TDRStyle
2418  for c in [canvas, canvasRatio]:
2419  c.SetTopMargin(0.05)
2420  c.SetBottomMargin(0.13)
2421  c.SetLeftMargin(0.16)
2422  c.SetRightMargin(0.05)
2423 
2424  lx1def = 0.6
2425  lx2def = 0.95
2426  ly1def = 0.85
2427  ly2def = 0.95
2428 
2429  ret = []
2430 
2431  for plot in self._plots:
2432  if plot.isEmpty():
2433  continue
2434 
2435  ratioForThisPlot = plot.isRatio(ratio)
2436  c = canvas
2437  if ratioForThisPlot:
2438  c = canvasRatio
2439  c.cd()
2440  self._modifyPadForRatio(c)
2441 
2442  # Draw plot to canvas
2443  c.cd()
2444  plot.draw(c, ratioForThisPlot, self._ratioFactor, 1)
2445 
2446  if plot._legend:
2447  # Setup legend
2448  lx1 = lx1def
2449  lx2 = lx2def
2450  ly1 = ly1def
2451  ly2 = ly2def
2452 
2453  if plot._legendDx is not None:
2454  lx1 += plot._legendDx
2455  lx2 += plot._legendDx
2456  if plot._legendDy is not None:
2457  ly1 += plot._legendDy
2458  ly2 += plot._legendDy
2459  if plot._legendDw is not None:
2460  lx2 += plot._legendDw
2461  if plot._legendDh is not None:
2462  ly1 -= plot._legendDh
2463 
2464  c.cd()
2465  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2466  denomUncertainty=(ratioForThisPlot and plot.drawRatioUncertainty))
2467 
2468  ret.extend(self._save(c, saveFormat, prefix=prefix, postfix="_"+plot.getName(), single=True, directory=directory))
2469  return ret
2470 
2471  def _modifyPadForRatio(self, pad):
2472  """Internal method to set divide a pad to two for ratio plots"""
2473  _modifyPadForRatio(pad, self._ratioFactor)
2474 
2475  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2476  if not self._legend:
2477  return None
2478 
2479  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2480  l.SetTextSize(textSize)
2481  l.SetLineColor(1)
2482  l.SetLineWidth(1)
2483  l.SetLineStyle(1)
2484  l.SetFillColor(0)
2485  l.SetMargin(0.07)
2486 
2487  plot.addToLegend(l, legendLabels, denomUncertainty)
2488  l.Draw()
2489  return l
2490 
2491  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory=""):
2492  # Save the canvas to file and clear
2493  name = self._name
2494  if prefix is not None:
2495  name = prefix+name
2496  if postfix is not None:
2497  name = name+postfix
2498  name = os.path.join(directory, name)
2499 
2500  if not verbose: # silence saved file printout
2501  backup = ROOT.gErrorIgnoreLevel
2502  ROOT.gErrorIgnoreLevel = ROOT.kWarning
2503  canvas.SaveAs(name+saveFormat)
2504  if not verbose:
2505  ROOT.gErrorIgnoreLevel = backup
2506 
2507  if single:
2508  canvas.Clear()
2509  canvas.SetLogx(False)
2510  canvas.SetLogy(False)
2511  else:
2512  canvas.Clear("D") # keep subpads
2513 
2514  return [name+saveFormat]
2515 
2517  """Resembles DQM GUI's "On side" layout.
2518 
2519  Like PlotGroup, but has only a description of a single plot. The
2520  plot is drawn separately for each file. Useful for 2D histograms."""
2521 
2522  def __init__(self, name, plot, ncols=2, onlyForPileup=False):
2523  super(PlotOnSideGroup, self).__init__(name, [], ncols=ncols, legend=False, onlyForPileup=onlyForPileup)
2524  self._plot = plot
2525  self._plot.setProperties(ratio=False)
2526 
2527  def append(self, *args, **kwargs):
2528  raise Exception("PlotOnSideGroup.append() is not implemented")
2529 
2530  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2531  self._plots = []
2532  for element in tdirectoryNEvents:
2533  pl = self._plot.clone()
2534  pl.create([element], requireAllHistograms)
2535  self._plots.append(pl)
2536 
2537  def draw(self, *args, **kwargs):
2538  kargs = copy.copy(kwargs)
2539  kargs["ratio"] = False
2540  kargs["separate"] = False
2541  return super(PlotOnSideGroup, self).draw(*args, **kargs)
2542 
2544 
2545  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2546  def __init__(self, *plotGroups, **kwargs):
2547  """Constructor.
2548 
2549  Arguments:
2550  plotGroups -- List of PlotGroup objects
2551 
2552  Keyword arguments
2553  loopSubFolders -- Should the subfolders be looped over? (default: True)
2554  onlyForPileup -- Plots this folder only for pileup samples
2555  onlyForElectron -- Plots this folder only for electron samples
2556  onlyForConversion -- Plots this folder only for conversion samples
2557  onlyForBHadron -- Plots this folder only for B-hadron samples
2558  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2559  page -- Optional string for the page in HTML generatin
2560  section -- Optional string for the section within a page in HTML generation
2561  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".
2562  """
2563  self._plotGroups = list(plotGroups)
2564  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
2565  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
2566  self._onlyForElectron = kwargs.pop("onlyForElectron", False)
2567  self._onlyForConversion = kwargs.pop("onlyForConversion", False)
2568  self._onlyForBHadron = kwargs.pop("onlyForBHadron", False)
2569  self._purpose = kwargs.pop("purpose", None)
2570  self._page = kwargs.pop("page", None)
2571  self._section = kwargs.pop("section", None)
2572  self._numberOfEventsHistogram = kwargs.pop("numberOfEventsHistogram", None)
2573  if len(kwargs) > 0:
2574  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
2575 
2576  def loopSubFolders(self):
2577  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2578  return self._loopSubFolders
2579 
2580  def onlyForPileup(self):
2581  """Return True if the folder is intended only for pileup samples"""
2582  return self._onlyForPileup
2583 
2584  def onlyForElectron(self):
2585  return self._onlyForElectron
2586 
2588  return self._onlyForConversion
2589 
2590  def onlyForBHadron(self):
2591  return self._onlyForBHadron
2592 
2593  def getPurpose(self):
2594  return self._purpose
2595 
2596  def getPage(self):
2597  return self._page
2598 
2599  def getSection(self):
2600  return self._section
2601 
2603  return self._numberOfEventsHistogram
2604 
2605  def append(self, plotGroup):
2606  self._plotGroups.append(plotGroup)
2607 
2608  def set(self, plotGroups):
2609  self._plotGroups = plotGroups
2610 
2611  def getPlotGroups(self):
2612  return self._plotGroups
2613 
2614  def getPlotGroup(self, name):
2615  for pg in self._plotGroups:
2616  if pg.getName() == name:
2617  return pg
2618  raise Exception("No PlotGroup named '%s'" % name)
2619 
2620  def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2621  """Create histograms from a list of TFiles.
2622 
2623  Arguments:
2624  dirsNEvents -- List of (TDirectory, nevents) pairs
2625  labels -- List of strings for legend labels corresponding the files
2626  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2627  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2628  """
2629 
2630  if len(dirsNEvents) != len(labels):
2631  raise Exception("len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2632 
2633  self._labels = labels
2634 
2635  for pg in self._plotGroups:
2636  if pg.onlyForPileup() and not isPileupSample:
2637  continue
2638  pg.create(dirsNEvents, requireAllHistograms)
2639 
2640  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2641  """Draw and save all plots using settings of a given algorithm.
2642 
2643  Arguments:
2644  prefix -- Optional string for file name prefix (default None)
2645  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2646  saveFormat -- String specifying the plot format (default '.pdf')
2647  ratio -- Add ratio to the plot (default True)
2648  directory -- Directory where to save the file (default "")
2649  """
2650  ret = []
2651 
2652  for pg in self._plotGroups:
2653  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio, directory=directory))
2654  return ret
2655 
2656 
2657  # These are to be overridden by derived classes for customisation
2658  def translateSubFolder(self, dqmSubFolderName):
2659  """Method called to (possibly) translate a subfolder name to more 'readable' form
2660 
2661  The implementation in this (base) class just returns the
2662  argument. The idea is that a deriving class might want to do
2663  something more complex (like trackingPlots.TrackingPlotFolder
2664  does)
2665  """
2666  return dqmSubFolderName
2667 
2668  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
2669  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2670  ret = ""
2671  if plotFolderName != "":
2672  ret += "_"+plotFolderName
2673  if translatedDqmSubFolder is not None:
2674  ret += "_"+translatedDqmSubFolder
2675  yield ret
2676 
2677  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
2678  """Return True if this subfolder should be processed
2679 
2680  Arguments:
2681  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2682  translatedDqmSubFolder -- Return value of translateSubFolder
2683  """
2684  return translatedDqmSubFolder in limitOnlyTo
2685 
2687  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2688  def __init__(self, subfolder, translated):
2689  self.subfolder = subfolder
2690  self.translated = translated
2691 
2692  def equalTo(self, other):
2693  """Equality is defined by the 'translated' name"""
2694  return self.translated == other.translated
2695 
2697  """Plotter for one DQM folder.
2698 
2699  This class is supposed to be instantiated by the Plotter class (or
2700  PlotterItem, to be more specific), and not used directly by the
2701  user.
2702  """
2703  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2704  """
2705  Constructor
2706 
2707  Arguments:
2708  name -- Name of the folder (is used in the output directory naming)
2709  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2710  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2711  plotFolder -- PlotFolder object
2712  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.
2713  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.
2714  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2715  """
2716  self._name = name
2717  self._possibleDqmFolders = possibleDqmFolders
2718  self._plotFolder = plotFolder
2719  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
2720  if dqmSubFolders is None:
2721  self._dqmSubFolders = None
2722  else:
2723  # Match the subfolders between files in case the lists differ
2724  # equality is by the 'translated' name
2725  subfolders = {}
2726  for sf_list in dqmSubFolders:
2727  for sf in sf_list:
2728  sf_translated = self._plotFolder.translateSubFolder(sf)
2729  if sf_translated is not None and not sf_translated in subfolders:
2730  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
2731 
2732  self._dqmSubFolders = subfolders.values()
2733  self._dqmSubFolders.sort(key=lambda sf: sf.subfolder)
2734 
2735  self._fallbackNames = fallbackNames
2736  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2737  self._tableCreators = tableCreators
2738 
2739  def getName(self):
2740  return self._name
2741 
2742  def getPurpose(self):
2743  return self._plotFolder.getPurpose()
2744 
2745  def getPage(self):
2746  return self._plotFolder.getPage()
2747 
2748  def getSection(self):
2749  return self._plotFolder.getSection()
2750 
2751  def onlyForPileup(self):
2752  return self._plotFolder.onlyForPileup()
2753 
2754  def onlyForElectron(self):
2755  return self._plotFolder.onlyForElectron()
2756 
2758  return self._plotFolder.onlyForConversion()
2759 
2760  def onlyForBHadron(self):
2761  return self._plotFolder.onlyForBHadron()
2762 
2764  return self._possibleDqmFolders
2765 
2766  def getDQMSubFolders(self, limitOnlyTo=None):
2767  """Get list of subfolders, possibly limiting to some of them.
2768 
2769  Keyword arguments:
2770  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2771  """
2772 
2773  if self._dqmSubFolders is None:
2774  return [None]
2775 
2776  if limitOnlyTo is None:
2777  return self._dqmSubFolders
2778 
2779  return [s for s in self._dqmSubFolders if self._plotFolder.limitSubFolder(limitOnlyTo, s.translated)]
2780 
2781  def getTableCreators(self):
2782  return self._tableCreators
2783 
2784  def getSelectionNameIterator(self, dqmSubFolder):
2785  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2786  for name in [self._name]+self._fallbackNames:
2787  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2788  yield selname
2789 
2790  def getSelectionName(self, dqmSubFolder):
2791  return next(self.getSelectionNameIterator(dqmSubFolder))
2792 
2793  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2794  """Create histograms from a list of TFiles.
2795  Arguments:
2796  files -- List of TFiles
2797  labels -- List of strings for legend labels corresponding the files
2798  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2799  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2800  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2801  """
2802 
2803  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2804  neventsHisto = self._plotFolder.getNumberOfEventsHistogram()
2805  dirsNEvents = []
2806 
2807  for tfile in files:
2808  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2809  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2811  for fallbackFunc in self._fallbackDqmSubFolders:
2812  fallback = fallbackFunc(subfolder)
2813  if fallback is not None:
2814  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2815  if ret is not GetDirectoryCode.SubDirNotExist:
2816  break
2817  d = GetDirectoryCode.codesToNone(ret)
2818  nev = None
2819  if neventsHisto is not None and tfile is not None:
2820  hnev = _getObject(tfile, neventsHisto)
2821  if hnev is not None:
2822  nev = hnev.GetEntries()
2823  dirsNEvents.append( (d, nev) )
2824 
2825  self._plotFolder.create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2826 
2827  def draw(self, *args, **kwargs):
2828  """Draw and save all plots using settings of a given algorithm."""
2829  return self._plotFolder.draw(*args, **kwargs)
2830 
2831 
2833  """Instance of plotter that knows the directory content, holds many folders."""
2834  def __init__(self, folders):
2835  self._plotterFolders = [f for f in folders if f is not None]
2836 
2837  def iterFolders(self, limitSubFoldersOnlyTo=None):
2838  for plotterFolder in self._plotterFolders:
2839  limitOnlyTo = None
2840  if limitSubFoldersOnlyTo is not None:
2841  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2842 
2843  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2844  yield plotterFolder, dqmSubFolder
2845 
2846 # Helper for Plotter
2848  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2849  """ Constructor
2850 
2851  Arguments:
2852  name -- Name of the folder (is used in the output directory naming)
2853  possibleDirs -- List of strings for possible directories of histograms in TFiles
2854  plotFolder -- PlotFolder object
2855 
2856  Keyword arguments
2857  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.
2858  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.
2859  """
2860  self._name = name
2861  self._possibleDirs = possibleDirs
2862  self._plotFolder = plotFolder
2863  self._fallbackNames = fallbackNames
2864  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2865  self._tableCreators = []
2866 
2867  def getName(self):
2868  return self._name
2869 
2870  def getPlotFolder(self):
2871  return self._plotFolder
2872 
2873  def appendTableCreator(self, tc):
2874  self._tableCreators.append(tc)
2875 
2876  def readDirs(self, files):
2877  """Read available subfolders from the files
2878 
2879  Arguments:
2880  files -- List of strings for paths to files, or list of TFiles
2881 
2882  For each file, loop over 'possibleDirs', and read the
2883  subfolders of first one that exists.
2884 
2885  Returns a PlotterFolder if at least one file for which one of
2886  'possibleDirs' exists. Otherwise, return None to signal that
2887  there is nothing available for this PlotFolder.
2888  """
2889  subFolders = None
2890  if self._plotFolder.loopSubFolders():
2891  subFolders = []
2892  possibleDirFound = False
2893  for fname in files:
2894  if fname is None:
2895  continue
2896 
2897  isOpenFile = isinstance(fname, ROOT.TFile)
2898  if isOpenFile:
2899  tfile = fname
2900  else:
2901  tfile = ROOT.TFile.Open(fname)
2902  for pd in self._possibleDirs:
2903  d = tfile.Get(pd)
2904  if d:
2905  possibleDirFound = True
2906  if subFolders is not None:
2907  subf = []
2908  for key in d.GetListOfKeys():
2909  if isinstance(key.ReadObj(), ROOT.TDirectory):
2910  subf.append(key.GetName())
2911  subFolders.append(subf)
2912  break
2913 
2914  if not isOpenFile:
2915  tfile.Close()
2916 
2917  if not possibleDirFound:
2918  return None
2919 
2920  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2921 
2923  def __init__(self, possibleDirs, tableCreator):
2924  self._possibleDirs = possibleDirs
2925  self._tableCreator = tableCreator
2926 
2927  def create(self, openFiles, legendLabels, dqmSubFolder):
2928  if isinstance(dqmSubFolder, list):
2929  if len(dqmSubFolder) != len(openFiles):
2930  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2931  else:
2932  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2933  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2934 
2935  tbl = []
2936  for f, sf in zip(openFiles, dqmSubFolder):
2937  data = None
2938  tdir = _getDirectory(f, self._possibleDirs, sf)
2939  if tdir is not None:
2940  data = self._tableCreator.create(tdir)
2941  tbl.append(data)
2942 
2943  # Check if we have any content
2944  allNones = True
2945  colLen = 0
2946  for col in tbl:
2947  if col is not None:
2948  allNones = False
2949  colLen = len(col)
2950  break
2951  if allNones:
2952  return None
2953 
2954  # Replace all None columns with lists of column length
2955  for i in range(len(tbl)):
2956  if tbl[i] is None:
2957  tbl[i] = [None]*colLen
2958 
2959  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2960  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2961 
2962 class Plotter:
2963  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2964  def __init__(self):
2965  self._plots = []
2966  _setStyle()
2967  ROOT.TH1.AddDirectory(False)
2968 
2969  def append(self, *args, **kwargs):
2970  """Append a plot folder to the plotter.
2971 
2972  All arguments are forwarded to the constructor of PlotterItem.
2973  """
2974  self._plots.append(PlotterItem(*args, **kwargs))
2975 
2976  def appendTable(self, attachToFolder, *args, **kwargs):
2977  for plotterItem in self._plots:
2978  if plotterItem.getName() == attachToFolder:
2979  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2980  return
2981  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2982 
2983  def clear(self):
2984  """Remove all plot folders and tables"""
2985  self._plots = []
2986 
2988  return [item.getName() for item in self._plots]
2989 
2990  def getPlotFolders(self):
2991  return [item.getPlotFolder() for item in self._plots]
2992 
2993  def getPlotFolder(self, name):
2994  for item in self._plots:
2995  if item.getName() == name:
2996  return item.getPlotFolder()
2997  raise Exception("No PlotFolder named '%s'" % name)
2998 
2999  def readDirs(self, *files):
3000  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
3001  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def remove(self, name)
Definition: plotting.py:2299
def create(self, tdirectory)
Definition: plotting.py:871
def iterSelectionName(self, plotFolderName, translatedDqmSubFolder)
Definition: plotting.py:2668
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:2580
def __init__(self)
Definition: plotting.py:1676
def append(self, plotGroup)
Definition: plotting.py:2605
def getName(self)
Definition: plotting.py:2293
def _setStyle()
Definition: plotting.py:22
def _getYmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:388
def loopSubFolders(self)
Definition: plotting.py:2576
def _copyStyle(src, dst)
Definition: plotting.py:1662
def __init__(self, name, plots, kwargs)
Definition: plotting.py:2248
def __init__(self, name, nameA, nameB, title="")
Definition: plotting.py:762
def getPossibleDQMFolders(self)
Definition: plotting.py:2763
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:2608
def setXTitle(self, title)
Definition: plotting.py:1387
def setLogx(self, log)
Definition: plotting.py:1351
def readDirs(self, files)
Definition: plotting.py:2999
def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory="")
Definition: plotting.py:2332
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:2837
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:2471
def Draw(self, options="")
Definition: plotting.py:1646
def equalTo(self, other)
Definition: plotting.py:2692
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:2923
def getTableCreators(self)
Definition: plotting.py:2781
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:2306
def setXLabelSize(self, size)
Definition: plotting.py:1296
def _getYminIgnoreOutlier(th1)
Definition: plotting.py:406
def append(self, args, kwargs)
Definition: plotting.py:2969
def getDQMSubFolders(self, limitOnlyTo=None)
Definition: plotting.py:2766
def _getXmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:357
def append(self, plot)
Definition: plotting.py:2309
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:2322
bool equal(const T &first, const T &second)
Definition: Equal.h:32
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:2611
void divide(dqm::legacy::MonitorElement *eff, const dqm::legacy::MonitorElement *numerator, const dqm::legacy::MonitorElement *denominator)
Function to fill an efficiency histograms with binomial errors.
Definition: Histograms.h:20
def create(self, openFiles, legendLabels, dqmSubFolder)
Definition: plotting.py:2927
def _calculateRatios(histos, ratioUncertainty=False)
Definition: plotting.py:149
def appendTable(self, attachToFolder, args, kwargs)
Definition: plotting.py:2976
def setYTitleOffset(self, offset)
Definition: plotting.py:1305
def setYTitle(self, title)
Definition: plotting.py:1399
def clear(self)
Definition: plotting.py:2983
def create(self, tdirectory)
Definition: plotting.py:827
def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory="")
Definition: plotting.py:2491
def setYTitleSize(self, size)
Definition: plotting.py:1302
def onlyForElectron(self)
Definition: plotting.py:2584
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:2790
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:2296
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:2876
def _mergeBinLabelsY(histos)
Definition: plotting.py:709
def getPlotFolder(self)
Definition: plotting.py:2870
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:2318
#define end
Definition: vmac.h:39
def getPlot(self, name)
Definition: plotting.py:2312
def setProperties(self, kwargs)
Definition: plotting.py:2287
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:2225
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:2537
def create(self, tdirectory)
Definition: plotting.py:987
def onlyForConversion(self)
Definition: plotting.py:2587
def _getYminMaxAroundMedian(obj, coverage, coverageRange=None)
Definition: plotting.py:422
def getPlotFolder(self, name)
Definition: plotting.py:2993
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:2757
def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder)
Definition: plotting.py:2677
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:17
def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False)
Definition: plotting.py:2620
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:2760
def adjustMarginRight(self, adjust)
Definition: plotting.py:1375
def getPlotFolders(self)
Definition: plotting.py:2990
def __init__(self, subfolder, translated)
Definition: plotting.py:2688
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:2475
def translateSubFolder(self, dqmSubFolderName)
Definition: plotting.py:2658
def __str__(self)
Definition: plotting.py:1138
def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory)
Definition: plotting.py:2409
def __init__(self, folders)
Definition: plotting.py:2834
def setTitle(self, title)
Definition: plotting.py:1284
def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators)
Definition: plotting.py:2703
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:2590
def __init__(self)
Definition: plotting.py:2964
def isEmpty(self)
Definition: plotting.py:1847
def _getXmin(obj, limitToNonZeroContent=False)
Definition: plotting.py:341
def onlyForElectron(self)
Definition: plotting.py:2754
def append(self, args, kwargs)
Definition: plotting.py:2527
def create(self, tdirNEvents, requireAllHistograms=False)
Definition: plotting.py:1888
def getPlotGroup(self, name)
Definition: plotting.py:2614
def getPage(self)
Definition: plotting.py:2596
def appendTableCreator(self, tc)
Definition: plotting.py:2873
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:2793
def create(self, tdirectoryNEvents, requireAllHistograms=False)
Definition: plotting.py:2530
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:2751
def clone(self, kwargs)
Definition: plotting.py:1836
def __init__(self, name, plot, ncols=2, onlyForPileup=False)
Definition: plotting.py:2522
def getPlotFolderNames(self)
Definition: plotting.py:2987
def getNumberOfEventsHistogram(self)
Definition: plotting.py:2602
def _mergeBinLabels(labelsAll)
Definition: plotting.py:712
def getPurpose(self)
Definition: plotting.py:2593
def __init__(self, plotGroups, kwargs)
Definition: plotting.py:2546
#define str(s)
def setLogx(self, log)
Definition: plotting.py:1459
def draw(self, args, kwargs)
Definition: plotting.py:2827
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:2784
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:2640
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:2848
def _getObject(tdirectory, name)
Definition: plotting.py:50
def getSection(self)
Definition: plotting.py:2599