-
Notifications
You must be signed in to change notification settings - Fork 104
/
style.go
342 lines (302 loc) · 10.1 KB
/
style.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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package chart
import (
"fmt"
"image/color"
"math"
)
// Symbol is the list of different symbols.
var Symbol = []int{
'o', // empty circle
'=', // empty square
'%', // empty triangle up
'&', // empty diamond
'+', // plus
'X', // cross
'*', // star
'0', // bulls eys
'@', // filled circle
'#', // filled square
'A', // filled triangle up
'W', // filled triangle down
'V', // empty triangle down
'Z', // filled diamond
'.', // tiny dot
}
// SymbolIndex returns the index of the symbol s in Symbol or -1 if not found.
func SymbolIndex(s int) (idx int) {
for idx = 0; idx < len(Symbol); idx++ {
if Symbol[idx] == s {
return idx
}
}
return -1
}
// NextSymbol returns the next symbol of s: Either in the global list Symbol
// or (if not found there) the next character.
func NextSymbol(s int) int {
if idx := SymbolIndex(s); idx != -1 {
return Symbol[(idx+1)%len(Symbol)]
}
return s + 1
}
// CharacterWidth is a table of the (relative) width of common runes.
var CharacterWidth = map[int]float32{'a': 16.8, 'b': 17.0, 'c': 15.2, 'd': 16.8, 'e': 16.8, 'f': 8.5, 'g': 17.0,
'h': 16.8, 'i': 5.9, 'j': 5.9, 'k': 16.8, 'l': 6.9, 'm': 25.5, 'n': 16.8, 'o': 16.8, 'p': 17.0, 'q': 17.0,
'r': 10.2, 's': 15.2, 't': 8.4, 'u': 16.8, 'v': 15.4, 'w': 22.2, 'x': 15.2, 'y': 15.2, 'z': 15.2,
'A': 20.2, 'B': 20.2, 'C': 22.2, 'D': 22.2, 'E': 20.2, 'F': 18.6, 'G': 23.5, 'H': 22.0, 'I': 8.2, 'J': 15.2,
'K': 20.2, 'L': 16.8, 'M': 25.5, 'N': 22.0, 'O': 23.5, 'P': 20.2, 'Q': 23.5, 'R': 21.1, 'S': 20.2, 'T': 18.5,
'U': 22.0, 'V': 20.2, 'W': 29.0, 'X': 20.2, 'Y': 20.2, 'Z': 18.8, ' ': 8.5,
'1': 16.8, '2': 16.8, '3': 16.8, '4': 16.8, '5': 16.8, '6': 16.8, '7': 16.8, '8': 16.8, '9': 16.8, '0': 16.8,
'.': 8.2, ',': 8.2, ':': 8.2, ';': 8.2, '+': 17.9, '"': 11.0, '*': 11.8, '%': 27.0, '&': 20.2, '/': 8.4,
'(': 10.2, ')': 10.2, '=': 18.0, '?': 16.8, '!': 8.5, '[': 8.2, ']': 8.2, '{': 10.2, '}': 10.2, '$': 16.8,
'<': 18.0, '>': 18.0, '§': 16.8, '°': 12.2, '^': 14.2, '~': 18.0,
}
var averageCharacterWidth float32
func init() {
n := 0
for _, w := range CharacterWidth {
averageCharacterWidth += w
n++
}
averageCharacterWidth /= float32(n)
averageCharacterWidth = 15
}
// Style contains all information about all graphic elements in a chart.
// All colors are in the form "#rrggbb" with rr/gg/bb hexvalues.
// Not all elements of a plot use all fields in this struct.
type Style struct {
Symbol int // 0: no symbol; any codepoint: this symbol
SymbolColor color.Color // color of symbol
SymbolSize float64 // scaling factor of symbol
LineStyle LineStyle // SolidLine, DashedLine, DottedLine, .... see below
LineColor color.Color // color of line
LineWidth int // 0: no line, >=1 width of line in pixel
Font Font // the font to use
FillColor color.Color
}
// PlotStyle describes how data and functions are drawn in scatter plots.
// Can be used to describe how a key entry is drawn
type PlotStyle int
const (
PlotStylePoints PlotStyle = iota + 1 // draw symbol at data point
PlotStyleLines // connect data points by straight lines
PlotStyleLinesPoints // symbols and lines
PlotStyleBox // produce boxplot
)
func (ps PlotStyle) undefined() bool {
return int(ps) < 1 || int(ps) > 3
}
// LineStyle describes the different types of lines.
type LineStyle int
// The supported line styles.
const (
SolidLine LineStyle = iota // ----------------------
DashedLine // ---- ---- ---- ----
DottedLine // - - - - - - - - - - -
DashDotDotLine // ---- - - ---- - -
LongDashLine //
LongDotLine
)
// Font describes a font.
type Font struct {
Name string // "": default
Size FontSize // relative size of font to default in output graphics
Color color.Color // "": default, other: use this
}
// FontSize is the relative font size used in chart. Five sizes seem enough.
type FontSize int
const (
TinyFontSize FontSize = iota - 2
SmallFontSize
NormalFontSize
LargeFontSize
HugeFontSize
)
func (d *Style) empty() bool {
return d.Symbol == 0 && d.SymbolColor == nil && d.LineStyle == 0 &&
d.LineColor == nil && d.FillColor == nil && d.SymbolSize == 0
}
// Standard colors used by AutoStyle
var StandardColors = []color.Color{
color.NRGBA{0xcc, 0x00, 0x00, 0xff}, // red
color.NRGBA{0x00, 0xbb, 0x00, 0xff}, // green
color.NRGBA{0x00, 0x00, 0xdd, 0xff}, // blue
color.NRGBA{0x99, 0x66, 0x00, 0xff}, // brown
color.NRGBA{0xbb, 0x00, 0xbb, 0xff}, // violet
color.NRGBA{0x00, 0xaa, 0xaa, 0xff}, // turquise
color.NRGBA{0xbb, 0xbb, 0x00, 0xff}, // yellow
}
// Standard line styles used by AutoStyle (fill=false)
var StandardLineStyles = []LineStyle{SolidLine, DashedLine, DottedLine, LongDashLine, LongDotLine}
// Standard symbols used by AutoStyle
var StandardSymbols = []int{'o', '=', '%', '&', '+', 'X', '*', '@', '#', 'A', 'Z'}
// How much brighter/darker filled elements become.
var StandardFillFactor = 0.5
// AutoStyle produces a styles based on StandardColors, StandardLineStyles, and StandardSymbols.
// Call with fill = true for charts with filled elements (hist, bar, cbar, pie).
func AutoStyle(i int, fill bool) (style Style) {
nc, nl, ns := len(StandardColors), len(StandardLineStyles), len(StandardSymbols)
si := i % ns
ci := i % nc
li := i % nl
style.Symbol = StandardSymbols[si]
style.SymbolColor = StandardColors[ci]
style.LineColor = StandardColors[ci]
style.SymbolSize = 1
if fill {
style.LineStyle = SolidLine
style.LineWidth = 3
if i < nc {
style.FillColor = lighter(style.LineColor, StandardFillFactor)
} else if i <= 2*nc {
style.FillColor = darker(style.LineColor, StandardFillFactor)
} else {
style.FillColor = style.LineColor
}
} else {
style.LineStyle = StandardLineStyles[li]
style.LineWidth = 1
}
return
}
// PlotElement identifies one element in a plot/chart
type PlotElement int
const (
MajorAxisElement PlotElement = iota
MinorAxisElement
MajorTicElement
MinorTicElement
ZeroAxisElement
GridLineElement
GridBlockElement
KeyElement
TitleElement
RangeLimitElement
)
// PlotOptions contains a Style for each PlotElement. If a PlotOption does not
// contain a certainPlotElement the value in DefaultStyle is used.
type PlotOptions map[PlotElement]Style
func elementStyle(options PlotOptions, element PlotElement) Style {
if style, ok := options[element]; ok {
return style
}
if style, ok := DefaultOptions[element]; ok {
return style
}
return Style{LineColor: color.NRGBA{0x80, 0x80, 0x80, 0xff}, LineWidth: 1, LineStyle: SolidLine}
}
func ElementStyle(options PlotOptions, element PlotElement) Style {
return elementStyle(options, element)
}
// DefaultStyle maps chart elements to styles.
var DefaultOptions = map[PlotElement]Style{
MajorAxisElement: Style{LineColor: color.NRGBA{0, 0, 0, 0xff}, LineWidth: 2, LineStyle: SolidLine}, // axis
MinorAxisElement: Style{LineColor: color.NRGBA{0, 0, 0, 0xff}, LineWidth: 2, LineStyle: SolidLine}, // mirrored axis
MajorTicElement: Style{LineColor: color.NRGBA{0, 0, 0, 0xff}, LineWidth: 1, LineStyle: SolidLine},
MinorTicElement: Style{LineColor: color.NRGBA{0, 0, 0, 0xff}, LineWidth: 1, LineStyle: SolidLine},
ZeroAxisElement: Style{LineColor: color.NRGBA{0x40, 0x40, 0x40, 0xff}, LineWidth: 1, LineStyle: SolidLine},
GridLineElement: Style{LineColor: color.NRGBA{0x80, 0x80, 0x80, 0xff}, LineWidth: 1, LineStyle: SolidLine},
GridBlockElement: Style{LineColor: color.NRGBA{0xe6, 0xfc, 0xfc, 0xff}, LineWidth: 0, FillColor: color.NRGBA{0xe6, 0xfc, 0xfc, 0xff}},
KeyElement: Style{LineColor: color.NRGBA{0x20, 0x20, 0x20, 0xff}, LineWidth: 1, LineStyle: SolidLine,
FillColor: color.NRGBA{0xf0, 0xf0, 0xf0, 0xc0}, Font: Font{Size: SmallFontSize}},
TitleElement: Style{LineColor: color.NRGBA{0, 0, 0, 0xff}, LineWidth: 1, LineStyle: SolidLine,
FillColor: color.NRGBA{0xec, 0xc7, 0x50, 0xff}, Font: Font{Size: LargeFontSize}},
RangeLimitElement: Style{Font: Font{Size: SmallFontSize}},
}
func hsv2rgb(h, s, v int) (r, g, b int) {
H := int(math.Floor(float64(h) / 60))
S, V := float64(s)/100, float64(v)/100
f := float64(h)/60 - float64(H)
p := V * (1 - S)
q := V * (1 - S*f)
t := V * (1 - S*(1-f))
switch H {
case 0, 6:
r, g, b = int(255*V), int(255*t), int(255*p)
case 1:
r, g, b = int(255*q), int(255*V), int(255*p)
case 2:
r, g, b = int(255*p), int(255*V), int(255*t)
case 3:
r, g, b = int(255*p), int(255*q), int(255*V)
case 4:
r, g, b = int(255*t), int(255*p), int(255*V)
case 5:
r, g, b = int(255*V), int(255*p), int(255*q)
default:
panic(fmt.Sprintf("Ooops: Strange H value %d in hsv2rgb(%d,%d,%d).", H, h, s, v))
}
return
}
func f3max(a, b, c float64) float64 {
switch true {
case a > b && a >= c:
return a
case b > c && b >= a:
return b
case c > a && c >= b:
return c
}
return a
}
func f3min(a, b, c float64) float64 {
switch true {
case a < b && a <= c:
return a
case b < c && b <= a:
return b
case c < a && c <= b:
return c
}
return a
}
func rgb2hsv(r, g, b int) (h, s, v int) {
R, G, B := float64(r)/255, float64(g)/255, float64(b)/255
if R == G && G == B {
h, s = 0, 0
v = int(r * 255)
} else {
max, min := f3max(R, G, B), f3min(R, G, B)
if max == R {
h = int(60 * (G - B) / (max - min))
} else if max == G {
h = int(60 * (2 + (B-R)/(max-min)))
} else {
h = int(60 * (4 + (R-G)/(max-min)))
}
if max == 0 {
s = 0
} else {
s = int(100 * (max - min) / max)
}
v = int(100 * max)
}
if h < 0 {
h += 360
}
return
}
func lighter(col color.Color, f float64) color.NRGBA {
r, g, b, a := col.RGBA()
h, s, v := rgb2hsv(int(r/256), int(g/256), int(b/256))
f = 1 - f
s = int(float64(s) * f)
v += int((100 - float64(v)) * f)
if v > 100 {
v = 100
}
rr, gg, bb := hsv2rgb(h, s, v)
return color.NRGBA{uint8(rr), uint8(gg), uint8(bb), uint8(a / 256)}
}
func darker(col color.Color, f float64) color.NRGBA {
r, g, b, a := col.RGBA()
h, s, v := rgb2hsv(int(r), int(g), int(b))
f = 1 - f
v = int(float64(v) * f)
s += int((100 - float64(s)) * f)
if s > 100 {
s = 100
}
rr, gg, bb := hsv2rgb(h, s, v)
return color.NRGBA{uint8(rr), uint8(gg), uint8(bb), uint8(a / 256)}
}