def plot2dhistogram(hist, outfilepath, outfmts=['.png'], histtitle=None, logx=False, logy=False, drawoptions='colztexte', cmin=None, cmax=None, docmstext=False, cms_in_grid=True, cmstext_size_factor=0.3, extracmstext='', lumitext='', topmargin=None, bottommargin=None, leftmargin=None, rightmargin=None, extrainfos=[], infosize=None, infoleft=None, infotop=None ): # options: # - cmin and cmax: minimum and maximum values for the color scales # note: in default "colz" behaviour, bins above cmax are colored as cmax, # while bins below cmin are left blank. # they can be colored as cmin by using drawoption "col0z" instead of "colz", # but "col0ztexte" does not seem to write the bin contents for those bins... pt.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) # set global properties cheight = 500 # height of canvas cwidth = 700 # width of canvas titlefont = 4; titlesize = 22 axtitlefont = 4; axtitlesize = 22 infofont = 4 if infosize is None: infosize = 15 # title offset ytitleoffset = 1.5 xtitleoffset = 1.5 # margins: if topmargin is None: topmargin = 0.15 if bottommargin is None: bottommargin = 0.15 if leftmargin is None: leftmargin = 0.15 if rightmargin is None: rightmargin = 0.15 xmin = hist.GetXaxis().GetXmin() xmax = hist.GetXaxis().GetXmax() ymin = hist.GetYaxis().GetXmin() ymax = hist.GetYaxis().GetXmax() zmin = hist.GetMinimum() zmax = hist.GetMaximum()
def plotsinglehistogram(hist, figname, title=None, xaxtitle=None, yaxtitle=None, label=None, color=None, logy=False, drawoptions='', lumitext='', extralumitext='', topmargin=None, bottommargin=None, leftmargin=None, rightmargin=None, xaxlabelfont=None, xaxlabelsize=None, writebincontent=False, bincontentfont=None, bincontentsize=None, bincontentfmt=None, extrainfos=[], infosize=None, infoleft=None, infotop=None): ### drawing a single histogram # - label: string for the legend entry for this histogram. # note: if label is 'auto', the implicit title of the TH1 will be used. # - drawoptions: string passed to TH1.Draw. # use "HIST" for histogram style (no error bars) # "E" for error bars # "X0" to suppress horizontal error bars # see https://root.cern/doc/master/classTHistPainter.html for a full list of options # - extrainfos is a list of strings with extra info to display # - infosize: font size of extra info # - infoleft: left border of extra info text (default leftmargin + 0.05) # - infotop: top border of extra info text (default 1 - topmargin - 0.1) pt.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) ### parse arguments if color is None: color = ROOT.kAzure - 4 ### initializations # make canvas c1 = ROOT.TCanvas("c1", "c1") c1.SetCanvasSize(600, 600) pad1 = ROOT.TPad("pad1", "", 0., 0., 1., 1.) pad1.SetTicks(1, 1) pad1.SetFrameLineWidth(2) pad1.SetGrid() pad1.Draw() pad1.cd() # set fonts and text sizes titlefont = 6 titlesize = 26 if xaxlabelfont is None: xaxlabelfont = 4 if xaxlabelsize is None: xaxlabelsize = 22 yaxlabelfont = 4 yaxlabelsize = 22 axtitlefont = 4 axtitlesize = 26 legendfont = 4 if bincontentfont is None: bincontentfont = 4 if bincontentsize is None: bincontentsize = 12 if bincontentfmt is None: bincontentfmt = '{:.3f}' infofont = 4 if infosize is None: infosize = 20 # margins if leftmargin is None: leftmargin = 0.15 if rightmargin is None: rightmargin = 0.05 if topmargin is None: topmargin = 0.05 if bottommargin is None: bottommargin = 0.15 pad1.SetBottomMargin(bottommargin) pad1.SetLeftMargin(leftmargin) pad1.SetRightMargin(rightmargin) pad1.SetTopMargin(topmargin) # extra info box parameters if infoleft is None: infoleft = leftmargin + 0.05 if infotop is None: infotop = 1 - topmargin - 0.1 # legendbox pentryheight = 0.05 plegendbox = ([ leftmargin + 0.3, 1 - topmargin - pentryheight, 1 - rightmargin - 0.03, 1 - topmargin - 0.03 ]) ### set histogram properties hist.SetLineColor(color) hist.SetLineWidth(3) hist.Sumw2() ### make the legend if label is not None: leg = ROOT.TLegend(plegendbox[0], plegendbox[1], plegendbox[2], plegendbox[3]) leg.SetTextFont(10 * legendfont + 3) leg.SetFillColor(ROOT.kWhite) leg.SetBorderSize(1) if label == 'auto': label = hist.GetTitle() leg.AddEntry(hist, label, "l") hist.Draw(drawoptions) ### X-axis layout xax = hist.GetXaxis() xax.SetNdivisions(5, 4, 0, ROOT.kTRUE) xax.SetLabelFont(10 * xaxlabelfont + 3) xax.SetLabelSize(xaxlabelsize) if xaxtitle is not None: xax.SetTitle(xaxtitle) xax.SetTitleFont(10 * axtitlefont + 3) xax.SetTitleSize(axtitlesize) xax.SetTitleOffset(1.2) ### Y-axis layout if not logy: print(hist.GetMaximum()) hist.SetMaximum(hist.GetMaximum() * 1.2) print(hist.GetMaximum()) hist.SetMinimum(0.) else: c1.SetLogy() hist.SetMaximum(hist.GetMaximum() * 10) hist.SetMinimum(hist.GetMaximum() / 1e7) yax = hist.GetYaxis() yax.SetMaxDigits(3) yax.SetNdivisions(8, 4, 0, ROOT.kTRUE) yax.SetLabelFont(10 * yaxlabelfont + 3) yax.SetLabelSize(yaxlabelsize) if yaxtitle is not None: yax.SetTitle(yaxtitle) yax.SetTitleFont(10 * axtitlefont + 3) yax.SetTitleSize(axtitlesize) yax.SetTitleOffset(1.5) hist.Draw(drawoptions) # title # note: use of title is not recommended if title is not None: ttitle = ROOT.TLatex() ttitle.SetTextFont(10 * titlefont + 3) ttitle.SetTextSize(titlesize) titlebox = (0.15, 0.92) # draw all objects hist.Draw(drawoptions) ROOT.gPad.RedrawAxis() pt.drawLumi(pad1, extratext=extralumitext, lumitext=lumitext) if label is not None: leg.Draw("same") if title is not None: ttitle.DrawLatexNDC(titlebox[0], titlebox[1], title) # draw extra info tinfo = ROOT.TLatex() tinfo.SetTextFont(10 * infofont + 3) tinfo.SetTextSize(infosize) for i, info in enumerate(extrainfos): vspace = 0.07 * (float(infosize) / 20) tinfo.DrawLatexNDC(infoleft, infotop - (i + 1) * vspace, info) # write bin contents if writebincontent: bintext = ROOT.TLatex() bintext.SetTextAlign(21) bintext.SetTextFont(bincontentfont) bintext.SetTextSize(bincontentsize) for i in range(1, hist.GetNbinsX() + 1): xcoord = hist.GetXaxis().GetBinCenter(i) ycoord = hist.GetBinContent(i) + hist.GetBinErrorUp(i) printvalue = hist.GetBinContent(i) bintext.DrawLatex(xcoord, ycoord + 0.05, bincontentfmt.format(printvalue)) c1.SaveAs(figname.split('.')[0] + '.png') c1.SaveAs(figname.split('.')[0] + '.eps') c1.SaveAs(figname.split('.')[0] + '.pdf')
def plotdatavsmc(outfile, datahist, mchistlist, mcsysthist=None, mcstathist=None, dostat=True, signals=None, datalabel=None, labelmap=None, colormap=None, xaxtitle=None, xaxinteger=False, yaxtitle=None, yaxlog=False, yaxrange=None, p2yaxtitle=None, p2yaxrange=None, p1legendncols=None, p1legendbox=None, extracmstext='', lumi=None, extrainfos=[], infosize=None): ### make a (stacked) simulation vs. data plot # arguments: # - outfile is the output file where the figure will be saved # - datahist is the data histogram # - mchistlist is a list of simulated histograms # (note that the title of each histogram will be used to determine its label!) # (note that the title of each histogram will be used to determine its color!) # - mcsyshist is a histogram containing the total systematic variation on the simulation # (note: the bin content of mcsyshist must be the absolute difference wrt nominal!) # (note: if dostat is False, mcsysthist must be the total uncertainty, not only systematic! # if dostat is True, mcsysthist is syst only; # stat error will be taken from mcstathist if it is defined or from mchistlist otherwise!) # - mcstathist is a histogram containing the total statistical variation on the simulation # (note: see above!) # - signals = list of signal processes (will be put at the top of the plot) # - datalabel is a string that will be used as legend entry for datahist (default: Data) # - labelmap is a dict mapping strings (histogram titles) to legend entries # - colormap is a dict mapping strings (histogram titles) to colors # - xaxtitle is the x-axis title (default: no title) # - xaxinteger is a boolean whether to use only integer axis labels and ticks for the x-axis # - yaxtitle: y-axis title (default: no title) # - yaxlog is whether to make the y-axis log scale # - yaxrange: tuple of (min,max) for plotting range (default: automatic range) # - p2yaxtitle: y-axis title of bottom pad (default: datalabel/Pred.) # - p2yaxrange: tuple of (min,max) for plotting range of bottom pad (default: (0,2)) # - lumi is the luminosity value (float, in pb-1) that will be displayed # - extrainfos is a list of strings with extra info to display # - infosize: font size of extra info pt.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) ### parse arguments if datalabel is None: datalabel = "Data" # if no labelmap, use histogram titles if labelmap is None: labelmap = {} for hist in mchistlist: labelmap[hist.GetTitle()] = hist.GetTitle() # if no colormap, use default colors if colormap is None: clist = ([ ROOT.kRed - 7, ROOT.kOrange, ROOT.kCyan - 7, ROOT.kYellow + 1, ROOT.kBlue - 10, ROOT.kBlue - 6, ROOT.kTeal - 5, ROOT.kMagenta - 7 ]) colormap = {} for i, hist in enumerate(mchistlist): colormap[hist.GetTitle()] = clist[i % len(clist)] # y-axis title and range for bottom pad if p2yaxtitle is None: p2yaxtitle = datalabel + ' / Pred.' if p2yaxrange is None: p2yaxrange = (0., 1.999) ### define global parameters for size and positioning cheight = 600 # height of canvas cwidth = 600 # width of canvas rfrac = 0.3 # fraction of ratio plot in canvas # fonts and sizes: labelfont = 4 labelsize = 22 axtitlefont = 4 axtitlesize = 27 infofont = 4 if infosize is None: infosize = 20 # title offset ytitleoffset = 1.5 # temporary modification for an annoying comment on one figure: xtitleoffset = 3.5 # margins: p1topmargin = 0.07 p2bottommargin = 0.4 leftmargin = 0.15 rightmargin = 0.05 # legend box if p1legendbox is None: p1legendbox = [ leftmargin + 0.35, 1 - p1topmargin - 0.3, 1 - rightmargin - 0.03, 1 - p1topmargin - 0.03 ] if p1legendncols is None: p1legendncols = 2 p2legendbox = [leftmargin + 0.03, 0.84, 1 - rightmargin - 0.03, 0.97] # extra info box parameters infoleft = leftmargin + 0.05 infotop = 1 - p1topmargin - 0.1 # marker properties for data markerstyle = 20 markercolor = 1 markersize = 0.75 ### order mc histograms # order by sumofweights mchistlist = orderhistograms(mchistlist) # get signals to the back (i.e. on top of the plot) if signals is not None: for signal in signals[::-1]: sindex = findbytitle(mchistlist, signal) if (sindex > -1): indices = list(range(len(mchistlist))) indices.remove(sindex) indices = indices + [sindex] mchistlist = [mchistlist[i] for i in indices] ### operations on mc histograms mchistsum = mchistlist[0].Clone() mchistsum.Reset() mchiststack = ROOT.THStack("mchiststack", "") for hist in mchistlist: stackcol(hist, colormap.get(hist.GetTitle(), ROOT.kBlack)) clip(hist) # set negative bins to zero! mchistsum.Add(hist) mchiststack.Add(hist) ### calculate total mc error and set its histogram properties mcerror = mchistsum.Clone() mcstaterror = mchistsum.Clone() # in case of dostat=False: mcsysthist represents total variation to plot if not dostat: if mcsysthist is None: raise Exception('ERROR in histplotter.py: option dostat = False' + ' is not compatible with mcsysthist = None') else: for i in range(1, mchistsum.GetNbinsX() + 1): mcerror.SetBinError(i, mcsysthist.GetBinContent(i)) # in case of dostat=True: add stat errors and syst errors quadratically else: if mcstathist is not None: # take statistical variations from externally provided histograms for i in range(1, mchistsum.GetNbinsX() + 1): mcstaterror.SetBinError(i, mcstathist.GetBinContent(i)) mcerror.SetBinError(i, mcstathist.GetBinContent(i)) if mcsysthist is not None: for i in range(1, mchistsum.GetNbinsX() + 1): staterror = mcstaterror.GetBinError(i) systerror = mcsysthist.GetBinContent(i) mcerror.SetBinError( i, np.sqrt(np.power(staterror, 2) + np.power(systerror, 2))) mcerror.SetLineWidth(0) mcerror.SetMarkerStyle(0) mcerror.SetFillStyle(3254) mcerror.SetFillColor(ROOT.kBlack) ### calculate total and statistical mc error (scaled) scstaterror = mcerror.Clone() scerror = mcerror.Clone() for i in range(1, mcerror.GetNbinsX() + 1): scstaterror.SetBinContent(i, 1.) scerror.SetBinContent(i, 1.) if not mcerror.GetBinContent(i) == 0: scstaterror.SetBinError( i, mcstaterror.GetBinError(i) / mcstaterror.GetBinContent(i)) scerror.SetBinError( i, mcerror.GetBinError(i) / mcerror.GetBinContent(i)) else: scstaterror.SetBinError(i, 0.) scerror.SetBinError(i, 0.) # in case of dostat=False: plot only total scaled variation, same style as unscaled if not dostat: scstaterror.Reset() scerror.SetLineWidth(0) scerror.SetMarkerStyle(0) scerror.SetFillStyle(3254) scerror.SetFillColor(ROOT.kBlack) # in case of dostat=True: plot total and stat only scaled variation else: scstaterror.SetFillStyle(1001) scstaterror.SetFillColor(ROOT.kGray + 1) scstaterror.SetMarkerStyle(0) scerror.SetFillStyle(3254) scerror.SetFillColor(ROOT.kBlack) scerror.SetMarkerStyle(0) ### operations on data histogram datahist.SetMarkerStyle(markerstyle) datahist.SetMarkerColor(markercolor) datahist.SetMarkerSize(markersize) datahist.SetLineColor(markercolor) ### calculate data to mc ratio ratiograph = ROOT.TGraphAsymmErrors(datahist) for i in range(1, datahist.GetNbinsX() + 1): if not mchistsum.GetBinContent(i) == 0: ratiograph.GetY()[i - 1] *= 1. / mchistsum.GetBinContent(i) ratiograph.SetPointError( i - 1, 0, 0, datahist.GetBinErrorLow(i) / mchistsum.GetBinContent(i), datahist.GetBinErrorUp(i) / mchistsum.GetBinContent(i)) # avoid drawing empty mc or data bins else: ratiograph.GetY()[i - 1] = 1e6 if (datahist.GetBinContent(i) <= 0): ratiograph.GetY()[i - 1] += 1e6 ### make legend for upper plot and add all histograms legend = ROOT.TLegend(p1legendbox[0], p1legendbox[1], p1legendbox[2], p1legendbox[3]) legend.SetNColumns(p1legendncols) legend.SetFillStyle(0) legend.AddEntry(datahist, datalabel, "pe1") for hist in mchistlist[::-1]: legend.AddEntry(hist, labelmap.get(hist.GetTitle(), '-'), "f") legend.AddEntry(mcerror, "Uncertainty", "f") ### make legend for lower plot and add all histograms # (note: will not be drawn if dostat = False) legend2 = ROOT.TLegend(p2legendbox[0], p2legendbox[1], p2legendbox[2], p2legendbox[3]) legend2.SetNColumns(3) legend2.SetFillStyle(0) legend2.AddEntry(scstaterror, "Stat. uncertainty", "f") ### make canvas and pads c1 = ROOT.TCanvas("c1", "c1") c1.SetCanvasSize(cwidth, cheight) pad1 = ROOT.TPad("pad1", "", 0., rfrac, 1., 1.) pad1.SetTopMargin(p1topmargin) pad1.SetBottomMargin(0.03) pad1.SetLeftMargin(leftmargin) pad1.SetRightMargin(rightmargin) pad1.SetFrameLineWidth(2) pad1.Draw() pad2 = ROOT.TPad("pad2", "", 0., 0., 1., rfrac) pad2.SetTopMargin(0.01) pad2.SetBottomMargin(p2bottommargin) pad2.SetLeftMargin(leftmargin) pad2.SetRightMargin(rightmargin) pad2.SetFrameLineWidth(2) pad2.Draw() ### make upper part of the plot pad1.cd() # determine range of pad if (yaxlog): pad1.SetLogy() (rangemin, rangemax) = getminmax(datahist, mcerror, yaxlog) if yaxrange is not None: (rangemin, rangemax) = yaxrange # temporary modification for an annoying comment on one figure: #rangemax = rangemax*1.2 mcerror.SetMinimum(rangemin) mcerror.SetMaximum(rangemax) # X-axis layout xax = mcerror.GetXaxis() xax.SetNdivisions(10, 5, 0, ROOT.kTRUE) xax.SetLabelSize(0) # Y-axis layout yax = mcerror.GetYaxis() yax.SetMaxDigits(4) yax.SetNdivisions(10, 5, 0, ROOT.kTRUE) yax.SetLabelFont(10 * labelfont + 3) yax.SetLabelSize(labelsize) if yaxtitle is not None: yax.SetTitle(yaxtitle) yax.SetTitleFont(10 * axtitlefont + 3) yax.SetTitleSize(axtitlesize) yax.SetTitleOffset(ytitleoffset) # draw mcerror first to get range correct mcerror.Draw("e2") # now draw in correct order mchiststack.Draw("hist same") mcerror.Draw("e2 same") datahist.Draw("pe e1 x0 same") # (note: e1 draws error bars, x0 suppresses horizontal error bars) legend.Draw("same") # draw some extra info if needed ROOT.gPad.RedrawAxis() # draw header lumistr = '' if lumi is not None: lumistr = '{0:.3g}'.format(lumi / 1000.) + ' fb^{-1} (13 TeV)' pt.drawLumi(pad1, extratext=extracmstext, lumitext=lumistr) # draw extra info tinfo = ROOT.TLatex() tinfo.SetTextFont(10 * infofont + 3) tinfo.SetTextSize(infosize) for i, info in enumerate(extrainfos): vspace = 0.07 * (float(infosize) / 20) tinfo.DrawLatexNDC(infoleft, infotop - (i + 1) * vspace, info) ### make the lower part of the plot pad2.cd() # X-axis layout xax = scerror.GetXaxis() if xaxinteger: # option 1: #xax.SetOption("I") # does not work properly since it is not defined for TAxis, only TGAxis (?) # option 2: xax.SetNdivisions(xax.GetNbins() * 2, 0, 0, ROOT.kFALSE) for i in range(0, xax.GetNbins() + 1): xax.ChangeLabel(2 * i + 1, -1, -1, -1, -1, -1, " ") # works, but half-integer ticks are still there... # option 3: #xax.SetNdivisions(xax.GetNbins(),0,0,ROOT.kFALSE) #xax.CenterLabels() # does not work properly since labels are still half-integer but simply shifted else: xax.SetNdivisions(10, 5, 0, ROOT.kTRUE) xax.SetLabelSize(labelsize) xax.SetLabelFont(10 * labelfont + 3) if xaxtitle is not None: xax.SetTitle(xaxtitle) xax.SetTitleFont(10 * axtitlefont + 3) xax.SetTitleSize(axtitlesize) xax.SetTitleOffset(xtitleoffset) # Y-axis layout yax = scerror.GetYaxis() yax.SetRangeUser(p2yaxrange[0], p2yaxrange[1]) yax.SetTitle(p2yaxtitle) yax.SetMaxDigits(3) yax.SetNdivisions(4, 5, 0) yax.SetLabelFont(10 * labelfont + 3) yax.SetLabelSize(labelsize) yax.SetTitleFont(10 * axtitlefont + 3) yax.SetTitleSize(axtitlesize) yax.SetTitleOffset(ytitleoffset) # draw objects scerror.Draw("e2") if dostat: scstaterror.Draw("e2 same") ratiograph.Draw("p e1 same") if dostat: legend2.Draw("same") ROOT.gPad.RedrawAxis() # make and draw unit ratio line xmax = datahist.GetXaxis().GetBinUpEdge(datahist.GetNbinsX()) xmin = datahist.GetXaxis().GetBinLowEdge(1) line = ROOT.TLine(xmin, 1, xmax, 1) line.SetLineStyle(2) line.Draw("same") ### save the plot c1.SaveAs(outfile + '.png') c1.SaveAs(outfile + '.eps') c1.SaveAs(outfile + '.pdf')
def plotmultihistograms(histlist, figname=None, title=None, xaxtitle=None, yaxtitle=None, normalize=False, normalizefirst=False, dolegend=True, labellist=None, colorlist=None, logy=False, ymaxlinfactor=1.8, yminlogfactor=0.2, ymaxlogfactor=100, drawoptions='', lumitext='', extralumitext='', doratio=False, ratiorange=None, ylims=None, yminzero=False): ### plot multiple overlaying histograms (e.g. for shape comparison) # note: the ratio plot will show ratios w.r.t. the first histogram in the list! # arguments: # - histlist, colorlist, labellist: lists of TH1, ROOT colors and labels respectively # - figname: name of the figure to save (if None, do not save but return plot dictionary) # - title, xaxtitle, yaxtitle, figname: self-explanatory # - normalize: boolean whether to put all histogram integrals to unit surface area # - normalizefirst: boolean whether to normalize first histogram in list and scale others # by the same factor (do not use together with normalize) # - dolegend: boolean whether to make a legend (histogram title is used if no labellist) # - logy: boolean whether to make y-axis logarithmic # - ymaxlinfactor: factor by which to multiply maximum y value (for linear y-axis) # - yminlogfactor and ymaxlogfactor: same as above but for log scale # - drawoptions: string passed to TH1.Draw # see https://root.cern/doc/master/classTHistPainter.html for a full list of options # - lumitext and extralumitext: luminosity value and extra text # - ratiorange: a tuple of (ylow,yhigh) for the ratio pad, default (0,2) # - ylims: a tuple of (ylow,yhigh) for the upper pad # - yminzero: whether to clip minimum y to zero. pt.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) ### parse arguments if colorlist is None: colorlist = ([ ROOT.kAzure - 4, ROOT.kAzure + 6, ROOT.kViolet, ROOT.kMagenta - 9, ROOT.kRed, ROOT.kGreen + 1, ROOT.kGreen - 1 ]) if (len(histlist) > len(colorlist)): raise Exception('ERROR in plotmultihistograms:' + ' histogram list is longer than color list') if (labellist is not None and len(labellist) != len(histlist)): raise Exception( 'ERROR in plotmultihistograms:' + ' length of label list does not agree with histogram list') ### define global parameters for size and positioning cheight = 600 # height of canvas cwidth = 600 # width of canvas rfrac = 0.33 # fraction of bottom plot showing the ratio if not doratio: rfrac = 0 # fonts and sizes: labelfont = 4 labelsize = 22 axtitlefont = 4 axtitlesize = 26 infofont = 4 infosize = 26 legendfont = 4 # margins and title offsets ytitleoffset = 1.5 p1topmargin = 0.05 p1bottommargin = 0.15 xtitleoffset = 1. if doratio: p1topmargin = 0.07 p1bottommargin = 0.03 xtitleoffset = 3.5 p2topmargin = 0.01 p2bottommargin = 0.4 leftmargin = 0.15 rightmargin = 0.05 # legend box pentryheight = 0.05 if doratio: pentryheight = 0.07 nentries = 1 + len(histlist) if nentries > 3: pentryheight = pentryheight * 0.8 plegendbox = ([ leftmargin + 0.3, 1 - p1topmargin - 0.03 - pentryheight * nentries, 1 - rightmargin - 0.03, 1 - p1topmargin - 0.03 ]) ### normalization and style operations on histograms scale = 1 if (normalizefirst): scale = histlist[0].Integral("width") for i, hist in enumerate(histlist): hist.SetLineWidth(3) hist.SetLineColor(colorlist[i]) hist.SetMarkerSize(0) if normalize: scale = hist.Integral("width") for j in range(0, hist.GetNbinsX() + 2): hist.SetBinContent(j, hist.GetBinContent(j) / scale) hist.SetBinError(j, hist.GetBinError(j) / scale) ### make ratio histograms ratiohistlist = [] for hist in histlist: rhist = hist.Clone() for j in range(0, rhist.GetNbinsX() + 2): scale = histlist[0].GetBinContent(j) if scale < 1e-12: rhist.SetBinContent(j, 0) rhist.SetBinError(j, 10) else: rhist.SetBinContent(j, rhist.GetBinContent(j) / scale) rhist.SetBinError(j, rhist.GetBinError(j) / scale) ratiohistlist.append(rhist) ### make legend for upper plot and add all histograms legend = ROOT.TLegend(plegendbox[0], plegendbox[1], plegendbox[2], plegendbox[3]) legend.SetNColumns(1) legend.SetFillColor(ROOT.kWhite) legend.SetTextFont(10 * legendfont + 3) legend.SetBorderSize(1) for i, hist in enumerate(histlist): label = hist.GetTitle() if labellist is not None: label = labellist[i] legend.AddEntry(hist, label, "l") ### make canvas and pads c1 = ROOT.TCanvas("c1", "c1") c1.SetCanvasSize(cwidth, cheight) pad1 = ROOT.TPad("pad1", "", 0., rfrac, 1., 1.) pad1.SetTopMargin(p1topmargin) pad1.SetBottomMargin(p1bottommargin) pad1.SetLeftMargin(leftmargin) pad1.SetRightMargin(rightmargin) pad1.SetTicks(1, 1) pad1.SetFrameLineWidth(2) pad1.SetGrid() pad1.Draw() if doratio: pad2 = ROOT.TPad("pad2", "", 0., 0., 1., rfrac) pad2.SetTopMargin(p2topmargin) pad2.SetBottomMargin(p2bottommargin) pad2.SetLeftMargin(leftmargin) pad2.SetRightMargin(rightmargin) pad2.SetTicks(1, 1) pad2.SetFrameLineWidth(2) pad2.SetGrid() pad2.Draw() ### make upper part of the plot pad1.cd() # get x-limits (for later use) nbins = histlist[0].GetNbinsX() xlims = (histlist[0].GetBinLowEdge(1), histlist[0].GetBinLowEdge(nbins) + histlist[0].GetBinWidth(nbins)) # get and set y-limits (totmin, totmax) = ht.getminmax(histlist) # in case of log scale if logy: pad1.SetLogy() if ylims is None: ylims = (totmin * yminlogfactor, totmax * ymaxlogfactor) # in case of lin scale else: if ylims is None: ylims = (0., totmax * ymaxlinfactor) if yminzero and ylims[0] < 0: ylims = (0., ylims[1]) histlist[0].SetMaximum(ylims[1]) histlist[0].SetMinimum(ylims[0]) # X-axis layout xax = histlist[0].GetXaxis() xax.SetNdivisions(5, 4, 0, ROOT.kTRUE) if doratio: xax.SetLabelSize(0) else: xax.SetLabelSize(labelsize) xax.SetLabelFont(10 * labelfont + 3) if xaxtitle is not None: xax.SetTitle(xaxtitle) xax.SetTitleFont(10 * axtitlefont + 3) xax.SetTitleSize(axtitlesize) xax.SetTitleOffset(xtitleoffset) # Y-axis layout yax = histlist[0].GetYaxis() yax.SetMaxDigits(3) yax.SetNdivisions(8, 4, 0, ROOT.kTRUE) yax.SetLabelFont(10 * labelfont + 3) yax.SetLabelSize(labelsize) if yaxtitle is not None: yax.SetTitle(yaxtitle) yax.SetTitleFont(10 * axtitlefont + 3) yax.SetTitleSize(axtitlesize) yax.SetTitleOffset(ytitleoffset) # histograms histlist[0].Draw(drawoptions) for hist in histlist[1:]: hist.Draw("same " + drawoptions) if dolegend: legend.Draw("same") ROOT.gPad.RedrawAxis() # draw header pt.drawLumi(pad1, extratext=extralumitext, lumitext=lumitext, rfrac=rfrac) if not doratio: # return or save the plot if figname is None: plotobject = { 'canvas': c1, 'pads': (pad1), 'xlims': xlims, 'ylims': ylims, 'legend': legend } return plotobject else: c1.SaveAs(figname.replace('.png', '') + '.png') c1.SaveAs(figname.replace('.png', '') + '.eps') c1.SaveAs(figname.replace('.png', '') + '.pdf') return None ### make the lower part of the plot pad2.cd() xax = ratiohistlist[0].GetXaxis() xax.SetNdivisions(5, 4, 0, ROOT.kTRUE) xax.SetLabelSize(labelsize) xax.SetLabelFont(10 * labelfont + 3) if xaxtitle is not None: xax.SetTitle(xaxtitle) xax.SetTitleFont(10 * axtitlefont + 3) xax.SetTitleSize(axtitlesize) xax.SetTitleOffset(xtitleoffset) # Y-axis layout yax = ratiohistlist[0].GetYaxis() if ratiorange == None: ratiorange = (0, 1.999) yax.SetRangeUser(ratiorange[0], ratiorange[1]) yax.SetMaxDigits(3) yax.SetNdivisions(4, 5, 0, ROOT.kTRUE) yax.SetLabelFont(10 * labelfont + 3) yax.SetLabelSize(labelsize) yax.SetTitle('Ratio') yax.SetTitleFont(10 * axtitlefont + 3) yax.SetTitleSize(axtitlesize) yax.SetTitleOffset(ytitleoffset) # draw objects ratiohistlist[0].Draw(drawoptions) for hist in ratiohistlist[1:]: hist.Draw("same " + drawoptions) ROOT.gPad.RedrawAxis() # make and draw unit ratio line xmax = histlist[0].GetXaxis().GetBinUpEdge(histlist[0].GetNbinsX()) xmin = histlist[0].GetXaxis().GetBinLowEdge(1) line = ROOT.TLine(xmin, 1, xmax, 1) line.SetLineStyle(2) line.Draw("same") # return or save the plot if figname is None: plotobject = { 'canvas': c1, 'pads': (pad1, pad2), 'xlims': xlims, 'ylims': ylims, 'legend': legend } return plotobject else: c1.SaveAs(figname.replace('.png', '') + '.png') c1.SaveAs(figname.replace('.png', '') + '.eps') c1.SaveAs(figname.replace('.png', '') + '.pdf') return None
def multi_contour_plot(histlist, outfilepath, outfmts=['.png'], contours=None, labellist=None, colormaplist=None, title=None, titlesize=None, xaxtitle=None, xaxtitlesize=None, xaxtitleoffset=None, yaxtitle=None, yaxtitlesize=None, yaxtitleoffset=None, extracmstext='', lumitext='', extrainfos=[], infosize=None, infoleft=None, infotop=None): ### draw multiple 2D contours in a figure # input arguments: # - histlist: list of TH2D objects # - outfilepath: path where to store the figure # - outfmts: list of output formats (.png, .pdf, .eps and possibly others are allowed) # - contours: either a list with countour levels to draw, # or an integer number of (equally-spaced) contour levels (default 5). # note: contour levels are the same for each histogram # and determined based on the first histogram in histlist; # possibly to be made more flexible later! # - labellist: list of legend entries # - colorlist: list of ROOT color maps # note that the maps must be in string format and not contain 'ROOT.', # so for example 'kBird', 'kViridis', etc. # notes: # - the axes will be drawn based on the first histogram in histlist # (though in most foreseen use cases they would all have the same axis ranges anyway) pt.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) # set global properties cheight = 500 # height of canvas cwidth = 700 # width of canvas titlefont = 4 if titlesize is None: titlesize = 22 xaxtitlefont = 4 if xaxtitlesize is None: xaxtitlesize = 22 yaxtitlefont = 4 if yaxtitlesize is None: yaxtitlesize = 22 infofont = 4 if infosize is None: infosize = 15 # title offset if yaxtitleoffset is None: ytitleoffset = 1.5 if xaxtitleoffset is None: xtitleoffset = 1.5 # margins topmargin = 0.15 bottommargin = 0.15 leftmargin = 0.15 rightmargin = 0.15 # legend box legendbox = [ leftmargin + 0.45, 1 - topmargin - 0.2, 1 - rightmargin - 0.03, 1 - topmargin - 0.03 ] # extra info box parameters if infoleft is None: infoleft = leftmargin + 0.05 if infotop is None: infotop = 1 - topmargin - 0.1 # get a reference histogram for axes hist = histlist[0] # get axis properties xmin = hist.GetXaxis().GetXmin() xmax = hist.GetXaxis().GetXmax() ymin = hist.GetYaxis().GetXmin() ymax = hist.GetYaxis().GetXmax() zmin = hist.GetMinimum() zmax = hist.GetMaximum() # parse color map list argument # note: using multiple color maps in one canvas is not easy in ROOT, # and requires the magic below. if colormaplist is None: colormaplist = ['kViridis', 'kSolar'] if len(histlist) > len(colormaplist): raise Exception( 'ERROR: this many histograms do not support automatic colors.' + ' Please provide a color map list explicitly.') colormaplist = colormaplist[:len(histlist)] texeclist = [] for i, cmap in enumerate(colormaplist): texeclist.append( ROOT.TExec('ex{}'.format(i), 'gStyle->SetPalette({});'.format(cmap))) # parse labellist argument dolegend = True if labellist is None: labellist = [''] * len(histlist) dolegend = False # set the line widths and colors for all histograms # note: line colors are not used for drawing (color palettes are used instead), # but they are displayed in the legend. # note: again, a bit of ROOT magic seems to be needed to simply extract # a color from a color map... for ihist, cmap in zip(histlist, colormaplist): ihist.SetLineWidth(2) cmapnb = getattr(ROOT, cmap) ROOT.gStyle.SetPalette(cmapnb) color = ROOT.TColor.GetPalette().At(128) ihist.SetLineColor(color) # make legend and add all histograms legend = ROOT.TLegend(legendbox[0], legendbox[1], legendbox[2], legendbox[3]) legend.SetFillStyle(1001) legend.SetFillColor(ROOT.kWhite) for ihist, ilabel in zip(histlist, labellist): legend.AddEntry(ihist, ilabel, "l") # parse contours argument if contours is None: contours = 5 if isinstance(contours, int): contours = list(np.linspace(zmin, zmax, contours, endpoint=False)) ncontours = len(contours) contours = array.array('d', contours) # set the contours for all histograms for ihist in histlist: ihist.SetContour(ncontours, contours) # create canvas c1 = ROOT.TCanvas("c1", "c1") c1.SetCanvasSize(cwidth, cheight) c1.SetTopMargin(topmargin) c1.SetBottomMargin(bottommargin) c1.SetLeftMargin(leftmargin) c1.SetRightMargin(rightmargin) # set title and label properties if xaxtitle is None: xaxtitle = '' hist.GetXaxis().SetTitle(xaxtitle) hist.GetXaxis().SetTitleOffset(xtitleoffset) hist.GetXaxis().SetTitleFont(xaxtitlefont * 10 + 3) hist.GetXaxis().SetTitleSize(xaxtitlesize) if yaxtitle is None: yaxtitle = '' hist.GetYaxis().SetTitle(yaxtitle) hist.GetYaxis().SetTitleOffset(ytitleoffset) hist.GetYaxis().SetTitleFont(yaxtitlefont * 10 + 3) hist.GetYaxis().SetTitleSize(yaxtitlesize) # set title ttitle = ROOT.TLatex() ttitle.SetTextFont(titlefont * 10 + 3) ttitle.SetTextSize(titlesize) # draw contours hist.Draw() for i, ihist in enumerate(histlist): texeclist[i].Draw('same') ihist.Draw('cont1 same') c1.Update() # redraw first histogram to get the axes texeclist[0].Draw('same') hist.Draw('cont1 same') c1.Update() # draw diagonal line bisect = ROOT.TLine(xmin, ymin, xmax, ymax) bisect.SetLineWidth(1) bisect.SetLineColor(ROOT.kRed) bisect.SetLineStyle(9) bisect.Draw('same') # draw header if dolegend: legend.Draw('same') ttitle.DrawLatexNDC(leftmargin, 0.9, histtitle) pt.drawLumi(c1, extratext=extracmstext, cmstext_size_factor=0.3, lumitext=lumitext) # draw extra info tinfo = ROOT.TLatex() tinfo.SetTextFont(10 * infofont + 3) tinfo.SetTextSize(infosize) for i, info in enumerate(extrainfos): vspace = 0.07 * (float(infosize) / 20) tinfo.DrawLatexNDC(infoleft, infotop - (i + 1) * vspace, info) # save the plot c1.Update() outfilepath = os.path.splitext(outfilepath)[0] for outfmt in outfmts: c1.SaveAs(outfilepath + outfmt) # set gStyle back to default after messing with it ROOT.gStyle.SetPalette(ROOT.kBird)
def plot2dhistogram(hist, outfilepath, histtitle='', logx=False, logy=False, drawoptions='colztexte'): tools.setTDRstyle() ROOT.gROOT.SetBatch(ROOT.kTRUE) # set global properties cheight = 500 # height of canvas cwidth = 700 # width of canvas titlefont = 4 titlesize = 22 axtitlefont = 4 axtitlesize = 22 # title offset ytitleoffset = 1.5 xtitleoffset = 1.5 # margins: topmargin = 0.15 bottommargin = 0.15 leftmargin = 0.15 rightmargin = 0.15 xmin = hist.GetXaxis().GetXmin() xmax = hist.GetXaxis().GetXmax() ymin = hist.GetYaxis().GetXmin() ymax = hist.GetYaxis().GetXmax() zmin = hist.GetMinimum() zmax = hist.GetMaximum() # create canvas c1 = ROOT.TCanvas("c1", "c1") c1.SetCanvasSize(cwidth, cheight) c1.SetTopMargin(topmargin) c1.SetBottomMargin(bottommargin) c1.SetLeftMargin(leftmargin) c1.SetRightMargin(rightmargin) # create copy of histogram with adapted outerflow bins if needed if (logx or logy): hist = getsmallouterflowhist(hist) hist.SetMinimum(zmin) hist.SetMaximum(zmax) # set title and label properties hist.GetXaxis().SetTitleOffset(xtitleoffset) hist.GetXaxis().SetTitleFont(axtitlefont * 10 + 3) hist.GetXaxis().SetTitleSize(axtitlesize) hist.GetYaxis().SetTitleOffset(ytitleoffset) hist.GetYaxis().SetTitleFont(axtitlefont * 10 + 3) hist.GetYaxis().SetTitleSize(axtitlesize) if logx: c1.SetLogx() hist.GetXaxis().SetMoreLogLabels() if logy: c1.SetLogy() hist.GetYaxis().SetMoreLogLabels() # set title ttitle = ROOT.TLatex() ttitle.SetTextFont(titlefont * 10 + 3) ttitle.SetTextSize(titlesize) # draw hist.Draw(drawoptions) ttitle.DrawLatexNDC(leftmargin, 0.9, histtitle) # save the plot c1.Update() c1.SaveAs(outfilepath)