Exemplo n.º 1
0
width = 0.4
textsize = 0.04
xstart = 0.4
ystart = 0.75

latex = TLatex()
latex.SetNDC()
latex.SetTextAlign(12)
latex.SetTextSize(textsize)
latex.DrawLatex(xstart, 0.85, "CMS Preliminary")
latex.DrawLatex(xstart, 0.80, "4.96 fb^{-1}, #sqrt{s}=7TeV")

legend = TLegend(xstart, ystart - 7.7 * textsize, xstart + width, ystart)
legend.SetFillColor(0)
legend.SetTextSize(textsize)
legend.SetColumnSeparation(0.0)
legend.SetEntrySeparation(0.1)
legend.SetMargin(0.2)

# Add data to legend
if nj == "2":
    legend.AddEntry(rooplot.findObject("h_dataset"), "2-jet Data", "p")
elif nj == "3":
    legend.AddEntry(rooplot.findObject("h_dataset"), "3-jet Data", "p")
elif nj == "4":
    legend.AddEntry(rooplot.findObject("h_dataset"), "#geq4-jet Data", "p")
elif nj == "23":
    legend.AddEntry(rooplot.findObject("h_dataset"), "2+3-jet Data", "p")

# Add functions to legend
for func in ["f1", "f2", "f3"]:
Exemplo n.º 2
0
def plotter(plotdir,
            plot,
            xTitle,
            logY,
            channels=['VV'],
            includeData=False,
            scaleSignal=0,
            UserRange=[None, None, None, None],
            initPath=''):

    # channelTex={'WPWP':'W^{+}W^{+}', 'WPWM':'W^{+}W^{-}','WMWM':'W^{-}W^{-}','WPZ':'W^{+}Z','WMZ':'W^{-}Z','ZZ':'ZZ'}
    channelTex = {'ZZ': 'ZZ'}
    # plotstyle=[(1,1),(1,2),(2,1),(2,2),(4,1),(4,2)]
    plotstyle = [(1, 1)]
    #             0              1                       2                        3             4              5             6
    # Backgrounds=['QCD',     'WJetsToQQ_HT600ToInf', 'ZJetsToQQ_HT600ToInf',     'TT',         'WW',          'WZ',         'ZZ']
    # BGColors=   [rt.kAzure+7,   rt.kRed-4,              rt.kOrange-2,            rt.kGreen+2,  rt.kOrange+7,  rt.kBlue+1,   rt.kMagenta+2]
    # BGTeX=      ['QCD',        'W+JetsToQQ',           'Z+JetsToQQ',             'TTbar'],      'WW',          'WZ',         'ZZ']
    #stackOrder= [4,5,6,2,1,3,0]
    Backgrounds = [
        'QCD'
    ]  # 'WJetsToQQ_HT600ToInf', 'ZJetsToQQ_HT600ToInf', 'ZZ']  'TT',         'WW',          'WZ',         'ZZ']
    BGColors = [
        rt.kAzure + 7
    ]  #,              rt.kOrange-2,            rt.kGreen+2],  rt.kOrange+7,  rt.kBlue+1,   rt.kMagenta+2]
    BGTeX = ['QCD']  #

    stackOrder = [0]

    PreSelection = [
        'nocuts', 'common', 'corrections', 'cleaner', 'softdropmassCorr',
        'AK4pfidfilter', 'AK8pfidfilter', 'invMAk8sel', 'detaAk8sel',
        'AK8N2sel'
    ]
    Selection = [
        'preselection', 'softdropAK8sel', 'tau21sel', 'deltaR48', 'VVRegion',
        'AK4N2sel', 'OpSignsel', 'detaAk4sel', 'invMAk4sel_1p0', 'Kin_AK8'
    ]

    cutnames = [
        'cleaner', 'AK8N2sel', 'invMAk8sel', 'detaAk8sel', 'softdropAK8sel',
        'tau21sel', 'AK4cleaner', 'AK4N2sel', 'OpSignsel', 'detaAk4sel',
        'invMAk4sel_1p0'
    ]

    cuts = {
        'cleaner':
        '#splitline{p_{T-AK8} > 200 GeV, |#eta_{AK8}| < 2.5}{p_{T-AK4} > 30 GeV, |#eta_{AK4}| < 5.0}',
        'AK8N2sel': 'N_{AK8} #geq 2',
        'invMAk8sel': 'M_{jj-AK8} > 1050 GeV',
        'detaAk8sel': '|#Delta#eta_{jj-AK8}|<1.3',
        'softdropAK8sel': '65 GeV <M_{SD}< 105 GeV',
        'tau21sel': '0 #leq #tau_{2}/#tau_{1}<0.45',
        # 'AK4cleaner':'p_{T-AK4} > 30 GeV, |#eta_{AK4}| < 5.0',
        'AK4cleaner': '',
        'AK4N2sel': 'N_{AK4} #geq 2',
        'OpSignsel': '#eta_{1-AK4} #eta_{2-AK4} < 0',
        'detaAk4sel': '|#Delta#eta_{jj-AK4}| > 3.0',
        'invMAk4sel_1p0': 'M_{jj-AK4} > 1.0 TeV'
    }

    VV = ('VV' in channels)
    seperate = (not VV)
    if VV:
        #channels=["WPWP","WPWM","WMWM","WPZ","WMZ","ZZ"]
        channels = ["ZZ"]

    plottitle = plotdir + '_' + plot

    lumi = 36.814
    xLabelSize = 18.
    yLabelSize = 18.
    xTitleSize = 20.
    yTitleSize = 22.
    xTitleOffset = 4.
    yTitleOffset = 1.3

    printout = False
    Portrait = True
    cutname = False
    ratio = includeData

    if ('highbin' in plot):
        binning = 'dijetbinning'
    else:
        binning = 'default'

    if (Portrait):
        canvX = 600
        canvY = 600
    else:
        canvX = 900
        canvY = 675

    if (plotdir in PreSelection):
        region = 'PreSelection'
        initPath = ''
        referenceHistPath = 'detaAk8sel/N_pv'
        if (PreSelection.index(plotdir) < 4 and ('softdrop' in plot)):
            return 'SofdropMass not filled yet!'
    else:
        region = 'SignalRegion'
        referenceHistPath = 'tau21sel/N_pv'
    referenceHistPath = plotdir + '/' + plot

    if (initPath == ''):
        path = '/nfs/dust/cms/user/loemkerj/bachelor/CMSSW_10_2_16/src/UHH2/aQGCVVjjhadronic/%s' % region
    else:
        path = initPath
    outputPath = path.replace(
        '/nfs/dust/cms/user/loemkerj/bachelor/CMSSW_10_2_16/src/UHH2/aQGCVVjjhadronic/SignalRegion',
        'plots/')
    if (plotdir in PreSelection):
        CutNumber = PreSelection.index(plotdir)
    else:
        CutNumber = Selection.index(plotdir)
    outputPath = outputPath + '/%02i_%s' % (CutNumber, plotdir) + '/'
    if (printout):
        print('InputPath:', path)
        print('OutputPath:', outputPath)
    #check if OutputPath exists - and if not create it!
    if not os.path.exists(outputPath):
        os.makedirs(outputPath)
    # path='/home/albrec/Master/signal/'
    scaleVV = (scaleSignal != 0)
    VVScale = scaleSignal

    if (UserRange[2] == None or UserRange[3] == None):
        YRangeUser = False
        Ymin = UserRange[2]
        Ymax = UserRange[3]
    else:
        YRangeUser = True
        Ymin = UserRange[2]
        Ymax = UserRange[3]

    if (UserRange[0] == None or UserRange[1] == None):
        XRangeUser = False
        Xmin = UserRange[0]
        Xmax = UserRange[1]
    else:
        XRangeUser = True
        Xmin = UserRange[0]
        Xmax = UserRange[1]

    # YRangeUser=False
    # Ymin=0.11
    # Ymax=9*10**3

    # XRangeUser=False
    # Xmin=0
    # Xmax=6000.

    gROOT.ProcessLine("gErrorIgnoreLevel = 2001;")
    SFiles = []
    for i in range(len(channels)):
        SFiles.append(
            TFile(
                path +
                "/uhh2.AnalysisModuleRunner.MC.MC_aQGC_%sjj_hadronic_2016v3.root"
                % channels[i]))
