diff --git a/hplot/README.md b/hplot/README.md index 10afe6eea..8db9e3aa3 100644 --- a/hplot/README.md +++ b/hplot/README.md @@ -875,10 +875,9 @@ func ExampleHStack_withBand() { hh1.LineStyle.Color = color.Black hh1.Band.FillColor = color.NRGBA{G: 210, A: 200} - hh2 := hplot.NewH1D(h2, hplot.WithBand(true)) + hh2 := hplot.NewH1D(h2, hplot.WithBand(false)) hh2.FillColor = colors[1] hh2.LineStyle.Width = 0 - hh2.Band.FillColor = color.NRGBA{B: 220, A: 200} hh3 := hplot.NewH1D(h3, hplot.WithBand(true)) hh3.FillColor = colors[2] @@ -887,12 +886,28 @@ func ExampleHStack_withBand() { hs := []*hplot.H1D{hh1, hh2, hh3} - tp := hplot.NewTiledPlot(draw.Tiles{Cols: 1, Rows: 2}) + hh4 := hplot.NewH1D(h1) + hh4.FillColor = colors[0] + hh4.LineStyle.Color = color.Black + + hh5 := hplot.NewH1D(h2) + hh5.FillColor = colors[1] + hh5.LineStyle.Width = 0 + + hh6 := hplot.NewH1D(h3) + hh6.FillColor = colors[2] + hh6.LineStyle.Color = color.Black + + hsHistoNoBand := []*hplot.H1D{hh4, hh5, hh6} + + tp := hplot.NewTiledPlot(draw.Tiles{Cols: 2, Rows: 2}) tp.Align = true { p := tp.Plot(0, 0) - p.Title.Text = "With Band, Stack: OFF" + p.Title.Text = "Histos With or Without Band, Stack: OFF" + p.Title.Padding = 10 + p.X.Label.Text = "X" p.Y.Label.Text = "Y" hstack := hplot.NewHStack(hs, hplot.WithBand(true)) hstack.Stack = hplot.HStackOff @@ -904,9 +919,27 @@ func ExampleHStack_withBand() { p.Legend.Left = true } + { + p := tp.Plot(0, 1) + p.Title.Text = "Histos Without Band, Stack: OFF" + p.Title.Padding = 10 + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true)) + hstack.Stack = hplot.HStackOff + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + { p := tp.Plot(1, 0) - p.Title.Text = "With Band, Stack: ON" + p.Title.Text = "Histos With or Without Band, Stack: ON" + p.Title.Padding = 10 p.X.Label.Text = "X" p.Y.Label.Text = "Y" hstack := hplot.NewHStack(hs, hplot.WithBand(true)) @@ -919,7 +952,159 @@ func ExampleHStack_withBand() { p.Legend.Left = true } - err := tp.Save(15*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_band.png") + { + p := tp.Plot(1, 1) + p.Title.Text = "Histos Without Band, Stack: ON" + p.Title.Padding = 10 + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true)) + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + err := tp.Save(25*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_band.png") + if err != nil { + log.Fatalf("error: %+v", err) + } +} +``` + +### Stack of 1D histograms with a band, with a log-y scale + +![hstack-logy-example](https://github.com/go-hep/hep/raw/master/hplot/testdata/hstack_logy_golden.png) + +[embedmd]:# (example_hstack_test.go go /func ExampleHStack_withLogY/ /\n}/) +```go +func ExampleHStack_withLogY() { + h1 := hbook.NewH1D(50, -8, 12) + h2 := hbook.NewH1D(50, -8, 12) + h3 := hbook.NewH1D(50, -8, 12) + + const seed = 1234 + fillH1(h1, 2000, -2, 1, seed) + fillH1(h2, 2000, +3, 3, seed) + fillH1(h3, 2000, +4, 1, seed) + + colors := []color.Color{ + color.NRGBA{122, 195, 106, 150}, + color.NRGBA{90, 155, 212, 150}, + color.NRGBA{250, 167, 91, 150}, + } + logy := hplot.WithLogY(true) + + hh1 := hplot.NewH1D(h1, hplot.WithBand(true), logy) + hh1.FillColor = colors[0] + hh1.LineStyle.Color = color.Black + hh1.Band.FillColor = color.NRGBA{G: 210, A: 200} + + hh2 := hplot.NewH1D(h2, hplot.WithBand(false), logy) + hh2.FillColor = colors[1] + hh2.LineStyle.Width = 0 + + hh3 := hplot.NewH1D(h3, hplot.WithBand(true), logy) + hh3.FillColor = colors[2] + hh3.LineStyle.Color = color.Black + hh3.Band.FillColor = color.NRGBA{R: 220, A: 200} + + hs := []*hplot.H1D{hh1, hh2, hh3} + + hh4 := hplot.NewH1D(h1, logy) + hh4.FillColor = colors[0] + hh4.LineStyle.Color = color.Black + + hh5 := hplot.NewH1D(h2, logy) + hh5.FillColor = colors[1] + hh5.LineStyle.Width = 0 + + hh6 := hplot.NewH1D(h3, logy) + hh6.FillColor = colors[2] + hh6.LineStyle.Color = color.Black + + hsHistoNoBand := []*hplot.H1D{hh4, hh5, hh6} + + tp := hplot.NewTiledPlot(draw.Tiles{Cols: 2, Rows: 2}) + tp.Align = true + + { + p := tp.Plot(0, 0) + p.Title.Text = "Histos With or Without Band, Stack: OFF" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy) + hstack.Stack = hplot.HStackOff + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(0, 1) + p.Title.Text = "Histos Without Band, Stack: OFF" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy) + hstack.Stack = hplot.HStackOff + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(1, 0) + p.Title.Text = "Histos With or Without Band, Stack: ON" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy) + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(1, 1) + p.Title.Text = "Histos Without Band, Stack: ON" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy) + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + err := tp.Save(25*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_logy.png") if err != nil { log.Fatalf("error: %+v", err) } diff --git a/hplot/example_hstack_test.go b/hplot/example_hstack_test.go index 5995b09e3..06fe283a9 100644 --- a/hplot/example_hstack_test.go +++ b/hplot/example_hstack_test.go @@ -12,6 +12,7 @@ import ( "go-hep.org/x/hep/hplot" "golang.org/x/exp/rand" "gonum.org/v1/gonum/stat/distuv" + "gonum.org/v1/plot" "gonum.org/v1/plot/vg" "gonum.org/v1/plot/vg/draw" ) @@ -149,6 +150,7 @@ func ExampleHStack_withBand() { p := tp.Plot(0, 0) p.Title.Text = "Histos With or Without Band, Stack: OFF" p.Title.Padding = 10 + p.X.Label.Text = "X" p.Y.Label.Text = "Y" hstack := hplot.NewHStack(hs, hplot.WithBand(true)) hstack.Stack = hplot.HStackOff @@ -181,6 +183,7 @@ func ExampleHStack_withBand() { p := tp.Plot(1, 0) p.Title.Text = "Histos With or Without Band, Stack: ON" p.Title.Padding = 10 + p.X.Label.Text = "X" p.Y.Label.Text = "Y" hstack := hplot.NewHStack(hs, hplot.WithBand(true)) hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} @@ -214,6 +217,135 @@ func ExampleHStack_withBand() { } } +func ExampleHStack_withLogY() { + h1 := hbook.NewH1D(50, -8, 12) + h2 := hbook.NewH1D(50, -8, 12) + h3 := hbook.NewH1D(50, -8, 12) + + const seed = 1234 + fillH1(h1, 2000, -2, 1, seed) + fillH1(h2, 2000, +3, 3, seed) + fillH1(h3, 2000, +4, 1, seed) + + colors := []color.Color{ + color.NRGBA{122, 195, 106, 150}, + color.NRGBA{90, 155, 212, 150}, + color.NRGBA{250, 167, 91, 150}, + } + logy := hplot.WithLogY(true) + + hh1 := hplot.NewH1D(h1, hplot.WithBand(true), logy) + hh1.FillColor = colors[0] + hh1.LineStyle.Color = color.Black + hh1.Band.FillColor = color.NRGBA{G: 210, A: 200} + + hh2 := hplot.NewH1D(h2, hplot.WithBand(false), logy) + hh2.FillColor = colors[1] + hh2.LineStyle.Width = 0 + + hh3 := hplot.NewH1D(h3, hplot.WithBand(true), logy) + hh3.FillColor = colors[2] + hh3.LineStyle.Color = color.Black + hh3.Band.FillColor = color.NRGBA{R: 220, A: 200} + + hs := []*hplot.H1D{hh1, hh2, hh3} + + hh4 := hplot.NewH1D(h1, logy) + hh4.FillColor = colors[0] + hh4.LineStyle.Color = color.Black + + hh5 := hplot.NewH1D(h2, logy) + hh5.FillColor = colors[1] + hh5.LineStyle.Width = 0 + + hh6 := hplot.NewH1D(h3, logy) + hh6.FillColor = colors[2] + hh6.LineStyle.Color = color.Black + + hsHistoNoBand := []*hplot.H1D{hh4, hh5, hh6} + + tp := hplot.NewTiledPlot(draw.Tiles{Cols: 2, Rows: 2}) + tp.Align = true + + { + p := tp.Plot(0, 0) + p.Title.Text = "Histos With or Without Band, Stack: OFF" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy) + hstack.Stack = hplot.HStackOff + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(0, 1) + p.Title.Text = "Histos Without Band, Stack: OFF" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy) + hstack.Stack = hplot.HStackOff + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(1, 0) + p.Title.Text = "Histos With or Without Band, Stack: ON" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hs, hplot.WithBand(true), logy) + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + { + p := tp.Plot(1, 1) + p.Title.Text = "Histos Without Band, Stack: ON" + p.Title.Padding = 10 + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = plot.LogTicks{} + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + hstack := hplot.NewHStack(hsHistoNoBand, hplot.WithBand(true), logy) + hstack.Band.FillColor = color.NRGBA{R: 100, G: 100, B: 100, A: 200} + p.Add(hstack, hplot.NewGrid()) + p.Legend.Add("h1", hs[0]) + p.Legend.Add("h2", hs[1]) + p.Legend.Add("h3", hs[2]) + p.Legend.Top = true + p.Legend.Left = true + } + + err := tp.Save(25*vg.Centimeter, 15*vg.Centimeter, "testdata/hstack_logy.png") + if err != nil { + log.Fatalf("error: %+v", err) + } +} + func fillH1(h *hbook.H1D, n int, mu, sigma float64, seed uint64) { dist := distuv.Normal{ Mu: mu, diff --git a/hplot/h1d.go b/hplot/h1d.go index a558e3129..5e4d1ef8f 100644 --- a/hplot/h1d.go +++ b/hplot/h1d.go @@ -171,25 +171,32 @@ func (h1 *H1D) withBand() error { bins := h1.Hist.Binning.Bins var ( - top = make(plotter.XYs, 2*len(bins)) - bot = make(plotter.XYs, 2*len(bins)) + top = make(plotter.XYs, 0, 2*len(bins)) + bot = make(plotter.XYs, 0, 2*len(bins)) ) - for i := range top { - ibin := i / 2 - bin := bins[ibin] - xmin, xmax := bin.XEdges().Min, bin.XEdges().Max + for i := 0; i < 2*len(bins); i++ { + var ( + ibin = i / 2 + bin = bins[ibin] + xmin = bin.XEdges().Min + xmax = bin.XEdges().Max + sumw = bin.SumW() + errw = bin.ErrW() + ymin = sumw - 0.5*errw + ymax = sumw + 0.5*errw + ) + switch { + case h1.LogY && ymin <= 0: + continue + } switch { case i%2 != 0: - top[i].X = xmax - top[i].Y = bin.SumW() - 0.5*bin.ErrW() - bot[i].X = xmax - bot[i].Y = bin.SumW() + 0.5*bin.ErrW() + top = append(top, plotter.XY{X: xmax, Y: ymin}) + bot = append(bot, plotter.XY{X: xmax, Y: ymax}) default: - top[i].X = xmin - top[i].Y = bin.SumW() - 0.5*bin.ErrW() - bot[i].X = xmin - bot[i].Y = bin.SumW() + 0.5*bin.ErrW() + top = append(top, plotter.XY{X: xmin, Y: ymin}) + bot = append(bot, plotter.XY{X: xmin, Y: ymax}) } } diff --git a/hplot/hstack.go b/hplot/hstack.go index 1e59689eb..c6b0e4074 100644 --- a/hplot/hstack.go +++ b/hplot/hstack.go @@ -89,8 +89,10 @@ func NewHStack(histos []*H1D, opts ...Options) *HStack { } if cfg.band { - plotHtot := NewH1D(hstack.summedH1D(), WithBand(true)) - hstack.Band = plotHtot.Band + hstack.Band = NewH1D(hstack.summedH1D(), + WithBand(true), + WithLogY(cfg.log.y), + ).Band } return hstack diff --git a/hplot/hstack_test.go b/hplot/hstack_test.go index 584c9a2c0..d3c164aaa 100644 --- a/hplot/hstack_test.go +++ b/hplot/hstack_test.go @@ -19,6 +19,7 @@ import ( func TestHStack(t *testing.T) { checkPlot(cmpimg.CheckPlot)(ExampleHStack, t, "hstack.png") checkPlot(cmpimg.CheckPlot)(ExampleHStack_withBand, t, "hstack_band.png") + checkPlot(cmpimg.CheckPlot)(ExampleHStack_withLogY, t, "hstack_logy.png") } func TestHStackPanic(t *testing.T) { diff --git a/hplot/testdata/hstack_band_golden.png b/hplot/testdata/hstack_band_golden.png index 6f72c039a..604d9bd2a 100644 Binary files a/hplot/testdata/hstack_band_golden.png and b/hplot/testdata/hstack_band_golden.png differ diff --git a/hplot/testdata/hstack_logy_golden.png b/hplot/testdata/hstack_logy_golden.png new file mode 100644 index 000000000..8a53f3861 Binary files /dev/null and b/hplot/testdata/hstack_logy_golden.png differ