Set real aspect ratio of TH2 or TGraph

This is a follow-up op Resize canvas to get real aspect ratio of TH2 or TGraph

When you have a TH2, TGraph or TGraph2D where your x and y axes are in the same units (e.g. both MeV, or both mm, or both in ns), you normally want to get a real aspect ratio in your monitor or canvas. In other words, you want that the unit length of each axis occupies the same number of pixels, so that the representation on your monitor is proportional to reality and not distorted.

I wrote a simple function to account for this resizing of your canvas, so that a real aspect ratio is obtained. It does not take any extra parameters, as the length of each axes is automatically calculated, and the canvas is resized so that the TFrame (inset within the canvas) ratio equals the axes length ratio.

It would be nice if it could be implemented as TCanvas::SetRealAspectRatio(const Int_t axis=1);

// Run with       root -l -n test_ratio.cpp+
// Alternatively: root -l -n test_ratio.cpp+ -b -q

#include "TCanvas.h"
#include "TH2F.h"
#include "TF2.h"
#include "TROOT.h"
#include "TFrame.h"
#include "TMath.h"

/**
 * \brief Function to resize a canvas so that a 2D histogram or TGraph / TGraph2D are shown in real aspect ratio
 * \param c pointer to a TCanvas
 * \param axis 1 for resizing horizontally (x-axis) in order to get real aspect ratio, 2 for the resizing vertically (y-axis)
 * \return false if error is encountered, true otherwise
 * \note For defining the concept real aspect ratio, it is assumed that x and y axes are in same units, e.g. both in MeV or both in ns
 * \note You can resize either the the width of the canvas or the height, but not both at the same time
 * \note Resize the canvas before calling this function, if you want a larger height or width of reference if you call with parameter axis= 1 or 2, respectively
 * \note Call this function AFTER drawing AND zooming (SetUserRange) your TGraph or Histogram, otherwise it cannot infer your actual axes lengths
 * \note This function ensures that the TFrame has a real aspect ratio, this does not mean that the full pad (i.e. the canvas or png output) including margins has exactly the same ratio
 * \note This function does not work if canvas is divided in several subpads
 * \see https://root-forum.cern.ch/t/resize-canvas-to-get-real-aspect-ratio-of-th2-or-tgraph/20718/1
 */
bool SetRealAspectRatio(TCanvas* const c, const Int_t axis = 1)
{
	if(!c)
	{
		cout << "Error in SetRealAspectRatio: canvas is NULL";
		return false;
	}
	
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get x-y coordinates at the edges of the canvas (extrapolating outside the axes, NOT at the edges of the histogram)
		const Double_t x1 = c->GetX1();
		const Double_t y1 = c->GetY1();
		const Double_t x2 = c->GetX2();
		const Double_t y2 = c->GetY2();
		
		//Get the length of extrapolated x and y axes
		const Double_t xlength2 = x2 - x1;
		const Double_t ylength2 = y2 - y1;
		const Double_t ratio2 = xlength2/ylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << "WindX\tWindY\tCanvX\tCanvY\tx1\ty1\tx2\ty2\tratiox/y\tCanvX/CanvY" << endl;
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << x1 << "\t" << y1 << "\t" << x2 << "\t" << y2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tOriginal Canvas" << endl;
		
		if(axis==1)
		{
			c->SetCanvasSize(TMath::Nint(npy*ratio2), npy);
			c->SetWindowSize((bnpx-npx)+TMath::Nint(npy*ratio2), bnpy);
		}
		else if(axis==2)
		{
			c->SetCanvasSize(npx, TMath::Nint(npx/ratio2));
			c->SetWindowSize(bnpx, (bnpy-npy)+TMath::Nint(npx/ratio2));
		}
		else
		{
			cout << "Error in SetRealAspectRatio: axis value " << axis << " is neither 1 (resize along x-axis) nor 2 (resize along y-axis).";
			return false;
		}
	}
	
	//Check now that resizing has worked
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get x-y coordinates at the edges of the canvas (extrapolating outside the axes, NOT at the edges of the histogram)
		const Double_t x1 = c->GetX1();
		const Double_t y1 = c->GetY1();
		const Double_t x2 = c->GetX2();
		const Double_t y2 = c->GetY2();
		
		//Get the length of extrapolated x and y axes
		const Double_t xlength2 = x2 - x1;
		const Double_t ylength2 = y2 - y1;
		const Double_t ratio2 = xlength2/ylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << x1 << "\t" << y1 << "\t" << x2 << "\t" << y2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tModified Canvas" << endl;
		
		//Check accuracy +/-1 pixel due to rounding
		if(abs(TMath::Nint(npy*ratio2) - npx)<2)
		{
			cout << "Resizing finished successfully." << endl;
			return true;
		}
		else
		{
			cout << "Resizing failed." << endl;
			return false;
		}
	}
	// References:
	// https://root.cern.ch/root/roottalk/roottalk01/3676.html
	// https://root-forum.cern.ch/t/making-the-both-axes-square-on-the-pad/4325/1
}

