podman

Форк
0
329 строк · 5.9 Кб
1
package goterm
2

3
import (
4
	"fmt"
5
	"math"
6
	"strings"
7
	"unicode/utf8"
8
)
9

10
const (
11
	AXIS_LEFT = iota
12
	AXIS_RIGHT
13
)
14

15
const (
16
	DRAW_INDEPENDENT = 1 << iota
17
	DRAW_RELATIVE
18
)
19

20
type DataTable struct {
21
	columns []string
22

23
	rows [][]float64
24
}
25

26
func (d *DataTable) AddColumn(name string) {
27
	d.columns = append(d.columns, name)
28
}
29

30
func (d *DataTable) AddRow(elms ...float64) {
31
	d.rows = append(d.rows, elms)
32
}
33

34
type Chart interface {
35
	Draw(data DataTable, flags int) string
36
}
37

38
type LineChart struct {
39
	Buf      []string
40
	chartBuf []string
41

42
	data *DataTable
43

44
	Width  int
45
	Height int
46

47
	chartHeight int
48
	chartWidth  int
49

50
	paddingX int
51

52
	paddingY int
53

54
	Flags int
55
}
56

57
func genBuf(size int) []string {
58
	buf := make([]string, size)
59

60
	for i := 0; i < size; i++ {
61
		buf[i] = " "
62
	}
63

64
	return buf
65
}
66

67
// Format float
68
func ff(num interface{}) string {
69
	return fmt.Sprintf("%.1f", num)
70
}
71

72
func NewLineChart(width, height int) *LineChart {
73
	chart := new(LineChart)
74
	chart.Width = width
75
	chart.Height = height
76
	chart.Buf = genBuf(width * height)
77

78
	// axis lines + axies text
79
	chart.paddingY = 2
80

81
	return chart
82
}
83

84
func (c *LineChart) DrawAxes(maxX, minX, maxY, minY float64, index int) {
85
	side := AXIS_LEFT
86

87
	if c.Flags&DRAW_INDEPENDENT != 0 {
88
		if index%2 == 0 {
89
			side = AXIS_RIGHT
90
		}
91

92
		c.DrawLine(c.paddingX-1, 1, c.Width-c.paddingX, 1, "-")
93
	} else {
94
		c.DrawLine(c.paddingX-1, 1, c.Width-1, 1, "-")
95
	}
96

97
	if side == AXIS_LEFT {
98
		c.DrawLine(c.paddingX-1, 1, c.paddingX-1, c.Height-1, "│")
99
	} else {
100
		c.DrawLine(c.Width-c.paddingX, 1, c.Width-c.paddingX, c.Height-1, "│")
101
	}
102

103
	left := 0
104
	if side == AXIS_RIGHT {
105
		left = c.Width - c.paddingX + 1
106
	}
107

108
	if c.Flags&DRAW_RELATIVE != 0 {
109
		c.writeText(ff(minY), left, 1)
110
	} else {
111
		if minY > 0 {
112
			c.writeText("0", left, 1)
113
		} else {
114
			c.writeText(ff(minY), left, 1)
115
		}
116
	}
117

118
	c.writeText(ff(maxY), left, c.Height-1)
119

120
	c.writeText(ff(minX), c.paddingX, 0)
121

122
	x_col := c.data.columns[0]
123
	c.writeText(c.data.columns[0], c.Width/2-utf8.RuneCountInString(x_col)/2, 1)
124

125
	if c.Flags&DRAW_INDEPENDENT != 0 || len(c.data.columns) < 3 {
126
		col := c.data.columns[index]
127

128
		for idx, char := range strings.Split(col, "") {
129
			start_from := c.Height/2 + len(col)/2 - idx
130

131
			if side == AXIS_LEFT {
132
				c.writeText(char, c.paddingX-1, start_from)
133
			} else {
134
				c.writeText(char, c.Width-c.paddingX, start_from)
135
			}
136
		}
137
	}
138

139
	if c.Flags&DRAW_INDEPENDENT != 0 {
140
		c.writeText(ff(maxX), c.Width-c.paddingX-len(ff(maxX)), 0)
141
	} else {
142
		c.writeText(ff(maxX), c.Width-len(ff(maxX)), 0)
143
	}
144
}
145

146
func (c *LineChart) writeText(text string, x, y int) {
147
	coord := y*c.Width + x
148

149
	for idx, char := range strings.Split(text, "") {
150
		c.Buf[coord+idx] = char
151
	}
152
}
153

