1
2
3
4
5 package plotter
6
7 import (
8 "errors"
9 "image/color"
10 "math"
11
12 "gonum.org/v1/plot"
13 "gonum.org/v1/plot/vg"
14 "gonum.org/v1/plot/vg/draw"
15 )
16
17
18
19 type BarChart struct {
20 Values
21
22
23 Width vg.Length
24
25
26 Color color.Color
27
28
29 draw.LineStyle
30
31
32
33
34 Offset vg.Length
35
36
37
38
39
40 XMin float64
41
42
43
44
45
46 Horizontal bool
47
48
49
50 stackedOn *BarChart
51 }
52
53
54
55
56 func NewBarChart(vs Valuer, width vg.Length) (*BarChart, error) {
57 if width <= 0 {
58 return nil, errors.New("plotter: width parameter was not positive")
59 }
60 values, err := CopyValues(vs)
61 if err != nil {
62 return nil, err
63 }
64 return &BarChart{
65 Values: values,
66 Width: width,
67 Color: color.Black,
68 LineStyle: DefaultLineStyle,
69 }, nil
70 }
71
72
73
74
75 func (b *BarChart) BarHeight(i int) float64 {
76 ht := 0.0
77 if b == nil {
78 return 0
79 }
80 if i >= 0 && i < len(b.Values) {
81 ht += b.Values[i]
82 }
83 if b.stackedOn != nil {
84 ht += b.stackedOn.BarHeight(i)
85 }
86 return ht
87 }
88
89
90
91
92 func (b *BarChart) StackOn(on *BarChart) {
93 b.XMin = on.XMin
94 b.Offset = on.Offset
95 b.stackedOn = on
96 }
97
98
99 func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
100 trCat, trVal := plt.Transforms(&c)
101 if b.Horizontal {
102 trCat, trVal = trVal, trCat
103 }
104
105 for i, ht := range b.Values {
106 catVal := b.XMin + float64(i)
107 catMin := trCat(float64(catVal))
108 if !b.Horizontal {
109 if !c.ContainsX(catMin) {
110 continue
111 }
112 } else {
113 if !c.ContainsY(catMin) {
114 continue
115 }
116 }
117 catMin = catMin - b.Width/2 + b.Offset
118 catMax := catMin + b.Width
119 bottom := b.stackedOn.BarHeight(i)
120 valMin := trVal(bottom)
121 valMax := trVal(bottom + ht)
122
123 var pts []vg.Point
124 var poly []vg.Point
125 if !b.Horizontal {
126 pts = []vg.Point{
127 {X: catMin, Y: valMin},
128 {X: catMin, Y: valMax},
129 {X: catMax, Y: valMax},
130 {X: catMax, Y: valMin},
131 }
132 poly = c.ClipPolygonY(pts)
133 } else {
134 pts = []vg.Point{
135 {X: valMin, Y: catMin},
136 {X: valMin, Y: catMax},
137 {X: valMax, Y: catMax},
138 {X: valMax, Y: catMin},
139 }
140 poly = c.ClipPolygonX(pts)
141 }
142 c.FillPolygon(b.Color, poly)
143
144 var outline [][]vg.Point
145 if !b.Horizontal {
146 pts = append(pts, vg.Point{X: catMin, Y: valMin})
147 outline = c.ClipLinesY(pts)
148 } else {
149 pts = append(pts, vg.Point{X: valMin, Y: catMin})
150 outline = c.ClipLinesX(pts)
151 }
152 c.StrokeLines(b.LineStyle, outline...)
153 }
154 }
155
156
157 func (b *BarChart) DataRange() (xmin, xmax, ymin, ymax float64) {
158 catMin := b.XMin
159 catMax := catMin + float64(len(b.Values)-1)
160
161 valMin := math.Inf(1)
162 valMax := math.Inf(-1)
163 for i, val := range b.Values {
164 valBot := b.stackedOn.BarHeight(i)
165 valTop := valBot + val
166 valMin = math.Min(valMin, math.Min(valBot, valTop))
167 valMax = math.Max(valMax, math.Max(valBot, valTop))
168 }
169 if !b.Horizontal {
170 return catMin, catMax, valMin, valMax
171 }
172 return valMin, valMax, catMin, catMax
173 }
174
175
176 func (b *BarChart) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
177 boxes := make([]plot.GlyphBox, len(b.Values))
178 for i := range b.Values {
179 cat := b.XMin + float64(i)
180 if !b.Horizontal {
181 boxes[i].X = plt.X.Norm(cat)
182 boxes[i].Rectangle = vg.Rectangle{
183 Min: vg.Point{X: b.Offset - b.Width/2},
184 Max: vg.Point{X: b.Offset + b.Width/2},
185 }
186 } else {
187 boxes[i].Y = plt.Y.Norm(cat)
188 boxes[i].Rectangle = vg.Rectangle{
189 Min: vg.Point{Y: b.Offset - b.Width/2},
190 Max: vg.Point{Y: b.Offset + b.Width/2},
191 }
192 }
193 }
194 return boxes
195 }
196
197
198 func (b *BarChart) Thumbnail(c *draw.Canvas) {
199 pts := []vg.Point{
200 {X: c.Min.X, Y: c.Min.Y},
201 {X: c.Min.X, Y: c.Max.Y},
202 {X: c.Max.X, Y: c.Max.Y},
203 {X: c.Max.X, Y: c.Min.Y},
204 }
205 poly := c.ClipPolygonY(pts)
206 c.FillPolygon(b.Color, poly)
207
208 pts = append(pts, vg.Point{X: c.Min.X, Y: c.Min.Y})
209 outline := c.ClipLinesY(pts)
210 c.StrokeLines(b.LineStyle, outline...)
211 }
212
View as plain text