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

233 lines
6.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 (
"math"
"gonum.org/v1/plot"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
)
// DefaultCapWidth is the default width of error bar caps.
var DefaultCapWidth = vg.Points(5)
// YErrorBars implements the plot.Plotter, plot.DataRanger,
// and plot.GlyphBoxer interfaces, drawing vertical error
// bars, denoting error in Y values.
type YErrorBars struct {
XYs
// YErrors is a copy of the Y errors for each point.
YErrors
// LineStyle is the style used to draw the error bars.
draw.LineStyle
// CapWidth is the width of the caps drawn at the top
// of each error bar.
CapWidth vg.Length
}
// NewYErrorBars returns a new YErrorBars plotter, or an error on failure.
// The error values from the YErrorer interface are interpreted as relative
// to the corresponding Y value. The errors for a given Y value are computed
// by taking the absolute value of the error returned by the YErrorer
// and subtracting the first and adding the second to the Y value.
func NewYErrorBars(yerrs interface {
XYer
YErrorer
}) (*YErrorBars, error) {
errors := make(YErrors, yerrs.Len())
for i := range errors {
errors[i].Low, errors[i].High = yerrs.YError(i)
if err := CheckFloats(errors[i].Low, errors[i].High); err != nil {
return nil, err
}
}
xys, err := CopyXYs(yerrs)
if err != nil {
return nil, err
}
return &YErrorBars{
XYs: xys,
YErrors: errors,
LineStyle: DefaultLineStyle,
CapWidth: DefaultCapWidth,
}, nil
}
// Plot implements the Plotter interface, drawing labels.
func (e *YErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
trX, trY := p.Transforms(&c)
for i, err := range e.YErrors {
x := trX(e.XYs[i].X)
ylow := trY(e.XYs[i].Y - math.Abs(err.Low))
yhigh := trY(e.XYs[i].Y + math.Abs(err.High))
bar := c.ClipLinesY([]vg.Point{{X: x, Y: ylow}, {X: x, Y: yhigh}})
c.StrokeLines(e.LineStyle, bar...)
e.drawCap(&c, x, ylow)
e.drawCap(&c, x, yhigh)
}
}
// drawCap draws the cap if it is not clipped.
func (e *YErrorBars) drawCap(c *draw.Canvas, x, y vg.Length) {
if !c.Contains(vg.Point{X: x, Y: y}) {
return
}
c.StrokeLine2(e.LineStyle, x-e.CapWidth/2, y, x+e.CapWidth/2, y)
}
// DataRange implements the plot.DataRanger interface.
func (e *YErrorBars) DataRange() (xmin, xmax, ymin, ymax float64) {
xmin, xmax = Range(XValues{e})
ymin = math.Inf(1)
ymax = math.Inf(-1)
for i, err := range e.YErrors {
y := e.XYs[i].Y
ylow := y - math.Abs(err.Low)
yhigh := y + math.Abs(err.High)
ymin = math.Min(math.Min(math.Min(ymin, y), ylow), yhigh)
ymax = math.Max(math.Max(math.Max(ymax, y), ylow), yhigh)
}
return
}
// GlyphBoxes implements the plot.GlyphBoxer interface.
func (e *YErrorBars) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
rect := vg.Rectangle{
Min: vg.Point{
X: -e.CapWidth / 2,
Y: -e.LineStyle.Width / 2,
},
Max: vg.Point{
X: +e.CapWidth / 2,
Y: +e.LineStyle.Width / 2,
},
}
var bs []plot.GlyphBox
for i, err := range e.YErrors {
x := plt.X.Norm(e.XYs[i].X)
y := e.XYs[i].Y
bs = append(bs,
plot.GlyphBox{X: x, Y: plt.Y.Norm(y - err.Low), Rectangle: rect},
plot.GlyphBox{X: x, Y: plt.Y.Norm(y + err.High), Rectangle: rect})
}
return bs
}
// XErrorBars implements the plot.Plotter, plot.DataRanger,
// and plot.GlyphBoxer interfaces, drawing horizontal error
// bars, denoting error in Y values.
type XErrorBars struct {
XYs
// XErrors is a copy of the X errors for each point.
XErrors
// LineStyle is the style used to draw the error bars.
draw.LineStyle
// CapWidth is the width of the caps drawn at the top
// of each error bar.
CapWidth vg.Length
}
// Returns a new XErrorBars plotter, or an error on failure. The error values
// from the XErrorer interface are interpreted as relative to the corresponding
// X value. The errors for a given X value are computed by taking the absolute
// value of the error returned by the XErrorer and subtracting the first and
// adding the second to the X value.
func NewXErrorBars(xerrs interface {
XYer
XErrorer
}) (*XErrorBars, error) {
errors := make(XErrors, xerrs.Len())
for i := range errors {
errors[i].Low, errors[i].High = xerrs.XError(i)
if err := CheckFloats(errors[i].Low, errors[i].High); err != nil {
return nil, err
}
}
xys, err := CopyXYs(xerrs)
if err != nil {
return nil, err
}
return &XErrorBars{
XYs: xys,
XErrors: errors,
LineStyle: DefaultLineStyle,
CapWidth: DefaultCapWidth,
}, nil
}
// Plot implements the Plotter interface, drawing labels.
func (e *XErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
trX, trY := p.Transforms(&c)
for i, err := range e.XErrors {
y := trY(e.XYs[i].Y)
xlow := trX(e.XYs[i].X - math.Abs(err.Low))
xhigh := trX(e.XYs[i].X + math.Abs(err.High))
bar := c.ClipLinesX([]vg.Point{{X: xlow, Y: y}, {X: xhigh, Y: y}})
c.StrokeLines(e.LineStyle, bar...)
e.drawCap(&c, xlow, y)
e.drawCap(&c, xhigh, y)
}
}
// drawCap draws the cap if it is not clipped.
func (e *XErrorBars) drawCap(c *draw.Canvas, x, y vg.Length) {
if !c.Contains(vg.Point{X: x, Y: y}) {
return
}
c.StrokeLine2(e.LineStyle, x, y-e.CapWidth/2, x, y+e.CapWidth/2)
}
// DataRange implements the plot.DataRanger interface.
func (e *XErrorBars) DataRange() (xmin, xmax, ymin, ymax float64) {
ymin, ymax = Range(YValues{e})
xmin = math.Inf(1)
xmax = math.Inf(-1)
for i, err := range e.XErrors {
x := e.XYs[i].X
xlow := x - math.Abs(err.Low)
xhigh := x + math.Abs(err.High)
xmin = math.Min(math.Min(math.Min(xmin, x), xlow), xhigh)
xmax = math.Max(math.Max(math.Max(xmax, x), xlow), xhigh)
}
return
}
// GlyphBoxes implements the plot.GlyphBoxer interface.
func (e *XErrorBars) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
rect := vg.Rectangle{
Min: vg.Point{
X: -e.LineStyle.Width / 2,
Y: -e.CapWidth / 2,
},
Max: vg.Point{
X: +e.LineStyle.Width / 2,
Y: +e.CapWidth / 2,
},
}
var bs []plot.GlyphBox
for i, err := range e.XErrors {
x := e.XYs[i].X
y := plt.Y.Norm(e.XYs[i].Y)
bs = append(bs,
plot.GlyphBox{X: plt.X.Norm(x - err.Low), Y: y, Rectangle: rect},
plot.GlyphBox{X: plt.X.Norm(x + err.High), Y: y, Rectangle: rect})
}
return bs
}