add trend for avg mpg

This commit is contained in:
Kameron Kenny 2025-03-19 13:25:34 -04:00
parent 292c9770ef
commit db49cf17cd
2 changed files with 95 additions and 16 deletions

View File

@ -0,0 +1,63 @@
//
// MPGTrendChartView.swift
// Gas Man
//
// Created by Kameron Kenny on 3/19/25.
//
import SwiftUI
import Charts
import CoreData
struct MPGTrendChartView: View {
let fuelLogs: [FuelLog]
// Compute trend data by sorting logs by date ascending
// and calculating MPG for each interval where fullTank is true.
var trendData: [(date: Date, mpg: Double)] {
let sortedLogs = fuelLogs.sorted { ($0.date ?? Date()) < ($1.date ?? Date()) }
var data: [(Date, Double)] = []
// Start from index 1 because we need a previous log to compute the difference.
for i in 1..<sortedLogs.count {
let previous = sortedLogs[i - 1]
let current = sortedLogs[i]
if current.fullTank, let date = current.date,
current.odometer > previous.odometer,
current.fuelVolume > 0 {
let mpg = (current.odometer - previous.odometer) / current.fuelVolume
data.append((date, mpg))
}
}
return data
}
var body: some View {
VStack {
if trendData.isEmpty {
Text("No MPG trend data available.")
.foregroundColor(.secondary)
} else {
Chart {
ForEach(trendData, id: \.date) { point in
LineMark(
x: .value("Date", point.date),
y: .value("MPG", point.mpg)
)
PointMark(
x: .value("Date", point.date),
y: .value("MPG", point.mpg)
)
}
}
.chartXAxis {
AxisMarks(values: .automatic(desiredCount: 4))
}
.chartYAxis {
AxisMarks(values: .automatic(desiredCount: 5))
}
}
}
.padding()
.navigationTitle("MPG Trend")
}
}

View File

@ -5,7 +5,6 @@
// Created by Kameron Kenny on 3/19/25. // Created by Kameron Kenny on 3/19/25.
// //
import SwiftUI import SwiftUI
import CoreData import CoreData
@ -18,7 +17,7 @@ struct StatsView: View {
animation: .default) animation: .default)
private var fuelLogs: FetchedResults<FuelLog> private var fuelLogs: FetchedResults<FuelLog>
// Fetch all vehicles sorted by make (or any preferred order) // Fetch all vehicles sorted by make
@FetchRequest( @FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Vehicle.make, ascending: true)], sortDescriptors: [NSSortDescriptor(keyPath: \Vehicle.make, ascending: true)],
animation: .default) animation: .default)
@ -60,7 +59,7 @@ struct StatsView: View {
return sum / Double(mpgValues.count) return sum / Double(mpgValues.count)
} }
// Price per gallon stats: lowest, highest, average. // Price per gallon stats.
private var pricePerGallonStats: (min: Double?, max: Double?, avg: Double?) { private var pricePerGallonStats: (min: Double?, max: Double?, avg: Double?) {
let prices = filteredFuelLogs.map { $0.pricePerGalon } let prices = filteredFuelLogs.map { $0.pricePerGalon }
guard !prices.isEmpty else { return (nil, nil, nil) } guard !prices.isEmpty else { return (nil, nil, nil) }
@ -70,7 +69,7 @@ struct StatsView: View {
return (minPrice, maxPrice, avgPrice) return (minPrice, maxPrice, avgPrice)
} }
// Distance between fill-ups stats: lowest, highest, average. // Distance between fill-ups stats.
private var distanceStats: (min: Double?, max: Double?, avg: Double?) { private var distanceStats: (min: Double?, max: Double?, avg: Double?) {
let logs = filteredFuelLogs let logs = filteredFuelLogs
guard logs.count > 1 else { return (nil, nil, nil) } guard logs.count > 1 else { return (nil, nil, nil) }
@ -103,7 +102,7 @@ struct StatsView: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
Form { Form {
// Vehicle Selector // Vehicle Selector Section
Section(header: Text("Select Vehicle")) { Section(header: Text("Select Vehicle")) {
Picker("Vehicle", selection: $selectedVehicleID) { Picker("Vehicle", selection: $selectedVehicleID) {
ForEach(vehicles, id: \.id) { vehicle in ForEach(vehicles, id: \.id) { vehicle in
@ -114,20 +113,36 @@ struct StatsView: View {
.pickerStyle(MenuPickerStyle()) .pickerStyle(MenuPickerStyle())
} }
// Stats for Fuel Logs // Fill-Up Count Section
Section(header: Text("Fill-Up Count")) { Section {
Text("\(fillUpCount)") HStack {
} Text("Fill-Up Count:")
Spacer()
Section(header: Text("Average MPG")) { Text("\(fillUpCount)")
if let avgMPG = averageMPG { .bold()
Text("\(avgMPG, specifier: "%.1f") MPG")
} else {
Text("N/A")
.foregroundColor(.secondary)
} }
} }
// Average MPG Section with NavigationLink to chart.
Section {
NavigationLink(destination: MPGTrendChartView(fuelLogs: filteredFuelLogs)) {
HStack {
Text("Average MPG:")
Spacer()
if let avgMPG = averageMPG {
Text("\(avgMPG, specifier: "%.1f") MPG")
.bold()
} else {
Text("N/A")
.foregroundColor(.secondary)
}
Image(systemName: "chart.bar.fill")
.foregroundColor(.blue)
}
}
}
// Price per Gallon Stats
Section(header: Text("Price per Gallon")) { Section(header: Text("Price per Gallon")) {
let stats = pricePerGallonStats let stats = pricePerGallonStats
HStack { HStack {
@ -191,6 +206,7 @@ struct StatsView: View {
} }
} }
// Gallons per Fill-Up Stats
Section(header: Text("Gallons per Fill-Up")) { Section(header: Text("Gallons per Fill-Up")) {
let gStats = gallonsStats let gStats = gallonsStats
HStack { HStack {