Since I learned that there is a
TPad::BuildLegend I was disappointed that it does not automatically try to find a good place.
Here is some Python code that does just that:
import ROOT import itertools.chain # Some convenience function to easily iterate over the parts of the collections # Needed if importing this script from another script in case TMultiGraphs are used ROOT.SetMemoryPolicy(ROOT.kMemoryStrict) # Start a bit right of the Yaxis and above the Xaxis to not overlap with the ticks start, stop = 0.13, 0.89 x_width, y_width = 0.3, 0.2 PLACES = [(start, stop - y_width, start + x_width, stop), # top left opt (start, start, start + x_width, start + y_width), # bottom left opt (stop - x_width, stop - y_width, stop, stop), # top right opt (stop - x_width, start, stop, start + y_width), # bottom right opt (stop - x_width, 0.5 - y_width / 2, stop, 0.5 + y_width / 2), # right (start, 0.5 - y_width / 2, start + x_width, 0.5 + y_width / 2)] # left def transform_to_user(canvas, x1, y1, x2, y2): """ Transforms from Pad coordinates to User coordinates. This can probably be replaced by using the built-in conversion commands. """ xstart = canvas.GetX1() xlength = canvas.GetX2() - xstart xlow = xlength * x1 + xstart xhigh = xlength * x2 + xstart if canvas.GetLogx(): xlow = 10**xlow xhigh = 10**xhigh ystart = canvas.GetY1() ylength = canvas.GetY2() - ystart ylow = ylength * y1 + ystart yhigh = ylength * y2 + ystart if canvas.GetLogy(): ylow = 10**ylow yhigh = 10**yhigh return xlow, ylow, xhigh, yhigh def overlap_h(hist, x1, y1, x2, y2): xlow = hist.FindFixBin(x1) xhigh = hist.FindFixBin(x2) for i in range(xlow, xhigh + 1): val = hist.GetBinContent(i) # Values if y1 <= val <= y2: return True # Errors if val + hist.GetBinErrorUp(i) > y1 and val - hist.GetBinErrorLow(i) < y2: # print "Overlap with histo", hist.GetName(), "at bin", i return True return False def overlap_rect(rect1, rect2): """Do the two rectangles overlap?""" if rect1 > rect2 or rect1 < rect2: return False if rect1 > rect2 or rect1 < rect2: return False return True def overlap_g(graph, x1, y1, x2, y2): x_values = list(graph.GetX()) y_values = list(graph.GetY()) x_err = list(graph.GetEX()) or  * len(x_values) y_err = list(graph.GetEY()) or  * len(y_values) for x, ex, y, ey in zip(x_values, x_err, y_values, y_err): # Could maybe be less conservative if overlap_rect((x1, y1, x2, y2), (x - ex, y - ey, x + ex, y + ey)): # print "Overlap with graph", graph.GetName(), "at point", (x, y) return True return False def place_legend(canvas, x1=None, y1=None, x2=None, y2=None, header="", option="LP"): # If position is specified, use that if all(x is not None for x in (x1, x2, y1, y2)): return canvas.BuildLegend(x1, y1, x2, y2, header, option) # Make sure all objects are correctly registered canvas.Update() # Build a list of objects to check for overlaps objects =  for x in canvas.GetListOfPrimitives(): if isinstance(x, ROOT.TH1) or isinstance(x, ROOT.TGraph): objects.append(x) elif isinstance(x, ROOT.THStack) or isinstance(x, ROOT.TMultiGraph): objects.extend(x) for place in PLACES: place_user = canvas.PadtoU(*place) # Make sure there are no overlaps if any(obj.Overlap(*place_user) for obj in objects): continue return canvas.BuildLegend(place, place, place, place, header, option) # As a fallback, use the default values, taken from TCanvas::BuildLegend return canvas.BuildLegend(0.5, 0.67, 0.88, 0.88, header, option) # Monkey patch ROOT objects to make it all work ROOT.THStack.__iter__ = lambda self: iter(self.GetHists()) ROOT.TMultiGraph.__iter__ = lambda self: iter(self.GetListOfGraphs()) ROOT.TH1.Overlap = overlap_h ROOT.TGraph.Overlap = overlap_g ROOT.TPad.PadtoU = transform_to_user ROOT.TPad.PlaceLegend = place_legend
Obviously this should be implemented in the actual C++ source, then the monkey-patching at the end would no longer be necessary.
After saving this to a script, e.g.
legend.py you can just do:
from legend import * c = ROOT.TCanvas() h = ROOT.TH1D("h", "h", 100, 0, 100) h.Draw() c.PlaceLegend()
Note that I had some more places to try originally, but left them out for brevity. One can even try to calculate an optimum size for it (using the number of entries and the longest title).