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
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
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
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