1
2
3
4
5 package plotter
6
7 import (
8 "errors"
9 "fmt"
10 "image/color"
11 "math"
12
13 "gonum.org/v1/plot"
14 "gonum.org/v1/plot/vg"
15 "gonum.org/v1/plot/vg/draw"
16 )
17
18
19
20 type Histogram struct {
21
22 Bins []HistogramBin
23
24
25 Width float64
26
27
28
29
30 FillColor color.Color
31
32
33
34 draw.LineStyle
35
36
37
38
39
40
41
42 LogY bool
43 }
44
45
46
47
48
49
50
51
52
53
54 func NewHistogram(xy XYer, n int) (*Histogram, error) {
55 if n <= 0 {
56 return nil, errors.New("Histogram with non-positive number of bins")
57 }
58 bins, width := binPoints(xy, n)
59 return &Histogram{
60 Bins: bins,
61 Width: width,
62 FillColor: color.Gray{128},
63 LineStyle: DefaultLineStyle,
64 }, nil
65 }
66
67
68
69
70 func NewHist(vs Valuer, n int) (*Histogram, error) {
71 return NewHistogram(unitYs{vs}, n)
72 }
73
74 type unitYs struct {
75 Valuer
76 }
77
78 func (u unitYs) XY(i int) (float64, float64) {
79 return u.Value(i), 1.0
80 }
81
82
83
84 func (h *Histogram) Plot(c draw.Canvas, p *plot.Plot) {
85 trX, trY := p.Transforms(&c)
86
87 for _, bin := range h.Bins {
88 ymin := c.Min.Y
89 ymax := c.Min.Y
90 if bin.Weight != 0 {
91 ymax = trY(bin.Weight)
92 }
93 xmin := trX(bin.Min)
94 xmax := trX(bin.Max)
95 pts := []vg.Point{
96 {X: xmin, Y: ymin},
97 {X: xmax, Y: ymin},
98 {X: xmax, Y: ymax},
99 {X: xmin, Y: ymax},
100 }
101 if h.FillColor != nil {
102 c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
103 }
104 pts = append(pts, vg.Point{X: xmin, Y: ymin})
105 c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
106 }
107 }
108
109
110 func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
111 xmin = math.Inf(+1)
112 xmax = math.Inf(-1)
113 ymin = math.Inf(+1)
114 ymax = math.Inf(-1)
115 ylow := math.Inf(+1)
116 for _, bin := range h.Bins {
117 if bin.Max > xmax {
118 xmax = bin.Max
119 }
120 if bin.Min < xmin {
121 xmin = bin.Min
122 }
123 if bin.Weight > ymax {
124 ymax = bin.Weight
125 }
126 if bin.Weight < ymin {
127 ymin = bin.Weight
128 }
129 if bin.Weight != 0 && bin.Weight < ylow {
130 ylow = bin.Weight
131 }
132 }
133 switch h.LogY {
134 case true:
135 if ymin == 0 && !math.IsInf(ylow, +1) {
136
137 ymin = ylow * 0.5
138 }
139 default:
140 ymin = 0
141 }
142 return
143 }
144
145
146
147 func (h *Histogram) Normalize(sum float64) {
148 mass := 0.0
149 for _, b := range h.Bins {
150 mass += b.Weight
151 }
152 for i := range h.Bins {
153 h.Bins[i].Weight *= sum / (h.Width * mass)
154 }
155 }
156
157
158 func (h *Histogram) Thumbnail(c *draw.Canvas) {
159 ymin := c.Min.Y
160 ymax := c.Max.Y
161 xmin := c.Min.X
162 xmax := c.Max.X
163
164 pts := []vg.Point{
165 {X: xmin, Y: ymin},
166 {X: xmax, Y: ymin},
167 {X: xmax, Y: ymax},
168 {X: xmin, Y: ymax},
169 }
170 if h.FillColor != nil {
171 c.FillPolygon(h.FillColor, c.ClipPolygonXY(pts))
172 }
173 pts = append(pts, vg.Point{X: xmin, Y: ymin})
174 c.StrokeLines(h.LineStyle, c.ClipLinesXY(pts)...)
175 }
176
177
178
179
180
181
182
183
184
185 func binPoints(xys XYer, n int) (bins []HistogramBin, width float64) {
186 xmin, xmax := Range(XValues{xys})
187 if n <= 0 {
188 m := 0.0
189 for i := 0; i < xys.Len(); i++ {
190 _, y := xys.XY(i)
191 m += math.Max(y, 1.0)
192 }
193 n = int(math.Ceil(math.Sqrt(m)))
194 }
195 if n < 1 || xmax <= xmin {
196 n = 1
197 }
198
199 bins = make([]HistogramBin, n)
200
201 w := (xmax - xmin) / float64(n)
202 if w == 0 {
203 w = 1
204 }
205 for i := range bins {
206 bins[i].Min = xmin + float64(i)*w
207 bins[i].Max = xmin + float64(i+1)*w
208 }
209
210 for i := 0; i < xys.Len(); i++ {
211 x, y := xys.XY(i)
212 bin := int((x - xmin) / w)
213 if x == xmax {
214 bin = n - 1
215 }
216 if bin < 0 || bin >= n {
217 panic(fmt.Sprintf("%g, xmin=%g, xmax=%g, w=%g, bin=%d, n=%d\n",
218 x, xmin, xmax, w, bin, n))
219 }
220 bins[bin].Weight += y
221 }
222 return bins, w
223 }
224
225
226
227 type HistogramBin struct {
228 Min, Max float64
229 Weight float64
230 }
231
View as plain text