#uhh2.AnalysisModuleRunner.MC.MC_aQGC_ZZjj_hadronic_2016v3.root
##Open Files to get BackgroundHist:
    BFiles = []
    for i in range(len(Backgrounds)):
        BFiles.append(
            TFile(path +
                  "/uhh2.AnalysisModuleRunner.MC.MC_%s.root" % Backgrounds[i]))

    #Open File to get DataHist:

# DataFile = TFile(path+"/uhh2.AnalysisModuleRunner.Data.DATA.root")

#gROOT.ProcessLine( "gErrorIgnoreLevel = 0;")

    if (includeData == True):
        #calculate QCDscale with Integrals from the following Histogram:
        # referenceHistPath = 'tau21sel/N_AK4'
        # referenceHistPath = 'detaAk8sel/N_pv'
        # referenceHistPath = 'tau21sel/met_pt_over_sumptAK8_2'
        QCDscale = float(DataFile.Get(referenceHistPath).Integral())
        QCDNorm = 1
        for i in range(len(BFiles)):
            if ('QCD' in BFiles[i].GetName()):
                QCDNorm = float(BFiles[i].Get(referenceHistPath).Integral())
            else:
                QCDscale -= float(BFiles[i].Get(referenceHistPath).Integral())
        QCDscale /= QCDNorm
    else:
        QCDscale = 1.0
    if (printout):
        print('using QCDscale:', QCDscale)

    SHists = []
    for i in range(len(channels)):
        SHists.append(SFiles[i].Get(plotdir + '/' + plot))

    BHists = []
    for i in range(len(BFiles)):
        BHists.append(BFiles[i].Get(plotdir + '/' + plot))
        if ('QCD' in BFiles[i].GetName()):
            BHists[-1].Scale(QCDscale)

    #if(includeData):
    #    DataHist=DataFile.Get(plotdir+'/'+plot)

    if (binning == 'dijetbinning'):
        fitbinning = array('d')
        binwidth = 200
        NBins = (14000 / binwidth) - ((1040 / binwidth) + 1)
        NBins = int(NBins)
        for i in range(NBins + 1):
            fitbinning.append(1050 + i * binwidth)

        for i in range(len(channels)):
            SHists[i] = SHists[i].Rebin(NBins, "new binning", fitbinning)
        for i in range(len(Backgrounds)):
            BHists[i] = BHists[i].Rebin(NBins, "new binning", fitbinning)

        if (includeData):
            DataHist = DataHist.Rebin(NBins, "new binning", fitbinning)

    canv = TCanvas(plottitle, plottitle, canvX, canvY)

    yplot = 0.7
    yratio = 0.3
    ymax = 1.0
    xmax = 1.0
    xmin = 0.0
    if (ratio):
        plotpad = TPad("plotpad", "Plot", xmin, ymax - yplot, xmax, ymax)
        ratiopad = TPad("ratiopad", "Ratio", xmin, ymax - yplot - yratio, xmax,
                        ymax - yplot)
    else:
        plotpad = TPad("plotpad", "Plot", xmin, ymax - yplot - yratio, xmax,
                       ymax)

    plotpad.SetTopMargin(0.08)
    plotpad.SetLeftMargin(0.1)
    plotpad.SetRightMargin(0.05)
    plotpad.SetTicks()
    plotpad.Draw()

    if (ratio):
        plotpad.SetBottomMargin(0.016)
        ratiopad.SetTopMargin(0.016)
        ratiopad.SetBottomMargin(0.35)
        ratiopad.SetLeftMargin(0.1)
        ratiopad.SetRightMargin(0.05)
        ratiopad.SetTicks()
        ratiopad.Draw()
    else:
        plotpad.SetBottomMargin(0.1)

    if (logY):
        plotpad.SetLogy()
        canv.SetLogy()
    if ('-logX' in xTitle):
        plotpad.SetLogx()
        if (ratio):
            ratiopad.SetLogx()
        canv.SetLogx()

    drawOptions = "HE"

    stack = THStack(plottitle, plottitle)

    BHist = THStack(plottitle, plottitle)

    # for i in range(len(Backgrounds)):
    for i in stackOrder:
        # BHists[i].SetFillColor(BGColors[i])
        BHists[i].SetLineColor(BGColors[i])
        BHist.Add(BHists[i], 'Hist')

    BHist.SetTitle(plottitle)

    BHistErr = BHists[0].Clone()
    for i in range(1, len(Backgrounds)):
        BHistErr.Add(BHists[i])

    BHistErr.SetFillStyle(3204)
    BHistErr.SetFillColor(rt.kGray + 2)
    BHistErr.SetLineColor(1)

    BGMax = BHist.GetMaximum()
    SIGMax = 0
    #if(VV):       #new
    #    SIGMax=VVsum.GetMaximum()
    #else:
    for i in range(len(channels)):
        tmpmax = SHists[i].GetMaximum()
        if (tmpmax > SIGMax):
            SIGMax = tmpmax
    if (scaleVV):
        SIGMax = SIGMax * VVScale
    if (logY):
        MAX = 0.9 * float(10**(magnitude(max(BGMax, SIGMax)) + 1))
        MIN = float(10**(magnitude(max(BGMax, SIGMax)) - 5))
        MIN += float(10**(magnitude(MIN)))
        legendMIN = math.log(max(BGMax, SIGMax)) / math.log(MAX)
    else:
        MAX = (1.0 / 0.8) * max(BGMax, SIGMax)
        legendMIN = 0.7
        MIN = 0.
    legendMIN = (legendMIN * 0.7) + 0.3 - 0.016

    legend = TLegend(0.5, 0.75, 0.85, 0.89)
    legend.SetFillStyle(0)
    legend.SetTextSize(0.02)
    legend.SetMargin(0.4)
    legend.SetNColumns(2)
    legend.SetColumnSeparation(0.3)

    if (includeData):
        DataHist.SetMarkerStyle(8)
        DataHist.SetLineColor(1)
        DataHist.SetTitle(plottitle)

    if VV:
        for i in range(len(channels)):
            if (i == 0):
                VVsum = SHists[i].Clone()
            else:
                VVsum.Add(SHists[i])
        legentry = 'VVjj'
        if (scaleVV):
            VVsum.Scale(VVScale)
            legentry += ' *%0.f' % VVScale
        VVsum.SetLineColor(1)
        VVsum.SetLineStyle(plotstyle[0][1])
        VVsum.SetLineWidth(2)
        legend.AddEntry(VVsum, legentry)
    else:
        for i in range(len(channels)):
            SHists[i].SetLineColor(plotstyle[i][0])
            SHists[i].SetLineStyle(plotstyle[i][1])
            SHists[i].SetLineWidth(2)
            legentry = "%sjj" % channelTex[channels[i]]
            if (scaleVV):
                SHists[i].Scale(VVScale)
                legentry += ' *%.2E' % VVScale
            legend.AddEntry(SHists[i], legentry)

    for i in stackOrder:
        legend.AddEntry(BHists[i], BGTeX[i], "f")
    legend.AddEntry(BHistErr, "stat. Uncertainty", "f")

    if (includeData):
        legend.AddEntry(DataHist, "Data", "lep")

    canv.SetTitle(plottitle)

    BHistErr.GetYaxis().SetTitle('Events')
    BHistErr.GetYaxis().SetRangeUser(MIN, MAX)
    BHistErr.GetYaxis().SetTitleFont(43)
    BHistErr.GetYaxis().SetTitleSize(yTitleSize)
    BHistErr.GetYaxis().SetTitleOffset(yTitleOffset)
    BHistErr.GetYaxis().SetLabelFont(43)
    BHistErr.GetYaxis().SetLabelSize(yLabelSize)
    if (ratio):
        BHistErr.GetXaxis().SetTitleSize(0.0)
        BHistErr.GetXaxis().SetLabelSize(0.0)
    else:
        BHistErr.GetXaxis().SetTitle(xTitle)
        BHistErr.GetXaxis().SetTitleFont(43)
        BHistErr.GetXaxis().SetTitleSize(xTitleSize)
        # BHistErr.GetXaxis().SetTitleOffset(xTitleOffset)
        BHistErr.GetXaxis().SetTitleOffset(1.2)
        BHistErr.GetXaxis().SetLabelFont(43)
        BHistErr.GetXaxis().SetLabelSize(xLabelSize)
        # BHistErr.GetXaxis().SetTickLength(0.08)
        # BHistErr.GetXaxis().SetNdivisions(506)

    if (YRangeUser):
        BHistErr.GetYaxis().SetRangeUser(Ymin, Ymax)
    if (XRangeUser):
        BHistErr.GetXaxis().SetRangeUser(Xmin, Xmax)

    plotpad.cd()

    BHistErr.Draw("E2")
    BHist.Draw("HistSAME")
    BHistErr.Draw("E2SAME")

    if (VV):
        VVsum.Draw("SAME" + drawOptions)
    elif ('-noSig' not in xTitle):
        for i in range(len(channels)):
            SHists[i].Draw("SAME" + drawOptions)

    if (includeData):
        DataHist.Draw("APE1SAME")

    plotpad.RedrawAxis()
    if (ratio):
        ratiopad.cd()

        if (includeData):
            ratioHist = DataHist.Clone()
        else:
            ratioHist = BHistErr.Clone()
        ratioHist.SetLineColor(rt.kBlack)
        # ratioHist.Sumw2()
        ratioHist.SetStats(0)
        ratioHist.Divide(BHistErr)
        ratioHist.SetMarkerStyle(21)
        ratioHist.SetMarkerSize(0.7)

        #Yaxis
        ratioHist.GetYaxis().SetRangeUser(0.3, 1.7)
        ratioHist.GetYaxis().SetTitle("Data/BG")
        ratioHist.GetYaxis().CenterTitle()
        ratioHist.GetYaxis().SetTitleFont(43)
        ratioHist.GetYaxis().SetTitleSize(yTitleSize)
        ratioHist.GetYaxis().SetTitleOffset(yTitleOffset)
        ratioHist.GetYaxis().SetLabelFont(43)
        ratioHist.GetYaxis().SetLabelSize(yLabelSize)
        ratioHist.GetYaxis().SetNdivisions(506)
        #Xaxis
        ratioHist.GetXaxis().SetTitle(xTitle)
        ratioHist.GetXaxis().SetTitleFont(43)
        ratioHist.GetXaxis().SetTitleSize(xTitleSize)
        ratioHist.GetXaxis().SetTitleOffset(xTitleOffset)
        ratioHist.GetXaxis().SetLabelFont(43)
        ratioHist.GetXaxis().SetLabelSize(xLabelSize)
        ratioHist.GetXaxis().SetTickLength(0.08)
        ratioHist.GetXaxis().SetNdivisions(506)

        # if(YRangeUser):
        #     ratioHist.GetYaxis().SetRangeUser(Ymin,Ymax)
        if (XRangeUser):
            ratioHist.GetXaxis().SetRangeUser(Xmin, Xmax)
            ratioXMin = Xmin
            ratioXMax = Xmax
        else:
            ratioXMin = ratioHist.GetXaxis().GetXmin()
            ratioXMax = ratioHist.GetXaxis().GetXmax()
        ratioHist.Draw("ep")

        zeropercent = TLine(ratioXMin, 1, ratioXMax, 1)
        zeropercent.Draw()
        plus10percent = TLine(ratioXMin, 1.1, ratioXMax, 1.1)
        plus10percent.SetLineStyle(rt.kDashed)
        plus10percent.Draw()
        minus10percent = TLine(ratioXMin, 0.9, ratioXMax, 0.9)
        minus10percent.SetLineStyle(rt.kDashed)
        minus10percent.Draw()

    canv.cd()
    gPad.RedrawAxis()
    legend.Draw()

    latex = TLatex()
    latex.SetNDC(kTRUE)
    latex.SetTextSize(20)
    latex.DrawLatex(0.69, 0.953, "%.2f fb^{-1} (13 TeV)" % lumi)
    latex.DrawLatex(0.1, 0.953, "private work")

    lastcut = 'nocuts'
    for cut in cutnames:
        if cut in plotdir:
            lastcut = cut

    if (not (lastcut == 'nocuts') and cutname):
        # latex.SetTextSize(0.03)
        latex.SetTextSize(15)
        for l in range(cutnames.index(lastcut) + 1):
            latex.DrawLatex(0.12, 0.8 - l * 0.04, cuts[cutnames[l]])

    canv.Update()
    canv.Print(outputPath + '/%s_%s.pdf' % (plotdir, plot))
    #prevents memory leak in Canvas Creation/Deletion
    #see: https://root.cern.ch/root/roottalk/roottalk04/2484.html
    gSystem.ProcessEvents()
    if (ratio):
        del ratiopad
    del plotpad, canv
    # gc.collect()
    return 'done!'
