Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added documentation/doxygen/images/canvas_divide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 50 additions & 35 deletions graf2d/gpad/src/TPad.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1225,13 +1225,16 @@ Int_t TPad::DistancetoPrimitive(Int_t px, Int_t py)
/// Automatic pad generation by division.
///
/// - The current canvas is divided in nx by ny equal divisions (pads).
/// - xmargin defines the horizontal spacing around each pad as a percentage of the canvas
/// width. Therefore, the distance between two adjacent pads along the x-axis is equal
/// to twice the xmargin value.
/// - ymargin defines the vertical spacing around each pad as a percentage of the canvas
/// height. Therefore, the distance between two adjacent pads along the y-axis is equal
/// to twice the ymargin value.
/// - xmargin defines the horizontal spacing between each pad as a percentage of the canvas
/// width.
/// - ymargin defines the vertical spacing between each pad as a percentage of the canvas
/// height.
/// - color is the color of the new pads. If 0, color is the canvas color.
/// - All pads are contained within the inner area defined by the canvas margins.
///
/// Note that, if you don't have a background color of your pad, the spacing between pads
/// might look larger than specified, since in the default case, each pad has internally
/// an empty space on the right equal to the space filled on the left for the y axis labels.
///
/// Pads are automatically named `canvasname_n` where `n` is the division number
/// starting from top left pad.
Expand All @@ -1240,6 +1243,15 @@ Int_t TPad::DistancetoPrimitive(Int_t px, Int_t py)
///
/// \image html gpad_pad3.png
///
/// Example if:
/// /// ~~~ {.cpp}
/// c->SetMargin(0.30, 0.05, 0.10, 0.10);
/// c->Divide(nx, ny, 0.03, 0.05, 46);
/// ~~~
/// \image html canvas_divide.png
///
/// More examples are in `tutorials/visualisation/graphics/canvas_divide_example.C`
///
/// Once a pad is divided into sub-pads, one can set the current pad
/// to a subpad with a given division number as illustrated above
/// with TPad::cd(subpad_number).
Expand Down Expand Up @@ -1298,40 +1310,43 @@ void TPad::Divide(Int_t nx, Int_t ny, Float_t xmargin, Float_t ymargin, Int_t co
TContext ctxt(kTRUE);

cd();
if (nx <= 0) nx = 1;
if (ny <= 0) ny = 1;
Int_t ix, iy;
Double_t x1, y1, x2, y2, dx, dy;
TPad *pad;
if (nx == 0)
nx = 1;
if (ny == 0)
ny = 1;

Double_t xl = GetLeftMargin();
Double_t xr = GetRightMargin();
Double_t yb = GetBottomMargin();
Double_t yt = GetTopMargin();

TString name, title;
Int_t n = 0;
if (color == 0) color = GetFillColor();
if (xmargin >= 0 && ymargin >= 0) {
//general case
dy = 1/Double_t(ny);
dx = 1/Double_t(nx);
for (iy=0;iy<ny;iy++) {
y2 = 1 - iy*dy - ymargin;
y1 = y2 - dy + 2*ymargin;
if (y1 < 0) y1 = 0;
if (y1 > y2) continue;
for (ix=0;ix<nx;ix++) {
x1 = ix*dx + xmargin;
x2 = x1 +dx -2*xmargin;
if (x1 > x2) continue;
auto dx = (1 - xl - xr - xmargin * (nx - 1)) / nx; // width of a subpad
auto dy = (1 - yt - yb - ymargin * (ny - 1)) / ny; // height of a subpad

Int_t n = 0;
for (auto iy = 0; iy < ny; iy++) {
auto y2 = 1 - yt - iy * (dy + ymargin);
auto y1 = y2 - dy;
if (y1 < yb)
y1 = yb;
for (auto ix = 0; ix < nx; ix++) {
auto x1 = xl + ix * (dx + xmargin);
auto x2 = x1 + dx;
if (x2 > (1 - xr))
xr = 1 - xr;
n++;
name.Form("%s_%d", GetName(), n);
pad = new TPad(name.Data(), name.Data(), x1, y1, x2, y2, color);
auto pad = new TPad(name.Data(), name.Data(), x1, y1, x2, y2, color);
pad->SetNumber(n);
pad->Draw();
}
}
} else {
// special case when xmargin < 0 or ymargin < 0
Double_t xl = GetLeftMargin();
Double_t xr = GetRightMargin();
Double_t yb = GetBottomMargin();
Double_t yt = GetTopMargin();
xl /= (1-xl+xr)*nx;
xr /= (1-xl+xr)*nx;
yb /= (1-yb+yt)*ny;
Expand All @@ -1340,23 +1355,23 @@ void TPad::Divide(Int_t nx, Int_t ny, Float_t xmargin, Float_t ymargin, Int_t co
SetRightMargin(xr);
SetBottomMargin(yb);
SetTopMargin(yt);
dx = (1-xl-xr)/nx;
dy = (1-yb-yt)/ny;
auto dx = (1 - xl - xr) / nx;
auto dy = (1 - yb - yt) / ny;
Int_t number = 0;
for (Int_t i=0;i<nx;i++) {
x1 = i*dx+xl;
x2 = x1 + dx;
auto x1 = i * dx + xl;
auto x2 = x1 + dx;
if (i == 0) x1 = 0;
if (i == nx-1) x2 = 1-xr;
for (Int_t j=0;j<ny;j++) {
number = j*nx + i +1;
y2 = 1 -j*dy -yt;
y1 = y2 - dy;
auto y2 = 1 - j * dy - yt;
auto y1 = y2 - dy;
if (j == 0) y2 = 1-yt;
if (j == ny-1) y1 = 0;
name.Form("%s_%d", GetName(), number);
title.Form("%s_%d", GetTitle(), number);
pad = new TPad(name.Data(), title.Data(), x1, y1, x2, y2, color);
auto pad = new TPad(name.Data(), title.Data(), x1, y1, x2, y2, color);
pad->SetNumber(number);
pad->SetBorderMode(0);
if (i == 0) pad->SetLeftMargin(xl*nx);
Expand Down
142 changes: 142 additions & 0 deletions tutorials/visualisation/graphics/canvas_divide_example.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/// \file
/// \ingroup tutorial_graphics
/// \notebook -js
/// \preview Example of canvas division into subpads
///
/// ROOT 6.40.0 changed how the canvas are divided into subpads. Before, the TPad::Divide
/// with typical arguments (positive `xmargin` and `ymargin` values) function was not respecting
/// canvas own margins for the inner area. This limited flexibility of defining canvas layout.
///
/// As it was changed and fixed in 6.40.0, this macro demonstrates how the new TPad::Divide function
/// works, what are the current default values, and how to reproduce the old behaviour.
///
/// This example can be run with optional argument (default is 0):
/// * 0 - will demonstrate current custom layout behaviour,
/// * 1 - will show default values (starting from 6.40.0) where the canvas margins are respected
/// and the subpad canvas are customised (the default values are put explicitly because we
/// also want to modify the pads background colour for better visibility of the layout),
/// * 2 (or anything else than 0, 1) - will restore old default values (for root before 6.40.0).
/// Note that the `xmargin` and `ymargin` are doubled in TPad::Divide call in respect to the
/// old defaults, and the canvas margins also must be modified.
/// ~~~{.cpp}
/// const auto xmargin = 0.01; // old default xmargin
/// const auto ymargin = 0.01; // old default ymargin
/// c->SetMargin(xmargin, xmargin, ymargin, ymargin);
/// c->Divide(nx, ny, 2 * xmargin, 2 * ymargin, 46);
/// ~~~
///
/// \macro_image
/// \macro_code
///
/// \author Rafał Lalik
void canvas_divide_example(int use_variant = 0)
{
const auto nx = 3; // top-level pad division
const auto ny = 2;

auto c = new TCanvas("canvas_divide", "canvas_divide", 600, 400);
c->SetFillColor(19);

if (use_variant == 0) {
c->SetMargin(0.30, 0.05, 0.10, 0.10);
c->Divide(nx, ny, 0.03, 0.05, 46);
} else if (use_variant == 1) {
c->Divide(nx, ny, 0.01, 0.01, 46);
} else {
const auto xmargin = 0.01; // old default
const auto ymargin = 0.01; // old default
c->SetMargin(xmargin, xmargin, ymargin, ymargin);
c->Divide(nx, ny, 2 * xmargin, 2 * ymargin, 46);
}

// display layout details

const auto ml = c->GetLeftMargin();
const auto mb = c->GetBottomMargin();
const auto mr = c->GetRightMargin();
const auto mt = c->GetTopMargin();

auto h = new TH1F("", "", 100, -3.3, 3.3);
h->GetXaxis()->SetLabelFont(43);
h->GetXaxis()->SetLabelSize(12);
h->GetYaxis()->SetLabelFont(43);
h->GetYaxis()->SetLabelSize(12);
h->GetYaxis()->SetNdivisions(505);
h->SetMaximum(30 * nx * ny);
h->SetFillColor(41);

Int_t number = 0;
for (Int_t i = 0; i < nx * ny; i++) {
number++;
c->cd(number);
h->FillRandom("gaus", 1000);
h->DrawCopy();
}

c->cd();

TArrow arr;

arr.DrawArrow(0, 0.5, ml, 0.5, 0.01, "<|>");
arr.DrawArrow(0.5, 0, 0.5, mb, 0.01, "<|>");
arr.DrawArrow(1 - mr, 0.5, 1, 0.5, 0.01, "<|>");
arr.DrawArrow(0.5, 1 - mt, 0.5, 1, 0.01, "<|>");

TLatex tex_x;
tex_x.SetNDC(1);
tex_x.SetTextSize(0.03);
tex_x.SetTextAlign(12);
tex_x.SetTextAngle(90);

TLatex tex_y;
tex_y.SetNDC(1);
tex_y.SetTextSize(0.03);
tex_y.SetTextAlign(12);

tex_x.DrawLatex(ml / 2, 0.5, TString::Format(" ml = %.2f", ml));
tex_x.DrawLatex(1 - mr / 2, 0.5, TString::Format(" mr = %.2f", mr));

tex_y.DrawLatex(0.5, mb / 2, TString::Format(" mb = %.2f", mb));
tex_y.DrawLatex(0.5, 1 - mt / 2, TString::Format(" mt = %.2f", mt));

for (int i = 0; i < nx; ++i) {
for (int j = 0; j < ny; ++j) {

float x1, x2, xc, y1, y2, yc;

auto spad = c->GetPad(1 + j * nx + i); // current pad
x1 = spad->GetXlowNDC() + spad->GetWNDC();
xc = spad->GetXlowNDC() + spad->GetWNDC() / 2;
if (i < (nx - 1)) {
auto spad_nx = c->GetPad(1 + j * nx + (i + 1)); // next pad in x
x2 = spad_nx->GetXlowNDC();
}
auto xm = x2 - x1;

if (j < (ny - 1)) {
auto spad_ny = c->GetPad(1 + (j + 1) * nx + i); // next pad in y
y1 = spad_ny->GetYlowNDC() + spad->GetHNDC();
}
y2 = spad->GetYlowNDC();
yc = spad->GetYlowNDC() + spad->GetHNDC() / 2;
auto ym = y2 - y1;

if (i < (nx - 1)) {
arr.DrawArrow(x1, yc, x2, yc, 0.01, "<|>");
tex_x.DrawLatex((x1 + x2) / 2, yc, TString::Format(" xm = %.2f", xm));
}
if (j < (ny - 1)) {
arr.DrawArrow(xc, y1, xc, y2, 0.01, "<|>");
tex_y.DrawLatex(xc, (y1 + y2) / 2, TString::Format(" ym = %.2f", ym));
}
}
}

TText text;
text.SetTextSize(0.03);
text.SetTextFont(102);
text.SetNDC(1);

text.DrawText(0.01, 0.97, "c->SetMargin(ml, mr, mb, mt);");
text.DrawText(0.01, 0.94, "c->Divide(nx, ny, xm, ym);");
}
Loading