Пример #1
0
    def get1DHistoFromDraw(self, variableString, binning, selectionString = None, weightString = None, binningIsExplicit = False, addOverFlowBin = None, isProfile = False):
        ''' Get TH1D/TProfile1D from draw command using selectionString, weight. If binningIsExplicit is true, 
            the binning argument (a list) is translated into variable bin widths. 
            addOverFlowBin can be 'upper', 'lower', 'both' and will add 
            the corresponding overflow bin to the last bin of a 1D histogram'''

        selectionString_ = self.combineWithSampleSelection( selectionString )
        weightString_    = self.combineWithSampleWeight( weightString )

        tmp=str(uuid.uuid4())
        if binningIsExplicit:
            binningArgs = (len(binning)-1, array('d', binning))
        else:
            binningArgs = binning

        cls = ROOT.TProfile if isProfile else ROOT.TH1D

        res = cls(tmp, tmp, *binningArgs)

        #weight = weightString if weightString else "1"

        self.chain.Draw(variableString+">>"+tmp, "("+weightString_+")*("+selectionString_+")", 'goff')
       
        Plot.addOverFlowBin1D( res, addOverFlowBin )
 
        return res
Пример #2
0
    def get1DHistoFromDraw(self, variableString, binning, selectionString = None, weightString = None, binningIsExplicit = False, addOverFlowBin = None, isProfile = False):
        ''' Get TH1D/TProfile1D from draw command using selectionString, weight. If binningIsExplicit is true, 
            the binning argument (a list) is translated into variable bin widths. 
            addOverFlowBin can be 'upper', 'lower', 'both' and will add 
            the corresponding overflow bin to the last bin of a 1D histogram'''

        selectionString_ = self.combineWithSampleSelection( selectionString )
        weightString_    = self.combineWithSampleWeight( weightString )

        tmp=str(uuid.uuid4())
        if binningIsExplicit:
            binningArgs = (len(binning)-1, array('d', binning))
        else:
            binningArgs = binning

        cls = ROOT.TProfile if isProfile else ROOT.TH1D

        res = cls(tmp, tmp, *binningArgs)

        #weight = weightString if weightString else "1"

        self.chain.Draw(variableString+">>"+tmp, "("+weightString_+")*("+selectionString_+")", 'goff')
       
        Plot.addOverFlowBin1D( res, addOverFlowBin )
 
        return res
Пример #3
0
    def get1DHistoFromDraw(self,
                           variableString,
                           binning,
                           selectionString=None,
                           weightString=None,
                           binningIsExplicit=False,
                           addOverFlowBin=None,
                           isProfile=False):
        ''' Get TH1D/TProfile1D from draw command using selectionString, weight. If binningIsExplicit is true, 
            the binning argument (a list) is translated into variable bin widths. 
            addOverFlowBin can be 'upper', 'lower', 'both' and will add 
            the corresponding overflow bin to the last bin of a 1D histogram.
            isProfile can be True (default) or the TProfile build option (e.g. a string 's' ), see
            https://root.cern.ch/doc/master/classTProfile.html#a1ff9340284c73ce8762ab6e7dc0e6725'''

        selectionString_ = self.combineWithSampleSelection(selectionString)
        weightString_ = self.combineWithSampleWeight(weightString)

        tmp = str(uuid.uuid4())
        if binningIsExplicit:
            binningArgs = (len(binning) - 1, array('d', binning))
        else:
            binningArgs = binning

        if isProfile:
            if type(isProfile) == type(""):
                res = ROOT.TProfile(tmp, tmp, *(binningArgs + (isProfile, )))
            else:
                res = ROOT.TProfile(tmp, tmp, *binningArgs)
        else:
            res = ROOT.TH1D(tmp, tmp, *binningArgs)

        #weight = weightString if weightString else "1"

        self.chain.Draw(variableString + ">>" + tmp,
                        "(" + weightString_ + ")*(" + selectionString_ + ")",
                        'goff')

        Plot.addOverFlowBin1D(res, addOverFlowBin)

        return res
