How to set a color for zero-entry bins in CONT histograms

Dear ROOT friends,

I have a 2D histogram, which I plot with the CONT or CONTZ option. Let’s say there is a peak in the middle of the plot, sticking out like an island from the sea (as you would get from the FirstContour.C tutorial, for instance, see example image attached).

In this plot, the colorful island is embedded in a white (or GREY, if you stick to the wonderful ROOT default…) sea representing the zero-entry bins. While this is a perfectly correct representation of the data, it can sometimes produce a rather misleading impression: the sharp boundary of the island, going from violet to white often does not correspond to reality, where some distribution smoothly fades out to zero. To fix this, I would like to have the option of giving the zero-entry bins the lowest level color (i.e. violet in case of the pretty palette). I couldn’t find a simple way to do that. A work-around would be to simply add a small epsilon to all bin entries, thus bringing the zero-entry bins up to 0.00001, which would make them enter the lowest contour. But that’s not a good option if you have automatically generated 2D histograms like when using TGraph2D objects, or, as in the FirstContour tutorial from an ntuple->Draw() command.

So, does anyone know how to give a color to the zero-entry bins in CONT plots?

Many thanks in advance,

Thomas


You can achieve what you are looking for by changing the frame color. Add the following line in the macro:

c1->SetFrameFillColor(4);

and you will get the attached result.
Choose an other color if you prefer …


Great, thank you very much!
Do you happen to know how I could make that more generic by asking for the lowest-level color of the current palette? I can’t find a gStyle->GetPalette()->GetContourColor(0) or so…

The deep sea palette you are using starts with kViolet+2. So simply do:

c1->SetFrameFillColor(kViolet+2);

I know… (but it’s “pretty”, not “deep sea”)
However, in my application, I allow the user to switch between pretty, deep sea, and even a custom-defined palette. So I don’t know in advance what the current palette is. I am just wondering if I can ask gStyle for this information, or if I have to do it the brute-force way and store this information myself everytmie the user changes the palette. That’s what I meant by “more generic” - sorry if I was not clear enough!

Ok, sorry. Took me some times to find out the answer in the help. Here it is:

c1->SetFrameFillColor(TColor::GetColorPalette(0));

Hi Olivier,

thanks for your effort!

9 minutes isn’t too bad! :slight_smile:

Playing around with this some more I realize two more issues:

  1. the color actually used by the lowest-level contour not only depends on the palette, but also on the number of contours used, of course (20 by default). Is there a way to access this very color (i.e. the one the user will have at the bottom of his/her color palette to the right)?

  2. this color in fact NEVER gets used by the contour plot (“CONT”)! I wonder if this is a feature or a bug. Consider the macro below, where I have a peak and a few “calibration bins” to the right. In COL the full information is there, whereas in CONT the bins below 100 (that should get the lowest color) are not shown. In this way, the entire tail of the distribution is hidden. One might argue that contours get their color from a different algorithm than colored bins from COL, but still, it is highly misleading if a color appears on the color palette to the right but then never gets used in the plot. One or the other has to be fixed to have a coherent representation of the data.

What do you think? (Sorry for being persistent…)

Thomas

My macro: (the numbers are chosen to get color palette roughly from 0 to 2000)

void cont()
{
   TCanvas *c1 = new TCanvas("c1","c1",1200,800);
   gStyle->SetOptStat(0);
   gStyle->SetPalette(1);
   TH2F *h1 = new TH2F("h1","h1",40,-4,4,40,-4,4);
   Double_t a,b;
   for (Int_t i=0;i<306500;i++) {gRandom->Rannor(a,b);h1->Fill(a-1.5,b-1.5);}

   for (Int_t i=0;i<550;i++) {h1->Fill(3.5,3);}
   for (Int_t i=0;i<450;i++) {h1->Fill(3.5,2);}
   for (Int_t i=0;i<350;i++) {h1->Fill(3.5,1);}
   for (Int_t i=0;i<250;i++) {h1->Fill(3.5,0);}
   for (Int_t i=0;i<150;i++) {h1->Fill(3.5,-1);}
   for (Int_t i=0;i< 50;i++) {h1->Fill(3.5,-2);}
   for (Int_t i=0;i<  5;i++) {h1->Fill(3.5,-3);}

   Int_t npal=1;
   c1->Divide(2,2);
   c1->cd(1);
   c1_1->SetRightMargin(0.15);
   //c1_1->SetFrameFillColor(TColor::GetColorPalette(npal));
   h1->Draw("contz");

   c1->cd(2);
   c1_2->SetRightMargin(0.15);
   //c1_2->SetFrameFillColor(TColor::GetColorPalette(npal));
   h1->Draw("lego2z");

   c1->cd(3);
   c1_3->SetRightMargin(0.15);
   //c1_3->SetFrameFillColor(TColor::GetColorPalette(npal));
   h1->Draw("colz");

   c1->cd(4);
   c1_4->SetRightMargin(0.15);
   h1->Draw("boxz");

}          

