From 5ff62ec082b0e18a8453cdc3b5e7995816f64bec Mon Sep 17 00:00:00 2001 From: Olivier Couet Date: Fri, 24 Oct 2025 14:09:05 +0200 Subject: [PATCH 1/4] Implement TScatter2D (5D plots) --- core/base/inc/TVirtualPad.h | 1 + graf2d/gpad/inc/TPad.h | 1 + graf2d/gpad/src/TPad.cxx | 25 +- hist/hist/CMakeLists.txt | 2 + hist/hist/inc/LinkDef.h | 1 + hist/hist/inc/TScatter2D.h | 73 ++++ hist/hist/inc/TVirtualGraphPainter.h | 2 + hist/hist/src/TScatter.cxx | 11 +- hist/hist/src/TScatter2D.cxx | 270 ++++++++++++++ hist/histpainter/inc/TGraphPainter.h | 2 + hist/histpainter/src/TGraphPainter.cxx | 350 ++++++++++++++---- .../visualisation/graphs/gr006_scatter.C | 42 ++- .../visualisation/graphs/gr019_scatter2d.C | 53 +++ 13 files changed, 737 insertions(+), 96 deletions(-) create mode 100644 hist/hist/inc/TScatter2D.h create mode 100644 hist/hist/src/TScatter2D.cxx create mode 100644 tutorials/visualisation/graphs/gr019_scatter2d.C diff --git a/core/base/inc/TVirtualPad.h b/core/base/inc/TVirtualPad.h index ab8a71434d9c2..9e527e1559286 100644 --- a/core/base/inc/TVirtualPad.h +++ b/core/base/inc/TVirtualPad.h @@ -189,6 +189,7 @@ class TVirtualPad : public TObject, public TAttLine, public TAttFill, virtual void PaintPolyLineNDC(Int_t n, Double_t *x, Double_t *y, Option_t *option="") = 0; virtual void PaintPolyMarker(Int_t n, Float_t *x, Float_t *y, Option_t *option="") = 0; virtual void PaintPolyMarker(Int_t n, Double_t *x, Double_t *y, Option_t *option="") = 0; + virtual void PaintMarker3D(Double_t x, Double_t y, Double_t z) = 0; virtual void PaintModified() = 0; virtual void PaintText(Double_t x, Double_t y, const char *text) = 0; virtual void PaintText(Double_t x, Double_t y, const wchar_t *text) = 0; diff --git a/graf2d/gpad/inc/TPad.h b/graf2d/gpad/inc/TPad.h index 5c0d74f3b6c63..5bc9c9f481276 100644 --- a/graf2d/gpad/inc/TPad.h +++ b/graf2d/gpad/inc/TPad.h @@ -301,6 +301,7 @@ friend class TWebCanvas; void PaintPolyLineNDC(Int_t n, Double_t *x, Double_t *y, Option_t *option="") override; void PaintPolyMarker(Int_t n, Float_t *x, Float_t *y, Option_t *option="") override; void PaintPolyMarker(Int_t n, Double_t *x, Double_t *y, Option_t *option="") override; + void PaintMarker3D(Double_t x, Double_t y, Double_t z) override; void PaintModified() override; void PaintText(Double_t x, Double_t y, const char *text) override; void PaintText(Double_t x, Double_t y, const wchar_t *text) override; diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index ca11fa203a18d..7b60ee326fb86 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -4491,8 +4491,8 @@ void TPad::PaintLine3D(Float_t *p1, Float_t *p2) void TPad::PaintLine3D(Double_t *p1, Double_t *p2) { - //take into account perspective view if (!fView) return; + // convert from 3-D to 2-D pad coordinate system Double_t xpad[6]; Double_t temp[3]; @@ -4504,6 +4504,29 @@ void TPad::PaintLine3D(Double_t *p1, Double_t *p2) PaintLine(xpad[0],xpad[1],xpad[3],xpad[4]); } +//////////////////////////////////////////////////////////////////////////////// +/// Paint 3-D marker in the CurrentPad. + +void TPad::PaintMarker3D(Double_t x, Double_t y, Double_t z) +{ + if (!fView) return; + + Double_t rmin[3], rmax[3]; + fView->GetRange(rmin, rmax); + + // convert from 3-D to 2-D pad coordinate system + Double_t xpad[3]; + Double_t temp[3]; + temp[0] = x; + temp[1] = y; + temp[2] = z; + if (xrmax[0]) return; + if (yrmax[1]) return; + if (zrmax[2]) return; + fView->WCtoNDC(temp, &xpad[0]); + PaintPolyMarker(1, &xpad[0], &xpad[1]); +} + //////////////////////////////////////////////////////////////////////////////// /// Paint polyline in CurrentPad World coordinates. diff --git a/hist/hist/CMakeLists.txt b/hist/hist/CMakeLists.txt index 1fce5ad5c0f35..8b2d06bcc293f 100644 --- a/hist/hist/CMakeLists.txt +++ b/hist/hist/CMakeLists.txt @@ -43,6 +43,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Hist TGraphSmooth.h TGraphTime.h TScatter.h + TScatter2D.h TH1C.h TH1D.h TH1F.h @@ -136,6 +137,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Hist TGraphSmooth.cxx TGraphTime.cxx TScatter.cxx + TScatter2D.cxx TH1.cxx TH1K.cxx TH1Merger.cxx diff --git a/hist/hist/inc/LinkDef.h b/hist/hist/inc/LinkDef.h index 23ff0bbd814ee..4ca3476b581b9 100644 --- a/hist/hist/inc/LinkDef.h +++ b/hist/hist/inc/LinkDef.h @@ -48,6 +48,7 @@ #pragma link C++ class TGraphMultiErrors+; #pragma link C++ class TGraphBentErrors+; #pragma link C++ class TScatter+; +#pragma link C++ class TScatter2D+; #pragma link C++ class TGraph2D-; #pragma link C++ class TGraph2DErrors-; #pragma link C++ class TGraph2DAsymmErrors-; diff --git a/hist/hist/inc/TScatter2D.h b/hist/hist/inc/TScatter2D.h new file mode 100644 index 0000000000000..a409e75148e4a --- /dev/null +++ b/hist/hist/inc/TScatter2D.h @@ -0,0 +1,73 @@ +// @(#)root/hist:$Id$ +// Author: Olivier Couet 23/09/2025 + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_TScatter2D +#define ROOT_TScatter2D + + +////////////////////////////////////////////////////////////////////////// +// // +// TScatter2D // +// // +// A scatter plot able to draw five variables on a single plot // +// // +////////////////////////////////////////////////////////////////////////// + +#include "TNamed.h" +#include "TAttLine.h" +#include "TAttFill.h" +#include "TAttMarker.h" +#include "TGraph2D.h" + +class TH3F; + +class TScatter2D : public TNamed, public TAttLine, public TAttFill, public TAttMarker { + +protected: + Int_t fNpoints{-1}; ///< Number of points of arrays fX, fY and fZ + TGraph2D *fGraph{nullptr}; ///< Pointer to graph holding X, Y and Z positions + Double_t *fColor{nullptr}; ///< [fNpoints] array of colors + Double_t *fSize{nullptr}; ///< [fNpoints] array of marker sizes + Double_t fMaxMarkerSize{5.}; ///< Largest marker size used to paint the markers + Double_t fMinMarkerSize{1.}; ///< Smallest marker size used to paint the markers + Double_t fMargin{.1}; ///< Margin around the plot in % + +public: + TScatter2D(); + TScatter2D(Int_t n); + TScatter2D(Int_t n, Double_t *x, Double_t *y, Double_t *z, const Double_t *col = nullptr, const Double_t *size = nullptr); + ~TScatter2D() override; + + Int_t DistancetoPrimitive(Int_t px, Int_t py) override; + void ExecuteEvent(Int_t event, Int_t px, Int_t py) override; + Double_t *GetColor() const {return fColor;} ///< Get the array of colors + Double_t *GetSize() const {return fSize;} ///< Get the array of marker sizes + Double_t GetMargin() const {return fMargin;} ///< Set the margin around the plot in % + Double_t GetMaxMarkerSize() const {return fMaxMarkerSize;} ///< Get the largest marker size used to paint the markers + Double_t GetMinMarkerSize() const {return fMinMarkerSize;} ///< Get the smallest marker size used to paint the markers + TGraph2D *GetGraph() const {return fGraph;} ///< Get the graph holding X, Y and Z positions + TH2D *GetHistogram() const; ///< Get the graph histogram used for drawing axis + TAxis *GetXaxis() const ; + TAxis *GetYaxis() const ; + TAxis *GetZaxis() const ; + + void SetMaxMarkerSize(Double_t max) {fMaxMarkerSize = max;} ///< Set the largest marker size used to paint the markers + void SetMinMarkerSize(Double_t min) {fMinMarkerSize = min;} ///< Set the smallest marker size used to paint the markers + void SetMargin(Double_t); + void Print(Option_t *chopt="") const override; + void SavePrimitive(std::ostream &out, Option_t *option = "") override; + void Paint(Option_t *chopt="") override; + + + ClassDefOverride(TScatter2D,1) //A 2D scatter plot +}; +#endif + diff --git a/hist/hist/inc/TVirtualGraphPainter.h b/hist/hist/inc/TVirtualGraphPainter.h index 4bf8d559ab3b2..19ad070a06863 100644 --- a/hist/hist/inc/TVirtualGraphPainter.h +++ b/hist/hist/inc/TVirtualGraphPainter.h @@ -23,6 +23,7 @@ class TGraph; class TScatter; +class TScatter2D; class TF1; class TVirtualGraphPainter : public TObject { @@ -42,6 +43,7 @@ class TVirtualGraphPainter : public TObject { virtual void PaintGraph(TGraph *theGraph, Int_t npoints, const Double_t *x, const Double_t *y, Option_t *chopt) = 0; virtual void PaintGrapHist(TGraph *theGraph, Int_t npoints, const Double_t *x, const Double_t *y, Option_t *chopt) = 0; virtual void PaintScatter(TScatter *theScatter, Option_t *option) = 0; + virtual void PaintScatter2D(TScatter2D *theScatter, Option_t *option) = 0; virtual void PaintStats(TGraph *theGraph, TF1 *fit) = 0; virtual void SetHighlight(TGraph *theGraph) = 0; diff --git a/hist/hist/src/TScatter.cxx b/hist/hist/src/TScatter.cxx index 0cd1f6833a005..13476f84d3e60 100644 --- a/hist/hist/src/TScatter.cxx +++ b/hist/hist/src/TScatter.cxx @@ -11,20 +11,11 @@ #include "TROOT.h" -#include "TBuffer.h" #include "TScatter.h" -#include "TStyle.h" -#include "TMath.h" -#include "TVirtualPad.h" #include "TH2.h" #include "TVirtualGraphPainter.h" -#include "strtok.h" - -#include -#include -#include -#include + #include //////////////////////////////////////////////////////////////////////////////// diff --git a/hist/hist/src/TScatter2D.cxx b/hist/hist/src/TScatter2D.cxx new file mode 100644 index 0000000000000..a39b46fd55b3f --- /dev/null +++ b/hist/hist/src/TScatter2D.cxx @@ -0,0 +1,270 @@ +// @(#)root/hist:$Id$ +// Author: Olivier Couet 23/09/2025 + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +#include "TROOT.h" +#include "TScatter2D.h" +#include "TH1.h" +#include "TVirtualGraphPainter.h" + +#include + + +//////////////////////////////////////////////////////////////////////////////// + +/** \class TScatter2D + \ingroup Graphs +A TScatter2D is able to draw give variables scatter plot on a single plot. The three first +variables are the x, y and z position of the markers (stored in a TGraph2D), the fourth is +mapped on the current color map and the fifth on the marker size. + +The following example demonstrates how it works: + +Begin_Macro(source) +../../../tutorials/visualisation/graphs/gr019_scatter2d.C +End_Macro + +### TScatter2D's plotting options +TScatter2D can be drawn with the following options: + +| Option | Description | +|----------|-------------------------------------------------------------------| +| "SAME" | Superimpose on previous picture in the same pad.| + +*/ + + +//////////////////////////////////////////////////////////////////////////////// +/// TScatter2D default constructor. + +TScatter2D::TScatter2D() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// TScatter2D normal constructor. +/// +/// the arrays are preset to zero + +TScatter2D::TScatter2D(Int_t n) +{ + fGraph = new TGraph2D(n); + fNpoints = fGraph->GetN(); + + fColor = new Double_t[fNpoints]; + fSize = new Double_t[fNpoints]; + + memset(fColor, 0, fNpoints * sizeof(Double_t)); + memset(fSize, 0, fNpoints * sizeof(Double_t)); + fMaxMarkerSize = 5.; + fMinMarkerSize = 1.; + fMargin = 0.1; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// TScatter2D normal constructor. + +TScatter2D::TScatter2D(Int_t n, Double_t *x, Double_t *y, Double_t *z, const Double_t *col, const Double_t *size) +{ + Bool_t status = TH1::AddDirectoryStatus(); + TH1::AddDirectory(kFALSE); + fGraph = new TGraph2D(n, x, y, z); + fNpoints = fGraph->GetN(); + + Int_t bufsize = sizeof(Double_t) * fNpoints; + if (col) { + fColor = new Double_t[fNpoints]; + memcpy(fColor, col, bufsize); + } + if (size) { + fSize = new Double_t[fNpoints]; + memcpy(fSize, size, bufsize); + } + + fMaxMarkerSize = 5.; + fMinMarkerSize = 1.; + fMargin = 0.1; + + TH1::AddDirectory(status); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// TScatter2D default destructor. + +TScatter2D::~TScatter2D() +{ + delete fGraph; + delete [] fColor; + delete [] fSize; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Compute distance from point px,py,pz to a scatter plot. +/// +/// Compute the closest distance of approach from point px,py,pz to this scatter plot. +/// The distance is computed in pixels units. + +Int_t TScatter2D::DistancetoPrimitive(Int_t px, Int_t py) +{ + Int_t distance = 9999; + if (fGraph) distance = fGraph->DistancetoPrimitive(px, py); + return distance; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Execute action corresponding to one event. +/// +/// This member function is called when a graph is clicked with the locator +/// +/// If Left button clicked on one of the line end points, this point +/// follows the cursor until button is released. +/// +/// if Middle button clicked, the line is moved parallel to itself +/// until the button is released. + +void TScatter2D::ExecuteEvent(Int_t event, Int_t px, Int_t py) +{ + if (fGraph) fGraph->ExecuteEvent(event, px, py); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Returns a pointer to the histogram used to draw the axis + +TH2D *TScatter2D::GetHistogram() const +{ + return fGraph->GetHistogram();; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Get the scatter's x axis. + +TAxis *TScatter2D::GetXaxis() const +{ + return fGraph->GetXaxis(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Get the scatter's y axis. + +TAxis *TScatter2D::GetYaxis() const +{ + return fGraph->GetYaxis(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Get the scatter's z axis. + +TAxis *TScatter2D::GetZaxis() const +{ + return fGraph->GetZaxis(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Paint this scatter plot with its current attributes. + +void TScatter2D::Paint(Option_t *option) +{ + TVirtualGraphPainter *painter = TVirtualGraphPainter::GetPainter(); + if (painter) painter->PaintScatter2D(this, option); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Print graph and errors values. + +void TScatter2D::Print(Option_t *) const +{ + Double_t *X = fGraph->GetX(); + Double_t *Y = fGraph->GetY(); + Double_t *Z = fGraph->GetZ(); + for (Int_t i = 0; i < fNpoints; i++) { + printf("x[%d]=%g, y[%d]=%g, z[%d]=%g", i, X[i], i, Y[i], i, Z[i]); + if (fColor) printf(", color[%d]=%g", i, fColor[i]); + if (fSize) printf(", size[%d]=%g", i, fSize[i]); + printf("\n"); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set the margin around the plot in % + +void TScatter2D::SetMargin(Double_t margin) +{ + if (fMargin != margin) { + fMargin = margin; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Save primitive as a C++ statement(s) on output stream out + +void TScatter2D::SavePrimitive(std::ostream &out, Option_t *option /*= ""*/) +{ + char quote = '"'; + out << " " << std::endl; + static Int_t frameNumber = 1000; + frameNumber++; + + Int_t i; + Double_t *X = fGraph->GetX(); + Double_t *Y = fGraph->GetY(); + Double_t *Z = fGraph->GetZ(); + TString fXName = TString::Format("%s_fx%d",GetName(),frameNumber); + TString fYName = TString::Format("%s_fy%d", GetName(),frameNumber); + TString fZName = TString::Format("%s_fz%d", GetName(),frameNumber); + TString fColorName = TString::Format("%s_fcolor%d",GetName(),frameNumber); + TString fSizeName = TString::Format("%s_fsize%d",GetName(),frameNumber); + out << " Double_t " << fXName << "[" << fNpoints << "] = {" << std::endl; + for (i = 0; i < fNpoints-1; i++) out << " " << X[i] << "," << std::endl; + out << " " << X[fNpoints-1] << "};" << std::endl; + out << " Double_t " << fYName << "[" << fNpoints << "] = {" << std::endl; + for (i = 0; i < fNpoints-1; i++) out << " " << Y[i] << "," << std::endl; + out << " " << Y[fNpoints-1] << "};" << std::endl; + out << " Double_t " << fZName << "[" << fNpoints << "] = {" << std::endl; + for (i = 0; i < fNpoints-1; i++) out << " " << Z[i] << "," << std::endl; + out << " " << Z[fNpoints-1] << "};" << std::endl; + out << " Double_t " << fColorName << "[" << fNpoints << "] = {" << std::endl; + for (i = 0; i < fNpoints-1; i++) out << " " << fColor[i] << "," << std::endl; + out << " " << fColor[fNpoints-1] << "};" << std::endl; + out << " Double_t " << fSizeName << "[" << fNpoints << "] = {" << std::endl; + for (i = 0; i < fNpoints-1; i++) out << " " << fSize[i] << "," << std::endl; + out << " " << fSize[fNpoints-1] << "};" << std::endl; + + if (gROOT->ClassSaved(TScatter2D::Class())) + out << " "; + else + out << " TScatter2D *"; + out << "scat = new TScatter2D(" << fNpoints << "," << fXName << "," << fYName << "," << fZName << "," + << fColorName << "," << fSizeName << ");" << std::endl; + + out << " scat->SetName(" << quote << GetName() << quote << ");" << std::endl; + out << " scat->SetTitle(" << quote << GetTitle() << quote << ");" << std::endl; + out << " scat->SetMargin(" << GetMargin() << ");" << std::endl; + out << " scat->SetMinMarkerSize(" << GetMinMarkerSize() << ");" << std::endl; + out << " scat->SetMaxMarkerSize(" << GetMaxMarkerSize() << ");" << std::endl; + + SaveFillAttributes(out, "scat", 0, 1001); + SaveLineAttributes(out, "scat", 1, 1, 1); + SaveMarkerAttributes(out, "scat", 1, 1, 1); + + out << " scat->Draw(" << quote << option << quote << ");" << std::endl; +} diff --git a/hist/histpainter/inc/TGraphPainter.h b/hist/histpainter/inc/TGraphPainter.h index 8d360d2116c36..da2ea50367788 100644 --- a/hist/histpainter/inc/TGraphPainter.h +++ b/hist/histpainter/inc/TGraphPainter.h @@ -27,6 +27,7 @@ class TGraph; class TF1; class TScatter; +class TScatter2D; class TGraphPainter : public TVirtualGraphPainter { @@ -56,6 +57,7 @@ class TGraphPainter : public TVirtualGraphPainter { void PaintGraphReverse(TGraph *theGraph, Option_t *option); void PaintGraphSimple(TGraph *theGraph, Option_t *option); void PaintScatter(TScatter *theScatter, Option_t *option) override; + void PaintScatter2D(TScatter2D *theScatter, Option_t *option) override; void PaintPolyLineHatches(TGraph *theGraph, Int_t n, const Double_t *x, const Double_t *y); void PaintStats(TGraph *theGraph, TF1 *fit) override; void SetHighlight(TGraph *theGraph) override; diff --git a/hist/histpainter/src/TGraphPainter.cxx b/hist/histpainter/src/TGraphPainter.cxx index eb51453dd2bb6..635ea603119d0 100644 --- a/hist/histpainter/src/TGraphPainter.cxx +++ b/hist/histpainter/src/TGraphPainter.cxx @@ -29,6 +29,8 @@ #include "TGraphPolar.h" #include "TGraphQQ.h" #include "TScatter.h" +#include "TScatter2D.h" +#include "TGraph2D.h" #include "TPaletteAxis.h" #include "TLatex.h" #include "TArrow.h" @@ -4370,6 +4372,8 @@ void TGraphPainter::PaintGraphReverse(TGraph *theGraph, Option_t *option) void TGraphPainter::PaintScatter(TScatter *theScatter, Option_t* chopt) { + TGraph* theGraph = theScatter->GetGraph(); + Int_t optionAxis, optionSkipCol; TString opt = chopt; @@ -4378,35 +4382,26 @@ void TGraphPainter::PaintScatter(TScatter *theScatter, Option_t* chopt) if (opt.Contains("A")) optionAxis = 1; else optionAxis = 0; if (opt.Contains("SKIPCOL")) optionSkipCol = 1; else optionSkipCol = 0; - double *theX = theScatter->GetGraph()->GetX(); - double *theY = theScatter->GetGraph()->GetY(); - int n = theScatter->GetGraph()->GetN(); + double *theX = theGraph->GetX(); + double *theY = theGraph->GetY(); + int n = theGraph->GetN(); double *theColor = theScatter->GetColor(); double *theSize = theScatter->GetSize(); double MinMarkerSize = theScatter->GetMinMarkerSize(); double MaxMarkerSize = theScatter->GetMaxMarkerSize(); - double minx = DBL_MAX; - double maxx = -DBL_MAX; - double miny = DBL_MAX; - double maxy = -DBL_MAX; - double minc = DBL_MAX; - double maxc = -DBL_MAX; - double mins = DBL_MAX; - double maxs = -DBL_MAX; - for (int i=0; iGetHistogram(); - if (optionAxis) h->Paint(" "); - if (h->GetMinimum() < h->GetMaximum()) { - if (mincGetMinimum()) minc = h->GetMinimum(); - if (maxc>h->GetMaximum()) maxc = h->GetMaximum(); - } else { - Error("PaintScatter", "Mininal (%g) and Maximal (%g) values of the internal histogram are not valid",h->GetMinimum(),h->GetMaximum()); - } + if (optionAxis) { + h->Paint(" "); + if (h->GetMinimum() < h->GetMaximum()) { + if (mincGetMinimum()) minc = h->GetMinimum(); + if (maxc>h->GetMaximum()) maxc = h->GetMaximum(); + } else { + Error("PaintScatter", "Mininal (%g) and Maximal (%g) values of the internal histogram are not valid",h->GetMinimum(),h->GetMaximum()); + } - // Define and paint palette - if (theColor) { - TPaletteAxis *palette; - TList *functions = theScatter->GetGraph()->GetListOfFunctions(); - palette = (TPaletteAxis*)functions->FindObject("palette"); - TView *view = gPad->GetView(); - if (palette) { - if (view) { - if (!palette->TestBit(TPaletteAxis::kHasView)) { - functions->Remove(palette); - delete palette; palette = nullptr; - } - } else { - if (palette->TestBit(TPaletteAxis::kHasView)) { - functions->Remove(palette); - delete palette; palette = nullptr; + // Define and paint palette + if (theColor) { + TPaletteAxis *palette; + TList *functions = theGraph->GetListOfFunctions(); + palette = (TPaletteAxis*)functions->FindObject("palette"); + TView *view = gPad->GetView(); + if (palette) { + if (view) { + if (!palette->TestBit(TPaletteAxis::kHasView)) { + functions->Remove(palette); + delete palette; palette = nullptr; + } + } else { + if (palette->TestBit(TPaletteAxis::kHasView)) { + functions->Remove(palette); + delete palette; palette = nullptr; + } } } + if (!palette) { + Double_t xup = gPad->GetUxmax(); + Double_t x2 = gPad->PadtoX(gPad->GetX2()); + Double_t ymin = gPad->PadtoY(gPad->GetUymin()); + Double_t ymax = gPad->PadtoY(gPad->GetUymax()); + Double_t xr = 0.05*(gPad->GetX2() - gPad->GetX1()); + Double_t xmin = gPad->PadtoX(xup +0.1*xr); + Double_t xmax = gPad->PadtoX(xup + xr); + if (xmax > x2) xmax = gPad->PadtoX(gPad->GetX2()-0.01*xr); + palette = new TPaletteAxis(xmin,ymin,xmax,ymax,minc,maxc); + palette->SetLabelColor(h->GetZaxis()->GetLabelColor()); + palette->SetLabelFont(h->GetZaxis()->GetLabelFont()); + palette->SetLabelOffset(h->GetZaxis()->GetLabelOffset()); + palette->SetLabelSize(h->GetZaxis()->GetLabelSize()); + palette->SetTitleOffset(h->GetZaxis()->GetTitleOffset()); + palette->SetTitleSize(h->GetZaxis()->GetTitleSize()); + palette->SetNdivisions(h->GetZaxis()->GetNdivisions()); + palette->SetTitle(h->GetZaxis()->GetTitle()); + palette->SetTitleColor(h->GetZaxis()->GetTitleColor()); + palette->SetTitleFont(h->GetZaxis()->GetTitleFont()); + + functions->AddFirst(palette); + } + if (palette) palette->Paint(); } - if (!palette) { - Double_t xup = gPad->GetUxmax(); - Double_t x2 = gPad->PadtoX(gPad->GetX2()); - Double_t ymin = gPad->PadtoY(gPad->GetUymin()); - Double_t ymax = gPad->PadtoY(gPad->GetUymax()); - Double_t xr = 0.05*(gPad->GetX2() - gPad->GetX1()); - Double_t xmin = gPad->PadtoX(xup +0.1*xr); - Double_t xmax = gPad->PadtoX(xup + xr); - if (xmax > x2) xmax = gPad->PadtoX(gPad->GetX2()-0.01*xr); - palette = new TPaletteAxis(xmin,ymin,xmax,ymax,minc,maxc); - palette->SetLabelColor(h->GetZaxis()->GetLabelColor()); - palette->SetLabelFont(h->GetZaxis()->GetLabelFont()); - palette->SetLabelOffset(h->GetZaxis()->GetLabelOffset()); - palette->SetLabelSize(h->GetZaxis()->GetLabelSize()); - palette->SetTitleOffset(h->GetZaxis()->GetTitleOffset()); - palette->SetTitleSize(h->GetZaxis()->GetTitleSize()); - palette->SetNdivisions(h->GetZaxis()->GetNdivisions()); - palette->SetTitle(h->GetZaxis()->GetTitle()); - palette->SetTitleColor(h->GetZaxis()->GetTitleColor()); - palette->SetTitleFont(h->GetZaxis()->GetTitleFont()); - - functions->AddFirst(palette); + } else { + TScatter *s; + TIter next(gPad->GetListOfPrimitives()); + while ((s = (TScatter *)next())) { + if (!s->InheritsFrom(TScatter::Class())) continue; + if (theColor) { + double *ColorInPad = s->GetColor(); + if (ColorInPad) { + minc = TMath::MinElement(n, ColorInPad); + maxc = TMath::MaxElement(n, ColorInPad); + } + } + if (theSize) { + double *SizeInPad = s->GetSize(); + if (SizeInPad) { + mins = TMath::MinElement(n, SizeInPad); + maxs = TMath::MaxElement(n, SizeInPad); + } + } + break; } - if (palette) palette->Paint(); } // Draw markers @@ -4565,6 +4583,198 @@ void TGraphPainter::PaintScatter(TScatter *theScatter, Option_t* chopt) } +//////////////////////////////////////////////////////////////////////////////// +/// Paint a scatter plot + +void TGraphPainter::PaintScatter2D(TScatter2D *theScatter, Option_t* chopt) +{ + + TGraph2D* theGraph = theScatter->GetGraph(); + + Int_t optionSAME = 0, optionSkipCol = 0; + + TString opt = chopt; + opt.ToUpper(); + + if (opt.Contains("SAME")) { + optionSAME = 1; + opt.ReplaceAll("SAME"," "); + } + if (opt.Contains("SKIPCOL")) { + optionSkipCol = 1; + opt.ReplaceAll("SKIPCOL"," "); + } + + opt.Append("TRI0"); + + double *theX = theGraph->GetX(); + double *theY = theGraph->GetY(); + double *theZ = theGraph->GetZ(); + int n = theGraph->GetN(); + double *theColor = theScatter->GetColor(); + double *theSize = theScatter->GetSize(); + double MinMarkerSize = theScatter->GetMinMarkerSize(); + double MaxMarkerSize = theScatter->GetMaxMarkerSize(); + + double minc = 0, maxc = 0., mins = 0., maxs = 0.; + if (theColor) { + minc = TMath::MinElement(n, theColor); + maxc = TMath::MaxElement(n, theColor); + } + if (theSize) { + mins = TMath::MinElement(n, theSize); + maxs = TMath::MaxElement(n, theSize); + } + + // Make sure minimum and maximum values are different + Double_t d, e = 0.1; + if (theColor) { + if (minc == maxc) { + if (theColor[0] == 0.) { + minc = -e; + maxc = e; + } else { + d = TMath::Abs(theColor[0]*e); + minc = theColor[0] - d; + maxc = theColor[0] + d; + } + } + } + if (theSize) { + if (mins == maxs) { + if (theSize[0] == 0.) { + mins = -e; + maxs = e; + } else { + d = TMath::Abs(theSize[0]*e); + mins = theSize[0] - d; + maxs = theSize[0] + d; + } + } + } + + theGraph->SetTitle(theScatter->GetTitle()); + + if (!optionSAME) { + theGraph->Paint(opt.Data()); + + // Define and paint palette + if (theColor) { + TPaletteAxis *palette; + TList *functions = theScatter->GetGraph()->GetListOfFunctions(); + palette = (TPaletteAxis*)functions->FindObject("palette"); + TView *view = gPad->GetView(); + if (palette) { + if (view) { + if (!palette->TestBit(TPaletteAxis::kHasView)) { + functions->Remove(palette); + delete palette; palette = nullptr; + } + } else { + if (palette->TestBit(TPaletteAxis::kHasView)) { + functions->Remove(palette); + delete palette; palette = nullptr; + } + } + } + if (!palette) { + Double_t xup = gPad->GetUxmax(); + Double_t x2 = gPad->PadtoX(gPad->GetX2()); + Double_t ymin = gPad->PadtoY(gPad->GetUymin()); + Double_t ymax = gPad->PadtoY(gPad->GetUymax()); + Double_t xr = 0.05*(gPad->GetX2() - gPad->GetX1()); + Double_t xmin = gPad->PadtoX(xup +0.1*xr); + Double_t xmax = gPad->PadtoX(xup + xr); + if (xmax > x2) xmax = gPad->PadtoX(gPad->GetX2()-0.01*xr); + palette = new TPaletteAxis(xmin,ymin,xmax,ymax,minc,maxc); + palette->SetLabelColor(theGraph->GetZaxis()->GetLabelColor()); + palette->SetLabelFont(theGraph->GetZaxis()->GetLabelFont()); + palette->SetLabelOffset(theGraph->GetZaxis()->GetLabelOffset()); + palette->SetLabelSize(theGraph->GetZaxis()->GetLabelSize()); + // palette->SetTitleOffset(theGraph->GetZaxis()->GetTitleOffset()); + // palette->SetTitleSize(theGraph->GetZaxis()->GetTitleSize()); + palette->SetNdivisions(theGraph->GetZaxis()->GetNdivisions()); + // palette->SetTitle(theGraph->GetZaxis()->GetTitle()); + // palette->SetTitleColor(theGraph->GetZaxis()->GetTitleColor()); + // palette->SetTitleFont(theGraph->GetZaxis()->GetTitleFont()); + + functions->AddFirst(palette); + } + if (palette) palette->Paint(); + } + } else { + TScatter2D *s2; + TIter next(gPad->GetListOfPrimitives()); + while ((s2 = (TScatter2D *)next())) { + if (!s2->InheritsFrom(TScatter2D::Class())) continue; + if (theColor) { + double *ColorInPad = s2->GetColor(); + if (ColorInPad) { + minc = TMath::MinElement(n, ColorInPad); + maxc = TMath::MaxElement(n, ColorInPad); + } + } + if (theSize) { + double *SizeInPad = s2->GetSize(); + if (SizeInPad) { + mins = TMath::MinElement(n, SizeInPad); + maxs = TMath::MaxElement(n, SizeInPad); + } + } + break; + } + } + + // Draw markers + auto nbcol = gStyle->GetNumberOfColors(); + int logx = gPad->GetLogx(); + int logy = gPad->GetLogy(); + int logz = gPad->GetLogz(); + int nc; + double x,y,z,c, ms; + for (Int_t i = 0; i < n; i++) { + if (theColor) { + c = theColor[i]; + if (cmaxc) { + if (optionSkipCol) continue; + c = maxc; + } + nc = TMath::Nint(((c-minc)/(maxc-minc))*(nbcol-1)); + if (nc > nbcol-1) nc = nbcol-1; + theScatter->SetMarkerColor(gStyle->GetColorPalette(nc)); + } + if (theSize) { + ms = (MaxMarkerSize-MinMarkerSize)*((theSize[i]-mins)/(maxs-mins))+MinMarkerSize; + theScatter->SetMarkerSize(ms); + } + theScatter->TAttMarker::Modify(); + if (logx) { + if (theX[i]>0) x = log10(theX[i]); + else break; + } else { + x = theX[i]; + } + if (logy) { + if (theY[i]>0) y = log10(theY[i]); + else break; + } else { + y = theY[i]; + } + if (logz) { + if (theZ[i]>0) z = log10(theZ[i]); + else break; + } else { + z = theZ[i]; + } + gPad->PaintMarker3D(x, y, z); + } +} + + //////////////////////////////////////////////////////////////////////////////// /// Paint a simple graph, without errors bars. diff --git a/tutorials/visualisation/graphs/gr006_scatter.C b/tutorials/visualisation/graphs/gr006_scatter.C index b02839ee9761b..476c1859a763a 100644 --- a/tutorials/visualisation/graphs/gr006_scatter.C +++ b/tutorials/visualisation/graphs/gr006_scatter.C @@ -16,28 +16,40 @@ void gr006_scatter() gStyle->SetPalette(kBird, 0, 0.6); // define a transparent palette const int n = 175; - double x[n]; - double y[n]; - double c[n]; - double s[n]; + double x1[n]; + double y1[n]; + double c1[n]; + double s1[n]; + double x2[n]; + double y2[n]; + double c2[n]; + double s2[n]; // Define four random data sets auto r = new TRandom(); for (int i=0; iRndm(i); - y[i] = 200*r->Rndm(i); - c[i] = 300*r->Rndm(i); - s[i] = 400*r->Rndm(i); + x1[i] = 100*r->Rndm(i); + y1[i] = 200*r->Rndm(i); + c1[i] = 300*r->Rndm(i); + s1[i] = 400*r->Rndm(i); + x2[i] = 100*r->Rndm(i); + y2[i] = 200*r->Rndm(i); + c2[i] = 100*r->Rndm(i); + s2[i] = 200*r->Rndm(i); } - auto scatter = new TScatter(n, x, y, c, s); - scatter->SetMarkerStyle(20); - scatter->SetTitle("Scatter plot title;X title;Y title;Z title"); - scatter->GetXaxis()->SetRangeUser(20.,90.); - scatter->GetYaxis()->SetRangeUser(55.,90.); - scatter->GetZaxis()->SetRangeUser(10.,200.); + auto scatter1 = new TScatter(n, x1, y1, c1, s1); + scatter1->SetMarkerStyle(20); + scatter1->SetTitle("Scatter plot title;X title;Y title;Z title"); + scatter1->GetXaxis()->SetRangeUser(20.,90.); + scatter1->GetYaxis()->SetRangeUser(55.,90.); + scatter1->GetZaxis()->SetRangeUser(10.,200.); // an alternative way to zoom the Z-axis: // scatter->GetHistogram()->SetMinimum(10); // scatter->GetHistogram()->SetMaximum(200); - scatter->Draw("A"); + scatter1->Draw("A"); + + auto scatter2 = new TScatter(n, x2, y2, c2, s2); + scatter2->SetMarkerStyle(21); + scatter2->Draw(); } diff --git a/tutorials/visualisation/graphs/gr019_scatter2d.C b/tutorials/visualisation/graphs/gr019_scatter2d.C new file mode 100644 index 0000000000000..58152f2d7838d --- /dev/null +++ b/tutorials/visualisation/graphs/gr019_scatter2d.C @@ -0,0 +1,53 @@ +/// \file +/// \ingroup tutorial_graphs +/// \notebook +/// Draw a 2D scatter plot. +/// +/// \macro_image +/// \macro_code +/// +/// \author Olivier Couet + +void gr019_scatter2d() +{ + auto canvas = new TCanvas(); +// canvas->SetRightMargin(0.14); + gStyle->SetPalette(kBird, 0, 0.6); // define a transparent palette + + const int n = 50; + double x1[n]; + double y1[n]; + double z1[n]; + double c1[n]; + double s1[n]; + double x2[n]; + double y2[n]; + double z2[n]; + double c2[n]; + double s2[n]; + + // Define four random data set + auto r = new TRandom(); + for (int i=0; iRndm(i); + y1[i] = 200*r->Rndm(i); + z1[i] = 10*r->Rndm(i); + c1[i] = 100*r->Rndm(i); + s1[i] = 400*r->Rndm(i); + x2[i] = 100*r->Rndm(i); + y2[i] = 200*r->Rndm(i); + z2[i] = 10*r->Rndm(i); + c2[i] = 50*r->Rndm(i); + s2[i] = 100*r->Rndm(i); + } + + auto scatter1 = new TScatter2D(n, x1, y1, z1, c1, s1); + scatter1->SetTitle("Scatter plot title;X title;Y title;Z title;C title"); + scatter1->SetMarkerStyle(20); + + auto scatter2 = new TScatter2D(n, x2, y2, z2, c2, s2); + scatter2->SetMarkerStyle(21); + + scatter1->Draw(); + scatter2->Draw("SAME"); +} From 4096f75ee6cd08ea52f3e824d0f19254eb7991e0 Mon Sep 17 00:00:00 2001 From: Olivier Couet Date: Fri, 24 Oct 2025 15:03:26 +0200 Subject: [PATCH 2/4] Fix typo --- hist/hist/src/TScatter2D.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hist/hist/src/TScatter2D.cxx b/hist/hist/src/TScatter2D.cxx index a39b46fd55b3f..0205ae96c8311 100644 --- a/hist/hist/src/TScatter2D.cxx +++ b/hist/hist/src/TScatter2D.cxx @@ -22,7 +22,7 @@ /** \class TScatter2D \ingroup Graphs -A TScatter2D is able to draw give variables scatter plot on a single plot. The three first +A TScatter2D is able to draw five variables scatter plot on a single plot. The three first variables are the x, y and z position of the markers (stored in a TGraph2D), the fourth is mapped on the current color map and the fifth on the marker size. From 4ea121d87075a950ffdf1b626bfe179569dfb188 Mon Sep 17 00:00:00 2001 From: Olivier Couet Date: Fri, 24 Oct 2025 15:37:31 +0200 Subject: [PATCH 3/4] Add protection against null fGraph --- hist/hist/src/TScatter2D.cxx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hist/hist/src/TScatter2D.cxx b/hist/hist/src/TScatter2D.cxx index 0205ae96c8311..22890a5a6163f 100644 --- a/hist/hist/src/TScatter2D.cxx +++ b/hist/hist/src/TScatter2D.cxx @@ -145,7 +145,8 @@ void TScatter2D::ExecuteEvent(Int_t event, Int_t px, Int_t py) TH2D *TScatter2D::GetHistogram() const { - return fGraph->GetHistogram();; + if (fGraph) return fGraph->GetHistogram(); + else return nullptr; } @@ -154,7 +155,8 @@ TH2D *TScatter2D::GetHistogram() const TAxis *TScatter2D::GetXaxis() const { - return fGraph->GetXaxis(); + if (fGraph) return fGraph->GetXaxis(); + else return nullptr; } @@ -163,7 +165,8 @@ TAxis *TScatter2D::GetXaxis() const TAxis *TScatter2D::GetYaxis() const { - return fGraph->GetYaxis(); + if (fGraph) return fGraph->GetYaxis(); + else return nullptr; } @@ -172,7 +175,8 @@ TAxis *TScatter2D::GetYaxis() const TAxis *TScatter2D::GetZaxis() const { - return fGraph->GetZaxis(); + if (fGraph) return fGraph->GetZaxis(); + else return nullptr; } @@ -191,6 +195,8 @@ void TScatter2D::Paint(Option_t *option) void TScatter2D::Print(Option_t *) const { + if (!fGraph) return; + Double_t *X = fGraph->GetX(); Double_t *Y = fGraph->GetY(); Double_t *Z = fGraph->GetZ(); @@ -219,6 +225,8 @@ void TScatter2D::SetMargin(Double_t margin) void TScatter2D::SavePrimitive(std::ostream &out, Option_t *option /*= ""*/) { + if (!fGraph) return; + char quote = '"'; out << " " << std::endl; static Int_t frameNumber = 1000; From 79c6fdd39ed1b21382cc1fca10c5d386c73f7021 Mon Sep 17 00:00:00 2001 From: Olivier Couet Date: Fri, 24 Oct 2025 16:09:59 +0200 Subject: [PATCH 4/4] Change SavePrimitives to the new way (like TScatter.cxx) --- hist/hist/src/TScatter2D.cxx | 65 ++++++++++-------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/hist/hist/src/TScatter2D.cxx b/hist/hist/src/TScatter2D.cxx index 22890a5a6163f..6150a821a7102 100644 --- a/hist/hist/src/TScatter2D.cxx +++ b/hist/hist/src/TScatter2D.cxx @@ -225,54 +225,25 @@ void TScatter2D::SetMargin(Double_t margin) void TScatter2D::SavePrimitive(std::ostream &out, Option_t *option /*= ""*/) { - if (!fGraph) return; - - char quote = '"'; - out << " " << std::endl; - static Int_t frameNumber = 1000; - frameNumber++; - - Int_t i; - Double_t *X = fGraph->GetX(); - Double_t *Y = fGraph->GetY(); - Double_t *Z = fGraph->GetZ(); - TString fXName = TString::Format("%s_fx%d",GetName(),frameNumber); - TString fYName = TString::Format("%s_fy%d", GetName(),frameNumber); - TString fZName = TString::Format("%s_fz%d", GetName(),frameNumber); - TString fColorName = TString::Format("%s_fcolor%d",GetName(),frameNumber); - TString fSizeName = TString::Format("%s_fsize%d",GetName(),frameNumber); - out << " Double_t " << fXName << "[" << fNpoints << "] = {" << std::endl; - for (i = 0; i < fNpoints-1; i++) out << " " << X[i] << "," << std::endl; - out << " " << X[fNpoints-1] << "};" << std::endl; - out << " Double_t " << fYName << "[" << fNpoints << "] = {" << std::endl; - for (i = 0; i < fNpoints-1; i++) out << " " << Y[i] << "," << std::endl; - out << " " << Y[fNpoints-1] << "};" << std::endl; - out << " Double_t " << fZName << "[" << fNpoints << "] = {" << std::endl; - for (i = 0; i < fNpoints-1; i++) out << " " << Z[i] << "," << std::endl; - out << " " << Z[fNpoints-1] << "};" << std::endl; - out << " Double_t " << fColorName << "[" << fNpoints << "] = {" << std::endl; - for (i = 0; i < fNpoints-1; i++) out << " " << fColor[i] << "," << std::endl; - out << " " << fColor[fNpoints-1] << "};" << std::endl; - out << " Double_t " << fSizeName << "[" << fNpoints << "] = {" << std::endl; - for (i = 0; i < fNpoints-1; i++) out << " " << fSize[i] << "," << std::endl; - out << " " << fSize[fNpoints-1] << "};" << std::endl; - - if (gROOT->ClassSaved(TScatter2D::Class())) - out << " "; - else - out << " TScatter2D *"; - out << "scat = new TScatter2D(" << fNpoints << "," << fXName << "," << fYName << "," << fZName << "," - << fColorName << "," << fSizeName << ");" << std::endl; - - out << " scat->SetName(" << quote << GetName() << quote << ");" << std::endl; - out << " scat->SetTitle(" << quote << GetTitle() << quote << ");" << std::endl; - out << " scat->SetMargin(" << GetMargin() << ");" << std::endl; - out << " scat->SetMinMarkerSize(" << GetMinMarkerSize() << ");" << std::endl; - out << " scat->SetMaxMarkerSize(" << GetMaxMarkerSize() << ");" << std::endl; - + TString arr_x = SavePrimitiveVector(out, "scat_x", fNpoints, fGraph->GetX(), kTRUE); + TString arr_y = SavePrimitiveVector(out, "scat_y", fNpoints, fGraph->GetY()); + TString arr_z = SavePrimitiveVector(out, "scat_z", fNpoints, fGraph->GetZ()); + TString arr_col = SavePrimitiveVector(out, "scat_col", fNpoints, fColor); + TString arr_size = SavePrimitiveVector(out, "scat_size", fNpoints, fSize); + + SavePrimitiveConstructor(out, Class(), "scat", + TString::Format("%d, %s.data(), %s.data(), %s.data(), %s.data(), %s.data()", fNpoints, arr_x.Data(), + arr_y.Data(), arr_z.Data(), arr_col.Data(), arr_size.Data()), + kFALSE); + + SavePrimitiveNameTitle(out, "scat"); SaveFillAttributes(out, "scat", 0, 1001); SaveLineAttributes(out, "scat", 1, 1, 1); SaveMarkerAttributes(out, "scat", 1, 1, 1); - out << " scat->Draw(" << quote << option << quote << ");" << std::endl; -} + out << " scat->SetMargin(" << GetMargin() << ");\n"; + out << " scat->SetMinMarkerSize(" << GetMinMarkerSize() << ");\n"; + out << " scat->SetMaxMarkerSize(" << GetMaxMarkerSize() << ");\n"; + + SavePrimitiveDraw(out, "scat", option); +} \ No newline at end of file