Skip to content

Commit

Permalink
plotter: Step implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
igrmk committed Nov 23, 2018
1 parent 59819ff commit b3189bc
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 0 deletions.
168 changes: 168 additions & 0 deletions plotter/step.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright ©2015 The gonum 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 plotter

import (
"image/color"
"math"

"gonum.org/v1/plot"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
)

// StepType specifies where to put a vertical line joining two steps
type StepType int

const (
// StepTypePre means that a vertical line is placed at the begining of a horizonal line (|‾‾)
StepTypePre StepType = iota

// StepTypeMid means that a vertical line is placed between two horizontal lines (_|‾)
StepTypeMid

// StepTypePost means that a vertial line is placed at the end of a horizonal line (__|)
StepTypePost
)

// Step implements the Plotter interface, drawing a stepped line.
type Step struct {
// XYs is a copy of the points for this line.
XYs

// StepStyle is the type of step line
StepType StepType

// LineStyle is the style of the line connecting the points.
LineStyle *draw.LineStyle

// ShadeColor is the color of the shaded area.
ShadeColor color.Color
}

// NewStep returns a Step that uses the default line style and does not draw glyphs.
func NewStep(xys XYer) (*Step, error) {
data, err := CopyXYs(xys)
if err != nil {
return nil, err
}
return &Step{
XYs: data,
LineStyle: &DefaultLineStyle,
}, nil
}

// Plot draws the Step, implementing the plot.Plotter interface.
func (pts *Step) Plot(c draw.Canvas, plt *plot.Plot) {
trX, trY := plt.Transforms(&c)
ps := make([]vg.Point, len(pts.XYs))

for i, p := range pts.XYs {
ps[i].X = trX(p.X)
ps[i].Y = trY(p.Y)
}

if pts.ShadeColor != nil && len(ps) > 0 {
c.SetColor(pts.ShadeColor)
minY := trY(plt.Y.Min)
var pa vg.Path
pa.Move(vg.Point{X: ps[0].X, Y: minY})
prev := ps[0]
if pts.StepType != StepTypePre {
pa.Line(prev)
}
for _, pt := range ps[1:] {
switch pts.StepType {
case StepTypePre:
pa.Line(vg.Point{X: prev.X, Y: pt.Y})
case StepTypeMid:
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
case StepTypePost:
pa.Line(vg.Point{X: pt.X, Y: prev.Y})
}
pa.Line(pt)
prev = pt
}
pa.Line(vg.Point{X: ps[len(pts.XYs)-1].X, Y: minY})
pa.Close()
c.Fill(pa)
}

if pts.LineStyle != nil {
lines := c.ClipLinesXY(ps)
if len(lines) == 0 {
return
}
c.SetLineStyle(*pts.LineStyle)
for _, l := range lines {
if len(l) == 0 {
continue
}
var p vg.Path
prev := l[0]
p.Move(prev)
for _, pt := range l[1:] {
switch pts.StepType {
case StepTypePre:
p.Line(vg.Point{X: prev.X, Y: pt.Y})
case StepTypeMid:
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
case StepTypePost:
p.Line(vg.Point{X: pt.X, Y: prev.Y})
}
p.Line(pt)
prev = pt
}
c.Stroke(p)
}
}
}

// DataRange returns the minimum and maximum
// x and y values, implementing the plot.DataRanger
// interface.
func (pts *Step) DataRange() (xmin, xmax, ymin, ymax float64) {
if pts.ShadeColor != nil {
xmin, xmax, ymin, ymax = XYRange(pts)
ymin = math.Min(ymin, 0.)
ymax = math.Max(ymax, 0.)
return
}
return XYRange(pts)
}

// Thumbnail the thumbnail for the Step,
// implementing the plot.Thumbnailer interface.
func (pts *Step) Thumbnail(c *draw.Canvas) {
if pts.ShadeColor != nil {
points := []vg.Point{
{X: c.Min.X, Y: c.Min.Y},
{X: c.Min.X, Y: c.Max.Y},
{X: c.Max.X, Y: c.Max.Y},
{X: c.Max.X, Y: c.Min.Y},
}
poly := c.ClipPolygonY(points)
c.FillPolygon(pts.ShadeColor, poly)
} else if pts.LineStyle != nil {
y := c.Center().Y
c.StrokeLine2(*pts.LineStyle, c.Min.X, y, c.Max.X, y)
}
}

// NewStepPoints returns both a Step and a
// Points for the given point data.
func NewStepPoints(xys XYer) (*Step, *Scatter, error) {
s, err := NewScatter(xys)
if err != nil {
return nil, nil, err
}
l := &Step{
XYs: s.XYs,
LineStyle: &DefaultLineStyle,
}
return l, s, nil
}
99 changes: 99 additions & 0 deletions plotter/step_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright ©2015 The gonum 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 plotter

import (
"image/color"
"log"
"testing"

"golang.org/x/exp/rand"

"gonum.org/v1/plot"
"gonum.org/v1/plot/internal/cmpimg"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
)

// ExampleStep draws some filled step lines.
func ExampleStep() {
rnd := rand.New(rand.NewSource(1))

// randomPoints returns some random x, y points
// with some interesting kind of trend.
randomPoints := func(n int, x float64) XYs {
pts := make(XYs, n)
for i := range pts {
if i == 0 {
pts[i].X = x + rnd.Float64()
} else {
pts[i].X = pts[i-1].X + 0.5 + rnd.Float64()
}
pts[i].Y = 5. + 10*rnd.Float64()
}
return pts
}

n := 4

p, err := plot.New()
if err != nil {
log.Panic(err)
}
p.Title.Text = "Step Example"
p.X.Label.Text = "X"
p.Y.Label.Text = "Y"
p.Add(NewGrid())

stepPre, err := NewStep(randomPoints(n, 0.))
if err != nil {
log.Panic(err)
}
stepPre.ShadeColor = color.RGBA{A: 40}

stepMid, err := NewStep(randomPoints(n, 3.5))
if err != nil {
log.Panic(err)
}
stepMid.StepType = StepTypeMid
stepMid.LineStyle = &draw.LineStyle{Color: color.RGBA{R: 196, B: 128, A: 255}, Width: vg.Points(1)}

stepPost, err := NewStep(randomPoints(n, 7.))
if err != nil {
log.Panic(err)
}
stepPost.StepType = StepTypePost
stepPost.LineStyle = &draw.LineStyle{Color: color.RGBA{B: 255, A: 255}, Width: vg.Points(1)}
stepPost.ShadeColor = color.RGBA{B: 255, A: 40}

scPre, err := NewScatter(stepPre.XYs)
if err != nil {
log.Panic(err)
}

scMid, err := NewScatter(stepMid.XYs)
if err != nil {
log.Panic(err)
}

scPost, err := NewScatter(stepPost.XYs)
if err != nil {
log.Panic(err)
}

p.Add(stepPre, stepMid, stepPost, scPre, scMid, scPost)
p.Legend.Add("pre", stepPre)
p.Legend.Add("mid", stepMid)
p.Legend.Add("post", stepPost)

err = p.Save(200, 200, "testdata/step.pdf")
if err != nil {
log.Panic(err)
}
}

func TestStep(t *testing.T) {
cmpimg.CheckPlot(ExampleStep, t, "step.pdf")
}
Binary file added plotter/testdata/step_golden.pdf
Binary file not shown.

0 comments on commit b3189bc

Please sign in to comment.