From c94ac52fa853bb6fdf6243efe3517a14d0568f51 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 16:03:08 +0200 Subject: [PATCH 1/6] [hist] use internal variable to preserve histogram draw range Instead direct usage of gPad->GetUxmin() for each histogram bin copy its value into variable. Do it in color painting where also polar coordinates are used. --- hist/histpainter/src/THistPainter.cxx | 73 +++++++++++++++------------ 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index 1a879742f0334..591bbfbdd94d9 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -5838,8 +5838,8 @@ void THistPainter::PaintColorLevels(Option_t*) // Initialize the levels on the Z axis Int_t ncolors = gStyle->GetNumberOfColors(); - Int_t ndiv = fH->GetContour(); - if (ndiv == 0 ) { + Int_t ndiv = fH->GetContour(); + if (ndiv == 0) { ndiv = gStyle->GetNumberContours(); fH->SetContour(ndiv); } @@ -5847,6 +5847,11 @@ void THistPainter::PaintColorLevels(Option_t*) if (!fH->TestBit(TH1::kUserContour)) fH->SetContour(ndiv); Double_t scale = (dz ? ndivz / dz : 1.0); + Double_t xmin = gPad->GetUxmin(); + Double_t xmax = gPad->GetUxmax(); + Double_t ymin = gPad->GetUymin(); + Double_t ymax = gPad->GetUymax(); + Int_t color; TProfile2D* prof2d = dynamic_cast(fH); for (Int_t j=Hparam.yfirst; j<=Hparam.ylast;j++) { @@ -5873,35 +5878,36 @@ void THistPainter::PaintColorLevels(Option_t*) } } - if (Hoption.Logz) { - if (z > 0) z = TMath::Log10(z); - else z = zmin; - } - if (z < zmin && !Hoption.Zero) continue; + if (Hoption.Logz) + z = z > 0 ? TMath::Log10(z) : zmin; + if (z < zmin && !Hoption.Zero) + continue; xup = xk + xstep; xlow = xk; if (Hoption.Logx) { - if (xup > 0) xup = TMath::Log10(xup); - else continue; - if (xlow > 0) xlow = TMath::Log10(xlow); - else continue; + if ((xup <= 0) || (xlow <= 0)) + continue; + xup = TMath::Log10(xup); + xlow = TMath::Log10(xlow); } yup = yk + ystep; ylow = yk; if (Hoption.Logy) { - if (yup > 0) yup = TMath::Log10(yup); - else continue; - if (ylow > 0) ylow = TMath::Log10(ylow); - else continue; - } - if (xup < gPad->GetUxmin()) continue; - if (yup < gPad->GetUymin()) continue; - if (xlow > gPad->GetUxmax()) continue; - if (ylow > gPad->GetUymax()) continue; - if (xlow < gPad->GetUxmin()) xlow = gPad->GetUxmin(); - if (ylow < gPad->GetUymin()) ylow = gPad->GetUymin(); - if (xup > gPad->GetUxmax()) xup = gPad->GetUxmax(); - if (yup > gPad->GetUymax()) yup = gPad->GetUymax(); + if ((yup <= 0) || (ylow <= 0)) + continue; + yup = TMath::Log10(yup); + ylow = TMath::Log10(ylow); + } + if ((xup < xmin) || (yup < ymin) || (xlow > xmax) || (ylow > ymax)) + continue; + if (xlow < xmin) + xlow = xmin; + if (ylow < ymin) + ylow = ymin; + if (xup > xmax) + xup = xmax; + if (yup > ymax) + yup = ymax; if (fH->TestBit(TH1::kUserContour)) { zc = fH->GetContourLevelPad(0); @@ -5920,21 +5926,22 @@ void THistPainter::PaintColorLevels(Option_t*) } Int_t theColor = Int_t((color+0.99)*Float_t(ncolors)/Float_t(ndivz)); - if (theColor > ncolors-1) theColor = ncolors-1; + if (theColor > ncolors-1) + theColor = ncolors-1; auto fillColor = gStyle->GetColorPalette(theColor); if (Hoption.System != kPOLAR) { fH->SetFillColor(fillColor); fH->TAttFill::Modify(); gPad->PaintBox(xlow, ylow, xup, yup); } else { - Double_t midx = (gPad->GetUxmin() + gPad->GetUxmax()) / 2, - midy = (gPad->GetUymin() + gPad->GetUymax()) / 2, - a1 = (xlow - gPad->GetUxmin()) / (gPad->GetUxmax() - gPad->GetUxmin()) * 360, - a2 = (xup - gPad->GetUxmin()) / (gPad->GetUxmax() - gPad->GetUxmin()) * 360, - rx = gPad->GetUxmax() - gPad->GetUxmin(), - ry = gPad->GetUymax() - gPad->GetUymin(), - r1 = (ylow - gPad->GetUymin()) / (gPad->GetUymax() - gPad->GetUymin()) * rx / 2, - r2 = (yup - gPad->GetUymin()) / (gPad->GetUymax() - gPad->GetUymin()) * rx / 2; + Double_t midx = (xmin + xmax) / 2; + Double_t midy = (ymin + ymax) / 2; + Double_t rx = xmax - xmin; + Double_t ry = ymax - ymin; + Double_t a1 = (xlow - xmin) / (xmax - xmin) * 360; + Double_t a2 = (xup - xmin) / (xmax - xmin) * 360; + Double_t r1 = (ylow - ymin) / (ymax - ymin) * rx / 2; + Double_t r2 = (yup - ymin) / (ymax - ymin) * rx / 2; TCrown crown(midx, midy, r1, r2, a1, a2); crown.SetYXRatio(rx > 0 ? ry / rx : 1); From 5f5abb64d1eecd46c475d1dee042a2b2d003147e Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 16:18:38 +0200 Subject: [PATCH 2/6] [hist] introduce POLF draw option In such case polar ranges remains fixed and when histogram zoomed, only part of histogram bins will be drawn without rescale of angle and radius ranges Also POLF draw option automatically use 0 as minimal radius when Y range of histogram is not negative - then real polar coordinates will be displayed --- hist/histpainter/inc/Hoption.h | 1 + hist/histpainter/src/THistPainter.cxx | 43 +++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/hist/histpainter/inc/Hoption.h b/hist/histpainter/inc/Hoption.h index a1c12e25deb7b..fc899f3e8c886 100644 --- a/hist/histpainter/inc/Hoption.h +++ b/hist/histpainter/inc/Hoption.h @@ -41,6 +41,7 @@ typedef struct Hoption_t { int Box; ///< "BOX" Draw 2D plot with proportional Boxes. int Char; ///< "CHAR" Draw 2D plot with a character set. int Color; ///< "COL" Draw 2D plot with Colored boxes. + int Polar; ///< "POL" Draw 2D plot with Polar coordinates. int Contour; ///< "CONTn" Draw 2D plot as a Contour plot (0 <= n <= 5). int Func; ///< "FUNC" Draw only the function (for example in case of fit). int Hist; ///< "HIST" Draw only the histogram. diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index 591bbfbdd94d9..e5d22ae9d2446 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -4063,6 +4063,7 @@ Int_t THistPainter::MakeChopt(Option_t *choptin) Hoption.Lego = Hoption.Surf = Hoption.Off = Hoption.Tri = 0; Hoption.Proj = Hoption.AxisPos = Hoption.Spec = Hoption.Pie = 0; Hoption.Candle = 0; + Hoption.Polar = 0; // special 2D options Hoption.List = 0; @@ -4334,7 +4335,9 @@ Int_t THistPainter::MakeChopt(Option_t *choptin) l = strstr(chopt,"AXIS"); if (l) { Hoption.Axis = 1; memcpy(l," ",4); } l = strstr(chopt,"AXIG"); if (l) { Hoption.Axis = 2; memcpy(l," ",4); } l = strstr(chopt,"SCAT"); if (l) { Hoption.Scat = 1; memcpy(l," ",4); } - l = strstr(chopt,"POL"); if (l) { Hoption.System = kPOLAR; memcpy(l," ",3); } + l = strstr(chopt,"POLN"); if (l) { Hoption.System = kPOLAR; Hoption.Polar = 3; memcpy(l," ",4); } + l = strstr(chopt,"POLF"); if (l) { Hoption.System = kPOLAR; Hoption.Polar = 2; memcpy(l," ",4); } + l = strstr(chopt,"POL"); if (l) { Hoption.System = kPOLAR; Hoption.Polar = 1; memcpy(l," ",3); } l = strstr(chopt,"CYL"); if (l) { Hoption.System = kCYLINDRICAL; memcpy(l," ",3); } l = strstr(chopt,"SPH"); if (l) { Hoption.System = kSPHERICAL; memcpy(l," ",3); } l = strstr(chopt,"PSR"); if (l) { Hoption.System = kRAPIDITY; memcpy(l," ",3); } @@ -5852,6 +5855,36 @@ void THistPainter::PaintColorLevels(Option_t*) Double_t ymin = gPad->GetUymin(); Double_t ymax = gPad->GetUymax(); + // range used for polar coordinates + Double_t pxmin = xmin, pxmax = xmax, pymin = ymin, pymax = ymax; + if ((Hoption.System == kPOLAR) && (Hoption.Polar == 2)) { + pxmin = fXaxis->GetXmin(); + pxmax = fXaxis->GetXmax(); + if (Hoption.Logx) { + if (pxmax <= 0) + return; + pxmax = TMath::Log10(pxmax); + if (pxmin <= 0) + pxmin = pxmax - 5; + else + pxmin = TMath::Log10(pxmin); + } + pymin = fYaxis->GetXmin(); + pymax = fYaxis->GetXmax(); + if (Hoption.Logy) { + if (pymax <= 0) + return; + pymax = TMath::Log10(pymax); + if (pymin <= 0) + pymin = pymax - 5; + else + pymin = TMath::Log10(pymin); + } else if ((pymax > 0) && (pymin >= 0)) { + // force minimal radius to 0 to display natural polar graphics + pymin = 0; + } + } + Int_t color; TProfile2D* prof2d = dynamic_cast(fH); for (Int_t j=Hparam.yfirst; j<=Hparam.ylast;j++) { @@ -5938,10 +5971,10 @@ void THistPainter::PaintColorLevels(Option_t*) Double_t midy = (ymin + ymax) / 2; Double_t rx = xmax - xmin; Double_t ry = ymax - ymin; - Double_t a1 = (xlow - xmin) / (xmax - xmin) * 360; - Double_t a2 = (xup - xmin) / (xmax - xmin) * 360; - Double_t r1 = (ylow - ymin) / (ymax - ymin) * rx / 2; - Double_t r2 = (yup - ymin) / (ymax - ymin) * rx / 2; + Double_t a1 = (xlow - pxmin) / (pxmax - pxmin) * 360; + Double_t a2 = (xup - pxmin) / (pxmax - pxmin) * 360; + Double_t r1 = (ylow - pymin) / (pymax - pymin) * rx / 2; + Double_t r2 = (yup - pymin) / (pymax - pymin) * rx / 2; TCrown crown(midx, midy, r1, r2, a1, a2); crown.SetYXRatio(rx > 0 ? ry / rx : 1); From 401577099bfd8889c7704782c4d36d7c1d52a229 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 16:38:36 +0200 Subject: [PATCH 3/6] [hist] introduce POLN draw option It is "natural" polar coordinates where X axis represents angle in radians and Y axis is radius. When zooming range remains fixed. --- hist/histpainter/src/THistPainter.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index e5d22ae9d2446..bd0297e2b4d76 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -5857,7 +5857,7 @@ void THistPainter::PaintColorLevels(Option_t*) // range used for polar coordinates Double_t pxmin = xmin, pxmax = xmax, pymin = ymin, pymax = ymax; - if ((Hoption.System == kPOLAR) && (Hoption.Polar == 2)) { + if ((Hoption.System == kPOLAR) && (Hoption.Polar > 1)) { pxmin = fXaxis->GetXmin(); pxmax = fXaxis->GetXmax(); if (Hoption.Logx) { @@ -5971,8 +5971,15 @@ void THistPainter::PaintColorLevels(Option_t*) Double_t midy = (ymin + ymax) / 2; Double_t rx = xmax - xmin; Double_t ry = ymax - ymin; - Double_t a1 = (xlow - pxmin) / (pxmax - pxmin) * 360; - Double_t a2 = (xup - pxmin) / (pxmax - pxmin) * 360; + Double_t a1, a2; + + if (Hoption.Polar == 3) { + a1 = xlow / TMath::Pi() * 180; + a2 = xup / TMath::Pi() * 180; + } else { + a1 = (xlow - pxmin) / (pxmax - pxmin) * 360; + a2 = (xup - pxmin) / (pxmax - pxmin) * 360; + } Double_t r1 = (ylow - pymin) / (pymax - pymin) * rx / 2; Double_t r2 = (yup - pymin) / (pymax - pymin) * rx / 2; From 1c4cc615d02b7743c78e949197d9fcf66da58a1c Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 16:50:03 +0200 Subject: [PATCH 4/6] [hist] mention POLN and POLF draw options in documentation --- hist/histpainter/src/THistPainter.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index bd0297e2b4d76..d9550bcbca268 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -309,7 +309,9 @@ using `TH1::GetOption`: | "SAME0" | Same as "SAME" but do not use the z-axis range of the first plot. | | "SAMES0" | Same as "SAMES" but do not use the z-axis range of the first plot. | | "CYL" | Use Cylindrical coordinates. The X coordinate is mapped on the angle and the Y coordinate on the cylinder length.| -| "POL" | Use Polar coordinates. The X coordinate is mapped on the angle and the Y coordinate on the radius.| +| "POL" | Use Polar coordinates. The visible X range mapped on the angle and the visible Y coordinate on the radius.| +| "POLF" | Fixed Polar coordinates. The histogram X coordinate mapped on the angle and the Y coordinate on the radius.| +| "POLN" | Natural Polar coordinates. The X coordinate directly represent angle in radian and the Y coordinate is the radius.| | "SPH" | Use Spherical coordinates. The X coordinate is mapped on the latitude and the Y coordinate on the longitude.| | "PSR" | Use PseudoRapidity/Phi coordinates. The X coordinate is mapped on Phi.| | "SURF" | Draw a surface plot with hidden line removal.| From 97f9697615f3e0d131f59ea0f93401066c087cf7 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 17:07:29 +0200 Subject: [PATCH 5/6] [hist] shift angle range in polar coordinates Instead of range [0..360) now X axis will be mapped to [-180..+180) range. This make more intuitive behaviour during histogram zooming when central X range of histogram appears around angle 0 near right side --- hist/histpainter/src/THistPainter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index d9550bcbca268..dd2bfc7eb0011 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -5979,8 +5979,8 @@ void THistPainter::PaintColorLevels(Option_t*) a1 = xlow / TMath::Pi() * 180; a2 = xup / TMath::Pi() * 180; } else { - a1 = (xlow - pxmin) / (pxmax - pxmin) * 360; - a2 = (xup - pxmin) / (pxmax - pxmin) * 360; + a1 = ((xlow - pxmin) / (pxmax - pxmin) - 0.5) * 360; + a2 = ((xup - pxmin) / (pxmax - pxmin) - 0.5) * 360; } Double_t r1 = (ylow - pymin) / (pymax - pymin) * rx / 2; Double_t r2 = (yup - pymin) / (pymax - pymin) * rx / 2; From 4b779d7a16866569a64e0aa510a6ece9fd6fe3d7 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 17:13:06 +0200 Subject: [PATCH 6/6] [hist] add new POLF/POLN draw options to release notes --- README/ReleaseNotes/v642/index.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README/ReleaseNotes/v642/index.md b/README/ReleaseNotes/v642/index.md index 3161e2ec44078..953c6e2001eb4 100644 --- a/README/ReleaseNotes/v642/index.md +++ b/README/ReleaseNotes/v642/index.md @@ -107,6 +107,17 @@ or read the bin count from the relevant histogram or plot frame instead. ## Graphics and GUI +### New POLF and POLN draw options for TH2 + +Since ROOT 6.36 implementation of "POL" draw option was changed. Angle and radius range automatically scaled to visible histogram range filling full 2*Pi angle and full radius range. +This let display TH2 in polar coordinates for any provided range settings, but produced plots are not intuitive. Therefore two new options for polar coordinates were introduced. + +"POLF" - fixed polar coordinates. In such case full histogram X range mapped to -PI .. +Pi. +And Y axis mapped to radius. In case of histogram zooming angle and radius of each bin remains +the same (therefore name "fixed"), just number of displayed bins are reduced. + +"POLN" - natural polar coordinates. In this case X axis directly represents angle value in radians (therefore name "natural") and Y axis is just radius. + ### Store canvas as HTML file Now canvas (or several canvases) can be stored in portable HTML file.