void test_ratio()
{
   TF2 *xyg = new TF2("xyg","xygaus",0,10,0,10);
   xyg->SetParameters(1,4.5,0.5,-4.5,0.5);  //amplitude, meanx,sigmax,meany,sigmay
   
   TCanvas* c = new TCanvas("c","c");
   c->SetGridx();
   c->SetGridy();
   TH2* h2 = new TH2F("h2","h2", 50,0,10,  100,-8,-1);
   h2->GetXaxis()->SetTitle("x / mm");
   h2->GetYaxis()->SetTitle("y / mm");
   h2->GetXaxis()->SetRangeUser(3,6);
   h2->GetYaxis()->SetRangeUser(-7,-2);
   h2->FillRandom("xyg",1000000);
   h2->Draw("COLZ");
   c->SaveAs("test_ratio_default.png");
   
   //Resize canvas horizontally
   SetRealAspectRatio(c);
   c->SaveAs("test_ratio_real_x.png");
   
   //Resize again, now vertically, it should have no effect, as it is already real aspect ratio
   SetRealAspectRatio(c,2);
   c->SaveAs("test_ratio_real_x_y.png");
   
   //Resize only vertically, i.e. generate a new canvas and do not apply first resizing along x
   c = new TCanvas("cy","c");
   c->SetGridx();
   c->SetGridy();
   h2 = new TH2F("hy","h2", 50,0,10,  100,-8,-1);
   h2->GetXaxis()->SetTitle("x / mm");
   h2->GetYaxis()->SetTitle("y / mm");
   h2->GetXaxis()->SetRangeUser(3,6);
   h2->GetYaxis()->SetRangeUser(-7,-2);
   h2->FillRandom("xyg",1000000);
   h2->Draw("COLZ");
   SetRealAspectRatio(c,2);
   c->SaveAs("test_ratio_real_y.png");
}








1 Like

I provide now an extended version, including an extra function to resize your canvas in order to obtain not a real, but a custom desired aspect ratio of your axes (a screen pixel ratio, i.e. units and numbers of your axes are completely ignored).

Implementation as

c->SetFrameAspectRatio(const Double_t frameRatio, const Int_t axis=1);

would be nice.

// Run with       root -l -n test_ratio.cpp+
// Alternatively: root -l -n test_ratio.cpp+ -b -q

#include "TCanvas.h"
#include "TH2F.h"
#include "TF2.h"
#include "TROOT.h"
#include "TFrame.h"
#include "TMath.h"

Int_t countpads(TVirtualPad *pad) {
   //count the number of pads in pad
   if (!pad) return 0;
   Int_t npads = 0;
   TObject *obj;
   TIter next(pad->GetListOfPrimitives());
   while ((obj = next())) {
      if (obj->InheritsFrom(TVirtualPad::Class())) npads++;
   }
   return npads;
   //Reference https://root.cern.ch/root/roottalk/roottalk02/0654.html
}

