I’m trying to create multiple pads in a Jupyter notebook with the ROOT kernel. The relevant code is
selections=["4j1b", "4j2b"]
c = TCanvas("name", "Background")
c.Divide(2,1)
for i, sel in enumerate(selections):
c.cd(i)
histack = THStack("hs_%s" % sel, "Background")
for color, hist in enumerate(histograms["background"][sel].values()):
hist.SetFillColor(color)
histack.Add(hist)
histack.Draw("HIST")
histack.GetXaxis().SetRangeUser(0, 0.2e3)
c.BuildLegend(0.8, 0.9, 0.9, 0.5)
c.Draw()
What I get is just a single pad, as if cd() or Divide() are not working.
I also noticed that there are no examples with TCanvas.Divide in the python tutorials folder in ROOT’s source tree.
Do I need to use TPads explicitly in Python?
@couet The last one is TH1::Divide, though, not TPad::Divide.
What you are doing wrong is that on the first iteration of for i, sel in enumerate(selections):, you call c.cd(0), because enumerate starts at 0. This will return the canvas, instead of the first pad. After drawing there, this probably messes up everything else.
Instead, just do for i, sel in enumerate(selections, 1):. This way it starts counting at 1, instead.
@Graipher You’re absolutely right, that fixed the pads issue.
However, the histogram stack is only shown in the last pad. The other pads are empty. I tried the same code with a TH1, not a THStack and in that case it worked fine… Is there some flag that should be set in the THStack object?
I think there are still a few things wrong/that can be improved:
You should not need to call c.Draw every iteration
More importantly, you overwrite histack every iteration, so the first stack will be deleted, which results in an empty pad (since you made sure to overwrite it). Use `histack.DrawClone(“HIST”) instead, this will create a copy and draw that copy, so it should not matter that you overwrite the original stack.
Your legend building is called on the canvas, when you probably want it on the pad. For this to work, just save the current pad in a variable.
You could use the new option "PFC", which should pick the next color from the current palette for each histogram.
The drawing of the stack is only needed after the stack is complete.
Thus, I would use something like this:
for i, sel in enumerate(selections, 1):
pad = c.cd(i)
histack = THStack("hs_%s" % sel, "Background")
for hist in histograms["background"][sel].values():
histack.Add(hist)
histack.DrawClone("HIST PFC")
histack.GetXaxis().SetRangeUser(0, 0.2e3)
pad.BuildLegend(0.8, 0.9, 0.9, 0.5)
But I’m creating a new histogram with each iteration. I understand that root does some behind-the-scenes magic where it holds named objects in the memory and since the THStack objects have different names between iterations, they should not override each other…
I changed the code to this:
selections=["4j1b", "4j2b"]
c = TCanvas("name", "Background", 1000, 300)
c.Divide(2,1)
for i, sel in enumerate(selections, 1):
pad = c.cd(i)
histack = THStack("hs_%s" % sel, "Background")
for hist in histograms["background"][sel].values():
histack.Add(hist)
hs=histack.DrawClone("HIST PFC")
hs.GetXaxis().SetRangeUser(0, 0.2e3)
pad.BuildLegend(0.8, 0.9, 0.9, 0.5)
c.Draw()
While you are right about ROOT (I did not see that the names were different…), Python’s garbage collection will delete objects without any reference pointing to them (and since you overwrite histack, this will probably happen).
Maybe try explicitly keeping the stacks in memory, something like this:
stacks = []
for i, sel in enumerate(selections, 1):
pad = c.cd(i)
histack = THStack("hs_%s" % sel, "Background")
for hist in histograms["background"][sel].values():
histack.Add(hist)
histack.Draw("HIST PFC")
histack.GetXaxis().SetRangeUser(0, 0.2e3)
pad.BuildLegend(0.8, 0.9, 0.9, 0.5)
stacks.append(histack)