1
2
3
4
5 package plotter
6
7 import (
8 "image/color"
9
10 "gonum.org/v1/plot"
11 "gonum.org/v1/plot/vg"
12 "gonum.org/v1/plot/vg/draw"
13 )
14
15
16 type StepKind int
17
18 const (
19
20 NoStep StepKind = iota
21
22
23 PreStep
24
25
26
27 MidStep
28
29
30 PostStep
31 )
32
33
34 type Line struct {
35
36 XYs
37
38
39 StepStyle StepKind
40
41
42
43 draw.LineStyle
44
45
46
47 FillColor color.Color
48 }
49
50
51
52 func NewLine(xys XYer) (*Line, error) {
53 data, err := CopyXYs(xys)
54 if err != nil {
55 return nil, err
56 }
57 return &Line{
58 XYs: data,
59 LineStyle: DefaultLineStyle,
60 }, nil
61 }
62
63
64 func (pts *Line) Plot(c draw.Canvas, plt *plot.Plot) {
65 trX, trY := plt.Transforms(&c)
66 ps := make([]vg.Point, len(pts.XYs))
67
68 for i, p := range pts.XYs {
69 ps[i].X = trX(p.X)
70 ps[i].Y = trY(p.Y)
71 }
72
73 if pts.FillColor != nil && len(ps) > 0 {
74 minY := trY(plt.Y.Min)
75 fillPoly := []vg.Point{{X: ps[0].X, Y: minY}}
76 switch pts.StepStyle {
77 case PreStep:
78 fillPoly = append(fillPoly, ps[1:]...)
79 case PostStep:
80 fillPoly = append(fillPoly, ps[:len(ps)-1]...)
81 default:
82 fillPoly = append(fillPoly, ps...)
83 }
84 fillPoly = append(fillPoly, vg.Point{X: ps[len(ps)-1].X, Y: minY})
85 fillPoly = c.ClipPolygonXY(fillPoly)
86 if len(fillPoly) > 0 {
87 c.SetColor(pts.FillColor)
88 var pa vg.Path
89 prev := fillPoly[0]
90 pa.Move(prev)
91 for _, pt := range fillPoly[1:] {
92 switch pts.StepStyle {
93 case NoStep:
94 pa.Line(pt)
95 case PreStep:
96 pa.Line(vg.Point{X: prev.X, Y: pt.Y})
97 pa.Line(pt)
98 case MidStep:
99 pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
100 pa.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
101 pa.Line(pt)
102 case PostStep:
103 pa.Line(vg.Point{X: pt.X, Y: prev.Y})
104 pa.Line(pt)
105 }
106 prev = pt
107 }
108 pa.Close()
109 c.Fill(pa)
110 }
111 }
112
113 lines := c.ClipLinesXY(ps)
114 if pts.LineStyle.Width != 0 && len(lines) != 0 {
115 c.SetLineStyle(pts.LineStyle)
116 for _, l := range lines {
117 if len(l) == 0 {
118 continue
119 }
120 var p vg.Path
121 prev := l[0]
122 p.Move(prev)
123 for _, pt := range l[1:] {
124 switch pts.StepStyle {
125 case PreStep:
126 p.Line(vg.Point{X: prev.X, Y: pt.Y})
127 case MidStep:
128 p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: prev.Y})
129 p.Line(vg.Point{X: (prev.X + pt.X) / 2, Y: pt.Y})
130 case PostStep:
131 p.Line(vg.Point{X: pt.X, Y: prev.Y})
132 }
133 p.Line(pt)
134 prev = pt
135 }
136 c.Stroke(p)
137 }
138 }
139 }
140
141
142
143 func (pts *Line) DataRange() (xmin, xmax, ymin, ymax float64) {
144 return XYRange(pts)
145 }
146
147
148 func (pts *Line) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
149 r := 0.5 * pts.LineStyle.Width
150 rect := vg.Rectangle{
151 Min: vg.Point{
152 X: -r,
153 Y: -r,
154 },
155 Max: vg.Point{
156 X: +r,
157 Y: +r,
158 },
159 }
160
161 bs := make([]plot.GlyphBox, pts.XYs.Len())
162 for i := range bs {
163 x, y := pts.XY(i)
164 bs[i] = plot.GlyphBox{
165 X: plt.X.Norm(x),
166 Y: plt.Y.Norm(y),
167 Rectangle: rect,
168 }
169 }
170 return bs
171 }
172
173
174 func (pts *Line) Thumbnail(c *draw.Canvas) {
175 if pts.FillColor != nil {
176 var topY vg.Length
177 if pts.LineStyle.Width == 0 {
178 topY = c.Max.Y
179 } else {
180 topY = (c.Min.Y + c.Max.Y) / 2
181 }
182 points := []vg.Point{
183 {X: c.Min.X, Y: c.Min.Y},
184 {X: c.Min.X, Y: topY},
185 {X: c.Max.X, Y: topY},
186 {X: c.Max.X, Y: c.Min.Y},
187 }
188 poly := c.ClipPolygonY(points)
189 c.FillPolygon(pts.FillColor, poly)
190 }
191
192 if pts.LineStyle.Width != 0 {
193 y := c.Center().Y
194 c.StrokeLine2(pts.LineStyle, c.Min.X, y, c.Max.X, y)
195 }
196 }
197
198
199
200 func NewLinePoints(xys XYer) (*Line, *Scatter, error) {
201 s, err := NewScatter(xys)
202 if err != nil {
203 return nil, nil, err
204 }
205 l := &Line{
206 XYs: s.XYs,
207 LineStyle: DefaultLineStyle,
208 }
209 return l, s, nil
210 }
211
View as plain text