-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.go
More file actions
165 lines (131 loc) · 4.13 KB
/
Copy pathlayout.go
File metadata and controls
165 lines (131 loc) · 4.13 KB
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
package gochart
import (
"fmt"
"image/color"
"math"
"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 value Y 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 :/
// using a valMax of 0 will create an infinite loop somewhere.
return b.RelY(b.H) - normalizeToRange(value, 0, math.Max(max, 1), 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)
maxYLabelW, _ := widestLabelSize(canvas, l.yAxis.Scale().Labels())
yAxisWidth := maxYLabelW + defaultMargin
xAxisHeight := l.xAxis.Height(canvas)
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
}