Use the option CONT4 instead of CONT.

OK, this is the solution to my very first problem!
(Should be the default behaviour in my view - the output of CONT is just inconsistent).

Another thing I forgot to mention
3) there is also an inconsistency between the way COL and LEGO2 color their bins: COL keeps zero-entry bins white, whereas LEGO2 gives them the color of the lowest contour. See the macro attached above for an example. I don’t see any obvious reason for this either.

Many thanks for your help in any case!

Thomas

There is a reason, may be not obvious but there is one. The COL option is one of the most widely use to draw 2D histograms. Over the years it has been agreed with users that the empty bins should not be drawn with this option. Because they are empty… We tried once to draw them as the other bins but we had to revert that change because we got many complaints. We draw the 0 bins only if the 2D histo has some negative bins because in that case the 0 bins might be not empty.

Thanks for this clarification. I can live with the COL vs LEGO2 inconsistency (although one could just change LEGO2 to be in line with COL). After all it’s just a matter of convention how you treat empty bins. But in the CONT case you can end up with whole areas of non-empty bins that should be violet according to the color palette drawn on the right but turn out to be white. This is clearly wrong. I am guessing that the reason for omitting this last contour is that CONT has difficulty defining a contour line between empty and almost-empty bins, since that edge might be very fuzzy. But in that case the color palette should be adapted to reflect what is drawn on the plot, i.e. the color of the bottom square of the color palette should be white. The user has the right to know up to which bin content the color white is used (in the example macro above: up to 100 entries!!). So IMHO there is a need for action.

Thomas

To make it clear you think that an extra contour should be drawn around the violet part when you use this macro ? if that’s what you mean I agree. An will look more closely

void cont10()
{
   TCanvas *c1 = new TCanvas("c1","c1",800,800);
   gStyle->SetOptStat(0);
   gStyle->SetPalette(1);
   TH2F *h1 = new TH2F("h1","h1",40,-4,4,40,-4,4);
   Double_t a,b;
   for (Int_t i=0;i<306500;i++) {gRandom->Rannor(a,b);h1->Fill(a-1.5,b-1.5);}

   for (Int_t i=0;i<550;i++) {h1->Fill(3.5,3);}
   for (Int_t i=0;i<450;i++) {h1->Fill(3.5,2);}
   for (Int_t i=0;i<350;i++) {h1->Fill(3.5,1);}
   for (Int_t i=0;i<250;i++) {h1->Fill(3.5,0);}
   for (Int_t i=0;i<150;i++) {h1->Fill(3.5,-1);}
   for (Int_t i=0;i< 50;i++) {h1->Fill(3.5,-2);}
   for (Int_t i=0;i<  5;i++) {h1->Fill(3.5,-3);}

   h1->Draw("colz"); 
   h1->Draw("cont3 same");
}

Yes, exactly. To make it even more clear, if I do h1->Draw(“contz”); in the above macro and point my finger to (1,0), then the resulting plot tells me that I am pointing to a region with empty bins, whereas in fact, the region has bins with content between 0 and 100, marked violet on the palette. In other words, the plot gives me the wrong information!

When you are using the contour options without specifying any contour levels, an automatic slicing is done. The histogram is sliced along Z into equidistant slices (between zmin and zmax) so they may not match what the eye would logicallly consider as contours looking at the COL option. If you want to have contours at some specific altitudes you can specify them as shown in the following example.

void cont10()
{
   TCanvas *c1 = new TCanvas("c1","c1",800,800);
   gStyle->SetOptStat(0);
   gStyle->SetPalette(1);
   TH2F *h1 = new TH2F("h1","h1",40,-4,4,40,-4,4);
   Double_t a,b;
   for (Int_t i=0;i<306500;i++) {gRandom->Rannor(a,b);h1->Fill(a-1.5,b-1.5);}

   for (Int_t i=0;i<550;i++) {h1->Fill(3.5,3);}
   for (Int_t i=0;i<450;i++) {h1->Fill(3.5,2);}
   for (Int_t i=0;i<350;i++) {h1->Fill(3.5,1);}
   for (Int_t i=0;i<250;i++) {h1->Fill(3.5,0);}
   for (Int_t i=0;i<150;i++) {h1->Fill(3.5,-1);}
   for (Int_t i=0;i< 50;i++) {h1->Fill(3.5,-2);}
   for (Int_t i=0;i<  5;i++) {h1->Fill(3.5,-3);}

   double contours[4];
   contours[0] = 3;  
   contours[1] = 100; 
   contours[2] = 900;
   contours[3] = 1500;


   h1->DrawCopy("colz");
   h1->SetContour(4,contours);
   h1->Draw("cont3 same");
}

Thank you for this really useful information and example. I appreciate the fact that CONT will not draw a contour at zero since it is the zmin in this case.

