Plotting 2D Histogram as a Plane Under a 3D Histogram


_ROOT Version: 6.14.00
_Platform: MacOS 10.14.2
_Compiler: MacOS Clang


Hello,

For some analysis of proton energies and number densities in the XY plane I have put together two histograms. A 3D histogram (3D visualisation of a TH2D) that I have modified to illustrate the average energy as the height of the bars for each XY bin, and a 2D (2D visualisation of a TH2D) histogram which simply contains the numbers of events in each XY bin. I have attached images of these histograms.

I would like to know if there is a way I can plot the number density histogram as a plane somewhere beneath the 3D histogram (e.g. at z axis = -50), such that I can convey both the number and energy density for each XY bin. I have attached an image from MATLAB documentation of the basic concept, except I am not looking to merely do a 2D projection of my 3D histogram, rather use a separate histogram.

Hope I explained that reasonably clearly!

Thank you.

That’s not really easy to do. What I can propose is something like that:

{
  TH2F *h1 = new TH2F("h1","",40,0,10,40,0,10);
  TH2F *h2 = new TH2F("h2","",40,0,10,40,0,10);

  for(int i =0; i<1000; i++) {
     h1->Fill(gRandom->Gaus(6,1),gRandom->Gaus(6,1));
     h2->Fill(gRandom->Gaus(4,.5),gRandom->Gaus(4,.5));
  }

  h2->SetLineColorAlpha(22,0.);
  gStyle->SetPalette(kBird,0,0.7);
  h1->Draw("surf");
  h2->Draw("surf3 same");
}

Thank you! That ought to do it :slight_smile:

Almost there: Is there a way to set the zero colour of the 2D plot on the top to be clear? i.e. without the sea of blue… Then it should be perfect! I’ve attached an image of where I’m currently at.

Thank you again.

I don’t know if it’s possible with a draw option, but here’s a hack that may work for you :slight_smile: (I guess it only works if the “zero” bins are actually empty, not filled with zeroes, but I haven’t tested that).
When you get the final plot like above, save the canvas as a C file (e.g. “c1.C”). Then open that C file and look for a TH2 histogram that has a line with SetContour(xx), where “xx” is some integer. In a simple example, I find

h1->SetContour(20);

Under that line there should be a list of “xx” (20, here) lines giving the “values” for each contour, like

h1->SetContourLevel(0,0);
h1->SetContourLevel(1,3.15);
… etc …

