Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions hplot/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,20 @@ type StepsKind byte

const (
NoSteps StepsKind = iota

// HiSteps connects two points by following lines: horizontal, vertical, horizontal.
// Vertical line is placed following the histogram/error-bins informations.
HiSteps

// PreSteps connects two points by following lines: vertical, horizontal.
PreSteps

// MidSteps connects two points by following lines: horizontal, vertical, horizontal.
// Vertical line is placed in the middle of the interval.
MidSteps

// PostSteps connects two points by following lines: horizontal, vertical.
PostSteps
)

type config struct {
Expand Down
63 changes: 62 additions & 1 deletion hplot/s2d.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ func (pts *S2D) withBand() error {
bot[i].Y = y - math.Abs(ymin)
}
}
case PreSteps:
panic("presteps not implemented")
case MidSteps:
panic("midsteps not implemented")
case PostSteps:
panic("poststeps not implemented")
}
pts.Band = NewBand(color.Gray{200}, top, bot)
return nil
Expand Down Expand Up @@ -162,6 +168,18 @@ func NewS2D(data plotter.XYer, opts ...Options) *S2D {
s.GlyphStyle = cfg.glyph
}

switch s.Steps {
case HiSteps:
// check we have ErrX for all data points.
xerrs := s.Data.(plotter.XErrorer)
for i := range s.Data.Len() {
xmin, xmax := xerrs.XError(i)
if xmin == 0 && xmax == 0 {
panic("s2d with HiSteps needs XErr informations for all points")
}
}
}

return s
}

Expand All @@ -185,7 +203,8 @@ func (pts *S2D) Plot(c draw.Canvas, plt *plot.Plot) {
panic(err)
}

if pts.Steps == HiSteps {
switch pts.Steps {
case HiSteps:
xerr := pts.Data.(plotter.XErrorer)
dsteps := make(plotter.XYs, 0, 2*len(data))
for i, d := range data {
Expand All @@ -194,6 +213,48 @@ func (pts *S2D) Plot(c draw.Canvas, plt *plot.Plot) {
dsteps = append(dsteps, plotter.XY{X: d.X + xmax, Y: d.Y})
}
data = dsteps
case PreSteps:
var (
prev plotter.XY
dsteps = make(plotter.XYs, 0, 2*len(data))
)
prev.X, prev.Y = data.XY(0)
dsteps = append(dsteps, prev)
for _, pt := range data[1:] {
dsteps = append(dsteps, plotter.XY{X: prev.X, Y: pt.Y})
dsteps = append(dsteps, pt)
prev = pt
}
data = dsteps
case MidSteps:
var (
prev plotter.XY
dsteps = make(plotter.XYs, 0, 2*len(data))
)
prev.X, prev.Y = data.XY(0)
dsteps = append(dsteps, prev)
for _, pt := range data[1:] {
dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: prev.Y})
dsteps = append(dsteps, plotter.XY{X: 0.5 * (prev.X + pt.X), Y: pt.Y})
dsteps = append(dsteps, pt)
prev = pt
}
data = dsteps
case PostSteps:
var (
prev plotter.XY
dsteps = make(plotter.XYs, 0, 2*len(data))
)
prev.X, prev.Y = data.XY(0)
dsteps = append(dsteps, prev)
for _, pt := range data[1:] {
dsteps = append(dsteps, plotter.XY{X: pt.X, Y: prev.Y})
dsteps = append(dsteps, pt)
prev = pt
}
data = dsteps
case NoSteps:
// ok.
}

line := plotter.Line{
Expand Down
43 changes: 43 additions & 0 deletions hplot/s2d_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,49 @@ func ExampleS2D_withStepsKind() {
}
}

// ExampleS2D_withPreMidPostSteps draws some scatter points using a step-like style
func ExampleS2D_withPreMidPostSteps() {
s2ds := make(map[hplot.StepsKind]*hbook.S2D)
steps := []hplot.StepsKind{hplot.PreSteps, hplot.MidSteps, hplot.PostSteps}
names := []string{"pre-steps", "mid-steps", "post-steps"}

for i, step := range steps {
pts := []hbook.Point2D{
{X: 1 + float64(i)*10, Y: 1},
{X: 2 + float64(i)*10, Y: 2},
{X: 3 + float64(i)*10, Y: 3},
{X: 4 + float64(i)*10, Y: 4},
}
s2ds[step] = hbook.NewS2D(pts...)
}

p := hplot.New()
p.Title.Text = "Scatter-2D (with steps)"
p.X.Label.Text = "X"
p.X.Min = 0
p.X.Max = 25
p.Y.Label.Text = "Y"
p.Y.Min = 0
p.Add(plotter.NewGrid())

for i, step := range steps {
s := hplot.NewS2D(s2ds[step], hplot.WithStepsKind(step))
s.GlyphStyle.Shape = draw.CircleGlyph{}
s.GlyphStyle.Color = plotutil.Color(i + 1)
s.GlyphStyle.Radius = vg.Points(2)
s.LineStyle.Width = 1
s.LineStyle.Color = plotutil.Color(i + 1)

p.Add(s)
p.Legend.Add(names[i], s)
}

err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/s2d_premidpost_steps.png")
if err != nil {
log.Fatal(err)
}
}

