// // ChartDataBuildable.swift // Reflect // // Created by Trey Tartt on 1/17/22. // import SwiftUI typealias Year = Int typealias Month = Int protocol ChartDataBuildable { associatedtype ChartType: ChartViewItemBuildable // [Year: [Month: [View]] func buildGridData(withData data: [MoodEntryModel]) -> [Year: [Month: [ChartType]]] } extension ChartDataBuildable { public func buildGridData(withData data: [MoodEntryModel]) -> [Year: [Month: [ChartType]]] { guard let earliestEntry = data.first, let lastEntry = data.last else { return [:] } let calendar = Calendar.current // Pre-group entries by year/month/day in a single pass - O(n) instead of O(n*m) // Key: "year-month-day" -> entry var entriesByDate = [String: MoodEntryModel]() for entry in data { let components = calendar.dateComponents([.year, .month, .day], from: entry.forDate) let key = "\(components.year!)-\(components.month!)-\(components.day!)" entriesByDate[key] = entry } let earliestYear = calendar.component(.year, from: earliestEntry.forDate) let latestYear = calendar.component(.year, from: lastEntry.forDate) // Cache expensive lookups let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable() let shape = UserDefaultsStore.getCustomBGShape() var returnData = [Int: [Int: [ChartType]]]() for year in earliestYear...latestYear { var allMonths = [Int: [ChartType]]() for month in 1...12 { var components = DateComponents() components.month = month components.year = year guard let startDateOfMonth = calendar.date(from: components) else { continue } allMonths[month] = createViewFor( year: year, month: month, forMonth: startDateOfMonth, entriesByDate: entriesByDate, moodTint: moodTint, shape: shape, calendar: calendar ) } returnData[year] = allMonths } return returnData } private func createViewFor( year: Int, month: Int, forMonth monthDate: Date, entriesByDate: [String: MoodEntryModel], moodTint: MoodTintable.Type, shape: BGShape, calendar: Calendar ) -> [ChartType] { var filledOutArray = [ChartType]() let range = calendar.range(of: .day, in: .month, for: monthDate)! let numDays = range.count for day in 1...numDays { let key = "\(year)-\(month)-\(day)" if let item = entriesByDate[key] { // O(1) dictionary lookup instead of O(n) filter let view = ChartType(color: moodTint.color(forMood: item.mood), weekDay: Int(item.weekDay), shape: shape) filledOutArray.append(view) } else { let thisDate = calendar.date(bySetting: .day, value: day, of: monthDate)! let view = ChartType(color: Mood.placeholder.color, weekDay: calendar.component(.weekday, from: thisDate), shape: shape) filledOutArray.append(view) } } for _ in filledOutArray.count...32 { let view = ChartType(color: Mood.placeholder.color, weekDay: 2, shape: shape) filledOutArray.append(view) } return filledOutArray } } struct DayChartViewChartBuilder: ChartDataBuildable { typealias ChartType = DayChartView }