Drawing a label in user coordinates

I would like to have a label with border, that stays on its original position (user coordinates) when changing the axis range after it has been drawn.

The code below illustrates the desired behaviour. The TPaveLabel label has a border, but it stays on its position relative to the pad. The TLatex label on the other hand behaves after changes in the range like I want it to, but it is missing a border.

So I can think of two ways to solve my problem:

  1. Change the behaviour of TLatexLabel to be automatically redrawn/repositioned after changes in the pad range, or
  2. Add a border to a TLatex label.

Unfortunately I was not able to figure out a solution to any of those problems. So if somebody could help with that or maybe point to a completely different solution, this would be highly appreciated.

Thanks in advance

   TH2F* hist = new TH2F("name", "title", 100, 0, 100, 100, 0, 100);
   TPaveLabel* label_fixed = new TPaveLabel(40,50,60,60,"label_{fixed}");
   TLatex* label_noborder = new TLatex(50, 30, "label_{borderless}");

“original position” is not user coordinates. It is NDC … If you draw the PaveLabel in user coordinates it will more when you’ll change the axis range. You should use NDC coordinates. Add the option NDC in the TPavelLabel constructor

Note also that “same” is not needed when you draw TPaveLabel or the TLatex. It is not a valid option and will be ignored.

Thanks for your reply.

But I am still not completely sure, if I understand the behavior of TPaveLabel correctly. So please correct me if anything is wrong:
As far as I have understood now the coordinates the TPaveLabel actually uses are always NDC coordinates. But the coordinates I am passing are intentionally user coordinates. At the time of the creation of that label I do not yet know the dimensions of the final Pad.
In order for TPaveLabel to work, there has to be a conversion behind the scenes which is triggered whether I use the option “NDC” or not. Is this what you mean with:

Apparently this conversion can only be done, after the pad has been created and with that a conversion from user coordinates to ndc (or pixel). Furthermore you can create a TPaveLabel before any Pad has been created, so I assume that the conversion is done during the call to Draw.
This however is only done once (if option “NDC” was used) or never.

So if everything so far is correct, I was erroneous expecting a behavior from TPaveLabel which I am used to from other graphical root objects I have used so far. Nevertheless I can rule out the use of a TPaveLabel to solve my initial problem:

I need a label (with border) that stays on a certain user position. So if the label was created at x=50 and the range [0,100] is shown, it should be visible. If the range [100,200] is shown it should not be visible.

Anyhow, does root provide such a label type? I would very much prefer learning how to use an already existing one over trying to invent one by myself :slight_smile:

The best would be to provide a small script showing the behavior you are complaining about and I will try to modify it.

Hi friends,
att. a little macro from which I think it does what bsiebeck looks for:
run it with:

.L mytpavelabel.C+

Then zoom in X or YAxis, the label should stay at its pad coordinates
and keep its size.
It assumes the alignment is lower left.

If ok, I wonder if it could go (optionally) into ROOT.


mytpavelabel.C (3.5 KB)

Hopefully the script testlabel.C below shows better what I am looking for. It will create two pads in one canvas. The label should be visible only in the left pad, not in the right one.
As this is a boiled down showcase, I tried to simulate the problem by moving the construction of the label into an own function. Inside this function the only coordinates that should be used are user coordinates. This is simulated by drawing two copies of the original histogram.
Apart from that it should also be possible to change the yrange in the right pad, either by using SetRangeUser or manually with the mouse. In both cases the label should stay on the maximum and not on its original position with respect to the Pad.

void add_data(TH2I* Hist, UInt_t NDataPoints = 1) {
   for (auto n = 0; n < NDataPoints; ++n) {
      auto ycoord = rand() % static_cast<UInt_t>(Hist->GetYaxis()->GetXmax());
      for (auto xcoord = 0; xcoord < Hist->GetXaxis()->GetXmax(); ++xcoord) {
         Hist->Fill(xcoord, ycoord);

TObject* add_label(TH2I* Hist) {
   auto xcoord1 = 0;
   auto xcoord2 = Hist->GetXaxis()->GetXmax() / 2;
   Int_t maxX, maxY, maxZ;
   Hist->GetMaximumBin(maxX, maxY, maxZ);
   TPaveLabel* label = new TPaveLabel(xcoord1, maxY, xcoord2, maxY+10, "maximum", "");
//both of the next two lines actually do behave exactly like I want (at least placement wise)
//TBox* label = new TBox(xcoord1, maxY, xcoord2, maxY+10); label->SetFillColor(kWhite);
//TLatex* label = new TLatex(xcoord1, maxY, "maximum"); 
   return label;

void testlabel() {
   auto hist = new TH2I("histname", "histtitle", 100, 0, 100, 100, 0, 100);
   add_data(hist, 1000);
   TObject* label = add_label(hist);
   TCanvas* can = new TCanvas("name", "title", 800, 400);
   hist->SetTitle("label should be visible");
   hist->SetTitle("label should NOT be visible");

@OSchaile Thanks! This is indeed exactly what I was looking for.

Just replaced the TPaveLabel construction with MyTPaveLabel* label = new MyTPaveLabel(xcoord1, maxY, xcoord2, maxY+10, "maximum"); in my script from above and it works.
Now I will try that in the actual program.

Dear Rooters,

as indicated previously I propose to allow an option “FIX”
to the TPave types of widgets (e.g. TPaveLabel)
(The name FIX can of course be discussed)

If this option is given in the ctor and TPads coordinates
are used (no “NDC”) then in case the display ranges of axis
are changed (e.g. zoom) then the TPaves lower left position in TPads
coordinates is preserved. The size of the TPave is also preserved.
This allows e.g. to label a peak in a spectrum.

Originally I thought to allow for an alignment type:
i.e. 22: preserve the position of the center of the pave.
This however would need much more coding effort and extra
field values in the class.

The modification is quite simple:
Replace in TPave.cxx, TPave::ConvertNDCtoPad()lines: 164-167

      if (!fOption.Contains("FIX") || fOption.Contains("NDC")) {
			fX1    = xp1 + fX1NDC*dpx;
			fY1    = yp1 + fY1NDC*dpy;
			fX2    = xp1 + fX2NDC*dpx;
			fY2    = yp1 + fY2NDC*dpy;
		} else {
			// keep lower left, dont change fX1 fY1, keep size
			Double_t sizeX= dpx * (fX2NDC-fX1NDC);
			Double_t sizeY= dpy * (fY2NDC-fY1NDC);
			fX2 = fX1 + sizeX;
			fY2 = fY1 + sizeY;

I expect no side effects but you never know.
For convenience I add again the (somewhat cleaned) test program.


mytpavelabel.C (3.1 KB)

1 Like

@OSchaile I used your Label and extended it a little bit to fit my special needs. Now one is able to choose the alignment type. As you mentioned this needed the addition of new fields.
Also there is an option to fix the size of the label in the NDC coordinate space by calling FixNDCSize(). When doing so the behaviour should be just like the behaviour of your original label or a TLatex object. If the size is not fixed (default) then the behaviour of the label with regard to resizing/positioning is like the behaviour of TBox (see the attached scipt).
I have just worked on the non “NDC” version, so that the “NDC” part would have to be tested.
Your idea of an additional option “FIX” could be extended as well to automatically call FixNDCSize() toggled by another option for instance “SIZE”.

Also as ConvertNDCtoPad() is public, there should be a check for gPad to avoid dereferencing nullptr. This is also not done in TPave, so there might be a reason for this that I do not know of.
testlabel.C (1.3 KB)
TLabel.cc (5.5 KB)
TLabel.hh (1.0 KB)

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