1 from __future__
import print_function
2 from __future__
import absolute_import
3 from builtins
import range
14 ROOT.gROOT.SetBatch(
True)
15 ROOT.PyConfig.IgnoreCommandLineOptions =
True
20 _ratioYTitle =
"Ratio"
35 ROOT.gROOT.SetStyle(
"Plain")
36 ROOT.gStyle.SetPadRightMargin(0.07)
37 ROOT.gStyle.SetPadLeftMargin(0.13)
38 ROOT.gStyle.SetTitleFont(font,
"XYZ")
39 ROOT.gStyle.SetTitleSize(titleSize,
"XYZ")
40 ROOT.gStyle.SetTitleOffset(1.2,
"Y")
42 ROOT.gStyle.SetLabelFont(font,
"XYZ")
43 ROOT.gStyle.SetLabelSize(labelSize,
"XYZ")
44 ROOT.gStyle.SetTextSize(labelSize)
45 ROOT.gStyle.SetStatFont(font)
46 ROOT.gStyle.SetStatFontSize(statSize)
48 ROOT.TGaxis.SetMaxDigits(4)
51 obj = tdirectory.Get(name)
54 print(
"Did not find {obj} from {dir}".
format(obj=name, dir=tdirectory.GetPath()))
59 if hasattr(nameOrCreator,
"create"):
60 return nameOrCreator.create(tdirectory)
75 """Get TDirectory from TFile."""
78 for pdf
in possibleDirs:
81 if subDir
is not None:
88 print(
"Did not find subdirectory '%s' from directory '%s' in file %s" % (subDir, pdf, tfile.GetName()))
95 print(
"Did not find any of directories '%s' from file %s" % (
",".
join(possibleDirs), tfile.GetName()))
102 values = collections.OrderedDict()
103 for i
in range(1, th1.GetNbinsX()+1):
104 binLabel = th1.GetXaxis().GetBinLabel(i)
105 if renameBin
is not None:
106 binLabel = renameBin(binLabel)
107 values[binLabel] = (th1.GetBinContent(i), th1.GetBinError(i))
113 backup = ROOT.gErrorIgnoreLevel
114 ROOT.gErrorIgnoreLevel = ROOT.kError
115 canvas = ROOT.TCanvas(name, name, width, height)
117 ROOT.gErrorIgnoreLevel = backup
123 divisionPoint = 1-1/ratioFactor
125 topMargin = pad.GetTopMargin()
126 bottomMargin = pad.GetBottomMargin()
127 divisionPoint += (1-divisionPoint)*bottomMargin
128 divisionPointForPad1 = 1-( (1-divisionPoint) / (1-0.02) )
133 ylow = divisionPointForPad1
136 pad1.SetPad(xlow, ylow, xup, yup)
137 pad1.SetFillStyle(4000)
138 pad1.SetBottomMargin(0.02)
144 pad2.SetPad(xlow, ylow, xup, yup)
145 pad2.SetFillStyle(4000)
146 pad2.SetTopMargin(0.0)
147 pad2.SetBottomMargin(bottomMargin/(ratioFactor*divisionPoint))
150 """Calculate the ratios for a list of histograms"""
152 def _divideOrZero(numerator, denominator):
155 return numerator/denominator
158 if a == 0.
and b == 0.:
162 def findBins(wrap, bins_xvalues):
164 currBin = wrap.begin()
166 while i < len(bins_xvalues)
and currBin < wrap.end():
167 (xcenter, xlow, xhigh) = bins_xvalues[i]
168 xlowEdge = xcenter-xlow
169 xupEdge = xcenter+xhigh
171 (curr_center, curr_low, curr_high) = wrap.xvalues(currBin)
172 curr_lowEdge = curr_center-curr_low
173 curr_upEdge = curr_center+curr_high
175 if equal(xlowEdge, curr_lowEdge)
and equal(xupEdge, curr_upEdge):
179 elif curr_upEdge <= xlowEdge:
181 elif curr_lowEdge >= xupEdge:
188 if len(ret) != len(bins_xvalues):
189 ret.extend([
None]*( len(bins_xvalues) - len(ret) ))
195 def __init__(self, th1, uncertainty):
197 self._uncertainty = uncertainty
199 xaxis = th1.GetXaxis()
200 xaxis_arr = xaxis.GetXbins()
201 if xaxis_arr.GetSize() > 0:
202 lst = [xaxis_arr[i]
for i
in range(0, xaxis_arr.GetSize())]
203 arr = array.array(
"d", lst)
204 self._ratio = ROOT.TH1F(
"foo",
"foo", xaxis.GetNbins(), arr)
206 self._ratio = ROOT.TH1F(
"foo",
"foo", xaxis.GetNbins(), xaxis.GetXmin(), xaxis.GetXmax())
208 self._ratio.SetStats(0)
209 self._ratio.SetLineColor(ROOT.kBlack)
210 self._ratio.SetLineWidth(1)
211 def draw(self, style=None):
214 if self._uncertainty:
218 self._ratio.Draw(
"same "+st)
222 return self._th1.GetNbinsX()+1
223 def xvalues(self, bin):
224 xval = self._th1.GetBinCenter(bin)
225 xlow = xval-self._th1.GetXaxis().GetBinLowEdge(bin)
226 xhigh = self._th1.GetXaxis().GetBinUpEdge(bin)-xval
227 return (xval, xlow, xhigh)
228 def yvalues(self, bin):
229 yval = self._th1.GetBinContent(bin)
230 yerr = self._th1.GetBinError(bin)
231 return (yval, yerr, yerr)
233 return self._th1.GetBinContent(bin)
234 def divide(self, bin, scale):
235 self._ratio.SetBinContent(bin, _divideOrZero(self._th1.GetBinContent(bin), scale))
236 self._ratio.SetBinError(bin, _divideOrZero(self._th1.GetBinError(bin), scale))
243 def __init__(self, gr, uncertainty):
245 self._uncertainty = uncertainty
252 def draw(self, style=None):
253 if self._ratio
is None:
257 if self._uncertainty:
261 self._ratio.Draw(
"same "+st)
265 return self._gr.GetN()
266 def xvalues(self, bin):
267 return (self._gr.GetX()[bin], self._gr.GetErrorXlow(bin), self._gr.GetErrorXhigh(bin))
268 def yvalues(self, bin):
269 return (self._gr.GetY()[bin], self._gr.GetErrorYlow(bin), self._gr.GetErrorYhigh(bin))
271 return self._gr.GetY()[bin]
272 def divide(self, bin, scale):
277 if bin >= self._gr.GetN():
280 xvals = self.xvalues(bin)
283 self._xvalues.
append(xval)
284 self._xerrslow.
append(xvals[1])
285 self._xerrshigh.
append(xvals[2])
286 yvals = self.yvalues(bin)
287 self._yvalues.
append(yvals[0] / scale)
288 if self._uncertainty:
289 self._yerrslow.
append(yvals[1] / scale)
290 self._yerrshigh.
append(yvals[2] / scale)
295 if len(self._xvalues) == 0:
298 self._ratio = ROOT.TGraphAsymmErrors(len(self._xvalues), array.array(
"d", self._xvalues), array.array(
"d", self._yvalues),
299 array.array(
"d", self._xerrslow), array.array(
"d", self._xerrshigh),
300 array.array(
"d", self._yerrslow), array.array(
"d", self._yerrshigh))
304 class WrapTGraph2D(WrapTGraph):
305 def __init__(self, gr, uncertainty):
306 WrapTGraph.__init__(self, gr, uncertainty)
307 def xvalues(self, bin):
308 return (self._gr.GetX()[bin], self._gr.GetErrorX(bin), self._gr.GetErrorX(bin))
309 def yvalues(self, bin):
310 return (self._gr.GetY()[bin], self._gr.GetErrorY(bin), self._gr.GetErrorY(bin))
313 if isinstance(o, ROOT.TH1):
314 return WrapTH1(o, ratioUncertainty)
315 elif isinstance(o, ROOT.TGraph):
316 return WrapTGraph(o, ratioUncertainty)
317 elif isinstance(o, ROOT.TGraph2D):
318 return WrapTGraph2D(o, ratioUncertainty)
320 wrappers = [
wrap(h)
for h
in histos]
324 ref_bins = [ref.xvalues(b)
for b
in range(ref.begin(), ref.end())]
326 wrappers_bins.append(findBins(w, ref_bins))
328 for i, bin
in enumerate(
range(ref.begin(), ref.end())):
329 (scale, ylow, yhigh) = ref.yvalues(bin)
330 for w, bins
in zip(wrappers, wrappers_bins):
333 w.divide(bins[i], scale)
342 if isinstance(obj, ROOT.TH1):
343 xaxis = obj.GetXaxis()
344 if limitToNonZeroContent:
345 for i
in range(1, obj.GetNbinsX()+1):
346 if obj.GetBinContent(i) != 0:
347 return xaxis.GetBinLowEdge(i)
351 return xaxis.GetBinLowEdge(xaxis.GetFirst())
352 elif isinstance(obj, ROOT.TGraph)
or isinstance(obj, ROOT.TGraph2D):
353 m =
min([obj.GetX()[i]
for i
in range(0, obj.GetN())])
354 return m*0.9
if m > 0
else m*1.1
358 if isinstance(obj, ROOT.TH1):
359 xaxis = obj.GetXaxis()
360 if limitToNonZeroContent:
361 for i
in range(obj.GetNbinsX(), 0, -1):
362 if obj.GetBinContent(i) != 0:
363 return xaxis.GetBinUpEdge(i)
367 return xaxis.GetBinUpEdge(xaxis.GetLast())
368 elif isinstance(obj, ROOT.TGraph)
or isinstance(obj, ROOT.TGraph2D):
369 m =
max([obj.GetX()[i]
for i
in range(0, obj.GetN())])
370 return m*1.1
if m > 0
else m*0.9
374 if isinstance(obj, ROOT.TH2):
375 yaxis = obj.GetYaxis()
376 return yaxis.GetBinLowEdge(yaxis.GetFirst())
377 elif isinstance(obj, ROOT.TH1):
378 if limitToNonZeroContent:
379 lst = [obj.GetBinContent(i)
for i
in range(1, obj.GetNbinsX()+1)
if obj.GetBinContent(i) != 0 ]
380 return min(lst)
if len(lst) != 0
else 0
382 return obj.GetMinimum()
383 elif isinstance(obj, ROOT.TGraph)
or isinstance(obj, ROOT.TGraph2D):
384 m =
min([obj.GetY()[i]
for i
in range(0, obj.GetN())])
385 return m*0.9
if m > 0
else m*1.1
389 if isinstance(obj, ROOT.TH2):
390 yaxis = obj.GetYaxis()
391 return yaxis.GetBinUpEdge(yaxis.GetLast())
392 elif isinstance(obj, ROOT.TH1):
393 if limitToNonZeroContent:
394 lst = [obj.GetBinContent(i)
for i
in range(1, obj.GetNbinsX()+1)
if obj.GetBinContent(i) != 0 ]
395 return max(lst)
if len(lst) != 0
else 0
397 return obj.GetMaximum()
398 elif isinstance(obj, ROOT.TGraph)
or isinstance(obj, ROOT.TGraph2D):
399 m =
max([obj.GetY()[i]
for i
in range(0, obj.GetN())])
400 return m*1.1
if m > 0
else m*0.9
404 return max([th1.GetBinContent(i)+th1.GetBinError(i)
for i
in range(1, th1.GetNbinsX()+1)])
407 yvals = sorted([n
for n
in [th1.GetBinContent(i)
for i
in range(1, th1.GetNbinsX()+1)]
if n>0])
409 return th1.GetMinimum()
414 ind_min = len(yvals)-1 -
int(len(yvals)*0.95)
415 min_val = yvals[ind_min]
416 for i
in range(0, ind_min):
417 if yvals[i] > 0.1*min_val:
423 inRange =
lambda x:
True
424 inRange2 =
lambda xmin,xmax:
True
426 inRange =
lambda x: coverageRange[0] <= x <= coverageRange[1]
427 inRange2 =
lambda xmin,xmax: coverageRange[0] <= xmin
and xmax <= coverageRange[1]
429 if isinstance(obj, ROOT.TH1):
430 yvals = [obj.GetBinContent(i)
for i
in range(1, obj.GetNbinsX()+1)
if inRange2(obj.GetXaxis().GetBinLowEdge(i), obj.GetXaxis().GetBinUpEdge(i))]
431 yvals = [x
for x
in yvals
if x != 0]
432 elif isinstance(obj, ROOT.TGraph)
or isinstance(obj, ROOT.TGraph2D):
433 yvals = [obj.GetY()[i]
for i
in range(0, obj.GetN())
if inRange(obj.GetX()[i])]
439 return (yvals[0], yvals[0])
441 return (yvals[0], yvals[1])
444 nvals =
int(len(yvals)*coverage)
447 if len(yvals) % 2 == 0:
449 return ( yvals[half-1], yvals[half] )
451 middle = len(yvals)/2
452 return ( yvals[middle-1], yvals[middle+1] )
453 ind_min = (len(yvals)-nvals)/2
454 ind_max = len(yvals)-1 - ind_min
456 return (yvals[ind_min], yvals[ind_max])
458 def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None):
459 """Find x-y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
463 ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
466 xmin -- Minimum x value; if None, take the minimum of TH1s
467 xmax -- Maximum x value; if None, take the maximum of TH1s
468 ymin -- Minimum y value; if None, take the minimum of TH1s
469 ymax -- Maximum y value; if None, take the maximum of TH1s
474 if xmin
is None or xmax
is None or isinstance(xmin, list)
or isinstance(max, list):
478 xmins.append(
_getXmin(th1, limitToNonZeroContent=isinstance(xmin, list)))
479 xmaxs.append(
_getXmax(th1, limitToNonZeroContent=isinstance(xmax, list)))
482 xmins = [h
for h
in xmins
if h
is not None]
483 xmaxs = [h
for h
in xmaxs
if h
is not None]
487 elif isinstance(xmin, list):
491 print(
"Histogram is zero, using the smallest given value for xmin from",
str(xmin))
494 xmins_below = [x
for x
in xmin
if x<=xm]
495 if len(xmins_below) == 0:
499 print(
"Histogram minimum x %f is below all given xmin values %s, using the smallest one" % (xm,
str(xmin)))
501 xmin =
max(xmins_below)
505 elif isinstance(xmax, list):
509 print(
"Histogram is zero, using the smallest given value for xmax from",
str(xmin))
512 xmaxs_above = [x
for x
in xmax
if x>xm]
513 if len(xmaxs_above) == 0:
517 print(
"Histogram maximum x %f is above all given xmax values %s, using the maximum one" % (xm,
str(xmax)))
519 xmax =
min(xmaxs_above)
522 th1.GetXaxis().SetRangeUser(xmin, xmax)
524 return (xmin, ymin, xmax, ymax)
526 def _findBoundsY(th1s, ylog, ymin=None, ymax=None, coverage=None, coverageRange=None):
527 """Find y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
531 ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
534 ymin -- Minimum y value; if None, take the minimum of TH1s
535 ymax -- Maximum y value; if None, take the maximum of TH1s
536 coverage -- If set, use only values within the 'coverage' part around the median are used for min/max (useful for ratio)
537 coverageRange -- If coverage and this are set, use only the x axis specified by an (xmin,xmax) pair for the coverage
539 if coverage
is not None or isinstance(th1s[0], ROOT.TH2):
545 y_scale_max =
lambda y: y
546 y_scale_min =
lambda y: y
549 y_scale_max =
lambda y: y*1.5
551 y_scale_max =
lambda y: y*1.05
552 y_scale_min =
lambda y: y*0.9
554 if ymin
is None or ymax
is None or isinstance(ymin, list)
or isinstance(ymax, list):
558 if coverage
is not None:
561 if ylog
and isinstance(ymin, list):
564 _ymin =
_getYmin(th1, limitToNonZeroContent=isinstance(ymin, list))
565 _ymax =
_getYmax(th1, limitToNonZeroContent=isinstance(ymax, list))
573 elif isinstance(ymin, list):
574 ym_unscaled =
min(ymins)
575 ym = y_scale_min(ym_unscaled)
576 ymins_below = [y
for y
in ymin
if y<=ym]
577 if len(ymins_below) == 0:
579 if ym_unscaled < ymin:
581 print(
"Histogram minimum y %f is below all given ymin values %s, using the smallest one" % (ym,
str(ymin)))
583 ymin =
max(ymins_below)
588 ymax = y_scale_max(
max(ymaxs+[ymin]))
589 elif isinstance(ymax, list):
590 ym_unscaled =
max(ymaxs)
591 ym = y_scale_max(ym_unscaled)
592 ymaxs_above = [y
for y
in ymax
if y>ym]
593 if len(ymaxs_above) == 0:
595 if ym_unscaled > ymax:
597 print(
"Histogram maximum y %f is above all given ymax values %s, using the maximum one" % (ym_unscaled,
str(ymax)))
599 ymax =
min(ymaxs_above)
602 th1.GetYaxis().SetRangeUser(ymin, ymax)
608 for b
in range(1, histos[0].GetNbinsX()+1):
611 if h.GetBinContent(b) > 0:
617 if len(binsToRemove) > 0:
620 for i
in range(len(xbinlabels)):
621 if (i+1)
not in binsToRemove:
622 xbinlab_new.append(xbinlabels[i])
623 xbinlabels = xbinlab_new
629 for b
in range(1, h.GetNbinsX()+1):
630 if b
not in binsToRemove:
631 values.append( (h.GetXaxis().GetBinLabel(b), h.GetBinContent(b), h.GetBinError(b)) )
634 h_new = h.Clone(h.GetName()+
"_empty")
635 h_new.SetBins(len(values), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(values))
636 for b, (l, v, e)
in enumerate(values):
637 h_new.GetXaxis().SetBinLabel(b+1, l)
638 h_new.SetBinContent(b+1, v)
639 h_new.SetBinError(b+1, e)
641 histos_new.append(h_new)
644 return (histos, xbinlabels)
647 xbinsToRemove = set()
648 ybinsToRemove = set()
649 for ih, h
in enumerate(histos):
650 for bx
in range(1, h.GetNbinsX()+1):
652 for by
in range(1, h.GetNbinsY()+1):
653 if h.GetBinContent(bx, by) > 0:
657 xbinsToRemove.add(bx)
659 xbinsToRemove.discard(bx)
661 for by
in range(1, h.GetNbinsY()+1):
663 for bx
in range(1, h.GetNbinsX()+1):
664 if h.GetBinContent(bx, by) > 0:
668 ybinsToRemove.add(by)
670 ybinsToRemove.discard(by)
672 if len(xbinsToRemove) > 0
or len(ybinsToRemove) > 0:
675 for b
in range(1, len(xbinlabels)+1):
676 if b
not in xbinsToRemove:
677 xbinlabels_new.append(histos[0].GetXaxis().GetBinLabel(b))
679 xbinlabels = xbinlabels_new
682 for b
in range(1, len(ybinlabels)+1):
683 if b
not in ybinsToRemove:
684 ybinlabels.append(histos[0].GetYaxis().GetBinLabel(b))
686 ybinlabels = xbinlabels_new
689 if len(xbinlabels) == 0
or len(ybinlabels) == 0:
690 return (histos_new, xbinlabels, ybinlabels)
692 h_new = ROOT.TH2F(h.GetName()+
"_empty", h.GetTitle(), len(xbinlabels),0,len(xbinlabels), len(ybinlabels),0,len(ybinlabels))
693 for b, l
in enumerate(xbinlabels):
694 h_new.GetXaxis().SetBinLabel(b+1, l)
695 for b, l
in enumerate(ybinlabels):
696 h_new.GetYaxis().SetBinLabel(b+1, l)
698 for ix, bx
in enumerate(xbins):
699 for iy, by
in enumerate(ybins):
700 h_new.SetBinContent(ix+1, iy+1, h.GetBinContent(bx, by))
701 h_new.SetBinError(ix+1, iy+1, h.GetBinError(bx, by))
702 histos_new.append(h_new)
704 return (histos, xbinlabels, ybinlabels)
707 return _mergeBinLabels([[h.GetXaxis().GetBinLabel(i)
for i
in range(1, h.GetNbinsX()+1)]
for h
in histos])
710 return _mergeBinLabels([[h.GetYaxis().GetBinLabel(i)
for i
in range(1, h.GetNbinsY()+1)]
for h
in histos])
713 labels_merged = labelsAll[0]
714 for labels
in labelsAll[1:]:
715 diff = difflib.unified_diff(labels_merged, labels, n=
max(len(labels_merged), len(labels)))
722 operation.append(item[0])
724 if lab
in labels_merged:
726 ind = labels_merged.index(lab)
727 if operation[ind] ==
"-" and operation[-1] ==
"+":
728 labels_merged.remove(lab)
730 elif operation[ind] ==
"+" and operation[-1] ==
"-":
734 raise Exception(
"This should never happen")
735 labels_merged.append(lab)
738 if len(labels_merged) == 0:
739 labels_merged = labels
746 h_new = h.Clone(h.GetName()+
"_xbinlabels")
747 h_new.SetBins(len(xbinlabels), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(xbinlabels))
748 for i, label
in enumerate(xbinlabels):
749 bin = h.GetXaxis().FindFixBin(label)
751 h_new.SetBinContent(i+1, h.GetBinContent(bin))
752 h_new.SetBinError(i+1, h.GetBinError(bin))
754 h_new.SetBinContent(i+1, 0)
755 h_new.SetBinError(i+1, 0)
756 histos_new.append(h_new)
761 """Class for subtracting two histograms"""
766 name -- String for name of the resulting histogram (A-B)
767 nameA -- String for A histogram
768 nameB -- String for B histogram
771 title -- String for a title of the resulting histogram (default "")
773 Uncertainties are calculated with the assumption that B is a
774 subset of A, and the histograms contain event counts.
782 """String representation, returns the name"""
786 """Create and return the fake+duplicate histogram from a TDirectory"""
790 if not histoA
or not histoB:
793 ret = histoA.Clone(self.
_name)
799 ret.SetCanExtend(
False)
801 for i
in range(0, histoA.GetNbinsX()+2):
802 val = histoA.GetBinContent(i)-histoB.GetBinContent(i)
803 ret.SetBinContent(i, val)
804 ret.SetBinError(i, math.sqrt(val))
809 """Class to transform bin contents in an arbitrary way."""
814 name -- String for name of the resulting histogram
815 histo -- String for a source histogram (needs to be cumulative)
816 func -- Function to operate on the bin content
824 """String representation, returns the name"""
828 """Create and return the transformed histogram from a TDirectory"""
833 ret = histo.Clone(self.
_name)
839 ret.SetCanExtend(
False)
841 for i
in range(0, histo.GetNbinsX()+2):
842 ret.SetBinContent(i, self.
_func(histo.GetBinContent(i)))
846 """Class to calculate the fake+duplicate rate"""
847 def __init__(self, name, assoc, dup, reco, title=""):
851 name -- String for the name of the resulting efficiency histogram
852 assoc -- String for the name of the "associated" histogram
853 dup -- String for the name of the "duplicates" histogram
854 reco -- String for the name of the "reco" (denominator) histogram
857 title -- String for a title of the resulting histogram (default "")
859 The result is calculated as 1 - (assoc - dup) / reco
868 """String representation, returns the name"""
872 """Create and return the fake+duplicate histogram from a TDirectory"""
879 if not hassoc
or not hdup
or not hreco:
882 hfakedup = hreco.Clone(self.
_name)
883 hfakedup.SetTitle(self.
_title)
885 for i
in range(1, hassoc.GetNbinsX()+1):
886 numerVal = hassoc.GetBinContent(i) - hdup.GetBinContent(i)
887 denomVal = hreco.GetBinContent(i)
889 fakedupVal = (1 - numerVal / denomVal)
if denomVal != 0.0
else 0.0
890 errVal = math.sqrt(fakedupVal*(1-fakedupVal)/denomVal)
if (denomVal != 0.0
and fakedupVal <= 1)
else 0.0
892 hfakedup.SetBinContent(i, fakedupVal)
893 hfakedup.SetBinError(i, errVal)
898 """Class for making a cut efficiency histograms.
908 name -- String for name of the resulting histogram
909 histo -- String for a source histogram (needs to be cumulative)
916 """String representation, returns the name"""
920 """Create and return the cut efficiency histogram from a TDirectory"""
926 ascending = histo.GetBinContent(0) < histo.GetBinContent(histo.GetNbinsX())
928 n_tot = histo.GetBinContent(histo.GetNbinsX())
930 n_tot = histo.GetBinContent(0)
935 ret = histo.Clone(self.
_name)
939 for i
in range(1, histo.GetNbinsX()+1):
940 n = histo.GetBinContent(i)
942 errVal = math.sqrt(val*(1-val)/n_tot)
943 ret.SetBinContent(i, val)
944 ret.SetBinError(i, errVal)
948 """Class to create a histogram by aggregating bins of another histogram to a bin of the resulting histogram."""
949 def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None, ignoreMissingBins=False, minExistingBins=None, originalOrder=False, reorder=None):
953 name -- String for the name of the resulting histogram
954 histoName -- String for the name of the source histogram
955 mapping -- Dictionary for mapping the bins (see below)
958 normalizeTo -- Optional string of a bin label in the source histogram. If given, all bins of the resulting histogram are divided by the value of this bin.
959 scale -- Optional number for scaling the histogram (passed to ROOT.TH1.Scale())
960 renameBin -- Optional function (string -> string) to rename the bins of the input histogram
961 originalOrder -- Boolean for using the order of bins in the histogram (default False)
962 reorder -- Optional function to reorder the bins
964 Mapping structure (mapping):
966 Dictionary (you probably want to use collections.OrderedDict)
967 should be a mapping from the destination bin label to a list
968 of source bin labels ("dst -> [src]").
981 raise Exception(
"reorder is not None and originalOrder is True, please set only one of them")
984 """String representation, returns the name"""
988 """Create and return the histogram from a TDirectory"""
994 binValues = [
None]*len(self.
_mapping)
1000 for i, (key, labels)
in enumerate(six.iteritems(self.
_mapping)):
1006 sumTime += values[l][0]
1007 sumErrorSq += values[l][1]**2
1013 binValues[i] = (sumTime, math.sqrt(sumErrorSq))
1016 ivalue = len(values)+1
1022 ivalue = values.keys().
index(lab)
1024 binIndexOrder.append( (ivalue, i) )
1027 binIndexOrder.sort(key=
lambda t: t[0])
1030 for i
in range(0, len(binValues)):
1031 fromIndex = binIndexOrder[i][1]
1032 tmpVal.append(binValues[fromIndex])
1033 tmpLab.append(binLabels[fromIndex])
1037 order = self.
_reorder(tdirectory, binLabels)
1038 binValues = [binValues[i]
for i
in order]
1039 binLabels = [binLabels[i]
for i
in order]
1045 for i, val
in enumerate(binValues):
1048 binValues = [v
for v
in binValues
if v
is not None]
1049 binLabels = [v
for v
in binLabels
if v
is not None]
1050 if len(binValues) == 0:
1053 result = ROOT.TH1F(self.
_name, self.
_name, len(binValues), 0, len(binValues))
1054 for i, (value, label)
in enumerate(
zip(binValues, binLabels)):
1055 if value
is not None:
1056 result.SetBinContent(i+1, value[0])
1057 result.SetBinError(i+1, value[1])
1058 result.GetXaxis().SetBinLabel(i+1, label)
1065 value = th1.GetBinContent(bin)
1067 result.Scale(1/value)
1069 if self.
_scale is not None:
1070 result.Scale(self.
_scale)
1075 """Class to create a histogram by aggregaging integrals of another histoggrams."""
1080 name -- String for the name of the resulting histogram
1081 mapping -- Dictionary for mapping the bin label to a histogram name
1084 normalizeTo -- Optional string for a histogram. If given, all bins of the resulting histograqm are divided by the integral of this histogram.
1091 """String representation, returns the name"""
1095 """Create and return the histogram from a TDirectory"""
1097 for key, histoName
in six.iteritems(self.
_mapping):
1101 result.append( (key, th1.Integral(0, th1.GetNbinsX()+1)) )
1102 if len(result) == 0:
1105 res = ROOT.TH1F(self.
_name, self.
_name, len(result), 0, len(result))
1107 for i, (name, count)
in enumerate(result):
1108 res.SetBinContent(i+1, count)
1109 res.GetXaxis().SetBinLabel(i+1, name)
1115 scale = th1.Integral(0, th1.GetNbinsX()+1)
1121 """Class to construct a ROC curve (e.g. efficiency vs. fake rate) from two histograms"""
1122 def __init__(self, name, xhistoName, yhistoName, zaxis=False):
1126 name -- String for the name of the resulting histogram
1127 xhistoName -- String for the name of the x-axis histogram (or another "creator" object)
1128 yhistoName -- String for the name of the y-axis histogram (or another "creator" object)
1131 zaxis -- If set to True (default False), create a TGraph2D with z axis showing the cut value (recommended drawStyle 'pcolz')
1139 """String representation, returns the name"""
1143 """Create and return the histogram from a TDirectory"""
1146 if xhisto
is None or yhisto
is None:
1157 for i
in range(1, xhisto.GetNbinsX()+1):
1158 x.append(xhisto.GetBinContent(i))
1159 xerrup.append(xhisto.GetBinError(i))
1160 xerrdown.append(xhisto.GetBinError(i))
1162 y.append(yhisto.GetBinContent(i))
1163 yerrup.append(yhisto.GetBinError(i))
1164 yerrdown.append(yhisto.GetBinError(i))
1166 z.append(xhisto.GetXaxis().GetBinUpEdge(i))
1169 if x.count(0.0) == len(x)
or y.count(0.0) == len(y):
1172 arr =
lambda v: array.array(
"d", v)
1175 gr = ROOT.TGraph2D(len(x), arr(x), arr(y), arr(z))
1177 gr = ROOT.TGraphAsymmErrors(len(x), arr(x), arr(y), arr(xerrdown), arr(xerrup), arr(yerrdown), arr(yerrup))
1183 _plotStylesColor = [4, 2, ROOT.kBlack, ROOT.kOrange+7, ROOT.kMagenta-3]
1184 _plotStylesMarker = [21, 20, 22, 34, 33]
1186 def _drawFrame(pad, bounds, zmax=None, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None, suffix=""):
1187 """Function to draw a frame
1190 pad -- TPad to where the frame is drawn
1191 bounds -- List or 4-tuple for (xmin, ymin, xmax, ymax)
1194 zmax -- Maximum Z, needed for TH2 histograms
1195 xbinlabels -- Optional list of strings for x axis bin labels
1196 xbinlabelsize -- Optional number for the x axis bin label size
1197 xbinlabeloption -- Optional string for the x axis bin options (passed to ROOT.TH1.LabelsOption())
1198 suffix -- Optional string for a postfix of the frame name
1200 if xbinlabels
is None and ybinlabels
is None:
1201 frame = pad.DrawFrame(*bounds)
1204 nbins = len(xbinlabels)
1205 if ybinlabels
is None:
1206 frame = ROOT.TH1F(
"hframe"+suffix,
"", nbins, bounds[0], bounds[2])
1207 frame.SetMinimum(bounds[1])
1208 frame.SetMaximum(bounds[3])
1209 frame.GetYaxis().SetLimits(bounds[1], bounds[3])
1211 ybins = len(ybinlabels)
1212 frame = ROOT.TH2F(
"hframe"+suffix,
"", nbins,bounds[0],bounds[2], ybins,bounds[1],bounds[3])
1213 frame.SetMaximum(zmax)
1215 frame.SetBit(ROOT.TH1.kNoStats)
1216 frame.SetBit(ROOT.kCanDelete)
1219 xaxis = frame.GetXaxis()
1220 for i
in range(nbins):
1221 xaxis.SetBinLabel(i+1, xbinlabels[i])
1222 if xbinlabelsize
is not None:
1223 xaxis.SetLabelSize(xbinlabelsize)
1224 if xbinlabeloption
is not None:
1225 frame.LabelsOption(xbinlabeloption)
1227 if ybinlabels
is not None:
1228 yaxis = frame.GetYaxis()
1229 for i, lab
in enumerate(ybinlabels):
1230 yaxis.SetBinLabel(i+1, lab)
1231 if xbinlabelsize
is not None:
1232 yaxis.SetLabelSize(xbinlabelsize)
1233 if xbinlabeloption
is not None:
1234 frame.LabelsOption(xbinlabeloption,
"Y")
1239 """Class for creating and managing a frame for a simple, one-pad plot"""
1240 def __init__(self, pad, bounds, zmax, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ybinlabels=None):
1242 self.
_frame =
_drawFrame(pad, bounds, zmax, xbinlabels, xbinlabelsize, xbinlabeloption, ybinlabels)
1250 yoffsetFactor *= 1.5
1251 xoffsetFactor *= 1.5
1256 self.
_frame.GetYaxis().SetTitleOffset(self.
_frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
1257 self.
_frame.GetXaxis().SetTitleOffset(self.
_frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
1261 self.
_pad.SetLogx(log)
1264 self.
_pad.SetLogy(log)
1267 self.
_pad.SetGridx(grid)
1270 self.
_pad.SetGridy(grid)
1273 self.
_pad.SetLeftMargin(self.
_pad.GetLeftMargin()+adjust)
1279 self.
_pad.SetRightMargin(self.
_pad.GetRightMargin()+adjust)
1285 self.
_frame.SetTitle(title)
1288 self.
_frame.GetXaxis().SetTitle(title)
1291 self.
_frame.GetXaxis().SetTitleSize(size)
1294 self.
_frame.GetXaxis().SetTitleOffset(offset)
1297 self.
_frame.GetXaxis().SetLabelSize(size)
1300 self.
_frame.GetYaxis().SetTitle(title)
1303 self.
_frame.GetYaxis().SetTitleSize(size)
1306 self.
_frame.GetYaxis().SetTitleOffset(offset)
1309 self.
_pad.RedrawAxis()
1312 """Class for creating and managing a frame for a ratio plot with two subpads"""
1313 def __init__(self, pad, bounds, zmax, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, ratioYTitle=_ratioYTitle):
1316 if xbinlabels
is not None:
1323 self.
_frame.GetXaxis().SetLabelSize(0)
1324 self.
_frame.GetXaxis().SetTitleSize(0)
1326 yoffsetFactor = ratioFactor
1327 divisionPoint = 1-1/ratioFactor
1328 xoffsetFactor = 1/divisionPoint
1331 xoffsetFactor *= 0.6
1334 xoffsetFactor *= 1.5
1337 xoffsetFactor *= 2.3
1342 self.
_frame.GetYaxis().SetTitleOffset(self.
_frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1347 self.
_frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
1352 self.
_pad.SetLogx(log)
1356 self.
_pad.SetLogy(log)
1359 self.
_pad.SetGridx(grid)
1363 self.
_pad.SetGridy(grid)
1367 self.
_pad.SetLeftMargin(self.
_pad.GetLeftMargin()+adjust)
1376 self.
_pad.SetRightMargin(self.
_pad.GetRightMargin()+adjust)
1385 self.
_frame.SetTitle(title)
1394 self.
_frameRatio.GetXaxis().SetTitleOffset(offset)
1400 self.
_frame.GetYaxis().SetTitle(title)
1406 self.
_frame.GetYaxis().SetTitleSize(size)
1410 self.
_frame.GetYaxis().SetTitleOffset(offset)
1411 self.
_frameRatio.GetYaxis().SetTitleOffset(offset)
1415 self.
_pad.RedrawAxis()
1424 self.
_coverPad = ROOT.TPad(
"coverpad",
"coverpad", xmin, ymin, xmax, ymax)
1432 """Class for creating and managing a frame for a plot from TGraph2D"""
1433 def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
1436 self.
_pad = pad.cd(1)
1440 (xlow, ylow, width, height) = (self.
_pad.GetXlowNDC(), self.
_pad.GetYlowNDC(), self.
_pad.GetWNDC(), self.
_pad.GetHNDC())
1444 bottomMargin = self.
_pad.GetBottomMargin()
1445 bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
1447 ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
1448 topMarginNew = self.
_pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
1450 self.
_pad.SetPad(xlow, ylowNew, xup, yup)
1451 self.
_pad.SetTopMargin(topMarginNew)
1452 self.
_pad.SetBottomMargin(bottomMarginNew)
1472 self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1476 self.
_pad.SetRightMargin(self.
_pad.GetRightMargin()+adjust)
1507 self.
_firstHisto.GetZaxis().SetTitleOffset(offset)
1512 self.
_pad.SetPhi(epsilon)
1513 self.
_pad.SetTheta(90+epsilon)
1518 if hasattr(self,
"_xtitle"):
1520 if hasattr(self,
"_xtitlesize"):
1522 if hasattr(self,
"_xlabelsize"):
1523 self.
_firstHisto.GetXaxis().SetLabelSize(self._labelsize)
1524 if hasattr(self,
"_ytitle"):
1526 if hasattr(self,
"_ytitlesize"):
1528 if hasattr(self,
"_ytitleoffset"):
1532 """Abstraction on top of TLatex"""
1533 def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None):
1537 x -- X coordinate of the text (in NDC)
1538 y -- Y coordinate of the text (in NDC)
1539 text -- String to draw
1540 size -- Size of text (None for the default value, taken from gStyle)
1541 bold -- Should the text be bold?
1542 align -- Alignment of text (left, center, right)
1543 color -- Color of the text
1544 font -- Specify font explicitly
1553 self.
_l.SetTextFont(self.
_l.GetTextFont()-20)
1554 if font
is not None:
1555 self.
_l.SetTextFont(font)
1556 if size
is not None:
1557 self.
_l.SetTextSize(size)
1558 if isinstance(align, str):
1559 if align.lower() ==
"left":
1560 self.
_l.SetTextAlign(11)
1561 elif align.lower() ==
"center":
1562 self.
_l.SetTextAlign(21)
1563 elif align.lower() ==
"right":
1564 self.
_l.SetTextAlign(31)
1566 raise Exception(
"Error: Invalid option '%s' for text alignment! Options are: 'left', 'center', 'right'."%align)
1568 self.
_l.SetTextAlign(align)
1569 self.
_l.SetTextColor(color)
1572 """Draw the text to the current TPad.
1575 options -- For interface compatibility, ignored
1577 Provides interface compatible with ROOT's drawable objects.
1583 """Class for drawing text and a background box."""
1584 def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, **kwargs):
1588 xmin -- X min coordinate of the box (NDC)
1589 ymin -- Y min coordinate of the box (NDC) (if None, deduced automatically)
1590 xmax -- X max coordinate of the box (NDC)
1591 ymax -- Y max coordinate of the box (NDC)
1592 lineheight -- Line height
1593 fillColor -- Fill color of the box
1594 transparent -- Should the box be transparent? (in practive the TPave is not created)
1596 Keyword arguments are forwarded to constructor of PlotText
1613 """Add text to current position"""
1620 def move(self, dx=0, dy=0, dw=0, dh=0):
1621 """Move the box and the contained text objects
1624 dx -- Movement in x (positive is to right)
1625 dy -- Movement in y (positive is to up)
1626 dw -- Increment of width (negative to decrease width)
1627 dh -- Increment of height (negative to decrease height)
1629 dx and dy affect to both box and text objects, dw and dh
1630 affect the box only.
1634 if self.
_ymin is not None:
1639 if self.
_ymin is not None:
1647 """Draw the box and the text to the current TPad.
1650 options -- Forwarded to ROOT.TPave.Draw(), and the Draw() of the contained objects
1655 ymin = self.currenty - 0.01
1656 self.
_pave = ROOT.TPave(self.xmin, self.ymin, self.xmax, self.ymax, 0,
"NDC")
1657 self.
_pave.SetFillColor(self.fillColor)
1664 if hasattr(src,
"GetLineColor")
and hasattr(dst,
"SetLineColor"):
1665 properties.extend([
"LineColor",
"LineStyle",
"LineWidth"])
1666 if hasattr(src,
"GetFillColor")
and hasattr(dst,
"SetFillColor"):
1667 properties.extend([
"FillColor",
"FillStyle"])
1668 if hasattr(src,
"GetMarkerColor")
and hasattr(dst,
"SetMarkerColor"):
1669 properties.extend([
"MarkerColor",
"MarkerSize",
"MarkerStyle"])
1671 for prop
in properties:
1672 getattr(dst,
"Set"+prop)(getattr(src,
"Get"+prop)())
1675 """Denotes an empty place in a group."""
1695 """Represents one plot, comparing one or more histograms."""
1700 name -- String for name of the plot, or Efficiency object
1703 fallback -- Dictionary for specifying fallback (default None)
1704 outname -- String for an output name of the plot (default None for the same as 'name')
1705 title -- String for a title of the plot (default None)
1706 xtitle -- String for x axis title (default None)
1707 xtitlesize -- Float for x axis title size (default None)
1708 xtitleoffset -- Float for x axis title offset (default None)
1709 xlabelsize -- Float for x axis label size (default None)
1710 ytitle -- String for y axis title (default None)
1711 ytitlesize -- Float for y axis title size (default None)
1712 ytitleoffset -- Float for y axis title offset (default None)
1713 ztitle -- String for z axis title (default None)
1714 ztitleoffset -- Float for z axis title offset (default None)
1715 xmin -- Float for x axis minimum (default None, i.e. automatic)
1716 xmax -- Float for x axis maximum (default None, i.e. automatic)
1717 ymin -- Float for y axis minimum (default 0)
1718 ymax -- Float for y axis maximum (default None, i.e. automatic)
1719 xlog -- Bool for x axis log status (default False)
1720 ylog -- Bool for y axis log status (default False)
1721 xgrid -- Bool for x axis grid status (default True)
1722 ygrid -- Bool for y axis grid status (default True)
1723 stat -- Draw stat box? (default False)
1724 fit -- Do gaussian fit? (default False)
1725 statx -- Stat box x coordinate (default 0.65)
1726 staty -- Stat box y coordinate (default 0.8)
1727 statyadjust -- List of floats for stat box y coordinate adjustments (default None)
1728 normalizeToUnitArea -- Normalize histograms to unit area? (default False)
1729 normalizeToNumberOfEvents -- Normalize histograms to number of events? If yes, the PlotFolder needs 'numberOfEventsHistogram' set to a histogram filled once per event (default False)
1730 profileX -- Take histograms via ProfileX()? (default False)
1731 fitSlicesY -- Take histograms via FitSlicesY() (default False)
1732 rebinX -- rebin x axis (default None)
1733 scale -- Scale histograms by a number (default None)
1734 xbinlabels -- List of x axis bin labels (if given, default None)
1735 xbinlabelsize -- Size of x axis bin labels (default None)
1736 xbinlabeloption -- Option string for x axis bin labels (default None)
1737 removeEmptyBins -- Bool for removing empty bins, but only if histogram has bin labels (default False)
1738 printBins -- Bool for printing bin values, but only if histogram has bin labels (default False)
1739 drawStyle -- If "hist", draw as line instead of points (default None)
1740 drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
1741 lineWidth -- If drawStyle=="hist", the width of line (default 2)
1742 legendDx -- Float for moving TLegend in x direction for separate=True (default None)
1743 legendDy -- Float for moving TLegend in y direction for separate=True (default None)
1744 legendDw -- Float for changing TLegend width for separate=True (default None)
1745 legendDh -- Float for changing TLegend height for separate=True (default None)
1746 legend -- Bool to enable/disable legend (default True)
1747 adjustMarginLeft -- Float for adjusting left margin (default None)
1748 adjustMarginRight -- Float for adjusting right margin (default None)
1749 ratio -- Possibility to disable ratio for this particular plot (default None)
1750 ratioYmin -- Float for y axis minimum in ratio pad (default: list of values)
1751 ratioYmax -- Float for y axis maximum in ratio pad (default: list of values)
1752 ratioFit -- Fit straight line in ratio? (default None)
1753 ratioUncertainty -- Plot uncertainties on ratio? (default True)
1754 ratioCoverageXrange -- Range of x axis values (xmin,xmax) to limit the automatic ratio y axis range calculation to (default None for disabled)
1755 histogramModifier -- Function to be called in create() to modify the histograms (default None)
1759 def _set(attr, default):
1760 setattr(self,
"_"+attr, kwargs.get(attr, default))
1762 _set(
"fallback",
None)
1763 _set(
"outname",
None)
1766 _set(
"xtitle",
None)
1767 _set(
"xtitlesize",
None)
1768 _set(
"xtitleoffset",
None)
1769 _set(
"xlabelsize",
None)
1770 _set(
"ytitle",
None)
1771 _set(
"ytitlesize",
None)
1772 _set(
"ytitleoffset",
None)
1773 _set(
"ztitle",
None)
1774 _set(
"ztitleoffset",
None)
1791 _set(
"statyadjust",
None)
1793 _set(
"normalizeToUnitArea",
False)
1794 _set(
"normalizeToNumberOfEvents",
False)
1795 _set(
"profileX",
False)
1796 _set(
"fitSlicesY",
False)
1797 _set(
"rebinX",
None)
1800 _set(
"xbinlabels",
None)
1801 _set(
"xbinlabelsize",
None)
1802 _set(
"xbinlabeloption",
None)
1803 _set(
"removeEmptyBins",
False)
1804 _set(
"printBins",
False)
1806 _set(
"drawStyle",
"EP")
1807 _set(
"drawCommand",
None)
1808 _set(
"lineWidth", 2)
1810 _set(
"legendDx",
None)
1811 _set(
"legendDy",
None)
1812 _set(
"legendDw",
None)
1813 _set(
"legendDh",
None)
1814 _set(
"legend",
True)
1816 _set(
"adjustMarginLeft",
None)
1817 _set(
"adjustMarginRight",
None)
1820 _set(
"ratioYmin", [0, 0.2, 0.5, 0.7, 0.8, 0.9, 0.95])
1821 _set(
"ratioYmax", [1.05, 1.1, 1.2, 1.3, 1.5, 1.8, 2, 2.5, 3, 4, 5])
1822 _set(
"ratioFit",
None)
1823 _set(
"ratioUncertainty",
True)
1824 _set(
"ratioCoverageXrange",
None)
1826 _set(
"histogramModifier",
None)
1831 for name, value
in six.iteritems(kwargs):
1832 if not hasattr(self,
"_"+name):
1833 raise Exception(
"No attribute '%s'" % name)
1834 setattr(self,
"_"+name, value)
1838 raise Exception(
"Plot can be cloned only before histograms have been created")
1839 cl = copy.copy(self)
1840 cl.setProperties(**kwargs)
1844 """Return number of existing histograms."""
1845 return len([h
for h
in self.
_histograms if h
is not None])
1848 """Return true if there are no histograms created for the plot"""
1853 if isinstance(h, ROOT.TGraph2D):
1858 if self._ratio
is None:
1860 return ratio
and self._ratio
1863 if self._outname
is not None:
1864 return self._outname
1865 if isinstance(self.
_name, list):
1871 """Return true if the ratio uncertainty should be drawn"""
1872 return self._ratioUncertainty
1875 """Create one histogram from a TDirectory."""
1880 if isinstance(name, list):
1884 if h
is not None and self._normalizeToNumberOfEvents
and nevents
is not None and nevents != 0:
1885 h.Scale(1.0/nevents)
1888 def create(self, tdirNEvents, requireAllHistograms=False):
1889 """Create histograms from list of TDirectories"""
1890 self.
_histograms = [self.
_createOne(self.
_name, i, tdirNEvent[0], tdirNEvent[1])
for i, tdirNEvent
in enumerate(tdirNEvents)]
1892 if self._fallback
is not None:
1896 self.
_histograms[i] = self.
_createOne(self._fallback[
"name"], i, tdirNEvents[i][0], tdirNEvents[i][1])
1897 profileX[i] = self._fallback.get(
"profileX", self._profileX)
1899 if self._histogramModifier
is not None:
1903 raise Exception(
"More histograms (%d) than there are plot styles (%d) defined. Please define more plot styles in this file" % (len(self.
_histograms), len(_plotStylesColor)))
1908 def _modifyHisto(th1, profileX):
1913 th1 = th1.ProfileX()
1915 if self._fitSlicesY:
1916 ROOT.TH1.AddDirectory(
True)
1918 th1 = ROOT.gDirectory.Get(th1.GetName()+
"_2")
1919 th1.SetDirectory(
None)
1921 ROOT.TH1.AddDirectory(
False)
1923 if self._title
is not None:
1924 th1.SetTitle(self._title)
1926 if self._scale
is not None:
1927 th1.Scale(self._scale)
1931 if self._fallback
is not None:
1935 if requireAllHistograms
and None in self.
_histograms:
1939 """Set stats box."""
1942 if h
is not None and hasattr(h,
"SetStats"):
1946 def _doStats(h, col, dy):
1951 if self._fit
and h.GetEntries() > 0.5:
1953 f = h.GetListOfFunctions().FindObject(
"gaus")
1961 st = h.GetListOfFunctions().FindObject(
"stats")
1965 st.SetX1NDC(startingX)
1966 st.SetX2NDC(startingX+0.3)
1967 st.SetY1NDC(startingY+dy)
1968 st.SetY2NDC(startingY+dy+0.15)
1969 st.SetTextColor(col)
1972 for i, h
in enumerate(histos):
1973 if self._statyadjust
is not None and i < len(self._statyadjust):
1974 dy += self._statyadjust[i]
1976 _doStats(h, _plotStylesColor[i], dy)
1980 """Normalise histograms to unit area"""
1988 if h.GetSumw2().fN <= 0:
1992 def draw(self, pad, ratio, ratioFactor, nrows):
1993 """Draw the histograms using values for a given algorithm."""
2004 if self._normalizeToUnitArea:
2007 if self._rebinX
is not None:
2009 h.Rebin(self._rebinX)
2011 def _styleMarker(h, msty, col):
2012 h.SetMarkerStyle(msty)
2013 h.SetMarkerColor(col)
2014 h.SetMarkerSize(0.7)
2018 def _styleHist(h, msty, col):
2019 _styleMarker(h, msty, col)
2021 h.SetLineWidth(self._lineWidth)
2024 style = _styleMarker
2025 if "hist" in self._drawStyle.lower():
2028 if "l" in self._drawStyle.lower():
2036 style(h, _plotStylesMarker[i], _plotStylesColor[i])
2038 if len(histos) == 0:
2045 histosHaveBinLabels = len(histos[0].GetXaxis().GetBinLabel(1)) > 0
2046 xbinlabels = self._xbinlabels
2048 if xbinlabels
is None:
2049 if histosHaveBinLabels:
2051 if isinstance(histos[0], ROOT.TH2):
2060 if self._removeEmptyBins
and histosHaveBinLabels:
2063 if isinstance(histos[0], ROOT.TH2):
2068 if len(histos) == 0:
2070 print(
"No histograms with non-empty bins for plot {name}".
format(name=self.
getName()))
2073 if self._printBins
and histosHaveBinLabels:
2074 print(
"####################")
2076 width =
max([len(l)
for l
in xbinlabels])
2077 tmp =
"%%-%ds " % width
2078 for b
in range(1, histos[0].GetNbinsX()+1):
2079 s = tmp % xbinlabels[b-1]
2081 s +=
"%.3f " % h.GetBinContent(b)
2086 xmin=self._xmin, xmax=self._xmax,
2087 ymin=self._ymin, ymax=self._ymax)
2089 if isinstance(histos[0], ROOT.TH2):
2090 zmax =
max([h.GetMaximum()
for h
in histos])
2098 ratioHistos = [h
for h
in [r.getRatio()
for r
in self.
_ratios[1:]]
if h
is not None]
2100 if len(ratioHistos) > 0:
2101 ratioBoundsY =
_findBoundsY(ratioHistos, ylog=
False, ymin=self._ratioYmin, ymax=self._ratioYmax, coverage=0.68, coverageRange=self._ratioCoverageXrange)
2103 ratioBoundsY = (0.9, 1,1)
2105 if self._ratioFit
is not None:
2106 for i, rh
in enumerate(ratioHistos):
2107 tf_line = ROOT.TF1(
"line%d"%i,
"[0]+x*[1]")
2108 tf_line.SetRange(self._ratioFit[
"rangemin"], self._ratioFit[
"rangemax"])
2109 fitres = rh.Fit(tf_line,
"RINSQ")
2110 tf_line.SetLineColor(rh.GetMarkerColor())
2111 tf_line.SetLineWidth(2)
2113 box =
PlotTextBox(xmin=self._ratioFit.get(
"boxXmin", 0.14), ymin=
None,
2114 xmax=self._ratioFit.get(
"boxXmax", 0.35), ymax=self._ratioFit.get(
"boxYmax", 0.09),
2115 color=rh.GetMarkerColor(), font=43, size=11, lineheight=0.02)
2116 box.move(dx=(box.width()+0.01)*i)
2119 box.addText(
"Const: %.4f#pm%.4f" % (fitres.Parameter(0), fitres.ParError(0)))
2120 box.addText(
"Slope: %.4f#pm%.4f" % (fitres.Parameter(1), fitres.ParError(1)))
2131 self.
_setStats(histos, self._statx, self._staty)
2135 frame =
FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
2138 ratioBounds = (bounds[0], ratioBoundsY[0], bounds[2], ratioBoundsY[1])
2139 frame =
FrameRatio(pad, bounds, zmax, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
2141 frame =
Frame(pad, bounds, zmax, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption, ybinlabels=ybinlabels)
2144 frame.setLogx(self._xlog)
2145 frame.setLogy(self._ylog)
2146 frame.setGridx(self._xgrid)
2147 frame.setGridy(self._ygrid)
2152 if self._drawStyle
is not None:
2153 ds = self._drawStyle
2154 if self._drawCommand
is not None:
2155 ds = self._drawCommand
2160 frame.setTitle(histos[0].GetTitle())
2162 frame.setXTitle( histos[0].GetXaxis().GetTitle() )
2163 elif self.
_xtitle is not None:
2165 if self._xtitlesize
is not None:
2166 frame.setXTitleSize(self._xtitlesize)
2167 if self._xtitleoffset
is not None:
2168 frame.setXTitleOffset(self._xtitleoffset)
2169 if self._xlabelsize
is not None:
2170 frame.setXLabelSize(self._xlabelsize)
2172 frame.setYTitle( histos[0].GetYaxis().GetTitle() )
2173 elif self.
_ytitle is not None:
2175 if self._ytitlesize
is not None:
2176 frame.setYTitleSize(self._ytitlesize)
2177 if self._ytitleoffset
is not None:
2178 frame.setYTitleOffset(self._ytitleoffset)
2179 if self._ztitle
is not None:
2180 frame.setZTitle(self._ztitle)
2181 if self._ztitleoffset
is not None:
2182 frame.setZTitleOffset(self._ztitleoffset)
2183 if self._adjustMarginLeft
is not None:
2184 frame.adjustMarginLeft(self._adjustMarginLeft)
2185 if self._adjustMarginRight
is not None:
2186 frame.adjustMarginRight(self._adjustMarginRight)
2188 frame.adjustMarginLeft(0.03)
2189 frame.adjustMarginRight(0.08)
2195 for i, h
in enumerate(histos):
2197 if isTGraph2D
and i == 0:
2198 o = o.replace(
"sames",
"")
2205 if ratio
and len(histos) > 0:
2206 frame._padRatio.cd()
2207 firstRatio = self.
_ratios[0].getRatio()
2208 if self._ratioUncertainty
and firstRatio
is not None:
2209 firstRatio.SetFillStyle(1001)
2210 firstRatio.SetFillColor(ROOT.kGray)
2211 firstRatio.SetLineColor(ROOT.kGray)
2212 firstRatio.SetMarkerColor(ROOT.kGray)
2213 firstRatio.SetMarkerSize(0)
2215 frame._padRatio.RedrawAxis(
"G")
2226 """Add histograms to a legend.
2230 legendLabels -- List of strings for the legend labels
2232 first = denomUncertainty
2241 entry = legend.AddEntry(self.
_forLegend, label,
"lpf")
2244 legend.AddEntry(h, label,
"LP")
2247 """Group of plots, results a TCanvas"""
2252 name -- String for name of the TCanvas, used also as the basename of the picture files
2253 plots -- List of Plot objects
2256 ncols -- Number of columns (default 2)
2257 legendDx -- Float for moving TLegend in x direction (default None)
2258 legendDy -- Float for moving TLegend in y direction (default None)
2259 legendDw -- Float for changing TLegend width (default None)
2260 legendDh -- Float for changing TLegend height (default None)
2261 legend -- Bool for disabling legend (default True for legend being enabled)
2262 overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
2263 onlyForPileup -- Plots this group only for pileup samples
2270 def _set(attr, default):
2271 setattr(self,
"_"+attr, kwargs.get(attr, default))
2275 _set(
"legendDx",
None)
2276 _set(
"legendDy",
None)
2277 _set(
"legendDw",
None)
2278 _set(
"legendDh",
None)
2279 _set(
"legend",
True)
2281 _set(
"overrideLegendLabels",
None)
2283 _set(
"onlyForPileup",
False)
2288 for name, value
in six.iteritems(kwargs):
2289 if not hasattr(self,
"_"+name):
2290 raise Exception(
"No attribute '%s'" % name)
2291 setattr(self,
"_"+name, value)
2300 for i, plot
in enumerate(self.
_plots):
2301 if plot.getName() == name:
2304 raise Exception(
"Did not find Plot '%s' from PlotGroup '%s'" % (name, self.
_name))
2314 if plot.getName() == name:
2316 raise Exception(
"No Plot named '%s'" % name)
2319 """Return True if the PlotGroup is intended only for pileup samples"""
2320 return self._onlyForPileup
2322 def create(self, tdirectoryNEvents, requireAllHistograms=False):
2323 """Create histograms from a list of TDirectories.
2326 tdirectoryNEvents -- List of (TDirectory, nevents) pairs
2327 requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2330 plot.create(tdirectoryNEvents, requireAllHistograms)
2332 def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2333 """Draw the histograms using values for a given algorithm.
2336 legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
2337 prefix -- Optional string for file name prefix (default None)
2338 separate -- Save the plots of a group to separate files instead of a file per group (default False)
2339 saveFormat -- String specifying the plot format (default '.pdf')
2340 ratio -- Add ratio to the plot (default True)
2341 directory -- Directory where to save the file (default "")
2344 if self._overrideLegendLabels
is not None:
2345 legendLabels = self._overrideLegendLabels
2348 onlyEmptyPlots =
True
2350 if not plot.isEmpty():
2351 onlyEmptyPlots =
False
2357 return self.
_drawSeparate(legendLabels, prefix, saveFormat, ratio, directory)
2359 cwidth = 500*self._ncols
2360 nrows =
int((len(self.
_plots)+self._ncols-1)/self._ncols)
2361 cheight = 500 * nrows
2368 canvas.Divide(self._ncols, nrows)
2370 for i, plot
in enumerate(self.
_plots):
2371 pad = canvas.cd(i+1)
2375 for i, plot
in enumerate(self.
_plots):
2376 pad = canvas.cd(i+1)
2377 if not plot.isEmpty():
2382 if len(self.
_plots) <= 4:
2392 if self._legendDx
is not None:
2393 lx1 += self._legendDx
2394 lx2 += self._legendDx
2395 if self._legendDy
is not None:
2396 ly1 += self._legendDy
2397 ly2 += self._legendDy
2398 if self._legendDw
is not None:
2399 lx2 += self._legendDw
2400 if self._legendDh
is not None:
2401 ly1 -= self._legendDh
2402 plot =
max(self.
_plots, key=
lambda p: p.getNumberOfHistograms())
2403 denomUnc = sum([p.drawRatioUncertainty()
for p
in self.
_plots]) > 0
2404 legend = self.
_createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2405 denomUncertainty=(ratio
and denomUnc))
2407 return self.
_save(canvas, saveFormat, prefix=prefix, directory=directory)
2410 """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2418 for c
in [canvas, canvasRatio]:
2419 c.SetTopMargin(0.05)
2420 c.SetBottomMargin(0.13)
2421 c.SetLeftMargin(0.16)
2422 c.SetRightMargin(0.05)
2435 ratioForThisPlot = plot.isRatio(ratio)
2437 if ratioForThisPlot:
2453 if plot._legendDx
is not None:
2454 lx1 += plot._legendDx
2455 lx2 += plot._legendDx
2456 if plot._legendDy
is not None:
2457 ly1 += plot._legendDy
2458 ly2 += plot._legendDy
2459 if plot._legendDw
is not None:
2460 lx2 += plot._legendDw
2461 if plot._legendDh
is not None:
2462 ly1 -= plot._legendDh
2465 legend = self.
_createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2466 denomUncertainty=(ratioForThisPlot
and plot.drawRatioUncertainty))
2468 ret.extend(self.
_save(c, saveFormat, prefix=prefix, postfix=
"/"+plot.getName(), single=
True, directory=directory))
2472 """Internal method to set divide a pad to two for ratio plots"""
2475 def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2476 if not self._legend:
2479 l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2480 l.SetTextSize(textSize)
2487 plot.addToLegend(l, legendLabels, denomUncertainty)
2491 def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False, directory=""):
2494 if not os.path.exists(directory+
'/'+name):
2495 os.makedirs(directory+
'/'+name)
2496 if prefix
is not None:
2498 if postfix
is not None:
2500 name = os.path.join(directory, name)
2503 backup = ROOT.gErrorIgnoreLevel
2504 ROOT.gErrorIgnoreLevel = ROOT.kWarning
2505 canvas.SaveAs(name+saveFormat)
2507 ROOT.gErrorIgnoreLevel = backup
2511 canvas.SetLogx(
False)
2512 canvas.SetLogy(
False)
2516 return [name+saveFormat]
2519 """Resembles DQM GUI's "On side" layout.
2521 Like PlotGroup, but has only a description of a single plot. The
2522 plot is drawn separately for each file. Useful for 2D histograms."""
2524 def __init__(self, name, plot, ncols=2, onlyForPileup=False):
2525 super(PlotOnSideGroup, self).
__init__(name, [], ncols=ncols, legend=
False, onlyForPileup=onlyForPileup)
2530 raise Exception(
"PlotOnSideGroup.append() is not implemented")
2532 def create(self, tdirectoryNEvents, requireAllHistograms=False):
2534 for element
in tdirectoryNEvents:
2536 pl.create([element], requireAllHistograms)
2540 kargs = copy.copy(kwargs)
2541 kargs[
"ratio"] =
False
2542 kargs[
"separate"] =
False
2543 return super(PlotOnSideGroup, self).
draw(*args, **kargs)
2547 """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2552 plotGroups -- List of PlotGroup objects
2555 loopSubFolders -- Should the subfolders be looped over? (default: True)
2556 onlyForPileup -- Plots this folder only for pileup samples
2557 onlyForElectron -- Plots this folder only for electron samples
2558 onlyForConversion -- Plots this folder only for conversion samples
2559 onlyForBHadron -- Plots this folder only for B-hadron samples
2560 purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2561 page -- Optional string for the page in HTML generatin
2562 section -- Optional string for the section within a page in HTML generation
2563 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".
2576 raise Exception(
"Got unexpected keyword arguments: "+
",".
join(kwargs.keys()))
2579 """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2583 """Return True if the folder is intended only for pileup samples"""
2618 if pg.getName() == name:
2620 raise Exception(
"No PlotGroup named '%s'" % name)
2622 def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2623 """Create histograms from a list of TFiles.
2626 dirsNEvents -- List of (TDirectory, nevents) pairs
2627 labels -- List of strings for legend labels corresponding the files
2628 isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2629 requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2632 if len(dirsNEvents) != len(labels):
2633 raise Exception(
"len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2638 if pg.onlyForPileup()
and not isPileupSample:
2640 pg.create(dirsNEvents, requireAllHistograms)
2642 def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True, directory=""):
2643 """Draw and save all plots using settings of a given algorithm.
2646 prefix -- Optional string for file name prefix (default None)
2647 separate -- Save the plots of a group to separate files instead of a file per group (default False)
2648 saveFormat -- String specifying the plot format (default '.pdf')
2649 ratio -- Add ratio to the plot (default True)
2650 directory -- Directory where to save the file (default "")
2655 ret.extend(pg.draw(self.
_labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio, directory=directory))
2661 """Method called to (possibly) translate a subfolder name to more 'readable' form
2663 The implementation in this (base) class just returns the
2664 argument. The idea is that a deriving class might want to do
2665 something more complex (like trackingPlots.TrackingPlotFolder
2668 return dqmSubFolderName
2671 """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2673 if plotFolderName !=
"":
2674 ret +=
"_"+plotFolderName
2675 if translatedDqmSubFolder
is not None:
2676 ret +=
"_"+translatedDqmSubFolder
2680 """Return True if this subfolder should be processed
2683 limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2684 translatedDqmSubFolder -- Return value of translateSubFolder
2686 return translatedDqmSubFolder
in limitOnlyTo
2689 """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2695 """Equality is defined by the 'translated' name"""
2699 """Plotter for one DQM folder.
2701 This class is supposed to be instantiated by the Plotter class (or
2702 PlotterItem, to be more specific), and not used directly by the
2705 def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2710 name -- Name of the folder (is used in the output directory naming)
2711 possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2712 dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2713 plotFolder -- PlotFolder object
2714 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.
2715 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.
2716 tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2722 if dqmSubFolders
is None:
2728 for sf_list
in dqmSubFolders:
2730 sf_translated = self.
_plotFolder.translateSubFolder(sf)
2731 if sf_translated
is not None and not sf_translated
in subfolders:
2732 subfolders[sf_translated] =
DQMSubFolder(sf, sf_translated)
2769 """Get list of subfolders, possibly limiting to some of them.
2772 limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2778 if limitOnlyTo
is None:
2787 """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2789 for selname
in self.
_plotFolder.iterSelectionName(name, dqmSubFolder.translated
if dqmSubFolder
is not None else None):
2795 def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2796 """Create histograms from a list of TFiles.
2798 files -- List of TFiles
2799 labels -- List of strings for legend labels corresponding the files
2800 dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2801 isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2802 requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2805 subfolder = dqmSubFolder.subfolder
if dqmSubFolder
is not None else None
2806 neventsHisto = self.
_plotFolder.getNumberOfEventsHistogram()
2814 fallback = fallbackFunc(subfolder)
2815 if fallback
is not None:
2819 d = GetDirectoryCode.codesToNone(ret)
2821 if neventsHisto
is not None and tfile
is not None:
2823 if hnev
is not None:
2824 nev = hnev.GetEntries()
2825 dirsNEvents.append( (d, nev) )
2827 self.
_plotFolder.
create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2830 """Draw and save all plots using settings of a given algorithm."""
2835 """Instance of plotter that knows the directory content, holds many folders."""
2842 if limitSubFoldersOnlyTo
is not None:
2843 limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(),
None)
2845 for dqmSubFolder
in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2846 yield plotterFolder, dqmSubFolder
2850 def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2854 name -- Name of the folder (is used in the output directory naming)
2855 possibleDirs -- List of strings for possible directories of histograms in TFiles
2856 plotFolder -- PlotFolder object
2859 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.
2860 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.
2879 """Read available subfolders from the files
2882 files -- List of strings for paths to files, or list of TFiles
2884 For each file, loop over 'possibleDirs', and read the
2885 subfolders of first one that exists.
2887 Returns a PlotterFolder if at least one file for which one of
2888 'possibleDirs' exists. Otherwise, return None to signal that
2889 there is nothing available for this PlotFolder.
2894 possibleDirFound =
False
2899 isOpenFile = isinstance(fname, ROOT.TFile)
2903 tfile = ROOT.TFile.Open(fname)
2907 possibleDirFound =
True
2908 if subFolders
is not None:
2910 for key
in d.GetListOfKeys():
2911 if isinstance(key.ReadObj(), ROOT.TDirectory):
2912 subf.append(key.GetName())
2913 subFolders.append(subf)
2919 if not possibleDirFound:
2929 def create(self, openFiles, legendLabels, dqmSubFolder):
2930 if isinstance(dqmSubFolder, list):
2931 if len(dqmSubFolder) != len(openFiles):
2932 raise Exception(
"When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2934 dqmSubFolder = [dqmSubFolder]*len(openFiles)
2935 dqmSubFolder = [sf.subfolder
if sf
is not None else None for sf
in dqmSubFolder]
2938 for f, sf
in zip(openFiles, dqmSubFolder):
2941 if tdir
is not None:
2957 for i
in range(len(tbl)):
2959 tbl[i] = [
None]*colLen
2965 """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2969 ROOT.TH1.AddDirectory(
False)
2972 """Append a plot folder to the plotter.
2974 All arguments are forwarded to the constructor of PlotterItem.
2979 for plotterItem
in self.
_plots:
2980 if plotterItem.getName() == attachToFolder:
2983 raise Exception(
"Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2986 """Remove all plot folders and tables"""
2990 return [item.getName()
for item
in self.
_plots]
2993 return [item.getPlotFolder()
for item
in self.
_plots]
2997 if item.getName() == name:
2998 return item.getPlotFolder()
2999 raise Exception(
"No PlotFolder named '%s'" % name)
3002 """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""