Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
591 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright ©2016 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 palette | ||
|
||
import ( | ||
"image" | ||
|
||
"github.com/gonum/plot" | ||
"github.com/gonum/plot/vg" | ||
"github.com/gonum/plot/vg/draw" | ||
) | ||
|
||
// ColorMapLegend is a plot.Plotter that draws a color bar legend for a ColorMap. | ||
type ColorMapLegend struct { | ||
// Vertical determines wether the legend will be | ||
// plotted vertically or horizontally. | ||
// The default is false (horizontal). | ||
Vertical bool | ||
|
||
// NColors specifies the number of colors to be | ||
// shown in the legend. The default is 255. | ||
NColors int | ||
|
||
cm ColorMap | ||
} | ||
|
||
// NewColorMapLegend creates a new legend plotter. | ||
func NewColorMapLegend(cm ColorMap) *ColorMapLegend { | ||
return &ColorMapLegend{ | ||
cm: cm, | ||
NColors: 255, | ||
} | ||
} | ||
|
||
// Plot implements the Plot method of the plot.Plotter interface. | ||
func (l *ColorMapLegend) 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.NColors}, | ||
}) | ||
for i := 0; i < l.NColors; i++ { | ||
color, err := l.cm.At(float64(i) / float64(l.NColors-1)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
if l.Vertical { | ||
img.Set(0, l.NColors-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.NColors, Y: 1}, | ||
}) | ||
for i := 0; i < l.NColors; i++ { | ||
color, err := l.cm.At(float64(i) / float64(l.NColors-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 *ColorMapLegend) 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 | ||
} | ||
|
||
// SetupPlot changes the default settings of p so that | ||
// they are appropriate for plotting a color bar legend. | ||
func (l *ColorMapLegend) SetupPlot(p *plot.Plot) { | ||
if l.Vertical { | ||
p.HideX() | ||
p.Y.Padding = 0 | ||
} else { | ||
p.HideY() | ||
p.X.Padding = 0 | ||
} | ||
} | ||
|
||
// GlyphBoxes implements the GlyphBoxes method | ||
// of the plot.GlyphBoxer interface. | ||
func (l *ColorMapLegend) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright ©2016 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 palette | ||
|
||
import ( | ||
"log" | ||
"testing" | ||
|
||
"github.com/gonum/plot" | ||
"github.com/gonum/plot/internal/cmpimg" | ||
"github.com/gonum/plot/palette/moreland" | ||
) | ||
|
||
func ExampleColorMapLegend_horizontal() { | ||
p, err := plot.New() | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
cm := moreland.ExtendedBlackBody() | ||
cm.SetMax(1) | ||
l := NewColorMapLegend(cm) | ||
p.Add(l) | ||
l.SetupPlot(p) | ||
|
||
if err = p.Save(300, 40, "testdata/colorMapLegendHorizontal.png"); err != nil { | ||
log.Panic(err) | ||
} | ||
} | ||
|
||
func TestColorMapLegend_horizontal(t *testing.T) { | ||
cmpimg.CheckPlot(ExampleColorMapLegend_horizontal, t, "colorMapLegendHorizontal.png") | ||
} | ||
|
||
func ExampleColorMapLegend_vertical() { | ||
p, err := plot.New() | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
cm := moreland.ExtendedBlackBody() | ||
cm.SetMax(1) | ||
l := NewColorMapLegend(cm) | ||
l.Vertical = true | ||
p.Add(l) | ||
l.SetupPlot(p) | ||
|
||
if err = p.Save(40, 300, "testdata/colorMapLegendVertical.png"); err != nil { | ||
log.Panic(err) | ||
} | ||
} | ||
|
||
func TestColorMapLegend_vertical(t *testing.T) { | ||
cmpimg.CheckPlot(ExampleColorMapLegend_vertical, t, "colorMapLegendVertical.png") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
// Copyright ©2016 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 palette | ||
|
||
import ( | ||
"fmt" | ||
"image/color" | ||
|
||
"github.com/gonum/plot" | ||
"github.com/gonum/plot/vg" | ||
"github.com/gonum/plot/vg/draw" | ||
) | ||
|
||
var ( | ||
// DefaultLineStyle is the default style for drawing | ||
// lines. | ||
DefaultLineStyle = draw.LineStyle{ | ||
Color: color.Black, | ||
Width: vg.Points(1), | ||
Dashes: []vg.Length{}, | ||
DashOffs: 0, | ||
} | ||
) | ||
|
||
// Legend is a plot.Plotter that draws a legend for a Palette. | ||
type Legend struct { | ||
p []color.Color | ||
|
||
// Width is the width of each color rectangle. | ||
Width vg.Length | ||
|
||
// LineStyle is the style of the outline of the rectangles. | ||
draw.LineStyle | ||
|
||
// Offset is added to the X location of each rectangle. | ||
// When the Offset is zero, the rectangles are drawn | ||
// centered at their X location. | ||
Offset vg.Length | ||
|
||
// XMin is the X location of the first rectangle. XMin | ||
// can be changed to move the legend | ||
// down the X axis in order to make grouped | ||
// legends. | ||
XMin float64 | ||
|
||
// Horizontal determines wether the legend will be | ||
// plotted horizontally or vertically. | ||
// The default is false (vertical). | ||
Horizontal bool | ||
} | ||
|
||
// NewLegend creates a new legend plotter. | ||
func NewLegend(p Palette, width vg.Length) *Legend { | ||
return &Legend{ | ||
p: p.Colors(), | ||
Width: width, | ||
LineStyle: DefaultLineStyle, | ||
} | ||
} | ||
|
||
// Plot implements the Plot method of the plot.Plotter interface. | ||
func (l *Legend) Plot(c draw.Canvas, plt *plot.Plot) { | ||
trCat, trVal := plt.Transforms(&c) | ||
if !l.Horizontal { | ||
trCat, trVal = trVal, trCat | ||
} | ||
|
||
for i, rectColor := range l.p { | ||
catVal := l.XMin + float64(i) | ||
catMin := trCat(float64(catVal)) | ||
if l.Horizontal { | ||
if !c.ContainsX(catMin) { | ||
continue | ||
} | ||
} else { | ||
if !c.ContainsY(catMin) { | ||
continue | ||
} | ||
} | ||
catMin = catMin - l.Width/2 + l.Offset | ||
catMax := catMin + l.Width | ||
valMin := trVal(0) | ||
valMax := trVal(1) | ||
|
||
var pts []vg.Point | ||
var poly []vg.Point | ||
if l.Horizontal { | ||
pts = []vg.Point{ | ||
{X: catMin, Y: valMin}, | ||
{X: catMin, Y: valMax}, | ||
{X: catMax, Y: valMax}, | ||
{X: catMax, Y: valMin}, | ||
} | ||
poly = c.ClipPolygonY(pts) | ||
} else { | ||
pts = []vg.Point{ | ||
{X: valMin, Y: catMin}, | ||
{X: valMin, Y: catMax}, | ||
{X: valMax, Y: catMax}, | ||
{X: valMax, Y: catMin}, | ||
} | ||
poly = c.ClipPolygonX(pts) | ||
} | ||
c.FillPolygon(rectColor, poly) | ||
|
||
var outline [][]vg.Point | ||
if l.Horizontal { | ||
pts = append(pts, vg.Point{X: catMin, Y: valMin}) | ||
outline = c.ClipLinesY(pts) | ||
} else { | ||
pts = append(pts, vg.Point{X: valMin, Y: catMin}) | ||
outline = c.ClipLinesX(pts) | ||
} | ||
c.StrokeLines(l.LineStyle, outline...) | ||
} | ||
} | ||
|
||
// DataRange implements the plot.DataRanger interface. | ||
func (l *Legend) DataRange() (xmin, xmax, ymin, ymax float64) { | ||
catMin := l.XMin | ||
catMax := catMin + float64(len(l.p)-1) | ||
|
||
valMin := 0.0 | ||
valMax := 1.0 | ||
if l.Horizontal { | ||
return catMin, catMax, valMin, valMax | ||
} | ||
return valMin, valMax, catMin, catMax | ||
} | ||
|
||
// GlyphBoxes implements the GlyphBoxer interface. | ||
func (l *Legend) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { | ||
boxes := make([]plot.GlyphBox, len(l.p)) | ||
for i := range boxes { | ||
cat := l.XMin + float64(i) | ||
if l.Horizontal { | ||
boxes[i].X = plt.X.Norm(cat) | ||
boxes[i].Rectangle = vg.Rectangle{ | ||
Min: vg.Point{X: l.Offset - l.Width/2}, | ||
Max: vg.Point{X: l.Offset + l.Width/2}, | ||
} | ||
} else { | ||
boxes[i].Y = plt.Y.Norm(cat) | ||
boxes[i].Rectangle = vg.Rectangle{ | ||
Min: vg.Point{Y: l.Offset - l.Width/2}, | ||
Max: vg.Point{Y: l.Offset + l.Width/2}, | ||
} | ||
} | ||
} | ||
return boxes | ||
} | ||
|
||
// Legend creates a Legend plotter for this StringMap. | ||
func (sm *StringMap) Legend(width vg.Length) *Legend { | ||
return &Legend{ | ||
p: sm.Colors, | ||
Width: width, | ||
LineStyle: DefaultLineStyle, | ||
} | ||
} | ||
|
||
// Legend creates a Legend plotter for this IntMap. | ||
func (im *IntMap) Legend(width vg.Length) *Legend { | ||
return &Legend{ | ||
p: im.Colors, | ||
Width: width, | ||
LineStyle: DefaultLineStyle, | ||
} | ||
} | ||
|
||
// SetupPlot changes the default settings of p so that | ||
// they are appropriate for plotting a legend. | ||
func (sm *StringMap) SetupPlot(l *Legend, p *plot.Plot) { | ||
if !l.Horizontal { | ||
p.HideX() | ||
p.Y.Padding = 0 | ||
p.NominalY(sm.Categories...) | ||
} else { | ||
p.HideY() | ||
p.X.Padding = 0 | ||
p.NominalX(sm.Categories...) | ||
} | ||
} | ||
|
||
// SetupPlot changes the default settings of p so that | ||
// they are appropriate for plotting a legend. | ||
func (im *IntMap) SetupPlot(l *Legend, p *plot.Plot) { | ||
cats := make([]string, len(im.Categories)) | ||
for i, c := range im.Categories { | ||
cats[i] = fmt.Sprintf("%d", c) | ||
} | ||
if !l.Horizontal { | ||
p.HideX() | ||
p.Y.Padding = 0 | ||
p.NominalY(cats...) | ||
} else { | ||
p.HideY() | ||
p.X.Padding = 0 | ||
p.NominalX(cats...) | ||
} | ||
} |
Oops, something went wrong.