From 422f844943d0dba8b500952956c32e86474940e2 Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Tue, 10 May 2022 23:37:58 -0700 Subject: [PATCH] Add metrics graphs to itgui --- cmd/itgui/graph.go | 150 +++++++++++++++++++++++++++++++++++++++++++++ cmd/itgui/main.go | 5 ++ go.mod | 3 + go.sum | 10 +++ 4 files changed, 168 insertions(+) create mode 100644 cmd/itgui/graph.go diff --git a/cmd/itgui/graph.go b/cmd/itgui/graph.go new file mode 100644 index 0000000..9f2cf85 --- /dev/null +++ b/cmd/itgui/graph.go @@ -0,0 +1,150 @@ +package main + +import ( + "context" + "database/sql" + "image/color" + "os" + "path/filepath" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/x/fyne/widget/charts" + "go.arsenm.dev/itd/api" + _ "modernc.org/sqlite" +) + +func graphTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { + // Get user configuration directory + userCfgDir, err := os.UserConfigDir() + if err != nil { + return nil + } + cfgDir := filepath.Join(userCfgDir, "itd") + dbPath := filepath.Join(cfgDir, "metrics.db") + + // If stat on database returns error, return nil + if _, err := os.Stat(dbPath); err != nil { + return nil + } + + // Open database + db, err := sql.Open("sqlite", dbPath) + if err != nil { + return nil + } + + // Get heart rate data and create chart + heartRateData := getData(db, "bpm", "heartRate") + heartRate := newLineChartData(nil, heartRateData) + + // Get step count data and create chart + stepCountData := getData(db, "steps", "stepCount") + stepCount := newLineChartData(nil, stepCountData) + + // Get battery level data and create chart + battLevelData := getData(db, "percent", "battLevel") + battLevel := newLineChartData(nil, battLevelData) + + // Get motion data + motionData := getMotionData(db) + // Create chart for each coordinate + xChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorRed), motionData["X"]) + yChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorGreen), motionData["Y"]) + zChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorBlue), motionData["Z"]) + + // Create new max container with all the charts + motion := container.NewMax(xChart, yChart, zChart) + + // Create tabs for charts + chartTabs := container.NewAppTabs( + container.NewTabItem("Heart Rate", heartRate), + container.NewTabItem("Step Count", stepCount), + container.NewTabItem("Battery Level", battLevel), + container.NewTabItem("Motion", motion), + ) + // Place tabs on left + chartTabs.SetTabLocation(container.TabLocationLeading) + return chartTabs +} + +func newLineChartData(col color.Color, data []float64) *charts.LineChart { + // Create new line chart + lc := charts.NewLineChart(nil) + setOpts(lc, col) + // If no data, make the stroke transparent + if len(data) == 0 { + lc.Options().StrokeColor = color.RGBA{0, 0, 0, 0} + } + // Set data + lc.SetData(data) + return lc +} + +func setOpts(lc *charts.LineChart, col color.Color) { + // Get pointer to options + opts := lc.Options() + // Set fill color to transparent + opts.FillColor = color.RGBA{0, 0, 0, 0} + // Set stroke width + opts.StrokeWidth = 2 + // If color provided + if col != nil { + // Set stroke color + opts.StrokeColor = col + } else { + // Set stroke color to orange primary color + opts.StrokeColor = theme.PrimaryColorNamed(theme.ColorOrange) + } +} + +func getData(db *sql.DB, field, table string) []float64 { + // Get data from database + rows, err := db.Query("SELECT " + field + " FROM " + table + " ORDER BY time;") + if err != nil { + return nil + } + defer rows.Close() + + var out []float64 + for rows.Next() { + var val int64 + // Scan data into int + err := rows.Scan(&val) + if err != nil { + return nil + } + + // Convert to float64 and append to data slice + out = append(out, float64(val)) + } + + return out +} + +func getMotionData(db *sql.DB) map[string][]float64 { + // Get data from database + rows, err := db.Query("SELECT X, Y, Z FROM motion ORDER BY time;") + if err != nil { + return nil + } + defer rows.Close() + + out := map[string][]float64{} + for rows.Next() { + var x, y, z int64 + // Scan data into ints + err := rows.Scan(&x, &y, &z) + if err != nil { + return nil + } + + // Convert to float64 and append to appropriate slice + out["X"] = append(out["X"], float64(x)) + out["Y"] = append(out["Y"], float64(y)) + out["Z"] = append(out["Z"], float64(z)) + } + + return out +} diff --git a/cmd/itgui/main.go b/cmd/itgui/main.go index 265e76a..dfbc515 100644 --- a/cmd/itgui/main.go +++ b/cmd/itgui/main.go @@ -36,6 +36,11 @@ func main() { container.NewTabItem("Firmware", firmwareTab(ctx, client, w)), ) + metricsTab := graphTab(ctx, client, w) + if metricsTab != nil { + tabs.Append(container.NewTabItem("Metrics", metricsTab)) + } + // When a tab is selected tabs.OnSelected = func(ti *container.TabItem) { // If the tab's name is FS diff --git a/go.mod b/go.mod index f3a0ad6..11a8d14 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,11 @@ module go.arsenm.dev/itd go 1.17 +replace fyne.io/x/fyne => github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb + require ( fyne.io/fyne/v2 v2.1.4 + fyne.io/x/fyne v0.0.0-20220107050838-c4a1de51d4ce github.com/cheggaaa/pb/v3 v3.0.8 github.com/gen2brain/dlgs v0.0.0-20211108104213-bade24837f0b github.com/godbus/dbus/v5 v5.0.6 diff --git a/go.sum b/go.sum index b70ed48..5b56255 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,10 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +fyne.io/fyne/v2 v2.1.0/go.mod h1:c1vwI38Ebd0dAdxVa6H1Pj6/+cK1xtDy61+I31g+s14= fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc= fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ= +github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -82,6 +84,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -111,10 +114,12 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211204153444-caad923f49f4 h1:KgfIc81yNEUKNAsF+Mt3C1Cl+iQqKF1r7nWEKzL0c2Y= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211204153444-caad923f49f4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -196,6 +201,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -268,6 +274,8 @@ github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxm github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb h1:+fP6ENsbd+BUOmD/kSjNtrOmi2vgJ/JfWDSWjTKmTVY= +github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb/go.mod h1:jBspDudEQ+Rdono8vBGHDtMUPE8ZpB/xq7FUYRqT3CI= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -376,6 +384,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -473,6 +482,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=