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 = 1
1247  if nrows == 2:
1248  yoffsetFactor *= 2
1249  xoffsetFactor *= 2
1250  elif nrows >= 5:
1251  yoffsetFactor *= 1.5
1252  xoffsetFactor *= 1.5
1253  elif nrows >= 3:
1254  yoffsetFactor *= 4
1255  xoffsetFactor *= 3
1256 
1257  self._frame.GetYaxis().SetTitleOffset(self._frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
1258  self._frame.GetXaxis().SetTitleOffset(self._frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
1259 
1260 
1261  def setLogx(self, log):
1262  self._pad.SetLogx(log)
1263 
1264  def setLogy(self, log):
1265  self._pad.SetLogy(log)
1266 
1267  def setGridx(self, grid):
1268  self._pad.SetGridx(grid)
1269 
1270  def setGridy(self, grid):
1271  self._pad.SetGridy(grid)
1272 
1273  def adjustMarginLeft(self, adjust):
1274  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1275  # Need to redraw frame after adjusting the margin
1276  self._pad.cd()
1277  self._frame.Draw("")
1278 
1279  def adjustMarginRight(self, adjust):
1280  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1281  # Need to redraw frame after adjusting the margin
1282  self._pad.cd()
1283  self._frame.Draw("")
1284 
1285  def setTitle(self, title):
1286  self._frame.SetTitle(title)
1287 
1288  def setXTitle(self, title):
1289  self._frame.GetXaxis().SetTitle(title)
1290 
1291  def setXTitleSize(self, size):
1292  self._frame.GetXaxis().SetTitleSize(size)
1293 
1294  def setXTitleOffset(self, offset):
1295  self._frame.GetXaxis().SetTitleOffset(offset)
1296 
1297  def setXLabelSize(self, size):
1298  self._frame.GetXaxis().SetLabelSize(size)
1299 
1300  def setYTitle(self, title):
1301  self._frame.GetYaxis().SetTitle(title)
1302 
1303  def setYTitleSize(self, size):
1304  self._frame.GetYaxis().SetTitleSize(size)
1305 
1306  def setYTitleOffset(self, offset):
1307  self._frame.GetYaxis().SetTitleOffset(offset)
1308 
1309  def redrawAxis(self):
1310  self._pad.RedrawAxis()
1311 
1313  """Class for creating and managing a frame for a ratio plot with two subpads"""
1314  def __init__(self, pad, bounds, zmax, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ratioYTitle=_ratioYTitle):
1315  self._parentPad = pad
1316  self._pad = pad.cd(1)
1317  if xbinlabels is not None:
1318  self._frame = _drawFrame(self._pad, bounds, zmax, [""]*len(xbinlabels))
1319  else:
1320  self._frame = _drawFrame(self._pad, bounds, zmax)
1321  self._padRatio = pad.cd(2)
1322  self._frameRatio = _drawFrame(self._padRatio, ratioBounds, zmax, xbinlabels, xbinlabelsize, xbinlabeloption)
1323 
1324  self._frame.GetXaxis().SetLabelSize(0)
1325  self._frame.GetXaxis().SetTitleSize(0)
1326 
1327  yoffsetFactor = ratioFactor
1328  divisionPoint = 1-1/ratioFactor
1329  xoffsetFactor = 1/divisionPoint #* 0.6
1330 
1331  if nrows == 1:
1332  xoffsetFactor *= 0.6
1333  elif nrows == 2:
1334  yoffsetFactor *= 2
1335  xoffsetFactor *= 1.5
1336  elif nrows == 3:
1337  yoffsetFactor *= 4
1338  xoffsetFactor *= 2.3
1339  elif nrows >= 4:
1340  yoffsetFactor *= 5
1341  xoffsetFactor *= 3
1342 
1343  self._frame.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1344  self._frameRatio.GetYaxis().SetLabelSize(int(self._frameRatio.GetYaxis().GetLabelSize()*0.8))
1345  self._frameRatio.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1346  self._frameRatio.GetXaxis().SetTitleOffset(self._frameRatio.GetXaxis().GetTitleOffset()*xoffsetFactor)
1347 
1348  self._frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
1349 
1350  self._frameRatio.GetYaxis().SetTitle(ratioYTitle)
1351 
1352  def setLogx(self, log):
1353  self._pad.SetLogx(log)
1354  self._padRatio.SetLogx(log)
1355 
1356  def setLogy(self, log):
1357  self._pad.SetLogy(log)
1358 
1359  def setGridx(self, grid):
1360  self._pad.SetGridx(grid)
1361  self._padRatio.SetGridx(grid)
1362 
1363  def setGridy(self, grid):
1364  self._pad.SetGridy(grid)
1365  self._padRatio.SetGridy(grid)
1366 
1367  def adjustMarginLeft(self, adjust):
1368  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1369  self._padRatio.SetLeftMargin(self._padRatio.GetLeftMargin()+adjust)
1370  # Need to redraw frame after adjusting the margin
1371  self._pad.cd()
1372  self._frame.Draw("")
1373  self._padRatio.cd()
1374  self._frameRatio.Draw("")
1375 
1376  def adjustMarginRight(self, adjust):
1377  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1378  self._padRatio.SetRightMargin(self._padRatio.GetRightMargin()+adjust)
1379  # Need to redraw frames after adjusting the margin
1380  self._pad.cd()
1381  self._frame.Draw("")
1382  self._padRatio.cd()
1383  self._frameRatio.Draw("")
1384 
1385  def setTitle(self, title):
1386  self._frame.SetTitle(title)
1387 
1388  def setXTitle(self, title):
1389  self._frameRatio.GetXaxis().SetTitle(title)
1390 
1391  def setXTitleSize(self, size):
1392  self._frameRatio.GetXaxis().SetTitleSize(size)
1393 
1394  def setXTitleOffset(self, offset):
1395  self._frameRatio.GetXaxis().SetTitleOffset(offset)
1396 
1397  def setXLabelSize(self, size):
1398  self._frameRatio.GetXaxis().SetLabelSize(size)
1399 
1400  def setYTitle(self, title):
1401  self._frame.GetYaxis().SetTitle(title)
1402 
1403  def setYTitleRatio(self, title):
1404  self._frameRatio.GetYaxis().SetTitle(title)
1405 
1406  def setYTitleSize(self, size):
1407  self._frame.GetYaxis().SetTitleSize(size)
1408  self._frameRatio.GetYaxis().SetTitleSize(size)
1409 
1410  def setYTitleOffset(self, offset):
1411  self._frame.GetYaxis().SetTitleOffset(offset)
1412  self._frameRatio.GetYaxis().SetTitleOffset(offset)
1413 
1414  def redrawAxis(self):
1415  self._padRatio.RedrawAxis()
1416  self._pad.RedrawAxis()
1417 
1418  self._parentPad.cd()
1419 
1420  # pad to hide the lowest y axis label of the main pad
1421  xmin=0.065
1422  ymin=0.285
1423  xmax=0.128
1424  ymax=0.33
1425  self._coverPad = ROOT.TPad("coverpad", "coverpad", xmin, ymin, xmax, ymax)
1426  self._coverPad.SetBorderMode(0)
1427  self._coverPad.Draw()
1428 
1429  self._pad.cd()
1430  self._pad.Pop() # Move the first pad on top
1431 
1433  """Class for creating and managing a frame for a plot from TGraph2D"""
1434  def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
1435  self._pad = pad
1436  if ratioOrig:
1437  self._pad = pad.cd(1)
1438 
1439  # adjust margins because of not having the ratio, we want
1440  # the same bottom margin, so some algebra gives this
1441  (xlow, ylow, width, height) = (self._pad.GetXlowNDC(), self._pad.GetYlowNDC(), self._pad.GetWNDC(), self._pad.GetHNDC())
1442  xup = xlow+width
1443  yup = ylow+height
1444 
1445  bottomMargin = self._pad.GetBottomMargin()
1446  bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
1447 
1448  ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
1449  topMarginNew = self._pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
1450 
1451  self._pad.SetPad(xlow, ylowNew, xup, yup)
1452  self._pad.SetTopMargin(topMarginNew)
1453  self._pad.SetBottomMargin(bottomMarginNew)
1454 
1455  self._xtitleoffset = 1.8
1456  self._ytitleoffset = 2.3
1457 
1458  self._firstHisto = histos[0]
1459 
1460  def setLogx(self, log):
1461  pass
1462 
1463  def setLogy(self, log):
1464  pass
1465 
1466  def setGridx(self, grid):
1467  pass
1468 
1469  def setGridy(self, grid):
1470  pass
1471 
1472  def adjustMarginLeft(self, adjust):
1473  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1474  self._pad.cd()
1475 
1476  def adjustMarginRight(self, adjust):
1477  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1478  self._pad.cd()
1479 
1480  def setTitle(self, title):
1481  pass
1482 
1483  def setXTitle(self, title):
1484  self._xtitle = title
1485 
1486  def setXTitleSize(self, size):
1487  self._xtitlesize = size
1488 
1489  def setXTitleOffset(self, size):
1490  self._xtitleoffset = size
1491 
1492  def setXLabelSize(self, size):
1493  self._xlabelsize = size
1494 
1495  def setYTitle(self, title):
1496  self._ytitle = title
1497 
1498  def setYTitleSize(self, size):
1499  self._ytitlesize = size
1500 
1501  def setYTitleOffset(self, offset):
1502  self._ytitleoffset = offset
1503 
1504  def setZTitle(self, title):
1505  self._firstHisto.GetZaxis().SetTitle(title)
1506 
1507  def setZTitleOffset(self, offset):
1508  self._firstHisto.GetZaxis().SetTitleOffset(offset)
1509 
1510  def redrawAxis(self):
1511  # set top view
1512  epsilon = 1e-7
1513  self._pad.SetPhi(epsilon)
1514  self._pad.SetTheta(90+epsilon)
1515 
1516  self._firstHisto.GetXaxis().SetTitleOffset(self._xtitleoffset)
1517  self._firstHisto.GetYaxis().SetTitleOffset(self._ytitleoffset)
1518 
1519  if hasattr(self, "_xtitle"):
1520  self._firstHisto.GetXaxis().SetTitle(self._xtitle)
1521  if hasattr(self, "_xtitlesize"):
1522  self._firstHisto.GetXaxis().SetTitleSize(self._xtitlesize)
1523  if hasattr(self, "_xlabelsize"):
1524  self._firstHisto.GetXaxis().SetLabelSize(self._labelsize)
1525  if hasattr(self, "_ytitle"):
1526  self._firstHisto.GetYaxis().SetTitle(self._ytitle)
1527  if hasattr(self, "_ytitlesize"):
1528  self._firstHisto.GetYaxis().SetTitleSize(self._ytitlesize)
1529  if hasattr(self, "_ytitleoffset"):
1530  self._firstHisto.GetYaxis().SetTitleOffset(self._ytitleoffset)
1531 
1532 class PlotText:
1533  """Abstraction on top of TLatex"""
1534  def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None):
1535  """Constructor.
1536 
1537  Arguments:
1538  x -- X coordinate of the text (in NDC)
1539  y -- Y coordinate of the text (in NDC)
1540  text -- String to draw
1541  size -- Size of text (None for the default value, taken from gStyle)
1542  bold -- Should the text be bold?
1543  align -- Alignment of text (left, center, right)
1544  color -- Color of the text
1545  font -- Specify font explicitly
1546  """
1547  self._x = x
1548  self._y = y
1549  self._text = text
1550 
1551  self._l = ROOT.TLatex()
1552  self._l.SetNDC()
1553  if not bold:
1554  self._l.SetTextFont(self._l.GetTextFont()-20) # bold -> normal
1555  if font is not None:
1556  self._l.SetTextFont(font)
1557  if size is not None:
1558  self._l.SetTextSize(size)
1559  if isinstance(align, str):
1560  if align.lower() == "left":
1561  self._l.SetTextAlign(11)
1562  elif align.lower() == "center":
1563  self._l.SetTextAlign(21)
1564  elif align.lower() == "right":
1565  self._l.SetTextAlign(31)
1566  else:
1567  raise Exception("Error: Invalid option '%s' for text alignment! Options are: 'left', 'center', 'right'."%align)
1568  else:
1569  self._l.SetTextAlign(align)
1570  self._l.SetTextColor(color)
1571 
1572  def Draw(self, options=None):
1573  """Draw the text to the current TPad.
1574 
1575  Arguments:
1576  options -- For interface compatibility, ignored
1577 
1578  Provides interface compatible with ROOT's drawable objects.
1579  """
1580  self._l.DrawLatex(self._x, self._y, self._text)
1581 
1582 
1584  """Class for drawing text and a background box."""
1585  def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, **kwargs):
1586  """Constructor
1587 
1588  Arguments:
1589  xmin -- X min coordinate of the box (NDC)
1590  ymin -- Y min coordinate of the box (NDC) (if None, deduced automatically)
1591  xmax -- X max coordinate of the box (NDC)
1592  ymax -- Y max coordinate of the box (NDC)
1593  lineheight -- Line height
1594  fillColor -- Fill color of the box
1595  transparent -- Should the box be transparent? (in practive the TPave is not created)
1596 
1597  Keyword arguments are forwarded to constructor of PlotText
1598  """
1599  # ROOT.TPave Set/GetX1NDC() etc don't seem to work as expected.
1600  self._xmin = xmin
1601  self._xmax = xmax
1602  self._ymin = ymin
1603  self._ymax = ymax
1604  self._lineheight = lineheight
1605  self._fillColor = fillColor
1606  self._transparent = transparent
1607  self._texts = []
1608  self._textArgs = {}
1609  self._textArgs.update(kwargs)
1610 
1611  self._currenty = ymax
1612 
1613  def addText(self, text):
1614  """Add text to current position"""
1615  self._currenty -= self._lineheight
1616  self._texts.append(PlotText(self._xmin+0.01, self._currenty, text, **self._textArgs))
1617 
1618  def width(self):
1619  return self._xmax-self._xmin
1620 
1621  def move(self, dx=0, dy=0, dw=0, dh=0):
1622  """Move the box and the contained text objects
1623 
1624  Arguments:
1625  dx -- Movement in x (positive is to right)
1626  dy -- Movement in y (positive is to up)
1627  dw -- Increment of width (negative to decrease width)
1628  dh -- Increment of height (negative to decrease height)
1629 
1630  dx and dy affect to both box and text objects, dw and dh
1631  affect the box only.
1632  """
1633  self._xmin += dx
1634  self._xmax += dx
1635  if self._ymin is not None:
1636  self._ymin += dy
1637  self._ymax += dy
1638 
1639  self._xmax += dw
1640  if self._ymin is not None:
1641  self._ymin -= dh
1642 
1643  for t in self._texts:
1644  t._x += dx
1645  t._y += dy
1646 
1647  def Draw(self, options=""):
1648  """Draw the box and the text to the current TPad.
1649 
1650  Arguments:
1651  options -- Forwarded to ROOT.TPave.Draw(), and the Draw() of the contained objects
1652  """
1653  if not self._transparent:
1654  ymin = self.ymin
1655  if ymin is None:
1656  ymin = self.currenty - 0.01
1657  self._pave = ROOT.TPave(self.xmin, self.ymin, self.xmax, self.ymax, 0, "NDC")
1658  self._pave.SetFillColor(self.fillColor)
1659  self._pave.Draw(options)
1660  for t in self._texts:
1661  t.Draw(options)
1662 
1663 def _copyStyle(src, dst):
1664  properties = []
1665  if hasattr(src, "GetLineColor") and hasattr(dst, "SetLineColor"):
1666  properties.extend(["LineColor", "LineStyle", "LineWidth"])
1667  if hasattr(src, "GetFillColor") and hasattr(dst, "SetFillColor"):
1668  properties.extend(["FillColor", "FillStyle"])
1669  if hasattr(src, "GetMarkerColor") and hasattr(dst, "SetMarkerColor"):
1670  properties.extend(["MarkerColor", "MarkerSize", "MarkerStyle"])
1671 
1672  for prop in properties:
1673  getattr(dst, "Set"+prop)(getattr(src, "Get"+prop)())
1674 
1676  """Denotes an empty place in a group."""
1677  def __init__(self):
1678  pass
1679 
1680  def getName(self):
1681  return None
1682 
1684  return False
1685 
1686  def create(self, *args, **kwargs):
1687  pass
1688 
1689  def isEmpty(self):
1690  return True
1691 
1693  return 0
1694 
1695 class Plot:
1696  """Represents one plot, comparing one or more histograms."""
1697  def __init__(self, name, **kwargs):
1698  """ Constructor.
1699 
1700  Arguments:
1701  name -- String for name of the plot, or Efficiency object
1702 
1703  Keyword arguments:
1704  fallback -- Dictionary for specifying fallback (default None)
1705  outname -- String for an output name of the plot (default None for the same as 'name')
1706  title -- String for a title of the plot (default None)
1707  xtitle -- String for x axis title (default None)
1708  xtitlesize -- Float for x axis title size (default None)
1709  xtitleoffset -- Float for x axis title offset (default None)
1710  xlabelsize -- Float for x axis label size (default None)
1711  ytitle -- String for y axis title (default None)
1712  ytitlesize -- Float for y axis title size (default None)
1713  ytitleoffset -- Float for y axis title offset (default None)
1714  ztitle -- String for z axis title (default None)
1715  ztitleoffset -- Float for z axis title offset (default None)
1716  xmin -- Float for x axis minimum (default None, i.e. automatic)
1717  xmax -- Float for x axis maximum (default None, i.e. automatic)
1718  ymin -- Float for y axis minimum (default 0)
1719  ymax -- Float for y axis maximum (default None, i.e. automatic)
1720  xlog -- Bool for x axis log status (default False)
1721  ylog -- Bool for y axis log status (default False)
1722  xgrid -- Bool for x axis grid status (default True)
1723  ygrid -- Bool for y axis grid status (default True)
1724  stat -- Draw stat box? (default False)
1725  fit -- Do gaussian fit? (default False)
1726  statx -- Stat box x coordinate (default 0.65)
1727  staty -- Stat box y coordinate (default 0.8)
1728  statyadjust -- List of floats for stat box y coordinate adjustments (default None)
1729  normalizeToUnitArea -- Normalize histograms to unit area? (default False)
1730  normalizeToNumberOfEvents -- Normalize histograms to number of events? If yes, the PlotFolder needs 'numberOfEventsHistogram' set to a histogram filled once per event (default False)
1731  profileX -- Take histograms via ProfileX()? (default False)
1732  fitSlicesY -- Take histograms via FitSlicesY() (default False)
1733  rebinX -- rebin x axis (default None)
1734  scale -- Scale histograms by a number (default None)
1735  xbinlabels -- List of x axis bin labels (if given, default None)
1736  xbinlabelsize -- Size of x axis bin labels (default None)
1737  xbinlabeloption -- Option string for x axis bin labels (default None)
1738  removeEmptyBins -- Bool for removing empty bins, but only if histogram has bin labels (default False)
1739  printBins -- Bool for printing bin values, but only if histogram has bin labels (default False)
1740  drawStyle -- If "hist", draw as line instead of points (default None)
1741  drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
1742  lineWidth -- If drawStyle=="hist", the width of line (default 2)
1743  legendDx -- Float for moving TLegend in x direction for separate=True (default None)
1744  legendDy -- Float for moving TLegend in y direction for separate=True (default None)
1745  legendDw -- Float for changing TLegend width for separate=True (default None)
1746  legendDh -- Float for changing TLegend height for separate=True (default None)
1747  legend -- Bool to enable/disable legend (default True)
1748  adjustMarginLeft -- Float for adjusting left margin (default None)
1749  adjustMarginRight -- Float for adjusting right margin (default None)
1750  ratio -- Possibility to disable ratio for this particular plot (default None)
1751  ratioYmin -- Float for y axis minimum in ratio pad (default: list of values)
1752  ratioYmax -- Float for y axis maximum in ratio pad (default: list of values)
1753  ratioFit -- Fit straight line in ratio? (default None)
1754  ratioUncertainty -- Plot uncertainties on ratio? (default True)
1755  ratioCoverageXrange -- Range of x axis values (xmin,xmax) to limit the automatic ratio y axis range calculation to (default None for disabled)
1756  histogramModifier -- Function to be called in create() to modify the histograms (default None)
1757  """
1758  self._name = name
1759 
1760  def _set(attr, default):
1761  setattr(self, "_"+attr, kwargs.get(attr, default))
1762 
1763  _set("fallback", None)
1764  _set("outname", None)
1765 
1766  _set("title", None)
1767  _set("xtitle", None)
1768  _set("xtitlesize", None)
1769  _set("xtitleoffset", None)
1770  _set("xlabelsize", None)
1771  _set("ytitle", None)
1772  _set("ytitlesize", None)
1773  _set("ytitleoffset", None)
1774  _set("ztitle", None)
1775  _set("ztitleoffset", None)
1776 
1777  _set("xmin", None)
1778  _set("xmax", None)
1779  _set("ymin", 0.)
1780  _set("ymax", None)
1781 
1782  _set("xlog", False)
1783  _set("ylog", False)
1784  _set("xgrid", True)
1785  _set("ygrid", True)
1786 
1787  _set("stat", False)
1788  _set("fit", False)
1789 
1790  _set("statx", 0.65)
1791  _set("staty", 0.8)
1792  _set("statyadjust", None)
1793 
1794  _set("normalizeToUnitArea", False)
1795  _set("normalizeToNumberOfEvents", False)
1796  _set("profileX", False)
1797  _set("fitSlicesY", False)
1798  _set("rebinX", None)
1799 
1800  _set("scale", None)
1801  _set("xbinlabels", None)
1802  _set("xbinlabelsize", None)
1803  _set("xbinlabeloption", None)
1804  _set("removeEmptyBins", False)
1805  _set("printBins", False)
1806 
1807  _set("drawStyle", "EP")
1808  _set("drawCommand", None)
1809  _set("lineWidth", 2)
1810 
1811  _set("legendDx", None)
1812  _set("legendDy", None)
1813  _set("legendDw", None)
1814  _set("legendDh", None)
1815  _set("legend", True)
1816 
1817  _set("adjustMarginLeft", None)
1818  _set("adjustMarginRight", None)
1819 
1820  _set("ratio", None)
1821  _set("ratioYmin", [0, 0.2, 0.5, 0.7, 0.8, 0.9, 0.95])
1822  _set("ratioYmax", [1.05, 1.1, 1.2, 1.3, 1.5, 1.8, 2, 2.5, 3, 4, 5])
1823  _set("ratioFit", None)
1824  _set("ratioUncertainty", True)
1825  _set("ratioCoverageXrange", None)
1826 
1827  _set("histogramModifier", None)
1828 
1829  self._histograms = []
1830 
1831  def setProperties(self, **kwargs):
1832  for name, value in kwargs.items():
1833  if not hasattr(self, "_"+name):
1834  raise Exception("No attribute '%s'" % name)
1835  setattr(self, "_"+name, value)
1836 
1837  def clone(self, **kwargs):
1838  if not self.isEmpty():
1839  raise Exception("Plot can be cloned only before histograms have been created")
1840  cl = copy.copy(self)
1841  cl.setProperties(**kwargs)
1842  return cl
1843 
1845  """Return number of existing histograms."""
1846  return len([h for h in self._histograms if h is not None])
1847 
1848  def isEmpty(self):
1849  """Return true if there are no histograms created for the plot"""
1850  return self.getNumberOfHistograms() == 0
1851 
1852  def isTGraph2D(self):
1853  for h in self._histograms:
1854  if isinstance(h, ROOT.TGraph2D):
1855  return True
1856  return False
1857 
1858  def isRatio(self, ratio):
1859  if self._ratio is None:
1860  return ratio
1861  return ratio and self._ratio
1862 
1863  def setName(self, name):
1864  self._name = name
1865 
1866  def getName(self):
1867  if self._outname is not None:
1868  return self._outname
1869  if isinstance(self._name, list):
1870  return str(self._name[0])
1871  else:
1872  return str(self._name)
1873 
1875  """Return true if the ratio uncertainty should be drawn"""
1876  return self._ratioUncertainty
1877 
1878  def _createOne(self, name, index, tdir, nevents):
1879  """Create one histogram from a TDirectory."""
1880  if tdir == None:
1881  return None
1882 
1883  # If name is a list, pick the name by the index
1884  if isinstance(name, list):
1885  name = name[index]
1886 
1887  h = _getOrCreateObject(tdir, name)
1888  if h is not None and self._normalizeToNumberOfEvents and nevents is not None and nevents != 0:
1889  h.Scale(1.0/nevents)
1890  return h
1891 
1892  def create(self, tdirNEvents, requireAllHistograms=False):
1893  """Create histograms from list of TDirectories"""
1894  self._histograms = [self._createOne(self._name, i, tdirNEvent[0], tdirNEvent[1]) for i, tdirNEvent in enumerate(tdirNEvents)]
1895 
1896  if self._fallback is not None:
1897  profileX = [self._profileX]*len(self._histograms)
1898  for i in range(0, len(self._histograms)):
1899  if self._histograms[i] is None:
1900  self._histograms[i] = self._createOne(self._fallback["name"], i, tdirNEvents[i][0], tdirNEvents[i][1])
1901  profileX[i] = self._fallback.get("profileX", self._profileX)
1902 
1903  if self._histogramModifier is not None:
1904  self._histograms = self._histogramModifier(self._histograms)
1905 
1906  if len(self._histograms) > len(_plotStylesColor):
1907  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)))
1908 
1909  # Modify histograms here in case self._name returns numbers
1910  # and self._histogramModifier creates the histograms from
1911  # these numbers
1912  def _modifyHisto(th1, profileX):
1913  if th1 is None:
1914  return None
1915 
1916  if profileX:
1917  th1 = th1.ProfileX()
1918 
1919  if self._fitSlicesY:
1920  ROOT.TH1.AddDirectory(True)
1921  th1.FitSlicesY()
1922  th1 = ROOT.gDirectory.Get(th1.GetName()+"_2")
1923  th1.SetDirectory(None)
1924  #th1.SetName(th1.GetName()+"_ref")
1925  ROOT.TH1.AddDirectory(False)
1926 
1927  if self._title is not None:
1928  th1.SetTitle(self._title)
1929 
1930  if self._scale is not None:
1931  th1.Scale(self._scale)
1932 
1933  return th1
1934 
1935  if self._fallback is not None:
1936  self._histograms = list(map(_modifyHisto, self._histograms, profileX))
1937  else:
1938  self._histograms = list(map(lambda h: _modifyHisto(h, self._profileX), self._histograms))
1939  if requireAllHistograms and None in self._histograms:
1940  self._histograms = [None]*len(self._histograms)
1941 
1942  def _setStats(self, histos, startingX, startingY):
1943  """Set stats box."""
1944  if not self._stat:
1945  for h in histos:
1946  if h is not None and hasattr(h, "SetStats"):
1947  h.SetStats(0)
1948  return
1949 
1950  def _doStats(h, col, dy):
1951  if h is None:
1952  return
1953  h.SetStats(True)
1954 
1955  if self._fit and h.GetEntries() > 0.5:
1956  h.Fit("gaus", "Q")
1957  f = h.GetListOfFunctions().FindObject("gaus")
1958  if f == None:
1959  h.SetStats(0)
1960  return
1961  f.SetLineColor(col)
1962  f.SetLineWidth(1)
1963  h.Draw()
1964  ROOT.gPad.Update()
1965  st = h.GetListOfFunctions().FindObject("stats")
1966  if self._fit:
1967  st.SetOptFit(0o010)
1968  st.SetOptStat(1001)
1969  st.SetOptStat(1110)
1970  st.SetX1NDC(startingX)
1971  st.SetX2NDC(startingX+0.3)
1972  st.SetY1NDC(startingY+dy)
1973  st.SetY2NDC(startingY+dy+0.12)
1974  st.SetTextColor(col)
1975 
1976  dy = 0.0
1977  for i, h in enumerate(histos):
1978  if self._statyadjust is not None and i < len(self._statyadjust):
1979  dy += self._statyadjust[i]
1980 
1981  _doStats(h, _plotStylesColor[i], dy)
1982  dy -= 0.16
1983 
1984  def _normalize(self):
1985  """Normalise histograms to unit area"""
1986 
1987  for h in self._histograms:
1988  if h is None:
1989  continue
1990  i = h.Integral()
1991  if i == 0:
1992  continue
1993  if h.GetSumw2().fN <= 0: # to suppress warning
1994  h.Sumw2()
1995  h.Scale(1.0/i)
1996 
1997  def draw(self, pad, ratio, ratioFactor, nrows):
1998  """Draw the histograms using values for a given algorithm."""
1999 # if len(self._histograms) == 0:
2000 # print "No histograms for plot {name}".format(name=self._name)
2001 # return
2002 
2003  isTGraph2D = self.isTGraph2D()
2004  if isTGraph2D:
2005  # Ratios for the TGraph2Ds is not that interesting
2006  ratioOrig = ratio
2007  ratio = False
2008 
2009  if self._normalizeToUnitArea:
2010  self._normalize()
2011 
2012  if self._rebinX is not None:
2013  for h in self._histograms:
2014  h.Rebin(self._rebinX)
2015 
2016  def _styleMarker(h, msty, col):
2017  h.SetMarkerStyle(msty)
2018  h.SetMarkerColor(col)
2019  h.SetMarkerSize(0.7)
2020  h.SetLineColor(1)
2021  h.SetLineWidth(1)
2022 
2023  def _styleHist(h, msty, col):
2024  _styleMarker(h, msty, col)
2025  h.SetLineColor(col)
2026  h.SetLineWidth(self._lineWidth)
2027 
2028  # Use marker or hist style
2029  style = _styleMarker
2030  if "hist" in self._drawStyle.lower():
2031  style = _styleHist
2032  if len(self._histograms) > 0 and isinstance(self._histograms[0], ROOT.TGraph):
2033  if "l" in self._drawStyle.lower():
2034  style = _styleHist
2035 
2036  # Apply style to histograms, filter out Nones
2037  histos = []
2038  for i, h in enumerate(self._histograms):
2039  if h is None:
2040  continue
2041  style(h, _plotStylesMarker[i], _plotStylesColor[i])
2042  histos.append(h)
2043  if len(histos) == 0:
2044  if verbose:
2045  print("No histograms for plot {name}".format(name=self.getName()))
2046  return
2047 
2048  # Extract x bin labels, make sure that only bins with same
2049  # label are compared with each other
2050  histosHaveBinLabels = len(histos[0].GetXaxis().GetBinLabel(1)) > 0
2051  xbinlabels = self._xbinlabels
2052  ybinlabels = None
2053  if xbinlabels is None:
2054  if histosHaveBinLabels:
2055  xbinlabels = _mergeBinLabelsX(histos)
2056  if isinstance(histos[0], ROOT.TH2):
2057  ybinlabels = _mergeBinLabelsY(histos)
2058 
2059  if len(histos) > 1: # don't bother if only one histogram
2060  # doing this for TH2 is pending for use case, for now there is only 1 histogram/plot for TH2
2061  histos = _th1IncludeOnlyBins(histos, xbinlabels)
2062  self._tmp_histos = histos # need to keep these in memory too ...
2063 
2064  # Remove empty bins, but only if histograms have bin labels
2065  if self._removeEmptyBins and histosHaveBinLabels:
2066  # at this point, all histograms have been "equalized" by their x binning and labels
2067  # therefore remove bins which are empty in all histograms
2068  if isinstance(histos[0], ROOT.TH2):
2069  (histos, xbinlabels, ybinlabels) = _th2RemoveEmptyBins(histos, xbinlabels, ybinlabels)
2070  else:
2071  (histos, xbinlabels) = _th1RemoveEmptyBins(histos, xbinlabels)
2072  self._tmp_histos = histos # need to keep these in memory too ...
2073  if len(histos) == 0:
2074  if verbose:
2075  print("No histograms with non-empty bins for plot {name}".format(name=self.getName()))
2076  return
2077 
2078  if self._printBins and histosHaveBinLabels:
2079  print("####################")
2080  print(self._name)
2081  width = max([len(l) for l in xbinlabels])
2082  tmp = "%%-%ds " % width
2083  for b in range(1, histos[0].GetNbinsX()+1):
2084  s = tmp % xbinlabels[b-1]
2085  for h in histos:
2086  s += "%.3f " % h.GetBinContent(b)
2087  print(s)
2088  print()
2089 
2090  bounds = _findBounds(histos, self._ylog,
2091  xmin=self._xmin, xmax=self._xmax,
2092  ymin=self._ymin, ymax=self._ymax)
2093  zmax = None
2094  if isinstance(histos[0], ROOT.TH2):
2095  zmax = max([h.GetMaximum() for h in histos])
2096 
2097  # need to keep these in memory
2100 
2101  if ratio:
2102  self._ratios = _calculateRatios(histos, self._ratioUncertainty) # need to keep these in memory too ...
2103  ratioHistos = [h for h in [r.getRatio() for r in self._ratios[1:]] if h is not None]
2104 
2105  if len(ratioHistos) > 0:
2106  ratioBoundsY = _findBoundsY(ratioHistos, ylog=False, ymin=self._ratioYmin, ymax=self._ratioYmax, coverage=0.68, coverageRange=self._ratioCoverageXrange)
2107  else:
2108  ratioBoundsY = (0.9, 1,1) # hardcoded default in absence of valid ratio calculations
2109 
2110  if self._ratioFit is not None:
2111  for i, rh in enumerate(ratioHistos):
2112  tf_line = ROOT.TF1("line%d"%i, "[0]+x*[1]")
2113  tf_line.SetRange(self._ratioFit["rangemin"], self._ratioFit["rangemax"])
2114  fitres = rh.Fit(tf_line, "RINSQ")
2115  tf_line.SetLineColor(rh.GetMarkerColor())
2116  tf_line.SetLineWidth(2)
2117  self._ratioAdditional.append(tf_line)
2118  box = PlotTextBox(xmin=self._ratioFit.get("boxXmin", 0.14), ymin=None, # None for automatix
2119  xmax=self._ratioFit.get("boxXmax", 0.35), ymax=self._ratioFit.get("boxYmax", 0.09),
2120  color=rh.GetMarkerColor(), font=43, size=11, lineheight=0.02)
2121  box.move(dx=(box.width()+0.01)*i)
2122  #box.addText("Const: %.4f" % fitres.Parameter(0))
2123  #box.addText("Slope: %.4f" % fitres.Parameter(1))
2124  box.addText("Const: %.4f#pm%.4f" % (fitres.Parameter(0), fitres.ParError(0)))
2125  box.addText("Slope: %.4f#pm%.4f" % (fitres.Parameter(1), fitres.ParError(1)))
2126  self._mainAdditional.append(box)
2127 
2128 
2129  # Create bounds before stats in order to have the
2130  # SetRangeUser() calls made before the fit
2131  #
2132  # stats is better to be called before frame, otherwise get
2133  # mess in the plot (that frame creation cleans up)
2134  if ratio:
2135  pad.cd(1)
2136  self._setStats(histos, self._statx, self._staty)
2137 
2138  # Create frame
2139  if isTGraph2D:
2140  frame = FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
2141  else:
2142  if ratio:
2143  ratioBounds = (bounds[0], ratioBoundsY[0], bounds[2], ratioBoundsY[1])
2144  frame = FrameRatio(pad, bounds, zmax, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
2145  else:
2146  frame = Frame(pad, bounds, zmax, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption, ybinlabels=ybinlabels)
2147 
2148  # Set log and grid
2149  frame.setLogx(self._xlog)
2150  frame.setLogy(self._ylog)
2151  frame.setGridx(self._xgrid)
2152  frame.setGridy(self._ygrid)
2153 
2154  # Construct draw option string
2155  opt = "sames" # s for statbox or something?
2156  ds = ""
2157  if self._drawStyle is not None:
2158  ds = self._drawStyle
2159  if self._drawCommand is not None:
2160  ds = self._drawCommand
2161  if len(ds) > 0:
2162  opt += " "+ds
2163 
2164  # Set properties of frame
2165  frame.setTitle(histos[0].GetTitle())
2166  if self._xtitle == 'Default':
2167  frame.setXTitle( histos[0].GetXaxis().GetTitle() )
2168  elif self._xtitle is not None:
2169  frame.setXTitle(self._xtitle)
2170  if self._xtitlesize is not None:
2171  frame.setXTitleSize(self._xtitlesize)
2172  if self._xtitleoffset is not None:
2173  frame.setXTitleOffset(self._xtitleoffset)
2174  if self._xlabelsize is not None:
2175  frame.setXLabelSize(self._xlabelsize)
2176  if self._ytitle == 'Default':
2177  frame.setYTitle( histos[0].GetYaxis().GetTitle() )
2178  elif self._ytitle is not None:
2179  frame.setYTitle(self._ytitle)
2180  if self._ytitlesize is not None:
2181  frame.setYTitleSize(self._ytitlesize)
2182  if self._ytitleoffset is not None:
2183  frame.setYTitleOffset(self._ytitleoffset)
2184  if self._ztitle is not None:
2185  frame.setZTitle(self._ztitle)
2186  if self._ztitleoffset is not None:
2187  frame.setZTitleOffset(self._ztitleoffset)
2188  if self._adjustMarginLeft is not None:
2189  frame.adjustMarginLeft(self._adjustMarginLeft)
2190  if self._adjustMarginRight is not None:
2191  frame.adjustMarginRight(self._adjustMarginRight)
2192  elif "z" in opt:
2193  frame.adjustMarginLeft(0.03)
2194  frame.adjustMarginRight(0.08)
2195 
2196  # Draw histograms
2197  if ratio:
2198  frame._pad.cd()
2199 
2200  for i, h in enumerate(histos):
2201  o = opt
2202  if isTGraph2D and i == 0:
2203  o = o.replace("sames", "")
2204  h.Draw(o)
2205 
2206  for addl in self._mainAdditional:
2207  addl.Draw("same")
2208 
2209  # Draw ratios
2210  if ratio and len(self._ratios) > 0:
2211  frame._padRatio.cd()
2212  firstRatio = self._ratios[0].getRatio()
2213  if self._ratioUncertainty and firstRatio is not None:
2214  firstRatio.SetFillStyle(1001)
2215  firstRatio.SetFillColor(ROOT.kGray)
2216  firstRatio.SetLineColor(ROOT.kGray)
2217  firstRatio.SetMarkerColor(ROOT.kGray)
2218  firstRatio.SetMarkerSize(0)
2219  self._ratios[0].draw("E2")
2220  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
2221  for r in self._ratios[1:]:
2222  r.draw()
2223 
2224  for addl in self._ratioAdditional:
2225  addl.Draw("same")
2226 
2227  frame.redrawAxis()
2228  self._frame = frame # keep the frame in memory for sure
2229 
2230  def addToLegend(self, legend, legendLabels, denomUncertainty):
2231  """Add histograms to a legend.
2232 
2233  Arguments:
2234  legend -- TLegend
2235  legendLabels -- List of strings for the legend labels
2236  """
2237  first = denomUncertainty
2238  for h, label in zip(self._histograms, legendLabels):
2239  if h is None:
2240  first = False
2241  continue
2242  if first:
2243  self._forLegend = h.Clone()
2244  self._forLegend.SetFillStyle(1001)
2245  self._forLegend.SetFillColor(ROOT.kGray)
2246  entry = legend.AddEntry(self._forLegend, label, "lpf")
2247  first = False
2248  else:
2249  legend.AddEntry(h, label, "LP")
2250 
2252  """Group of plots, results a TCanvas"""
2253  def __init__(self, name, plots, **kwargs):
2254  """Constructor.
2255 
2256  Arguments:
2257  name -- String for name of the TCanvas, used also as the basename of the picture files
2258  plots -- List of Plot objects
2259 
2260  Keyword arguments:
2261  ncols -- Number of columns (default 2)
2262  legendDx -- Float for moving TLegend in x direction (default None)
2263  legendDy -- Float for moving TLegend in y direction (default None)
2264  legendDw -- Float for changing TLegend width (default None)
2265  legendDh -- Float for changing TLegend height (default None)
2266  legend -- Bool for disabling legend (default True for legend being enabled)
2267  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
2268  onlyForPileup -- Plots this group only for pileup samples
2269  """
2270  super(PlotGroup, self).__init__()
2271 
2272  self._name = name
2273  self._plots = plots
2274 
2275  def _set(attr, default):
2276  setattr(self, "_"+attr, kwargs.get(attr, default))
2277 
2278  _set("ncols", 2)
2279 
2280  _set("legendDx", None)
2281  _set("legendDy", None)
2282  _set("legendDw", None)
2283  _set("legendDh", None)
2284  _set("legend", True)
2285 
2286  _set("overrideLegendLabels", None)
2287 
2288  _set("onlyForPileup", False)
2289 
2290  self._ratioFactor = 1.25
2291 
2292  def setProperties(self, **kwargs):
2293  for name, value in kwargs.items():
2294  if not hasattr(self, "_"+name):
2295  raise Exception("No attribute '%s'" % name)
2296  setattr(self, "_"+name, value)
2297 
2298  def getName(self):
2299  return self._name
2300 
2301  def getPlots(self):
2302  return self._plots
2303 
2304  def remove(self, name):
2305  for i, plot in enumerate(self._plots):
2306  if plot.getName() == name:
2307  del self._plots[i]
2308  return
2309  raise Exception("Did not find Plot '%s' from PlotGroup '%s'" % (name, self._name))
2310 
2311  def clear(self):
2312  self._plots = []
2313 
2314  def append(self, plot):
2315  self._plots.append(plot)
2316 
2317  def getPlot(self, name):
2318  for plot in self._plots:
2319  if plot.getName() == name:
2320  return plot
2321  raise Exception("No Plot named '%s'" % name)
2322 
2323  def onlyForPileup(self):
2324  """Return True if the PlotGroup is intended only for pileup samples"""
2325  return self._onlyForPileup
2326 
2327  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2328  """Create histograms from a list of TDirectories.
2329 
2330  Arguments:
2331  tdirectoryNEvents -- List of (TDirectory, nevents) pairs
2332  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2333  """
2334  for plot in self._plots:
2335  plot.create(tdirectoryNEvents, requireAllHistograms)
2336 
2337  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2338  """Draw the histograms using values for a given algorithm.
2339 
2340  Arguments:
2341  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
2342  prefix -- Optional string for file name prefix (default None)
2343  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2344  saveFormat -- String specifying the plot format (default '.pdf')
2345  ratio -- Add ratio to the plot (default True)
2346  directory -- Directory where to save the file (default "")
2347  """
2348 
2349  if self._overrideLegendLabels is not None:
2350  legendLabels = self._overrideLegendLabels
2351 
2352  # Do not draw the group if it would be empty
2353  onlyEmptyPlots = True
2354  for plot in self._plots:
2355  if not plot.isEmpty():
2356  onlyEmptyPlots = False
2357  break
2358  if onlyEmptyPlots:
2359  return []
2360 
2361  if separate:
2362  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio, directory)
2363 
2364  cwidth = 500*self._ncols
2365  nrows = int((len(self._plots)+self._ncols-1)/self._ncols) # this should work also for odd n
2366  cheight = 500 * nrows
2367 
2368  if ratio:
2369  cheight = int(cheight*self._ratioFactor)
2370 
2371  canvas = _createCanvas(self._name, cwidth, cheight)
2372 
2373  canvas.Divide(self._ncols, nrows)
2374  if ratio:
2375  for i, plot in enumerate(self._plots):
2376  pad = canvas.cd(i+1)
2377  self._modifyPadForRatio(pad)
2378 
2379  # Draw plots to canvas
2380  for i, plot in enumerate(self._plots):
2381  pad = canvas.cd(i+1)
2382  if not plot.isEmpty():
2383  plot.draw(pad, ratio, self._ratioFactor, nrows)
2384 
2385  if plot._legend:
2386  # Setup legend
2387  canvas.cd()
2388  if len(self._plots) <= 4:
2389  lx1 = 0.2
2390  lx2 = 0.9
2391  ly1 = 0.48
2392  ly2 = 0.53
2393  else:
2394  lx1 = 0.1
2395  lx2 = 0.9
2396  ly1 = 0.64
2397  ly2 = 0.67
2398  if self._legendDx is not None:
2399  lx1 += self._legendDx
2400  lx2 += self._legendDx
2401  if self._legendDy is not None:
2402  ly1 += self._legendDy
2403  ly2 += self._legendDy
2404  if self._legendDw is not None:
2405  lx2 += self._legendDw
2406  if self._legendDh is not None:
2407  ly1 -= self._legendDh
2408  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
2409  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
2410  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2411  denomUncertainty=(ratio and denomUnc))
2412 
2413  return self._save(canvas, saveFormat, prefix=prefix, directory=directory)
2414 
2415  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory):
2416  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2417  width = 500
2418  height = 500
2419 
2420  lx1def = 0.6
2421  lx2def = 0.95
2422  ly1def = 0.85
2423  ly2def = 0.95
2424 
2425  ret = []
2426 
2427  for plot in self._plots:
2428  if plot.isEmpty():
2429  continue
2430 
2431  canvas = _createCanvas(self._name+"Single", width, height)
2432  canvasRatio = _createCanvas(self._name+"SingleRatio", width, int(height*self._ratioFactor))
2433 
2434  # from TDRStyle
2435  for c in [canvas, canvasRatio]:
2436  c.SetTopMargin(0.05)
2437  c.SetBottomMargin(0.13)
2438  c.SetLeftMargin(0.16)
2439  c.SetRightMargin(0.05)
2440 
2441  ratioForThisPlot = plot.isRatio(ratio)
2442  c = canvas
2443  if ratioForThisPlot:
2444  c = canvasRatio
2445  c.cd()
2446  self._modifyPadForRatio(c)
2447 
2448  # Draw plot to canvas
2449  c.cd()
2450  plot.draw(c, ratioForThisPlot, self._ratioFactor, 1)
2451 
2452  if plot._legend:
2453  # Setup legend
2454  lx1 = lx1def
2455  lx2 = lx2def
2456  ly1 = ly1def
2457  ly2 = ly2def
2458 
2459  if plot._legendDx is not None:
2460  lx1 += plot._legendDx
2461  lx2 += plot._legendDx
2462  if plot._legendDy is not None:
2463  ly1 += plot._legendDy
2464  ly2 += plot._legendDy
2465  if plot._legendDw is not None:
2466  lx2 += plot._legendDw
2467  if plot._legendDh is not None:
2468  ly1 -= plot._legendDh
2469 
2470  c.cd()
2471  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2472  denomUncertainty=(ratioForThisPlot and plot.drawRatioUncertainty))
2473 
2474  ret.extend(self._save(c, saveFormat, prefix=prefix, postfix="/"+plot.getName(), single=True, directory=directory))
2475  return ret
2476 
2477  def _modifyPadForRatio(self, pad):
2478  """Internal method to set divide a pad to two for ratio plots"""
2479  _modifyPadForRatio(pad, self._ratioFactor)
2480 
2481  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2482  if not self._legend:
2483  return None
2484 
2485  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2486  l.SetTextSize(textSize)
2487  l.SetLineColor(1)
2488  l.SetLineWidth(1)
2489  l.SetLineStyle(1)
2490  l.SetFillColor(0)
2491  l.SetMargin(0.07)
2492 
2493  plot.addToLegend(l, legendLabels, denomUncertainty)
2494  l.Draw()
2495  return l
2496 
2497  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory=""):
2498  # Save the canvas to file and clear
2499  name = self._name
2500  if not os.path.exists(directory+'/'+name):
2501  os.makedirs(directory+'/'+name, exist_ok=True)
2502  if prefix is not None:
2503  name = prefix+name
2504  if postfix is not None:
2505  name = name+postfix
2506  name = os.path.join(directory, name)
2507 
2508  if not verbose: # silence saved file printout
2509  backup = ROOT.gErrorIgnoreLevel
2510  ROOT.gErrorIgnoreLevel = ROOT.kWarning
2511  canvas.SaveAs(name+saveFormat)
2512  if not verbose:
2513  ROOT.gErrorIgnoreLevel = backup
2514 
2515  if single:
2516  canvas.Clear()
2517  canvas.SetLogx(False)
2518  canvas.SetLogy(False)
2519  else:
2520  canvas.Clear("D") # keep subpads
2521 
2522  return [name+saveFormat]
2523 
2525  """Resembles DQM GUI's "On side" layout.
2526 
2527  Like PlotGroup, but has only a description of a single plot. The
2528  plot is drawn separately for each file. Useful for 2D histograms."""
2529 
2530  def __init__(self, name, plot, ncols=2, onlyForPileup=False):
2531  super(PlotOnSideGroup, self).__init__(name, [], ncols=ncols, legend=False, onlyForPileup=onlyForPileup)
2532  self._plot = plot
2533  self._plot.setProperties(ratio=False)
2534 
2535  def append(self, *args, **kwargs):
2536  raise Exception("PlotOnSideGroup.append() is not implemented")
2537 
2538  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2539  self._plots = []
2540  for i, element in enumerate(tdirectoryNEvents):
2541  pl = self._plot.clone()
2542  pl.create([element], requireAllHistograms)
2543  pl.setName(pl.getName()+"_"+str(i))
2544  self._plots.append(pl)
2545 
2546  def draw(self, *args, **kwargs):
2547  kargs = copy.copy(kwargs)
2548  kargs["ratio"] = False
2549  return super(PlotOnSideGroup, self).draw(*args, **kargs)
2550 
2552 
2553  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2554  def __init__(self, *plotGroups, **kwargs):
2555  """Constructor.
2556 
2557  Arguments:
2558  plotGroups -- List of PlotGroup objects
2559 
2560  Keyword arguments
2561  loopSubFolders -- Should the subfolders be looped over? (default: True)
2562  onlyForPileup -- Plots this folder only for pileup samples
2563  onlyForElectron -- Plots this folder only for electron samples
2564  onlyForConversion -- Plots this folder only for conversion samples
2565  onlyForBHadron -- Plots this folder only for B-hadron samples
2566  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2567  page -- Optional string for the page in HTML generatin
2568  section -- Optional string for the section within a page in HTML generation
2569  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".
2570  """
2571  self._plotGroups = list(plotGroups)
2572  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
2573  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
2574  self._onlyForElectron = kwargs.pop("onlyForElectron", False)
2575  self._onlyForConversion = kwargs.pop("onlyForConversion", False)
2576  self._onlyForBHadron = kwargs.pop("onlyForBHadron", False)
2577  self._purpose = kwargs.pop("purpose", None)
2578  self._page = kwargs.pop("page", None)
2579  self._section = kwargs.pop("section", None)
2580  self._numberOfEventsHistogram = kwargs.pop("numberOfEventsHistogram", None)
2581  if len(kwargs) > 0:
2582  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
2583 
2584  def loopSubFolders(self):
2585  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2586  return self._loopSubFolders
2587 
2588  def onlyForPileup(self):
2589  """Return True if the folder is intended only for pileup samples"""
2590  return self._onlyForPileup
2591 
2592  def onlyForElectron(self):
2593  return self._onlyForElectron
2594 
2596  return self._onlyForConversion
2597 
2598  def onlyForBHadron(self):
2599  return self._onlyForBHadron
2600 
2601  def getPurpose(self):
2602  return self._purpose
2603 
2604  def getPage(self):
2605  return self._page
2606 
2607  def getSection(self):
2608  return self._section
2609 
2611  return self._numberOfEventsHistogram
2612 
2613  def append(self, plotGroup):
2614  self._plotGroups.append(plotGroup)
2615 
2616  def set(self, plotGroups):
2617  self._plotGroups = plotGroups
2618 
2619  def getPlotGroups(self):
2620  return self._plotGroups
2621 
2622  def getPlotGroup(self, name):
2623  for pg in self._plotGroups:
2624  if pg.getName() == name:
2625  return pg
2626  raise Exception("No PlotGroup named '%s'" % name)
2627 
2628  def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2629  """Create histograms from a list of TFiles.
2630 
2631  Arguments:
2632  dirsNEvents -- List of (TDirectory, nevents) pairs
2633  labels -- List of strings for legend labels corresponding the files
2634  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2635  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2636  """
2637 
2638  if len(dirsNEvents) != len(labels):
2639  raise Exception("len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2640 
2641  self._labels = labels
2642 
2643  for pg in self._plotGroups:
2644  if pg.onlyForPileup() and not isPileupSample:
2645  continue
2646  pg.create(dirsNEvents, requireAllHistograms)
2647 
2648  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2649  """Draw and save all plots using settings of a given algorithm.
2650 
2651  Arguments:
2652  prefix -- Optional string for file name prefix (default None)
2653  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2654  saveFormat -- String specifying the plot format (default '.pdf')
2655  ratio -- Add ratio to the plot (default True)
2656  directory -- Directory where to save the file (default "")
2657  """
2658  ret = []
2659 
2660  for pg in self._plotGroups:
2661  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio, directory=directory))
2662  return ret
2663 
2664 
2665  # These are to be overridden by derived classes for customisation
2666  def translateSubFolder(self, dqmSubFolderName):
2667  """Method called to (possibly) translate a subfolder name to more 'readable' form
2668 
2669  The implementation in this (base) class just returns the
2670  argument. The idea is that a deriving class might want to do
2671  something more complex (like trackingPlots.TrackingPlotFolder
2672  does)
2673  """
2674  return dqmSubFolderName
2675 
2676  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
2677  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2678  ret = ""
2679  if plotFolderName != "":
2680  ret += "_"+plotFolderName
2681  if translatedDqmSubFolder is not None:
2682  ret += "_"+translatedDqmSubFolder
2683  yield ret
2684 
2685  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
2686  """Return True if this subfolder should be processed
2687 
2688  Arguments:
2689  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2690  translatedDqmSubFolder -- Return value of translateSubFolder
2691  """
2692  return translatedDqmSubFolder in limitOnlyTo
2693 
2695  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2696  def __init__(self, subfolder, translated):
2697  self.subfolder = subfolder
2698  self.translated = translated
2699 
2700  def equalTo(self, other):
2701  """Equality is defined by the 'translated' name"""
2702  return self.translated == other.translated
2703 
2705  """Plotter for one DQM folder.
2706 
2707  This class is supposed to be instantiated by the Plotter class (or
2708  PlotterItem, to be more specific), and not used directly by the
2709  user.
2710  """
2711  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2712  """
2713  Constructor
2714 
2715  Arguments:
2716  name -- Name of the folder (is used in the output directory naming)
2717  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2718  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2719  plotFolder -- PlotFolder object
2720  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.
2721  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.
2722  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2723  """
2724  self._name = name
2725  self._possibleDqmFolders = possibleDqmFolders
2726  self._plotFolder = plotFolder
2727  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
2728  if dqmSubFolders is None:
2729  self._dqmSubFolders = None
2730  else:
2731  # Match the subfolders between files in case the lists differ
2732  # equality is by the 'translated' name
2733  subfolders = {}
2734  for sf_list in dqmSubFolders:
2735  for sf in sf_list:
2736  sf_translated = self._plotFolder.translateSubFolder(sf)
2737  if sf_translated is not None and not sf_translated in subfolders:
2738  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
2739 
2740  self._dqmSubFolders = sorted(subfolders.values(), key=lambda sf: sf.subfolder)
2741 
2742  self._fallbackNames = fallbackNames
2743  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2744  self._tableCreators = tableCreators
2745 
2746  def getName(self):
2747  return self._name
2748 
2749  def getPurpose(self):
2750  return self._plotFolder.getPurpose()
2751 
2752  def getPage(self):
2753  return self._plotFolder.getPage()
2754 
2755  def getSection(self):
2756  return self._plotFolder.getSection()
2757 
2758  def onlyForPileup(self):
2759  return self._plotFolder.onlyForPileup()
2760 
2761  def onlyForElectron(self):
2762  return self._plotFolder.onlyForElectron()
2763 
2765  return self._plotFolder.onlyForConversion()
2766 
2767  def onlyForBHadron(self):
2768  return self._plotFolder.onlyForBHadron()
2769 
2771  return self._possibleDqmFolders
2772 
2773  def getDQMSubFolders(self, limitOnlyTo=None):
2774  """Get list of subfolders, possibly limiting to some of them.
2775 
2776  Keyword arguments:
2777  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2778  """
2779 
2780  if self._dqmSubFolders is None:
2781  return [None]
2782 
2783  if limitOnlyTo is None:
2784  return self._dqmSubFolders
2785 
2786  return [s for s in self._dqmSubFolders if self._plotFolder.limitSubFolder(limitOnlyTo, s.translated)]
2787 
2788  def getTableCreators(self):
2789  return self._tableCreators
2790 
2791  def getSelectionNameIterator(self, dqmSubFolder):
2792  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2793  for name in [self._name]+self._fallbackNames:
2794  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2795  yield selname
2796 
2797  def getSelectionName(self, dqmSubFolder):
2798  return next(self.getSelectionNameIterator(dqmSubFolder))
2799 
2800  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2801  """Create histograms from a list of TFiles.
2802  Arguments:
2803  files -- List of TFiles
2804  labels -- List of strings for legend labels corresponding the files
2805  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2806  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2807  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2808  """
2809 
2810  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2811  neventsHisto = self._plotFolder.getNumberOfEventsHistogram()
2812  dirsNEvents = []
2813 
2814  for tfile in files:
2815  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2816  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2818  for fallbackFunc in self._fallbackDqmSubFolders:
2819  fallback = fallbackFunc(subfolder)
2820  if fallback is not None:
2821  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2822  if ret is not GetDirectoryCode.SubDirNotExist:
2823  break
2824  d = GetDirectoryCode.codesToNone(ret)
2825  nev = None
2826  if neventsHisto is not None and tfile is not None:
2827  hnev = _getObject(tfile, neventsHisto)
2828  if hnev is not None:
2829  nev = hnev.GetEntries()
2830  dirsNEvents.append( (d, nev) )
2831 
2832  self._plotFolder.create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2833 
2834  def draw(self, *args, **kwargs):
2835  """Draw and save all plots using settings of a given algorithm."""
2836  return self._plotFolder.draw(*args, **kwargs)
2837 
2838 
2840  """Instance of plotter that knows the directory content, holds many folders."""
2841  def __init__(self, folders):
2842  self._plotterFolders = [f for f in folders if f is not None]
2843 
2844  def iterFolders(self, limitSubFoldersOnlyTo=None):
2845  for plotterFolder in self._plotterFolders:
2846  limitOnlyTo = None
2847  if limitSubFoldersOnlyTo is not None:
2848  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2849 
2850  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2851  yield plotterFolder, dqmSubFolder
2852 
2853 # Helper for Plotter
2855  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2856  """ Constructor
2857 
2858  Arguments:
2859  name -- Name of the folder (is used in the output directory naming)
2860  possibleDirs -- List of strings for possible directories of histograms in TFiles
2861  plotFolder -- PlotFolder object
2862 
2863  Keyword arguments
2864  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.
2865  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.
2866  """
2867  self._name = name
2868  self._possibleDirs = possibleDirs
2869  self._plotFolder = plotFolder
2870  self._fallbackNames = fallbackNames
2871  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2872  self._tableCreators = []
2873 
2874  def getName(self):
2875  return self._name
2876 
2877  def getPlotFolder(self):
2878  return self._plotFolder
2879 
2880  def appendTableCreator(self, tc):
2881  self._tableCreators.append(tc)
2882 
2883  def readDirs(self, files):
2884  """Read available subfolders from the files
2885 
2886  Arguments:
2887  files -- List of strings for paths to files, or list of TFiles
2888 
2889  For each file, loop over 'possibleDirs', and read the
2890  subfolders of first one that exists.
2891 
2892  Returns a PlotterFolder if at least one file for which one of
2893  'possibleDirs' exists. Otherwise, return None to signal that
2894  there is nothing available for this PlotFolder.
2895  """
2896  subFolders = None
2897  if self._plotFolder.loopSubFolders():
2898  subFolders = []
2899  possibleDirFound = False
2900  for fname in files:
2901  if fname is None:
2902  continue
2903 
2904  isOpenFile = isinstance(fname, ROOT.TFile)
2905  if isOpenFile:
2906  tfile = fname
2907  else:
2908  tfile = ROOT.TFile.Open(fname)
2909  for pd in self._possibleDirs:
2910  d = tfile.Get(pd)
2911  if d:
2912  possibleDirFound = True
2913  if subFolders is not None:
2914  subf = []
2915  for key in d.GetListOfKeys():
2916  if isinstance(key.ReadObj(), ROOT.TDirectory):
2917  subf.append(key.GetName())
2918  subFolders.append(subf)
2919  break
2920  else:
2921  print("Did not find directory '%s' from file %s" % (pd, tfile.GetName()))
2922 
2923  if not isOpenFile:
2924  tfile.Close()
2925 
2926  if not possibleDirFound:
2927  return None
2928 
2929  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2930 
2932  def __init__(self, possibleDirs, tableCreator):
2933  self._possibleDirs = possibleDirs
2934  self._tableCreator = tableCreator
2935 
2936  def create(self, openFiles, legendLabels, dqmSubFolder):
2937  if isinstance(dqmSubFolder, list):
2938  if len(dqmSubFolder) != len(openFiles):
2939  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2940  else:
2941  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2942  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2943 
2944  tbl = []
2945  for f, sf in zip(openFiles, dqmSubFolder):
2946  data = None
2947  tdir = _getDirectory(f, self._possibleDirs, sf)
2948  if tdir is not None:
2949  data = self._tableCreator.create(tdir)
2950  tbl.append(data)
2951 
2952  # Check if we have any content
2953  allNones = True
2954  colLen = 0
2955  for col in tbl:
2956  if col is not None:
2957  allNones = False
2958  colLen = len(col)
2959  break
2960  if allNones:
2961  return None
2962 
2963  # Replace all None columns with lists of column length
2964  for i in range(len(tbl)):
2965  if tbl[i] is None:
2966  tbl[i] = [None]*colLen
2967 
2968  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2969  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2970 
2971 class Plotter:
2972  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2973  def __init__(self):
2974  self._plots = []
2975  _setStyle()
2976  ROOT.TH1.AddDirectory(False)
2977 
2978  def append(self, *args, **kwargs):
2979  """Append a plot folder to the plotter.
2980 
2981  All arguments are forwarded to the constructor of PlotterItem.
2982  """
2983  self._plots.append(PlotterItem(*args, **kwargs))
2984 
2985  def appendTable(self, attachToFolder, *args, **kwargs):
2986  for plotterItem in self._plots:
2987  if plotterItem.getName() == attachToFolder:
2988  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2989  return
2990  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2991 
2992  def clear(self):
2993  """Remove all plot folders and tables"""
2994  self._plots = []
2995 
2997  return [item.getName() for item in self._plots]
2998 
2999  def getPlotFolders(self):
3000  return [item.getPlotFolder() for item in self._plots]
3001 
3002  def getPlotFolder(self, name):
3003  for item in self._plots:
3004  if item.getName() == name:
3005  return item.getPlotFolder()
3006  raise Exception("No PlotFolder named '%s'" % name)
3007 
3008  def readDirs(self, *files):
3009  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
3010  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def remove(self, name)
Definition: plotting.py:2304
def create(self, tdirectory)
Definition: plotting.py:872
def iterSelectionName(self, plotFolderName, translatedDqmSubFolder)
Definition: plotting.py:2676
def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None)
Definition: plotting.py:1534
def getName(self)
Definition: plotting.py:1680
def onlyForPileup(self)
Definition: plotting.py:2588
def __init__(self)
Definition: plotting.py:1677
def append(self, plotGroup)
Definition: plotting.py:2613
def getName(self)
Definition: plotting.py:2298
def _setStyle()
Definition: plotting.py:21
def _getYmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:389
def loopSubFolders(self)
Definition: plotting.py:2584
def _copyStyle(src, dst)
Definition: plotting.py:1663
def __init__(self, name, plots, kwargs)
Definition: plotting.py:2253
def __init__(self, name, nameA, nameB, title="")
Definition: plotting.py:763
def getPossibleDQMFolders(self)
Definition: plotting.py:2770
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1472
def _findBoundsY(th1s, ylog, ymin=None, ymax=None, coverage=None, coverageRange=None)
Definition: plotting.py:527
def set(self, plotGroups)
Definition: plotting.py:2616
def setXTitle(self, title)
Definition: plotting.py:1388
def setLogx(self, log)
Definition: plotting.py:1352
def readDirs(self, files)
Definition: plotting.py:3008
def setName(self, name)
Definition: plotting.py:1863
def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory="")
Definition: plotting.py:2337
def setXLabelSize(self, size)
Definition: plotting.py:1492
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:1466
def _getOrCreateObject(tdirectory, nameOrCreator)
Definition: plotting.py:57
def iterFolders(self, limitSubFoldersOnlyTo=None)
Definition: plotting.py:2844
def redrawAxis(self)
Definition: plotting.py:1414
def setYTitleSize(self, size)
Definition: plotting.py:1498
def setXLabelSize(self, size)
Definition: plotting.py:1397
def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None)
Definition: plotting.py:459
def _modifyPadForRatio(self, pad)
Definition: plotting.py:2477
def Draw(self, options="")
Definition: plotting.py:1647
def equalTo(self, other)
Definition: plotting.py:2700
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:1279
def addText(self, text)
Definition: plotting.py:1613
def __init__(self, possibleDirs, tableCreator)
Definition: plotting.py:2932
def getTableCreators(self)
Definition: plotting.py:2788
def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, kwargs)
Definition: plotting.py:1585
def _getYmin(obj, limitToNonZeroContent=False)
Definition: plotting.py:374
def create(self, args, kwargs)
Definition: plotting.py:1686
def setXTitleSize(self, size)
Definition: plotting.py:1291
def create(self, tdirectory)
Definition: plotting.py:1143
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1367
def clear(self)
Definition: plotting.py:2311
def setXLabelSize(self, size)
Definition: plotting.py:1297
def _getYminIgnoreOutlier(th1)
Definition: plotting.py:407
def append(self, args, kwargs)
Definition: plotting.py:2978
def getDQMSubFolders(self, limitOnlyTo=None)
Definition: plotting.py:2773
def _getXmax(obj, limitToNonZeroContent=False)
Definition: plotting.py:358
def append(self, plot)
Definition: plotting.py:2314
def setGridx(self, grid)
Definition: plotting.py:1359
def setLogy(self, log)
Definition: plotting.py:1463
def setGridy(self, grid)
Definition: plotting.py:1270
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:1683
def setZTitle(self, title)
Definition: plotting.py:1504
def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor)
Definition: plotting.py:1434
def setZTitleOffset(self, offset)
Definition: plotting.py:1507
def setXTitleSize(self, size)
Definition: plotting.py:1486
def __init__(self, pad, bounds, zmax, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ratioYTitle=_ratioYTitle)
Definition: plotting.py:1314
def getNumberOfHistograms(self)
Definition: plotting.py:1844
def create(self, tdirectoryNEvents, requireAllHistograms=False)
Definition: plotting.py:2327
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:1621
def __init__(self, name, kwargs)
Definition: plotting.py:1697
def getPlotGroups(self)
Definition: plotting.py:2619
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:2936
def _calculateRatios(histos, ratioUncertainty=False)
Definition: plotting.py:148
def appendTable(self, attachToFolder, args, kwargs)
Definition: plotting.py:2985
def setYTitleOffset(self, offset)
Definition: plotting.py:1306
def setYTitle(self, title)
Definition: plotting.py:1400
def clear(self)
Definition: plotting.py:2992
def create(self, tdirectory)
Definition: plotting.py:828
def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory="")
Definition: plotting.py:2497
def setYTitleSize(self, size)
Definition: plotting.py:1303
def onlyForElectron(self)
Definition: plotting.py:2592
def draw(name, histos, styles=_defaultStyles, legendLabels=[], kwargs)
def _setStats(self, histos, startingX, startingY)
Definition: plotting.py:1942
def setLogy(self, log)
Definition: plotting.py:1264
def getSelectionName(self, dqmSubFolder)
Definition: plotting.py:2797
def _getDirectory(args, kwargs)
Definition: plotting.py:97
def setYTitle(self, title)
Definition: plotting.py:1300
def Draw(self, options=None)
Definition: plotting.py:1572
def draw(self, pad, ratio, ratioFactor, nrows)
Definition: plotting.py:1997
Definition: style.py:1
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
def setTitle(self, title)
Definition: plotting.py:1385
def setXTitle(self, title)
Definition: plotting.py:1288
def getPlots(self)
Definition: plotting.py:2301
def create(self, tdirectory)
Definition: plotting.py:786
def isRatio(self, ratio)
Definition: plotting.py:1858
def setGridx(self, grid)
Definition: plotting.py:1267
def readDirs(self, files)
Definition: plotting.py:2883
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:2877
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def adjustMarginRight(self, adjust)
Definition: plotting.py:1476
def adjustMarginLeft(self, adjust)
Definition: plotting.py:1273
def onlyForPileup(self)
Definition: plotting.py:2323
def getPlot(self, name)
Definition: plotting.py:2317
def setProperties(self, kwargs)
Definition: plotting.py:2292
def __str__(self)
Definition: plotting.py:782
def addToLegend(self, legend, legendLabels, denomUncertainty)
Definition: plotting.py:2230
def _getYmaxWithError(th1)
Definition: plotting.py:404
def setXTitleOffset(self, offset)
Definition: plotting.py:1294
def _getDirectoryDetailed(tfile, possibleDirs, subDir=None)
Definition: plotting.py:73
def setXTitleOffset(self, size)
Definition: plotting.py:1489
def draw(self, args, kwargs)
Definition: plotting.py:2546
def create(self, tdirectory)
Definition: plotting.py:988
def onlyForConversion(self)
Definition: plotting.py:2595
def _getYminMaxAroundMedian(obj, coverage, coverageRange=None)
Definition: plotting.py:423
def getPlotFolder(self, name)
Definition: plotting.py:3002
def setYTitleOffset(self, offset)
Definition: plotting.py:1501
def setLogy(self, log)
Definition: plotting.py:1356
def __init__(self, name, histo, title="")
Definition: plotting.py:905
def __str__(self)
Definition: plotting.py:824
def onlyForConversion(self)
Definition: plotting.py:2764
def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder)
Definition: plotting.py:2685
def setXTitleSize(self, size)
Definition: plotting.py:1391
def setYTitleRatio(self, title)
Definition: plotting.py:1403
def drawRatioUncertainty(self)
Definition: plotting.py:1874
static std::string join(char **cmd)
Definition: RemoteFile.cc:19
def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False)
Definition: plotting.py:2628
def setGridy(self, grid)
Definition: plotting.py:1469
def getNumberOfHistograms(self)
Definition: plotting.py:1692
def __init__(self, name, assoc, dup, reco, title="")
Definition: plotting.py:848
def onlyForBHadron(self)
Definition: plotting.py:2767
def adjustMarginRight(self, adjust)
Definition: plotting.py:1376
def getPlotFolders(self)
Definition: plotting.py:2999
def __init__(self, subfolder, translated)
Definition: plotting.py:2696
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:1394
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:1984
def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True)
Definition: plotting.py:2481
def translateSubFolder(self, dqmSubFolderName)
Definition: plotting.py:2666
def __str__(self)
Definition: plotting.py:1139
def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory)
Definition: plotting.py:2415
def __init__(self, folders)
Definition: plotting.py:2841
def setTitle(self, title)
Definition: plotting.py:1285
def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators)
Definition: plotting.py:2711
def setXTitle(self, title)
Definition: plotting.py:1483
def setGridy(self, grid)
Definition: plotting.py:1363
def setYTitleSize(self, size)
Definition: plotting.py:1406
def setYTitle(self, title)
Definition: plotting.py:1495
def _th1RemoveEmptyBins(histos, xbinlabels)
Definition: plotting.py:607
#define update(a, b)
def onlyForBHadron(self)
Definition: plotting.py:2598
def __init__(self)
Definition: plotting.py:2973
def isEmpty(self)
Definition: plotting.py:1848
def _getXmin(obj, limitToNonZeroContent=False)
Definition: plotting.py:342
def onlyForElectron(self)
Definition: plotting.py:2761
def append(self, args, kwargs)
Definition: plotting.py:2535
def create(self, tdirNEvents, requireAllHistograms=False)
Definition: plotting.py:1892
def getPlotGroup(self, name)
Definition: plotting.py:2622
def getPage(self)
Definition: plotting.py:2604
def appendTableCreator(self, tc)
Definition: plotting.py:2880
def isTGraph2D(self)
Definition: plotting.py:1852
def _modifyPadForRatio(pad, ratioFactor)
Definition: plotting.py:119
def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False)
Definition: plotting.py:2800
def create(self, tdirectoryNEvents, requireAllHistograms=False)
Definition: plotting.py:2538
def _createCanvas(name, width, height)
Definition: plotting.py:109
def setYTitleOffset(self, offset)
Definition: plotting.py:1410
def redrawAxis(self)
Definition: plotting.py:1309
auto wrap(F iFunc) -> decltype(iFunc())
def setLogx(self, log)
Definition: plotting.py:1261
def onlyForPileup(self)
Definition: plotting.py:2758
def clone(self, kwargs)
Definition: plotting.py:1837
def __init__(self, name, plot, ncols=2, onlyForPileup=False)
Definition: plotting.py:2530
def getPlotFolderNames(self)
Definition: plotting.py:2996
def getNumberOfEventsHistogram(self)
Definition: plotting.py:2610
def _mergeBinLabels(labelsAll)
Definition: plotting.py:713
def getPurpose(self)
Definition: plotting.py:2601
def __init__(self, plotGroups, kwargs)
Definition: plotting.py:2554
#define str(s)
def setLogx(self, log)
Definition: plotting.py:1460
def draw(self, args, kwargs)
Definition: plotting.py:2834
def _th1ToOrderedDict(th1, renameBin=None)
Definition: plotting.py:100
def isEmpty(self)
Definition: plotting.py:1689
def _th2RemoveEmptyBins(histos, xbinlabels, ybinlabels)
Definition: plotting.py:647
def getSelectionNameIterator(self, dqmSubFolder)
Definition: plotting.py:2791
def getName(self)
Definition: plotting.py:1866
def create(self, tdirectory)
Definition: plotting.py:1095
def setProperties(self, kwargs)
Definition: plotting.py:1831
def _mergeBinLabelsX(histos)
Definition: plotting.py:707
def _createOne(self, name, index, tdir, nevents)
Definition: plotting.py:1878
def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory="")
Definition: plotting.py:2648
def setTitle(self, title)
Definition: plotting.py:1480
def _th1IncludeOnlyBins(histos, xbinlabels)
Definition: plotting.py:744
def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[])
Definition: plotting.py:2855
def _getObject(tdirectory, name)
Definition: plotting.py:49
def getSection(self)
Definition: plotting.py:2607