Unable to Create Multiple Pads with TCanvas.Divide in Python

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?

There is at list 3 python tutorials using Divide:

$ grep Divide tutorials/*/*.py
tutorials/graphs/zones.py:c1.Divide(2,2,0,0)
tutorials/math/mathcoreStatFunc.py:c1.Divide(2,2)

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

1 Like

@couet Thanks, I found them online. My version of ROOT didn’t have them for some reason:

grep Divide tutorials/*/*.py 
tutorials/pyroot/ratioplot.py:    h3.Divide(h2)

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

  1. You should not need to call c.Draw every iteration
  2. 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.
  3. 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.
  4. You could use the new option "PFC", which should pick the next color from the current palette for each histogram.
  5. 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)

Yes right … I was to quick. I erased it. But the 2 first are what @davereikher was looking for.

ROOT is continuously evolving. I did the grep on the master version. The reference guide online is build from that version.

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

but it’s still giving me the same result:

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)

This seems to work for me.

Awesome, it works. Thanks!

Glad I could help :slight_smile:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.