Exemplo n.º 3
0
class DataMCPlot(object):
    '''Handles a Data vs MC plot.

    Features a list of histograms (some of them being stacked),
    and several Drawing functions.
    '''
    _f_keeper = {}
    _t_keeper = {}

    def __init__(self, name):
        self.histosDict = {}
        self.histos = []
        self.supportHist = None
        self.name = name
        self.stack = None
        self.legendOn = True
        self.legend = None
        #        self.legendBorders = 0.20, 0.46, 0.44, 0.89
        #        self.legendPos = 'left'
        self.legendBorders = 0.20, 0.78, 0.80, 0.88
        self.legendPos = 'top'
        # self.lastDraw = None
        # self.lastDrawArgs = None
        self.nostack = None
        self.blindminx = None
        self.blindmaxx = None
        self.groups = {}
        self.axisWasSet = False
        self.histPref = histPref

    def __contains__(self, name):
        return name in self.histosDict

    def __getitem__(self, name):
        return self.histosDict[name]

    def readTree(self,
                 file_name,
                 tree_name='tree',
                 verbose=False,
                 friend_func=None):
        '''Cache files/trees'''
        if file_name in self.__class__._t_keeper:
            ttree = self.__class__._t_keeper[file_name]
            if verbose:
                print 'got cached tree', ttree
        else:
            tfile = self.__class__._f_keeper[file_name] = TFile.Open(file_name)
            ttree = self.__class__._t_keeper[file_name] = tfile.Get(tree_name)
            if verbose:
                print 'read tree', ttree, 'from file', file_name

        if friend_func:
            file_name = friend_func(file_name)
            friend_tree = self.readTree(file_name, tree_name, verbose)
            ttree.AddFriend(friend_tree)

        gROOT.cd()

        return ttree

    def Blind(self, minx, maxx, blindStack):
        self.blindminx = minx
        self.blindmaxx = maxx
        if self.stack and blindStack:
            self.stack.Blind(minx, maxx)
        if self.nostack:
            for hist in self.nostack:
                if hist.style.drawAsData:
                    hist.Blind(minx, maxx)

    def AddHistogram(self, name, histo, layer=0, legendLine=None, stack=True):
        '''Add a ROOT histogram, with a given name.

        Histograms will be drawn by increasing layer.'''
        tmp = Histogram(name, histo, layer, legendLine, stack=stack)
        self.histos.append(tmp)
        self.histosDict[name] = tmp
        return tmp

    def Group(self,
              groupName,
              namesToGroup,
              layer=None,
              style=None,
              silent=False):
        '''Group all histos with names in namesToGroup into a single
        histo with name groupName. All histogram properties are taken
        from the first histogram in namesToGroup.
        See UnGroup as well
        '''
        groupHist = None
        realNames = []
        actualNamesInGroup = []
        for name in namesToGroup:
            hist = self.histosDict.get(name, None)
            if hist is None:
                if not silent:
                    print 'warning, no histo with name', name
                continue
            if groupHist is None:
                groupHist = hist.Clone(groupName)
                self.histos.append(groupHist)
                self.histosDict[groupName] = groupHist
            else:
                groupHist.Add(hist)
            actualNamesInGroup.append(name)
            realNames.append(hist.realName)
            hist.on = False
        if groupHist:
            self.groups[groupName] = actualNamesInGroup
            groupHist.realName = ','.join(realNames)
            if style is not None:
                groupHist.SetStyle(style)
            self._ApplyPrefs()

    def UnGroup(self, groupName):
        '''Ungroup groupName, recover the histograms in the group'''
        group = self.groups.get(groupName, None)
        if group is None:
            print groupName, 'is not a group in this plot.'
            return
        for name in group:
            self.histosDict[name].on = True
        self.histosDict[groupName].on = False

    def Replace(self, name, pyhist):
        '''Not very elegant... should have a clone function in Histogram...'''
        oldh = self.histosDict.get(name, None)
        if oldh is None:
            print 'histogram', name, 'does not exist, cannot replace it.'
            return

        pythist = copy.deepcopy(pyhist)
        pythist.layer = oldh.layer
        pythist.stack = oldh.stack
        pythist.name = oldh.name
        pythist.legendLine = oldh.legendLine
        pythist.SetStyle(oldh.style)
        pythist.weighted.SetFillStyle(oldh.weighted.GetFillStyle())

        index = self.histos.index(oldh)
        self.histosDict[name] = pythist
        self.histos[index] = pythist

    def _SortedHistograms(self, reverse=False):
        '''Returns the histogram dictionary, sorted by increasing layer,
        excluding histograms which are not "on".

        This function is used in all the Draw functions.'''
        byLayer = sorted(self.histos, key=attrgetter('layer'))
        byLayerOn = [hist for hist in byLayer if (hist.on is True)]
        if reverse:
            byLayerOn.reverse()
        return byLayerOn

    def Hist(self, histName):
        '''Returns a histogram.

        Print the DataMCPlot object to see which histograms are available.'''
        return self.histosDict[histName]

    def DrawNormalized(self, opt=''):
        '''All histograms are drawn as PDFs, even the stacked ones'''
        same = ''
        for hist in self._SortedHistograms():
            hist.obj.DrawNormalized(same + opt)
            if same == '':
                same = 'same'
        self.DrawLegend()
        if TPad.Pad():
            TPad.Pad().Update()
        # self.lastDraw = 'DrawNormalized'
        # self.lastDrawArgs = [ opt ]

    def Draw(self, opt=''):
        '''All histograms are drawn.'''
        same = ''
        self.supportHist = None
        for hist in self._SortedHistograms():
            if self.supportHist is None:
                self.supportHist = hist
            hist.Draw(same + opt)
            if same == '':
                same = 'same'