Пример #4
0
def draw(plot, \
        yRange = "auto",
        extensions = ["pdf", "png", "root"],
        plot_directory = ".",
        logX = False, logY = True,
        ratio = None,
        scaling = {},
        sorting = False,
        legend = "auto",
        drawObjects = [],
        widths = {},
        canvasModifications = [],
        histModifications = [],
        copyIndexPHP = False,
        ):
    ''' yRange: 'auto' (default) or [low, high] where low/high can be 'auto'
        extensions: ["pdf", "png", "root"] (default)
        logX: True/False (default), logY: True(default)/False
        ratio: 'auto'(default) corresponds to {'histos':[(1,0)], 'logY':False, 'style':None, 'texY': 'Data / MC', 'yRange': (0.5, 1.5), 'drawObjects': []}
        scaling: {} (default). Scaling the i-th stack to the j-th is done by scaling = {i:j} with i,j integers
        sorting: True/False(default) Whether or not to sort the components of a stack wrt Integral
        legend: "auto" (default) or [x_low, y_low, x_high, y_high] or None. ([<legend_coordinates>], n) divides the legend into n columns. 
        drawObjects = [] Additional ROOT objects that are called by .Draw() 
        widths = {} (default) to update the widths. Values are {'y_width':500, 'x_width':500, 'y_ratio_width':200}
        canvasModifications = [] could be used to pass on lambdas to modify the canvas. histModifications similar for histos.
        copyIndexPHP: whether or not to copy index.php to the plot directory
    '''
    # FIXME -> Introduces CMSSW dependence
    ROOT.gROOT.LoadMacro("$CMSSW_BASE/src/RootTools/plot/scripts/tdrstyle.C")
    ROOT.setTDRStyle()
    defaultRatioStyle = {
        'histos': [(1, 0)],
        'logY': False,
        'style': None,
        'texY': 'Data / MC',
        'yRange': (0.5, 1.5),
        'drawObjects': []
    }

    if ratio is not None and not type(ratio) == type({}):
        raise ValueError(
            "'ratio' must be dict (default: {}). General form is '%r'." %
            defaultRatioStyle)

    # default_widths
    default_widths = {'y_width': 500, 'x_width': 500, 'y_ratio_width': 200}
    if ratio is not None: default_widths['x_width'] = 520
    # Updating with width arguments
    default_widths.update(widths)

    if not isinstance(scaling, dict):
        raise ValueError(
            "'scaling' must be of the form {0:1, 2:3} which normalizes stack[0] to stack[1] etc. Got '%r'"
            % scaling)

    # Make sure ratio dict has all the keys by updating the default
    if ratio is not None:
        defaultRatioStyle.update(ratio)
        ratio = defaultRatioStyle

    # Clone (including any attributes) and add up histos in stack
    histos = map(lambda l: map(lambda h: helpers.clone(h), l), plot.histos)

    # Add overflow bins for 1D plots
    if isinstance(plot, Plot.Plot):
        if plot.addOverFlowBin is not None:
            for s in histos:
                for p in s:
                    Plot.addOverFlowBin1D(p, plot.addOverFlowBin)

    for i, l in enumerate(histos):

        # recall the sample for use in the legend
        for j, h in enumerate(l):
            h.sample = plot.stack[i][j] if plot.stack is not None else None

            # Exectute style function on histo, therefore histo styler has precendence over stack styler
            if hasattr(h, "style"):
                h.style(h)

        # sort
        if sorting:
            l.sort(key=lambda h: -h.Integral())

        # Add up stacks
        for j, h in enumerate(l):
            for k in range(j + 1, len(l)):
                l[j].Add(l[k])
    # Scaling
    for source, target in scaling.iteritems():
        if not (isinstance(source, int) and isinstance(target, int)):
            raise ValueError(
                "Scaling should be {0:1, 1:2, ...}. Expected ints, got %r %r" %
                (source, target))

        source_yield = histos[source][0].Integral()

        if source_yield == 0:
            logger.warning("Requested to scale empty Stack? Do nothing.")
            continue

        factor = histos[target][0].Integral() / source_yield
        for h in histos[source]:
            h.Scale(factor)

    # Make canvas and if there is a ratio plot adjust the size of the pads

    if ratio is not None:
        default_widths['y_width'] += default_widths['y_ratio_width']
        scaleFacRatioPad = default_widths['y_width'] / float(
            default_widths['y_ratio_width'])
        y_border = default_widths['y_ratio_width'] / float(
            default_widths['y_width'])

    # delete canvas if it exists
    if hasattr("ROOT", "c1"):
        del ROOT.c1

    c1 = ROOT.TCanvas(
        str(uuid.uuid4()).replace('-', '_'), "drawHistos", 200, 10,
        default_widths['x_width'], default_widths['y_width'])

    if ratio is not None:
        c1.Divide(1, 2, 0, 0)
        topPad = c1.cd(1)
        topPad.SetBottomMargin(0)
        topPad.SetLeftMargin(0.15)
        topPad.SetTopMargin(0.07)
        topPad.SetRightMargin(0.05)
        topPad.SetPad(topPad.GetX1(), y_border, topPad.GetX2(), topPad.GetY2())
        bottomPad = c1.cd(2)
        bottomPad.SetTopMargin(0)
        bottomPad.SetRightMargin(0.05)
        bottomPad.SetLeftMargin(0.15)
        bottomPad.SetBottomMargin(scaleFacRatioPad * 0.13)
        bottomPad.SetPad(bottomPad.GetX1(), bottomPad.GetY1(),
                         bottomPad.GetX2(), y_border)
    else:
        topPad = c1
        topPad.SetBottomMargin(0.13)
        topPad.SetLeftMargin(0.15)
        topPad.SetTopMargin(0.07)
        topPad.SetRightMargin(0.05)

    for modification in canvasModifications:
        modification(c1)

    topPad.cd()

    # Range on y axis: Start with default
    if not yRange == "auto" and not (type(yRange) == type(
        ()) and len(yRange) == 2):
        raise ValueError(
            "'yRange' must bei either 'auto' or (yMin, yMax) where yMin/Max can be 'auto'. Got: %r"
            % yRange)

    max_ = max(l[0].GetMaximum() for l in histos)
    min_ = min(l[0].GetMinimum() for l in histos)

    # If legend is in the form (tuple, int) then the number of columns is provided
    legendColumns = 1
    if legend is not None and len(legend) == 2:
        legendColumns = legend[1]
        legend = legend[0]

    #Calculate legend coordinates in gPad coordinates
    if legend is not None:
        if legend == "auto":
            legendCoordinates = (0.50,
                                 0.93 - 0.05 * sum(map(len, plot.histos)),
                                 0.92, 0.93)
        else:
            legendCoordinates = legend

    if logY:
        yMax_ = 10**0.5 * max_
        yMin_ = 0.7
    else:
        yMax_ = 1.2 * max_
        yMin_ = 0 if min_ > 0 else 1.2 * min_
    if type(yRange) == type(()) and len(yRange) == 2:
        yMin_ = yRange[0] if not yRange[0] == "auto" else yMin_
        yMax_ = yRange[1] if not yRange[1] == "auto" else yMax_

    #Avoid overlap with the legend
    if (yRange == "auto" or yRange[1] == "auto") and (legend is not None):
        scaleFactor = 1
        # Get x-range and y
        legendMaskedArea = getLegendMaskedArea(legendCoordinates, topPad)
        for i, l in enumerate(histos):
            histo = histos[i][0]
            for i_bin in range(1, 1 + histo.GetNbinsX()):
                # low/high bin edge in the units of the x axis
                xLowerEdge_axis, xUpperEdge_axis = histo.GetBinLowEdge(
                    i_bin
                ), histo.GetBinLowEdge(i_bin) + histo.GetBinWidth(i_bin)
                # linear transformation to gPad system
                xLowerEdge = (xLowerEdge_axis - histo.GetXaxis().GetXmin()) / (
                    histo.GetXaxis().GetXmax() - histo.GetXaxis().GetXmin())
                xUpperEdge = (xUpperEdge_axis - histo.GetXaxis().GetXmin()) / (
                    histo.GetXaxis().GetXmax() - histo.GetXaxis().GetXmin())
                # maximum allowed fraction in given bin wrt to the legendMaskedArea: Either all (1) or legendMaskedArea['yLowerEdge']
                maxFraction = legendMaskedArea[
                    'yLowerEdge'] if xUpperEdge > legendMaskedArea[
                        'xLowerEdge'] and xLowerEdge < legendMaskedArea[
                            'xUpperEdge'] else 1
                # Save 20%
                maxFraction *= 0.8
                # Use: (y - yMin_) / (sf*yMax_ - yMin_) = maxFraction (and y->log(y) in log case).
                # Compute the maximum required scale factor s.
                y = histo.GetBinContent(i_bin)
                try:
                    if logY:
                        new_sf = yMin_ / yMax_ * (y / yMin_)**(
                            1. / maxFraction) if y > 0 else 1
                    else:
                        new_sf = 1. / yMax_ * (yMin_ +
                                               (y - yMin_) / maxFraction)
                    scaleFactor = new_sf if new_sf > scaleFactor else scaleFactor
                except ZeroDivisionError:
                    pass
                # print i_bin, xLowerEdge, xUpperEdge, 'yMin', yMin_, 'yMax', yMax_, 'y', y, 'maxFraction', maxFraction, scaleFactor, new_sf

        # Apply scale factor to avoid legend
        yMax_ = scaleFactor * yMax_

    # Draw the histos
    same = ""
    stuff = []
    for i, l in enumerate(histos):
        for j, h in enumerate(l):
            # Get draw option. Neither Clone nor copy preserves attributes of histo
            drawOption = histos[i][j].drawOption if hasattr(
                histos[i][j], "drawOption") else "hist"
            topPad.SetLogy(logY)
            topPad.SetLogx(logX)
            h.GetYaxis().SetRangeUser(yMin_, yMax_)
            h.GetXaxis().SetTitle(plot.texX)
            h.GetYaxis().SetTitle(plot.texY)
            # precision 3 fonts. see https://root.cern.ch/root/htmldoc//TAttText.html#T5
            h.GetXaxis().SetTitleFont(43)
            h.GetYaxis().SetTitleFont(43)
            h.GetXaxis().SetLabelFont(43)
            h.GetYaxis().SetLabelFont(43)
            h.GetXaxis().SetTitleSize(24)
            h.GetYaxis().SetTitleSize(24)
            h.GetXaxis().SetLabelSize(20)
            h.GetYaxis().SetLabelSize(20)

            if ratio is None:
                h.GetYaxis().SetTitleOffset(1.3)
            else:
                h.GetYaxis().SetTitleOffset(1.6)

            for modification in histModifications:
                modification(h)
            if drawOption == "e1" or drawOption == "e0":
                stuff.append(h)  #dataHist = h
            h.Draw(drawOption + same)
            same = "same"

    if not drawOption == 'AH':
        topPad.RedrawAxis()
    # Make the legend
    if legend is not None:
        legend_ = ROOT.TLegend(*legendCoordinates)
        legend_.SetNColumns(legendColumns)
        legend_.SetFillStyle(0)
        #        legend_.SetFillColor(0)
        legend_.SetShadowColor(ROOT.kWhite)
        legend_.SetBorderSize(0)
        #        legend_.SetBorderSize(1)
        for l in histos:
            for h in l:
                if hasattr(h.sample, "notInLegend"):
                    if h.sample.notInLegend: continue
                if hasattr(h, "texName"):
                    legend_text = h.texName
                elif hasattr(h, "legendText"):
                    legend_text = h.legendText
                elif h.sample is not None:
                    legend_text = h.sample.texName if hasattr(
                        h.sample, "texName") else h.sample.name
                else:
                    continue  #legend_text = "No title"
                if hasattr(h, "legendOption"):
                    legend_option = h.legendOption
                    legend_.AddEntry(h, legend_text, legend_option)
                else:
                    legend_.AddEntry(h, legend_text)
        legend_.Draw()

    for o in drawObjects:
        if o:
            if type(o) in [
                    ROOT.TF1, ROOT.TGraph, ROOT.TEfficiency, ROOT.TH1F,
                    ROOT.TH1D
            ]:
                if hasattr(o, 'drawOption'):
                    o.Draw('same ' + o.drawOption)
                else:
                    o.Draw('same')
            else:
                o.Draw()
        else:
            logger.debug("drawObjects has something I can't Draw(): %r", o)

    # re-draw the main objects (ratio histograms) after the objects, otherwise they might be hidden
    for h_main in stuff:
        drawOption = h_main.drawOption if hasattr(h_main,
                                                  "drawOption") else "hist"
        h_main.Draw(drawOption + same)

    # Make a ratio plot
    if ratio is not None:
        bottomPad.cd()
        # Make all the ratio histograms
        same = ''
        stuff = []
        for i_num, i_den in ratio['histos']:
            num = histos[i_num][0]
            den = histos[i_den][0]
            h_ratio = helpers.clone(num)
            stuff.append(h_ratio)
            # For a ratio of profiles, use projection (preserve attributes)
            if isinstance(h_ratio, ROOT.TProfile):
                attrs = h_ratio.__dict__
                h_ratio = h_ratio.ProjectionX()
                h_ratio.__dict__.update(attrs)
                h_ratio.Divide(den.ProjectionX())
            else:
                h_ratio.Divide(den)

            #if ratio['style'] is not None: ratio['style'](h_ratio)

            h_ratio.GetXaxis().SetTitle(plot.texX)
            h_ratio.GetYaxis().SetTitle(ratio['texY'])

            h_ratio.GetXaxis().SetTitleFont(43)
            h_ratio.GetYaxis().SetTitleFont(43)
            h_ratio.GetXaxis().SetLabelFont(43)
            h_ratio.GetYaxis().SetLabelFont(43)
            h_ratio.GetXaxis().SetTitleSize(24)
            h_ratio.GetYaxis().SetTitleSize(24)
            h_ratio.GetXaxis().SetLabelSize(20)
            h_ratio.GetYaxis().SetLabelSize(20)

            h_ratio.GetXaxis().SetTitleOffset(3.2)
            h_ratio.GetYaxis().SetTitleOffset(1.6)

            h_ratio.GetXaxis().SetTickLength(0.03 * 3)
            h_ratio.GetYaxis().SetTickLength(0.03 * 2)

            h_ratio.GetYaxis().SetRangeUser(*ratio['yRange'])
            h_ratio.GetYaxis().SetNdivisions(505)

            if ratio.has_key('histModifications'):
                for modification in ratio['histModifications']:
                    modification(h_ratio)
            drawOption = h_ratio.drawOption if hasattr(
                h_ratio, "drawOption") else "hist"
            if drawOption == "e1" or drawOption == "e0":  # hacking to show error bars within panel when central value is off scale
                graph = ROOT.TGraphAsymmErrors(
                    h_ratio)  # cloning in order to get layout
                graph.Set(0)
                for bin in range(1,
                                 h_ratio.GetNbinsX() +
                                 1):  # do not show error bars on hist
                    h_ratio.SetBinError(bin, 0.0001)
                    center = h_ratio.GetBinCenter(bin)
                    val = h_ratio.GetBinContent(bin)
                    errUp = num.GetBinErrorUp(bin) / den.GetBinContent(
                        bin) if den.GetBinContent(bin) > 0 else 0
                    errDown = num.GetBinErrorLow(bin) / den.GetBinContent(
                        bin) if den.GetBinContent(bin) > 0 else 0
                    graph.SetPoint(bin, center, val)
                    graph.SetPointError(bin, 0, 0, errDown, errUp)
                h_ratio.Draw("e0" + same)
                graph.Draw("P0 same")
                graph.drawOption = "P0"
                stuff.append(graph)
            else:
                h_ratio.Draw(drawOption + same)
            same = 'same'

        bottomPad.SetLogx(logX)
        bottomPad.SetLogy(ratio['logY'])

        line = ROOT.TPolyLine(2)
        line.SetPoint(0, h_ratio.GetXaxis().GetXmin(), 1.)
        line.SetPoint(1, h_ratio.GetXaxis().GetXmax(), 1.)
        line.SetLineWidth(1)
        line.Draw()

        for o in ratio['drawObjects']:
            if o:
                if hasattr(o, 'drawOption'):
                    o.Draw(o.drawOption)
                else:
                    o.Draw()
            else:
                logger.debug(
                    "ratio['drawObjects'] has something I can't Draw(): %r", o)
        # re-draw the main objects (ratio histograms) after the objects, otherwise they might be hidden
        for h_ratio in stuff:
            drawOption = h_ratio.drawOption if hasattr(
                h_ratio, "drawOption") else "hist"
            h_ratio.Draw(drawOption + same)

    if not os.path.exists(plot_directory):
        try:
            os.makedirs(plot_directory)
        except OSError:  # Resolve rare race condition
            pass

    if copyIndexPHP: plot_helpers.copyIndexPHP(plot_directory)

    c1.cd()

    for extension in extensions:
        filename = plot.name  # if plot.name is not None else plot.variable.name #FIXME -> the replacement with variable.name should already be in the Plot constructors
        ofile = os.path.join(plot_directory, "%s.%s" % (filename, extension))
        c1.Print(ofile)
    del c1
