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 44c5be7ef074d733992bdaebfc3ff2267b2083ff 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 | 44 ++++++++++++++++++++++++--- 2 files changed, 40 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..ed6db9e0315ec 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,37 @@ 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, pkr = 0.5; + 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); + } + pkr = 0.45; // makes fixed range more pretty + 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 +5972,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 * pkr; + Double_t r2 = (yup - pymin) / (pymax - pymin) * rx * pkr; TCrown crown(midx, midy, r1, r2, a1, a2); crown.SetYXRatio(rx > 0 ? ry / rx : 1); From d7b2fc7ba8f5181129dc9c8a40621bd7d6bfb6c5 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 | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index ed6db9e0315ec..78d8bf4b89853 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -5886,6 +5886,15 @@ void THistPainter::PaintColorLevels(Option_t*) } } + if ((Hoption.System == kPOLAR) && (Hoption.Polar == 3)) { + // do not touch boundaries when draw without axis histogram + if (!Hoption.Same) + pkr = 0.45; + // for natural coordinates force minimal radius to 0 + if (!Hoption.Logy && (pymax > 0)) + pymin = 0; + } + Int_t color; TProfile2D* prof2d = dynamic_cast(fH); for (Int_t j=Hparam.yfirst; j<=Hparam.ylast;j++) { @@ -5972,8 +5981,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 * pkr; Double_t r2 = (yup - pymin) / (pymax - pymin) * rx * pkr; From ea57d613e3ec72bce8890d5d033e01f689af9061 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 17:07:29 +0200 Subject: [PATCH 4/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 78d8bf4b89853..5291bdb1f2d68 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -5987,8 +5987,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 * pkr; Double_t r2 = (yup - pymin) / (pymax - pymin) * rx * pkr; From f9fd22b59ecc73d4eeef83d7d4b4bb52dd40a814 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 24 Jul 2026 16:50:03 +0200 Subject: [PATCH 5/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 5291bdb1f2d68..ac06af741c6b2 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 dd9bdbede35a21298095e343a60e34ba022a3fff 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.