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