From 2a4949fe8310813569762e8299c521901110c10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Lalik?= Date: Tue, 28 Oct 2025 00:31:37 +0100 Subject: [PATCH 1/5] [graf2d] allow zero outer margins when dividing canvas When nx or ny is negative, the pads is divided according to abs(nx) or abs(ny), but the outer margins is set to 0. The inner margins are equal to xmargin and ymargin, respectively. --- graf2d/gpad/src/TPad.cxx | 56 +++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index 2f046520d815f..b52a15ed22cbe 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -1298,30 +1298,44 @@ 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; + + // Outer margins, equal to half of the inner margin. Set to zero if nx or ny are negative. + Float_t xmargin_outer = xmargin; + Float_t ymargin_outer = ymargin; + + if (nx < 0) { + xmargin_outer = 0.0; + nx = -nx; + } + + if (ny < 0) { + ymargin_outer = 0.0; + ny = -ny; + } + 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 y2) continue; - for (ix=0;ix x2) continue; 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(); } @@ -1340,23 +1354,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;iSetNumber(number); pad->SetBorderMode(0); if (i == 0) pad->SetLeftMargin(xl*nx); From 3c5ee0ebdbf0dd66880ed6c4915de268a0c4c695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Lalik?= Date: Sat, 1 Nov 2025 03:43:03 +0100 Subject: [PATCH 2/5] Redesign on TPad::Divide() With this design pad is divided into subpads respecting the pad margins (old behavior: outer margins were equal to x/y margin). The x/y margin argument of the TPad::Divide is now a distance between subpads (old behavior: distance between pads was 2* x/y margin). This design allows to control the exact layout of subpads. This change will break existing code. If the old code was: c->Divide(3, 2, 0.02, 0.01); new code must be: c->SetLeftMargin(0.02); // old xmargin c->SetBottomMargin(0.01); // old ymargin c->SetRightMargin(0.02); // old xmargin c->SetTopMargin(0.01); // old ymargin c->Divide(3, 2, 0.04, 0.02); // two times old x,y margins to achieve the same result. --- graf2d/gpad/src/TPad.cxx | 39 ++---- .../graphics/canvas_divide_example.C | 131 ++++++++++++++++++ 2 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 tutorials/visualisation/graphics/canvas_divide_example.C diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index b52a15ed22cbe..c8ac6b4abdc67 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -1303,36 +1303,29 @@ void TPad::Divide(Int_t nx, Int_t ny, Float_t xmargin, Float_t ymargin, Int_t co if (ny == 0) ny = 1; - // Outer margins, equal to half of the inner margin. Set to zero if nx or ny are negative. - Float_t xmargin_outer = xmargin; - Float_t ymargin_outer = ymargin; - - if (nx < 0) { - xmargin_outer = 0.0; - nx = -nx; - } - - if (ny < 0) { - ymargin_outer = 0.0; - ny = -ny; - } + 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 - auto dx = (1 - 2 * xmargin * (nx - 1) - 2 * xmargin_outer) / nx; // width of a subpad - auto dy = (1 - 2 * ymargin * (ny - 1) - 2 * ymargin_outer) / ny; // height of a subpad + 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 - iy * dy - 2 * ymargin * iy - ymargin_outer; + auto y2 = 1 - yt - iy * (dy + ymargin); auto y1 = y2 - dy; - if (y1 < 0) y1 = 0; - if (y1 > y2) continue; + if (y1 < yb) + y1 = yb; for (auto ix = 0; ix < nx; ix++) { - auto x1 = ix * dx + 2 * xmargin * ix + xmargin_outer; + auto x1 = xl + ix * (dx + xmargin); auto x2 = x1 + dx; - if (x1 > x2) continue; + if (x2 > (1 - xr)) + xr = 1 - xr; n++; name.Form("%s_%d", GetName(), n); auto pad = new TPad(name.Data(), name.Data(), x1, y1, x2, y2, color); @@ -1342,10 +1335,6 @@ void TPad::Divide(Int_t nx, Int_t ny, Float_t xmargin, Float_t ymargin, Int_t co } } 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; diff --git a/tutorials/visualisation/graphics/canvas_divide_example.C b/tutorials/visualisation/graphics/canvas_divide_example.C new file mode 100644 index 0000000000000..9b5a0c3c7329f --- /dev/null +++ b/tutorials/visualisation/graphics/canvas_divide_example.C @@ -0,0 +1,131 @@ +// variants: 0 - custom values +// 1 - default values +// 2+ - old default values +void canvas_divide_example(int use_variant = 0) +{ + auto wx = 600; // width and heigh + auto wy = 400; + + auto nx = 3; // top-level pad division + auto ny = 2; + + auto ml = 0.30; // top-level pad margins + auto mb = 0.10; + auto mr = 0.05; + auto mt = 0.10; + + auto c = new TCanvas("canvas_divide", "canvas_divide", wx, wy); + c->SetFillColor(19); + + if (use_variant == 0) { + c->SetLeftMargin(ml); + c->SetBottomMargin(mb); + c->SetRightMargin(mr); + c->SetTopMargin(mt); + + c->Divide(nx, ny, 0.03, 0.05, 46); + } else if (use_variant == 1) { + c->Divide(nx, ny, 0.01, 0.01, 46); + } else { + c->SetLeftMargin(0.01); + c->SetBottomMargin(0.01); + c->SetRightMargin(0.01); + c->SetTopMargin(0.01); + + c->Divide(nx, ny, 0.02, 0.02, 46); + } + + ml = c->GetLeftMargin(); + mb = c->GetBottomMargin(); + mr = c->GetRightMargin(); + 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); + + if (use_variant == 0) { + text.DrawText(0.01, 0.90, "c->SetLeftMargin(ml);"); + text.DrawText(0.01, 0.85, "c->SetBottomMargin(mb);"); + text.DrawText(0.01, 0.80, "c->SetRightMargin(mr);"); + text.DrawText(0.01, 0.75, "c->SetTopMargin(mt);"); + text.DrawText(0.01, 0.70, "c->Divide(nx, ny, xm, ym);"); + } +} From 94c3c4ad1efb11a7e176e97b062819e41b711c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Lalik?= Date: Sat, 1 Nov 2025 04:14:32 +0100 Subject: [PATCH 3/5] Update and fix documentation. --- .../doxygen/images/canvas_divide.png | Bin 0 -> 11212 bytes graf2d/gpad/src/TPad.cxx | 20 ++++++++++++----- .../graphics/canvas_divide_example.C | 21 ++++-------------- 3 files changed, 18 insertions(+), 23 deletions(-) create mode 100644 documentation/doxygen/images/canvas_divide.png diff --git a/documentation/doxygen/images/canvas_divide.png b/documentation/doxygen/images/canvas_divide.png new file mode 100644 index 0000000000000000000000000000000000000000..41a31592823a2420336a39b17d5cc3a22aa8699d GIT binary patch literal 11212 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 literal 0 HcmV?d00001 diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index c8ac6b4abdc67..be5b96c6c3883 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -1225,13 +1225,12 @@ 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. /// /// Pads are automatically named `canvasname_n` where `n` is the division number /// starting from top left pad. @@ -1240,6 +1239,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). diff --git a/tutorials/visualisation/graphics/canvas_divide_example.C b/tutorials/visualisation/graphics/canvas_divide_example.C index 9b5a0c3c7329f..adceab3eb1bda 100644 --- a/tutorials/visualisation/graphics/canvas_divide_example.C +++ b/tutorials/visualisation/graphics/canvas_divide_example.C @@ -18,20 +18,12 @@ void canvas_divide_example(int use_variant = 0) c->SetFillColor(19); if (use_variant == 0) { - c->SetLeftMargin(ml); - c->SetBottomMargin(mb); - c->SetRightMargin(mr); - c->SetTopMargin(mt); - + c->SetMargin(ml, mr, mb, mt); c->Divide(nx, ny, 0.03, 0.05, 46); } else if (use_variant == 1) { c->Divide(nx, ny, 0.01, 0.01, 46); } else { - c->SetLeftMargin(0.01); - c->SetBottomMargin(0.01); - c->SetRightMargin(0.01); - c->SetTopMargin(0.01); - + c->SetMargin(0.01, 0.01, 0.01, 0.01); c->Divide(nx, ny, 0.02, 0.02, 46); } @@ -121,11 +113,6 @@ void canvas_divide_example(int use_variant = 0) text.SetTextFont(102); text.SetNDC(1); - if (use_variant == 0) { - text.DrawText(0.01, 0.90, "c->SetLeftMargin(ml);"); - text.DrawText(0.01, 0.85, "c->SetBottomMargin(mb);"); - text.DrawText(0.01, 0.80, "c->SetRightMargin(mr);"); - text.DrawText(0.01, 0.75, "c->SetTopMargin(mt);"); - text.DrawText(0.01, 0.70, "c->Divide(nx, ny, xm, ym);"); - } + text.DrawText(0.01, 0.97, "c->SetMargin(ml, mr, mb, mt);"); + text.DrawText(0.01, 0.94, "c->Divide(nx, ny, xm, ym);"); } From f1af6a495ff8a65a5b4c3506a66cfb2b80132950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Lalik?= Date: Wed, 5 Nov 2025 22:54:01 +0100 Subject: [PATCH 4/5] Add note about visual perception of pads spacing with empty backgrounds --- graf2d/gpad/src/TPad.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index be5b96c6c3883..999425a0389de 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -1232,6 +1232,10 @@ Int_t TPad::DistancetoPrimitive(Int_t px, Int_t py) /// - 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. /// From 0d336ef6c9b063c9eaecfe1994212878f4d75a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Lalik?= Date: Thu, 6 Nov 2025 08:40:51 +0100 Subject: [PATCH 5/5] Update example macro documentation --- .../graphics/canvas_divide_example.C | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/tutorials/visualisation/graphics/canvas_divide_example.C b/tutorials/visualisation/graphics/canvas_divide_example.C index adceab3eb1bda..acc8d00d7b94b 100644 --- a/tutorials/visualisation/graphics/canvas_divide_example.C +++ b/tutorials/visualisation/graphics/canvas_divide_example.C @@ -1,36 +1,60 @@ -// variants: 0 - custom values -// 1 - default values -// 2+ - old default values +/// \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) { - auto wx = 600; // width and heigh - auto wy = 400; + const auto nx = 3; // top-level pad division + const auto ny = 2; - auto nx = 3; // top-level pad division - auto ny = 2; - - auto ml = 0.30; // top-level pad margins - auto mb = 0.10; - auto mr = 0.05; - auto mt = 0.10; - - auto c = new TCanvas("canvas_divide", "canvas_divide", wx, wy); + auto c = new TCanvas("canvas_divide", "canvas_divide", 600, 400); c->SetFillColor(19); if (use_variant == 0) { - c->SetMargin(ml, mr, mb, mt); + 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 { - c->SetMargin(0.01, 0.01, 0.01, 0.01); - c->Divide(nx, ny, 0.02, 0.02, 46); + 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); } - ml = c->GetLeftMargin(); - mb = c->GetBottomMargin(); - mr = c->GetRightMargin(); - mt = c->GetTopMargin(); + // 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);