Drawing TF1 in a Loop

Hi, I have a simple macro that fails to execute as expected with ROOT6:

{
  const double ps[] = {1,2,3};
  const EColor colours[] = {kBlue,kRed,kGreen};
  std::vector<TF1> f1s;
  TString fname;
  TCanvas c1;
  c1.DrawFrame(-1,-1,1,1);
  for(size_t i = 0;i < 3; i++)
    {
      fname.Form("sin(%g*x)",ps[i]);
      f1s.emplace_back(fname.Data(),fname.Data(),-1,1);
      f1s[i].SetLineColor(colours[i]);
      f1s[i].Draw("same");
    }
}

If you make a file with that code and .x it, only the last green line is drawn. If you change to manually drawing each TF1:

f1s[0].Draw("same");
f1s[1].Draw("same");
f1s[2].Draw("same");

then they are drawn properly. Why is the draw method failing inside the loop? I can work around by using DrawCopy inside the loop instead, but why would this be necessary?

Jean-François

My guess is that the “emplace_back” call automatically reallocates the “f1s” storage space. As a result, all TF1 objects that have already been drawn will disappear and you will only see the very last one that has been created and added (as only the pointer to this newly created and added TF1 object will be valid, while all other “older” pointers are “invalidated” and so these TF1 objects disappeared from the pad).

Hi,

I agree with Wile E, good analysis, that’s probably it! I.e. the vector resizes, and doing so it calls ~TF1 on the existing ones, and they get removed from the pad. They did get copied into the newly allocated vector storage - but not drawn.

You can test this assumption by reserving extra vector size upfront, and hoping that your implementation actually cares about the reservation :slight_smile:

But either way: this discussion shows you that your approach is highly fragile. It’s probably better to draw after the vector is filled.

Cheers, Axel.

Thanks for the help.

I would argue that it shows the TF1 implementation is fragile, or even that it’s a comment on the ROOT object system. That makes me sound like a jerk, so please interpret it as a statement of general user frustration. Despite following the usual modern C++ advice (avoid raw pointers, use emplace instead of push when you can, etc), ROOT internals often get in the way.

Anyways, it looks like a solution is to have a separate loop for drawing once the vector is filled:

{ const double ps[] = {1,2,3}; const EColor colours[] = {kBlue,kRed,kGreen}; std::vector<TF1> f1s; TString fname; TCanvas c1; c1.DrawFrame(-1,-1,1,1); for(size_t i = 0;i < 3; i++) { fname.Form("sin(%g*x)",ps[i]); f1s.emplace_back(fname.Data(),fname.Data(),-1,1); f1s[i].SetLineColor(colours[i]); } for(auto && f : f1s) { f.Draw("same"); } }

Hi Jean-François,

Point taken, though this is not a trivial issue: if some objects are drawn on some canvases, other not, and again others on other canvases: how do you know what a copy should do? In the end that’s the fundamental question. You are saying “it should be drawn on the same canvas” but that’s not obvious to me. Maybe implementing a move c’tor would be a solution… Anyway, we’ll certainly keep this in mind in design discussions!

Cheers, Axel.

Note that there are many cases in ROOT that objects cannot be copied (because they cannot be “Cloned”).
This also includes TF1 objects created with different constructors.
So, I believe a better solution would be to use a “std::vector<TF1*>” (in this case any reallocation of the storage space will not “invalidate” these pointers).
I’m afraid ROOT is “raw-pointer oriented” by design and you will need to accept it.