190 lines
4.9 KiB
Go
190 lines
4.9 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 plot
|
|
|
|
import (
|
|
"math"
|
|
|
|
"gonum.org/v1/plot/font"
|
|
"gonum.org/v1/plot/text"
|
|
"gonum.org/v1/plot/vg"
|
|
"gonum.org/v1/plot/vg/draw"
|
|
)
|
|
|
|
// A Legend gives a description of the meaning of different
|
|
// data elements of the plot. Each legend entry has a name
|
|
// and a thumbnail, where the thumbnail shows a small
|
|
// sample of the display style of the corresponding data.
|
|
type Legend struct {
|
|
// TextStyle is the style given to the legend
|
|
// entry texts.
|
|
TextStyle text.Style
|
|
|
|
// Padding is the amount of padding to add
|
|
// between each entry in the legend. If Padding
|
|
// is zero then entries are spaced based on the
|
|
// font size.
|
|
Padding vg.Length
|
|
|
|
// Top and Left specify the location of the legend.
|
|
// If Top is true the legend is located along the top
|
|
// edge of the plot, otherwise it is located along
|
|
// the bottom edge. If Left is true then the legend
|
|
// is located along the left edge of the plot, and the
|
|
// text is positioned after the icons, otherwise it is
|
|
// located along the right edge and the text is
|
|
// positioned before the icons.
|
|
Top, Left bool
|
|
|
|
// XOffs and YOffs are added to the legend's
|
|
// final position.
|
|
XOffs, YOffs vg.Length
|
|
|
|
// YPosition specifies the vertical position of a legend entry.
|
|
// Valid values are [-1,+1], with +1 being the top of the
|
|
// entry vertical space, and -1 the bottom.
|
|
YPosition float64
|
|
|
|
// ThumbnailWidth is the width of legend thumbnails.
|
|
ThumbnailWidth vg.Length
|
|
|
|
// entries are all of the legendEntries described
|
|
// by this legend.
|
|
entries []legendEntry
|
|
}
|
|
|
|
// A legendEntry represents a single line of a legend, it
|
|
// has a name and an icon.
|
|
type legendEntry struct {
|
|
// text is the text associated with this entry.
|
|
text string
|
|
|
|
// thumbs is a slice of all of the thumbnails styles
|
|
thumbs []Thumbnailer
|
|
}
|
|
|
|
// Thumbnailer wraps the Thumbnail method, which
|
|
// draws the small image in a legend representing the
|
|
// style of data.
|
|
type Thumbnailer interface {
|
|
// Thumbnail draws an thumbnail representing
|
|
// a legend entry. The thumbnail will usually show
|
|
// a smaller representation of the style used
|
|
// to plot the corresponding data.
|
|
Thumbnail(c *draw.Canvas)
|
|
}
|
|
|
|
// NewLegend returns a legend with the default parameter settings.
|
|
func NewLegend() Legend {
|
|
return newLegend(DefaultTextHandler)
|
|
}
|
|
|
|
func newLegend(hdlr text.Handler) Legend {
|
|
return Legend{
|
|
YPosition: draw.PosBottom,
|
|
ThumbnailWidth: vg.Points(20),
|
|
TextStyle: text.Style{
|
|
Font: font.From(DefaultFont, 12),
|
|
Handler: hdlr,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Draw draws the legend to the given draw.Canvas.
|
|
func (l *Legend) Draw(c draw.Canvas) {
|
|
iconx := c.Min.X
|
|
sty := l.TextStyle
|
|
em := sty.Rectangle(" ")
|
|
textx := iconx + l.ThumbnailWidth + em.Max.X
|
|
if !l.Left {
|
|
iconx = c.Max.X - l.ThumbnailWidth
|
|
textx = iconx - em.Max.X
|
|
sty.XAlign--
|
|
}
|
|
textx += l.XOffs
|
|
iconx += l.XOffs
|
|
|
|
descent := sty.FontExtents().Descent
|
|
enth := l.entryHeight()
|
|
y := c.Max.Y - enth - descent
|
|
if !l.Top {
|
|
y = c.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1)
|
|
}
|
|
y += l.YOffs
|
|
|
|
icon := &draw.Canvas{
|
|
Canvas: c.Canvas,
|
|
Rectangle: vg.Rectangle{
|
|
Min: vg.Point{X: iconx, Y: y},
|
|
Max: vg.Point{X: iconx + l.ThumbnailWidth, Y: y + enth},
|
|
},
|
|
}
|
|
|
|
if l.YPosition < draw.PosBottom || draw.PosTop < l.YPosition {
|
|
panic("plot: invalid vertical offset for the legend's entries")
|
|
}
|
|
yoff := vg.Length(l.YPosition-draw.PosBottom) / 2
|
|
yoff += descent
|
|
|
|
for _, e := range l.entries {
|
|
for _, t := range e.thumbs {
|
|
t.Thumbnail(icon)
|
|
}
|
|
yoffs := (enth - descent - sty.Rectangle(e.text).Max.Y) / 2
|
|
yoffs += yoff
|
|
c.FillText(sty, vg.Point{X: textx, Y: icon.Min.Y + yoffs}, e.text)
|
|
icon.Min.Y -= enth + l.Padding
|
|
icon.Max.Y -= enth + l.Padding
|
|
}
|
|
}
|
|
|
|
// Rectangle returns the extent of the Legend.
|
|
func (l *Legend) Rectangle(c draw.Canvas) vg.Rectangle {
|
|
var width, height vg.Length
|
|
sty := l.TextStyle
|
|
entryHeight := l.entryHeight()
|
|
for i, e := range l.entries {
|
|
width = vg.Length(math.Max(float64(width), float64(l.ThumbnailWidth+sty.Rectangle(" "+e.text).Max.X)))
|
|
height += entryHeight
|
|
if i != 0 {
|
|
height += l.Padding
|
|
}
|
|
}
|
|
var r vg.Rectangle
|
|
if l.Left {
|
|
r.Max.X = c.Max.X
|
|
r.Min.X = c.Max.X - width
|
|
} else {
|
|
r.Max.X = c.Min.X + width
|
|
r.Min.X = c.Min.X
|
|
}
|
|
if l.Top {
|
|
r.Max.Y = c.Max.Y
|
|
r.Min.Y = c.Max.Y - height
|
|
} else {
|
|
r.Max.Y = c.Min.Y + height
|
|
r.Min.Y = c.Min.Y
|
|
}
|
|
return r
|
|
}
|
|
|
|
// entryHeight returns the height of the tallest legend
|
|
// entry text.
|
|
func (l *Legend) entryHeight() (height vg.Length) {
|
|
for _, e := range l.entries {
|
|
if h := l.TextStyle.Rectangle(e.text).Max.Y; h > height {
|
|
height = h
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Add adds an entry to the legend with the given name.
|
|
// The entry's thumbnail is drawn as the composite of all of the
|
|
// thumbnails.
|
|
func (l *Legend) Add(name string, thumbs ...Thumbnailer) {
|
|
l.entries = append(l.entries, legendEntry{text: name, thumbs: thumbs})
|
|
}
|