add trend for avg mpg
This commit is contained in:
parent
292c9770ef
commit
db49cf17cd
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
HStack {
|
||||||
|
Text("Fill-Up Count:")
|
||||||
|
Spacer()
|
||||||
Text("\(fillUpCount)")
|
Text("\(fillUpCount)")
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Average MPG")) {
|
// Average MPG Section with NavigationLink to chart.
|
||||||
|
Section {
|
||||||
|
NavigationLink(destination: MPGTrendChartView(fuelLogs: filteredFuelLogs)) {
|
||||||
|
HStack {
|
||||||
|
Text("Average MPG:")
|
||||||
|
Spacer()
|
||||||
if let avgMPG = averageMPG {
|
if let avgMPG = averageMPG {
|
||||||
Text("\(avgMPG, specifier: "%.1f") MPG")
|
Text("\(avgMPG, specifier: "%.1f") MPG")
|
||||||
|
.bold()
|
||||||
} else {
|
} else {
|
||||||
Text("N/A")
|
Text("N/A")
|
||||||
.foregroundColor(.secondary)
|
.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 {
|
||||||
|
|
Loading…
Reference in New Issue