diff --git a/main.go b/main.go index b91e38a9..626c5003 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ import ( "github.com/animenotifier/notify.moe/pages/profile" "github.com/animenotifier/notify.moe/pages/search" "github.com/animenotifier/notify.moe/pages/settings" + "github.com/animenotifier/notify.moe/pages/statistics" "github.com/animenotifier/notify.moe/pages/threads" "github.com/animenotifier/notify.moe/pages/tracks" "github.com/animenotifier/notify.moe/pages/user" @@ -91,6 +92,7 @@ func configure(app *aero.Application) *aero.Application { app.Ajax("/import/anilist/animelist", listimportanilist.Preview) app.Ajax("/import/anilist/animelist/finish", listimportanilist.Finish) app.Ajax("/admin", admin.Get) + app.Ajax("/statistics", statistics.Get) app.Ajax("/search", search.Get) app.Ajax("/search/:term", search.Get) app.Ajax("/users", users.Get) diff --git a/pages/statistics/statistics.go b/pages/statistics/statistics.go new file mode 100644 index 00000000..d9166cae --- /dev/null +++ b/pages/statistics/statistics.go @@ -0,0 +1,71 @@ +package statistics + +import ( + "net/http" + + "github.com/aerogo/aero" + "github.com/animenotifier/arn" + "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" +) + +// Get ... +func Get(ctx *aero.Context) string { + analytics, err := arn.AllAnalytics() + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Couldn't retrieve analytics", err) + } + + screenSizes := map[string]int{} + + for _, info := range analytics { + size := arn.ToString(info.Screen.Width) + " x " + arn.ToString(info.Screen.Height) + screenSizes[size]++ + } + + screenSizesSorted := []*utils.AnalyticsItem{} + + for size, count := range screenSizes { + item := &utils.AnalyticsItem{ + Key: size, + Value: count, + } + + if len(screenSizesSorted) == 0 { + screenSizesSorted = append(screenSizesSorted, item) + continue + } + + found := false + + for i := 0; i < len(screenSizesSorted); i++ { + if count >= screenSizesSorted[i].Value { + // Append empty element + screenSizesSorted = append(screenSizesSorted, nil) + + // Move all elements after index "i" 1 position up + copy(screenSizesSorted[i+1:], screenSizesSorted[i:]) + + // Set value for index "i" + screenSizesSorted[i] = item + + // Set flag + found = true + + // Leave loop + break + } + } + + if !found { + screenSizesSorted = append(screenSizesSorted, item) + } + } + + if len(screenSizesSorted) > 5 { + screenSizesSorted = screenSizesSorted[:5] + } + + return ctx.HTML(components.Statistics(screenSizesSorted)) +} diff --git a/pages/statistics/statistics.pixy b/pages/statistics/statistics.pixy new file mode 100644 index 00000000..269e5a05 --- /dev/null +++ b/pages/statistics/statistics.pixy @@ -0,0 +1,13 @@ +component Statistics(screenSizes []*utils.AnalyticsItem) + h1 Statistics + + .statistics + h3 Screen size + PieChart + //- canvas#screen-sizes.graph(data-values=utils.ToJSON(screenSizes)) + +component PieChart + svg.graph(viewBox="-1.05 -1.05 2.1 2.1") + path.slice.slice-1(d=utils.SVGSlicePath(0, 0.5)) + path.slice.slice-2(d=utils.SVGSlicePath(0.5, 0.8)) + path.slice.slice-3(d=utils.SVGSlicePath(0.8, 1.0)) \ No newline at end of file diff --git a/pages/statistics/statistics.scarlet b/pages/statistics/statistics.scarlet new file mode 100644 index 00000000..062fc5be --- /dev/null +++ b/pages/statistics/statistics.scarlet @@ -0,0 +1,24 @@ +.statistics + vertical + align-items center + +.graph + transform rotate(-90deg) + width 250px + height 250px + +.slice + color black + default-transition + transform scale(1) + :hover + transform scale(1.05) + +.slice-1 + opacity 0.8 + +.slice-2 + opacity 0.6 + +.slice-3 + opacity 0.4 \ No newline at end of file diff --git a/utils/AnalyticsItem.go b/utils/AnalyticsItem.go new file mode 100644 index 00000000..e7efafff --- /dev/null +++ b/utils/AnalyticsItem.go @@ -0,0 +1,7 @@ +package utils + +// AnalyticsItem ... +type AnalyticsItem struct { + Key string + Value int +} diff --git a/utils/SVGPieChartPath.go b/utils/SVGPieChartPath.go new file mode 100644 index 00000000..cc3e2b3a --- /dev/null +++ b/utils/SVGPieChartPath.go @@ -0,0 +1,27 @@ +package utils + +import ( + "fmt" + "math" +) + +// coords returns the coordinates for the given percentage. +func coords(percent float64) (float64, float64) { + x := math.Cos(2 * math.Pi * percent) + y := math.Sin(2 * math.Pi * percent) + return x, y +} + +// SVGSlicePath creates a path string for a slice in a pie chart. +func SVGSlicePath(from float64, to float64) string { + x1, y1 := coords(from) + x2, y2 := coords(to) + + largeArc := "0" + + if to-from > 0.5 { + largeArc = "1" + } + + return fmt.Sprintf("M %.2f %.2f A 1 1 0 %s 1 %.2f %.2f L 0 0", x1, y1, largeArc, x2, y2) +} diff --git a/utils/ToJSON.go b/utils/ToJSON.go new file mode 100644 index 00000000..614b4465 --- /dev/null +++ b/utils/ToJSON.go @@ -0,0 +1,9 @@ +package utils + +import "encoding/json" + +// ToJSON converts an object to a JSON string, ignoring errors. +func ToJSON(v interface{}) string { + str, _ := json.Marshal(v) + return string(str) +}