diff --git a/core/base/inc/TVirtualPad.h b/core/base/inc/TVirtualPad.h index b51e86c9d3d2f..b7d71263ab713 100644 --- a/core/base/inc/TVirtualPad.h +++ b/core/base/inc/TVirtualPad.h @@ -95,6 +95,7 @@ class TVirtualPad : public TObject, public TAttLine, public TAttFill, virtual void DrawClassObject(const TObject *obj, Option_t *option="") = 0; virtual TH1F *DrawFrame(Double_t xmin, Double_t ymin, Double_t xmax, Double_t ymax, const char *title="") = 0; virtual void ExecuteEventAxis(Int_t event, Int_t px, Int_t py, TAxis *axis) = 0; + virtual void AutoZoomed() { Emit("AutoZoomed()"); } // *SIGNAL* virtual void UnZoomed() { Emit("UnZoomed()"); } // *SIGNAL* virtual Short_t GetBorderMode() const = 0; virtual Short_t GetBorderSize() const = 0; diff --git a/hist/hist/inc/TAxis.h b/hist/hist/inc/TAxis.h index 69c8b59806892..038231a6c231f 100644 --- a/hist/hist/inc/TAxis.h +++ b/hist/hist/inc/TAxis.h @@ -173,6 +173,8 @@ class TAxis : public TNamed, public TAttAxis { virtual void SetTimeDisplay(Int_t value) {fTimeDisplay = (value != 0);} // *TOGGLE* virtual void SetTimeFormat(const char *format=""); // *MENU* virtual void SetTimeOffset(Double_t toffset, Option_t *option="local"); + virtual void AutoZoom(); // *MENU* + virtual void AutoZoomAll(); // *MENU* virtual void UnZoom(); // *MENU* virtual void ZoomOut(Double_t factor=0, Double_t offset=0); // *MENU* diff --git a/hist/hist/inc/TH1.h b/hist/hist/inc/TH1.h index afdddd087e055..8112e76bcd651 100644 --- a/hist/hist/inc/TH1.h +++ b/hist/hist/inc/TH1.h @@ -209,6 +209,8 @@ class TH1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { virtual void AddBinContent(Int_t bin, Double_t w) = 0; static void AddDirectory(Bool_t add=kTRUE); static Bool_t AddDirectoryStatus(); + virtual void AutoZoom(); // *MENU* + virtual void UnZoom(); // *MENU* void Browse(TBrowser *b) override; virtual Bool_t CanExtendAllAxes() const; virtual Double_t Chi2Test(const TH1* h2, Option_t *option = "UU", Double_t *res = nullptr) const; @@ -323,10 +325,11 @@ class TH1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { virtual Int_t GetQuantiles(Int_t n, Double_t *xp, const Double_t *p = nullptr); virtual Double_t GetRandom(TRandom * rng = nullptr) const; + void GetRangeOfFilledWeights(const Int_t dim, Int_t& first, Int_t& last, const Int_t margin, const bool includeUnderOverflow) const; virtual void GetStats(Double_t *stats) const; virtual Double_t GetStdDev(Int_t axis=1) const; virtual Double_t GetStdDevError(Int_t axis=1) const; - Double_t GetSumOfAllWeights(const bool includeOverflow) const; + Double_t GetSumOfAllWeights(const bool includeUnderOverflow) const; /// Return the sum of weights across all bins excluding under/overflows. /// \see TH1::GetSumOfAllWeights() virtual Double_t GetSumOfWeights() const { return GetSumOfAllWeights(false); } diff --git a/hist/hist/src/TAxis.cxx b/hist/hist/src/TAxis.cxx index dcefcf4117a7f..eaf2a5fd71dcb 100644 --- a/hist/hist/src/TAxis.cxx +++ b/hist/hist/src/TAxis.cxx @@ -17,6 +17,7 @@ #include "TList.h" #include "TAxisModLab.h" #include "TH1.h" +#include "THStack.h" #include "TObjString.h" #include "TDatime.h" #include "TTimeStamp.h" @@ -1269,7 +1270,7 @@ void TAxis::UnZoom() //unzoom object owning this axis SetRange(0,0); - TH1 *hobj1 = (TH1*)GetParent(); + TH1 *hobj1 = GetParent() && GetParent()->InheritsFrom(TH1::Class()) ? static_cast(GetParent()) : nullptr; if (!strstr(GetName(),"xaxis")) { if (!hobj1) return; if (hobj1->GetDimension() == 2) { @@ -1284,7 +1285,7 @@ void TAxis::UnZoom() hobj1->SetMinimum(fXmin); hobj1->SetMaximum(fXmax); } else { - if (fXmin==hobj1->GetMinimum() && fXmax==hobj1->GetMaximum()) { + if (fXmin==hobj1->GetMinimumStored() && fXmax==hobj1->GetMaximumStored()) { hobj1->SetMinimum(fXmin); hobj1->SetMaximum(fXmax); } else { @@ -1328,6 +1329,126 @@ void TAxis::UnZoom() gPad->UnZoomed(); } +//////////////////////////////////////////////////////////////////////////////// +/// Automatically zoom the current axis to the range of the parent histogram +/// filled with non-zero contents (weights or errors) +/// \note For the bin content axis (yaxis) of a TH1 or (zaxis) of a TH2, +/// UnZoom is called instead. TH3, the PaletteAxis does not implement AutoZoom nor UnZoom + +void TAxis::AutoZoom() +{ + if (!GetParent()) { + Warning("TAxis::AutoZoom","Cannot AutoZoom if parent does not exist. Did you mean to draw the TAxis first?"); + return; + } + if (!GetParent()->InheritsFrom(TH1::Class())) { + Warning("TAxis::AutoZoom","Cannot AutoZoom if axis parent (of type `%s`) does not derive from TH1.", GetParent()->ClassName()); + return; + } + auto dim = strstr(GetName(), "xaxis") ? 0 : strstr(GetName(), "yaxis") ? 1 : strstr(GetName(), "zaxis") ? 2 : -1; + TH1 *hobj1 = static_cast(GetParent()); + Int_t first = -1, last = -1; + + auto ndims = hobj1->GetDimension(); + // Sanity checks + if (dim == 0) { + if (ndims != 1 && ndims != 2 && ndims != 3) { + Warning("TAxis::AutoZoom","Cannot AutoZoom xaxis if TH has %d dimension(s)", ndims); + return; + } + } else if (dim == 1) { + if (ndims == 1) { + UnZoom(); // For a TH1, it acts as an AutoZoom of the yaxis (bin content axis) + return; + } + if (ndims != 2 && ndims != 3) { + Warning("TAxis::AutoZoom","Cannot AutoZoom yaxis if TH has %d dimension(s)", ndims); + return; + } + } else if (dim == 2) { + if (ndims == 2) { + UnZoom(); // For a TH2, it acts as an AutoZoom of the zaxis (bin content axis) + return; + } + if (ndims != 3) { + Warning("TAxis::AutoZoom","Cannot AutoZoom zaxis if TH has %d dimension(s)", ndims); + return; + } + } + + hobj1->GetRangeOfFilledWeights(dim, first, last, 1, false); + SetRange(first, last); + + if (gPad) + gPad->AutoZoomed(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Automatically zoom the current axis to the range of all histograms overlaid +/// in this pad and detect shared range filled with non-zero contents (weights or errors) +/// \note For the bin content axis (yaxis) of a TH1 or (zaxis) of a TH2, +/// UnZoom is called instead. TH3, the PaletteAxis does not implement AutoZoom nor UnZoom + +void TAxis::AutoZoomAll() +{ + if (!gPad) { + Warning("TAxis::AutoZoom","Cannot AutoZoom if gPad does not exist. Did you mean to draw the TAxis first?"); + return; + } + gPad->SetView(); + auto dim = strstr(GetName(), "xaxis") ? 0 : strstr(GetName(), "yaxis") ? 1 : strstr(GetName(), "zaxis") ? 2 : -1; + Int_t first = -1, last = -1; + + Double_t globalMin = DBL_MAX; + Double_t globalMax = -DBL_MAX; + TIter next(gPad->GetListOfPrimitives()); + while (TObject *obj= next()) { + if (!obj) + continue; + if (obj->InheritsFrom(TH1::Class())) { + TH1 *hobj = static_cast(obj); + if (dim > hobj->GetDimension()) + continue; + hobj->GetRangeOfFilledWeights(dim, first, last, 1, false); + TAxis *ax = (dim == 0) ? hobj->GetXaxis() : (dim == 1) ? hobj->GetYaxis() : (dim == 2) ? hobj->GetZaxis() : nullptr; + if (ax) { + globalMin = std::min(globalMin, ax->GetBinLowEdge(first)); + globalMax = std::max(globalMax, ax->GetBinUpEdge(last)); + } + } else if (obj->InheritsFrom(THStack::Class())) { + THStack *hs = static_cast(obj); + TIter hsnext(hs->begin()); + while (TObject *hsobj= hsnext()) { + if (!hsobj) + continue; + TH1 *hobj = static_cast(hsobj); + if (dim > hobj->GetDimension()) + continue; + hobj->GetRangeOfFilledWeights(dim, first, last, 1, false); + TAxis *ax = (dim == 0) ? hobj->GetXaxis() : (dim == 1) ? hobj->GetYaxis() : (dim == 2) ? hobj->GetZaxis() : nullptr; + if (ax) { + globalMin = std::min(globalMin, ax->GetBinLowEdge(first)); + globalMax = std::max(globalMax, ax->GetBinUpEdge(last)); + } + } + } + } + next.Reset(); + while (TObject *obj = next()) { + if (!obj || (!obj->InheritsFrom(TH1::Class()) && !obj->InheritsFrom(THStack::Class()))) + continue; + TH1 *hobj = obj->InheritsFrom(TH1::Class()) ? static_cast(obj) : static_cast(obj)->GetHistogram(); + if (dim > hobj->GetDimension()) + continue; + TAxis *ax = (dim == 0) ? hobj->GetXaxis() : (dim == 1) ? hobj->GetYaxis() : (dim == 2) ? hobj->GetZaxis() : nullptr; + if (ax) { + ax->SetRangeUser(globalMin, globalMax); + } + } + + gPad->AutoZoomed(); +} + //////////////////////////////////////////////////////////////////////////////// /// Zoom out by a factor of 'factor' (default =2) /// uses previous zoom factor by default diff --git a/hist/hist/src/TH1.cxx b/hist/hist/src/TH1.cxx index d252261cc3bd2..af076d55fc6e6 100644 --- a/hist/hist/src/TH1.cxx +++ b/hist/hist/src/TH1.cxx @@ -744,6 +744,54 @@ Bool_t TH1::AddDirectoryStatus() return fgAddDirectory; } +//////////////////////////////////////////////////////////////////////////////// +/// Autozoom histogram in all their axes +/// \see TAxis::AutoZoom + +void TH1::AutoZoom() +{ + const auto ndims = GetDimension(); + if (ndims < 1 || ndims > 3) + return; + // First apply autozoom in pure coordinate axis + if (ndims >= 1) + GetXaxis()->AutoZoom(); + if (ndims >= 2) + GetYaxis()->AutoZoom(); + if (ndims >= 3) + GetZaxis()->AutoZoom(); + // Now apply autozoom in the bin content axis if it's a TH1 or TH2 + if (ndims == 1) + GetYaxis()->AutoZoom(); + else if (ndims == 2) + GetZaxis()->AutoZoom(); + // For 3D, there is no UnZoom or AutoZoom implemented for TPaletteAxis +} + +//////////////////////////////////////////////////////////////////////////////// +/// Unzoom histogram in all their axes +/// \see TAxis::UnZoom + +void TH1::UnZoom() +{ + const auto ndims = GetDimension(); + if (ndims < 1 || ndims > 3) + return; + // First apply Unzoom in pure coordinate axis + if (ndims >= 1) + GetXaxis()->UnZoom(); + if (ndims >= 2) + GetYaxis()->UnZoom(); + if (ndims >= 3) + GetZaxis()->UnZoom(); + // Now apply unzoom in the bin content axis if it's a TH1 or TH2 + if (ndims == 1) + GetYaxis()->UnZoom(); + else if (ndims == 2) + GetZaxis()->UnZoom(); + // For 3D, there is no UnZoom or AutoZoom implemented for TPaletteAxis +} + //////////////////////////////////////////////////////////////////////////////// /// Browse the Histogram object. @@ -7904,22 +7952,158 @@ void TH1::ResetStats() if (fSumw2.fN > 0 && fTsumw > 0 && stats[1] > 0 ) fEntries = stats[0]*stats[0]/ stats[1]; } +//////////////////////////////////////////////////////////////////////////////// +/// Get the range of the histogram that is filled with non-empty contents (i.e. +/// non-zero content and non-zero error). +/// \param dim 0 for the x-axis, 1 for the y-axis, 2 for the z-axis, must be <= GetDimension() +/// \param first where the first non-empty bin index will be stored (minus margin) +/// \param last where the last non-empty bin index will be stored (plus margin) +/// \param margin number of bins to enlarge each side of the range, to leave some room +/// \param includeUnderOverflow when searching for non-empty bins, set to true to include the under/overflow bins + +void TH1::GetRangeOfFilledWeights(const Int_t dim, Int_t& first, Int_t& last, const Int_t margin, const bool includeUnderOverflow) const +{ + if (fBuffer) const_cast(this)->BufferEmpty(); + + const auto ndims = GetDimension(); + const Int_t startX = (includeUnderOverflow ? 0 : 1); + const Int_t startY = ndims < 2 ? 1 : startX; + const Int_t startZ = ndims < 3 ? 1 : startX; + const Int_t lastX = fXaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); + const Int_t lastY = ndims < 2 ? 1 : fYaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); + const Int_t lastZ = ndims < 3 ? 1 : fZaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); + + R__ASSERT(dim == 0 || dim == 1 || dim == 2); + if (ndims == 1) { + R__ASSERT(dim == 0); + } else if (ndims == 2) { + R__ASSERT(dim == 0 || dim == 1); + } else if (ndims == 3) { + R__ASSERT(dim == 0 || dim == 1 || dim == 2); + } + + if (dim == 0) { + first = startX; + for(Int_t binx = startX; binx <= lastX; binx++) { + for(auto biny = startY; biny <= lastY; biny++) { + for(auto binz = startZ; binz <= lastZ; binz++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + first = binx; + // Break: + binx = lastX; + biny = lastY; + binz = lastZ; + } + } + } + } + last = lastX; + for(Int_t binx = lastX; binx >= startX; binx--) { + for(auto biny = startY; biny <= lastY; biny++) { + for(auto binz = startZ; binz <= lastZ; binz++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + last = binx; + // Break: + binx = startX; + biny = lastY; + binz = lastZ; + } + } + } + } + } else if (dim == 1) { + first = startY; + for(auto biny = startY; biny <= lastY; biny++) { + for(Int_t binx = startX; binx <= lastX; binx++) { + for(auto binz = startZ; binz <= lastZ; binz++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + first = biny; + // Break: + binx = lastX; + biny = lastY; + binz = lastZ; + } + } + } + } + last = lastY; + for(Int_t biny = lastY; biny >= startY; biny--) { + for(auto binx = startX; binx <= lastX; binx++) { + for(auto binz = startZ; binz <= lastZ; binz++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + last = biny; + // Break: + binx = lastX; + biny = startY; + binz = lastZ; + } + } + } + } + } else if (dim == 2) { + first = startZ; + for(auto binz = startZ; binz <= lastZ; binz++) { + for(Int_t binx = startX; binx <= lastX; binx++) { + for(auto biny = startY; biny <= lastY; biny++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + first = biny; + // Break: + binx = lastX; + biny = lastY; + binz = lastZ; + } + } + } + } + last = lastZ; + for(Int_t binz = lastZ; binz >= startZ; binz--) { + for(auto binx = startX; binx <= lastX; binx++) { + for(auto biny = startY; biny <= lastY; biny++) { + auto bin = GetBin(binx, biny, binz); + if (RetrieveBinContent(bin) != 0 || GetBinError(bin) != 0) + { + last = binz; + // Break: + binx = lastX; + biny = lastY; + binz = startZ; + } + } + } + } + } + // Apply some margins + first = std::max(dim == 0 ? startX : dim == 1 ? startY : startZ, first - margin); + last = std::min(dim == 0 ? lastX : dim == 1 ? lastY : lastZ, last + margin); +} + //////////////////////////////////////////////////////////////////////////////// /// Return the sum of all weights /// \param includeOverflow true to include under/overflows bins, false to exclude those. /// \note Different from TH1::GetSumOfWeights, that always excludes those -Double_t TH1::GetSumOfAllWeights(const bool includeOverflow) const +Double_t TH1::GetSumOfAllWeights(const bool includeUnderOverflow) const { if (fBuffer) const_cast(this)->BufferEmpty(); - const Int_t start = (includeOverflow ? 0 : 1); - const Int_t lastX = fXaxis.GetNbins() + (includeOverflow ? 1 : 0); - const Int_t lastY = fYaxis.GetNbins() + (includeOverflow ? 1 : 0); - const Int_t lastZ = fZaxis.GetNbins() + (includeOverflow ? 1 : 0); + const auto ndims = GetDimension(); + const Int_t start = (includeUnderOverflow ? 0 : 1); + const Int_t lastX = fXaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); + const Int_t lastY = ndims < 2 ? 1 : fYaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); + const Int_t lastZ = ndims < 3 ? 1 : fZaxis.GetNbins() + (includeUnderOverflow ? 1 : 0); Double_t sum =0; - for(auto binz = start; binz <= lastZ; binz++) { - for(auto biny = start; biny <= lastY; biny++) { + for(auto binz = ndims < 3 ? 1 : start; binz <= lastZ; binz++) { + for(auto biny = ndims < 2 ? 1 : start; biny <= lastY; biny++) { for(auto binx = start; binx <= lastX; binx++) { const auto bin = GetBin(binx, biny, binz); sum += RetrieveBinContent(bin); diff --git a/roottest/root/io/filemerger/CMakeLists.txt b/roottest/root/io/filemerger/CMakeLists.txt index 35f983bc6f159..f4a5b048575db 100644 --- a/roottest/root/io/filemerger/CMakeLists.txt +++ b/roottest/root/io/filemerger/CMakeLists.txt @@ -1,6 +1,6 @@ ROOTTEST_ADD_TEST(execKeyOrder - MACRO execKeyOrder.C - OUTREF execKeyOrder.ref) + MACRO execKeyOrder.C + OUTREF execKeyOrder.ref) ROOTTEST_ADD_TEST(execCreateAndMerge diff --git a/roottest/root/meta/MemberComments.ref b/roottest/root/meta/MemberComments.ref index 25e00ec5c00b1..37caa172a5920 100644 --- a/roottest/root/meta/MemberComments.ref +++ b/roottest/root/meta/MemberComments.ref @@ -588,6 +588,10 @@ OBJ: TList TList Doubly linked list : 0 TH1::Class()->GetMenuItems(): OBJ: TList TList Doubly linked list : 0 + OBJ: TMethod AutoZoom *MENU* : 0 + void TH1::AutoZoom() + OBJ: TMethod UnZoom *MENU* : 0 + void TH1::UnZoom() OBJ: TMethod DrawPanel *MENU* : 0 void TH1::DrawPanel() OBJ: TMethod Fit *MENU* : 0 diff --git a/roottest/root/meta/MemberComments_win32.ref b/roottest/root/meta/MemberComments_win32.ref index c63169024ce6f..70e555751413f 100644 --- a/roottest/root/meta/MemberComments_win32.ref +++ b/roottest/root/meta/MemberComments_win32.ref @@ -588,6 +588,10 @@ OBJ: TList TList Doubly linked list : 0 TH1::Class()->GetMenuItems(): OBJ: TList TList Doubly linked list : 0 + OBJ: TMethod AutoZoom *MENU* : 0 + void TH1::AutoZoom() + OBJ: TMethod UnZoom *MENU* : 0 + void TH1::UnZoom() OBJ: TMethod DrawPanel *MENU* : 0 void TH1::DrawPanel() OBJ: TMethod Fit *MENU* : 0 diff --git a/roottest/root/meta/MemberComments_win64.ref b/roottest/root/meta/MemberComments_win64.ref index c63169024ce6f..70e555751413f 100644 --- a/roottest/root/meta/MemberComments_win64.ref +++ b/roottest/root/meta/MemberComments_win64.ref @@ -588,6 +588,10 @@ OBJ: TList TList Doubly linked list : 0 TH1::Class()->GetMenuItems(): OBJ: TList TList Doubly linked list : 0 + OBJ: TMethod AutoZoom *MENU* : 0 + void TH1::AutoZoom() + OBJ: TMethod UnZoom *MENU* : 0 + void TH1::UnZoom() OBJ: TMethod DrawPanel *MENU* : 0 void TH1::DrawPanel() OBJ: TMethod Fit *MENU* : 0 diff --git a/test/stress.cxx b/test/stress.cxx index 4db3b572c5b7a..c96b2c274c320 100644 --- a/test/stress.cxx +++ b/test/stress.cxx @@ -376,7 +376,7 @@ void stress2() if (OK) printf("OK\n"); else { printf("FAILED\n"); - printf("%-8s last =%lld, comp=%f\n"," ",last,comp); + printf("%-8s last=%lld, comp=%f vs lastgood=%lld\n"," ", last, comp, lastgood); } if (gPrintSubBench) { printf("Test 2 : "); gBenchmark->Show("stress");gBenchmark->Start("stress"); } }