ss13_se/charts.go
2017-12-05 23:31:22 +01:00

217 lines
4.7 KiB
Go

package ss13_se
import (
"bytes"
"fmt"
"io"
"net/http"
"sort"
"time"
chart "github.com/wcharczuk/go-chart"
)
// TODO BUG:
// - gonna have to fix the funky Y range ticks:
// High pop servers will have Y ranges between min/max values while
// low pop servers will show multiple ticks with same values (like 3,3,3,2,2,1,1,1,0)
// Needs to be standardized (from 0 to max, with sane increments in between).
//
// - Top of bars in barcharts doesn't align well to the Y range ticks
//
// - I'm also sure I fixed the bug with one day missing randomly from the charts..
var weekDaysOrder = []time.Weekday{
time.Monday,
time.Tuesday,
time.Wednesday,
time.Thursday,
time.Friday,
time.Saturday,
time.Sunday,
}
type renderableChart interface {
Render(chart.RendererProvider, io.Writer) error
}
func (a *App) renderChart(w http.ResponseWriter, c renderableChart) error {
buf := &bytes.Buffer{}
err := c.Render(chart.PNG, buf)
if err != nil {
//a.Log("Error while rendering chart: %s", err)
return HttpError{
Status: http.StatusInternalServerError,
Err: fmt.Errorf("error while rendering chart"),
}
}
w.Header().Add("Content-Type", "image/png")
_, err = io.Copy(w, buf)
if err != nil {
a.Log("Error while sending chart: %s", err)
return HttpError{
Status: http.StatusInternalServerError,
Err: fmt.Errorf("error while sending chart"),
}
}
return nil
}
func makeHistoryChart(points []ServerPoint, showLegend bool) chart.Chart {
var xVals []time.Time
var yVals []float64
for _, p := range points {
xVals = append(xVals, p.Time)
yVals = append(yVals, float64(p.Players))
}
series := chart.TimeSeries{
Name: "Players",
XValues: xVals,
YValues: yVals,
}
lr := &chart.LinearRegressionSeries{
Name: "Linear regression",
InnerSeries: series,
}
sma := &chart.SMASeries{
Name: "Simple moving avg.",
InnerSeries: series,
}
c := chart.Chart{
Background: chart.Style{
Padding: chart.Box{
Top: 40,
},
},
XAxis: chart.XAxis{
Style: chart.StyleShow(),
ValueFormatter: func(v interface{}) string {
t := int64(v.(float64))
return time.Unix(0, t).Format("Jan 02 15:04")
},
},
YAxis: chart.YAxis{
Style: chart.StyleShow(),
ValueFormatter: func(v interface{}) string {
return fmt.Sprintf("%.0f", v)
},
},
Series: []chart.Series{
series,
lr,
sma,
},
}
if showLegend {
c.Elements = []chart.Renderable{
chart.LegendThin(&c),
}
}
return c
}
// NOTE: The chart won't be renderable unless we've got at least two days/hours of history
func makeAverageChart(values map[int][]int, fnFormat func(int, float64) string, fnSort func([]int) []int) chart.BarChart {
var keys []int
avg := make(map[int]float64)
for i, vl := range values {
sum := 0
for _, v := range vl {
sum += v
}
avg[i] = float64(sum / len(vl))
keys = append(keys, i)
}
var bars []chart.Value
for _, k := range fnSort(keys) {
bars = append(bars, chart.Value{
Label: fnFormat(k, avg[k]),
Value: avg[k],
Style: chart.Style{
StrokeColor: chart.ColorBlue,
FillColor: chart.ColorBlue,
},
})
}
barW, barS := 50, 100
if len(avg) > 7 {
barW, barS = 20, 20
}
s := chart.Style{
Show: true,
StrokeWidth: 1,
}
return chart.BarChart{
BarWidth: barW,
BarSpacing: barS,
XAxis: s,
YAxis: chart.YAxis{
Style: s,
ValueFormatter: func(v interface{}) string {
return fmt.Sprintf("%.0f", v)
},
},
Bars: bars,
}
}
// Shortcut/helper func for the calling handler
func avgDailyChart(points []ServerPoint) chart.BarChart {
days := make(map[int][]int)
for _, p := range points {
d := int(p.Time.Weekday())
days[d] = append(days[d], p.Players)
}
now := time.Now()
formatter := func(i int, f float64) string {
d := time.Weekday(i)
extra := ""
if d == now.Weekday() {
extra = "*"
}
return fmt.Sprintf("%s%s", d, extra)
}
sorter := func(keys []int) []int {
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
// Fucking wankers and their fucking sundays
if keys[0] == int(time.Sunday) {
keys = append(keys[1:], int(time.Sunday))
}
return keys
}
return makeAverageChart(days, formatter, sorter)
}
// Shortcut/helper func for the calling handler
func avgHourlyChart(points []ServerPoint) chart.BarChart {
hours := make(map[int][]int)
for _, p := range points {
h := p.Time.Hour()
hours[h] = append(hours[h], p.Players)
}
now := time.Now()
formatter := func(i int, f float64) string {
extra := ""
if i == now.Hour() {
extra = "*"
}
return fmt.Sprintf("%02d%s", i, extra)
}
sorter := func(keys []int) []int {
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
return keys
}
return makeAverageChart(hours, formatter, sorter)
}