Changing TLegend (TPave) positioning after construction with SetX1(Y1)NDC?

Dear experts,

Is it possible to change the position of a TLegend (or any TPave object) after construction? I want to be able to construct a TLegend, fill it, then set its position (and size if possible) relative to the margins of my histogram. For instance:

: m_Legend(0.3, 0.15, "", "NBNDC") // in some initializer list
// by specifying just width and height and assuming automatic positioning

or

: m_Legend(0, 0, 0, 0, "", "NBNDC") // or specifying zero size and changing it later

then later on, before drawing

TStyle *s = gROOT->GetStyle("ATLAS");
float leftMargin = s->GetPadLeftMargin();
float topMargin = s->GetPadTopMargin();
m_Legend.SetX1NDC(leftMargin+0.02);
m_Legend.SetX2NDC(leftMargin+0.02+0.3); // or just set x1,y1 and rely on width and height of TLegend
m_Legend.SetY1NDC(1-topMargin-0.02-0.15);
m_Legend.SetY2NDC(1-topMargin-0.02);
m_Legend.Draw(); // legend isn't drawn with the new x1, x2, y1, y2 --
// neither with the automatic positioning, or with zero size, depending on construction
// I also tried m_Legend.DrawPave(x1,...)

// draw other objects with option "SAME"

c.Update();
c.Print(...);

I think I’m seeing the same problem as detailed here: TPaveText position before drawing

If there is a better way to programmatically set the positions of objects relative to the margins of a histogram, please let me know. Restricting the automatic positioning to upper left or upper right would also be sufficient.

Thanks,
Justin

1 Like

With the following macro I am able to change the legend position using SetX1NDC.

{
   double x[]  = { -100., 0., +100. };
   double py1[] = { 1., 2., 3. };
   double py2[] = { 1.1, 1.9, 2.8 };

   TCanvas *c1     = new TCanvas( "c1", "", 800, 600 );
   TMultiGraph *mg = new TMultiGraph("mg","mg");

   TGraph *tg1 = new TGraph( 3, x, py1 );
   tg1->SetName("tg1");
   tg1->SetLineColor( 3 );
   tg1->SetMarkerColor( 3 );
   tg1->SetMarkerStyle( 22 );
   mg->Add( tg1 );

   TGraph *tg2 = new TGraph( 3, x, py2 );
   tg2->SetName("tg2");
   tg2->SetLineColor( 4 );
   tg2->SetMarkerColor( 4 );
   tg2->SetMarkerStyle( 23 );
   mg->Add( tg2 );

   mg->Draw( "ACP" );

   TLegend *tl = new TLegend( 0.55, 0.65, 0.75, 0.85 );
   tl->AddEntry( "tg1", "Name Type 1" ,"P");
   tl->AddEntry( "tg2", "Name Type 2" ,"P");
   tl->AddEntry( tg1, "Address Type 1" ,"L");
   tl->AddEntry( tg2, "Address Type 2" ,"L");
   tl->Draw();
   gPad->Update();
   tl->SetX1NDC(0.01);
   tl->SetX2NDC(0.9);
   gPad->Modified();
}

If I draw the TLegend, then change the coordinates of the legend, then call Modified, the legend is drawn at the new coordinates. As demonstrated by your macro.

But if I change the coordinates first, then Draw, the legend is still drawn at the old coordinates.

This point was also raised in the thread I linked to, from five years ago: TPaveText position before drawing

Changing your macro from:

   TLegend *tl = new TLegend( 0.55, 0.65, 0.75, 0.85 );
...
   tl->Draw();
   gPad->Update();
   tl->SetX1NDC(0.01);
   tl->SetX2NDC(0.9);
   gPad->Modified(); // drawn at new coordinates

to

   TLegend *tl = new TLegend( 0.55, 0.65, 0.75, 0.85 );
...
   tl->SetX1NDC(0.01);
   tl->SetX2NDC(0.9);
   tl->Draw();
   tl->Update();
   gPad->Modified(); // drawn at old coordinates

(moving gPad->Modified() before tl->Draw() has no effect)

This is a bit annoying in my case, because I draw the legend inside some method that also draws other objects (DrawAll() draws a stack, a legend, a TGraph, etc…). If I call DrawAll(), I will have to retrieve the legend from the pad (GetPrimitive?), then SetX1NDC, then redraw it.

e.g.

void foo::DrawAll() {
  this->DrawStack();
  this->DrawSignal();
  this->DrawData();
  this->DrawLegend();
}
void foo::DrawLegend() {

TStyle *s = gROOT->GetStyle("ATLAS");
float leftMargin = s->GetPadLeftMargin();
float topMargin = s->GetPadTopMargin();
m_Legend.SetX1NDC(leftMargin+0.02);
m_Legend.SetX2NDC(leftMargin+0.02+0.3);
m_Legend.SetY1NDC(1-topMargin-0.02-0.15);
m_Legend.SetY2NDC(1-topMargin-0.02);
m_Legend.Draw();

}
TCanvas c("ctemp", "", cwx, cwy);
c.cd();
hc.DrawAll();
// DrawAll calls DrawLegend, which sets the coordinates of m_Legend before drawing it
// but m_Legend is not drawn with the new coordinates!
// non-ideal solution: I will have to go into the canvas, grab the TLegend, set its coordinates, and redraw it
c.Update();
c.Print(...);

UPDATE:
The solution I’ve implemented for now is: DrawLegend() does Draw, gPad->Update(), sets the coordinates, then gPad->Modified(). I think setting the coordinates before drawing should draw the legend at the new coordinates though…instead of this workaround of setting the coordinates after drawing, then Updating the pad…

1 Like

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