/**
 * \brief Function to resize a canvas so that a 2D histogram or TGraph / TGraph2D are shown in real aspect ratio
 * \param c pointer to a TCanvas
 * \param axis 1 for resizing horizontally (x-axis) in order to get real aspect ratio, 2 for the resizing vertically (y-axis)
 * \return false if error is encountered, true otherwise
 * \note For defining the concept real aspect ratio, it is assumed that x and y axes are in same units, e.g. both in MeV or both in ns
 * \note You can resize either the the width of the canvas or the height, but not both at the same time
 * \note Resize the canvas before calling this function, if you want a larger height or width of reference if you call with parameter axis= 1 or 2, respectively
 * \note Call this function AFTER drawing AND zooming (SetUserRange) your TGraph or Histogram, otherwise it cannot infer your actual axes lengths
 * \note This function ensures that the TFrame has a real aspect ratio, this does not mean that the full pad (i.e. the canvas or png output) including margins has exactly the same ratio
 * \note This function does not work if canvas is divided in several subpads
 * \note Still to implement, if your CRT screen has non-square pixels, then the function does not work. One should specify then the screen pixel ratio (pixelSizeY / pixelSizeX), and the function returns a correctly resized canvas so that the aspect ratio is real in your current screen. See https://en.wikipedia.org/wiki/Pixel_aspect_ratio
 * \see https://root-forum.cern.ch/t/resize-canvas-to-get-real-aspect-ratio-of-th2-or-tgraph/20718/1
 */
bool SetRealAspectRatio(TCanvas* const c, const Int_t axis = 1/*, const Double_t screenPixelRatio=1.*/)
{
	if(!c)
	{
		cout << "Error in SetRealAspectRatio: canvas is NULL" << endl;
		return false;
	}
	
	if(countpads(c)>0)
	{
		cout << "Error in SetRealAspectRatio: function not implemented yet for multiple subpads." << endl;
		return false;
	}
	
	
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get x-y coordinates at the edges of the canvas (extrapolating outside the axes, NOT at the edges of the histogram)
		const Double_t x1 = c->GetX1();
		const Double_t y1 = c->GetY1();
		const Double_t x2 = c->GetX2();
		const Double_t y2 = c->GetY2();
		
		//Get the length of extrapolated x and y axes
		const Double_t xlength2 = x2 - x1;
		const Double_t ylength2 = y2 - y1;
		const Double_t ratio2 = xlength2/ylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << "WindX\tWindY\tCanvX\tCanvY\tx1\ty1\tx2\ty2\tratiox/y\tCanvX/CanvY" << endl;
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << x1 << "\t" << y1 << "\t" << x2 << "\t" << y2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tOriginal Canvas" << endl;
		
		if(axis==1)
		{
			c->SetCanvasSize(TMath::Nint(npy*ratio2), npy);
			c->SetWindowSize((bnpx-npx)+TMath::Nint(npy*ratio2), bnpy);
		}
		else if(axis==2)
		{
			c->SetCanvasSize(npx, TMath::Nint(npx/ratio2));
			c->SetWindowSize(bnpx, (bnpy-npy)+TMath::Nint(npx/ratio2));
		}
		else
		{
			cout << "Error in SetRealAspectRatio: axis value " << axis << " is neither 1 (resize along x-axis) nor 2 (resize along y-axis).";
			return false;
		}
	}
	
	//Check now that resizing has worked
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get x-y coordinates at the edges of the canvas (extrapolating outside the axes, NOT at the edges of the histogram)
		const Double_t x1 = c->GetX1();
		const Double_t y1 = c->GetY1();
		const Double_t x2 = c->GetX2();
		const Double_t y2 = c->GetY2();
		
		//Get the length of extrapolated x and y axes
		const Double_t xlength2 = x2 - x1;
		const Double_t ylength2 = y2 - y1;
		const Double_t ratio2 = xlength2/ylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << x1 << "\t" << y1 << "\t" << x2 << "\t" << y2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tModified Canvas" << endl;
		
		//Check accuracy +/-1 pixel due to rounding
		if(abs(TMath::Nint(npy*ratio2) - npx)<2)
		{
			cout << "Resizing finished successfully." << endl;
			return true;
		}
		else
		{
			cout << "Resizing failed." << endl;
			return false;
		}
	}
	// References:
	// https://root.cern.ch/root/roottalk/roottalk01/3676.html
	// https://root-forum.cern.ch/t/making-the-both-axes-square-on-the-pad/4325/1
}

