Help converting function to return TPad *

Hi, I have a working function that makes a TCanvas, draws some stuff, and saves it to a file. I’d like to convert the function to instead return the TCanvas (or better, a TPad) so that I can put it on a Pad of a greater canvas outside the function before saving.

Unfortunately with my first attempt, a bunch of objects on the stack disappear when the function returns, and I am not sure how to make the TPad “own” these objects. Ideally a TPad * would be returned that I could ->Draw onto another canvas, and when I delete the TPad *, all the previously-on-stack objects would be cleaned up automatically.

Here is the original function, with proper includes, and a test() function that makes a file with the produced graph.

#include "TVectorD.h"
#include <vector>
#include "TString.h"
#include "TCanvas.h"
#include "TLine.h"
#include "TMath.h"
#include "TMarker.h"
#include "TH1.h"

  void MakePlot(TVectorD quantity, Double_t threshold, 
                const std::vector<Int_t>& clusters,TString filename)
  {
    TCanvas c1("c_makeplot","c_makeplot");
    quantity.Draw();
    TLine threshline(0,threshold,quantity.GetNoElements(),threshold);
    threshline.SetBit(kCanDelete);
    threshline.SetLineWidth(1);
    threshline.SetLineStyle(10);
    threshline.SetLineColor(kRed);
    if(TMath::Finite(threshold))
    {
      threshline.Draw();
    }
    TMarker tm;
    tm.SetBit(kCanDelete);
    tm.SetMarkerColor(kGreen);
    tm.SetMarkerStyle(4);
    tm.SetMarkerSize(1);
    for(auto c : clusters)
    {
      tm.DrawMarker(c,quantity[c]);
    }

    TString title = TString::Format("N Clusters: %lu",clusters.size());
    std::unique_ptr<TH1D> h_vec(static_cast<TH1D*>(c1.FindObject("TVectorD")));
    h_vec->SetTitle(title);
    
    auto oldlevel = gErrorIgnoreLevel;
    gErrorIgnoreLevel = kInfo+1;
    if(filename=="") filename = "default_name.pdf";
    c1.SaveAs(filename);
    gErrorIgnoreLevel = oldlevel;
    return;
  }

void test()
{
    double vals[] = {4,5,6,7,8,9};
    TVectorD tv(6,vals);
    std::vector<int> clusters{5,1};
    MakePlot(tv,4.5,clusters,"test_MakePlot.pdf");
}

To make the function return a TPad *, I know I’d have to “new” the TPad instead of using a TCanvas on the stack, but what about the TLine and TMarkers, and the temporary “TVectorD” histogram that is created when I do quantity.Draw()? How do I ensure that the TPad owns them and carries them outside the function? And how do I make sure that when the TPad * is eventually deleted, the owned items are also deleted?

Thanks,
Jean-François

[code]#include “TVectorD.h”
#include
#include “TString.h”
#include “TCanvas.h”
#include “TLine.h”
#include “TMath.h”
#include “TMarker.h”
#include “TH1.h”

TCanvas *MakePlot(TVectorD quantity, Double_t threshold,
const std::vector<Int_t>& clusters,TString filename)
{
TCanvas *c1 = new TCanvas(“c_makeplot”,“c_makeplot”);
quantity.Draw(); // quantity.DrawClone(); generates “segmentation fault”, but why?
TLine threshline(0,threshold,quantity.GetNoElements(),threshold);
threshline.SetBit(kCanDelete);
threshline.SetLineWidth(1);
threshline.SetLineStyle(10);
threshline.SetLineColor(kRed);
if(TMath::Finite(threshold))
{
threshline.DrawClone();
}
TMarker tm;
tm.SetBit(kCanDelete);
tm.SetMarkerColor(kGreen);
tm.SetMarkerStyle(4);
tm.SetMarkerSize(1);
for(auto c : clusters)
{
tm.DrawMarker(c,quantity[c]);
}

TString title = TString::Format(“N Clusters: %lu”,clusters.size());
TH1D h_vec = (static_cast<TH1D>(c1->FindObject(“TVectorD”)));
h_vec->SetTitle(title);

auto oldlevel = gErrorIgnoreLevel;
gErrorIgnoreLevel = kInfo+1;
if(filename=="") filename = “default_name.pdf”;
c1->SaveAs(filename);
gErrorIgnoreLevel = oldlevel;
return c1;
}

