Skip to content

Conversation

@JasMehta08
Copy link
Contributor

This Pull request:

Implement opt-in Reference Pad scaling for TAxis (Fixes #20484)

Changes or fixes:

This PR addresses the long-standing issue of inconsistent axis title/label sizing and offsetting between pads of different sizes (e.g., in ratio plots), as described in issue #20484.

The Problem:
When creating canvases with split pads (e.g., 70/30 split), the default behavior scales text sizes relative to the pad height. This results in tiny, unreadable text in the smaller auxiliary pad, requiring users to manually tune SetLabelSize and SetTitleOffset for every axis.

The Solution:
I have implemented an opt-in mechanism that allows an axis to use the dimensions of a "Reference Pad" for scaling calculations.

  • Added TAxis::SetRefPad(TVirtualPad *pad).
  • Implementation:
    • Added a variable fRefLength to TAxis to store the reference height (avoiding unsafe pointer storage).
    • Modified TGaxis::PaintAxis to check if fRefLength > 0.
    • If active, PaintAxis calculates a scaling factor (fRefLength / currentPadHeight) and temporarily applies it to fLabelSize, fTitleSize, fTickSize, fLabelOffset, and fTitleOffset using an AttributeRestorer (RAII) pattern.
    • This ensures proper scaling and prevents titles from overlapping with labels in small pads.

Design Choice:
I chose an opt-in approach (SetRefPad) rather than automatic scaling to preserve backward compatibility.

Note:
I also investigated applying this fix to TPaveStats and TLegend, but due to the complexity of their internal layout logic, those changes are not included in this PR. This PR focuses strictly on fixing the TAxis consistency, which was the core request of the issue.

Verification Code

The following macro demonstrates the fix on a standard ratio plot.
(Please refer to the attached image for the output)

void complete_test() {
   gStyle->SetOptStat(1111);
   gStyle->SetOptFit(0);
   
   auto c = new TCanvas("c", "Axis Scaling Fix - Realistic Ratio Plot", 1200, 600);
   c->Divide(2, 1);
   
   // Create standard histogram template with realistic data
   TH1D *hTemplate = new TH1D("hTemplate", 
      "Invariant Mass;Mass [GeV];Events / 2 GeV", 
      50, 50, 150);
   
   // Use a custom function to fill (Mean=91, Sigma=2.5 - Z peak style)
   TF1 *fRel = new TF1("fRel", "breitwigner", 50, 150);
   fRel->SetParameters(1000, 91.2, 2.5);
   hTemplate->FillRandom("fRel", 10000);
   
   hTemplate->SetLineColor(kBlack);
   hTemplate->SetMarkerStyle(20);
   hTemplate->SetMarkerSize(0.6);
   
   // Standard label sizes
   hTemplate->GetXaxis()->SetLabelSize(0.04);
   hTemplate->GetYaxis()->SetLabelSize(0.04);
   hTemplate->GetXaxis()->SetTitleSize(0.045);
   hTemplate->GetYaxis()->SetTitleSize(0.045);
   
   // Split ratio: Top 70%, Bottom 30%
   Double_t splitPoint = 0.30;
   
   // =============================================
   // LEFT SIDE: BEFORE (Standard Behavior)
   // =============================================
   c->cd(1);
   TPad *leftPad = (TPad*)gPad;
   leftPad->SetName("LeftPad");
   
   // Main Pad (70%)
   TPad *mainL = new TPad("mainL", "Main", 0.0, splitPoint, 1.0, 1.0);
   mainL->SetBottomMargin(0); // Joined pads
   mainL->SetLeftMargin(0.15);
   mainL->Draw();
   mainL->cd();
   
   TH1D *hMainL = (TH1D*)hTemplate->Clone("hMainL");
   hMainL->SetTitle("Original Behavior (Ratio Pad)");
   hMainL->Draw("E1");
   
   // Ratio Pad (30%)
   leftPad->cd();
   TPad *ratioL = new TPad("ratioL", "Ratio", 0.0, 0.0, 1.0, splitPoint);
   ratioL->SetTopMargin(0);
   ratioL->SetBottomMargin(0.35); // Large margin needed for X labels
   ratioL->SetLeftMargin(0.15);
   ratioL->SetGridy();
   ratioL->Draw();
   ratioL->cd();
   
   TH1D *hRatioL = (TH1D*)hTemplate->Clone("hRatioL");
   hRatioL->SetTitle("");
   hRatioL->GetYaxis()->SetTitle("Ratio");
   hRatioL->GetYaxis()->SetNdivisions(505);
   hRatioL->Draw("hist");
   
   // Add Label
   leftPad->cd();
   TLatex *lblL = new TLatex(0.5, 0.95, "Before: Labels too small in ratio pad");
   lblL->SetNDC();
   lblL->SetTextAlign(22);
   lblL->SetTextSize(0.04);
   lblL->Draw();

   // =============================================
   // RIGHT SIDE: AFTER (With SetRefPad)
   // =============================================
   c->cd(2);
   TPad *rightPad = (TPad*)gPad;
   rightPad->SetName("RightPad");
   
   // Main Pad (70%)
   TPad *mainR = new TPad("mainR", "Main", 0.0, splitPoint, 1.0, 1.0);
   mainR->SetBottomMargin(0);
   mainR->SetLeftMargin(0.15);
   mainR->Draw();
   mainR->cd();
   
   TH1D *hMainR = (TH1D*)hTemplate->Clone("hMainR");
   hMainR->SetTitle("With SetRefPad(main)");
   hMainR->Draw("E1");
   
   // Ratio Pad (30%)
   rightPad->cd();
   TPad *ratioR = new TPad("ratioR", "Ratio", 0.0, 0.0, 1.0, splitPoint);
   ratioR->SetTopMargin(0);
   ratioR->SetBottomMargin(0.35); 
   ratioR->SetLeftMargin(0.15);
   ratioR->SetGridy();
   ratioR->Draw();
   ratioR->cd();
   
   TH1D *hRatioR = (TH1D*)hTemplate->Clone("hRatioR");
   hRatioR->SetTitle("");
   hRatioR->GetYaxis()->SetTitle("Ratio");
   hRatioR->GetYaxis()->SetNdivisions(505);
   
   // THE FIX 
   // We tell the ratio plot axes: "Scale yourself as if you were in 'mainR'"
   hRatioR->GetXaxis()->SetRefPad(mainR);
   hRatioR->GetYaxis()->SetRefPad(mainR);
   
   hRatioR->Draw("hist");
   
   // Add Label
   rightPad->cd();
   TLatex *lblR = new TLatex(0.5, 0.95, "After: Consistent text size");
   lblR->SetNDC();
   lblR->SetTextAlign(22);
   lblR->SetTextSize(0.04);
   lblR->SetTextColor(kGreen+2);
   lblR->Draw();

   c->SaveAs("complete_test.png");
}
image

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

This PR fixes #20484

@github-actions
Copy link

Test Results

    20 files      20 suites   3d 16h 7m 55s ⏱️
 3 794 tests  3 789 ✅ 0 💤  5 ❌
72 877 runs  72 852 ✅ 0 💤 25 ❌

For more details on these failures, see this check.

Results for commit d1c338c.

void SetRefPad(TVirtualPad *pad);
Float_t GetRefLength() const { return fRefLength; }

ClassDefOverride(TAxis,11) //Axis class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to increment the class version here: the only data member you added to the class was fRefLength, which is not taking part in the I/O. This is also called a "transient" data member, and these are marked by the exclamation mark in the associated doc string.

What made you increase the class version in your most recent commit?

Copy link
Contributor Author

@JasMehta08 JasMehta08 Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification regarding the transient member. I initially kept the ClassDef version unchanged for that exact reason.

However, I later bumped it because the CI failed on roottest-root-aclic-offset-offset with a size mismatch error (Expected: 1000, Got: 1024). I misinterpreted this failure as the system rejecting the class layout change, which is why I attempted the version bump to fix it.

I have now re-analyzing the test output, I realize the failure is simply detecting the 24-byte size increase (3 axes × 8 bytes padding/float) caused by adding fRefLength.

I have reverted the ClassDef change in the latest commit. Could you advise on how to handle the roottest failure? Does the reference file (offset.ref) need to be updated to reflect the new TH1 size?

Thanks!

@JasMehta08 JasMehta08 requested a review from linev as a code owner December 22, 2025 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify axis titles and label sizing and offsetting across a set of pads

2 participants