/**
 * \brief Function to resize a canvas so that a 2D histogram or TGraph / TGraph2D (i.e. the) are shown in a custom ratio
 * \param c pointer to a TCanvas
 * \param frameRatio the desired size of y axis in pixels divided by size of the x axis in pixels
 * \param axis 1 for resizing horizontally (x-axis) in order to get real aspect ratio, 2 for the resizing vertically (y-axis)
 * \return false if error is encountered, true otherwise
 * \note For defining the concept frame aspect ratio, you refer to the length of x and y axes in pixels, independently of their units
 * \note You can resize either the the width of the canvas or the height, but not both at the same time
 * \note Resize the canvas before calling this function, if you want a larger height or width of reference if you call with parameter axis= 1 or 2, respectively
 * \note Call this function AFTER drawing AND zooming (SetUserRange) your TGraph or Histogram, otherwise it cannot infer your actual axes lengths
 * \note This function ensures that the TFrame has the specified ratio, this does not mean that the full pad (i.e. the canvas or png output) including margins has exactly the same ratio
 * \note This function does not work if canvas is divided in several subpads
 * \note Beware: this function does not take into account your axes units and numbers. It just takes into account the screen pixels!
 * \see https://root-forum.cern.ch/t/resize-canvas-to-get-real-aspect-ratio-of-th2-or-tgraph/20718/1
 */
bool SetFrameAspectRatio(TCanvas* const c, const Double_t frameRatio, const Int_t axis = 1)
{
	if(!c)
	{
		cout << "Error in SetFrameAspectRatio: canvas is NULL" << endl;
		return false;
	}
	
	if(countpads(c)>0)
	{
		cout << "Error in SetFrameAspectRatio: function not implemented yet for multiple subpads." << endl;
		return false;
	}
	
	
	
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get pixel coordinates at the edges of the TFrame (figure axes)
		const Int_t nx1 = c->XtoPixel(xmin);
		const Int_t ny1 = c->YtoPixel(ymin);
		const Int_t nx2 = c->XtoPixel(xmax);
		const Int_t ny2 = c->YtoPixel(ymax);
		
		//Get the length of x and y axes in pixels
		const Int_t nxlength2 = abs(nx2 - nx1);
		const Int_t nylength2 = abs(ny2 - ny1);
		const Double_t ratio2 = (Double_t)nxlength2/nylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << "WindX\tWindY\tCanvX\tCanvY\tx1\ty1\tx2\ty2\tratiox/y\tCanvX/CanvY" << endl;
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << nx1 << "\t" << ny1 << "\t" << nx2 << "\t" << ny2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tOriginal Canvas" << endl;
		
		if(axis==1)
		{
			c->SetCanvasSize(TMath::Nint(npx/frameRatio/ratio2), npy);
			c->SetWindowSize((bnpx-npx)+TMath::Nint(npx/frameRatio/ratio2), bnpy);
		}
		else if(axis==2)
		{
			c->SetCanvasSize(npx, TMath::Nint(npy*frameRatio*ratio2));
			c->SetWindowSize(bnpx, (bnpy-npy)+TMath::Nint(npy*frameRatio*ratio2));
		}
		else
		{
			cout << "Error in SetFrameAspectRatio: axis value " << axis << " is neither 1 (resize along x-axis) nor 2 (resize along y-axis).";
			return false;
		}
	}
	
	//Check now that resizing has worked
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get pixel coordinates at the edges of the TFrame (figure axes)
		const Int_t nx1 = c->XtoPixel(xmin);
		const Int_t ny1 = c->YtoPixel(ymin);
		const Int_t nx2 = c->XtoPixel(xmax);
		const Int_t ny2 = c->YtoPixel(ymax);
		
		//Get the length of x and y axes in pixels
		const Int_t nxlength2 = abs(nx2 - nx1);
		const Int_t nylength2 = abs(ny2 - ny1);
		const Double_t ratio2 = (Double_t)nxlength2/nylength2;
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << nx1 << "\t" << ny1 << "\t" << nx2 << "\t" << ny2 << "\t" << ratio2 << "\t" << (double)npx/npy << "\tModified Canvas" << endl;
		
		//Check accuracy +/-1 pixel due to rounding
		if(abs(TMath::Nint(nxlength2*frameRatio) - nylength2)<2)
		{
			cout << "Resizing finished successfully." << endl;
			return true;
		}
		else
		{
			cout << "Resizing failed." << " " << nxlength2 << " " << nylength2 << endl;
			return false;
		}
	}
	// References:
	// https://root.cern.ch/root/roottalk/roottalk01/3676.html
	// https://root-forum.cern.ch/t/making-the-both-axes-square-on-the-pad/4325/1
}


