Skip to content

Commit b3189bc

Browse files
committed
plotter: Step implemented
1 parent 59819ff commit b3189bc

3 files changed

Lines changed: 267 additions & 0 deletions

File tree

plotter/step.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright ©2015 The gonum Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package plotter
6+
7+
import (
8+
"image/color"
9+
"math"
10+
11+
"gonum.org/v1/plot"
12+
"gonum.org/v1/plot/vg"
13+
"gonum.org/v1/plot/vg/draw"
14+
)
15+
16+
// StepType specifies where to put a vertical line joining two steps
17+
type StepType int
18+
19+
const (
20+
// StepTypePre means that a vertical line is placed at the begining of a horizonal line (|‾‾)
21+
StepTypePre StepType = iota
22+
23+
// StepTypeMid means that a vertical line is placed between two horizontal lines (_|‾)
24+
StepTypeMid
25+
26+
// StepTypePost means that a vertial line is placed at the end of a horizonal line (__|)
27+
StepTypePost
28+
)
29+
30+
// Step implements the Plotter interface, drawing a stepped line.
31+
type Step struct {
32+
// XYs is a copy of the points for this line.
33+
XYs
34+
35+
// StepStyle is the type of step line
36+
StepType StepType
37+
38+
// LineStyle is the style of the line connecting the points.
39+
LineStyle *draw.LineStyle
40+
41+
// ShadeColor is the color of the shaded area.
42+
ShadeColor color.Color
43+
}
44+
45+
// NewStep returns a Step that uses the default line style and does not draw glyphs.
46+
func NewStep(xys XYer) (*Step, error) {
47+
data, err := CopyXYs(xys)
48+
if err != nil {
49+
return nil, err
50+
}
51+
return &Step{
52+
XYs: data,
53+
LineStyle: &DefaultLineStyle,
54+
}, nil
55+
}
56+
57+
// Plot draws the Step, implementing the plot.Plotter interface.
58+
func (pts *Step) Plot(c draw.Canvas, plt *plot.Plot) {
59+
trX, trY := plt.Transforms(&c)
60+
ps := make([]vg.Point, len(pts.XYs))
61+
62+
for i, p := range pts.XYs {
63+
ps[i].X = trX(p.X)
64+
ps[i].Y = trY(p.Y)
65+
}
66+
67+
if pts.ShadeColor != nil && len(ps) > 0 {
68+
c.SetColor(pts.ShadeColor)
69+
minY := trY(plt.Y.Min)
70+
var pa vg.Path
71+
pa.Move(vg.Point{X: ps[0].X, Y: minY})
72+
prev := ps[0]
73+
if pts.StepType != StepTypePre {
74+
pa.Line(prev)
75+
}
76+
for _, pt := range ps[1:] {
77+
switch pts.StepType {
78+
case StepTypePre:
79+
pa.Line(vg.Point{X: prev.X, Y: pt.Y})
80+
case StepTypeMid:
81+
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
82+
pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
83+
case StepTypePost:
84+
pa.Line(vg.Point{X: pt.X, Y: prev.Y})
85+
}
86+
pa.Line(pt)
87+
prev = pt
88+
}
89+
pa.Line(vg.Point{X: ps[len(pts.XYs)-1].X, Y: minY})
90+
pa.Close()
91+
c.Fill(pa)
92+
}
93+
94+
if pts.LineStyle != nil {
95+
lines := c.ClipLinesXY(ps)
96+
if len(lines) == 0 {
97+
return
98+
}
99+
c.SetLineStyle(*pts.LineStyle)
100+
for _, l := range lines {
101+
if len(l) == 0 {
102+
continue
103+
}
104+
var p vg.Path
105+
prev := l[0]
106+
p.Move(prev)
107+
for _, pt := range l[1:] {
108+
switch pts.StepType {
109+
case StepTypePre:
110+
p.Line(vg.Point{X: prev.X, Y: pt.Y})
111+
case StepTypeMid:
112+
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
113+
p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
114+
case StepTypePost:
115+
p.Line(vg.Point{X: pt.X, Y: prev.Y})
116+
}
117+
p.Line(pt)
118+
prev = pt
119+
}
120+
c.Stroke(p)
121+
}
122+
}
123+
}
124+
125+
// DataRange returns the minimum and maximum
126+
// x and y values, implementing the plot.DataRanger
127+
// interface.
128+
func (pts *Step) DataRange() (xmin, xmax, ymin, ymax float64) {
129+
if pts.ShadeColor != nil {
130+
xmin, xmax, ymin, ymax = XYRange(pts)
131+
ymin = math.Min(ymin, 0.)
132+
ymax = math.Max(ymax, 0.)
133+
return
134+
}
135+
return XYRange(pts)
136+
}
137+
138+
// Thumbnail the thumbnail for the Step,
139+
// implementing the plot.Thumbnailer interface.
140+
func (pts *Step) Thumbnail(c *draw.Canvas) {
141+
if pts.ShadeColor != nil {
142+
points := []vg.Point{
143+
{X: c.Min.X, Y: c.Min.Y},
144+
{X: c.Min.X, Y: c.Max.Y},
145+
{X: c.Max.X, Y: c.Max.Y},
146+
{X: c.Max.X, Y: c.Min.Y},
147+
}
148+
poly := c.ClipPolygonY(points)
149+
c.FillPolygon(pts.ShadeColor, poly)
150+
} else if pts.LineStyle != nil {
151+
y := c.Center().Y
152+
c.StrokeLine2(*pts.LineStyle, c.Min.X, y, c.Max.X, y)
153+
}
154+
}
155+
156+
// NewStepPoints returns both a Step and a
157+
// Points for the given point data.
158+
func NewStepPoints(xys XYer) (*Step, *Scatter, error) {
159+
s, err := NewScatter(xys)
160+
if err != nil {
161+
return nil, nil, err
162+
}
163+
l := &Step{
164+
XYs: s.XYs,
165+
LineStyle: &DefaultLineStyle,
166+
}
167+
return l, s, nil
168+
}