154
func (c *LineChart) Draw(data *DataTable) (out string) {
155
	var scaleY, scaleX float64
156

157
	c.data = data
158

159
	if c.Flags&DRAW_INDEPENDENT != 0 && len(data.columns) > 3 {
160
		fmt.Println("Error: Can't use DRAW_INDEPENDENT for more then 2 graphs")
161
		return ""
162
	}
163

164
	charts := len(data.columns) - 1
165

166
	prevPoint := [2]int{-1, -1}
167

168
	maxX, minX, maxY, minY := getBoundaryValues(data, -1)
169

170
	c.paddingX = int(math.Max(float64(len(ff(minY))), float64(len(ff(maxY))))) + 1
171

172
	c.chartHeight = c.Height - c.paddingY
173

174
	if c.Flags&DRAW_INDEPENDENT != 0 {
175
		c.chartWidth = c.Width - 2*c.paddingX
176
	} else {
177
		c.chartWidth = c.Width - c.paddingX - 1
178
	}
179

180
	scaleX = float64(c.chartWidth) / (maxX - minX)
181

182
	if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
183
		scaleY = float64(c.chartHeight) / (maxY - minY)
184
	} else {
185
		scaleY = float64(c.chartHeight) / maxY
186
	}
187

188
	for i := 1; i < charts+1; i++ {
189
		if c.Flags&DRAW_INDEPENDENT != 0 {
190
			maxX, minX, maxY, minY = getBoundaryValues(data, i)
191

192
			scaleX = float64(c.chartWidth-1) / (maxX - minX)
193
			scaleY = float64(c.chartHeight) / maxY
194

195
			if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
196
				scaleY = float64(c.chartHeight) / (maxY - minY)
197
			}
198
		}
199

200
		symbol := Color("•", i)
201

202
		c_data := getChartData(data, i)
203

204
		for _, point := range c_data {
205
			x := int((point[0]-minX)*scaleX) + c.paddingX
206
			y := int((point[1])*scaleY) + c.paddingY
207

208
			if c.Flags&DRAW_RELATIVE != 0 || minY < 0 {
209
				y = int((point[1]-minY)*scaleY) + c.paddingY
210
			}
211

212
			if prevPoint[0] == -1 {
213
				prevPoint[0] = x
214
				prevPoint[1] = y
215
			}
216

217
			if prevPoint[0] <= x {
218
				c.DrawLine(prevPoint[0], prevPoint[1], x, y, symbol)
219
			}
220

221
			prevPoint[0] = x
222
			prevPoint[1] = y
223
		}
224

225
		c.DrawAxes(maxX, minX, maxY, minY, i)
226
	}
227

228
	for row := c.Height - 1; row >= 0; row-- {
229
		out += strings.Join(c.Buf[row*c.Width:(row+1)*c.Width], "") + "\n"
230
	}
231

232
	return
233
}
234

235
func (c *LineChart) DrawLine(x0, y0, x1, y1 int, symbol string) {
236
	drawLine(x0, y0, x1, y1, func(x, y int) {
237
		coord := y*c.Width + x
238

239
		if coord > 0 && coord < len(c.Buf) {
240
			c.Buf[coord] = symbol
241
		}
242
	})
243
}
244

245
func getBoundaryValues(data *DataTable, index int) (maxX, minX, maxY, minY float64) {
246
	maxX = math.Inf(-1)
247
	minX = math.Inf(1)
248
	maxY = math.Inf(-1)
249
	minY = math.Inf(1)
250

251
	for _, r := range data.rows {
252
		maxX = math.Max(maxX, r[0])
253
		minX = math.Min(minX, r[0])
254

255
		for idx, c := range r {
256
			if idx > 0 {
257
				if index == -1 || index == idx {
258
					maxY = math.Max(maxY, c)
259
					minY = math.Min(minY, c)
260
				}
261
			}
262
		}
263
	}
264

265
	if maxY > 0 {
266
		maxY = maxY * 1.1
267
	} else {
268
		maxY = maxY * 0.9
269
	}
270

271
	if minY > 0 {
272
		minY = minY * 0.9
273
	} else {
274
		minY = minY * 1.1
275
	}
276

277
	return
278
}
279

280
// DataTable can contain data for multiple graphs, we need to extract only 1
281
func getChartData(data *DataTable, index int) (out [][]float64) {
282
	for _, r := range data.rows {
283
		out = append(out, []float64{r[0], r[index]})
284
	}
285

286
	return
287
}
288

289
// Algorithm for drawing line between two points
290
//
291
// http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
292
func drawLine(x0, y0, x1, y1 int, plot func(int, int)) {
293
	dx := x1 - x0
294
	if dx < 0 {
295
		dx = -dx
296
	}
297
	dy := y1 - y0
298
	if dy < 0 {
299
		dy = -dy
300
	}
301
	var sx, sy int
302
	if x0 < x1 {
303
		sx = 1
304
	} else {
305
		sx = -1
306
	}
307
	if y0 < y1 {
308
		sy = 1
309
	} else {
310
		sy = -1
311
	}
312
	err := dx - dy
313

314
	for {
315
		plot(x0, y0)
316
		if x0 == x1 && y0 == y1 {
317
			break
318
		}
319
		e2 := 2 * err
320
		if e2 > -dy {
321
			err -= dy
322
			x0 += sx
323
		}
324
		if e2 < dx {
325
			err += dx
326
			y0 += sy
327
		}
328
	}
329
}
330

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.