void test_ratio()
{
   TF2 *xyg = new TF2("xyg","xygaus",0,10,0,10);
   xyg->SetParameters(1,4.5,0.5,-4.5,0.5);  //amplitude, meanx,sigmax,meany,sigmay
   
   TCanvas* c = new TCanvas("c","c");
   c->SetGridx();
   c->SetGridy();
   TH2* h2 = new TH2F("h2","h2", 50,0,10,  100,-8,-1);
   h2->GetXaxis()->SetTitle("x / mm");
   h2->GetYaxis()->SetTitle("y / mm");
   h2->GetXaxis()->SetRangeUser(3,6);
   h2->GetYaxis()->SetRangeUser(-7,-2);
   h2->FillRandom("xyg",1000000);
   h2->Draw("COLZ");
   c->SaveAs("test_ratio_default.png");
   
   SetRealAspectRatio(c);
   c->SaveAs("test_ratio_real_x.png");
   
   SetRealAspectRatio(c,2);
   c->SaveAs("test_ratio_real_x_y.png");
   
   c = new TCanvas("cy","c");
   c->SetGridx();
   c->SetGridy();
   h2 = new TH2F("hy","h2", 50,0,10,  100,-8,-1);
   h2->GetXaxis()->SetTitle("x / mm");
   h2->GetYaxis()->SetTitle("y / mm");
   h2->GetXaxis()->SetRangeUser(3,6);
   h2->GetYaxis()->SetRangeUser(-7,-2);
   h2->FillRandom("xyg",1000000);
   h2->Draw("COLZ");
   SetRealAspectRatio(c,2);
   c->SaveAs("test_ratio_real_y.png");

   c = new TCanvas("c2","c2");
   c->SetGridx();
   c->SetGridy();
   h2 = new TH2F("h2b","h2", 50,0,10,  100,-8,-1);
   h2->GetXaxis()->SetTitle("x / mm");
   h2->GetYaxis()->SetTitle("y / mm");
   h2->GetXaxis()->SetRangeUser(3,6);
   h2->GetYaxis()->SetRangeUser(-7,-2);
   h2->FillRandom("xyg",1000000);
   h2->Draw("COLZ");
   c->SetCanvasSize(472.*(6-3)/(-2-(-7)),472);
   
	//Check now that resizing did not work
	{
		//Get the current min-max values if SetUserRange has been called
		c->Update();
		const Double_t xmin = c->GetUxmin();
		const Double_t xmax = c->GetUxmax();
		const Double_t ymin = c->GetUymin();
		const Double_t ymax = c->GetUymax();

		//Get the length of zoomed x and y axes
		const Double_t xlength = xmax - xmin;
		const Double_t ylength = ymax - ymin;
		const Double_t ratio = xlength/ylength;

		//Get how many pixels are occupied by the canvas
		const Int_t npx = c->GetWw();
		const Int_t npy = c->GetWh();
		
		//Get x-y coordinates at the edges of the canvas (extrapolating outside the axes, NOT at the edges of the histogram)
		const Double_t x1 = c->GetX1();
		const Double_t y1 = c->GetY1();
		const Double_t x2 = c->GetX2();
		const Double_t y2 = c->GetY2();
		
		//Get now number of pixels including window borders
		const Int_t bnpx = c->GetWindowWidth();
		const Int_t bnpy = c->GetWindowHeight();
		
		cout << "WindX\tWindY\tCanvX\tCanvY\tx1\ty1\tx2\ty2\tratiox/y\tCanvX/CanvY" << endl;
		cout << bnpx << "\t" << bnpy << "\t" << npx << "\t" << npy << "\t" << x1 << "\t" << y1 << "\t" << x2 << "\t" << y2 << "\t" << ratio << "\t" << (double)npx/npy << "\tModified Canvas" << endl;
		
		//Check accuracy +/-1 pixel due to rounding
		if(abs(TMath::Nint(npy*ratio) - npx)<2)
		{
			cout << "Resizing finished successfully." << endl;
		}
		else
		{
			cout << "Resizing failed." << endl;
		}
	}
	c->SaveAs("test_ratio_incorrect.png");
	
	c = new TCanvas("c3","c3");
	c->SetGridx();
	c->SetGridy();
	h2 = new TH2F("h3","h3", 50,0,10,  100,-8,-1);
	h2->GetXaxis()->SetTitle("x / mm");
	h2->GetYaxis()->SetTitle("y / mm");
	h2->GetXaxis()->SetRangeUser(3,6);
	h2->GetYaxis()->SetRangeUser(-7,-2);
	h2->FillRandom("xyg",1000000);
	h2->Draw("COLZ");
	SetFrameAspectRatio(c,2.0);
	c->SaveAs("test_ratio_frame_2_x.png");
	
	c = new TCanvas("c4","c4");
	c->SetGridx();
	c->SetGridy();
	h2 = new TH2F("h4","h3", 50,0,10,  100,-8,-1);
	h2->GetXaxis()->SetTitle("x / mm");
	h2->GetYaxis()->SetTitle("y / mm");
	h2->GetXaxis()->SetRangeUser(3,6);
	h2->GetYaxis()->SetRangeUser(-7,-2);
	h2->FillRandom("xyg",1000000);
	h2->Draw("COLZ");
	SetFrameAspectRatio(c,2.0,2);
	c->SaveAs("test_ratio_frame_2_y.png");
}