plotter/step_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright ©2015 The gonum Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package plotter
6+
7+
import (
8+
"image/color"
9+
"log"
10+
"testing"
11+
12+
"golang.org/x/exp/rand"
13+
14+
"gonum.org/v1/plot"
15+
"gonum.org/v1/plot/internal/cmpimg"
16+
"gonum.org/v1/plot/vg"
17+
"gonum.org/v1/plot/vg/draw"
18+
)
19+
20+
// ExampleStep draws some filled step lines.
21+
func ExampleStep() {
22+
rnd := rand.New(rand.NewSource(1))
23+
24+
// randomPoints returns some random x, y points
25+
// with some interesting kind of trend.
26+
randomPoints := func(n int, x float64) XYs {
27+
pts := make(XYs, n)
28+
for i := range pts {
29+
if i == 0 {
30+
pts[i].X = x + rnd.Float64()
31+
} else {
32+
pts[i].X = pts[i-1].X + 0.5 + rnd.Float64()
33+
}
34+
pts[i].Y = 5. + 10*rnd.Float64()
35+
}
36+
return pts
37+
}
38+
39+
n := 4
40+
41+
p, err := plot.New()
42+
if err != nil {
43+
log.Panic(err)
44+
}
45+
p.Title.Text = "Step Example"
46+
p.X.Label.Text = "X"
47+
p.Y.Label.Text = "Y"
48+
p.Add(NewGrid())
49+
50+
stepPre, err := NewStep(randomPoints(n, 0.))
51+
if err != nil {
52+
log.Panic(err)
53+
}
54+
stepPre.ShadeColor = color.RGBA{A: 40}
55+
56+
stepMid, err := NewStep(randomPoints(n, 3.5))
57+
if err != nil {
58+
log.Panic(err)
59+
}
60+
stepMid.StepType = StepTypeMid
61+
stepMid.LineStyle = &draw.LineStyle{Color: color.RGBA{R: 196, B: 128, A: 255}, Width: vg.Points(1)}
62+
63+
stepPost, err := NewStep(randomPoints(n, 7.))
64+
if err != nil {
65+
log.Panic(err)
66+
}
67+
stepPost.StepType = StepTypePost
68+
stepPost.LineStyle = &draw.LineStyle{Color: color.RGBA{B: 255, A: 255}, Width: vg.Points(1)}
69+
stepPost.ShadeColor = color.RGBA{B: 255, A: 40}
70+
71+
scPre, err := NewScatter(stepPre.XYs)
72+
if err != nil {
73+
log.Panic(err)
74+
}
75+
76+
scMid, err := NewScatter(stepMid.XYs)
77+
if err != nil {
78+
log.Panic(err)
79+
}
80+
81+
scPost, err := NewScatter(stepPost.XYs)
82+
if err != nil {
83+
log.Panic(err)
84+
}
85+
86+
p.Add(stepPre, stepMid, stepPost, scPre, scMid, scPost)
87+
p.Legend.Add("pre", stepPre)
88+
p.Legend.Add("mid", stepMid)
89+
p.Legend.Add("post", stepPost)
90+
91+
err = p.Save(200, 200, "testdata/step.pdf")
92+
if err != nil {
93+
log.Panic(err)
94+
}
95+
}
96+
97+
func TestStep(t *testing.T) {
98+
cmpimg.CheckPlot(ExampleStep, t, "step.pdf")
99+
}

plotter/testdata/step_golden.pdf

210 KB
Binary file not shown.

0 commit comments

Comments
 (0)