Now, just change the value (second number) of the first contour to something greater than zero (but less than the value of level 1 (3.15 in the example above); e.g.:

h1->SetContourLevel(0,0.1);

Save and run this new macro to reproduce the plot; the blue will be gone!

Hi there – thank you for the response!

Indeed my 2D histogram is filled with a bunch of zeros, a necessary consequence for when I construct the histogram. Therefore using your fix just changes the blue to white, as shown below. I do iterate through all the bins when I make this histogram, perhaps I can set a bin to be “empty” instead of setting to 0 as I currently do?

That being said, it would be ideal if this can be fixed with a simple draw option! Though I can’t seem to find anything in the documentation for THistPainter…

Thanks again.

EDIT: I have just tried to make a new 2D histogram filling it only with actual entries (essentially just skipping the “SetBinContent->(binNum,0)”), and unfortunately the same problem persists with the zero colour being white rather than clear.

Are you sure you are not initialising the histogram somewhere else?
This is the code for my example:

void surf() {
  TH2F *h1 = new TH2F("h1","",40,0,10,40,0,10);
  for(int i =20; i<30; i++) {
    for(int j =30; j<35; j++) {
      h1->SetBinContent(i,j,i+j);
    }
  }
  h1->Draw("surf3");
}

See if following the recipe you also get a solid white in this example. If so, then it could be your version of root (I ran it in root 5.34/36 on Windows 10, and root 6.14/06 on Xubuntu and both give the same results), or perhaps something in a configuration file (rootrc)? but here I’m just guessing, the developers or others may have better ideas.
EDIT: by the way, are there negatives in the histogram? Maybe this is why even avoiding the zeroes, the negative bins are being shown as zero, i.e. white. Try skipping those too, only do SetBinContent for content>0.

I have tried your code and I do produce the results you obtain – so no problem there.

I’ve found that the issue arises from which order you choose to draw the histograms. Once I’ve created my histograms, I draw them using:

hist2->SetLineColorAlpha(22,0.); //Make surface plot invisible in 3D space
hist2->SetContour(60); //smooth colour scale
hist2->SetContourLevel(0, 0.1); //Change 0 level to white instead of blue
hist->Draw("LEGO2Z0 SAME"); //Swap this line with the one below
hist2->Draw("SURF3 SAME");
c1->Update(); //update canvas
app->Run(hist); //run TApplication, doesn't matter if argument is hist or hist2

Where hist is the 3D histogram illustrating the average energy of particles in each XY bin and hist2 is the 2D histogram containing a simple number density of particles in the XY plane. I am using pre-compiled code and so I require the use of a TApplication to generate the canvas.

If you choose to draw hist2 first, then you obtain a clear ceiling (see attached image). However, this causes the z-scale to change to that of hist2, rather than hist as in the original plot, and is therefore incorrect. Remember, the 2D histogram contains a simple number density of particles in the XY plane. Choosing to draw hist first gives the image attached to my previous message.

Also note the strangely placed z-axis ticks (the dotted lines), which seem to suggest some overlay of ticks from both histograms. It is also worth noting that any controls to adjust axis labels etc. through the ROOT editor are non-functional, for whichever order you choose to draw the histograms.

So close yet so far…!

To hide the double grid lines, try setting the max and min of both histograms to the same:

  hist1->SetMinimum(0);
  hist2->SetMinimum(0);
  hist1->SetMaximum(170);
  hist2->SetMaximum(170);

About getting the “wrong” colour scale (TPaletteAxis), perhaps you can modify it to fit the values of the histogram you want, or create and draw your own, defined according to the histogram you want. I don’t know exactly how, but I think it is possible.

Thank you @dastudillo for your continued assistance :slight_smile:

Slight miscommunication: the colour scale on the previous plot is fine, the only issue is that the z-axis is that of the 2D number density histogram, rather than the 3D energy (range) histogram.

Setting the maxima and minima as you suggested does fix the double line effect, but leaves the histogram bars as small stubs on the ground, as we’ve set the maximum to be much higher than the maximum value in the histogram.

Perhaps @couet’s original solution may have a simple draw option to solve the problem?

Can you post your script as it is now ?

It is as shown in post 8 of this thread. Hist can be any 3D lego plot and Hist2 can be any 2D surface plot.

Do you have the macro producing the following plot ? I am not able to reproduce it.

From the previous code I sent in message 8, you can reproduce the plot by putting the line

hist2->Draw("SURF3 SAME");

Before the line

hist->Draw("LEGO2Z0 SAME");

Apologies as I am unable to put together a full working macro together at the moment. If it is still not working do let me know and I’ll try to put together something simple once I get the chance.

Thank you again.

May be you can start for the original macro I sent ?

{
  TH2F *h1 = new TH2F("h1","",40,0,10,40,0,10);
  TH2F *h2 = new TH2F("h2","",40,0,10,40,0,10);

  for(int i =0; i<1000; i++) {
     h1->Fill(gRandom->Gaus(6,1),gRandom->Gaus(6,1));
     h2->Fill(gRandom->Gaus(4,.5),gRandom->Gaus(4,.5));
  }

  h2->SetLineColorAlpha(22,0.);
  gStyle->SetPalette(kBird,0,0.7);
  h1->Draw("surf");
  h2->Draw("surf3 same");
}

I do not understand how you can get this plot with a first plot done with option same.

Here is the version of your code I use: For some reason you need to set the contour level to something like 60 to get the white background to show up, otherwise it stays blue…

TApplication* app = new TApplication("App", 0, 0);
	TH2F *h1 = new TH2F("h1","",40,0,10,40,0,10);
	TH2F *h2 = new TH2F("h2","",40,0,10,40,0,10);
	for(int i =0; i<1000; i++) {
		h1->Fill(gRandom->Gaus(6,1),gRandom->Gaus(6,1));
		h2->Fill(gRandom->Gaus(4,.5),gRandom->Gaus(4,.5));
	}
	TCanvas* c1 = new TCanvas("Test", "Test", 1080, 1080);
	h2->SetLineColorAlpha(22,0.);
	h2->SetContour(60);
	h2->SetContourLevel(0, 0.1);
	h1->Draw("LEGO2Z0 SAME");
    h2->Draw("SURF3 SAME");
	c1->Update();
	app->Run(h1);

This produces the following plot:

If you choose to draw h2 first, just by swapping the order of the draw lines, you get this plot:

I think this approach reaches its limits. A modification of THIstPainter would be needed. The option same with CONT already works on a surface but the contour is not drawn on top it is drawn in 3D, A new mechanism inspired from this one, but with contour on top, would be needed in THistPainter.

{
   auto c20=new TCanvas("c20","c20",600,400);
   int NBins = 50;
   double d = 2;
   auto h1 = new TH2F("h1", "Surface and contour with option SAME ", NBins, -d, d, NBins, -d, d);
   auto h2 = new TH2F("h2", "Surface and contour with option SAME ", NBins, -d, d, NBins, -d, d);

   for(int i =0; i<100000; i++) {
      h1->Fill(gRandom->Gaus(1.,0.5),gRandom->Gaus(1.,0.5));
      h2->Fill(gRandom->Gaus(0.,0.5),gRandom->Gaus(0.,0.5));
   }

   h1->Draw("surf2");
   h2->Draw("CONT1 SAME");
}

Ah that is a shame. Hopefully we could see something like this added in the future?

For the time-being at least, I’m able to make-do with the white ceiling, but just moved higher up in the z-plane to make the bars beneath fully visible.

Thank you @couet and @dastudillo for all of your help! :slight_smile:

You’re welcome. Sorry I couldn’t be of more help; anyway, I’m glad you have at least something usable :slight_smile:

@SaadShaikh

Indeed I found a way to do it:

{
   TH2F *h1 = new TH2F("h1","",40,0,10,40,0,10);
   TH2F *h2 = new TH2F("h2","",40,0,10,40,0,10);
   h1->SetStats(kFALSE);
   h2->SetStats(kFALSE);

   for(int i =0; i<1000; i++) {
      h1->Fill(gRandom->Gaus(6,1),gRandom->Gaus(6,1));
      h2->Fill(gRandom->Gaus(2,.5),gRandom->Gaus(2,.5));
   }
   TCanvas* c1 = new TCanvas("Test", "Test", 800, 800);
   h2->SetLineColorAlpha(22,0.);
   h2->SetContour(60);
   h2->SetContourLevel(0, 0.1);
   h1->Draw("LEGO2Z0");

   TPad *pad2 = new TPad("pad2","",0,0,1,1);
   ci = 1181;
   color = new TColor(ci, 0.52, 0.76, 0.64, " ", 0);
   pad2->SetFillColor(ci);
   pad2->SetFillStyle(4000);
   pad2->SetBorderMode(0);
   pad2->SetBorderSize(2);
   pad2->SetFrameFillColor(0);
   pad2->SetFrameBorderMode(0);
   pad2->SetFrameFillColor(0);
   pad2->Draw();
   pad2->cd();
   h2->Draw("SURF3 FB BB A");
}

may be the macro can be simplified a bit …