I have added a guard for multiple subpads. One may think about resizing the TFrame in these cases instead of the TCanvas itself, and checking that the new TFrame is contained within the subpad.




Cool thanks for providing this code. I don’t like the name “real” aspect ratio though, because even if the aspect ratio in pixels is correct, the pixels might not be square on the screen, especially on CRTs.

Good point. Maybe we can add an additional parameter, Double_t pixelRatio=1., so that a user can specify if his CRT monitor has not square pixels, and then gets a real aspect ratio image in it. Of course, he should know in advance what his monitor pixelRatio is (pixelSizeY / pixelSizeX).

See also
https://en.wikipedia.org/wiki/Pixel_aspect_ratio

I don’t think it needs an extra parameter, I only suggest avoiding the term “Real” aspect ratio because it’s less precise than it sounds.

Thanks Fernando, for this useful code.
Hope the ROOT maintainers find some standard way to pack this functionality into the framework. :smiley:

What about “SetProportionalAspectRatio” ?

I added the method TCanvas::SetRealAspectRatio in master.

2 Likes

@ferhue: If you think more should be added do not hesitate to propose a PR.

Would you add the same PR for 3D plots?

I am not sure how that would work for 3D plots.

Maybe there exist some Matlab / Matplotlib features which one could “reproduce” in ROOT?