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 00000000..62ece1c3 Binary files /dev/null and b/groot/testdata/tscatter.root differ