// ExampleS2D_withSteps_withBand draws some scatter points
// with their error bars, using a step-like style together with a band
func ExampleS2D_withStepsKind_withBand() {
Expand Down
103 changes: 103 additions & 0 deletions hplot/s2d_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package hplot_test

import (
"fmt"
"testing"

"go-hep.org/x/hep/hbook"
"go-hep.org/x/hep/hplot"
"gonum.org/v1/plot/cmpimg"
)

Expand All @@ -26,6 +29,106 @@ func TestScatter2DWithStepsKind(t *testing.T) {
checkPlot(cmpimg.CheckPlot)(ExampleS2D_withStepsKind, t, "s2d_steps.png")
}

func TestScatter2DWithPreMidPostSteps(t *testing.T) {
checkPlot(cmpimg.CheckPlot)(ExampleS2D_withPreMidPostSteps, t, "s2d_premidpost_steps.png")
}

func TestScatter2DWithStepsKindWithBand(t *testing.T) {
checkPlot(cmpimg.CheckPlot)(ExampleS2D_withStepsKind_withBand, t, "s2d_steps_band.png")
}

func TestScatter2DSteps(t *testing.T) {
for _, tc := range []struct {
name string
pts []hbook.Point2D
opts []hplot.Options
want error
}{
{
name: "histeps_no_xerr",
pts: []hbook.Point2D{
{X: 1, Y: 1, ErrY: hbook.Range{Min: 2, Max: 3}},
{X: 2, Y: 2, ErrY: hbook.Range{Min: 5, Max: 2}},
{X: 3, Y: 3, ErrY: hbook.Range{Min: 2, Max: 2}},
{X: 4, Y: 4, ErrY: hbook.Range{Min: 1.2, Max: 2}},
},
opts: []hplot.Options{hplot.WithStepsKind(hplot.HiSteps)},
want: fmt.Errorf("s2d with HiSteps needs XErr informations for all points"),
},
{
name: "histeps_missing_some_xerr",
pts: []hbook.Point2D{
{X: 1, Y: 1, ErrY: hbook.Range{Min: 2, Max: 3}, ErrX: hbook.Range{Min: 1, Max: 2}},
{X: 2, Y: 2, ErrY: hbook.Range{Min: 5, Max: 2}},
{X: 3, Y: 3, ErrY: hbook.Range{Min: 2, Max: 2}, ErrX: hbook.Range{Min: 1, Max: 2}},
{X: 4, Y: 4, ErrY: hbook.Range{Min: 1.2, Max: 2}},
},
opts: []hplot.Options{hplot.WithStepsKind(hplot.HiSteps)},
want: fmt.Errorf("s2d with HiSteps needs XErr informations for all points"),
},
{
name: "presteps_with_band", // TODO(sbinet)
pts: []hbook.Point2D{
{X: 1, Y: 1},
{X: 2, Y: 2},
{X: 3, Y: 3},
{X: 4, Y: 4},
},
opts: []hplot.Options{hplot.WithStepsKind(hplot.PreSteps), hplot.WithBand(true)},
want: fmt.Errorf("presteps not implemented"),
},
{
name: "midsteps_with_band", // TODO(sbinet)
pts: []hbook.Point2D{
{X: 1, Y: 1},
{X: 2, Y: 2},
{X: 3, Y: 3},
{X: 4, Y: 4},
},
opts: []hplot.Options{hplot.WithStepsKind(hplot.MidSteps), hplot.WithBand(true)},
want: fmt.Errorf("midsteps not implemented"),
},
{
name: "poststeps_with_band", // TODO(sbinet)
pts: []hbook.Point2D{
{X: 1, Y: 1},
{X: 2, Y: 2},
{X: 3, Y: 3},
{X: 4, Y: 4},
},
opts: []hplot.Options{hplot.WithStepsKind(hplot.PostSteps), hplot.WithBand(true)},
want: fmt.Errorf("poststeps not implemented"),
},
} {
t.Run(tc.name, func(t *testing.T) {
defer func() {
err := recover()
switch {
case err == nil && tc.want != nil:
t.Fatalf("expected a panic")
case err == nil && tc.want == nil:
// ok.
case err != nil && tc.want == nil:
panic(err) // bubble up
case err != nil && tc.want != nil:
var got string
switch err := err.(type) {
case error:
got = err.Error()
case string:
got = err
default:
panic(fmt.Errorf("invalid recover type %T", err))
}
if got, want := got, tc.want.Error(); got != want {
t.Fatalf("invalid error:\ngot= %q\nwant=%q", got, want)
}
}
}()

s2d := hbook.NewS2D(tc.pts...)

_ = hplot.NewS2D(s2d, tc.opts...)
})
}
}
Binary file added hplot/testdata/s2d_premidpost_steps_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading