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 var (
16
17 DefaultQuartMedianStyle = draw.GlyphStyle{
18 Color: color.Black,
19 Radius: vg.Points(1.5),
20 Shape: draw.CircleGlyph{},
21 }
22
23
24 DefaultQuartWhiskerStyle = draw.LineStyle{
25 Color: color.Black,
26 Width: vg.Points(0.5),
27 Dashes: []vg.Length{},
28 DashOffs: 0,
29 }
30 )
31
32
33
34
35
36
37 type QuartPlot struct {
38 fiveStatPlot
39
40
41
42
43 Offset vg.Length
44
45
46 MedianStyle draw.GlyphStyle
47
48
49
50 WhiskerStyle draw.LineStyle
51
52
53
54 Horizontal bool
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69 func NewQuartPlot(loc float64, values Valuer) (*QuartPlot, error) {
70 b := new(QuartPlot)
71 var err error
72 if b.fiveStatPlot, err = newFiveStat(0, loc, values); err != nil {
73 return nil, err
74 }
75
76 b.MedianStyle = DefaultQuartMedianStyle
77 b.WhiskerStyle = DefaultQuartWhiskerStyle
78
79 return b, err
80 }
81
82
83 func (b *QuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
84 if b.Horizontal {
85 b := &horizQuartPlot{b}
86 b.Plot(c, plt)
87 return
88 }
89
90 trX, trY := plt.Transforms(&c)
91 x := trX(b.Location)
92 if !c.ContainsX(x) {
93 return
94 }
95 x += b.Offset
96
97 med := vg.Point{X: x, Y: trY(b.Median)}
98 q1 := trY(b.Quartile1)
99 q3 := trY(b.Quartile3)
100 aLow := trY(b.AdjLow)
101 aHigh := trY(b.AdjHigh)
102
103 c.StrokeLine2(b.WhiskerStyle, x, aHigh, x, q3)
104 if c.ContainsY(med.Y) {
105 c.DrawGlyphNoClip(b.MedianStyle, med)
106 }
107 c.StrokeLine2(b.WhiskerStyle, x, aLow, x, q1)
108
109 ostyle := b.MedianStyle
110 ostyle.Radius = b.MedianStyle.Radius / 2
111 for _, out := range b.Outside {
112 y := trY(b.Value(out))
113 if c.ContainsY(y) {
114 c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
115 }
116 }
117 }
118
119
120
121
122 func (b *QuartPlot) DataRange() (float64, float64, float64, float64) {
123 if b.Horizontal {
124 b := &horizQuartPlot{b}
125 return b.DataRange()
126 }
127 return b.Location, b.Location, b.Min, b.Max
128 }
129
130
131
132 func (b *QuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
133 if b.Horizontal {
134 b := &horizQuartPlot{b}
135 return b.GlyphBoxes(plt)
136 }
137
138 bs := make([]plot.GlyphBox, len(b.Outside)+1)
139
140 ostyle := b.MedianStyle
141 ostyle.Radius = b.MedianStyle.Radius / 2
142 for i, out := range b.Outside {
143 bs[i].X = plt.X.Norm(b.Location)
144 bs[i].Y = plt.Y.Norm(b.Value(out))
145 bs[i].Rectangle = ostyle.Rectangle()
146 bs[i].Rectangle.Min.X += b.Offset
147 }
148 bs[len(bs)-1].X = plt.X.Norm(b.Location)
149 bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
150 bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
151 bs[len(bs)-1].Rectangle.Min.X += b.Offset
152 return bs
153 }
154
155
156
157
158
159 func (b *QuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
160 if b.Horizontal {
161 b := &horizQuartPlot{b}
162 return b.OutsideLabels(labels)
163 }
164 strs := make([]string, len(b.Outside))
165 for i, out := range b.Outside {
166 strs[i] = labels.Label(out)
167 }
168 o := quartPlotOutsideLabels{b, strs}
169 ls, err := NewLabels(o)
170 if err != nil {
171 return nil, err
172 }
173 off := 0.5 * b.MedianStyle.Radius
174 ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
175 return ls, nil
176 }
177
178 type quartPlotOutsideLabels struct {
179 qp *QuartPlot
180 labels []string
181 }
182
183 func (o quartPlotOutsideLabels) Len() int {
184 return len(o.qp.Outside)
185 }
186
187 func (o quartPlotOutsideLabels) XY(i int) (float64, float64) {
188 return o.qp.Location, o.qp.Value(o.qp.Outside[i])
189 }
190
191 func (o quartPlotOutsideLabels) Label(i int) string {
192 return o.labels[i]
193 }
194
195
196
197 type horizQuartPlot struct{ *QuartPlot }
198
199 func (b horizQuartPlot) Plot(c draw.Canvas, plt *plot.Plot) {
200 trX, trY := plt.Transforms(&c)
201 y := trY(b.Location)
202 if !c.ContainsY(y) {
203 return
204 }
205 y += b.Offset
206
207 med := vg.Point{X: trX(b.Median), Y: y}
208 q1 := trX(b.Quartile1)
209 q3 := trX(b.Quartile3)
210 aLow := trX(b.AdjLow)
211 aHigh := trX(b.AdjHigh)
212
213 c.StrokeLine2(b.WhiskerStyle, aHigh, y, q3, y)
214 if c.ContainsX(med.X) {
215 c.DrawGlyphNoClip(b.MedianStyle, med)
216 }
217 c.StrokeLine2(b.WhiskerStyle, aLow, y, q1, y)
218
219 ostyle := b.MedianStyle
220 ostyle.Radius = b.MedianStyle.Radius / 2
221 for _, out := range b.Outside {
222 x := trX(b.Value(out))
223 if c.ContainsX(x) {
224 c.DrawGlyphNoClip(ostyle, vg.Point{X: x, Y: y})
225 }
226 }
227 }
228
229
230
231
232 func (b horizQuartPlot) DataRange() (float64, float64, float64, float64) {
233 return b.Min, b.Max, b.Location, b.Location
234 }
235
236
237
238 func (b horizQuartPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
239 bs := make([]plot.GlyphBox, len(b.Outside)+1)
240
241 ostyle := b.MedianStyle
242 ostyle.Radius = b.MedianStyle.Radius / 2
243 for i, out := range b.Outside {
244 bs[i].X = plt.X.Norm(b.Value(out))
245 bs[i].Y = plt.Y.Norm(b.Location)
246 bs[i].Rectangle = ostyle.Rectangle()
247 bs[i].Rectangle.Min.Y += b.Offset
248 }
249 bs[len(bs)-1].X = plt.X.Norm(b.Median)
250 bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
251 bs[len(bs)-1].Rectangle = b.MedianStyle.Rectangle()
252 bs[len(bs)-1].Rectangle.Min.Y += b.Offset
253 return bs
254 }
255
256
257
258
259
260 func (b *horizQuartPlot) OutsideLabels(labels Labeller) (*Labels, error) {
261 strs := make([]string, len(b.Outside))
262 for i, out := range b.Outside {
263 strs[i] = labels.Label(out)
264 }
265 o := horizQuartPlotOutsideLabels{
266 quartPlotOutsideLabels{b.QuartPlot, strs},
267 }
268 ls, err := NewLabels(o)
269 if err != nil {
270 return nil, err
271 }
272 off := 0.5 * b.MedianStyle.Radius
273 ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
274 return ls, nil
275 }
276
277 type horizQuartPlotOutsideLabels struct {
278 quartPlotOutsideLabels
279 }
280
281 func (o horizQuartPlotOutsideLabels) XY(i int) (float64, float64) {
282 return o.qp.Value(o.qp.Outside[i]), o.qp.Location
283 }
284
View as plain text