...

Source file src/gonum.org/v1/plot/legend.go

Documentation: gonum.org/v1/plot

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package plot
     6  
     7  import (
     8  	"math"
     9  
    10  	"gonum.org/v1/plot/font"
    11  	"gonum.org/v1/plot/text"
    12  	"gonum.org/v1/plot/vg"
    13  	"gonum.org/v1/plot/vg/draw"
    14  )
    15  
    16  // A Legend gives a description of the meaning of different
    17  // data elements of the plot.  Each legend entry has a name
    18  // and a thumbnail, where the thumbnail shows a small
    19  // sample of the display style of the corresponding data.
    20  type Legend struct {
    21  	// TextStyle is the style given to the legend
    22  	// entry texts.
    23  	TextStyle text.Style
    24  
    25  	// Padding is the amount of padding to add
    26  	// between each entry in the legend.  If Padding
    27  	// is zero then entries are spaced based on the
    28  	// font size.
    29  	Padding vg.Length
    30  
    31  	// Top and Left specify the location of the legend.
    32  	// If Top is true the legend is located along the top
    33  	// edge of the plot, otherwise it is located along
    34  	// the bottom edge.  If Left is true then the legend
    35  	// is located along the left edge of the plot, and the
    36  	// text is positioned after the icons, otherwise it is
    37  	// located along the right edge and the text is
    38  	// positioned before the icons.
    39  	Top, Left bool
    40  
    41  	// XOffs and YOffs are added to the legend's
    42  	// final position.
    43  	XOffs, YOffs vg.Length
    44  
    45  	// YPosition specifies the vertical position of a legend entry.
    46  	// Valid values are [-1,+1], with +1 being the top of the
    47  	// entry vertical space, and -1 the bottom.
    48  	YPosition float64
    49  
    50  	// ThumbnailWidth is the width of legend thumbnails.
    51  	ThumbnailWidth vg.Length
    52  
    53  	// entries are all of the legendEntries described
    54  	// by this legend.
    55  	entries []legendEntry
    56  }
    57  
    58  // A legendEntry represents a single line of a legend, it
    59  // has a name and an icon.
    60  type legendEntry struct {
    61  	// text is the text associated with this entry.
    62  	text string
    63  
    64  	// thumbs is a slice of all of the thumbnails styles
    65  	thumbs []Thumbnailer
    66  }
    67  
    68  // Thumbnailer wraps the Thumbnail method, which
    69  // draws the small image in a legend representing the
    70  // style of data.
    71  type Thumbnailer interface {
    72  	// Thumbnail draws an thumbnail representing
    73  	// a legend entry.  The thumbnail will usually show
    74  	// a smaller representation of the style used
    75  	// to plot the corresponding data.
    76  	Thumbnail(c *draw.Canvas)
    77  }
    78  
    79  // NewLegend returns a legend with the default parameter settings.
    80  func NewLegend() Legend {
    81  	return newLegend(DefaultTextHandler)
    82  }
    83  
    84  func newLegend(hdlr text.Handler) Legend {
    85  	return Legend{
    86  		YPosition:      draw.PosBottom,
    87  		ThumbnailWidth: vg.Points(20),
    88  		TextStyle: text.Style{
    89  			Font:    font.From(DefaultFont, 12),
    90  			Handler: hdlr,
    91  		},
    92  	}
    93  }
    94  
    95  // Draw draws the legend to the given draw.Canvas.
    96  func (l *Legend) Draw(c draw.Canvas) {
    97  	iconx := c.Min.X
    98  	sty := l.TextStyle
    99  	em := sty.Rectangle(" ")
   100  	textx := iconx + l.ThumbnailWidth + em.Max.X
   101  	if !l.Left {
   102  		iconx = c.Max.X - l.ThumbnailWidth
   103  		textx = iconx - em.Max.X
   104  		sty.XAlign--
   105  	}
   106  	textx += l.XOffs
   107  	iconx += l.XOffs
   108  
   109  	descent := sty.FontExtents().Descent
   110  	enth := l.entryHeight()
   111  	y := c.Max.Y - enth - descent
   112  	if !l.Top {
   113  		y = c.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1)
   114  	}
   115  	y += l.YOffs
   116  
   117  	icon := &draw.Canvas{
   118  		Canvas: c.Canvas,
   119  		Rectangle: vg.Rectangle{
   120  			Min: vg.Point{X: iconx, Y: y},
   121  			Max: vg.Point{X: iconx + l.ThumbnailWidth, Y: y + enth},
   122  		},
   123  	}
   124  
   125  	if l.YPosition < draw.PosBottom || draw.PosTop < l.YPosition {
   126  		panic("plot: invalid vertical offset for the legend's entries")
   127  	}
   128  	yoff := vg.Length(l.YPosition-draw.PosBottom) / 2
   129  	yoff += descent
   130  
   131  	for _, e := range l.entries {
   132  		for _, t := range e.thumbs {
   133  			t.Thumbnail(icon)
   134  		}
   135  		yoffs := (enth - descent - sty.Rectangle(e.text).Max.Y) / 2
   136  		yoffs += yoff
   137  		c.FillText(sty, vg.Point{X: textx, Y: icon.Min.Y + yoffs}, e.text)
   138  		icon.Min.Y -= enth + l.Padding
   139  		icon.Max.Y -= enth + l.Padding
   140  	}
   141  }
   142  
   143  // Rectangle returns the extent of the Legend.
   144  func (l *Legend) Rectangle(c draw.Canvas) vg.Rectangle {
   145  	var width, height vg.Length
   146  	sty := l.TextStyle
   147  	entryHeight := l.entryHeight()
   148  	for i, e := range l.entries {
   149  		width = vg.Length(math.Max(float64(width), float64(l.ThumbnailWidth+sty.Rectangle(" "+e.text).Max.X)))
   150  		height += entryHeight
   151  		if i != 0 {
   152  			height += l.Padding
   153  		}
   154  	}
   155  	var r vg.Rectangle
   156  	if l.Left {
   157  		r.Max.X = c.Max.X
   158  		r.Min.X = c.Max.X - width
   159  	} else {
   160  		r.Max.X = c.Min.X + width
   161  		r.Min.X = c.Min.X
   162  	}
   163  	if l.Top {
   164  		r.Max.Y = c.Max.Y
   165  		r.Min.Y = c.Max.Y - height
   166  	} else {
   167  		r.Max.Y = c.Min.Y + height
   168  		r.Min.Y = c.Min.Y
   169  	}
   170  	return r
   171  }
   172  
   173  // entryHeight returns the height of the tallest legend
   174  // entry text.
   175  func (l *Legend) entryHeight() (height vg.Length) {
   176  	for _, e := range l.entries {
   177  		if h := l.TextStyle.Rectangle(e.text).Max.Y; h > height {
   178  			height = h
   179  		}
   180  	}
   181  	return
   182  }
   183  
   184  // Add adds an entry to the legend with the given name.
   185  // The entry's thumbnail is drawn as the composite of all of the
   186  // thumbnails.
   187  func (l *Legend) Add(name string, thumbs ...Thumbnailer) {
   188  	l.entries = append(l.entries, legendEntry{text: name, thumbs: thumbs})
   189  }
   190  

View as plain text