I am using a THStack for its nice features of automatically scaling the y axis limit to accomodate all graphs. However, this fails when the two graphs are on very different scales and (I think) if some bin reaches below zero.

Here is an example demonstrating the problem in PyROOT, but it also appears with equivalent code in C++:

Your two histograms minima are 0. So the rendering in log scale is not possible as log(0) is undefined. Therefore, to nevertheless plot something, ROOT takes a percentage of the maximum as lower limit to render the stack. A way to control this would be to define a minimum for the stack. A small positive value like 0.1 …

{
auto *stack = new THStack();
auto *h1 = new TH1D("h1", "h1", 100, -10, 10);
h1->FillRandom("gaus", 100000);
stack->Add(h1);
auto *h2 = new TH1D("h2", "h2", 100, -10, 10);
h2->FillRandom("gaus", 100);
h2->SetLineColor(kRed);
stack->Add(h2);
stack->SetMinimum(0.1);
auto *c = new TCanvas();
stack->Draw("nostack ep");
c->SetLogy();
printf("%g\n",stack->GetHistogram()->GetMinimum());
}

Yes, I also realized this was the problem, but was hoping to find an automatic solution, because if I have to specify it every time, then the whole advantage of the THStack is lost.

Maybe I’ll write a small function that looks for the smallest value, excluding empty bins…

This small (Python) function seems to do the job. I think behavior similar to this is more what you expect, because having some bins empty might happen quite often, especially if you pot multiple histograms on top of each other:

import ROOT
class THStack(ROOT.THStack):
def GetNonZeroMinimum(self):
min_val = self.GetMaximum()
for hist in self:
for val in hist:
if val != 0:
min_val = min(min_val, val)
return min_val
stack = THStack()
h1 = ROOT.TH1D("h1", "h1", 100, -10, 10)
h1.FillRandom("gaus", 100000)
stack.Add(h1)
h2 = ROOT.TH1D("h2", "h2", 100, -10, 10)
h2.FillRandom("gaus", 100)
h2.SetLineColor(ROOT.kRed)
stack.Add(h2)
c = ROOT.TCanvas()
stack.Draw("nostackep")
c.SetLogy()
stack.SetMinimum(stack.GetNonZeroMinimum() / 2.)
c.Update()
print stack.GetMinimum(), stack.GetMaximum()