This, however, was not my main concern. What I criticized was that a CONT plot will show a color on the palette that will never be used in the plot. This isn’t just an aesthetic problem, it is simply wrong. I tried to make this point in my previous two posts. Let me try again with a different story:

Two ROOT users, Dr. Cont and Dr. Col go on a hike. They are given information on the landscape in the form of 2D data. Of course they will draw a histogram of this information. Dr. Cont will draw it in CONTZ option, Dr. Col in COLZ. Before they set out on exploring the landscape, they agree to meet at the north face of the big mountain at an altitude of 100 m. Dr. Col looks at the color palette of his map, concludes that the meeting point is in the middle of the violet band at the north face, therefore starts hiking towards the point (x=-1.5,y=-1). Dr. Cont looks at his map with the very same color palette, also comes to the conclusion that the meeting point must be somewhere in the violet area (color palette says violet from 0 to 200!), but… there is no violet area in his plot!!

Clearly, Dr. Cont is misled by his plot. ROOT gives him a plot with inconsistent information: the color palette says, bins with content between 0 and 200 are colored in violet, but the plot does not do that. And here we don’t care about the color of bins with content = 0, we care about bins with content 20, 50, 120, etc. Real bins with real content are not correctly represented in this plot.

Do you understand my point now? I still believe that there is a need for action to correct this inconsistency: either make sure the plot gives the correct information, i.e. color everything violet in the lowest contour band (preferred solution, to maintain consistency with COL color palette), or correct the color palette: show the white color on the palette for the range 0 to 200 on the scale.

Thank you for reconsidering.

Thomas

void cont11()
{
  TCanvas *c1 = new TCanvas("c1","c1",1200,500);
  gStyle->SetOptStat(0);
  gStyle->SetPalette(1);
  TH2F *h1 = new TH2F("h1","h1",40,-4,4,40,-4,4);
  Double_t a,b;
  for (Int_t i=0;i<306500;i++) {gRandom->Rannor(a,b);h1->Fill(a-1.5,b-1.5);}

  for (Int_t i=0;i<550;i++) {h1->Fill(3.5,3);}
  for (Int_t i=0;i<450;i++) {h1->Fill(3.5,2);}
  for (Int_t i=0;i<350;i++) {h1->Fill(3.5,1);}
  for (Int_t i=0;i<250;i++) {h1->Fill(3.5,0);}
  for (Int_t i=0;i<150;i++) {h1->Fill(3.5,-1);}
  for (Int_t i=0;i< 50;i++) {h1->Fill(3.5,-2);}
  for (Int_t i=0;i<  5;i++) {h1->Fill(3.5,-3);}

  h1->SetContour(10);

  c1->Divide(2,1);
  c1->cd(1);
  c1_1->SetRightMargin(0.15);
  h1->Draw("contz");

  c1->cd(2);
  c1_2->SetRightMargin(0.15);
  h1->Draw("colz");

}


I do understand Dr. Cont and Dr. Col concern. The level with CONT are painted from the lower limit of the level up to the next level. This contour algorithm is an iso-values algorithm and therefore it makes more sense to use it as line contour. Anyway you can use filled contour too. I agree that the pallette is not used completly and may be we can improve the palette drawing. But the plot itself is correct if you look at the actual position of the first contour which is 200 (2000/10) it correspond exactly to the color on the palette.

Any way, why dont you use CONT4 as it gives you exactly want you want as the attached picture shows it ?


I agree that CONT4 gives what I want and thank you for pointing it out to me - I would never have guessed this option (see below). At this point, I am no longer worried about how I can achieve my desired result, I am just worried about ROOT as a tool: why does the “default” CONT option give a plot where the lowest contour level carries no color on the plot (but does carry one on the color palette), and the CONT4 option, that no one would ever guess, gives a consistent result. My recommendations would be:

  1. Make CONT4 the CONT default and move the current CONT behaviour to CONT4 (or 5, or whatever). I have no problem if you want to include illogical or crazy drawing options if some users prefer or need that, but they should not be at a position the normal user would consider the default option, and they should be documented clearly. Therefore:
  2. Insert a clear description of all this in the user guide. Right now it says:
    “CONT”: Draw a contour plot (same as CONT0)
    “CONT0”: Draw a contour plot using surface colors to distinguish contours
    "CONT4": Draw a contour plot using surface colors (SURF option at theta = 0)
    You will have to agree that there is no way a user can guess the behaviour of CONT0 and CONT4 from these descriptions…

These are just suggestions to make ROOT more useful to a wider circle of users that are not necessarily familiar with the entire history of ROOT and HEP plotting… ROOT is a great tool - it could be even better if details such as these could be found out without having to launch a discussion on the forum or wading through the source code. (Of course I understand you are all very busy with maintaining the code, let alone the documentation!)

Thomas