ATLAS Offline Software
Loading...
Searching...
No Matches
python/ROOTUtils.py
Go to the documentation of this file.
1# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
2
3"""
4Miscellaneous utilities for PyROOT.
5"""
6__author__ = 'Juerg Beringer'
7__version__ = '$Id: ROOTUtils.py 759047 2016-07-01 00:45:13Z beringer $'
8
9
10import ROOT
11
12protectedObjectList = []
13
14def protect(obj):
15 """Utility function to prevent ROOT objects from being garbage-collected,
16 (thus disappearing from plots) when they go out of scope."""
17 protectedObjectList.append(obj)
18 return obj
19
20
21# Define a set of useful canvas sizes for different purposes
22myCanvasSizes = { 'default': (700, 500),
23 'default2d': (545, 500),
24 'slide1': (900,560),
25 'slide2': (450,560),
26 'slide2text': (400,500),
27 'fig1': (700, 500),
28 'fig2': (1400, 500),
29 'page': (750,1000),
30 'landscape': (1600,1000),
31 'wide': (1000,500),
32 'extrawide': (1500,500),
33 'square': (700,700)
34 }
35
36
37class MyCanvas(ROOT.TCanvas):
38 """Class MyCanvas is a ROOT TCanvas that allows choosing one of
39 several typically used canvas sizes. It can automatically save
40 the canvas in different formats upon destruction provided the
41 user makes sure that all histograms still exist."""
42
43 # Class defaults
44 saveAsList = [ '.gif' ]
45 autoSaveOnExit = False
46 autoName = '%s.autosave%s'
47
48 def __init__(self,name='MyCanvas', size='default', xdivs=1, ydivs=1, saveAsList=None, autoSaveOnExit=None):
49 super(MyCanvas,self).__init__(name)
50 self.name = name
51 if saveAsList is not None:
52 self.saveAsList = saveAsList
53 else:
54 self.saveAsList = MyCanvas.saveAsList
55 if autoSaveOnExit is not None:
56 self.autoSaveOnExit = autoSaveOnExit
57 else:
58 self.autoSaveOnExit = MyCanvas.autoSaveOnExit
59 self.autoName = MyCanvas.autoName
60 self.Divide(xdivs,ydivs)
61
62 def __del__(self):
63 if self.autoSaveOnExit:
64 self.save()
65
66 def save(self):
67 for o in self.saveAsList:
68 if o[0]=='.':
69 self.SaveAs(self.autoName % (self.name,o))
70 else:
71 self.SaveAs(o)
72
73
75 """PlotLibrary is a base class that can be used to manage a set of
76 plots. Each individual plot should be created as a method of a
77 derived class. Other methods of the derived class that do not
78 generate plots should either have names starting with
79 underscore, or should be declared when calling __init__ of the
80 base class."""
81
82 def __init__(self, name = 'MyPlots', otherMethods=[]):
83 """Constructor. otherMethods specifies a list of methods of the derived
84 class that are not creating plots and should not be called by plot()."""
85 self.name = name
86 self.otherMethods = []
87 self.otherMethods.extend(otherMethods)
88 self.rootObjects = []
89
90 # Variables used by genPlot
91 self.whatList = []
92 self.singleCanvasSize = 'default'
93 self.allCanvasSize = 'page'
94 self.allCanvasDivs = (3,4)
95 self.saveAsList = []
97
98 def protect(self,obj):
99 """Protect ROOT object from garbage collection."""
100 self.rootObjects.append(obj)
101 return obj
102
103 def plot(self, plotName='',*args):
104 """Make one or all (if plotName=='') plots."""
105 if plotName:
106 self.__class__.__dict__[plotName](self,*args)
107 else:
108 for i in self.__class__.__dict__:
109 if i[0]=='_': continue # skip private methods
110 if i in self.otherMethods: continue
111 self.__class__.__dict__[i](self,*args)
112
113 def genPlot(self,what='ALL',code='plot',labels=[],*args):
114 """Make plots using a general code. genPlot makes either a single plot defined
115 by argument what, or all plots (if what=''). The plots made in the latter
116 case are specified by whatList. A canvas that is subdivided if necessary
117 is created before the plotting code is called."""
118 if what=='' or what.upper()=='ALL':
119 c = self.protect( MyCanvas('%s-%s-%s' % (self.name,what,code),
120 self.allCanvasSize,self.allCanvasDivs[0],self.allCanvasDivs[1]) )
121 iCanvas = 0
122 for w in self.whatList:
123 iCanvas += 1
124 c.cd(iCanvas)
125 try:
126 self.__class__.__dict__[code](self,w,*args)
127 except Exception:
128 self.__class__.__bases__[0].__dict__[code](self,w,*args)
129 ROOT.gPad.Update()
130 # For some strange reason, this works only for .eps, but not e.g. for gif files...???
131 for o in self.gPadSaveAsList:
132 if o[0]=='.':
133 ROOT.gPad.SaveAs('%s-%s-%s%s' % (self.name,code,w,o))
134 else:
135 ROOT.gPad.SaveAs(o)
136
137 # If we want to put some labels on an empty pad, add them now.
138 if labels!=[] and self.allCanvasDivs[0]*self.allCanvasDivs[1] > len(self.whatList):
139 iCanvas+=1
140 c.cd(iCanvas)
141 xtext=0.0
142 ytext=0.8
143 for label in labels:
144 drawText(xtext,ytext,0.06,label)
145 ytext=ytext-0.1
146 elif labels!=[]:
147 print ("ERROR: can't add labels unless we have an empty pad to use. Ignoring labels.")
148
149 for o in self.saveAsList:
150 if o[0]=='.':
151 c.SaveAs('%s-%s-%s%s' % (self.name,code,what,o))
152 else:
153 c.SaveAs(o)
154
155 else:
156 c = self.protect( MyCanvas(what,self.singleCanvasSize) )
157 try:
158 self.__class__.__dict__[code](self,what,*args)
159 except Exception:
160 self.__class__.__bases__[0].__dict__[code](self,what,*args)
161 ROOT.gPad.Update()
162 for o in self.saveAsList:
163 if o[0]=='.':
164 c.SaveAs('%s-%s-%s%s' % (self.name,code,what,o))
165 else:
166 c.SaveAs(o)
167
169 """StyleFactory is a helper class for assigning marker styles, line styles,
170 and colors in ROOT plots."""
171
172 markerSequence = [ 8, 21, 22, 23, 29, 4, 25, 26, 27, 28, 30, 2, 3, 6]
173 lineSequence = [ 1, 2, 3, 4]
174 colorSequence = [ 1, 2, 4, 6, 8]
175
176 def __init__(self,incMarker=True,incLine=True,incColor=True):
177 self.idMarker = -1
178 self.idLine = -1
179 self.idColor = -1
180 self.incMarker = 1 if incMarker else 0
181 self.incLine = 1 if incLine else 0
182 self.incColor = 1 if incColor else 0
183
184 def nextStyle(self):
185 """Get next style cycling through markers, lines and colors as specified by
186 the flags in the constructor. Returns a triplet of (marker,line,color) styles."""
187 self.idMarker += self.incMarker
188 self.idLine += self.incLine
189 self.idColor += self.incColor
190 return (StyleFactory.markerSequence[self.idMarker % len(StyleFactory.markerSequence)],
191 StyleFactory.lineSequence[self.idLine % len(StyleFactory.lineSequence)],
192 StyleFactory.colorSequence[self.idColor % len(StyleFactory.colorSequence)])
193
195 """Get next marker style."""
196 self.idMarker +=1
197 return StyleFactory.markerSequence[self.idMarker % len(StyleFactory.markerSequence)]
198
199 def nextLineStyle(self):
200 """Get next line style."""
201 self.idLine +=1
202 return StyleFactory.lineSequence[self.idLine % len(StyleFactory.lineSequence)]
203
204 def nextColorStyle(self):
205 """Get next color."""
206 self.idColor +=1
207 return StyleFactory.colorSequence[self.idColor % len(StyleFactory.colorSequence)]
208
209
210def drawAxisFrame(xmin,xmax,ymin,ymax,title='',xTitleOffset=None,yTitleOffset=None,doPlot=True,protectFrame=True):
211 frame = ROOT.TH2F('axisFrame',title,100,xmin,xmax,100,ymin,ymax)
212 frame.SetStats(False)
213 if xTitleOffset is not None:
214 frame.GetXaxis().SetTitleOffset(xTitleOffset)
215 if yTitleOffset is not None:
216 frame.GetYaxis().SetTitleOffset(yTitleOffset)
217 if doPlot:
218 frame.Draw()
219 if protectFrame:
220 protect(frame)
221 return frame
222
223
224def drawHorizontalBand(xmin,xmax,y,ywidth,color=33,protectBand=True,centralLine=False):
225 """Draw a horizontal band of width +-ywidth. Nice colors are 19 and 33."""
226 b = ROOT.TH1F('band','band',1,xmin,xmax)
227 b.SetBinContent(1,y)
228 b.SetBinError(1,ywidth)
229 b.SetFillColor(color)
230 b.SetMarkerColor(color)
231 b.SetMarkerSize(0.01)
232 b.Draw('SAMEe2')
233 if centralLine:
234 b.Draw('SAMEL')
235 if protectBand:
236 protect(b)
237 return b
238
239
240def drawText(x=0.74,y=0.87,dy=0.06,text='',font=62,color=1,align=11,linesep=';'):
241 """Draw a variable number of lines of text."""
242 t = ROOT.TLatex()
243 t.SetNDC()
244 t.SetTextFont(font)
245 t.SetTextColor(color)
246 t.SetTextAlign(align)
247 t.SetTextSize(dy/1.2)
248 for line in text.split(linesep):
249 t.DrawLatex(x,y,line)
250 y = y-dy
251 # Seems TLatex text doesn't need to be protected - why not?
252 return t
253
254
255#def drawLegend(x=0.18,y=0.9,s=0.07,legendList=[],fillColor=0,lineColor=0):
256def drawLegend(x1,y1,x2,y2,legendList=[],fillColor=0,lineColor=0,textSize=None,protectLegend=True):
257 """Draw a legend with one or more entries. legendList is a list of lists,
258 where each inner list defines the object, text and option of a legend entry.
259 NOTE: Must store returned TLegend, otherwise legend will disappear!"""
260 nlines = len(legendList)
261 if not nlines:
262 print ("ERROR: drawLegend called w/o any legend entries")
263 #maxchar = 0
264 #for e in legendList:
265 # maxchar = max(len(e[1]),maxchar)
266 #l = ROOT.TLegend(x,y-nlines*s,x+(2+maxchar)*s/3.7,y)
267 l = ROOT.TLegend(x1,y1,x2,y2)
268 l.SetFillColor(fillColor)
269 l.SetLineColor(lineColor)
270 l.SetShadowColor(lineColor)
271 if textSize:
272 l.SetTextSize(textSize)
273 for e in legendList:
274 l.AddEntry(e[0],e[1],e[2])
275 l.Draw()
276 if protectLegend:
277 protect(l)
278 return l
279
280
281def moveStats(h,dx,dy,xw=0,yw=0,label=''):
282 if not h:
283 return
284 ROOT.gPad.Update()
285 st = h.GetListOfFunctions().FindObject('stats')
286 if not st:
287 print ('ERROR: No stats found - cannot move it')
288 return
289 st.SetTextColor(h.GetLineColor())
290 st.SetX1NDC(st.GetX1NDC()+dx)
291 st.SetY1NDC(st.GetY1NDC()+dy)
292 st.SetX2NDC(st.GetX2NDC()+xw if xw!=0 else st.GetX2NDC()+dx)
293 st.SetY2NDC(st.GetY2NDC()+yw if yw!=0 else st.GetY2NDC()+dy)
294 if not label:
295 h.SetName(label)
296 ROOT.gPad.Modified()
297 ROOT.gPad.Update
298
299
300def atlasLabel(x,y,isPreliminary=False,color=1,offset=0.115,isForApproval=False,energy=8,customstring="",size=0.05):
301 if x is None or y is None:
302 print ("Must set (x,y) position using --atlasx and --atlasy to add labels. No ATLAS labels created.")
303 return
304 offset = offset/0.05*size
305 l = ROOT.TLatex()
306 l.SetNDC()
307 l.SetTextFont(72)
308 l.SetTextSize(size)
309 l.SetTextColor(color)
310 l.DrawLatex(x,y,"ATLAS")
311
312 p = ROOT.TLatex()
313 p.SetNDC()
314 p.SetTextFont(42)
315 p.SetTextSize(size)
316 p.SetTextColor(color)
317
318 if customstring != "":
319 p.DrawLatex(x+offset,y,customstring)
320 else:
321 if (isPreliminary):
322 p.DrawLatex(x+offset,y,"Preliminary")
323 if (isForApproval):
324 #p.DrawLatex(x+offset,y,"Internal for approval")
325 p.DrawLatex(x+offset,y,"Internal")
326
327 if energy:
328 if float(energy)<15:
329 p.DrawLatex(x, y-(0.07/0.05*size), "#sqrt{s} = %s TeV" % energy)
330 else:
331 p.DrawLatex(x, y-(0.07/0.05*size), "#sqrt{s} = %s GeV" % energy)
332
333
334
335def atlasStyle(protectStyle=True):
336 s = ROOT.TStyle('ATLAS','ATLAS Style V2.02')
337
338 # use plain black on white colors
339 icol=0 # WHITE
340 s.SetFrameBorderMode(icol)
341 s.SetFrameFillColor(icol)
342 s.SetCanvasBorderMode(icol)
343 s.SetCanvasColor(icol)
344 s.SetPadBorderMode(icol)
345 s.SetPadColor(icol)
346 s.SetStatColor(icol)
347 #s.SetFillColor(icol) # don't use: white fill color floa *all* objects
348
349 # set the paper & margin sizes
350 s.SetPaperSize(20,26)
351 s.SetPadTopMargin(0.05)
352 s.SetPadRightMargin(0.05)
353 s.SetPadBottomMargin(0.16)
354 s.SetPadLeftMargin(0.16)
355
356 # set title offsets (for axis label)
357 s.SetTitleXOffset(1.4)
358 s.SetTitleYOffset(1.4)
359
360 # use large fonts
361 #Int_t font=72 # Helvetica italics
362 font = 42 # Helvetica
363 tsize = 0.05
364 s.SetTextFont(font)
365
366 s.SetTextSize(tsize)
367 s.SetLabelFont(font,"x")
368 s.SetTitleFont(font,"x")
369 s.SetLabelFont(font,"y")
370 s.SetTitleFont(font,"y")
371 s.SetLabelFont(font,"z")
372 s.SetTitleFont(font,"z")
373
374 s.SetLabelSize(tsize,"x")
375 s.SetTitleSize(tsize,"x")
376 s.SetLabelSize(tsize,"y")
377 s.SetTitleSize(tsize,"y")
378 s.SetLabelSize(tsize,"z")
379 s.SetTitleSize(tsize,"z")
380
381 # use bold lines and markers
382 s.SetMarkerStyle(20)
383 s.SetMarkerSize(1.2)
384 s.SetHistLineWidth(2)
385 s.SetLineStyleString(2,"[12 12]") # postscript dashes
386
387 # get rid of X error bars and y error bar caps
388 #s.SetErrorX(0.001)
389
390 # get rid of error bar caps
391 s.SetEndErrorSize(0.)
392
393 # do not display any of the standard histogram decorations
394 s.SetOptTitle(0)
395 #s.SetOptStat(1111)
396 s.SetOptStat(0)
397 #s.SetOptFit(1111)
398 s.SetOptFit(0)
399
400 # put tick marks on top and RHS of plots
401 s.SetPadTickX(1)
402 s.SetPadTickY(1)
403
404 if protectStyle:
405 protect(s)
406
407 return s
408
409
410def setStyle(style = None):
411 if not style:
412 style = atlasStyle()
413 print ('\nApplying style %s (%s) ...\n' % (style.GetName(),style.GetTitle()))
414 ROOT.gROOT.SetStyle(style.GetName())
415 ROOT.gROOT.ForceStyle()
__init__(self, name='MyCanvas', size='default', xdivs=1, ydivs=1, saveAsList=None, autoSaveOnExit=None)
__init__(self, name='MyPlots', otherMethods=[])
genPlot(self, what='ALL', code='plot', labels=[], *args)
__init__(self, incMarker=True, incLine=True, incColor=True)
drawAxisFrame(xmin, xmax, ymin, ymax, title='', xTitleOffset=None, yTitleOffset=None, doPlot=True, protectFrame=True)
atlasStyle(protectStyle=True)
moveStats(h, dx, dy, xw=0, yw=0, label='')
drawText(x=0.74, y=0.87, dy=0.06, text='', font=62, color=1, align=11, linesep=';')
drawLegend(x1, y1, x2, y2, legendList=[], fillColor=0, lineColor=0, textSize=None, protectLegend=True)
atlasLabel(x, y, isPreliminary=False, color=1, offset=0.115, isForApproval=False, energy=8, customstring="", size=0.05)
drawHorizontalBand(xmin, xmax, y, ywidth, color=33, protectBand=True, centralLine=False)