summaryrefslogtreecommitdiffstats
path: root/pkg/plot/barchart.go
blob: 6bcacd93246b777bed6c6175516baf43a31ef7f8 (plain)
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
package plot

import (
	"bytes"
	"math"

	"github.com/sgreben/jp/pkg/draw"
)

// BarChart is a bar chart
type BarChart struct {
	draw.Canvas
	BarPaddingX int
}

// NewBarChart returns a new bar chart
func NewBarChart(canvas draw.Canvas) *BarChart {
	chart := new(BarChart)
	chart.Canvas = canvas
	chart.BarPaddingX = 1
	return chart
}

// Draw implements Chart
func (c *BarChart) Draw(data *DataTable) string {
	minY := math.Inf(1)
	maxY := math.Inf(-1)
	for _, row := range data.Rows {
		for _, y := range row {
			if y < minY {
				minY = y
			}
			if y > maxY {
				maxY = y
			}
		}
	}
	paddingX := 4
	paddingY := 3
	chartHeight := c.Size().Height - paddingY*c.RuneSize().Height
	chartWidth := c.Size().Width - 2*paddingX*c.RuneSize().Width
	scaleY := float64(chartHeight) / maxY
	barPaddedWidth := chartWidth / len(data.Columns)
	barWidth := barPaddedWidth - (c.BarPaddingX * c.RuneSize().Width)
	if barPaddedWidth < c.RuneSize().Width {
		barPaddedWidth = c.RuneSize().Width
	}
	if barWidth < c.RuneSize().Width {
		barWidth = c.RuneSize().Width
	}

	scaleY = float64(chartHeight) / maxY

	for i, group := range data.Columns {
		barLeft := paddingX*c.RuneSize().Width + barPaddedWidth*i
		barRight := barLeft + barWidth

		y := data.Rows[0][i]
		barHeight := y * scaleY
		barBottom := (paddingY - 1) * c.RuneSize().Height
		barTop := barBottom + int(barHeight)

		for x := barLeft; x < barRight; x++ {
			for y := barBottom; y < barTop; y++ {
				c.Set(y, x)
			}
		}

		// Group label
		barMiddle := int(math.Floor(float64(barLeft+barRight) / float64(2*c.RuneSize().Width)))
		c.GetBuffer().WriteCenter(0, barMiddle, []rune(group))

		// Count label
		countLabelY := int(math.Ceil(float64(barTop)/float64(c.RuneSize().Height))) * c.RuneSize().Height

		if countLabelY <= barBottom && y > 0 {
			c.GetBuffer().SetRow(barTop/c.RuneSize().Height, barLeft/c.RuneSize().Width, barRight/c.RuneSize().Width, '▁')
			countLabelY = 3 * c.RuneSize().Height
		}

		c.GetBuffer().WriteCenter(countLabelY/c.RuneSize().Height, barMiddle, Ff(y))
	}

	b := bytes.NewBuffer(nil)
	c.GetBuffer().Render(b)
	return b.String()
}