...
1
2
3
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
17
18
19
20 type Legend struct {
21
22
23 TextStyle text.Style
24
25
26
27
28
29 Padding vg.Length
30
31
32
33
34
35
36
37
38
39 Top, Left bool
40
41
42
43 XOffs, YOffs vg.Length
44
45
46
47
48 YPosition float64
49
50
51 ThumbnailWidth vg.Length
52
53
54
55 entries []legendEntry
56 }
57
58
59
60 type legendEntry struct {
61
62 text string
63
64
65 thumbs []Thumbnailer
66 }
67
68
69
70
71 type Thumbnailer interface {
72
73
74
75
76 Thumbnail(c *draw.Canvas)
77 }
78
79
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
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
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
174
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
185
186
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