-
Notifications
You must be signed in to change notification settings - Fork 0
/
layout.go
164 lines (131 loc) · 4.15 KB
/
layout.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
package gochart
import (
"fmt"
"image/color"
"github.com/fogleman/gg"
)
type BoundingBox struct {
X float64
Y float64
W float64
H float64
}
// MapX takes the given min/max and maps them to the box then returns the value X position within that scale.
// E.g. val:2 min:1 max: 3 of a 100x100 box will return 50
func (b BoundingBox) MapX(min, max, value float64) float64 {
return normalizeToRange(value, min, max, b.RelX(0), b.W+b.X) //todo: is +X needed?
}
// MapY takes the given min/max and maps them to the box then returns the valu Ye position within that scale.
// E.g. val:2 min:1 max: 3 of a 100x100 box will return 50
func (b BoundingBox) MapY(min, max, value float64) float64 {
//todo: min value must be zero'd otherwise it pushes the scale off the chart.
// this is a bug probably in normalizeToRange :/
return b.RelY(b.H) - normalizeToRange(value, 0, max, 0, b.H)
}
// RelX is the relative position within the canvas i.e. 0 is the far left of the box, not the far left
// of the complete canvas.
func (b BoundingBox) RelX(pos float64) float64 {
return b.X + pos
}
// RelY is the relative position within the canvas i.e. 0 is the bottom of the box, not the bottom
// of the complete canvas.
func (b BoundingBox) RelY(pos float64) float64 {
return b.Y + pos
}
func (b BoundingBox) DebugRender(canvas *gg.Context) {
canvas.Push()
defer canvas.Pop()
canvas.SetColor(color.RGBA{R: 0, G: 0, B: 0, A: 128})
canvas.DrawRectangle(b.RelX(0), b.RelY(0), b.W, b.H)
canvas.DrawString(fmt.Sprintf("x: %0.0f y: %0.0f w: %0.0f h: %0.0f", b.X, b.Y, b.W, b.H), b.X, b.Y+10)
canvas.Stroke()
}
func NewDynamicLayout(yAxis *YAxis, xAxis *XAxis, charts ...Plot) *DynamicLayout {
return &DynamicLayout{charts: charts, yAxis: yAxis, xAxis: xAxis}
}
// DynamicLayout will calculate size of axis based on the given data.
type DynamicLayout struct {
charts []Plot
yAxis *YAxis
xAxis *XAxis
}
func (l *DynamicLayout) Render(canvas *gg.Context, container BoundingBox) error {
//container.DebugRender(canvas)
//todo multiple X axis
_, maxXLabelH := widestLabelSize(canvas, l.xAxis.Scale().Labels())
maxYLabelW, _ := widestLabelSize(canvas, l.yAxis.Scale().Labels())
yAxisWidth := maxYLabelW + defaultMargin
xAxisHeight := maxXLabelH + defaultMargin
chartPosition := BoundingBox{
X: container.RelX(0) + yAxisWidth,
Y: container.RelY(0),
W: container.W - yAxisWidth,
H: container.H - xAxisHeight,
}
//chartPosition.DebugRender(canvas)
for _, ch := range l.charts {
if err := ch.Render(canvas, chartPosition); err != nil {
return err
}
}
leftAxisPosition := BoundingBox{
X: container.RelX(0),
Y: container.RelY(0),
W: yAxisWidth,
H: container.H - xAxisHeight,
}
if err := l.yAxis.Render(canvas, leftAxisPosition); err != nil {
return err
}
//leftAxisPosition.DebugRender(canvas)
bottomAxisPosition := BoundingBox{
X: container.RelX(0) + yAxisWidth,
Y: container.RelY(container.H) - xAxisHeight,
W: container.W - yAxisWidth,
H: xAxisHeight,
}
if err := l.xAxis.Render(canvas, bottomAxisPosition); err != nil {
return err
}
//bottomAxisPosition.DebugRender(canvas)
return nil
}
func New12ColGridLayout(rows ...GridRow) *GridLayout {
return &GridLayout{rows: rows, numColumns: 12}
}
type GridRow struct {
HeightPercent float64 // between 0:1 where 1 is 100% and 0.1 is 10%
Columns []GridColumn
}
type GridColumn struct {
ColSpan int64 // between 1:numColumns
El Renderable
}
type GridLayout struct {
numColumns int64
rows []GridRow
}
func (l *GridLayout) Render(canvas *gg.Context, container BoundingBox) error {
var heightOffset float64
for _, row := range l.rows {
rowHeight := container.H * row.HeightPercent
var widthOffset float64
var numColumnsRendered int64
for _, col := range row.Columns {
colWidth := (container.W / float64(l.numColumns)) * float64(minInt64(col.ColSpan, l.numColumns-numColumnsRendered))
bb := BoundingBox{
X: container.RelX(widthOffset),
Y: container.RelY(heightOffset),
W: colWidth,
H: rowHeight,
}
widthOffset += colWidth
numColumnsRendered += col.ColSpan
if col.El != nil {
col.El.Render(canvas, bb)
}
}
heightOffset += rowHeight
}
return nil
}