fixed dependencies
This commit is contained in:
457
vendor/gonum.org/v1/plot/plotter/boxplot.go
generated
vendored
Normal file
457
vendor/gonum.org/v1/plot/plotter/boxplot.go
generated
vendored
Normal file
@@ -0,0 +1,457 @@
|
||||
// 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"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// fiveStatPlot contains the shared fields for quartile
|
||||
// and box-whisker plots.
|
||||
type fiveStatPlot struct {
|
||||
// Values is a copy of the values of the values used to
|
||||
// create this box plot.
|
||||
Values
|
||||
|
||||
// Location is the location of the box along its axis.
|
||||
Location float64
|
||||
|
||||
// Median is the median value of the data.
|
||||
Median float64
|
||||
|
||||
// Quartile1 and Quartile3 are the first and
|
||||
// third quartiles of the data respectively.
|
||||
Quartile1, Quartile3 float64
|
||||
|
||||
// AdjLow and AdjHigh are the `adjacent' values
|
||||
// on the low and high ends of the data. The
|
||||
// adjacent values are the points to which the
|
||||
// whiskers are drawn.
|
||||
AdjLow, AdjHigh float64
|
||||
|
||||
// Min and Max are the extreme values of the data.
|
||||
Min, Max float64
|
||||
|
||||
// Outside are the indices of Vs for the outside points.
|
||||
Outside []int
|
||||
}
|
||||
|
||||
// BoxPlot implements the Plotter interface, drawing
|
||||
// a boxplot to represent the distribution of values.
|
||||
type BoxPlot struct {
|
||||
fiveStatPlot
|
||||
|
||||
// Offset is added to the x location of each box.
|
||||
// When the Offset is zero, the boxes are drawn
|
||||
// centered at their x location.
|
||||
Offset vg.Length
|
||||
|
||||
// Width is the width used to draw the box.
|
||||
Width vg.Length
|
||||
|
||||
// CapWidth is the width of the cap used to top
|
||||
// off a whisker.
|
||||
CapWidth vg.Length
|
||||
|
||||
// GlyphStyle is the style of the outside point glyphs.
|
||||
GlyphStyle draw.GlyphStyle
|
||||
|
||||
// FillColor is the color used to fill the box.
|
||||
// The default is no fill.
|
||||
FillColor color.Color
|
||||
|
||||
// BoxStyle is the line style for the box.
|
||||
BoxStyle draw.LineStyle
|
||||
|
||||
// MedianStyle is the line style for the median line.
|
||||
MedianStyle draw.LineStyle
|
||||
|
||||
// WhiskerStyle is the line style used to draw the
|
||||
// whiskers.
|
||||
WhiskerStyle draw.LineStyle
|
||||
|
||||
// Horizontal dictates whether the BoxPlot should be in the vertical
|
||||
// (default) or horizontal direction.
|
||||
Horizontal bool
|
||||
}
|
||||
|
||||
// NewBoxPlot returns a new BoxPlot that represents
|
||||
// the distribution of the given values. The style of
|
||||
// the box plot is that used for Tukey's schematic
|
||||
// plots in “Exploratory Data Analysis.”
|
||||
//
|
||||
// An error is returned if the boxplot is created with
|
||||
// no values.
|
||||
//
|
||||
// The fence values are 1.5x the interquartile before
|
||||
// the first quartile and after the third quartile. Any
|
||||
// value that is outside of the fences are drawn as
|
||||
// Outside points. The adjacent values (to which the
|
||||
// whiskers stretch) are the minimum and maximum
|
||||
// values that are not outside the fences.
|
||||
func NewBoxPlot(w vg.Length, loc float64, values Valuer) (*BoxPlot, error) {
|
||||
if w < 0 {
|
||||
return nil, errors.New("plotter: negative boxplot width")
|
||||
}
|
||||
|
||||
b := new(BoxPlot)
|
||||
var err error
|
||||
if b.fiveStatPlot, err = newFiveStat(w, loc, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Width = w
|
||||
b.CapWidth = 3 * w / 4
|
||||
|
||||
b.GlyphStyle = DefaultGlyphStyle
|
||||
b.BoxStyle = DefaultLineStyle
|
||||
b.MedianStyle = DefaultLineStyle
|
||||
b.WhiskerStyle = draw.LineStyle{
|
||||
Width: vg.Points(0.5),
|
||||
Dashes: []vg.Length{vg.Points(4), vg.Points(2)},
|
||||
}
|
||||
|
||||
if len(b.Values) == 0 {
|
||||
b.Width = 0
|
||||
b.GlyphStyle.Radius = 0
|
||||
b.BoxStyle.Width = 0
|
||||
b.MedianStyle.Width = 0
|
||||
b.WhiskerStyle.Width = 0
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func newFiveStat(w vg.Length, loc float64, values Valuer) (fiveStatPlot, error) {
|
||||
var b fiveStatPlot
|
||||
b.Location = loc
|
||||
|
||||
var err error
|
||||
if b.Values, err = CopyValues(values); err != nil {
|
||||
return fiveStatPlot{}, err
|
||||
}
|
||||
|
||||
sorted := make(Values, len(b.Values))
|
||||
copy(sorted, b.Values)
|
||||
sort.Float64s(sorted)
|
||||
|
||||
if len(sorted) == 1 {
|
||||
b.Median = sorted[0]
|
||||
b.Quartile1 = sorted[0]
|
||||
b.Quartile3 = sorted[0]
|
||||
} else {
|
||||
b.Median = median(sorted)
|
||||
b.Quartile1 = median(sorted[:len(sorted)/2])
|
||||
b.Quartile3 = median(sorted[len(sorted)/2:])
|
||||
}
|
||||
b.Min = sorted[0]
|
||||
b.Max = sorted[len(sorted)-1]
|
||||
|
||||
low := b.Quartile1 - 1.5*(b.Quartile3-b.Quartile1)
|
||||
high := b.Quartile3 + 1.5*(b.Quartile3-b.Quartile1)
|
||||
b.AdjLow = math.Inf(1)
|
||||
b.AdjHigh = math.Inf(-1)
|
||||
for i, v := range b.Values {
|
||||
if v > high || v < low {
|
||||
b.Outside = append(b.Outside, i)
|
||||
continue
|
||||
}
|
||||
if v < b.AdjLow {
|
||||
b.AdjLow = v
|
||||
}
|
||||
if v > b.AdjHigh {
|
||||
b.AdjHigh = v
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// median returns the median value from a
|
||||
// sorted Values.
|
||||
func median(vs Values) float64 {
|
||||
if len(vs) == 1 {
|
||||
return vs[0]
|
||||
}
|
||||
med := vs[len(vs)/2]
|
||||
if len(vs)%2 == 0 {
|
||||
med += vs[len(vs)/2-1]
|
||||
med /= 2
|
||||
}
|
||||
return med
|
||||
}
|
||||
|
||||
// Plot draws the BoxPlot on Canvas c and Plot plt.
|
||||
func (b *BoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
b.Plot(c, plt)
|
||||
return
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
x := trX(b.Location)
|
||||
if !c.ContainsX(x) {
|
||||
return
|
||||
}
|
||||
x += b.Offset
|
||||
|
||||
med := trY(b.Median)
|
||||
q1 := trY(b.Quartile1)
|
||||
q3 := trY(b.Quartile3)
|
||||
aLow := trY(b.AdjLow)
|
||||
aHigh := trY(b.AdjHigh)
|
||||
|
||||
pts := []vg.Point{
|
||||
{X: x - b.Width/2, Y: q1},
|
||||
{X: x - b.Width/2, Y: q3},
|
||||
{X: x + b.Width/2, Y: q3},
|
||||
{X: x + b.Width/2, Y: q1},
|
||||
{X: x - b.Width/2 - b.BoxStyle.Width/2, Y: q1},
|
||||
}
|
||||
box := c.ClipLinesY(pts)
|
||||
if b.FillColor != nil {
|
||||
c.FillPolygon(b.FillColor, c.ClipPolygonY(pts))
|
||||
}
|
||||
c.StrokeLines(b.BoxStyle, box...)
|
||||
|
||||
medLine := c.ClipLinesY([]vg.Point{
|
||||
{X: x - b.Width/2, Y: med},
|
||||
{X: x + b.Width/2, Y: med},
|
||||
})
|
||||
c.StrokeLines(b.MedianStyle, medLine...)
|
||||
|
||||
cap := b.CapWidth / 2
|
||||
whisks := c.ClipLinesY(
|
||||
[]vg.Point{{X: x, Y: q3}, {X: x, Y: aHigh}},
|
||||
[]vg.Point{{X: x - cap, Y: aHigh}, {X: x + cap, Y: aHigh}},
|
||||
[]vg.Point{{X: x, Y: q1}, {X: x, Y: aLow}},
|
||||
[]vg.Point{{X: x - cap, Y: aLow}, {X: x + cap, Y: aLow}},
|
||||
)
|
||||
c.StrokeLines(b.WhiskerStyle, whisks...)
|
||||
|
||||
for _, out := range b.Outside {
|
||||
y := trY(b.Value(out))
|
||||
if c.ContainsY(y) {
|
||||
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b *BoxPlot) DataRange() (float64, float64, float64, float64) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.DataRange()
|
||||
}
|
||||
return b.Location, b.Location, b.Min, b.Max
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the
|
||||
// points and for the median line of the boxplot,
|
||||
// implementing the plot.GlyphBoxer interface
|
||||
func (b *BoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.GlyphBoxes(plt)
|
||||
}
|
||||
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Location)
|
||||
bs[i].Y = plt.Y.Norm(b.Value(out))
|
||||
bs[i].Rectangle = b.GlyphStyle.Rectangle()
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Location)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
|
||||
bs[len(bs)-1].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{X: b.Offset - (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
Max: vg.Point{X: b.Offset + (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the box plot.
|
||||
func (b *BoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
if b.Horizontal {
|
||||
b := &horizBoxPlot{b}
|
||||
return b.OutsideLabels(labels)
|
||||
}
|
||||
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := boxPlotOutsideLabels{b, strs}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.GlyphStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type boxPlotOutsideLabels struct {
|
||||
box *BoxPlot
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) Len() int {
|
||||
return len(o.box.Outside)
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.box.Location, o.box.Value(o.box.Outside[i])
|
||||
}
|
||||
|
||||
func (o boxPlotOutsideLabels) Label(i int) string {
|
||||
return o.labels[i]
|
||||
}
|
||||
|
||||
// horizBoxPlot is like a regular BoxPlot, however,
|
||||
// it draws horizontally instead of Vertically.
|
||||
// TODO: Merge code for horizontal and vertical box plots as has been done for
|
||||
// bar charts.
|
||||
type horizBoxPlot struct{ *BoxPlot }
|
||||
|
||||
func (b horizBoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
trX, trY := plt.Transforms(&c)
|
||||
y := trY(b.Location)
|
||||
if !c.ContainsY(y) {
|
||||
return
|
||||
}
|
||||
y += b.Offset
|
||||
|
||||
med := trX(b.Median)
|
||||
q1 := trX(b.Quartile1)
|
||||
q3 := trX(b.Quartile3)
|
||||
aLow := trX(b.AdjLow)
|
||||
aHigh := trX(b.AdjHigh)
|
||||
|
||||
pts := []vg.Point{
|
||||
{X: q1, Y: y - b.Width/2},
|
||||
{X: q3, Y: y - b.Width/2},
|
||||
{X: q3, Y: y + b.Width/2},
|
||||
{X: q1, Y: y + b.Width/2},
|
||||
{X: q1, Y: y - b.Width/2 - b.BoxStyle.Width/2},
|
||||
}
|
||||
box := c.ClipLinesX(pts)
|
||||
if b.FillColor != nil {
|
||||
c.FillPolygon(b.FillColor, c.ClipPolygonX(pts))
|
||||
}
|
||||
c.StrokeLines(b.BoxStyle, box...)
|
||||
|
||||
medLine := c.ClipLinesX([]vg.Point{
|
||||
{X: med, Y: y - b.Width/2},
|
||||
{X: med, Y: y + b.Width/2},
|
||||
})
|
||||
c.StrokeLines(b.MedianStyle, medLine...)
|
||||
|
||||
cap := b.CapWidth / 2
|
||||
whisks := c.ClipLinesX(
|
||||
[]vg.Point{{X: q3, Y: y}, {X: aHigh, Y: y}},
|
||||
[]vg.Point{{X: aHigh, Y: y - cap}, {X: aHigh, Y: y + cap}},
|
||||
[]vg.Point{{X: q1, Y: y}, {X: aLow, Y: y}},
|
||||
[]vg.Point{{X: aLow, Y: y - cap}, {X: aLow, Y: y + cap}},
|
||||
)
|
||||
c.StrokeLines(b.WhiskerStyle, whisks...)
|
||||
|
||||
for _, out := range b.Outside {
|
||||
x := trX(b.Value(out))
|
||||
if c.ContainsX(x) {
|
||||
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DataRange returns the minimum and maximum x
|
||||
// and y values, implementing the plot.DataRanger
|
||||
// interface.
|
||||
func (b horizBoxPlot) DataRange() (float64, float64, float64, float64) {
|
||||
return b.Min, b.Max, b.Location, b.Location
|
||||
}
|
||||
|
||||
// GlyphBoxes returns a slice of GlyphBoxes for the
|
||||
// points and for the median line of the boxplot,
|
||||
// implementing the plot.GlyphBoxer interface
|
||||
func (b horizBoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
bs := make([]plot.GlyphBox, len(b.Outside)+1)
|
||||
for i, out := range b.Outside {
|
||||
bs[i].X = plt.X.Norm(b.Value(out))
|
||||
bs[i].Y = plt.Y.Norm(b.Location)
|
||||
bs[i].Rectangle = b.GlyphStyle.Rectangle()
|
||||
}
|
||||
bs[len(bs)-1].X = plt.X.Norm(b.Median)
|
||||
bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
|
||||
bs[len(bs)-1].Rectangle = vg.Rectangle{
|
||||
Min: vg.Point{Y: b.Offset - (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
Max: vg.Point{Y: b.Offset + (b.Width/2 + b.BoxStyle.Width/2)},
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// OutsideLabels returns a *Labels that will plot
|
||||
// a label for each of the outside points. The
|
||||
// labels are assumed to correspond to the
|
||||
// points used to create the box plot.
|
||||
func (b *horizBoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
|
||||
strs := make([]string, len(b.Outside))
|
||||
for i, out := range b.Outside {
|
||||
strs[i] = labels.Label(out)
|
||||
}
|
||||
o := horizBoxPlotOutsideLabels{
|
||||
boxPlotOutsideLabels{b.BoxPlot, strs},
|
||||
}
|
||||
ls, err := NewLabels(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off := 0.5 * b.GlyphStyle.Radius
|
||||
ls.Offset = ls.Offset.Add(vg.Point{X: off, Y: off})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
type horizBoxPlotOutsideLabels struct {
|
||||
boxPlotOutsideLabels
|
||||
}
|
||||
|
||||
func (o horizBoxPlotOutsideLabels) XY(i int) (float64, float64) {
|
||||
return o.box.Value(o.box.Outside[i]), o.box.Location
|
||||
}
|
||||
|
||||
// ValueLabels implements both the Valuer
|
||||
// and Labeller interfaces.
|
||||
type ValueLabels []struct {
|
||||
Value float64
|
||||
Label string
|
||||
}
|
||||
|
||||
// Len returns the number of items.
|
||||
func (vs ValueLabels) Len() int {
|
||||
return len(vs)
|
||||
}
|
||||
|
||||
// Value returns the value of item i.
|
||||
func (vs ValueLabels) Value(i int) float64 {
|
||||
return vs[i].Value
|
||||
}
|
||||
|
||||
// Label returns the label of item i.
|
||||
func (vs ValueLabels) Label(i int) string {
|
||||
return vs[i].Label
|
||||
}
|
||||
Reference in New Issue
Block a user