#        set_trace()
        yaxis = self.supportHist.GetYaxis()
        yaxis.SetRangeUser(0.01, 1.5 * ymax(self._SortedHistograms()))
        self.DrawLegend()
        if TPad.Pad():
            TPad.Pad().Update()
        # self.lastDraw = 'Draw'
        # self.lastDrawArgs = [ opt ]

    def CreateLegend(self, ratio=False, print_norm=False):
        if self.legend is None:
            self.legend = TLegend(*self.legendBorders)
            self.legend.SetFillColor(0)
            self.legend.SetFillStyle(0)
            self.legend.SetLineColor(0)
            self.legend.SetLineWidth(1)
            self.legend.SetNColumns(5)  # number of comps / 2 (or 3) + 1
            self.legend.SetEntrySeparation(0.2)
            self.legend.SetColumnSeparation(0.2)
            self.legend.SetBorderSize(0)
            self.legend.SetMargin(0.25)
        else:
            self.legend.Clear()
        hists = self._SortedHistograms(reverse=True)
        if ratio:
            hists = hists[:-1]  # removing the last histo.
        for index, hist in enumerate(hists):
            if print_norm:
                if not hist.legendLine:
                    hist.legendLine = hist.name
                hist.legendLine += ' ({norm:.1f})'.format(norm=hist.Yield())
            hist.AddEntry(self.legend)

    def DrawLegend(self, ratio=False, print_norm=False):
        '''Draw the legend.'''
        if self.legendOn:
            self.CreateLegend(ratio=ratio, print_norm=print_norm)
            self.legend.Draw('same')

    def DrawRatio(self, opt=''):
        '''Draw ratios : h_i / h_0.

        h_0 is the histogram with the smallest layer, and h_i, i>0 are the other histograms.
        if the DataMCPlot object contains N histograms, N-1 ratio plots will be drawn.
        To take another histogram as the denominator, change the layer of this histogram by doing:
        dataMCPlot.Hist("histName").layer = -99 '''
        same = ''
        denom = None
        self.ratios = []
        for hist in self._SortedHistograms():
            if denom == None:
                denom = hist
                continue
            ratio = copy.deepcopy(hist)
            ratio.obj.Divide(denom.obj)
            ratio.obj.Draw(same)
            self.ratios.append(ratio)
            if same == '':
                same = 'same'
        self.DrawLegend(ratio=True)
        if TPad.Pad():
            TPad.Pad().Update()
        # self.lastDraw = 'DrawRatio'
        # self.lastDrawArgs = [ opt ]

    def DrawDataOverMCMinus1(self, ymin=-0.5, ymax=0.5):
        stackedHists = []
        dataHist = None
        for hist in self._SortedHistograms():
            if hist.stack is False:
                dataHist = hist
                continue
            stackedHists.append(hist)
        self._BuildStack(stackedHists, ytitle='Data/MC')
        mcHist = self.BGHist()
        if dataHist == None:
            dataHist = mcHist  # this was added to avoid crashes for SR plots (without data)
        self.dataOverMCHist = copy.deepcopy(dataHist)
        # self.dataOverMCHist.Add(mcHist, -1)
        self.dataOverMCHist.Divide(mcHist)
        self.dataOverMCHist.Draw()
        yaxis = self.dataOverMCHist.GetYaxis()
        yaxis.SetRangeUser(ymin + 1., ymax + 1.)
        yaxis.SetTitle('Data/MC')
        yaxis.SetNdivisions(5)
        fraclines = 0.2
        if ymax <= 0.2 or ymin >= -0.2:
            fraclines = 0.1
        self.DrawRatioLines(self.dataOverMCHist, fraclines, 1.)
        if TPad.Pad():
            TPad.Pad().Update()

    def DrawRatioStack(self,
                       opt='',
                       xmin=None,
                       xmax=None,
                       ymin=None,
                       ymax=None):
        '''Draw ratios.

        The stack is considered as a single histogram.'''
        denom = None
        # import pdb; pdb.set_trace()
        histForRatios = []
        denom = None
        for hist in self._SortedHistograms():
            if hist.stack is False:
                # if several unstacked histograms, the highest layer is used
                denom = hist
                continue
            histForRatios.append(hist)
        self._BuildStack(histForRatios, ytitle='MC/Data')
        self.stack.Divide(denom.obj)
        if self.blindminx and self.blindmaxx:
            self.stack.Blind(self.blindminx, self.blindmaxx)
        self.stack.Draw(opt, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
        self.ratios = []
        for hist in self.nostack:
            if hist is denom:
                continue
            ratio = copy.deepcopy(hist)
            ratio.obj.Divide(denom.obj)
            ratio.obj.Draw('same')
            self.ratios.append(ratio)
        self.DrawLegend(ratio=True)
        self.DrawRatioLines(denom, 0.2, 1)
        if TPad.Pad():
            TPad.Pad().Update()

    def DrawNormalizedRatioStack(self,
                                 opt='',
                                 xmin=None,
                                 xmax=None,
                                 ymin=None,
                                 ymax=None):
        '''Draw ratios.

        The stack is considered as a single histogram.
        All histograms are normalized before computing the ratio'''
        denom = None
        histForRatios = []
        for hist in self._SortedHistograms():
            # taking the first histogram (lowest layer)
            # as the denominator histogram.
            if denom == None:
                denom = copy.deepcopy(hist)
                continue
            # other histograms will be divided by the denominator
            histForRatios.append(hist)
        self._BuildStack(histForRatios, ytitle='MC p.d.f. / Data p.d.f.')
        self.stack.Normalize()
        denom.Normalize()
        self.stack.Divide(denom.weighted)
        self.stack.Draw(opt, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
        self.ratios = []
        for hist in self.nostack:
            # print 'nostack ', hist
            ratio = copy.deepcopy(hist)
            ratio.Normalize()
            ratio.obj.Divide(denom.weighted)
            ratio.obj.Draw('same')
            self.ratios.append(ratio)
        self.DrawLegend(ratio=True)
        self.DrawRatioLines(denom, 0.2, 1)
        if TPad.Pad():
            TPad.Pad().Update()
        # self.lastDraw = 'DrawNormalizedRatioStack'
        # self.lastDrawArgs = [ opt ]

    def DrawRatioLines(self, hist, frac=0.2, y0=1.):
        '''Draw a line at y = 1, at 1+frac, and at 1-frac.

        hist is used to get the x axis range.'''
        xmin = hist.obj.GetXaxis().GetXmin()
        xmax = hist.obj.GetXaxis().GetXmax()
        line = TLine()
        line.DrawLine(xmin, y0, xmax, y0)
        line.SetLineStyle(2)
        line.DrawLine(xmin, y0 + frac, xmax, y0 + frac)
        line.DrawLine(xmin, y0 - frac, xmax, y0 - frac)

    def GetStack(self):
        '''Returns stack; builds stack if not there yet'''
        if not self.stack:
            self._BuildStack(self._SortedHistograms(), ytitle='Events')
        return self.stack

    def BGHist(self):
        return self.GetStack().totalHist

    def SignalHists(self):
        return [h for h in self.nostack if not h.style.drawAsData]

    def DrawStack(self,
                  opt='',
                  xmin=None,
                  xmax=None,
                  ymin=None,
                  ymax=None,
                  print_norm=False,
                  scale_signal=''):
        '''Draw all histograms, some of them in a stack.

        if Histogram.stack is True, the histogram is put in the stack.
        scale_signal: mc_int -> scale to stack integral'''
        self._BuildStack(self._SortedHistograms(), ytitle='Events')
        same = 'same'
        if len(self.nostack) == 0:
            same = ''
        self.supportHist = None
        for hist in self.nostack:
            if hist.style.drawAsData:
                hist.Draw('SAME' if self.supportHist else '')
            else:
                if scale_signal == 'mc_int':
                    hist.Scale(hist.Yield(weighted=True) / self.stack.integral)
                hist.Draw('SAME HIST' if self.supportHist else 'HIST')
            if not self.supportHist:
                self.supportHist = hist
        self.stack.Draw(opt + same, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
        if self.supportHist is None:
            self.supportHist = self.BGHist()
        if not self.axisWasSet:
            mxsup = self.supportHist.weighted.GetBinContent(
                self.supportHist.weighted.GetMaximumBin())
            mxstack = self.BGHist().weighted.GetBinContent(
                self.BGHist().weighted.GetMaximumBin())
            mx = max(mxsup, mxstack)
            if ymin is None:
                ymin = 0.01
            if ymax is None:
                ymax = mx * 2
                centrality = self.supportHist.weighted.GetRMS() / (
                    self.supportHist.weighted.GetXaxis().GetXmax() -
                    self.supportHist.weighted.GetXaxis().GetXmin())
                if centrality > 0.15:
                    ymax = mx * 2.2

            self.supportHist.GetYaxis().SetRangeUser(ymin, ymax)
            self.axisWasSet = True
        for hist in self.nostack:
            if self.blindminx and hist.style.drawAsData:
                hist.Blind(self.blindminx, self.blindmaxx)
            if hist.style.drawAsData:
                hist.Draw('SAME')
            else:
                hist.Draw('SAME HIST')

        if self.supportHist.weighted.GetMaximumBin(
        ) < self.supportHist.weighted.GetNbinsX() / 2:
            #            self.legendBorders = 0.62, 0.46, 0.88, 0.89
            self.legendBorders = 0.20, 0.78, 0.80, 0.88
            #            self.legendPos = 'right'
            self.legendPos = 'top'

        self.DrawLegend(print_norm=print_norm)
        if TPad.Pad():
            TPad.Pad().Update()


#        set_trace()

    def DrawNormalizedStack(self,
                            opt='',
                            xmin=None,
                            xmax=None,
                            ymin=0.001,
                            ymax=None):
        '''Draw all histograms, some of them in a stack.

        if Histogram.stack is True, the histogram is put in the stack.
        all histograms out of the stack, and the stack itself, are shown as PDFs.'''
        self._BuildStack(self._SortedHistograms(), ytitle='p.d.f.')
        self.stack.DrawNormalized(opt,
                                  xmin=xmin,
                                  xmax=xmax,
                                  ymin=ymin,
                                  ymax=ymax)
        for hist in self.nostack:
            hist.obj.DrawNormalized('same')
        self.DrawLegend()
        if TPad.Pad():
            TPad.Pad().Update()
        # self.lastDraw = 'DrawNormalizedStack'
        # self.lastDrawArgs = [ opt ]

    def Rebin(self, factor):
        '''Rebin, and redraw.'''
        # the dispatching technique is not too pretty,
        # but keeping a self.lastDraw function initialized to one of the Draw functions
        # when calling it creates a problem in deepcopy.
        for hist in self.histos:
            hist.Rebin(factor)
        self.axisWasSet = False

    def NormalizeToBinWidth(self):
        '''Normalize each Histograms bin to the bin width.'''
        for hist in self.histos:
            hist.NormalizeToBinWidth()

    def WriteDataCard(self,
                      filename=None,
                      verbose=True,
                      mode='RECREATE',
                      dir=None,
                      postfix=''):
        '''Export current plot to datacard'''
        if not filename:
            filename = self.name + '.root'

        outf = TFile(filename, mode)
        if dir and outf.Get(dir):
            print 'Directory', dir, 'already present in output file'
            if any(
                    outf.Get(dir + '/' + hist.name + postfix)
                    for hist in self._SortedHistograms()):
                print 'Recreating file because histograms already present'
                outf = TFile(filename, 'RECREATE')
        if dir:
            outf_dir = outf.Get(dir)
            if not outf_dir:
                outf_dir = outf.mkdir(dir)
            outf_dir.cd()

        for hist in self._SortedHistograms():
            'Writing', hist, 'as', hist.name
            hist.weighted.Write(hist.name + postfix)
        outf.Write()

    def _BuildStack(self, hists, ytitle=None):
        '''build a stack from a list of Histograms.

        The histograms for which Histogram.stack is False are put in self.nostack'''
        self.stack = None
        self.stack = Stack(self.name + '_stack', ytitle=ytitle)
        self.nostack = []
        for hist in hists:
            if hist.stack:
                self.stack.Add(hist)
            else:
                self.nostack.append(hist)

    def _GetHistPref(self, name):
        '''Return the preference dictionary for a given component'''
        thePref = None
        for prefpat, pref in self.histPref.iteritems():
            if fnmatch.fnmatch(name, prefpat):
                if thePref is not None:
                    print 'several matching preferences for', name
                thePref = pref
        if thePref is None:
            print 'cannot find preference for hist', name
            thePref = {'style': Style(), 'layer': 999, 'legend': name}
        return thePref

    def _ApplyPrefs(self):
        for hist in self.histos:
            pref = self._GetHistPref(hist.name)
            hist.layer = pref['layer']
            hist.SetStyle(pref['style'])
            hist.legendLine = pref['legend']

    def __str__(self):
        if self.stack is None:
            self._BuildStack(self._SortedHistograms(), ytitle='Events')
        tmp = [' '.join(['DataMCPlot: ', self.name])]
        tmp.append('Histograms:')
        for hist in self._SortedHistograms(reverse=True):
            tmp.append(' '.join(['\t', str(hist)]))
        tmp.append(
            'Stack yield = {integ:7.1f}'.format(integ=self.stack.integral))
        return '\n'.join(tmp)
Exemplo n.º 4
0
    def drawlegend(self, position=None, **kwargs):
        """Create and draw legend.
    Legend position can be controlled in several ways
      drawlegend(position)
      drawlegend(position=position)
    where position is a string which can contain the horizontal position, e.g.
      'left', 'center', 'right', 'L', 'C', 'R', 'x=0.3', ...
    where 'x' is the position (between 0 an 1) of the left side in the frame.
    The position string can also contain the vertical position as e.g.
      'top', 'middle', 'bottom', 'T', 'M', 'B', 'y=0.3', ...
    where 'y' is the position (between 0 an 1) of the top side in the frame.
    Instead of the strings, the exact legend coordinates can be controlled with
    the keywords x1, x2, y1 and y2, or, x1, y1, width and height:
      drawlegend(x1=0.2,width=0.4)
      drawlegend(x1=0.2,width=0.4,y1=0.9,height=0.4)
      drawlegend(x1=0.2,x2=0.6,y1=0.9,y2=0.4)
    These floats are normalized to the axis frame, ignoring the canvas margins:
    x=0 is the left, x=1 is the right, y=0 is the bottom and y=1 is the top side.
    Values less than 0, or larger than 1, will put the legend outside the frame.
    """
        #if not ratio:
        #  tsize *= 0.80
        #  signaltsize *= 0.80
        verbosity = LOG.getverbosity(self, kwargs)
        hists = self.hists
        errstyle = 'lep' if gStyle.GetErrorX() else 'ep'
        entries = kwargs.get('entries', [])
        bands = kwargs.get('band', [self.errband])  # error bands
        bands = ensurelist(bands, nonzero=True)
        bandentries = kwargs.get('bandentries', [])
        title = kwargs.get('header', None)
        title = kwargs.get('title', title)  # legend header/title
        latex = kwargs.get('latex',
                           True)  # automatically format strings as LaTeX
        style = kwargs.get('style', None)
        style0 = kwargs.get('style0', None)  # style of first histogram
        errstyle = kwargs.get('errstyle', errstyle)  # style for an error point
        styles = kwargs.get('styles', [])
        position = kwargs.get('pos', position)  # legend position
        position = kwargs.get('position', position) or self.position
        option = kwargs.get('option', '')
        border = kwargs.get('border', False)
        transparent = kwargs.get('transparent', True)
        x1_user = kwargs.get('x1', None)  # legend left side
        x2_user = kwargs.get('x2', None)  # legend right side
        y1_user = kwargs.get('y1', None)  # legend top side
        y2_user = kwargs.get('y2', None)  # legend bottom side
        width = kwargs.get('width', -1)  # legend width
        height = kwargs.get('height', -1)  # legend height
        tsize = kwargs.get('tsize', _lsize)  # text size
        twidth = kwargs.get('twidth',
                            None) or 1  # scalefactor for legend width
        theight = kwargs.get('theight',
                             None) or 1  # scalefactor for legend height
        texts = kwargs.get('text', [])  # extra text below legend
        ncols = kwargs.get('ncols',
                           self.ncols) or 1  # number of legend columns
        colsep = kwargs.get('colsep',
                            0.06)  # seperation between legend columns
        bold = kwargs.get('bold', True)  # bold legend header
        panel = kwargs.get('panel', 1)  # panel (top=1, bottom=2)
        texts = ensurelist(texts, nonzero=True)
        entries = ensurelist(entries, nonzero=False)
        bandentries = ensurelist(bandentries, nonzero=True)
        headerfont = 62 if bold else 42

        # CHECK
        LOG.insist(self.canvas, "Canvas does not exist!")
        self.canvas.cd(panel)
        scale = 485. / min(gPad.GetWh() * gPad.GetHNDC(),
                           gPad.GetWw() * gPad.GetWNDC())
        tsize *= scale  # text size

        # ENTRIES
        #if len(bandentries)==len(bands) and len(entries)>len(hists):
        #  for band, bandtitle in zip(band,bandentries):
        #    entries.insert(hists.index(band),bandtitle)
        while len(entries) < len(hists):
            entries.append(hists[len(entries)].GetTitle())
        while len(bandentries) < len(bands):
            bandentries.append(bands[len(bandentries)].GetTitle())
        hists = hists + bands
        entries = entries + bandentries
        if latex:
            title = maketitle(title)
            entries = [maketitle(e) for e in entries]
            texts = [maketitle(t) for t in texts]
        maxlen = estimatelen([title] + entries + texts)

        # STYLES
        if style0:
            styles[0] = style0
        while len(styles) < len(hists):
            hist = hists[len(styles)]
            if hist in bands:
                styles.append('f')
            elif style != None:
                styles.append(style)
            elif hasattr(hist, 'GetFillStyle') and hist.GetFillStyle() > 0:
                styles.append('f')
            elif 'E0' in hist.GetOption() or 'E1' in hist.GetOption():
                styles.append(errstyle)
            else:
                styles.append('lp')

        # NUMBER of LINES
        nlines = sum([1 + e.count('\n') for e in entries])
        #else:       nlines += 0.80
        if texts: nlines += sum([1 + t.count('\n') for t in texts])
        if ncols > 1: nlines /= float(ncols)
        if title: nlines += 1 + title.count('\n')

        # DIMENSIONS
        if width < 0:
            width = twidth * max(0.22, min(0.60, 0.036 + 0.016 * maxlen))
        if height < 0: height = theight * 1.34 * tsize * nlines
        if ncols > 1: width *= ncols / (1 - colsep)
        x2 = 0.90
        x1 = x2 - width
        y1 = 0.92
        y2 = y1 - height

        # POSITION
        if position == None:
            position = ""
        position = position.replace('left', 'L').replace(
            'center', 'C').replace('right', 'R').replace(  #.lower()
                'top', 'T').replace('middle', 'M').replace('bottom', 'B')
        if not any(c in position for c in 'TMBy'):  # set default vertical
            position += 'T'
        if not any(c in position for c in 'LCRx'):  # set default horizontal
            position += 'RR' if ncols > 1 else 'R'  # if title else 'L'

        if 'C' in position:
            if 'R' in position: center = 0.57
            elif 'L' in position: center = 0.43
            else: center = 0.50
            x1 = center - width / 2
            x2 = center + width / 2
        elif 'LL' in position:
            x1 = 0.03
            x2 = x1 + width
        elif 'L' in position:
            x1 = 0.08
            x2 = x1 + width
        elif 'RR' in position:
            x2 = 0.97
            x1 = x2 - width
        elif 'R' in position:
            x2 = 0.92
            x1 = x2 - width
        elif 'x=' in position:
            x1 = float(re.findall(r"x=(\d\.\d+)", position)[0])
            x2 = x1 + width
        if 'M' in position:
            if 'T' in position: middle = 0.57
            elif 'B' in position: middle = 0.43
            else: middle = 0.50
            y1 = middle - height / 2
            y2 = middle + height / 2
        elif 'TT' in position:
            y2 = 0.97
            y1 = y2 - height
        elif 'T' in position:
            y2 = 0.92
            y1 = y2 - height
        elif 'BB' in position:
            y1 = 0.03
            y2 = y1 + height
        elif 'B' in position:
            y1 = 0.08
            y2 = y1 + height
        elif 'y=' in position:
            y2 = float(re.findall(r"y=(\d\.\d+)", position)[0])
            y1 = y2 - height
        if x1_user != None:
            x1 = x1_user
            x2 = x1 + width if x2_user == None else x2_user
        if y1_user != None:
            y1 = y1_user
            y2 = y1 - height if y2_user == None else y2_user
        L, R = gPad.GetLeftMargin(), gPad.GetRightMargin()
        T, B = gPad.GetTopMargin(), gPad.GetBottomMargin()
        X1, X2 = L + (1 - L - R) * x1, L + (
            1 - L - R) * x2  # convert frame to canvas coordinates
        Y1, Y2 = B + (1 - T - B) * y1, B + (
            1 - T - B) * y2  # convert frame to canvas coordinates
        legend = TLegend(X1, Y1, X2, Y2)
        LOG.verb(
            "Plot.drawlegend: position=%r, height=%.3f, width=%.3f, (x1,y1,x2,y2)=(%.2f,%.2f,%.2f,%.2f), (X1,Y1,X2,Y2)=(%.2f,%.2f,%.2f,%.2f)"
            % (position, height, width, x1, y1, x2, y2, X1, Y1, X2, Y2),
            verbosity, 1)

        # MARGIN
        if ncols >= 2:
            margin = 0.090 / width
        else:
            margin = 0.044 / width
        legend.SetMargin(margin)

        # STYLE
        if transparent: legend.SetFillStyle(0)  # 0 = transparent
        else: legend.SetFillColor(0)
        legend.SetBorderSize(border)
        legend.SetTextSize(tsize)
        legend.SetTextFont(headerfont)  # bold for title
        if ncols > 1:
            legend.SetNColumns(ncols)
            legend.SetColumnSeparation(colsep)

        # HEADER
        if title:
            legend.SetHeader(title)
        legend.SetTextFont(42)  # no bold for entries

        # ENTRIES
        if hists:
            for hist1, entry1, style1 in columnize(zip(hists, entries, styles),
                                                   ncols):
                for entry in entry1.split('\n'):
                    legend.AddEntry(hist1, entry, style1)
                    hist1, style1 = 0, ''
        for line in texts:
            legend.AddEntry(0, line, '')

        if verbosity >= 2:
            print ">>> Plot.drawlegend: title=%r, texts=%s, latex=%s" % (
                title, texts, latex)
            print ">>> Plot.drawlegend: hists=%s" % (hists)
            print ">>> Plot.drawlegend: entries=%s" % (entries)
            print ">>> Plot.drawlegend: styles=%s" % (styles)
            print ">>> Plot.drawlegend: nlines=%s, len(hists)=%s, len(texts)=%s, ncols=%s, margin=%s" % (
                nlines, len(hists), len(texts), ncols, margin)

        legend.Draw(option)
        self.legends.append(legend)
        return legend
Exemplo n.º 5
0
def plotPostFitValues(channel,var,DM,year,*parameters,**kwargs):
    """Draw post-fit values for parameter using MultiDimFit and FitDiagnostics output."""
    if DM=='DM0' and 'm_2' in var: return
    print green("\n>>> plotPostFitValues %s, %s"%(DM, var))
    if len(parameters)==1 and isinstance(parameters[0],list): parameters = parameters[0]
    
    parameters  = [p.replace('$CAT',DM).replace('$CHANNEL',channel) for p in list(parameters)]
    title       = kwargs.get('title',     ""    )
    name        = kwargs.get('name',      ""    )
    indir       = kwargs.get('indir',     "output_%d"%year  )
    outdir      = kwargs.get('outdir',    "postfit_%d"%year )
    tag         = kwargs.get('tag',       ""    )
    plotlabel   = kwargs.get('plotlabel', ""    )
    compareFD   = kwargs.get('compareFD', False ) and N==1
    era         = "%d-13TeV"%year
    isBBB       = any("_bin_" in p for p in parameters)
    filename    = '%s/higgsCombine.%s_%s-%s%s-%s.MultiDimFit.mH90.root'%(indir,channel,var,DM,tag,era)
    filenamesFD = '%s/fitDiagnostics.%s_%s-%s%s-%s_TES*p*.root'%(indir,channel,var,DM,tag,era)
    ensureDirectory(outdir)
    if not name:
      name = formatParameter(parameters[0]).replace('_'+DM,'')
    if len(parameters)>1:
      name = "comparison_%s"%(name) #re.sub(r"bin_\d+","bin",name)
    canvasname = "%s/postfit-%s_%s_%s%s%s"%(outdir,name,var,DM,tag,plotlabel)
    print '>>>   file "%s"'%(filename)
    
    
    graphs      = [ ]
    graphsFD    = [ ]
    tvals       = [ ]
    pvals       = [ -2.2, +2.2 ]
    tes         = measureTES(filename)
    for parameter in parameters[:]:
      graph = getTGraphOfParameter(filename,'tes',parameter,xvals=tvals,yvals=pvals)
      if graph:
        graphs.append(graph)
      else:
        parameters.remove(parameter)
      if compareFD:
        graphFD = getTGraphOfParameter_FD(filenamesFD,parameter,xvals=tvals,yvals=pvals)
        if graphFD: graphsFD.append(graphFD)
    if len(parameters)!=len(graphs):
      warning("plotPostFitValues: len(parameters) = %d != %d = len(graphs)"%(len(parameters),len(graphs)))
      exit(1)
    N           = len(parameters)
    compareFD   = compareFD and len(graphsFD)>0
    parameters  = [formatParameter(p).replace('_'+DM,'') for p in parameters]
    graphsleg   = columnize(graphs,3)     if N>6 else columnize(graphs,2)     if N>3 else graphs # reordered for two columns
    paramsleg   = columnize(parameters,3) if N>6 else columnize(parameters,2) if N>3 else parameters # reordered for two columns
    
    xtitle  = 'tau energy scale'
    ytitle  = "%s post-fit value"%(parameters[0] if N==1 else "MultiDimFit")
    xmin, xmax = min(tvals), max(tvals)
    ymin, ymax = min(pvals), max(pvals)
    colors  = [ kBlack, kBlue, kRed, kGreen, kMagenta, kOrange, kTeal, kAzure+2, kYellow-3 ]
    
    canvas = TCanvas("canvas","canvas",100,100,800,650)
    canvas.SetFillColor(0)
    canvas.SetBorderMode(0)
    canvas.SetFrameFillStyle(0)
    canvas.SetFrameBorderMode(0)
    canvas.SetTopMargin(  0.07 ); canvas.SetBottomMargin( 0.13 )
    canvas.SetLeftMargin( 0.11 ); canvas.SetRightMargin(  0.05 )
    canvas.SetTickx(0)
    canvas.SetTicky(0)
    canvas.SetGrid()
    canvas.cd()
    
    maxmargin = 0.18*abs(ymax-ymin)
    minmargin = 0.20*abs(ymax-ymin) #(0.20 if N<5 else 0.10*ceil(N/2))
    ymin   = 0.0 if ymin>=0 else ymin-minmargin
    ymax   = ymax + minmargin
    
    textsize   = 0.045 if N==1 else 0.036
    lineheight = 0.055 if N==1 else 0.045
    x1, width  = 0.42, 0.25
    y1, height = 0.15, lineheight*(ceil(N/3.) if N>6 else ceil(N/2.) if N>3 else N)
    if title:     height += lineheight
    if compareFD: height += lineheight
    if   N>6:
      if isBBB: width = 0.62; x1 = 0.25
      else:     width = 0.70; x1 = 0.20
    elif N>3:   width = 0.52; x1 = 0.36
    legend = TLegend(x1,y1,x1+width,y1+height)
    legend.SetTextSize(textsize)
    legend.SetBorderSize(0)
    legend.SetFillStyle(0)
    legend.SetFillColor(0)
    if title:
      legend.SetTextFont(62)
      legend.SetHeader(title) 
    legend.SetTextFont(42)
    if N>6:
      legend.SetNColumns(3)
      legend.SetColumnSeparation(0.02)
    elif N>3:
      legend.SetNColumns(2)
      legend.SetColumnSeparation(0.03)
    
    frame = canvas.DrawFrame(xmin,ymin,xmax,ymax)
    frame.GetYaxis().SetTitleSize(0.060)
    frame.GetXaxis().SetTitleSize(0.060)
    frame.GetXaxis().SetLabelSize(0.050)
    frame.GetYaxis().SetLabelSize(0.050)
    frame.GetXaxis().SetLabelOffset(0.01)
    frame.GetYaxis().SetLabelOffset(0.01)
    frame.GetXaxis().SetTitleOffset(0.98)
    frame.GetYaxis().SetTitleOffset(0.84)
    frame.GetXaxis().SetNdivisions(508)
    frame.GetYaxis().SetTitle(ytitle)
    frame.GetXaxis().SetTitle(xtitle)
    
    for i, (parameter,graph) in enumerate(zip(parameters,graphs)):
      graph.SetLineColor(colors[i%len(colors)])
      graph.SetLineWidth(2)
      graph.SetLineStyle(1)
      graph.Draw('LSAME')
    
    for parameter,graph in zip(paramsleg,graphsleg):
      if N==1:
        legend.AddEntry(graph, "MultiDimFit", 'l')
      else:
        legend.AddEntry(graph, parameter, 'l')
    
    line = TLine(tes, ymin, tes, ymax)
    #line.SetLineWidth(1)
    line.SetLineStyle(7)
    line.Draw('SAME')
    latex = TLatex()
    latex.SetTextSize(0.045)
    latex.SetTextAlign(13)
    latex.SetTextFont(42)
    latex.DrawLatex(tes+0.02*(xmax-xmin),ymax-0.04*(ymax-ymin),"tes = %.3f"%tes)
    
    if compareFD:
      graphFD.SetLineColor(kBlue)
      graphFD.SetLineWidth(2)
      graphFD.SetLineStyle(2)
      graphFD.Draw('LSAME')
      legend.AddEntry(graphFD, "FitDiagnostics", 'l')
    
    legend.Draw()
    
    CMS_lumi.relPosX = 0.12
    CMS_lumi.CMS_lumi(canvas,13,0)
    gPad.SetTicks(1,1)
    gPad.Modified()
    frame.Draw('SAMEAXIS')
    
    canvas.SaveAs(canvasname+".png")
    if args.pdf: canvas.SaveAs(canvasname+".pdf")
    canvas.Close()