forked from Elara6331/itd
		
	
		
			
				
	
	
		
			151 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
}
 |