From 952b80918dfa8c4a533c94676aaf7b4c0d44411a Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 1 May 2017 15:28:29 -0700 Subject: [PATCH] plotter: Add ColorBar legend for palette.ColorMap --- plotter/colorbar.go | 107 ++++++++++++++++++ plotter/colorbar_test.go | 59 ++++++++++ .../testdata/colorBarHorizontal_golden.png | Bin 0 -> 2362 bytes plotter/testdata/colorBarVertical_golden.png | Bin 0 -> 3380 bytes 4 files changed, 166 insertions(+) create mode 100644 plotter/colorbar.go create mode 100644 plotter/colorbar_test.go create mode 100644 plotter/testdata/colorBarHorizontal_golden.png create mode 100644 plotter/testdata/colorBarVertical_golden.png diff --git a/plotter/colorbar.go b/plotter/colorbar.go new file mode 100644 index 00000000..98cc9e16 --- /dev/null +++ b/plotter/colorbar.go @@ -0,0 +1,107 @@ +// Copyright ©2017 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 ( + "image" + + "github.com/gonum/plot" + "github.com/gonum/plot/palette" + "github.com/gonum/plot/vg" + "github.com/gonum/plot/vg/draw" +) + +// ColorBar is a plot.Plotter that draws a color bar legend for a ColorMap. +type ColorBar struct { + // Vertical determines wether the legend will be + // plotted vertically or horizontally. + // The default is false (horizontal). + Vertical bool + + // Colors specifies the number of colors to be + // shown in the legend. The default is 255. + Colors int + + cm palette.ColorMap +} + +// NewColorBar creates a new ColorBar plotter. +func NewColorBar(cm palette.ColorMap) *ColorBar { + return &ColorBar{ + cm: cm, + Colors: 255, + } +} + +// Plot implements the Plot method of the plot.Plotter interface. +func (l *ColorBar) Plot(c draw.Canvas, p *plot.Plot) { + if l.cm.Max() == l.cm.Min() { + panic("palette: ColorMap Max==Min") + } + var img *image.NRGBA64 + var xmin, xmax, ymin, ymax vg.Length + if l.Vertical { + trX, trY := p.Transforms(&c) + xmin = trX(l.cm.Min()) + ymin = trY(0) + xmax = trX(l.cm.Max()) + ymax = trY(1) + img = image.NewNRGBA64(image.Rectangle{ + Min: image.Point{X: 0, Y: 0}, + Max: image.Point{X: 1, Y: l.Colors}, + }) + for i := 0; i < l.Colors; i++ { + color, err := l.cm.At(float64(i) / float64(l.Colors-1)) + if err != nil { + panic(err) + } + if l.Vertical { + img.Set(0, l.Colors-1-i, color) + } else { + img.Set(0, i, color) + } + } + } else { + trX, trY := p.Transforms(&c) + ymin = trY(l.cm.Min()) + xmin = trX(0) + ymax = trY(l.cm.Max()) + xmax = trX(1) + img = image.NewNRGBA64(image.Rectangle{ + Min: image.Point{X: 0, Y: 0}, + Max: image.Point{X: l.Colors, Y: 1}, + }) + for i := 0; i < l.Colors; i++ { + color, err := l.cm.At(float64(i) / float64(l.Colors-1)) + if err != nil { + panic(err) + } + img.Set(i, 0, color) + } + } + rect := vg.Rectangle{ + Min: vg.Point{X: xmin, Y: ymin}, + Max: vg.Point{X: xmax, Y: ymax}, + } + c.DrawImage(rect, img) +} + +// DataRange implements the DataRange method +// of the plot.DataRanger interface. +func (l *ColorBar) DataRange() (xmin, xmax, ymin, ymax float64) { + if l.cm.Max() == l.cm.Min() { + panic("palette: ColorMap Max==Min") + } + if l.Vertical { + return 0, 1, l.cm.Min(), l.cm.Max() + } + return l.cm.Min(), l.cm.Max(), 0, 1 +} + +// GlyphBoxes implements the GlyphBoxes method +// of the plot.GlyphBoxer interface. +func (l *ColorBar) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { + return nil +} diff --git a/plotter/colorbar_test.go b/plotter/colorbar_test.go new file mode 100644 index 00000000..7e5a876b --- /dev/null +++ b/plotter/colorbar_test.go @@ -0,0 +1,59 @@ +// Copyright ©2017 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 ( + "log" + "testing" + + "github.com/gonum/plot" + "github.com/gonum/plot/internal/cmpimg" + "github.com/gonum/plot/palette/moreland" +) + +func ExampleColorBar_horizontal() { + p, err := plot.New() + if err != nil { + log.Panic(err) + } + cm := moreland.ExtendedBlackBody() + cm.SetMax(1) + l := NewColorBar(cm) + p.Add(l) + p.HideY() + p.X.Padding = 0 + p.Title.Text = "Title" + + if err = p.Save(300, 48, "testdata/colorBarHorizontal.png"); err != nil { + log.Panic(err) + } +} + +func TestColorBar_horizontal(t *testing.T) { + cmpimg.CheckPlot(ExampleColorBar_horizontal, t, "colorBarHorizontal.png") +} + +func ExampleColorBar_vertical() { + p, err := plot.New() + if err != nil { + log.Panic(err) + } + cm := moreland.ExtendedBlackBody() + cm.SetMax(1) + l := NewColorBar(cm) + l.Vertical = true + p.Add(l) + p.HideX() + p.Y.Padding = 0 + p.Title.Text = "Title" + + if err = p.Save(40, 300, "testdata/colorBarVertical.png"); err != nil { + log.Panic(err) + } +} + +func TestColorBar_vertical(t *testing.T) { + cmpimg.CheckPlot(ExampleColorBar_vertical, t, "colorBarVertical.png") +} diff --git a/plotter/testdata/colorBarHorizontal_golden.png b/plotter/testdata/colorBarHorizontal_golden.png new file mode 100644 index 0000000000000000000000000000000000000000..3c0a246778bf1eeeb7ecd07d3822d6e90e6f02f3 GIT binary patch literal 2362 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