1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
// Copyright ©2015 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package plotter
import (
"errors"
"image/color"
"math"
"gonum.org/v1/plot"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
)
// A BarChart presents grouped data with rectangular bars
// with lengths proportional to the data values.
type BarChart struct {
Values
// Width is the width of the bars.
Width vg.Length
// Color is the fill color of the bars.
Color color.Color
// LineStyle is the style of the outline of the bars.
draw.LineStyle
// Offset is added to the X location of each bar.
// When the Offset is zero, the bars are drawn
// centered at their X location.
Offset vg.Length
// XMin is the X location of the first bar. XMin
// can be changed to move groups of bars
// down the X axis in order to make grouped
// bar charts.
XMin float64
// Horizontal dictates whether the bars should be in the vertical
// (default) or horizontal direction. If Horizontal is true, all
// X locations and distances referred to here will actually be Y
// locations and distances.
Horizontal bool
// stackedOn is the bar chart upon which
// this bar chart is stacked.
stackedOn *BarChart
}
// NewBarChart returns a new bar chart with a single bar for each value.
// The bars heights correspond to the values and their x locations correspond
// to the index of their value in the Valuer.
func NewBarChart(vs Valuer, width vg.Length) (*BarChart, error) {
if width <= 0 {
return nil, errors.New("Width parameter was not positive")
}
values, err := CopyValues(vs)
if err != nil {
return nil, err
}
return &BarChart{
Values: values,
Width: width,
Color: color.Black,
LineStyle: DefaultLineStyle,
}, nil
}
// BarHeight returns the maximum y value of the
// ith bar, taking into account any bars upon
// which it is stacked.
func (b *BarChart) BarHeight(i int) float64 {
ht := 0.0
if b == nil {
return 0
}
if i >= 0 && i < len(b.Values) {
ht += b.Values[i]
}
if b.stackedOn != nil {
ht += b.stackedOn.BarHeight(i)
}
return ht
}
// StackOn stacks a bar chart on top of another,
// and sets the XMin and Offset to that of the
// chart upon which it is being stacked.
func (b *BarChart) StackOn(on *BarChart) {
b.XMin = on.XMin
b.Offset = on.Offset
b.stackedOn = on
}
// Plot implements the plot.Plotter interface.
func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
trCat, trVal := plt.Transforms(&c)
if b.Horizontal {
trCat, trVal = trVal, trCat
}
for i, ht := range b.Values {
catVal := b.XMin + float64(i)
catMin := trCat(float64(catVal))
if !b.Horizontal {
if !c.ContainsX(catMin) {
continue
}
} else {
if !c.ContainsY(catMin) {
continue
}
}
catMin = catMin - b.Width/2 + b.Offset
catMax := catMin + b.Width
bottom := b.stackedOn.BarHeight(i)
valMin := trVal(bottom)
valMax := trVal(bottom + ht)
var pts []vg.Point
var poly []vg.Point
if !b.Horizontal {
pts = []vg.Point{
{catMin, valMin},
{catMin, valMax},
{catMax, valMax},
{catMax, valMin},
}
poly = c.ClipPolygonY(pts)
} else {
pts = []vg.Point{
{valMin, catMin},
{valMin, catMax},
{valMax, catMax},
{valMax, catMin},
}
poly = c.ClipPolygonX(pts)
}
c.FillPolygon(b.Color, poly)
var outline [][]vg.Point
if !b.Horizontal {
pts = append(pts, vg.Point{X: catMin, Y: valMin})
outline = c.ClipLinesY(pts)
} else {
pts = append(pts, vg.Point{X: valMin, Y: catMin})
outline = c.ClipLinesX(pts)
}
c.StrokeLines(b.LineStyle, outline...)
}
}
// DataRange implements the plot.DataRanger interface.
func (b *BarChart) DataRange() (xmin, xmax, ymin, ymax float64) {
catMin := b.XMin
catMax := catMin + float64(len(b.Values)-1)
valMin := math.Inf(1)
valMax := math.Inf(-1)
for i, val := range b.Values {
valBot := b.stackedOn.BarHeight(i)
valTop := valBot + val
valMin = math.Min(valMin, math.Min(valBot, valTop))
valMax = math.Max(valMax, math.Max(valBot, valTop))
}
if !b.Horizontal {
return catMin, catMax, valMin, valMax
}
return valMin, valMax, catMin, catMax
}
// GlyphBoxes implements the GlyphBoxer interface.
func (b *BarChart) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
boxes := make([]plot.GlyphBox, len(b.Values))
for i := range b.Values {
cat := b.XMin + float64(i)
if !b.Horizontal {
boxes[i].X = plt.X.Norm(cat)
boxes[i].Rectangle = vg.Rectangle{
Min: vg.Point{X: b.Offset - b.Width/2},
Max: vg.Point{X: b.Offset + b.Width/2},
}
} else {
boxes[i].Y = plt.Y.Norm(cat)
boxes[i].Rectangle = vg.Rectangle{
Min: vg.Point{Y: b.Offset - b.Width/2},
Max: vg.Point{Y: b.Offset + b.Width/2},
}
}
}
return boxes
}
// Thumbnail fulfills the plot.Thumbnailer interface.
func (b *BarChart) Thumbnail(c *draw.Canvas) {
pts := []vg.Point{
{c.Min.X, c.Min.Y},
{c.Min.X, c.Max.Y},
{c.Max.X, c.Max.Y},
{c.Max.X, c.Min.Y},
}
poly := c.ClipPolygonY(pts)
c.FillPolygon(b.Color, poly)
pts = append(pts, vg.Point{X: c.Min.X, Y: c.Min.Y})
outline := c.ClipLinesY(pts)
c.StrokeLines(b.LineStyle, outline...)
}
|