CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
plotting.py
Go to the documentation of this file.
1 import sys
2 import math
3 import array
4 
5 import ROOT
6 ROOT.gROOT.SetBatch(True)
7 ROOT.PyConfig.IgnoreCommandLineOptions = True
8 
9 def _getObject(tdirectory, name):
10  obj = tdirectory.Get(name)
11  if not obj:
12  print "Did not find {obj} from {dir}".format(obj=name, dir=tdirectory.GetPath())
13  return None
14  return obj
15 
16 def _getOrCreateObject(tdirectory, nameOrCreator):
17  if hasattr(nameOrCreator, "create"):
18  return nameOrCreator.create(tdirectory)
19  return _getObject(tdirectory, nameOrCreator)
20 
21 def _getXmin(obj):
22  if isinstance(obj, ROOT.TH1):
23  xaxis = obj.GetXaxis()
24  return xaxis.GetBinLowEdge(xaxis.GetFirst())
25  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
26  return min([obj.GetX()[i] for i in xrange(0, obj.GetN())])*0.9
27  raise Exception("Unsupported type %s" % str(obj))
28 
29 def _getXmax(obj):
30  if isinstance(obj, ROOT.TH1):
31  xaxis = obj.GetXaxis()
32  return xaxis.GetBinUpEdge(xaxis.GetLast())
33  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
34  return max([obj.GetX()[i] for i in xrange(0, obj.GetN())])*1.02
35  raise Exception("Unsupported type %s" % str(obj))
36 
37 def _getYmin(obj):
38  if isinstance(obj, ROOT.TH1):
39  return obj.GetMinimum()
40  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
41  return min([obj.GetY()[i] for i in xrange(0, obj.GetN())])
42  raise Exception("Unsupported type %s" % str(obj))
43 
44 def _getYmax(obj):
45  if isinstance(obj, ROOT.TH1):
46  return obj.GetMaximum()
47  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
48  return max([obj.GetY()[i] for i in xrange(0, obj.GetN())])
49  raise Exception("Unsupported type %s" % str(obj))
50 
52  return max([th1.GetBinContent(i)+th1.GetBinError(i) for i in xrange(1, th1.GetNbinsX()+1)])
53 
55  yvals = filter(lambda n: n>0, [th1.GetBinContent(i) for i in xrange(1, th1.GetNbinsX()+1)])
56  yvals.sort()
57  if len(yvals) == 0:
58  return th1.GetMinimum()
59  if len(yvals) == 1:
60  return yvals[0]
61 
62  # Define outlier as being x10 less than minimum of the 95 % of the non-zero largest values
63  ind_min = len(yvals)-1 - int(len(yvals)*0.95)
64  min_val = yvals[ind_min]
65  for i in xrange(0, ind_min):
66  if yvals[i] > 0.1*min_val:
67  return yvals[i]
68 
69  return min_val
70 
71 def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None):
72  """Find x-y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
73 
74  Arguments:
75  th1s -- List of TH1s
76  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
77 
78  Keyword arguments:
79  xmin -- Minimum x value; if None, take the minimum of TH1s
80  xmax -- Maximum x value; if None, take the maximum of TH1s
81  xmin -- Minimum y value; if None, take the minimum of TH1s
82  xmax -- Maximum y value; if None, take the maximum of TH1s
83  """
84 
85  def y_scale_max(y):
86  if ylog:
87  return 1.5*y
88  return 1.05*y
89 
90  def y_scale_min(y):
91  # assuming log
92  return 0.9*y
93 
94  if xmin is None or xmax is None or ymin is None or ymax is None or isinstance(ymin, list) or isinstance(ymax, list):
95  xmins = []
96  xmaxs = []
97  ymins = []
98  ymaxs = []
99  for th1 in th1s:
100  xmins.append(_getXmin(th1))
101  xmaxs.append(_getXmax(th1))
102  if ylog and isinstance(ymin, list):
103  ymins.append(_getYminIgnoreOutlier(th1))
104  else:
105  ymins.append(_getYmin(th1))
106  ymaxs.append(_getYmax(th1))
107 # ymaxs.append(_getYmaxWithError(th1))
108 
109  if xmin is None:
110  xmin = min(xmins)
111  if xmax is None:
112  xmax = max(xmaxs)
113  if ymin is None:
114  ymin = min(ymins)
115  elif isinstance(ymin, list):
116  ym_unscaled = min(ymins)
117  ym = y_scale_min(ym_unscaled)
118  ymins_below = filter(lambda y: y<=ym, ymin)
119  if len(ymins_below) == 0:
120  ymin = min(ymin)
121  if ym_unscaled < ymin:
122  print "Histogram minimum y %f is below all given ymin values %s, using the smallest one" % (ym, str(ymin))
123  else:
124  ymin = max(ymins_below)
125 
126  if ymax is None:
127  ymax = y_scale_max(max(ymaxs))
128  elif isinstance(ymax, list):
129  ym_unscaled = max(ymaxs)
130  ym = y_scale_max(ym_unscaled)
131  ymaxs_above = filter(lambda y: y>ym, ymax)
132  if len(ymaxs_above) == 0:
133  ymax = max(ymax)
134  if ym_unscaled > ymax:
135  print "Histogram maximum y %f is above all given ymax values %s, using the maximum one" % (ym_unscaled, str(ymax))
136  else:
137  ymax = min(ymaxs_above)
138 
139 
140  for th1 in th1s:
141  th1.GetXaxis().SetRangeUser(xmin, xmax)
142  th1.GetYaxis().SetRangeUser(ymin, ymax)
143 
144  return (xmin, ymin, xmax, ymax)
145 
146 class Subtract:
147  """Class for subtracting two histograms"""
148  def __init__(self, name, nameA, nameB, title=""):
149  """Constructor
150 
151  Arguments:
152  name -- String for name of the resulting histogram (A-B)
153  nameA -- String for A histogram
154  nameB -- String for B histogram
155 
156  Keyword arguments:
157  title -- String for a title of the resulting histogram (default "")
158 
159  Uncertainties are calculated with the assumption that B is a
160  subset of A, and the histograms contain event counts.
161  """
162  self._name = name
163  self._nameA = nameA
164  self._nameB = nameB
165  self._title = title
166 
167  def __str__(self):
168  """String representation, returns the name"""
169  return self._name
170 
171  def create(self, tdirectory):
172  """Create and return the fake+duplicate histogram from a TDirectory"""
173  histoA = _getObject(tdirectory, self._nameA)
174  histoB = _getObject(tdirectory, self._nameB)
175 
176  if not histoA or not histoB:
177  return None
178 
179  ret = histoA.Clone(self._name)
180  ret.SetTitle(self._title)
181 
182  for i in xrange(1, histoA.GetNbinsX()+1):
183  val = histoA.GetBinContent(i)-histoB.GetBinContent(i)
184  ret.SetBinContent(i, val)
185  ret.SetBinError(i, math.sqrt(val))
186 
187  return ret
188 
190  """Class to calculate the fake+duplicate rate"""
191  def __init__(self, name, assoc, dup, reco, title=""):
192  """Constructor.
193 
194  Arguments:
195  name -- String for the name of the resulting efficiency histogram
196  assoc -- String for the name of the "associated" histogram
197  dup -- String for the name of the "duplicates" histogram
198  reco -- String for the name of the "reco" (denominator) histogram
199 
200  Keyword arguments:
201  title -- String for a title of the resulting histogram (default "")
202 
203  The result is calculated as 1 - (assoc - dup) / reco
204  """
205  self._name = name
206  self._assoc = assoc
207  self._dup = dup
208  self._reco = reco
209  self._title = title
210 
211  def __str__(self):
212  """String representation, returns the name"""
213  return self._name
214 
215  def create(self, tdirectory):
216  """Create and return the fake+duplicate histogram from a TDirectory"""
217  # Get the numerator/denominator histograms
218  hassoc = _getObject(tdirectory, self._assoc)
219  hdup = _getObject(tdirectory, self._dup)
220  hreco = _getObject(tdirectory, self._reco)
221 
222  # Skip if any of them does not exist
223  if not hassoc or not hdup or not hreco:
224  return None
225 
226  hfakedup = hreco.Clone(self._name)
227  hfakedup.SetTitle(self._title)
228 
229  for i in xrange(1, hassoc.GetNbinsX()+1):
230  numerVal = hassoc.GetBinContent(i) - hdup.GetBinContent(i)
231  denomVal = hreco.GetBinContent(i)
232 
233  fakedupVal = (1 - numerVal / denomVal) if denomVal != 0.0 else 0.0
234  errVal = math.sqrt(fakedupVal*(1-fakedupVal)/denomVal) if (denomVal != 0.0 and fakedupVal <= 1) else 0.0
235 
236  hfakedup.SetBinContent(i, fakedupVal)
237  hfakedup.SetBinError(i, errVal)
238 
239  return hfakedup
240 
242  """Class to create a histogram by aggregating bins of another histogram to a bin of the resulting histogram."""
243  def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None):
244  """Constructor.
245 
246  Arguments:
247  name -- String for the name of the resulting histogram
248  histoName -- String for the name of the source histogram
249  mapping -- Dictionary or list for mapping the bins (see below)
250 
251  Keyword arguments:
252  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.
253  scale -- Optional number for scaling the histogram (passed to ROOT.TH1.Scale())
254  renameBin -- Optional function (string -> string) to rename the bins of the input histogram
255 
256  Mapping structure (mapping):
257 
258  Dictionary (you probably want to use collections.OrderedDict)
259  should be a mapping from the destination bin label to a list
260  of source bin labels ("dst -> [src]").
261 
262  If the mapping is a list, it should only contain the source
263  bin labels. In this case, the resulting histogram contains a
264  subset of the bins of the source histogram.
265  """
266  self._name = name
267  self._histoName = histoName
268  self._mapping = mapping
269  self._normalizeTo = normalizeTo
270  self._scale = scale
271  self._renameBin = renameBin
272 
273  def __str__(self):
274  """String representation, returns the name"""
275  return self._name
276 
277  def create(self, tdirectory):
278  """Create and return the histogram from a TDirectory"""
279  th1 = _getOrCreateObject(tdirectory, self._histoName)
280  if th1 is None:
281  return None
282 
283  result = ROOT.TH1F(self._name, self._name, len(self._mapping), 0, len(self._mapping))
284 
285  # TH1 can't really be used as a map/dict, so convert it here:
286  values = {}
287  for i in xrange(1, th1.GetNbinsX()+1):
288  binLabel = th1.GetXaxis().GetBinLabel(i)
289  if self._renameBin is not None:
290  binLabel = self._renameBin(binLabel)
291  values[binLabel] = (th1.GetBinContent(i), th1.GetBinError(i))
292 
293  if isinstance(self._mapping, list):
294  for i, label in enumerate(self._mapping):
295  try:
296  result.SetBinContent(i+1, values[label][0])
297  result.SetBinError(i+1, values[label][1])
298  except KeyError:
299  pass
300  result.GetXaxis().SetBinLabel(i+1, label)
301  else:
302  for i, (key, labels) in enumerate(self._mapping.iteritems()):
303  sumTime = 0
304  sumErrorSq = 0
305  for l in labels:
306  try:
307  sumTime += values[l][0]
308  sumErrorSq += values[l][1]**2
309  except KeyError:
310  pass
311  result.SetBinContent(i+1, sumTime)
312  result.SetBinError(i+1, math.sqrt(sumErrorSq))
313  result.GetXaxis().SetBinLabel(i+1, key)
314 
315  if self._normalizeTo is not None:
316  bin = th1.GetXaxis().FindBin(self._normalizeTo)
317  if bin <= 0:
318  print "Trying to normalize {name} to {binlabel}, which does not exist".format(name=self._name, binlabel=self._normalizeTo)
319  sys.exit(1)
320  value = th1.GetBinContent(bin)
321  if value != 0:
322  result.Scale(1/value)
323 
324  if self._scale is not None:
325  result.Scale(self._scale)
326 
327  return result
328 
330  """Class to create a histogram by aggregaging integrals of another histoggrams."""
331  def __init__(self, name, mapping, normalizeTo=None):
332  """Constructor.
333 
334  Arguments:
335  name -- String for the name of the resulting histogram
336  mapping -- Dictionary for mapping the bin label to a histogram name
337 
338  Keyword arguments:
339  normalizeTo -- Optional string for a histogram. If given, all bins of the resulting histograqm are divided by the integral of this histogram.
340  """
341  self._name = name
342  self._mapping = mapping
343  self._normalizeTo = normalizeTo
344 
345  def __str__(self):
346  """String representation, returns the name"""
347  return self._name
348 
349  def create(self, tdirectory):
350  """Create and return the histogram from a TDirectory"""
351  result = []
352  for key, histoName in self._mapping.iteritems():
353  th1 = _getObject(tdirectory, histoName)
354  if th1 is None:
355  continue
356  result.append( (key, th1.Integral(0, th1.GetNbinsX()+1)) ) # include under- and overflow bins
357  if len(result) == 0:
358  return None
359 
360  res = ROOT.TH1F(self._name, self._name, len(result), 0, len(result))
361 
362  for i, (name, count) in enumerate(result):
363  res.SetBinContent(i+1, count)
364  res.GetXaxis().SetBinLabel(i+1, name)
365 
366  if self._normalizeTo is not None:
367  th1 = _getObject(tdirectory, self._normalizeTo)
368  if th1 is None:
369  return None
370  scale = th1.Integral(0, th1.GetNbinsX()+1)
371  res.Scale(1/scale)
372 
373  return res
374 
375 class ROC:
376  """Class to construct a ROC curve (e.g. efficiency vs. fake rate) from two histograms"""
377  def __init__(self, name, xhistoName, yhistoName, zaxis=False):
378  """Constructor.
379 
380  Arguments:
381  name -- String for the name of the resulting histogram
382  xhistoName -- String for the name of the x-axis histogram (or another "creator" object)
383  yhistoName -- String for the name of the y-axis histogram (or another "creator" object)
384 
385  Keyword arguments:
386  zaxis -- If set to True (default False), create a TGraph2D with z axis showing the cut value (recommended drawStyle 'pcolz')
387  """
388  self._name = name
389  self._xhistoName = xhistoName
390  self._yhistoName = yhistoName
391  self._zaxis = zaxis
392 
393  def __str__(self):
394  """String representation, returns the name"""
395  return self._name
396 
397  def create(self, tdirectory):
398  """Create and return the histogram from a TDirectory"""
399  xhisto = _getOrCreateObject(tdirectory, self._xhistoName)
400  yhisto = _getOrCreateObject(tdirectory, self._yhistoName);
401  if xhisto is None or yhisto is None:
402  return None
403 
404  x = []
405  xerrup = []
406  xerrdown = []
407  y = []
408  yerrup = []
409  yerrdown = []
410  z = []
411 
412  for i in xrange(1, xhisto.GetNbinsX()+1):
413  x.append(xhisto.GetBinContent(i))
414  xerrup.append(xhisto.GetBinError(i))
415  xerrdown.append(xhisto.GetBinError(i))
416 
417  y.append(yhisto.GetBinContent(i))
418  yerrup.append(yhisto.GetBinError(i))
419  yerrdown.append(yhisto.GetBinError(i))
420 
421  z.append(xhisto.GetXaxis().GetBinUpEdge(i))
422 
423  arr = lambda v: array.array("d", v)
424  gr = None
425  if self._zaxis:
426  gr = ROOT.TGraph2D(len(x), arr(x), arr(y), arr(z))
427  else:
428  gr = ROOT.TGraphAsymmErrors(len(x), arr(x), arr(y), arr(xerrdown), arr(xerrup), arr(yerrdown), arr(yerrup))
429  gr.SetTitle("")
430  return gr
431 
432 
433 # Plot styles
434 _plotStylesColor = [4, 2, ROOT.kBlack, ROOT.kOrange+7, ROOT.kMagenta-3]
435 _plotStylesMarker = [21, 20, 22, 34, 33]
436 
437 def _drawFrame(pad, bounds, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, suffix=""):
438  """Function to draw a frame
439 
440  Arguments:
441  pad -- TPad to where the frame is drawn
442  bounds -- List or 4-tuple for (xmin, ymin, xmax, ymax)
443 
444  Keyword arguments:
445  xbinlabels -- Optional list of strings for x axis bin labels
446  xbinlabelsize -- Optional number for the x axis bin label size
447  xbinlabeloption -- Optional string for the x axis bin options (passed to ROOT.TH1.LabelsOption())
448  suffix -- Optional string for a postfix of the frame name
449  """
450  if xbinlabels is None:
451  frame = pad.DrawFrame(*bounds)
452  else:
453  # Special form needed if want to set x axis bin labels
454  nbins = len(xbinlabels)
455  frame = ROOT.TH1F("hframe"+suffix, "", nbins, bounds[0], bounds[2])
456  frame.SetBit(ROOT.TH1.kNoStats)
457  frame.SetBit(ROOT.kCanDelete)
458  frame.SetMinimum(bounds[1])
459  frame.SetMaximum(bounds[3])
460  frame.GetYaxis().SetLimits(bounds[1], bounds[3])
461  frame.Draw("")
462 
463  xaxis = frame.GetXaxis()
464  for i in xrange(nbins):
465  xaxis.SetBinLabel(i+1, xbinlabels[i])
466  if xbinlabelsize is not None:
467  xaxis.SetLabelSize(xbinlabelsize)
468  if xbinlabeloption is not None:
469  frame.LabelsOption(xbinlabeloption)
470 
471  return frame
472 
473 class Frame:
474  """Class for creating and managing a frame for a simple, one-pad plot"""
475  def __init__(self, pad, bounds, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
476  self._pad = pad
477  self._frame = _drawFrame(pad, bounds, xbinlabels, xbinlabelsize, xbinlabeloption)
478 
479  yoffsetFactor = 1
480  xoffsetFactor = 1
481  if nrows == 2:
482  yoffsetFactor *= 2
483  xoffsetFactor *= 2
484  elif nrows >= 3:
485  yoffsetFactor *= 4
486  xoffsetFactor *= 3
487 
488  self._frame.GetYaxis().SetTitleOffset(self._frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
489  self._frame.GetXaxis().SetTitleOffset(self._frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
490 
491 
492  def setLogx(self, log):
493  self._pad.SetLogx(log)
494 
495  def setLogy(self, log):
496  self._pad.SetLogy(log)
497 
498  def setGridx(self, grid):
499  self._pad.SetGridx(grid)
500 
501  def setGridy(self, grid):
502  self._pad.SetGridy(grid)
503 
504  def adjustMarginLeft(self, adjust):
505  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
506  # Need to redraw frame after adjusting the margin
507  self._pad.cd()
508  self._frame.Draw("")
509 
510  def adjustMarginRight(self, adjust):
511  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
512  # Need to redraw frame after adjusting the margin
513  self._pad.cd()
514  self._frame.Draw("")
515 
516  def setTitle(self, title):
517  self._frame.SetTitle(title)
518 
519  def setXTitle(self, title):
520  self._frame.GetXaxis().SetTitle(title)
521 
522  def setXTitleSize(self, size):
523  self._frame.GetXaxis().SetTitleSize(size)
524 
525  def setXTitleOffset(self, offset):
526  self._frame.GetXaxis().SetTitleOffset(offset)
527 
528  def setXLabelSize(self, size):
529  self._frame.GetXaxis().SetLabelSize(size)
530 
531  def setYTitle(self, title):
532  self._frame.GetYaxis().SetTitle(title)
533 
534  def setYTitleSize(self, size):
535  self._frame.GetYaxis().SetTitleSize(size)
536 
537  def setYTitleOffset(self, offset):
538  self._frame.GetYaxis().SetTitleOffset(offset)
539 
540  def redrawAxis(self):
541  self._pad.RedrawAxis()
542 
544  """Class for creating and managing a frame for a ratio plot with two subpads"""
545  def __init__(self, pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
546  self._parentPad = pad
547  self._pad = pad.cd(1)
548  if xbinlabels is not None:
549  self._frame = _drawFrame(self._pad, bounds, [""]*len(xbinlabels))
550  else:
551  self._frame = _drawFrame(self._pad, bounds)
552  self._padRatio = pad.cd(2)
553  self._frameRatio = _drawFrame(self._padRatio, ratioBounds, xbinlabels, xbinlabelsize, xbinlabeloption)
554 
555  self._frame.GetXaxis().SetLabelSize(0)
556  self._frame.GetXaxis().SetTitleSize(0)
557 
558  yoffsetFactor = ratioFactor
559  divisionPoint = 1-1/ratioFactor
560  xoffsetFactor = 1/divisionPoint #* 0.6
561 
562  if nrows == 1:
563  xoffsetFactor *= 0.6
564  elif nrows == 2:
565  yoffsetFactor *= 2
566  xoffsetFactor *= 1.5
567  elif nrows == 3:
568  yoffsetFactor *= 4
569  xoffsetFactor *= 2.3
570  elif nrows >= 4:
571  yoffsetFactor *= 5
572  xoffsetFactor *= 3
573 
574  self._frame.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
575  self._frameRatio.GetYaxis().SetLabelSize(int(self._frameRatio.GetYaxis().GetLabelSize()*0.8))
576  self._frameRatio.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
577  self._frameRatio.GetXaxis().SetTitleOffset(self._frameRatio.GetXaxis().GetTitleOffset()*xoffsetFactor)
578 
579  self._frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
580 
581  self._frameRatio.GetYaxis().SetTitle("Ratio")
582 
583  def setLogx(self, log):
584  self._pad.SetLogx(log)
585  self._padRatio.SetLogx(log)
586 
587  def setLogy(self, log):
588  self._pad.SetLogy(log)
589 
590  def setGridx(self, grid):
591  self._pad.SetGridx(grid)
592  self._padRatio.SetGridx(grid)
593 
594  def setGridy(self, grid):
595  self._pad.SetGridy(grid)
596  self._padRatio.SetGridy(grid)
597 
598  def adjustMarginLeft(self, adjust):
599  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
600  self._padRatio.SetLeftMargin(self._padRatio.GetLeftMargin()+adjust)
601  # Need to redraw frame after adjusting the margin
602  self._pad.cd()
603  self._frame.Draw("")
604  self._padRatio.cd()
605  self._frameRatio.Draw("")
606 
607  def adjustMarginRight(self, adjust):
608  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
609  self._padRatio.SetRightMargin(self._padRatio.GetRightMargin()+adjust)
610  # Need to redraw frames after adjusting the margin
611  self._pad.cd()
612  self._frame.Draw("")
613  self._padRatio.cd()
614  self._frameRatio.Draw("")
615 
616  def setTitle(self, title):
617  self._frame.SetTitle(title)
618 
619  def setXTitle(self, title):
620  self._frameRatio.GetXaxis().SetTitle(title)
621 
622  def setXTitleSize(self, size):
623  self._frameRatio.GetXaxis().SetTitleSize(size)
624 
625  def setYTitleOffset(self, offset):
626  self._frameRatio.GetXaxis().SetTitleOffset(offset)
627 
628  def setXLabelSize(self, size):
629  self._frameRatio.GetXaxis().SetLabelSize(size)
630 
631  def setYTitle(self, title):
632  self._frame.GetYaxis().SetTitle(title)
633 
634  def setYTitleRatio(self, title):
635  self._frameRatio.GetYaxis().SetTitle(title)
636 
637  def setYTitleSize(self, size):
638  self._frame.GetYaxis().SetTitleSize(size)
639  self._frameRatio.GetYaxis().SetTitleSize(size)
640 
641  def setYTitleOffset(self, offset):
642  self._frame.GetYaxis().SetTitleOffset(offset)
643  self._frameRatio.GetYaxis().SetTitleOffset(offset)
644 
645  def redrawAxis(self):
646  self._padRatio.RedrawAxis()
647  self._pad.RedrawAxis()
648 
649  self._parentPad.cd()
650 
651  # pad to hide the lowest y axis label of the main pad
652  xmin=0.065
653  ymin=0.285
654  xmax=0.128
655  ymax=0.33
656  self._coverPad = ROOT.TPad("coverpad", "coverpad", xmin, ymin, xmax, ymax)
657  self._coverPad.SetBorderMode(0)
658  self._coverPad.Draw()
659 
660  self._pad.cd()
661  self._pad.Pop() # Move the first pad on top
662 
664  """Class for creating and managing a frame for a plot from TGraph2D"""
665  def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
666  self._pad = pad
667  if ratioOrig:
668  self._pad = pad.cd(1)
669 
670  # adjust margins because of not having the ratio, we want
671  # the same bottom margin, so some algebra gives this
672  (xlow, ylow, width, height) = (self._pad.GetXlowNDC(), self._pad.GetYlowNDC(), self._pad.GetWNDC(), self._pad.GetHNDC())
673  xup = xlow+width
674  yup = ylow+height
675 
676  bottomMargin = self._pad.GetBottomMargin()
677  bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
678 
679  ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
680  topMarginNew = self._pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
681 
682  self._pad.SetPad(xlow, ylowNew, xup, yup)
683  self._pad.SetTopMargin(topMarginNew)
684  self._pad.SetBottomMargin(bottomMarginNew)
685 
686  self._view = ROOT.TView.CreateView()
687  self._view.SetRange(bounds[0], bounds[1], 0, bounds[2], bounds[3], 20) # 20 is from Harrison-Stetson, may need tuning?
688  self._view.Top()
689  self._view.ShowAxis()
690 
691  self._xtitleoffset = 1.8
692  self._ytitleoffset = 2.3
693 
694  self._firstHisto = histos[0]
695 
696  def setLogx(self, log):
697  pass
698 
699  def setLogy(self, log):
700  pass
701 
702  def setGridx(self, grid):
703  pass
704 
705  def setGridy(self, grid):
706  pass
707 
708  def adjustMarginLeft(self, adjust):
709  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
710  self._pad.cd()
711 
712  def adjustMarginRight(self, adjust):
713  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
714  self._pad.cd()
715 
716  def setTitle(self, title):
717  pass
718 
719  def setXTitle(self, title):
720  self._xtitle = title
721 
722  def setXTitleSize(self, size):
723  self._xtitlesize = size
724 
725  def setXTitleOffset(self, size):
726  self._xtitleoffset = size
727 
728  def setXLabelSize(self, size):
729  self._xlabelsize = size
730 
731  def setYTitle(self, title):
732  self._ytitle = title
733 
734  def setYTitleSize(self, size):
735  self._ytitlesize = size
736 
737  def setYTitleOffset(self, offset):
738  self._ytitleoffset = offset
739 
740  def setZTitle(self, title):
741  self._firstHisto.GetZaxis().SetTitle(title)
742 
743  def setZTitleOffset(self, offset):
744  self._firstHisto.GetZaxis().SetTitleOffset(offset)
745 
746  def redrawAxis(self):
747  # Disabling and enabled the 3D rulers somehow magically moves the axes to their proper places
748  ROOT.TAxis3D.ToggleRulers()
749  ROOT.TAxis3D.ToggleRulers()
750  axis = ROOT.TAxis3D.GetPadAxis()
751  axis.SetLabelColor(ROOT.kBlack);
752  axis.SetAxisColor(ROOT.kBlack);
753 
754  axis.GetXaxis().SetTitleOffset(self._xtitleoffset)
755  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
756 
757  if hasattr(self, "_xtitle"):
758  axis.GetXaxis().SetTitle(self._xtitle)
759  if hasattr(self, "_xtitlesize"):
760  axis.GetXaxis().SetTitleSize(self._xtitlesize)
761  if hasattr(self, "_xlabelsize"):
762  axis.GetXaxis().SetLabelSize(self._labelsize)
763  if hasattr(self, "_ytitle"):
764  axis.GetYaxis().SetTitle(self._ytitle)
765  if hasattr(self, "_ytitlesize"):
766  axis.GetYaxis().SetTitleSize(self._ytitlesize)
767  if hasattr(self, "_ytitleoffset"):
768  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
769 
770 def _copyStyle(src, dst):
771  properties = []
772  if hasattr(src, "GetLineColor") and hasattr(dst, "SetLineColor"):
773  properties.extend(["LineColor", "LineStyle", "LineWidth"])
774  if hasattr(src, "GetFillColor") and hasattr(dst, "SetFillColor"):
775  properties.extend(["FillColor", "FillStyle"])
776  if hasattr(src, "GetMarkerColor") and hasattr(dst, "SetMarkerColor"):
777  properties.extend(["MarkerColor", "MarkerSize", "MarkerStyle"])
778 
779  for prop in properties:
780  getattr(dst, "Set"+prop)(getattr(src, "Get"+prop)())
781 
782 class Plot:
783  """Represents one plot, comparing one or more histograms."""
784  def __init__(self, name, **kwargs):
785  """ Constructor.
786 
787  Arguments:
788  name -- String for name of the plot, or Efficiency object
789 
790  Keyword arguments:
791  title -- String for a title of the plot (default None)
792  xtitle -- String for x axis title (default None)
793  xtitlesize -- Float for x axis title size (default None)
794  xtitleoffset -- Float for x axis title offset (default None)
795  xlabelsize -- Float for x axis label size (default None)
796  ytitle -- String for y axis title (default None)
797  ytitlesize -- Float for y axis title size (default None)
798  ytitleoffset -- Float for y axis title offset (default None)
799  ztitle -- String for z axis title (default None)
800  ztitleoffset -- Float for z axis title offset (default None)
801  xmin -- Float for x axis minimum (default None, i.e. automatic)
802  xmax -- Float for x axis maximum (default None, i.e. automatic)
803  ymin -- Float for y axis minimum (default 0)
804  ymax -- Float for y axis maximum (default None, i.e. automatic)
805  xlog -- Bool for x axis log status (default False)
806  ylog -- Bool for y axis log status (default False)
807  xgrid -- Bool for x axis grid status (default True)
808  ygrid -- Bool for y axis grid status (default True)
809  stat -- Draw stat box? (default False)
810  fit -- Do gaussian fit? (default False)
811  statx -- Stat box x coordinate (default 0.65)
812  staty -- Stat box y coordinate (default 0.8)
813  statyadjust -- List of floats for stat box y coordinate adjustments (default None)
814  normalizeToUnitArea -- Normalize histograms to unit area? (default False)
815  profileX -- Take histograms via ProfileX()? (default False)
816  fitSlicesY -- Take histograms via FitSlicesY() (default False)
817  rebinX -- rebin x axis (default None)
818  scale -- Scale histograms by a number (default None)
819  xbinlabels -- List of x axis bin labels (if given, default None)
820  xbinlabelsize -- Size of x axis bin labels (default None)
821  xbinlabeloption -- Option string for x axis bin labels (default None)
822  drawStyle -- If "hist", draw as line instead of points (default None)
823  drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
824  lineWidth -- If drawStyle=="hist", the width of line (default 2)
825  legendDx -- Float for moving TLegend in x direction for separate=True (default None)
826  legendDy -- Float for moving TLegend in y direction for separate=True (default None)
827  legendDw -- Float for changing TLegend width for separate=True (default None)
828  legendDh -- Float for changing TLegend height for separate=True (default None)
829  adjustMarginRight -- Float for adjusting right margin (default None)
830  ratioYmin -- Float for y axis minimum in ratio pad (default 0.9)
831  ratioYmax -- Float for y axis maximum in ratio pad (default 1.1)
832  ratioUncertainty -- Plot uncertainties on ratio? (default True)
833  histogramModifier -- Function to be called in create() to modify the histograms (default None)
834  """
835  self._name = name
836 
837  def _set(attr, default):
838  setattr(self, "_"+attr, kwargs.get(attr, default))
839 
840  _set("title", None)
841  _set("xtitle", None)
842  _set("xtitlesize", None)
843  _set("xtitleoffset", None)
844  _set("xlabelsize", None)
845  _set("ytitle", None)
846  _set("ytitlesize", None)
847  _set("ytitleoffset", None)
848  _set("ztitle", None)
849  _set("ztitleoffset", None)
850 
851  _set("xmin", None)
852  _set("xmax", None)
853  _set("ymin", 0.)
854  _set("ymax", None)
855 
856  _set("xlog", False)
857  _set("ylog", False)
858  _set("xgrid", True)
859  _set("ygrid", True)
860 
861  _set("stat", False)
862  _set("fit", False)
863 
864  _set("statx", 0.65)
865  _set("staty", 0.8)
866  _set("statyadjust", None)
867 
868  _set("normalizeToUnitArea", False)
869  _set("profileX", False)
870  _set("fitSlicesY", False)
871  _set("rebinX", None)
872 
873  _set("scale", None)
874  _set("xbinlabels", None)
875  _set("xbinlabelsize", None)
876  _set("xbinlabeloption", None)
877 
878  _set("drawStyle", None)
879  _set("drawCommand", None)
880  _set("lineWidth", 2)
881 
882  _set("legendDx", None)
883  _set("legendDy", None)
884  _set("legendDw", None)
885  _set("legendDh", None)
886 
887  _set("adjustMarginRight", None)
888 
889  _set("ratioYmin", 0.9)
890  _set("ratioYmax", 1.1)
891  _set("ratioUncertainty", True)
892 
893  _set("histogramModifier", None)
894 
895  self._histograms = []
896 
898  """Return number of existing histograms."""
899  return len(filter(lambda h: h is not None, self._histograms))
900 
901  def isEmpty(self):
902  """Return true if there are no histograms created for the plot"""
903  return self.getNumberOfHistograms() == 0
904 
905  def isTGraph2D(self):
906  for h in self._histograms:
907  if isinstance(h, ROOT.TGraph2D):
908  return True
909  return False
910 
911  def getName(self):
912  if isinstance(self._name, list):
913  return str(self._name[0])
914  else:
915  return str(self._name)
916 
918  """Return true if the ratio uncertainty should be drawn"""
919  return self._ratioUncertainty
920 
921  def _createOne(self, index, tdir):
922  """Create one histogram from a TDirectory."""
923  if tdir == None:
924  return None
925 
926  # If name is a list, pick the name by the index
927  if isinstance(self._name, list):
928  name = self._name[index]
929  else:
930  name = self._name
931 
932  return _getOrCreateObject(tdir, name)
933 
934  def create(self, tdirs, requireAllHistograms=False):
935  """Create histograms from list of TDirectories"""
936  self._histograms = [self._createOne(i, tdir) for i, tdir in enumerate(tdirs)]
937 
938  if self._histogramModifier is not None:
939  self._histograms = self._histogramModifier(self._histograms)
940 
941  if len(self._histograms) > len(_plotStylesColor):
942  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)))
943 
944  # Modify histograms here in case self._name returns numbers
945  # and self._histogramModifier creates the histograms from
946  # these numbers
947  def _modifyHisto(th1):
948  if th1 is None:
949  return None
950 
951  if self._profileX:
952  th1 = th1.ProfileX()
953 
954  if self._fitSlicesY:
955  ROOT.TH1.AddDirectory(True)
956  th1.FitSlicesY()
957  th1 = ROOT.gDirectory.Get(th1.GetName()+"_2")
958  th1.SetDirectory(None)
959  #th1.SetName(th1.GetName()+"_ref")
960  ROOT.TH1.AddDirectory(False)
961 
962  if self._title is not None:
963  th1.SetTitle(self._title)
964 
965  if self._scale is not None:
966  th1.Scale(self._scale)
967 
968  return th1
969 
970  self._histograms = map(_modifyHisto, self._histograms)
971  if requireAllHistograms and None in self._histograms:
972  self._histograms = [None]*len(self._histograms)
973 
974  def _setStats(self, startingX, startingY):
975  """Set stats box."""
976  if not self._stat:
977  for h in self._histograms:
978  if h is not None and hasattr(h, "SetStats"):
979  h.SetStats(0)
980  return
981 
982  def _doStats(h, col, dy):
983  if h is None:
984  return
985  h.SetStats(True)
986 
987  if self._fit:
988  h.Fit("gaus", "Q")
989  f = h.GetListOfFunctions().FindObject("gaus")
990  if f == None:
991  h.SetStats(0)
992  return
993  f.SetLineColor(col)
994  f.SetLineWidth(1)
995  h.Draw()
996  ROOT.gPad.Update()
997  st = h.GetListOfFunctions().FindObject("stats")
998  if self._fit:
999  st.SetOptFit(0010)
1000  st.SetOptStat(1001)
1001  st.SetX1NDC(startingX)
1002  st.SetX2NDC(startingX+0.3)
1003  st.SetY1NDC(startingY+dy)
1004  st.SetY2NDC(startingY+dy+0.15)
1005  st.SetTextColor(col)
1006 
1007  dy = 0.0
1008  for i, h in enumerate(self._histograms):
1009  if self._statyadjust is not None and i < len(self._statyadjust):
1010  dy += self._statyadjust[i]
1011 
1012  _doStats(h, _plotStylesColor[i], dy)
1013  dy -= 0.19
1014 
1015  def _normalize(self):
1016  """Normalise histograms to unit area"""
1017 
1018  for h in self._histograms:
1019  if h is None:
1020  continue
1021  i = h.Integral()
1022  if i == 0:
1023  continue
1024  h.Sumw2()
1025  h.Scale(1.0/i)
1026 
1027  def draw(self, pad, ratio, ratioFactor, nrows):
1028  """Draw the histograms using values for a given algorithm."""
1029 # if len(self._histograms) == 0:
1030 # print "No histograms for plot {name}".format(name=self._name)
1031 # return
1032 
1033  isTGraph2D = self.isTGraph2D()
1034  if isTGraph2D:
1035  # Ratios for the TGraph2Ds is not that interesting
1036  ratioOrig = ratio
1037  ratio = False
1038 
1039  if self._normalizeToUnitArea:
1040  self._normalize()
1041 
1042  if self._rebinX is not None:
1043  for h in self._histograms:
1044  h.Rebin(self._rebinX)
1045 
1046  def _styleMarker(h, msty, col):
1047  h.SetMarkerStyle(msty)
1048  h.SetMarkerColor(col)
1049  h.SetMarkerSize(0.7)
1050  h.SetLineColor(1)
1051  h.SetLineWidth(1)
1052 
1053  def _styleHist(h, msty, col):
1054  _styleMarker(h, msty, col)
1055  h.SetLineColor(col)
1056  h.SetLineWidth(self._lineWidth)
1057 
1058  # Use marker or hist style
1059  style = _styleMarker
1060  if self._drawStyle is not None:
1061  if "hist" in self._drawStyle.lower():
1062  style = _styleHist
1063  if len(self._histograms) > 0 and isinstance(self._histograms[0], ROOT.TGraph):
1064  if "l" in self._drawStyle.lower():
1065  style = _styleHist
1066 
1067  # Apply style to histograms, filter out Nones
1068  histos = []
1069  for i, h in enumerate(self._histograms):
1070  if h is None:
1071  continue
1072  style(h, _plotStylesMarker[i], _plotStylesColor[i])
1073  histos.append(h)
1074  if len(histos) == 0:
1075  print "No histograms for plot {name}".format(name=self.getName())
1076  return
1077 
1078  bounds = _findBounds(histos, self._ylog,
1079  xmin=self._xmin, xmax=self._xmax,
1080  ymin=self._ymin, ymax=self._ymax)
1081 
1082  # Create bounds before stats in order to have the
1083  # SetRangeUser() calls made before the fit
1084  #
1085  # stats is better to be called before frame, otherwise get
1086  # mess in the plot (that frame creation cleans up)
1087  if ratio:
1088  pad.cd(1)
1089  self._setStats(self._statx, self._staty)
1090 
1091  xbinlabels = self._xbinlabels
1092  if xbinlabels is None:
1093  if len(histos[0].GetXaxis().GetBinLabel(1)) > 0:
1094  xbinlabels = []
1095  for i in xrange(1, histos[0].GetNbinsX()+1):
1096  xbinlabels.append(histos[0].GetXaxis().GetBinLabel(i))
1097 
1098  # Create frame
1099  if isTGraph2D:
1100  frame = FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
1101  else:
1102  if ratio:
1103  ratioBounds = (bounds[0], self._ratioYmin, bounds[2], self._ratioYmax)
1104  frame = FrameRatio(pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1105  else:
1106  frame = Frame(pad, bounds, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1107 
1108  # Set log and grid
1109  frame.setLogx(self._xlog)
1110  frame.setLogy(self._ylog)
1111  frame.setGridx(self._xgrid)
1112  frame.setGridy(self._ygrid)
1113 
1114  # Construct draw option string
1115  opt = "sames" # s for statbox or something?
1116  ds = ""
1117  if self._drawStyle is not None:
1118  ds = self._drawStyle
1119  if self._drawCommand is not None:
1120  ds = self._drawCommand
1121  if len(ds) > 0:
1122  opt += " "+ds
1123 
1124  # Set properties of frame
1125  frame.setTitle(histos[0].GetTitle())
1126  if self._xtitle is not None:
1127  frame.setXTitle(self._xtitle)
1128  if self._xtitlesize is not None:
1129  frame.setXTitleSize(self._xtitlesize)
1130  if self._xtitleoffset is not None:
1131  frame.setXTitleOffset(self._xtitleoffset)
1132  if self._xlabelsize is not None:
1133  frame.setXLabelSize(self._xlabelsize)
1134  if self._ytitle is not None:
1135  frame.setYTitle(self._ytitle)
1136  if self._ytitlesize is not None:
1137  frame.setYTitleSize(self._ytitlesize)
1138  if self._ytitleoffset is not None:
1139  frame.setYTitleOffset(self._ytitleoffset)
1140  if self._ztitle is not None:
1141  frame.setZTitle(self._ztitle)
1142  if self._ztitleoffset is not None:
1143  frame.setZTitleOffset(self._ztitleoffset)
1144  if self._adjustMarginRight is not None:
1145  frame.adjustMarginRight(self._adjustMarginRight)
1146  elif "z" in opt:
1147  frame.adjustMarginLeft(0.03)
1148  frame.adjustMarginRight(0.08)
1149 
1150  # Draw histograms
1151  if ratio:
1152  frame._pad.cd()
1153 
1154  for h in histos:
1155  h.Draw(opt)
1156 
1157  # Draw ratios
1158  if ratio and len(histos) > 0:
1159  frame._padRatio.cd()
1160  self._ratios = self._calculateRatios(histos) # need to keep these in memory too ...
1161  if self._ratioUncertainty and self._ratios[0]._ratio is not None:
1162  self._ratios[0]._ratio.SetFillStyle(1001)
1163  self._ratios[0]._ratio.SetFillColor(ROOT.kGray)
1164  self._ratios[0]._ratio.SetLineColor(ROOT.kGray)
1165  self._ratios[0]._ratio.SetMarkerColor(ROOT.kGray)
1166  self._ratios[0]._ratio.SetMarkerSize(0)
1167  self._ratios[0].draw("E2")
1168  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
1169  for r in self._ratios[1:]:
1170  r.draw()
1171 
1172  frame.redrawAxis()
1173  self._frame = frame # keep the frame in memory for sure
1174 
1175  def addToLegend(self, legend, legendLabels, denomUncertainty):
1176  """Add histograms to a legend.
1177 
1178  Arguments:
1179  legend -- TLegend
1180  legendLabels -- List of strings for the legend labels
1181  """
1182  first = denomUncertainty
1183  for h, label in zip(self._histograms, legendLabels):
1184  if h is None:
1185  first = False
1186  continue
1187  if first:
1188  self._forLegend = h.Clone()
1189  self._forLegend.SetFillStyle(1001)
1190  self._forLegend.SetFillColor(ROOT.kGray)
1191  entry = legend.AddEntry(self._forLegend, label, "lpf")
1192  first = False
1193  else:
1194  legend.AddEntry(h, label, "LP")
1195 
1196  def _calculateRatios(self, histos):
1197  """Calculate the ratios for a list of histograms"""
1198 
1199  def _divideOrZero(numerator, denominator):
1200  if denominator == 0:
1201  return 0
1202  return numerator/denominator
1203 
1204  # Define wrappers for TH1/TGraph/TGraph2D to have uniform interface
1205  # TODO: having more global wrappers would make some things simpler also elsewhere in the code
1206  class WrapTH1:
1207  def __init__(self, th1, uncertainty):
1208  self._th1 = th1
1209  self._uncertainty = uncertainty
1210 
1211  xaxis = th1.GetXaxis()
1212  xaxis_arr = xaxis.GetXbins()
1213  if xaxis_arr.GetSize() > 0: # unequal binning
1214  lst = [xaxis_arr[i] for i in xrange(0, xaxis_arr.GetSize())]
1215  arr = array.array("d", lst)
1216  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), arr)
1217  else:
1218  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), xaxis.GetXmin(), xaxis.GetXmax())
1219  _copyStyle(th1, self._ratio)
1220  self._ratio.SetStats(0)
1221  self._ratio.SetLineColor(ROOT.kBlack)
1222  self._ratio.SetLineWidth(1)
1223  def draw(self, style=None):
1224  st = style
1225  if st is None:
1226  if self._uncertainty:
1227  st = "EP"
1228  else:
1229  st = "HIST P"
1230  self._ratio.Draw("same "+st)
1231  def begin(self):
1232  return 1
1233  def end(self):
1234  return self._th1.GetNbinsX()+1
1235  def xvalues(self, bin):
1236  xval = self._th1.GetBinCenter(bin)
1237  xlow = xval-self._th1.GetXaxis().GetBinLowEdge(bin)
1238  xhigh = self._th1.GetXaxis().GetBinUpEdge(bin)-xval
1239  return (xval, xlow, xhigh)
1240  def yvalues(self, bin):
1241  yval = self._th1.GetBinContent(bin)
1242  yerr = self._th1.GetBinError(bin)
1243  return (yval, yerr, yerr)
1244  def y(self, bin):
1245  return self._th1.GetBinContent(bin)
1246  def divide(self, bin, scale, xcenter):
1247  self._ratio.SetBinContent(bin, _divideOrZero(self._th1.GetBinContent(bin), scale))
1248  self._ratio.SetBinError(bin, _divideOrZero(self._th1.GetBinError(bin), scale))
1249  def makeRatio(self):
1250  pass
1251 
1252  class WrapTGraph:
1253  def __init__(self, gr, uncertainty):
1254  self._gr = gr
1255  self._uncertainty = uncertainty
1256  self._xvalues = []
1257  self._xerrslow = []
1258  self._xerrshigh = []
1259  self._yvalues = []
1260  self._yerrshigh = []
1261  self._yerrslow = []
1262  self._binOffset = 0
1263  def draw(self, style=None):
1264  if self._ratio is None:
1265  return
1266  st = style
1267  if st is None:
1268  if self._uncertainty:
1269  st = "PZ"
1270  else:
1271  st = "PX"
1272  self._ratio.Draw("same "+st)
1273  def begin(self):
1274  return 0
1275  def end(self):
1276  return self._gr.GetN()
1277  def xvalues(self, bin):
1278  return (self._gr.GetX()[bin], self._gr.GetErrorXlow(bin), self._gr.GetErrorXhigh(bin))
1279  def yvalues(self, bin):
1280  return (self._gr.GetY()[bin], self._gr.GetErrorYlow(bin), self._gr.GetErrorYhigh(bin))
1281  def y(self, bin):
1282  return self._gr.GetY()[bin]
1283  def divide(self, bin, scale, xcenter):
1284  # Ignore bin if denominator is zero
1285  if scale == 0:
1286  return
1287  # No more items in the numerator
1288  if bin >= self._gr.GetN():
1289  return
1290  # denominator is missing an item
1291  trueBin = bin + self._binOffset
1292  xvals = self.xvalues(trueBin)
1293  xval = xvals[0]
1294  epsilon = 1e-3 * xval # to allow floating-point difference between TGraph and TH1
1295  if xval+epsilon < xcenter:
1296  self._binOffset += 1
1297  return
1298  # numerator is missing an item
1299  elif xval-epsilon > xcenter:
1300  self._binOffset -= 1
1301  return
1302 
1303  self._xvalues.append(xval)
1304  self._xerrslow.append(xvals[1])
1305  self._xerrshigh.append(xvals[2])
1306  yvals = self.yvalues(trueBin)
1307  self._yvalues.append(yvals[0] / scale)
1308  if self._uncertainty:
1309  self._yerrslow.append(yvals[1] / scale)
1310  self._yerrshigh.append(yvals[2] / scale)
1311  else:
1312  self._yerrslow.append(0)
1313  self._yerrshigh.append(0)
1314  def makeRatio(self):
1315  if len(self._xvalues) == 0:
1316  self._ratio = None
1317  return
1318  self._ratio = ROOT.TGraphAsymmErrors(len(self._xvalues), array.array("d", self._xvalues), array.array("d", self._yvalues),
1319  array.array("d", self._xerrslow), array.array("d", self._xerrshigh),
1320  array.array("d", self._yerrslow), array.array("d", self._yerrshigh))
1321  _copyStyle(self._gr, self._ratio)
1322  class WrapTGraph2D(WrapTGraph):
1323  def __init__(self, gr, uncertainty):
1324  WrapTGraph.__init__(self, gr, uncertainty)
1325  def xvalues(self, bin):
1326  return (self._gr.GetX()[bin], self._gr.GetErrorX(bin), self._gr.GetErrorX(bin))
1327  def yvalues(self, bin):
1328  return (self._gr.GetY()[bin], self._gr.GetErrorY(bin), self._gr.GetErrorY(bin))
1329 
1330  def wrap(o):
1331  if isinstance(o, ROOT.TH1):
1332  return WrapTH1(o, self._ratioUncertainty)
1333  elif isinstance(o, ROOT.TGraph):
1334  return WrapTGraph(o, self._ratioUncertainty)
1335  elif isinstance(o, ROOT.TGraph2D):
1336  return WrapTGraph2D(o, self._ratioUncertainty)
1337 
1338  wrappers = [wrap(h) for h in histos]
1339  ref = wrappers[0]
1340 
1341  for bin in xrange(ref.begin(), ref.end()):
1342  (scale, ylow, yhigh) = ref.yvalues(bin)
1343  (xval, xlow, xhigh) = ref.xvalues(bin)
1344  for w in wrappers:
1345  w.divide(bin, scale, xval)
1346 
1347  for w in wrappers:
1348  w.makeRatio()
1349 
1350  return wrappers
1351 
1353  """Group of plots, results a TCanvas"""
1354  def __init__(self, name, plots, **kwargs):
1355  """Constructor.
1356 
1357  Arguments:
1358  name -- String for name of the TCanvas, used also as the basename of the picture files
1359  plots -- List of Plot objects
1360 
1361  Keyword arguments:
1362  ncols -- Number of columns (default 2)
1363  legendDx -- Float for moving TLegend in x direction (default None)
1364  legendDy -- Float for moving TLegend in y direction (default None)
1365  legendDw -- Float for changing TLegend width (default None)
1366  legendDh -- Float for changing TLegend height (default None)
1367  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
1368  onlyForPileup -- Plots this group only for pileup samples
1369  """
1370  self._name = name
1371  self._plots = plots
1372 
1373  def _set(attr, default):
1374  setattr(self, "_"+attr, kwargs.get(attr, default))
1375 
1376  _set("ncols", 2)
1377 
1378  _set("legendDx", None)
1379  _set("legendDy", None)
1380  _set("legendDw", None)
1381  _set("legendDh", None)
1382 
1383  _set("overrideLegendLabels", None)
1384 
1385  _set("onlyForPileup", False)
1386 
1387  self._ratioFactor = 1.25
1388 
1389  def onlyForPileup(self):
1390  """Return True if the PlotGroup is intended only for pileup samples"""
1391  return self._onlyForPileup
1392 
1393  def create(self, tdirectories, requireAllHistograms=False):
1394  """Create histograms from a list of TDirectories.
1395 
1396  Arguments:
1397  tdirectories -- List of TDirectory objects
1398  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
1399  """
1400  for plot in self._plots:
1401  plot.create(tdirectories, requireAllHistograms)
1402 
1403  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=False):
1404  """Draw the histograms using values for a given algorithm.
1405 
1406  Arguments:
1407  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
1408  prefix -- Optional string for file name prefix (default None)
1409  separate -- Save the plots of a group to separate files instead of a file per group (default False)
1410  saveFormat -- String specifying the plot format (default '.pdf')
1411  ratio -- Add ratio to the plot (default False)
1412  """
1413 
1414  if self._overrideLegendLabels is not None:
1415  legendLabels = self._overrideLegendLabels
1416 
1417  # Do not draw the group if it would be empty
1418  onlyEmptyPlots = True
1419  for plot in self._plots:
1420  if not plot.isEmpty():
1421  onlyEmptyPlots = False
1422  break
1423  if onlyEmptyPlots:
1424  return []
1425 
1426  if separate:
1427  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio)
1428 
1429  cwidth = 500*self._ncols
1430  nrows = int((len(self._plots)+1)/self._ncols) # this should work also for odd n
1431  cheight = 500 * nrows
1432 
1433  if ratio:
1434  cheight = int(cheight*self._ratioFactor)
1435 
1436  canvas = ROOT.TCanvas(self._name, self._name, cwidth, cheight)
1437 
1438  canvas.Divide(self._ncols, nrows)
1439  if ratio:
1440  for i in xrange(0, len(self._plots)):
1441  pad = canvas.cd(i+1)
1442  self._modifyPadForRatio(pad)
1443 
1444  # Draw plots to canvas
1445  for i, plot in enumerate(self._plots):
1446  pad = canvas.cd(i+1)
1447  if not plot.isEmpty():
1448  plot.draw(pad, ratio, self._ratioFactor, nrows)
1449 
1450  # Setup legend
1451  canvas.cd()
1452  if len(self._plots) <= 4:
1453  lx1 = 0.2
1454  lx2 = 0.9
1455  ly1 = 0.48
1456  ly2 = 0.53
1457  else:
1458  lx1 = 0.1
1459  lx2 = 0.9
1460  ly1 = 0.64
1461  ly2 = 0.67
1462  if self._legendDx is not None:
1463  lx1 += self._legendDx
1464  lx2 += self._legendDx
1465  if self._legendDy is not None:
1466  ly1 += self._legendDy
1467  ly2 += self._legendDy
1468  if self._legendDw is not None:
1469  lx2 += self._legendDw
1470  if self._legendDh is not None:
1471  ly1 -= self._legendDh
1472  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
1473  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
1474  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
1475  denomUncertainty=(ratio and denomUnc))
1476 
1477  return self._save(canvas, saveFormat, prefix=prefix)
1478 
1479  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio):
1480  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
1481  width = 500
1482  height = 500
1483  if ratio:
1484  height = int(height*self._ratioFactor)
1485 
1486  canvas = ROOT.TCanvas(self._name+"Single", self._name, width, height)
1487  # from TDRStyle
1488  canvas.SetTopMargin(0.05)
1489  canvas.SetBottomMargin(0.13)
1490  canvas.SetLeftMargin(0.16)
1491  canvas.SetRightMargin(0.05)
1492 
1493  lx1def = 0.6
1494  lx2def = 0.95
1495  ly1def = 0.85
1496  ly2def = 0.95
1497 
1498  ret = []
1499 
1500  for plot in self._plots:
1501  if plot.isEmpty():
1502  continue
1503 
1504  if ratio:
1505  canvas.cd()
1506  self._modifyPadForRatio(canvas)
1507 
1508  # Draw plot to canvas
1509  canvas.cd()
1510  plot.draw(canvas, ratio, self._ratioFactor, 1)
1511 
1512 
1513  # Setup legend
1514  lx1 = lx1def
1515  lx2 = lx2def
1516  ly1 = ly1def
1517  ly2 = ly2def
1518 
1519  if plot._legendDx is not None:
1520  lx1 += plot._legendDx
1521  lx2 += plot._legendDx
1522  if plot._legendDy is not None:
1523  ly1 += plot._legendDy
1524  ly2 += plot._legendDy
1525  if plot._legendDw is not None:
1526  lx2 += plot._legendDw
1527  if plot._legendDh is not None:
1528  ly1 -= plot._legendDh
1529 
1530  canvas.cd()
1531  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
1532  denomUncertainty=(ratio and plot.drawRatioUncertainty))
1533 
1534  ret.extend(self._save(canvas, saveFormat, prefix=prefix, postfix="_"+plot.getName(), single=True))
1535  return ret
1536 
1537  def _modifyPadForRatio(self, pad):
1538  """Internal method to set divide a pad to two for ratio plots"""
1539  pad.Divide(1, 2)
1540 
1541  divisionPoint = 1-1/self._ratioFactor
1542 
1543  topMargin = pad.GetTopMargin()
1544  bottomMargin = pad.GetBottomMargin()
1545  divisionPoint += (1-divisionPoint)*bottomMargin # correct for (almost-)zeroing bottom margin of pad1
1546  divisionPointForPad1 = 1-( (1-divisionPoint) / (1-0.02) ) # then correct for the non-zero bottom margin, but for pad1 only
1547 
1548  # Set the lower point of the upper pad to divisionPoint
1549  pad1 = pad.cd(1)
1550  yup = 1.0
1551  ylow = divisionPointForPad1
1552  xup = 1.0
1553  xlow = 0.0
1554  pad1.SetPad(xlow, ylow, xup, yup)
1555  pad1.SetFillStyle(4000) # transparent
1556  pad1.SetBottomMargin(0.02) # need some bottom margin here for eps/pdf output (at least in ROOT 5.34)
1557 
1558  # Set the upper point of the lower pad to divisionPoint
1559  pad2 = pad.cd(2)
1560  yup = divisionPoint
1561  ylow = 0.0
1562  pad2.SetPad(xlow, ylow, xup, yup)
1563  pad2.SetFillStyle(4000) # transparent
1564  pad2.SetTopMargin(0.0)
1565  pad2.SetBottomMargin(bottomMargin/(self._ratioFactor*divisionPoint))
1566 
1567  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
1568  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
1569  l.SetTextSize(textSize)
1570  l.SetLineColor(1)
1571  l.SetLineWidth(1)
1572  l.SetLineStyle(1)
1573  l.SetFillColor(0)
1574  l.SetMargin(0.07)
1575 
1576  plot.addToLegend(l, legendLabels, denomUncertainty)
1577  l.Draw()
1578  return l
1579 
1580  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False):
1581  # Save the canvas to file and clear
1582  name = self._name
1583  if prefix is not None:
1584  name = prefix+name
1585  if postfix is not None:
1586  name = name+postfix
1587  canvas.SaveAs(name+saveFormat)
1588  if single:
1589  canvas.Clear()
1590  canvas.SetLogx(False)
1591  canvas.SetLogy(False)
1592  else:
1593  canvas.Clear("D") # keep subpads
1594 
1595  return [name+saveFormat]
1596 
1598  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
1599  def __init__(self, *plotGroups, **kwargs):
1600  """Constructor.
1601 
1602  Arguments:
1603  plotGroups -- List of PlotGroup objects
1604 
1605  Keyword arguments
1606  loopSubFolders -- Should the subfolders be looped over? (default: True)
1607  onlyForPileup -- Plots this folder only for pileup samples
1608  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
1609  page -- Optional string for the page in HTML generatin
1610  section -- Optional string for the section within a page in HTML generation
1611  """
1612  self._plotGroups = plotGroups
1613  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
1614  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
1615  self._purpose = kwargs.pop("purpose", None)
1616  self._page = kwargs.pop("page", None)
1617  self._section = kwargs.pop("section", None)
1618  if len(kwargs) > 0:
1619  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
1620 
1621  def loopSubFolders(self):
1622  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
1623  return self._loopSubFolders
1624 
1625  def onlyForPileup(self):
1626  """Return True if the folder is intended only for pileup samples"""
1627  return self._onlyForPileup
1628 
1629  def getPurpose(self):
1630  return self._purpose
1631 
1632  def getPage(self):
1633  return self._page
1634 
1635  def getSection(self):
1636  return self._section
1637 
1638  def append(self, plotGroup):
1639  self._plotGroups.append(plotGroup)
1640 
1641  def set(self, plotGroups):
1642  self._plotGroups = plotGroups
1643 
1644  def create(self, files, labels, possibleDqmFolders, dqmSubFolder=None, isPileupSample=True, requireAllHistograms=False):
1645  """Create histograms from a list of TFiles.
1646 
1647  Arguments:
1648  files -- List of TFiles
1649  labels -- List of strings for legend labels corresponding the files
1650  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
1651  dqmSubFolder -- Optional string for subdirectory inside the dqmFolder; if list of strings, then each corresponds to a TFile
1652  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
1653  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
1654  """
1655 
1656  if len(files) != len(labels):
1657  raise Exception("len(files) should be len(labels), now they are %d and %d" % (len(files), len(labels)))
1658 
1659  dirs = []
1660  self._labels = []
1661  if isinstance(dqmSubFolder, list):
1662  if len(dqmSubFolder) != len(files):
1663  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(files), now they are %d and %d" % (len(dqmSubFolder), len(files)))
1664  else:
1665  dqmSubFolder = [dqmSubFolder]*len(files)
1666 
1667  for fil, sf in zip(files, dqmSubFolder):
1668  dirs.append(self._getDir(fil, possibleDqmFolders, sf))
1669  self._labels = labels
1670 
1671  for pg in self._plotGroups:
1672  if pg.onlyForPileup() and not isPileupSample:
1673  continue
1674  pg.create(dirs, requireAllHistograms)
1675 
1676  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=False):
1677  """Draw and save all plots using settings of a given algorithm.
1678 
1679  Arguments:
1680  prefix -- Optional string for file name prefix (default None)
1681  separate -- Save the plots of a group to separate files instead of a file per group (default False)
1682  saveFormat -- String specifying the plot format (default '.pdf')
1683  ratio -- Add ratio to the plot (default False)
1684  """
1685  ret = []
1686 
1687  for pg in self._plotGroups:
1688  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio))
1689  return ret
1690 
1691 
1692  def _getDir(self, tfile, possibleDqmFolders, dqmSubFolder):
1693  """Get TDirectory from TFile."""
1694  if tfile is None:
1695  return None
1696  for pdf in possibleDqmFolders:
1697  d = tfile.Get(pdf)
1698  if d:
1699  if dqmSubFolder is not None:
1700  # Pick associator if given
1701  d = d.Get(dqmSubFolder)
1702  if d:
1703  return d
1704  else:
1705  print "Did not find subdirectory '%s' from directory '%s' in file %s" % (dqmSubFolder, pdf, tfile.GetName())
1706  return None
1707  else:
1708  return d
1709  print "Did not find any of directories '%s' from file %s" % (",".join(possibleDqmFolders), tfile.GetName())
1710  return None
1711 
1712  # These are to be overridden by derived classes for customisation
1713  def translateSubFolder(self, dqmSubFolderName):
1714  """Method called to (possibly) translate a subfolder name to more 'readable' form
1715 
1716  The implementation in this (base) class just returns the
1717  argument. The idea is that a deriving class might want to do
1718  something more complex (like trackingPlots.TrackingPlotFolder
1719  does)
1720  """
1721  return dqmSubFolderName
1722 
1723  def getSelectionName(self, plotFolderName, translatedDqmSubFolder):
1724  """Get selection name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
1725  ret = ""
1726  if plotFolderName != "":
1727  ret += "_"+plotFolderName
1728  if translatedDqmSubFolder is not None:
1729  ret += "_"+translatedDqmSubFolder
1730  return ret
1731 
1732  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
1733  """Return True if this subfolder should be processed
1734 
1735  Arguments:
1736  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
1737  translatedDqmSubFolder -- Return value of translateSubFolder
1738  """
1739  return translatedDqmSubFolder in limitOnlyTo
1740 
1742  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
1743  def __init__(self, subfolder, translated):
1744  self.subfolder = subfolder
1745  self.translated = translated
1746 
1747  def equalTo(self, other):
1748  """Equality is defined by the 'translated' name"""
1749  return self.translated == other.translated
1750 
1752  """Plotter for one DQM folder.
1753 
1754  This class is supposed to be instantiated by the Plotter class (or
1755  PlotterItem, to be more specific), and not used directly by the
1756  user.
1757  """
1758  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames):
1759  """
1760  Constructor
1761 
1762  Arguments:
1763  name -- Name of the folder (is used in the output directory naming)
1764  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
1765  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
1766  plotFolder -- PlotFolder object
1767  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.
1768  """
1769  self._name = name
1770  self._possibleDqmFolders = possibleDqmFolders
1771  self._plotFolder = plotFolder
1772  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
1773  if dqmSubFolders is None:
1774  self._dqmSubFolders = None
1775  else:
1776  self._dqmSubFolders = map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), dqmSubFolders[0])
1777  self._dqmSubFolders = filter(lambda sf: sf.translated is not None, self._dqmSubFolders)
1778 
1779  self._fallbackNames = fallbackNames
1780 
1781  # TODO: matchmaking of dqmsubfolders in case of differences between files
1782 
1783  def getName(self):
1784  return self._name
1785 
1786  def getPurpose(self):
1787  return self._plotFolder.getPurpose()
1788 
1789  def getPage(self):
1790  return self._plotFolder.getPage()
1791 
1792  def getSection(self):
1793  return self._plotFolder.getSection()
1794 
1795  def onlyForPileup(self):
1796  return self._plotFolder.onlyForPileup()
1797 
1799  return self._possibleDqmFolders
1800 
1801  def getDQMSubFolders(self, limitOnlyTo=None):
1802  """Get list of subfolders, possibly limiting to some of them.
1803 
1804  Keyword arguments:
1805  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
1806  """
1807 
1808  if self._dqmSubFolders is None:
1809  return [None]
1810 
1811  if limitOnlyTo is None:
1812  return self._dqmSubFolders
1813 
1814  return filter(lambda s: self._plotFolder.limitSubFolder(limitOnlyTo, s.translated), self._dqmSubFolders)
1815 
1816  def getSelectionNameIterator(self, dqmSubFolder):
1817  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
1818  for name in [self._name]+self._fallbackNames:
1819  yield self._plotFolder.getSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None)
1820 
1821  def getSelectionName(self, dqmSubFolder):
1822  return self.getSelectionNameIterator(dqmSubFolder).next()
1823 
1824  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
1825  """Create histograms from a list of TFiles.
1826  Arguments:
1827  files -- List of TFiles
1828  labels -- List of strings for legend labels corresponding the files
1829  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
1830  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
1831  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
1832  """
1833 
1834  # TODO: for cases of differently named subfolders, need to think something here
1835  self._plotFolder.create(files, labels, self._possibleDqmFolders, dqmSubFolder.subfolder if dqmSubFolder is not None else None, isPileupSample, requireAllHistograms)
1836 
1837  def draw(self, *args, **kwargs):
1838  """Draw and save all plots using settings of a given algorithm."""
1839  return self._plotFolder.draw(*args, **kwargs)
1840 
1842  """Instance of plotter that knows the directory content, holds many folders."""
1843  def __init__(self, folders):
1844  self._plotterFolders = filter(lambda f: f is not None, folders)
1845 
1846  def iterFolders(self, limitSubFoldersOnlyTo=None):
1847  for plotterFolder in self._plotterFolders:
1848  limitOnlyTo = None
1849  if limitSubFoldersOnlyTo is not None:
1850  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
1851 
1852  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
1853  yield plotterFolder, dqmSubFolder
1854 
1855 # Helper for Plotter
1857  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[]):
1858  """ Constructor
1859 
1860  Arguments:
1861  name -- Name of the folder (is used in the output directory naming)
1862  possibleDirs -- List of strings for possible directories of histograms in TFiles
1863  plotFolder -- PlotFolder object
1864 
1865  Keyword arguments
1866  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.
1867  """
1868  self._name = name
1869  self._possibleDirs = possibleDirs
1870  self._plotFolder = plotFolder
1871  self._fallbackNames = fallbackNames
1872 
1873  def readDirs(self, files):
1874  """Read available subfolders from the files
1875 
1876  Arguments:
1877  files -- List of strings for paths to files, or list of TFiles
1878 
1879  For each file, loop over 'possibleDirs', and read the
1880  subfolders of first one that exists.
1881 
1882  Returns a PlotterFolder if at least one file for which one of
1883  'possibleDirs' exists. Otherwise, return None to signal that
1884  there is nothing available for this PlotFolder.
1885  """
1886  subFolders = None
1887  if self._plotFolder.loopSubFolders():
1888  subFolders = []
1889  possibleDirFound = False
1890  for fname in files:
1891  isOpenFile = isinstance(fname, ROOT.TFile)
1892  if isOpenFile:
1893  tfile = fname
1894  else:
1895  tfile = ROOT.TFile.Open(fname)
1896  for pd in self._possibleDirs:
1897  d = tfile.Get(pd)
1898  if d:
1899  possibleDirFound = True
1900  if subFolders is not None:
1901  subf = []
1902  for key in d.GetListOfKeys():
1903  if isinstance(key.ReadObj(), ROOT.TDirectory):
1904  subf.append(key.GetName())
1905  subFolders.append(subf)
1906  break
1907 
1908  if not isOpenFile:
1909  tfile.Close()
1910 
1911  if not possibleDirFound:
1912  return None
1913 
1914  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames)
1915 
1916 class Plotter:
1917  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
1918  def __init__(self):
1919  self._plots = []
1920 
1921  _absoluteSize = True
1922  if _absoluteSize:
1923  font = 43
1924  titleSize = 22
1925  labelSize = 22
1926  statSize = 14
1927  else:
1928  font = 42
1929  titleSize = 0.05
1930  labelSize = 0.05
1931  statSize = 0.025
1932 
1933  ROOT.gROOT.SetStyle("Plain")
1934  ROOT.gStyle.SetPadRightMargin(0.07)
1935  ROOT.gStyle.SetPadLeftMargin(0.13)
1936  ROOT.gStyle.SetTitleFont(font, "XYZ")
1937  ROOT.gStyle.SetTitleSize(titleSize, "XYZ")
1938  ROOT.gStyle.SetTitleOffset(1.2, "Y")
1939  #ROOT.gStyle.SetTitleFontSize(0.05)
1940  ROOT.gStyle.SetLabelFont(font, "XYZ")
1941  ROOT.gStyle.SetLabelSize(labelSize, "XYZ")
1942  ROOT.gStyle.SetTextSize(labelSize)
1943  ROOT.gStyle.SetStatFont(font)
1944  ROOT.gStyle.SetStatFontSize(statSize)
1945 
1946  ROOT.TH1.AddDirectory(False)
1947 
1948  def append(self, *args, **kwargs):
1949  """Append a plot folder to the plotter.
1950 
1951  All arguments are forwarded to the constructor of PlotterItem.
1952  """
1953  self._plots.append(PlotterItem(*args, **kwargs))
1954 
1955  def readDirs(self, *files):
1956  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
1957  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def _getYmin
Definition: plotting.py:37
def _getXmax
Definition: plotting.py:29
def setXLabelSize
Definition: plotting.py:528
def _createOne
Definition: plotting.py:921
def _getYmaxWithError
Definition: plotting.py:51
def setXTitleOffset
Definition: plotting.py:525
def _getYminIgnoreOutlier
Definition: plotting.py:54
def _getXmin
Definition: plotting.py:21
def _getYmax
Definition: plotting.py:44
def adjustMarginLeft
Definition: plotting.py:504
def isTGraph2D
Definition: plotting.py:905
def _findBounds
Definition: plotting.py:71
def _drawFrame
Definition: plotting.py:437
def drawRatioUncertainty
Definition: plotting.py:917
#define end
Definition: vmac.h:37
T min(T a, T b)
Definition: MathUtil.h:58
def adjustMarginRight
Definition: plotting.py:510
def __init__
Definition: plotting.py:377
def _getObject
Definition: plotting.py:9
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def getNumberOfHistograms
Definition: plotting.py:897
def _getOrCreateObject
Definition: plotting.py:16
def _copyStyle
Definition: plotting.py:770
#define begin
Definition: vmac.h:30
def _calculateRatios
Definition: plotting.py:1196
def setYTitleSize
Definition: plotting.py:534
def setYTitleOffset
Definition: plotting.py:537
auto wrap(F iFunc) -> decltype(iFunc())
def setXTitleSize
Definition: plotting.py:522