forked from vdobler/chart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
key.go
264 lines (242 loc) · 7.13 KB
/
key.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package chart
import (
"strings"
)
// Key encapsulates settings for keys/legends in a chart.
//
// Key placement os governed by Pos which may take the following values:
// otl otc otr
// +-------------+
// olt |itl itc itr| ort
// | |
// olc |icl icc icr| orc
// | |
// olb |ibl ibc ibr| orb
// +-------------+
// obl obc obr
//
type Key struct {
Hide bool // Don't show key/legend if true
Cols int // Number of colums to use. If <0 fill rows before colums
Border int // -1: off, 0: std, 1...:other styles
Pos string // default "" is "itr"
Entries []KeyEntry // List of entries in the legend
X, Y int
}
// KeyEntry encapsulates an antry in the key/legend.
type KeyEntry struct {
Text string // Text to display
PlotStyle PlotStyle // What to show: symbol, line, bar, combi thereof
Style Style // How to show
}
// Place layouts the Entries in key in the requested (by key.Cols) matrix format
func (key Key) Place() (matrix [][]*KeyEntry) {
// count real entries in num, see if multilines are present in haveml
num := 0
for _, e := range key.Entries {
if e.Text == "" {
continue
}
num++
}
if num == 0 {
return // no entries
}
rowfirst := false
cols := key.Cols
if cols < 0 {
cols = -cols
rowfirst = true
}
if cols == 0 {
cols = 1
}
if num < cols {
cols = num
}
rows := (num + cols - 1) / cols
// Prevent empty last columns in the following case where 5 elements are placed
// columnsfirst into 4 columns
// Col 0 1 2 3
// AAA CCC EEE
// BBB DDD
if !rowfirst && rows*(cols-1) >= num {
cols--
}
// Arrays with infos
matrix = make([][]*KeyEntry, cols)
for i := 0; i < cols; i++ {
matrix[i] = make([]*KeyEntry, rows)
}
i := 0
for _, e := range key.Entries {
if e.Text == "" {
continue
}
var r, c int
if rowfirst {
r, c = i/cols, i%cols
} else {
c, r = i/rows, i%rows
}
matrix[c][r] = &KeyEntry{Text: e.Text, Style: e.Style, PlotStyle: e.PlotStyle}
// fmt.Printf("Place1 (%d,%d) = %d: %s\n", c,r, i, matrix[c][r].Text)
i++
}
return
}
func textviewlen(t string) (length float32) {
n := 0
for _, r := range t {
if w, ok := CharacterWidth[int(r)]; ok {
length += w
} else {
length += 23 // save above average
}
n++
}
length /= averageCharacterWidth
// fmt.Printf("Length >%s<: %d runes = %.2f (%d)\n", t, n, length, int(100*length/float32(n)))
return
}
func textDim(t string) (w float32, h int) {
lines := strings.Split(t, "\n")
for _, t := range lines {
tvl := textviewlen(t)
if tvl > w {
w = tvl
}
}
h = len(lines)
return
}
// The following variables control the layout of the key/legend box.
// All values are in font-units (fontheight for vertical, fontwidth for horizontal values)
var (
KeyHorSep float32 = 1.5 // Horizontal spacing between key box and content
KeyVertSep float32 = 0.5 // Vertical spacing between key box and content
KeyColSep float32 = 2.0 // Horizontal spacing between two columns in key
KeySymbolWidth float32 = 5 // Horizontal length/space reserved for symbol
KeySymbolSep float32 = 2 // Horizontal spacing bewteen symbol and text
KeyRowSep float32 = 0.75 // Vertical spacing between individual rows.
)
func (key Key) Layout(bg BasicGraphics, m [][]*KeyEntry, font Font) (w, h int, colwidth, rowheight []int) {
fontwidth, fontheight, _ := bg.FontMetrics(font)
cols, rows := len(m), len(m[0])
// Find total width and height
totalh := 0
rowheight = make([]int, rows)
for r := 0; r < rows; r++ {
rh := 0
for c := 0; c < cols; c++ {
e := m[c][r]
if e == nil {
continue
}
// fmt.Printf("Layout1 (%d,%d): %s\n", c,r,e.Text)
_, h := textDim(e.Text)
if h > rh {
rh = h
}
}
rowheight[r] = rh
totalh += rh
}
totalw := 0
colwidth = make([]int, cols)
// fmt.Printf("Making totalw for %d cols\n", cols)
for c := 0; c < cols; c++ {
var rw float32
for r := 0; r < rows; r++ {
e := m[c][r]
if e == nil {
continue
}
// fmt.Printf("Layout2 (%d,%d): %s\n", c,r,e.Text)
w, _ := textDim(e.Text)
if w > rw {
rw = w
}
}
irw := int(rw + 0.75)
colwidth[c] = irw
totalw += irw
// fmt.Printf("Width of col %d: %d. Total now: %d\n", c, irw, totalw)
}
if fontwidth == 1 && fontheight == 1 {
// totalw/h are characters only and still in character-units
totalw += int(KeyColSep) * (cols - 1) // add space between columns
totalw += int(2*KeyHorSep + 0.5) // add space for left/right border
totalw += int(KeySymbolWidth+KeySymbolSep+0.5) * cols // place for symbol and symbol-text sep
totalh += int(KeyRowSep) * (rows - 1) // add space between rows
vsep := KeyVertSep
if vsep < 1 {
vsep = 1
} // make sure there _is_ room (as KeyVertSep < 1)
totalh += int(2 * vsep) // add border at top/bottom
} else {
// totalw/h are characters only and still in character-units
totalw = int(float32(totalw) * fontwidth) // scale to pixels
totalw += int(KeyColSep * (float32(cols-1) * fontwidth)) // add space between columns
totalw += int(2 * KeyHorSep * fontwidth) // add space for left/right border
totalw += int((KeySymbolWidth+KeySymbolSep)*fontwidth) * cols // place for symbol and symbol-text sep
totalh *= fontheight
totalh += int(KeyRowSep * float32((rows-1)*fontheight)) // add space between rows
vsep := KeyVertSep * float32(fontheight)
if vsep < 1 {
vsep = 1
} // make sure there _is_ room (as KeyVertSep < 1)
totalh += int(2 * vsep) // add border at top/bottom
}
return totalw, totalh, colwidth, rowheight
}
func GenericKey(bg BasicGraphics, x, y int, key Key, options PlotOptions) {
m := key.Place()
if len(m) == 0 {
return
}
keyfont := elementStyle(options, KeyElement).Font
fw, fh, _ := bg.FontMetrics(keyfont)
tw, th, cw, rh := key.Layout(bg, m, keyfont)
style := elementStyle(options, KeyElement)
if key.Border >= 0 {
bg.Rect(x, y, tw, th, style)
}
x += int(KeyHorSep * fw)
vsep := KeyVertSep * float32(fh)
if vsep < 1 {
vsep = 1
} // make sure there _is_ room (as KeyVertSep < 1)
// fmt.Printf("Key: y = %d after %d\n", y, y+int(vsep)+fh/2)
y += int(vsep) + fh/2
for ci, col := range m {
yy := y
for ri, e := range col {
if e == nil || e.Text == "" {
continue
}
plotStyle := e.PlotStyle
// fmt.Printf("KeyEntry %s: PlotStyle = %d\n", e.Text, e.PlotStyle)
if plotStyle == -1 {
// heading only...
bg.Text(x, yy, e.Text, "cl", 0, keyfont)
} else {
// normal entry
if (plotStyle & PlotStyleLines) != 0 {
bg.Line(x, yy, x+int(KeySymbolWidth*fw), yy, e.Style)
}
if (plotStyle & PlotStylePoints) != 0 {
bg.Symbol(x+int(KeySymbolWidth*fw)/2, yy, e.Style)
}
if (plotStyle & PlotStyleBox) != 0 {
sh := fh / 2
a := x + int(KeySymbolWidth*fw)/2
bg.Rect(a-sh, yy-sh, 2*sh, 2*sh, e.Style)
}
bg.Text(x+int(fw*(KeySymbolWidth+KeySymbolSep)), yy, e.Text, "cl", 0, keyfont)
}
yy += fh*rh[ri] + int(KeyRowSep*float32(fh))
}
x += int((KeySymbolWidth + KeySymbolSep + KeyColSep + float32(cw[ci])) * fw)
}
}