From f8cd7f5e1382063108db8ceca8f49239a45019b6 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Thu, 21 Sep 2023 13:59:22 +0200 Subject: [PATCH] groot: add support for TScatter Signed-off-by: Sebastien Binet --- groot/gen.rboot.go | 1 + groot/rdict/cxx_root_streamers_gen.go | 158 +++++++++++++++++++++ groot/rhist/rw_test.go | 4 + groot/rhist/scatter.go | 196 ++++++++++++++++++++++++++ groot/riofs/gendata/gen-tscatter.go | 57 ++++++++ groot/riofs/riofs.go | 1 + groot/rvers/versions_gen.go | 1 + groot/testdata/tscatter.root | Bin 0 -> 4990 bytes 8 files changed, 418 insertions(+) create mode 100644 groot/rhist/scatter.go create mode 100644 groot/riofs/gendata/gen-tscatter.go create mode 100644 groot/testdata/tscatter.root diff --git a/groot/gen.rboot.go b/groot/gen.rboot.go index 814dd4a0..b48ad9ee 100644 --- a/groot/gen.rboot.go +++ b/groot/gen.rboot.go @@ -82,6 +82,7 @@ var ( "TLimit", "TLimitDataSource", "TMultiGraph", "TProfile", "TProfile2D", + "TScatter", // riofs "TDirectory", diff --git a/groot/rdict/cxx_root_streamers_gen.go b/groot/rdict/cxx_root_streamers_gen.go index 5847debf..47a4f992 100644 --- a/groot/rdict/cxx_root_streamers_gen.go +++ b/groot/rdict/cxx_root_streamers_gen.go @@ -4946,6 +4946,164 @@ func init() { Factor: 0.000000, }.New()}, })) + StreamerInfos.Add(NewCxxStreamerInfo("TScatter", 2, 0xc091e335, []rbytes.StreamerElement{ + NewStreamerBase(Element{ + Name: *rbase.NewNamed("TNamed", "The basis for a named object (name, title)"), + Type: rmeta.Base, + Size: 0, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, -541636036, 0, 0, 0}, + Offset: 0, + EName: "BASE", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 1), + NewStreamerBase(Element{ + Name: *rbase.NewNamed("TAttLine", "Line attributes"), + Type: rmeta.Base, + Size: 0, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, -1811462839, 0, 0, 0}, + Offset: 0, + EName: "BASE", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 2), + NewStreamerBase(Element{ + Name: *rbase.NewNamed("TAttFill", "Fill area attributes"), + Type: rmeta.Base, + Size: 0, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, -2545006, 0, 0, 0}, + Offset: 0, + EName: "BASE", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 2), + NewStreamerBase(Element{ + Name: *rbase.NewNamed("TAttMarker", "Marker attributes"), + Type: rmeta.Base, + Size: 0, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 689802220, 0, 0, 0}, + Offset: 0, + EName: "BASE", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 2), + &StreamerBasicType{StreamerElement: Element{ + Name: *rbase.NewNamed("fNpoints", "Number of points <= fMaxSize"), + Type: rmeta.Counter, + Size: 4, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "int", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + &StreamerObjectPointer{StreamerElement: Element{ + Name: *rbase.NewNamed("fHistogram", "Pointer to histogram used for drawing axis"), + Type: rmeta.ObjectP, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "TH2F*", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + &StreamerObjectPointer{StreamerElement: Element{ + Name: *rbase.NewNamed("fGraph", "Pointer to graph holding X and Y positions"), + Type: rmeta.ObjectP, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "TGraph*", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + NewStreamerBasicPointer(Element{ + Name: *rbase.NewNamed("fColor", "[fNpoints] array of colors"), + Type: 48, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "double*", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 2, "fNpoints", "TScatter"), + NewStreamerBasicPointer(Element{ + Name: *rbase.NewNamed("fSize", "[fNpoints] array of marker sizes"), + Type: 48, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "double*", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 2, "fNpoints", "TScatter"), + &StreamerBasicType{StreamerElement: Element{ + Name: *rbase.NewNamed("fMaxMarkerSize", "Largest marker size used to paint the markers"), + Type: rmeta.Double, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "double", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + &StreamerBasicType{StreamerElement: Element{ + Name: *rbase.NewNamed("fMinMarkerSize", "Smallest marker size used to paint the markers"), + Type: rmeta.Double, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "double", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + &StreamerBasicType{StreamerElement: Element{ + Name: *rbase.NewNamed("fMargin", "Margin around the plot in %"), + Type: rmeta.Double, + Size: 8, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "double", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + })) StreamerInfos.Add(NewCxxStreamerInfo("TDirectory", 5, 0x1e9b6f70, []rbytes.StreamerElement{ NewStreamerBase(Element{ Name: *rbase.NewNamed("TNamed", "The basis for a named object (name, title)"), diff --git a/groot/rhist/rw_test.go b/groot/rhist/rw_test.go index cf49f2b3..2f5fa95e 100644 --- a/groot/rhist/rw_test.go +++ b/groot/rhist/rw_test.go @@ -280,6 +280,10 @@ func TestWRBuffer(t *testing.T) { name: "TMultiGraph", want: loadFrom("../testdata/tgme.root", "mg"), }, + { + name: "TScatter", + want: loadFrom("../testdata/tscatter.root", "scatter"), + }, } { t.Run(tc.name, func(t *testing.T) { { diff --git a/groot/rhist/scatter.go b/groot/rhist/scatter.go new file mode 100644 index 00000000..98dd1a4d --- /dev/null +++ b/groot/rhist/scatter.go @@ -0,0 +1,196 @@ +// Copyright ©2024 The go-hep Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rhist + +import ( + "fmt" + "math" + "reflect" + + "go-hep.org/x/hep/groot/rbase" + "go-hep.org/x/hep/groot/rbytes" + "go-hep.org/x/hep/groot/root" + "go-hep.org/x/hep/groot/rtypes" + "go-hep.org/x/hep/groot/rvers" +) + +// Scatter implements ROOT's TScatter. +// A scatter plot able to draw four variables on a single plot. +type Scatter struct { + rbase.Named + attline rbase.AttLine + attfill rbase.AttFill + attmarker rbase.AttMarker + + npoints int32 // Number of points <= fMaxSize + histo *H2F // Pointer to histogram used for drawing axis + graph *tgraph // Pointer to graph holding X and Y positions + color []float64 // [fNpoints] array of colors + size []float64 // [fNpoints] array of marker sizes + + maxMarkerSize float64 // Largest marker size used to paint the markers + minMarkerSize float64 // Smallest marker size used to paint the markers + margin float64 // Margin around the plot in % +} + +func newScatter(n int) *Scatter { + return &Scatter{ + Named: *rbase.NewNamed("", ""), + attline: *rbase.NewAttLine(), + attfill: *rbase.NewAttFill(), + attmarker: *rbase.NewAttMarker(), + npoints: int32(n), + color: make([]float64, n), + size: make([]float64, n), + maxMarkerSize: 5, + minMarkerSize: 1, + margin: 0.1, + } +} + +func (*Scatter) RVersion() int16 { + return rvers.Scatter +} + +func (*Scatter) Class() string { + return "TScatter" +} + +func (s *Scatter) ROOTMerge(src root.Object) error { + switch src := src.(type) { + case *Scatter: + var err error + s.npoints += src.npoints + // FIXME(sbinet): implement ROOTMerge for TH2x + // err = s.histo.ROOTMerge(src.histo) + // if err != nil { + // return fmt.Errorf("rhist: could not merge Scatter's underlying H2F: %w", err) + // } + err = s.graph.ROOTMerge(src.graph) + if err != nil { + return fmt.Errorf("rhist: could not merge Scatter's underlying Graph: %w", err) + } + s.color = append(s.color, src.color...) + s.size = append(s.size, src.size...) + s.maxMarkerSize = math.Max(s.maxMarkerSize, src.maxMarkerSize) + s.minMarkerSize = math.Min(s.minMarkerSize, src.minMarkerSize) + // FIXME(sbinet): handle margin + return nil + default: + return fmt.Errorf("rhist: can not merge %T into %T", src, s) + } +} + +// ROOTMarshaler is the interface implemented by an object that can +// marshal itself to a ROOT buffer +func (s *Scatter) MarshalROOT(w *rbytes.WBuffer) (int, error) { + if w.Err() != nil { + return 0, w.Err() + } + + hdr := w.WriteHeader(s.Class(), s.RVersion()) + + w.WriteObject(&s.Named) + w.WriteObject(&s.attline) + w.WriteObject(&s.attfill) + w.WriteObject(&s.attmarker) + + w.WriteI32(s.npoints) + w.WriteObjectAny(s.histo) + w.WriteObjectAny(s.graph) + + w.WriteI8(1) + w.WriteArrayF64(s.color) + w.WriteI8(1) + w.WriteArrayF64(s.size) + + w.WriteF64(s.maxMarkerSize) + w.WriteF64(s.minMarkerSize) + w.WriteF64(s.margin) + + return w.SetHeader(hdr) +} + +// ROOTUnmarshaler is the interface implemented by an object that can +// unmarshal itself from a ROOT buffer +func (s *Scatter) UnmarshalROOT(r *rbytes.RBuffer) error { + if r.Err() != nil { + return r.Err() + } + + hdr := r.ReadHeader(s.Class(), s.RVersion()) + + r.ReadObject(&s.Named) + r.ReadObject(&s.attline) + r.ReadObject(&s.attfill) + r.ReadObject(&s.attmarker) + + s.npoints = r.ReadI32() + if hdr.Vers < 2 { + r.SetErr(fmt.Errorf("rhist: invalid TScatter version %d", hdr.Vers)) + return r.Err() + } + + histo := r.ReadObjectAny() + if histo != nil { + s.histo = histo.(*H2F) + } + graph := r.ReadObjectAny() + if graph != nil { + s.graph = graph.(*tgraph) + } + + _ = r.ReadI8() + s.color = make([]float64, s.npoints) + r.ReadArrayF64(s.color) + _ = r.ReadI8() + s.size = make([]float64, s.npoints) + r.ReadArrayF64(s.size) + + s.maxMarkerSize = r.ReadF64() + s.minMarkerSize = r.ReadF64() + s.margin = r.ReadF64() + + r.CheckHeader(hdr) + return r.Err() +} + +func (g *Scatter) RMembers() (mbrs []rbytes.Member) { + mbrs = append(mbrs, g.Named.RMembers()...) + mbrs = append(mbrs, g.attline.RMembers()...) + mbrs = append(mbrs, g.attfill.RMembers()...) + mbrs = append(mbrs, g.attmarker.RMembers()...) + mbrs = append(mbrs, []rbytes.Member{ + {Name: "fNpoints", Value: &g.npoints}, + {Name: "fHistogram", Value: &g.histo}, + {Name: "fGraph", Value: &g.graph}, + {Name: "fColor", Value: &g.color}, + {Name: "fSize", Value: &g.size}, + {Name: "fMaxMarkerSize", Value: &g.maxMarkerSize}, + {Name: "fMinMarkerSize", Value: &g.minMarkerSize}, + {Name: "fMargin", Value: &g.margin}, + }...) + + return mbrs +} + +func init() { + { + f := func() reflect.Value { + o := newScatter(0) + return reflect.ValueOf(o) + } + rtypes.Factory.Add("TScatter", f) + } +} + +var ( + _ root.Object = (*Scatter)(nil) + _ root.Named = (*Scatter)(nil) + _ root.Merger = (*Scatter)(nil) + _ rbytes.Marshaler = (*Scatter)(nil) + _ rbytes.Unmarshaler = (*Scatter)(nil) + _ rbytes.RSlicer = (*Scatter)(nil) +) diff --git a/groot/riofs/gendata/gen-tscatter.go b/groot/riofs/gendata/gen-tscatter.go new file mode 100644 index 00000000..3dfc6bc5 --- /dev/null +++ b/groot/riofs/gendata/gen-tscatter.go @@ -0,0 +1,57 @@ +// Copyright ©2024 The go-hep Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +package main + +import ( + "flag" + "log" + + "go-hep.org/x/hep/groot/internal/rtests" +) + +var ( + root = flag.String("f", "test-tscatter.root", "output ROOT file") +) + +func main() { + flag.Parse() + + out, err := rtests.RunCxxROOT("gentscatter", []byte(script), *root) + if err != nil { + log.Fatalf("could not run ROOT macro:\noutput:\n%v\nerror: %+v", string(out), err) + } +} + +const script = ` +void gentscatter(const char* fname) { + auto f = TFile::Open(fname, "RECREATE"); + + const int n = 5; + double xs[n] = {0, 1, 2, 3, 4}; + double ys[n] = {0, 2, 4, 6, 8}; + double cs[n] = {1, 3, 5, 7, 9}; + double ss[n] = {2, 4, 6, 8, 10}; + + auto s = new TScatter(n, xs, ys, cs, ss); + s->SetMarkerStyle(20); + s->SetMarkerColor(kRed); + s->SetTitle("Scatter plot;X;Y"); + s->SetName("scatter"); + + s->Draw("A"); // generate underlying TH2F. + auto h = s->GetHistogram(); + if (h == NULL) { + exit(1); + } + + f->WriteTObject(s); + f->Write(); + f->Close(); + + exit(0); +} +` diff --git a/groot/riofs/riofs.go b/groot/riofs/riofs.go index 0d6e772e..0c3b8b91 100644 --- a/groot/riofs/riofs.go +++ b/groot/riofs/riofs.go @@ -33,6 +33,7 @@ import ( //go:generate go run ./gendata/gen-tconflvl.go -f ../testdata/tconfidence-level.root //go:generate go run ./gendata/gen-tprofile.go -f ../testdata/tprofile.root //go:generate go run ./gendata/gen-tgme.go -f ../testdata/tgme.root +//go:generate go run ./gendata/gen-tscatter.go -f ../testdata/tscatter.root //go:generate go run ./gendata/gen-tdatime.go -f ../testdata/tdatime.root //go:generate go run ./gendata/gen-base.go -f ../testdata/tbase.root //go:generate go run ./gendata/gen-tcanvas.go -f ../testdata/tcanvas.root diff --git a/groot/rvers/versions_gen.go b/groot/rvers/versions_gen.go index af4e0616..d1e0cb38 100644 --- a/groot/rvers/versions_gen.go +++ b/groot/rvers/versions_gen.go @@ -94,6 +94,7 @@ const ( MultiGraph = 2 // ROOT version for TMultiGraph Profile = 7 // ROOT version for TProfile Profile2D = 8 // ROOT version for TProfile2D + Scatter = 2 // ROOT version for TScatter Directory = 5 // ROOT version for TDirectory DirectoryFile = 5 // ROOT version for TDirectoryFile File = 8 // ROOT version for TFile diff --git a/groot/testdata/tscatter.root b/groot/testdata/tscatter.root new file mode 100644 index 0000000000000000000000000000000000000000..62ece1c3dbf52d9b538ed19f46cc558d7c840bf8 GIT binary patch literal 4990 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 literal 0 HcmV?d00001