Skip to content

Commit

Permalink
plot,plotter: use errors instead of panics for failable plotters
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak committed May 12, 2017
1 parent 395bf78 commit 8b37956
Show file tree
Hide file tree
Showing 21 changed files with 133 additions and 78 deletions.
25 changes: 25 additions & 0 deletions errors.go
@@ -0,0 +1,25 @@
// 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 plot

import "fmt"

// Errors is a collection of plotting errors. An Errors value
// must have a length of at least one.
type Errors []error

func (e Errors) Error() string {
switch len(e) {
case 0:
panic("plot: invalid error")
case 1:
return e[0].Error()
case 2:
return fmt.Sprintf("plot: %v and 1 more error", e[0])
default:
return fmt.Sprintf("plot: %v and %d more errors", e[0], len(e)-1)
}

}
38 changes: 29 additions & 9 deletions plot.go
Expand Up @@ -71,7 +71,7 @@ type Plot struct {
// http://godoc.org/github.com/gonum/plot/plotter
type Plotter interface {
// Plot draws the data to a draw.Canvas.
Plot(draw.Canvas, *Plot)
Plot(draw.Canvas, *Plot) error
}

// DataRanger wraps the DataRange method.
Expand Down Expand Up @@ -149,7 +149,10 @@ func (p *Plot) Add(ps ...Plotter) {
// GlyphBoxer interface will have their GlyphBoxes
// taken into account when padding the plot so that
// none of their glyphs are clipped.
func (p *Plot) Draw(c draw.Canvas) {
//
// All errors raised during a call to Draw are returned
// in an Errors slice.
func (p *Plot) Draw(c draw.Canvas) error {
if p.BackgroundColor != nil {
c.SetColor(p.BackgroundColor)
c.Fill(c.Rectangle.Path())
Expand All @@ -171,11 +174,19 @@ func (p *Plot) Draw(c draw.Canvas) {
y.draw(padY(p, draw.Crop(c, 0, 0, xheight, 0)))

dataC := padY(p, padX(p, draw.Crop(c, ywidth, 0, xheight, 0)))
var errs Errors
for _, data := range p.plotters {
data.Plot(dataC, p)
err := data.Plot(dataC, p)
if err != nil {
errs = append(errs, err)
}
}

p.Legend.draw(draw.Crop(draw.Crop(c, ywidth, 0, 0, 0), 0, 0, xheight, 0))
if errs != nil {
return errs
}
return nil
}

// DataCanvas returns a new draw.Canvas that
Expand Down Expand Up @@ -447,13 +458,15 @@ func (p *Plot) NominalY(names ...string) {
// Supported formats are:
//
// eps, jpg|jpeg, pdf, png, svg, and tif|tiff.
//
// If WriterTo returns an error of type Errors some of
// the elements of the written plot may be invalid.
func (p *Plot) WriterTo(w, h vg.Length, format string) (io.WriterTo, error) {
c, err := draw.NewFormattedCanvas(w, h, format)
if err != nil {
return nil, err
}
p.Draw(draw.New(c))
return c, nil
return c, p.Draw(draw.New(c))
}

// Save saves the plot to an image file. The file format is determined
Expand All @@ -462,6 +475,9 @@ func (p *Plot) WriterTo(w, h vg.Length, format string) (io.WriterTo, error) {
// Supported extensions are:
//
// .eps, .jpg, .jpeg, .pdf, .png, .svg, .tif and .tiff.
//
// If Save returns an error of type Errors some of
// the elements of the saved plot may be invalid.
func (p *Plot) Save(w, h vg.Length, file string) (err error) {
f, err := os.Create(file)
if err != nil {
Expand All @@ -478,11 +494,15 @@ func (p *Plot) Save(w, h vg.Length, file string) (err error) {
if len(format) != 0 {
format = format[1:]
}
c, err := p.WriterTo(w, h, format)
c, errs := p.WriterTo(w, h, format)
_, err = c.WriteTo(f)

// If the call to c.WriteTo failed this is a hard
// error and we do not care about the plotting
// errors, otherwise return any errors that arose
// during rendering.
if err != nil {
return err
}

_, err = c.WriteTo(f)
return err
return errs
}
3 changes: 2 additions & 1 deletion plotter/barchart.go
Expand Up @@ -96,7 +96,7 @@ func (b *BarChart) StackOn(on *BarChart) {
}

// Plot implements the plot.Plotter interface.
func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) error {
trCat, trVal := plt.Transforms(&c)
if b.Horizontal {
trCat, trVal = trVal, trCat
Expand Down Expand Up @@ -151,6 +151,7 @@ func (b *BarChart) Plot(c draw.Canvas, plt *plot.Plot) {
}
c.StrokeLines(b.LineStyle, outline...)
}
return nil
}

// DataRange implements the plot.DataRanger interface.
Expand Down
15 changes: 9 additions & 6 deletions plotter/boxplot.go
Expand Up @@ -186,17 +186,16 @@ func median(vs Values) float64 {
}

// Plot draws the BoxPlot on Canvas c and Plot plt.
func (b *BoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
func (b *BoxPlot) Plot(c draw.Canvas, plt *plot.Plot) error {
if b.Horizontal {
b := &horizBoxPlot{b}
b.Plot(c, plt)
return
return b.Plot(c, plt)
}

trX, trY := plt.Transforms(&c)
x := trX(b.Location)
if !c.ContainsX(x) {
return
return nil
}
x += b.Offset

Expand Down Expand Up @@ -234,6 +233,8 @@ func (b *BoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
}
}

return nil
}

// DataRange returns the minimum and maximum x
Expand Down Expand Up @@ -318,11 +319,11 @@ func (o boxPlotOutsideLabels) Label(i int) string {
// bar charts.
type horizBoxPlot struct{ *BoxPlot }

func (b horizBoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
func (b horizBoxPlot) Plot(c draw.Canvas, plt *plot.Plot) error {
trX, trY := plt.Transforms(&c)
y := trY(b.Location)
if !c.ContainsY(y) {
return
return nil
}
y += b.Offset

Expand Down Expand Up @@ -360,6 +361,8 @@ func (b horizBoxPlot) Plot(c draw.Canvas, plt *plot.Plot) {
c.DrawGlyphNoClip(b.GlyphStyle, vg.Point{X: x, Y: y})
}
}

return nil
}

// DataRange returns the minimum and maximum x
Expand Down
4 changes: 3 additions & 1 deletion plotter/bubbles.go
Expand Up @@ -61,7 +61,7 @@ func NewBubbles(xyz XYZer, min, max vg.Length) (*Bubbles, error) {
}

// Plot implements the Plot method of the plot.Plotter interface.
func (bs *Bubbles) Plot(c draw.Canvas, plt *plot.Plot) {
func (bs *Bubbles) Plot(c draw.Canvas, plt *plot.Plot) error {
trX, trY := plt.Transforms(&c)

c.SetColor(bs.Color)
Expand All @@ -83,6 +83,8 @@ func (bs *Bubbles) Plot(c draw.Canvas, plt *plot.Plot) {
p.Close()
c.Fill(p)
}

return nil
}

// radius returns the radius of a bubble by linear interpolation.
Expand Down
3 changes: 2 additions & 1 deletion plotter/colorbar.go
Expand Up @@ -53,7 +53,7 @@ func (l *ColorBar) check() {
}

// Plot implements the Plot method of the plot.Plotter interface.
func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) {
func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) error {
l.check()
colors := l.colors(c)
var img *image.NRGBA64
Expand Down Expand Up @@ -98,6 +98,7 @@ func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) {
Max: vg.Point{X: xmax, Y: ymax},
}
c.DrawImage(rect, img)
return nil
}

// DataRange implements the DataRange method
Expand Down
13 changes: 11 additions & 2 deletions plotter/contour.go
Expand Up @@ -5,6 +5,7 @@
package plotter

import (
"errors"
"image/color"
"math"
"sort"
Expand Down Expand Up @@ -121,10 +122,16 @@ func quantilesR7(g GridXYZ, p []float64) []float64 {
const naive = false

// Plot implements the Plot method of the plot.Plotter interface.
func (h *Contour) Plot(c draw.Canvas, plt *plot.Plot) {
func (h *Contour) Plot(c draw.Canvas, plt *plot.Plot) error {
if h.Min > h.Max {
panic("contour: non-positive Z range")
}
if h.Min == h.Max {
return errors.New("contour: zero Z range")
}
if naive {
h.naivePlot(c, plt)
return
return nil
}

var pal []color.Color
Expand Down Expand Up @@ -178,6 +185,8 @@ func (h *Contour) Plot(c draw.Canvas, plt *plot.Plot) {
}
}
}

return nil
}

// naivePlot implements the a naive rendering approach for contours.
Expand Down
6 changes: 4 additions & 2 deletions plotter/errbars.go
Expand Up @@ -63,7 +63,7 @@ func NewYErrorBars(yerrs interface {
}

// Plot implements the Plotter interface, drawing labels.
func (e *YErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
func (e *YErrorBars) Plot(c draw.Canvas, p *plot.Plot) error {
trX, trY := p.Transforms(&c)
for i, err := range e.YErrors {
x := trX(e.XYs[i].X)
Expand All @@ -75,6 +75,7 @@ func (e *YErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
e.drawCap(&c, x, ylow)
e.drawCap(&c, x, yhigh)
}
return nil
}

// drawCap draws the cap if it is not clipped.
Expand Down Expand Up @@ -171,7 +172,7 @@ func NewXErrorBars(xerrs interface {
}

// Plot implements the Plotter interface, drawing labels.
func (e *XErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
func (e *XErrorBars) Plot(c draw.Canvas, p *plot.Plot) error {
trX, trY := p.Transforms(&c)
for i, err := range e.XErrors {
y := trY(e.XYs[i].Y)
Expand All @@ -183,6 +184,7 @@ func (e *XErrorBars) Plot(c draw.Canvas, p *plot.Plot) {
e.drawCap(&c, xlow, y)
e.drawCap(&c, xhigh, y)
}
return nil
}

// drawCap draws the cap if it is not clipped.
Expand Down
3 changes: 2 additions & 1 deletion plotter/functions.go
Expand Up @@ -30,7 +30,7 @@ func NewFunction(f func(float64) float64) *Function {

// Plot implements the Plotter interface, drawing a line
// that connects each point in the Line.
func (f *Function) Plot(c draw.Canvas, p *plot.Plot) {
func (f *Function) Plot(c draw.Canvas, p *plot.Plot) error {
trX, trY := p.Transforms(&c)

d := (p.X.Max - p.X.Min) / float64(f.Samples-1)
Expand All @@ -41,6 +41,7 @@ func (f *Function) Plot(c draw.Canvas, p *plot.Plot) {
line[i].Y = trY(f.F(x))
}
c.StrokeLines(f.LineStyle, c.ClipLinesXY(line)...)
return nil
}

// Thumbnail draws a line in the given style down the
Expand Down
3 changes: 2 additions & 1 deletion plotter/glyphbox.go
Expand Up @@ -26,7 +26,7 @@ func NewGlyphBoxes() *GlyphBoxes {
return g
}

func (g GlyphBoxes) Plot(c draw.Canvas, plt *plot.Plot) {
func (g GlyphBoxes) Plot(c draw.Canvas, plt *plot.Plot) error {
for _, b := range plt.GlyphBoxes(plt) {
x := c.X(b.X) + b.Rectangle.Min.X
y := c.Y(b.Y) + b.Rectangle.Min.Y
Expand All @@ -38,4 +38,5 @@ func (g GlyphBoxes) Plot(c draw.Canvas, plt *plot.Plot) {
{x, y},
})
}
return nil
}
6 changes: 4 additions & 2 deletions plotter/grid.go
Expand Up @@ -40,7 +40,7 @@ func NewGrid() *Grid {
}

// Plot implements the plot.Plotter interface.
func (g *Grid) Plot(c draw.Canvas, plt *plot.Plot) {
func (g *Grid) Plot(c draw.Canvas, plt *plot.Plot) error {
trX, trY := plt.Transforms(&c)

var (
Expand All @@ -66,7 +66,7 @@ func (g *Grid) Plot(c draw.Canvas, plt *plot.Plot) {

horiz:
if g.Horizontal.Color == nil {
return
return nil
}
for _, tk := range plt.Y.Tick.Marker.Ticks(plt.Y.Min, plt.Y.Max) {
if tk.IsMinor() {
Expand All @@ -78,4 +78,6 @@ horiz:
}
c.StrokeLine2(g.Horizontal, xmin, y, xmax, y)
}

return nil
}

0 comments on commit 8b37956

Please sign in to comment.