Пример #5
0
def draw(plot, \
        yRange = "auto", 
        extensions = ["pdf", "png", "root"], 
        plot_directory = ".", 
        logX = False, logY = True, 
        ratio = None, 
        scaling = {}, 
        sorting = False, 
        legend = "auto", 
        drawObjects = [],
        widths = {},
        canvasModifications = [],
        histModifications = [],
        copyIndexPHP = False,
        ):
    ''' yRange: 'auto' (default) or [low, high] where low/high can be 'auto'
        extensions: ["pdf", "png", "root"] (default)
        logX: True/False (default), logY: True(default)/False
        ratio: 'auto'(default) corresponds to {'histos':[(1,0)], 'logY':False, 'style':None, 'texY': 'Data / MC', 'yRange': (0.5, 1.5), 'drawObjects': []}
        scaling: {} (default). Scaling the i-th stack to the j-th is done by scaling = {i:j} with i,j integers
        sorting: True/False(default) Whether or not to sort the components of a stack wrt Integral
        legend: "auto" (default) or [x_low, y_low, x_high, y_high] or None. ([<legend_coordinates>], n) divides the legend into n columns. 
        drawObjects = [] Additional ROOT objects that are called by .Draw() 
        widths = {} (default) to update the widths. Values are {'y_width':500, 'x_width':500, 'y_ratio_width':200}
        canvasModifications = [] could be used to pass on lambdas to modify the canvas. histModifications similar for histos.
        copyIndexPHP: whether or not to copy index.php to the plot directory
    '''
    # FIXME -> Introduces CMSSW dependence
    ROOT.gROOT.LoadMacro("$CMSSW_BASE/src/RootTools/plot/scripts/tdrstyle.C")
    ROOT.setTDRStyle()
    defaultRatioStyle = {'histos':[(1,0)], 'logY':False, 'style':None, 'texY': 'Data / MC', 'yRange': (0.5, 1.5), 'drawObjects':[]}

    if ratio is not None and not type(ratio)==type({}):
        raise ValueError( "'ratio' must be dict (default: {}). General form is '%r'." % defaultRatioStyle)

    # default_widths    
    default_widths = {'y_width':500, 'x_width':500, 'y_ratio_width':200}
    if ratio is not None: default_widths['x_width'] = 520
    # Updating with width arguments 
    default_widths.update(widths)

    if not isinstance(scaling, dict):
        raise ValueError( "'scaling' must be of the form {0:1, 2:3} which normalizes stack[0] to stack[1] etc. Got '%r'" % scaling )
            
    # Make sure ratio dict has all the keys by updating the default
    if ratio is not None:
        defaultRatioStyle.update(ratio)
        ratio = defaultRatioStyle

    # Clone (including any attributes) and add up histos in stack
    histos = map(lambda l:map(lambda h:helpers.clone(h), l), plot.histos)

    # Add overflow bins for 1D plots
    if isinstance(plot, Plot.Plot):
	if plot.addOverFlowBin is not None:
	    for s in histos:
		for p in s:
		    Plot.addOverFlowBin1D( p, plot.addOverFlowBin )

    for i, l in enumerate(histos):

        # recall the sample for use in the legend
        for j, h in enumerate(l):
            h.sample = plot.stack[i][j] if plot.stack is not None else None

            # Exectute style function on histo, therefore histo styler has precendence over stack styler
            if hasattr(h, "style"):
                h.style(h)

        # sort 
        if sorting:
            l.sort(key=lambda h: -h.Integral())

        # Add up stacks
        for j, h in enumerate(l):
            for k in range(j+1, len(l) ):
                l[j].Add(l[k])
    # Scaling
    for source, target in scaling.iteritems():
        if not ( isinstance(source, int) and isinstance(target, int) ):
            raise ValueError( "Scaling should be {0:1, 1:2, ...}. Expected ints, got %r %r"%( source, target ) ) 

        source_yield = histos[source][0].Integral()

        if source_yield == 0:
            logger.warning( "Requested to scale empty Stack? Do nothing." )
            continue

        factor = histos[target][0].Integral()/source_yield
        for h in histos[source]:
            h.Scale( factor )

    # Make canvas and if there is a ratio plot adjust the size of the pads

    if ratio is not None:
        default_widths['y_width'] += default_widths['y_ratio_width']
        scaleFacRatioPad = default_widths['y_width']/float( default_widths['y_ratio_width'] )
        y_border = default_widths['y_ratio_width']/float( default_widths['y_width'] )

    # delete canvas if it exists
    if hasattr("ROOT","c1"): 
        del ROOT.c1 

    c1 = ROOT.TCanvas(str(uuid.uuid4()).replace('-','_'), "drawHistos",200,10, default_widths['x_width'], default_widths['y_width'])

    if ratio is not None:
        c1.Divide(1,2,0,0)
        topPad = c1.cd(1)
        topPad.SetBottomMargin(0)
        topPad.SetLeftMargin(0.15)
        topPad.SetTopMargin(0.07)
        topPad.SetRightMargin(0.05)
        topPad.SetPad(topPad.GetX1(), y_border, topPad.GetX2(), topPad.GetY2())
        bottomPad = c1.cd(2)
        bottomPad.SetTopMargin(0)
        bottomPad.SetRightMargin(0.05)
        bottomPad.SetLeftMargin(0.15)
        bottomPad.SetBottomMargin(scaleFacRatioPad*0.13)
        bottomPad.SetPad(bottomPad.GetX1(), bottomPad.GetY1(), bottomPad.GetX2(), y_border)
    else:
        topPad = c1
        topPad.SetBottomMargin(0.13)
        topPad.SetLeftMargin(0.15)
        topPad.SetTopMargin(0.07)
        topPad.SetRightMargin(0.05)

    for modification in canvasModifications: modification(c1)

    topPad.cd()

    # Range on y axis: Start with default
    if not yRange=="auto" and not (type(yRange)==type(()) and len(yRange)==2):
        raise ValueError( "'yRange' must bei either 'auto' or (yMin, yMax) where yMin/Max can be 'auto'. Got: %r"%yRange )

    max_ = max( l[0].GetMaximum() for l in histos )
    min_ = min( l[0].GetMinimum() for l in histos )

    # If legend is in the form (tuple, int) then the number of columns is provided
    legendColumns = 1
    if legend is not None and len(legend) == 2:
       legendColumns = legend[1]
       legend        = legend[0]

    #Calculate legend coordinates in gPad coordinates
    if legend is not None:
        if legend=="auto":
            legendCoordinates = (0.50,0.93-0.05*sum(map(len, plot.histos)),0.92,0.93)
        else:
            legendCoordinates = legend 

    if logY:
        yMax_ = 10**0.5*max_
        yMin_ = 0.7
    else:
        yMax_ = 1.2*max_
        yMin_ = 0 if min_>0 else 1.2*min_
    if type(yRange)==type(()) and len(yRange)==2:
        yMin_ = yRange[0] if not yRange[0]=="auto" else yMin_
        yMax_ = yRange[1] if not yRange[1]=="auto" else yMax_

    #Avoid overlap with the legend
    if (yRange=="auto" or yRange[1]=="auto") and (legend is not None):
        scaleFactor = 1
        # Get x-range and y
        legendMaskedArea = getLegendMaskedArea(legendCoordinates, topPad)
        for i, l in enumerate(histos):
            histo = histos[i][0]
            for i_bin in range(1, 1 + histo.GetNbinsX()):
                # low/high bin edge in the units of the x axis
                xLowerEdge_axis, xUpperEdge_axis = histo.GetBinLowEdge(i_bin), histo.GetBinLowEdge(i_bin)+histo.GetBinWidth(i_bin) 
                # linear transformation to gPad system
                xLowerEdge  = (xLowerEdge_axis  -  histo.GetXaxis().GetXmin())/(histo.GetXaxis().GetXmax() - histo.GetXaxis().GetXmin())
                xUpperEdge  = (xUpperEdge_axis -  histo.GetXaxis().GetXmin())/(histo.GetXaxis().GetXmax() - histo.GetXaxis().GetXmin())
                # maximum allowed fraction in given bin wrt to the legendMaskedArea: Either all (1) or legendMaskedArea['yLowerEdge']
                maxFraction = legendMaskedArea['yLowerEdge'] if xUpperEdge>legendMaskedArea['xLowerEdge'] and xLowerEdge<legendMaskedArea['xUpperEdge'] else 1
                # Save 20%
                maxFraction*=0.8
                # Use: (y - yMin_) / (sf*yMax_ - yMin_) = maxFraction (and y->log(y) in log case). 
                # Compute the maximum required scale factor s. 
                y = histo.GetBinContent(i_bin)
                try:
                    if logY:
                        new_sf = yMin_/yMax_*(y/yMin_)**(1./maxFraction) if y>0 else 1 
                    else:
                        new_sf = 1./yMax_*(yMin_ + (y-yMin_)/maxFraction ) 
                    scaleFactor = new_sf if new_sf>scaleFactor else scaleFactor
                except ZeroDivisionError:
                    pass 
                # print i_bin, xLowerEdge, xUpperEdge, 'yMin', yMin_, 'yMax', yMax_, 'y', y, 'maxFraction', maxFraction, scaleFactor, new_sf

        # Apply scale factor to avoid legend
        yMax_ = scaleFactor*yMax_

    # Draw the histos
    same = ""
    for i, l in enumerate(histos):
        for j, h in enumerate(l):
            # Get draw option. Neither Clone nor copy preserves attributes of histo
            drawOption = histos[i][j].drawOption if hasattr(histos[i][j], "drawOption") else "hist"
            topPad.SetLogy(logY)
            topPad.SetLogx(logX)
            h.GetYaxis().SetRangeUser(yMin_, yMax_)
            h.GetXaxis().SetTitle(plot.texX)
            h.GetYaxis().SetTitle(plot.texY)
            # precision 3 fonts. see https://root.cern.ch/root/htmldoc//TAttText.html#T5
            h.GetXaxis().SetTitleFont(43)
            h.GetYaxis().SetTitleFont(43)
            h.GetXaxis().SetLabelFont(43)
            h.GetYaxis().SetLabelFont(43)
            h.GetXaxis().SetTitleSize(24)
            h.GetYaxis().SetTitleSize(24)
            h.GetXaxis().SetLabelSize(20)
            h.GetYaxis().SetLabelSize(20)

            if ratio is None:
                h.GetYaxis().SetTitleOffset( 1.3 )
            else:
                h.GetYaxis().SetTitleOffset( 1.6 )

            for modification in histModifications: modification(h)
            #if drawOption=="e1": dataHist = h
            h.Draw(drawOption+same)
            same = "same"

    topPad.RedrawAxis()
    # Make the legend
    if legend is not None:
        legend_ = ROOT.TLegend(*legendCoordinates)
        legend_.SetNColumns(legendColumns)
        legend_.SetFillStyle(0)
