Files
sjy01-image-proc/vendor/gonum.org/v1/plot/plotter/barchart.go
2024-10-24 15:46:01 +08:00

212 lines
5.2 KiB
Go

// 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("plotter: 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{
{X: catMin, Y: valMin},
{X: catMin, Y: valMax},
{X: catMax, Y: valMax},
{X: catMax, Y: valMin},
}
poly = c.ClipPolygonY(pts)
} else {
pts = []vg.Point{
{X: valMin, Y: catMin},
{X: valMin, Y: catMax},
{X: valMax, Y: catMax},
{X: valMax, Y: 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{
{X: c.Min.X, Y: c.Min.Y},
{X: c.Min.X, Y: c.Max.Y},
{X: c.Max.X, Y: c.Max.Y},
{X: c.Max.X, Y: 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...)
}