CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
2251 class PlotGroup(object):
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  # Setup legend
2386  canvas.cd()
2387  if len(self._plots) <= 4:
2388  lx1 = 0.2
2389  lx2 = 0.9
2390  ly1 = 0.48
2391  ly2 = 0.53
2392  else:
2393  lx1 = 0.1
2394  lx2 = 0.9
2395  ly1 = 0.64
2396  ly2 = 0.67
2397  if self._legendDx is not None:
2398  lx1 += self._legendDx
2399  lx2 += self._legendDx
2400  if self._legendDy is not None:
2401  ly1 += self._legendDy
2402  ly2 += self._legendDy
2403  if self._legendDw is not None:
2404  lx2 += self._legendDw
2405  if self._legendDh is not None:
2406  ly1 -= self._legendDh
2407  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
2408  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
2409  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2410  denomUncertainty=(ratio and denomUnc))
2411 
2412  return self._save(canvas, saveFormat, prefix=prefix, directory=directory)
2413 
2414  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio, directory):
2415  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2416  width = 500
2417  height = 500
2418 
2419  lx1def = 0.6
2420  lx2def = 0.95
2421  ly1def = 0.85
2422  ly2def = 0.95
2423 
2424  ret = []
2425 
2426  for plot in self._plots:
2427  if plot.isEmpty():
2428  continue
2429 
2430  canvas = _createCanvas(self._name+"Single", width, height)
2431  canvasRatio = _createCanvas(self._name+"SingleRatio", width, int(height*self._ratioFactor))
2432 
2433  # from TDRStyle
2434  for c in [canvas, canvasRatio]:
2435  c.SetTopMargin(0.05)
2436  c.SetBottomMargin(0.13)
2437  c.SetLeftMargin(0.16)
2438  c.SetRightMargin(0.05)
2439 
2440  ratioForThisPlot = plot.isRatio(ratio)
2441  c = canvas
2442  if ratioForThisPlot:
2443  c = canvasRatio
2444  c.cd()
2445  self._modifyPadForRatio(c)
2446 
2447  # Draw plot to canvas
2448  c.cd()
2449  plot.draw(c, ratioForThisPlot, self._ratioFactor, 1)
2450 
2451  if plot._legend:
2452  # Setup legend
2453  lx1 = lx1def
2454  lx2 = lx2def
2455  ly1 = ly1def
2456  ly2 = ly2def
2457 
2458  if plot._legendDx is not None:
2459  lx1 += plot._legendDx
2460  lx2 += plot._legendDx
2461  if plot._legendDy is not None:
2462  ly1 += plot._legendDy
2463  ly2 += plot._legendDy
2464  if plot._legendDw is not None:
2465  lx2 += plot._legendDw
2466  if plot._legendDh is not None:
2467  ly1 -= plot._legendDh
2468 
2469  c.cd()
2470  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2471  denomUncertainty=(ratioForThisPlot and plot.drawRatioUncertainty))
2472 
2473  ret.extend(self._save(c, saveFormat, prefix=prefix, postfix="/"+plot.getName(), single=True, directory=directory))
2474  return ret
2475 
2476  def _modifyPadForRatio(self, pad):
2477  """Internal method to set divide a pad to two for ratio plots"""
2478  _modifyPadForRatio(pad, self._ratioFactor)
2479 
2480  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2481  if not self._legend:
2482  return None
2483 
2484  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2485  l.SetTextSize(textSize)
2486  l.SetLineColor(1)
2487  l.SetLineWidth(1)
2488  l.SetLineStyle(1)
2489  l.SetFillColor(0)
2490  l.SetMargin(0.07)
2491 
2492  plot.addToLegend(l, legendLabels, denomUncertainty)
2493  l.Draw()
2494  return l
2495 
2496  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory=""):
2497  # Save the canvas to file and clear
2498  name = self._name
2499  if not os.path.exists(directory+'/'+name):
2500  os.makedirs(directory+'/'+name, exist_ok=True)
2501  if prefix is not None:
2502  name = prefix+name
2503  if postfix is not None:
2504  name = name+postfix
2505  name = os.path.join(directory, name)
2506 
2507  if not verbose: # silence saved file printout
2508  backup = ROOT.gErrorIgnoreLevel
2509  ROOT.gErrorIgnoreLevel = ROOT.kWarning
2510  canvas.SaveAs(name+saveFormat)
2511  if not verbose:
2512  ROOT.gErrorIgnoreLevel = backup
2513 
2514  if single:
2515  canvas.Clear()
2516  canvas.SetLogx(False)
2517  canvas.SetLogy(False)
2518  else:
2519  canvas.Clear("D") # keep subpads
2520 
2521  return [name+saveFormat]
2522 
2524  """Resembles DQM GUI's "On side" layout.
2525 
2526  Like PlotGroup, but has only a description of a single plot. The
2527  plot is drawn separately for each file. Useful for 2D histograms."""
2528 
2529  def __init__(self, name, plot, ncols=2, onlyForPileup=False):
2530  super(PlotOnSideGroup, self).__init__(name, [], ncols=ncols, legend=False, onlyForPileup=onlyForPileup)
2531  self._plot = plot
2532  self._plot.setProperties(ratio=False)
2533 
2534  def append(self, *args, **kwargs):
2535  raise Exception("PlotOnSideGroup.append() is not implemented")
2536 
2537  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2538  self._plots = []
2539  for i, element in enumerate(tdirectoryNEvents):
2540  pl = self._plot.clone()
2541  pl.create([element], requireAllHistograms)
2542  pl.setName(pl.getName()+"_"+str(i))
2543  self._plots.append(pl)
2544 
2545  def draw(self, *args, **kwargs):
2546  kargs = copy.copy(kwargs)
2547  kargs["ratio"] = False
2548  return super(PlotOnSideGroup, self).draw(*args, **kargs)
2549 
2551 
2552  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2553  def __init__(self, *plotGroups, **kwargs):
2554  """Constructor.
2555 
2556  Arguments:
2557  plotGroups -- List of PlotGroup objects
2558 
2559  Keyword arguments
2560  loopSubFolders -- Should the subfolders be looped over? (default: True)
2561  onlyForPileup -- Plots this folder only for pileup samples
2562  onlyForElectron -- Plots this folder only for electron samples
2563  onlyForConversion -- Plots this folder only for conversion samples
2564  onlyForBHadron -- Plots this folder only for B-hadron samples
2565  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2566  page -- Optional string for the page in HTML generatin
2567  section -- Optional string for the section within a page in HTML generation
2568  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".
2569  """
2570  self._plotGroups = list(plotGroups)
2571  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
2572  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
2573  self._onlyForElectron = kwargs.pop("onlyForElectron", False)
2574  self._onlyForConversion = kwargs.pop("onlyForConversion", False)
2575  self._onlyForBHadron = kwargs.pop("onlyForBHadron", False)
2576  self._purpose = kwargs.pop("purpose", None)
2577  self._page = kwargs.pop("page", None)
2578  self._section = kwargs.pop("section", None)
2579  self._numberOfEventsHistogram = kwargs.pop("numberOfEventsHistogram", None)
2580  if len(kwargs) > 0:
2581  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
2582 
2583  def loopSubFolders(self):
2584  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2585  return self._loopSubFolders
2586 
2587  def onlyForPileup(self):
2588  """Return True if the folder is intended only for pileup samples"""
2589  return self._onlyForPileup
2590 
2591  def onlyForElectron(self):
2592  return self._onlyForElectron
2593 
2595  return self._onlyForConversion
2596 
2597  def onlyForBHadron(self):
2598  return self._onlyForBHadron
2599 
2600  def getPurpose(self):
2601  return self._purpose
2602 
2603  def getPage(self):
2604  return self._page
2605 
2606  def getSection(self):
2607  return self._section
2608 
2610  return self._numberOfEventsHistogram
2611 
2612  def append(self, plotGroup):
2613  self._plotGroups.append(plotGroup)
2614 
2615  def set(self, plotGroups):
2616  self._plotGroups = plotGroups
2617 
2618  def getPlotGroups(self):
2619  return self._plotGroups
2620 
2621  def getPlotGroup(self, name):
2622  for pg in self._plotGroups:
2623  if pg.getName() == name:
2624  return pg
2625  raise Exception("No PlotGroup named '%s'" % name)
2626 
2627  def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2628  """Create histograms from a list of TFiles.
2629 
2630  Arguments:
2631  dirsNEvents -- List of (TDirectory, nevents) pairs
2632  labels -- List of strings for legend labels corresponding the files
2633  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2634  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2635  """
2636 
2637  if len(dirsNEvents) != len(labels):
2638  raise Exception("len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2639 
2640  self._labels = labels
2641 
2642  for pg in self._plotGroups:
2643  if pg.onlyForPileup() and not isPileupSample:
2644  continue
2645  pg.create(dirsNEvents, requireAllHistograms)
2646 
2647  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2648  """Draw and save all plots using settings of a given algorithm.
2649 
2650  Arguments:
2651  prefix -- Optional string for file name prefix (default None)
2652  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2653  saveFormat -- String specifying the plot format (default '.pdf')
2654  ratio -- Add ratio to the plot (default True)
2655  directory -- Directory where to save the file (default "")
2656  """
2657  ret = []
2658 
2659  for pg in self._plotGroups:
2660  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio, directory=directory))
2661  return ret
2662 
2663 
2664  # These are to be overridden by derived classes for customisation
2665  def translateSubFolder(self, dqmSubFolderName):
2666  """Method called to (possibly) translate a subfolder name to more 'readable' form
2667 
2668  The implementation in this (base) class just returns the
2669  argument. The idea is that a deriving class might want to do
2670  something more complex (like trackingPlots.TrackingPlotFolder
2671  does)
2672  """
2673  return dqmSubFolderName
2674 
2675  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
2676  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2677  ret = ""
2678  if plotFolderName != "":
2679  ret += "_"+plotFolderName
2680  if translatedDqmSubFolder is not None:
2681  ret += "_"+translatedDqmSubFolder
2682  yield ret
2683 
2684  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
2685  """Return True if this subfolder should be processed
2686 
2687  Arguments:
2688  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2689  translatedDqmSubFolder -- Return value of translateSubFolder
2690  """
2691  return translatedDqmSubFolder in limitOnlyTo
2692 
2694  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2695  def __init__(self, subfolder, translated):
2696  self.subfolder = subfolder
2697  self.translated = translated
2698 
2699  def equalTo(self, other):
2700  """Equality is defined by the 'translated' name"""
2701  return self.translated == other.translated
2702 
2704  """Plotter for one DQM folder.
2705 
2706  This class is supposed to be instantiated by the Plotter class (or
2707  PlotterItem, to be more specific), and not used directly by the
2708  user.
2709  """
2710  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2711  """
2712  Constructor
2713 
2714  Arguments:
2715  name -- Name of the folder (is used in the output directory naming)
2716  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2717  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2718  plotFolder -- PlotFolder object
2719  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.
2720  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.
2721  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2722  """
2723  self._name = name
2724  self._possibleDqmFolders = possibleDqmFolders
2725  self._plotFolder = plotFolder
2726  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
2727  if dqmSubFolders is None:
2728  self._dqmSubFolders = None
2729  else:
2730  # Match the subfolders between files in case the lists differ
2731  # equality is by the 'translated' name
2732  subfolders = {}
2733  for sf_list in dqmSubFolders:
2734  for sf in sf_list:
2735  sf_translated = self._plotFolder.translateSubFolder(sf)
2736  if sf_translated is not None and not sf_translated in subfolders:
2737  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
2738 
2739  self._dqmSubFolders = sorted(subfolders.values(), key=lambda sf: sf.subfolder)
2740 
2741  self._fallbackNames = fallbackNames
2742  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2743  self._tableCreators = tableCreators
2744 
2745  def getName(self):
2746  return self._name
2747 
2748  def getPurpose(self):
2749  return self._plotFolder.getPurpose()
2750 
2751  def getPage(self):
2752  return self._plotFolder.getPage()
2753 
2754  def getSection(self):
2755  return self._plotFolder.getSection()
2756 
2757  def onlyForPileup(self):
2758  return self._plotFolder.onlyForPileup()
2759 
2760  def onlyForElectron(self):
2761  return self._plotFolder.onlyForElectron()
2762 
2764  return self._plotFolder.onlyForConversion()
2765 
2766  def onlyForBHadron(self):
2767  return self._plotFolder.onlyForBHadron()
2768 
2770  return self._possibleDqmFolders
2771 
2772  def getDQMSubFolders(self, limitOnlyTo=None):
2773  """Get list of subfolders, possibly limiting to some of them.
2774 
2775  Keyword arguments:
2776  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2777  """
2778 
2779  if self._dqmSubFolders is None:
2780  return [None]
2781 
2782  if limitOnlyTo is None:
2783  return self._dqmSubFolders
2784 
2785  return [s for s in self._dqmSubFolders if self._plotFolder.limitSubFolder(limitOnlyTo, s.translated)]
2786 
2787  def getTableCreators(self):
2788  return self._tableCreators
2789 
2790  def getSelectionNameIterator(self, dqmSubFolder):
2791  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2792  for name in [self._name]+self._fallbackNames:
2793  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2794  yield selname
2795 
2796  def getSelectionName(self, dqmSubFolder):
2797  return next(self.getSelectionNameIterator(dqmSubFolder))
2798 
2799  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2800  """Create histograms from a list of TFiles.
2801  Arguments:
2802  files -- List of TFiles
2803  labels -- List of strings for legend labels corresponding the files
2804  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2805  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2806  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2807  """
2808 
2809  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2810  neventsHisto = self._plotFolder.getNumberOfEventsHistogram()
2811  dirsNEvents = []
2812 
2813  for tfile in files:
2814  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2815  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2817  for fallbackFunc in self._fallbackDqmSubFolders:
2818  fallback = fallbackFunc(subfolder)
2819  if fallback is not None:
2820  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2821  if ret is not GetDirectoryCode.SubDirNotExist:
2822  break
2823  d = GetDirectoryCode.codesToNone(ret)
2824  nev = None
2825  if neventsHisto is not None and tfile is not None:
2826  hnev = _getObject(tfile, neventsHisto)
2827  if hnev is not None:
2828  nev = hnev.GetEntries()
2829  dirsNEvents.append( (d, nev) )
2830 
2831  self._plotFolder.create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2832 
2833  def draw(self, *args, **kwargs):
2834  """Draw and save all plots using settings of a given algorithm."""
2835  return self._plotFolder.draw(*args, **kwargs)
2836 
2837 
2839  """Instance of plotter that knows the directory content, holds many folders."""
2840  def __init__(self, folders):
2841  self._plotterFolders = [f for f in folders if f is not None]
2842 
2843  def iterFolders(self, limitSubFoldersOnlyTo=None):
2844  for plotterFolder in self._plotterFolders:
2845  limitOnlyTo = None
2846  if limitSubFoldersOnlyTo is not None:
2847  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2848 
2849  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2850  yield plotterFolder, dqmSubFolder
2851 
2852 # Helper for Plotter
2854  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2855  """ Constructor
2856 
2857  Arguments:
2858  name -- Name of the folder (is used in the output directory naming)
2859  possibleDirs -- List of strings for possible directories of histograms in TFiles
2860  plotFolder -- PlotFolder object
2861 
2862  Keyword arguments
2863  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.
2864  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.
2865  """
2866  self._name = name
2867  self._possibleDirs = possibleDirs
2868  self._plotFolder = plotFolder
2869  self._fallbackNames = fallbackNames
2870  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2871  self._tableCreators = []
2872 
2873  def getName(self):
2874  return self._name
2875 
2876  def getPlotFolder(self):
2877  return self._plotFolder
2878 
2879  def appendTableCreator(self, tc):
2880  self._tableCreators.append(tc)
2881 
2882  def readDirs(self, files):
2883  """Read available subfolders from the files
2884 
2885  Arguments:
2886  files -- List of strings for paths to files, or list of TFiles
2887 
2888  For each file, loop over 'possibleDirs', and read the
2889  subfolders of first one that exists.
2890 
2891  Returns a PlotterFolder if at least one file for which one of
2892  'possibleDirs' exists. Otherwise, return None to signal that
2893  there is nothing available for this PlotFolder.
2894  """
2895  subFolders = None
2896  if self._plotFolder.loopSubFolders():
2897  subFolders = []
2898  possibleDirFound = False
2899  for fname in files:
2900  if fname is None:
2901  continue
2902 
2903  isOpenFile = isinstance(fname, ROOT.TFile)
2904  if isOpenFile:
2905  tfile = fname
2906  else:
2907  tfile = ROOT.TFile.Open(fname)
2908  for pd in self._possibleDirs:
2909  d = tfile.Get(pd)
2910  if d:
2911  possibleDirFound = True
2912  if subFolders is not None:
2913  subf = []
2914  for key in d.GetListOfKeys():
2915  if isinstance(key.ReadObj(), ROOT.TDirectory):
2916  subf.append(key.GetName())
2917  subFolders.append(subf)
2918  break
2919  else:
2920  print("Did not find directory '%s' from file %s" % (pd, tfile.GetName()))
2921 
2922  if not isOpenFile:
2923  tfile.Close()
2924 
2925  if not possibleDirFound:
2926  return None
2927 
2928  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2929 
2931  def __init__(self, possibleDirs, tableCreator):
2932  self._possibleDirs = possibleDirs
2933  self._tableCreator = tableCreator
2934 
2935  def create(self, openFiles, legendLabels, dqmSubFolder):
2936  if isinstance(dqmSubFolder, list):
2937  if len(dqmSubFolder) != len(openFiles):
2938  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2939  else:
2940  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2941  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2942 
2943  tbl = []
2944  for f, sf in zip(openFiles, dqmSubFolder):
2945  data = None
2946  tdir = _getDirectory(f, self._possibleDirs, sf)
2947  if tdir is not None:
2948  data = self._tableCreator.create(tdir)
2949  tbl.append(data)
2950 
2951  # Check if we have any content
2952  allNones = True
2953  colLen = 0
2954  for col in tbl:
2955  if col is not None:
2956  allNones = False
2957  colLen = len(col)
2958  break
2959  if allNones:
2960  return None
2961 
2962  # Replace all None columns with lists of column length
2963  for i in range(len(tbl)):
2964  if tbl[i] is None:
2965  tbl[i] = [None]*colLen
2966 
2967  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2968  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2969 
2970 class Plotter:
2971  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2972  def __init__(self):
2973  self._plots = []
2974  _setStyle()
2975  ROOT.TH1.AddDirectory(False)
2976 
2977  def append(self, *args, **kwargs):
2978  """Append a plot folder to the plotter.
2979 
2980  All arguments are forwarded to the constructor of PlotterItem.
2981  """
2982  self._plots.append(PlotterItem(*args, **kwargs))
2983 
2984  def appendTable(self, attachToFolder, *args, **kwargs):
2985  for plotterItem in self._plots:
2986  if plotterItem.getName() == attachToFolder:
2987  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2988  return
2989  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2990 
2991  def clear(self):
2992  """Remove all plot folders and tables"""
2993  self._plots = []
2994 
2996  return [item.getName() for item in self._plots]
2997 
2998  def getPlotFolders(self):
2999  return [item.getPlotFolder() for item in self._plots]
3000 
3001  def getPlotFolder(self, name):
3002  for item in self._plots:
3003  if item.getName() == name:
3004  return item.getPlotFolder()
3005  raise Exception("No PlotFolder named '%s'" % name)
3006 
3007  def readDirs(self, *files):
3008  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
3009  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def _getYmin
Definition: plotting.py:374
def _getYminMaxAroundMedian
Definition: plotting.py:423
def _getXmax
Definition: plotting.py:358
def _th1IncludeOnlyBins
Definition: plotting.py:744
def _getYmaxWithError
Definition: plotting.py:404
def _th2RemoveEmptyBins
Definition: plotting.py:647
def setXTitleOffset
Definition: plotting.py:1294
def _getYminIgnoreOutlier
Definition: plotting.py:407
def _getXmin
Definition: plotting.py:342
def _mergeBinLabelsY
Definition: plotting.py:710
def _th1ToOrderedDict
Definition: plotting.py:100
def _th1RemoveEmptyBins
Definition: plotting.py:607
def _getYmax
Definition: plotting.py:389
def _createCanvas
Definition: plotting.py:109
bool equal(const T &first, const T &second)
Definition: Equal.h:32
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
const uint16_t range(const Frame &aFrame)
def _mergeBinLabels
Definition: plotting.py:713
def adjustMarginLeft
Definition: plotting.py:1273
def _findBounds
Definition: plotting.py:459
def _drawFrame
Definition: plotting.py:1187
Definition: style.py:1
def drawRatioUncertainty
Definition: plotting.py:1874
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def _mergeBinLabelsX
Definition: plotting.py:707
T min(T a, T b)
Definition: MathUtil.h:58
def adjustMarginRight
Definition: plotting.py:1279
def _getObject
Definition: plotting.py:49
def _modifyPadForRatio
Definition: plotting.py:119
static std::string join(char **cmd)
Definition: RemoteFile.cc:19
def _getDirectory
Definition: plotting.py:97
def getNumberOfHistograms
Definition: plotting.py:1844
def _getOrCreateObject
Definition: plotting.py:57
def _copyStyle
Definition: plotting.py:1663
string end
Definition: dataset.py:937
def _setStyle
Definition: plotting.py:21
def getNumberOfEventsHistogram
Definition: plotting.py:2609
def setYTitleOffset
Definition: plotting.py:1306
def _getDirectoryDetailed
Definition: plotting.py:73
auto wrap(F iFunc) -> decltype(iFunc())
def _findBoundsY
Definition: plotting.py:527
#define str(s)
def setProperties
Definition: plotting.py:1831
def getPlotFolderNames
Definition: plotting.py:2995
def _calculateRatios
Definition: plotting.py:148