233 lines
6.2 KiB
Go
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
|
|
}
|