#        legend_.SetFillColor(0)
        legend_.SetShadowColor(ROOT.kWhite)
        legend_.SetBorderSize(0)
#        legend_.SetBorderSize(1)
        for l in histos:
            for h in l:
                if hasattr(h.sample, "notInLegend"):
                    if h.sample.notInLegend: continue
                if hasattr(h, "texName"): 
                    legend_text = h.texName
                elif hasattr(h, "legendText"): 
                    legend_text = h.legendText
                elif h.sample is not None:
                    legend_text = h.sample.texName if hasattr(h.sample, "texName") else h.sample.name
                else:
                    continue #legend_text = "No title"   
                legend_.AddEntry(h, legend_text)
        legend_.Draw()

    for o in drawObjects:
        if o:
            if type(o) in [ ROOT.TF1, ROOT.TGraph ]:
                o.Draw('same')
            else:
                o.Draw()
        else:
            logger.debug( "drawObjects has something I can't Draw(): %r", o)
    # Make a ratio plot
    if ratio is not None:
        bottomPad.cd()
        # Make all the ratio histograms
        same=''
        stuff=[]
        for i_num, i_den in ratio['histos']:
            num = histos[i_num][0]
            den = histos[i_den][0]
            h_ratio = helpers.clone( num )
            stuff.append(h_ratio)
            # For a ratio of profiles, use projection (preserve attributes)
            if isinstance( h_ratio, ROOT.TProfile ):
                attrs = h_ratio.__dict__
                h_ratio = h_ratio.ProjectionX()
                h_ratio.__dict__.update( attrs )
                h_ratio.Divide( den.ProjectionX() )
            else:
                h_ratio.Divide( den )

            #if ratio['style'] is not None: ratio['style'](h_ratio) 

            h_ratio.GetXaxis().SetTitle(plot.texX)
            h_ratio.GetYaxis().SetTitle(ratio['texY'])

            h_ratio.GetXaxis().SetTitleFont(43)
            h_ratio.GetYaxis().SetTitleFont(43)
            h_ratio.GetXaxis().SetLabelFont(43)
            h_ratio.GetYaxis().SetLabelFont(43)
            h_ratio.GetXaxis().SetTitleSize(24)
            h_ratio.GetYaxis().SetTitleSize(24)
            h_ratio.GetXaxis().SetLabelSize(20)
            h_ratio.GetYaxis().SetLabelSize(20)

            h_ratio.GetXaxis().SetTitleOffset( 3.2 )
            h_ratio.GetYaxis().SetTitleOffset( 1.6 )

            h_ratio.GetXaxis().SetTickLength( 0.03*3 )
            h_ratio.GetYaxis().SetTickLength( 0.03*2 )


            h_ratio.GetYaxis().SetRangeUser( *ratio['yRange'] )
            h_ratio.GetYaxis().SetNdivisions(505)

            drawOption = h_ratio.drawOption if hasattr(h_ratio, "drawOption") else "hist"
            if drawOption == "e1":                          # hacking to show error bars within panel when central value is off scale
              graph = ROOT.TGraphAsymmErrors(h_ratio)       # cloning in order to get layout
              graph.Set(0)
              for bin in range(1, h_ratio.GetNbinsX()+1):   # do not show error bars on hist
                h_ratio.SetBinError(bin, 0.0001)
                center  = h_ratio.GetBinCenter(bin)
                val     = h_ratio.GetBinContent(bin)
                errUp   = num.GetBinErrorUp(bin)/den.GetBinContent(bin) if val > 0 else 0
                errDown = num.GetBinErrorLow(bin)/den.GetBinContent(bin) if val > 0 else 0
                graph.SetPoint(bin, center, val)
                graph.SetPointError(bin, 0, 0, errDown, errUp)
              h_ratio.Draw("e0"+same)
              graph.Draw("P0 same")
              stuff.append( graph )
            else:
              h_ratio.Draw(drawOption+same)
            same = 'same'

        bottomPad.SetLogx(logX)
        bottomPad.SetLogy(ratio['logY'])

        line = ROOT.TPolyLine(2)
        line.SetPoint(0, h_ratio.GetXaxis().GetXmin(), 1.)
        line.SetPoint(1, h_ratio.GetXaxis().GetXmax(), 1.)
        line.SetLineWidth(1)
        line.Draw()

        for o in ratio['drawObjects']:
            if o:
                o.Draw()
            else:
                logger.debug( "ratio['drawObjects'] has something I can't Draw(): %r", o)

    if not os.path.exists(plot_directory):
        try:
            os.makedirs(plot_directory)
        except OSError: # Resolve rare race condition
            pass

    if copyIndexPHP: plot_helpers.copyIndexPHP( plot_directory )

    c1.cd()

    for extension in extensions:
        filename = plot.name# if plot.name is not None else plot.variable.name #FIXME -> the replacement with variable.name should already be in the Plot constructors
        ofile = os.path.join( plot_directory, "%s.%s"%(filename, extension) )
        c1.Print( ofile )
    del c1