void test()
{
double vals[] = {4,5,6,7,8,9};
TVectorD tv(6,vals);
std::vector clusters{5,1};
MakePlot(tv,4.5,clusters,“test_MakePlot.pdf”);
}[/code]

Thanks. I get a segmentation fault with quantity.DrawClone(), but changing the unique_ptr to a plain TH1D* and using threshline.DrawClone() (or DrawLine with the same coordinates again) makes the leftover global canvas have the right stuff on it, even after the function returns.

So now my function works for returning a whole TCanvas *, but now I am stuck at trying to draw this TCanvas * into the subpads from a greater TCanvas that has been .Divide()d.

Schematically, I am trying to do this:

{
  TCanvas c1;
  c1.Divide(1,3);
  auto p1 = MakePlot(parameters); // Return either a TPad * or a TCanvas *.
  c1.cd(1);
  p1->Draw(); // I want to draw p1 into the first subpad of c1.
  auto p2 = MakePlot(different parameters);
  c1.cd(2);
  p2->Draw(); // I want to draw p2 into the second subpad of c2.
  ...
  cleanup code;
}
// Now c1 is out of scope and I have not leaked any objects from the above block.

From my tests, the TCanvas * returned from the function (p1, p2) do NOT get drawn into the subpads of c1. When I try to rewrite MakePlot to return a TPad *, I get a segmentation fault.

I’ve attached the semi-working code that returns a TCanvas *. It’s semi-working because the function MakePlot will return a TCanvas * that you can draw and save on its own (based on your suggestions above), but the test() function that tries to put them onto subpads of another canvas does not work. You can use test2() to avoid having to copy & paste the TVectorD construction steps.

Probably I am misunderstanding something fundamental about how TPads and TCanvases are related. I tried several variations (including e.g. replacing the TVirtualPads of c1 with the pad #0 obtained from p1), but nothing that I tried worked.

Jean-François
test_MakePlot.C (1.69 KB)

[code]#include “TVectorD.h”
#include
#include “TString.h”
#include “TCanvas.h”
#include “TPad.h”
#include “TLine.h”
#include “TMath.h”
#include “TMarker.h”
#include “TH1.h”

void MakePlot(TVectorD quantity, Double_t threshold,
const std::vector<Int_t>& clusters)
{
quantity.Draw();
TString title = TString::Format(“N Clusters: %lu”,clusters.size());
TH1D h_vec = static_cast<TH1D>(gPad->FindObject(“TVectorD”));
h_vec->SetTitle(title);

TLine threshline;
threshline.SetBit(kCanDelete);
threshline.SetLineWidth(1);
threshline.SetLineStyle(10);
threshline.SetLineColor(kRed);
if(TMath::Finite(threshold))
{
threshline.DrawLine(0,threshold,quantity.GetNoElements(),threshold);
}
TMarker tm;
tm.SetBit(kCanDelete);
tm.SetMarkerColor(kGreen);
tm.SetMarkerStyle(4);
tm.SetMarkerSize(1);
for(auto c : clusters)
{
tm.DrawMarker(c,quantity[c]);
}

return;
}

void test()
{
double vals[] = {4,5,6,7,8,9};
TVectorD quantity(6,vals);
std::vector clusters{5,1};

TCanvas *c1 = new TCanvas();
c1->Divide(1,3);

c1->cd(1);
MakePlot(quantity,4.5,clusters);

c1->cd(2);
MakePlot(quantity,7,clusters);

c1->cd(0);
c1->SaveAs(“test_MakePlot.pdf”);

delete c1;
}[/code]

Thanks, that works!

I’m a little put off by relying on the global state of gPad, but I’m the only user of this code, so I think it’ll be ok.

Jean-François