1
2
3
4
5 package plotter
6
7 import (
8 "math"
9
10 "gonum.org/v1/plot"
11 "gonum.org/v1/plot/vg"
12 "gonum.org/v1/plot/vg/draw"
13 )
14
15
16
17 type FieldXY interface {
18
19 Dims() (c, r int)
20
21
22
23 Vector(c, r int) XY
24
25
26
27 X(c int) float64
28
29
30
31 Y(r int) float64
32 }
33
34
35
36 type Field struct {
37 FieldXY FieldXY
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 DrawGlyph func(c vg.Canvas, sty draw.LineStyle, v XY)
55
56
57
58
59 LineStyle draw.LineStyle
60
61
62 max float64
63 }
64
65
66 func NewField(f FieldXY) *Field {
67 max := math.Inf(-1)
68 c, r := f.Dims()
69 for i := 0; i < c; i++ {
70 for j := 0; j < r; j++ {
71 v := f.Vector(i, j)
72 d := math.Hypot(v.X, v.Y)
73 if math.IsNaN(d) {
74 continue
75 }
76 max = math.Max(max, d)
77 }
78 }
79
80 return &Field{
81 FieldXY: f,
82 LineStyle: DefaultLineStyle,
83 max: max,
84 }
85 }
86
87
88 func (f *Field) Plot(c draw.Canvas, plt *plot.Plot) {
89 c.Push()
90 defer c.Pop()
91 c.SetLineStyle(f.LineStyle)
92
93 trX, trY := plt.Transforms(&c)
94
95 cols, rows := f.FieldXY.Dims()
96 for i := 0; i < cols; i++ {
97 var right, left float64
98 switch i {
99 case 0:
100 if cols == 1 {
101 right = 0.5
102 } else {
103 right = (f.FieldXY.X(1) - f.FieldXY.X(0)) / 2
104 }
105 left = -right
106 case cols - 1:
107 right = (f.FieldXY.X(cols-1) - f.FieldXY.X(cols-2)) / 2
108 left = -right
109 default:
110 right = (f.FieldXY.X(i+1) - f.FieldXY.X(i)) / 2
111 left = -(f.FieldXY.X(i) - f.FieldXY.X(i-1)) / 2
112 }
113
114 for j := 0; j < rows; j++ {
115 var up, down float64
116 switch j {
117 case 0:
118 if rows == 1 {
119 up = 0.5
120 } else {
121 up = (f.FieldXY.Y(1) - f.FieldXY.Y(0)) / 2
122 }
123 down = -up
124 case rows - 1:
125 up = (f.FieldXY.Y(rows-1) - f.FieldXY.Y(rows-2)) / 2
126 down = -up
127 default:
128 up = (f.FieldXY.Y(j+1) - f.FieldXY.Y(j)) / 2
129 down = -(f.FieldXY.Y(j) - f.FieldXY.Y(j-1)) / 2
130 }
131
132 x, y := trX(f.FieldXY.X(i)+left), trY(f.FieldXY.Y(j)+down)
133 dx, dy := trX(f.FieldXY.X(i)+right), trY(f.FieldXY.Y(j)+up)
134
135 if !c.Contains(vg.Point{X: x, Y: y}) || !c.Contains(vg.Point{X: dx, Y: dy}) {
136 continue
137 }
138
139 c.Push()
140 c.Translate(vg.Point{X: (x + dx) / 2, Y: (y + dy) / 2})
141
142 v := f.FieldXY.Vector(i, j)
143 s := math.Hypot(v.X, v.Y) / (2 * f.max)
144
145
146 if s != 0 {
147 c.Rotate(math.Atan2(v.Y, v.X))
148 c.Scale(s*float64(dx-x), s*float64(dy-y))
149 }
150 v.X /= f.max
151 v.Y /= f.max
152
153 if f.DrawGlyph == nil {
154 drawVector(c, v)
155 } else {
156 f.DrawGlyph(c, f.LineStyle, v)
157 }
158 c.Pop()
159 }
160 }
161 }
162
163 func drawVector(c vg.Canvas, v XY) {
164 if math.Hypot(v.X, v.Y) == 0 {
165 return
166 }
167
168 var pa vg.Path
169 pa.Move(vg.Point{})
170 pa.Line(vg.Point{X: 1, Y: 0})
171 pa.Close()
172 c.Stroke(pa)
173 }
174
175
176
177 func (f *Field) DataRange() (xmin, xmax, ymin, ymax float64) {
178 c, r := f.FieldXY.Dims()
179 switch c {
180 case 1:
181 xmax = f.FieldXY.X(0) + 0.5
182 xmin = f.FieldXY.X(0) - 0.5
183 default:
184 xmax = f.FieldXY.X(c-1) + (f.FieldXY.X(c-1)-f.FieldXY.X(c-2))/2
185 xmin = f.FieldXY.X(0) - (f.FieldXY.X(1)-f.FieldXY.X(0))/2
186 }
187 switch r {
188 case 1:
189 ymax = f.FieldXY.Y(0) + 0.5
190 ymin = f.FieldXY.Y(0) - 0.5
191 default:
192 ymax = f.FieldXY.Y(r-1) + (f.FieldXY.Y(r-1)-f.FieldXY.Y(r-2))/2
193 ymin = f.FieldXY.Y(0) - (f.FieldXY.Y(1)-f.FieldXY.Y(0))/2
194 }
195 return xmin, xmax, ymin, ymax
196 }
197
198
199
200 func (f *Field) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
201 c, r := f.FieldXY.Dims()
202 b := make([]plot.GlyphBox, 0, r*c)
203 for i := 0; i < c; i++ {
204 for j := 0; j < r; j++ {
205 b = append(b, plot.GlyphBox{
206 X: plt.X.Norm(f.FieldXY.X(i)),
207 Y: plt.Y.Norm(f.FieldXY.Y(j)),
208 Rectangle: vg.Rectangle{
209 Min: vg.Point{X: -5, Y: -5},
210 Max: vg.Point{X: +5, Y: +5},
211 },
212 })
213 }
214 }
215 return b
216 }
217
View as plain text