Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // ActivityHeatMap.swift
- //
- // Created by Marco Quinten (https://github.com/SplittyDev)
- // Created for FiveSheep OÜ (https://github.com/FiveSheepCo)
- //
- // Permission is hereby granted to use this for whatever project you like, whether free or paid.
- //
- import SwiftUI
- import Charts
- /// An activity graph similar to GitHub's contribution graph.
- struct ActivityHeatMap: View {
- let entries: [Entry] // TODO: Change this to your own type
- struct HeatmapEntry {
- let date: Date
- let active: Bool
- let isToday: Bool
- let isInFuture: Bool
- let gridRow: Int
- let gridCol: Int
- var color: Color {
- if active {
- Color.green
- } else if isToday {
- Color.blue
- } else if isInFuture {
- Color.gray.opacity(0.25)
- } else {
- Color.gray
- }
- }
- }
- /// Heatmap entries for the current month.
- var currentMonthEntries: [HeatmapEntry] {
- let now = Date.now
- let calendar = Calendar.current
- var results = [HeatmapEntry]()
- // Determine the first and last day of the current month
- let firstOfMonth = calendar.date(from: calendar.dateComponents(
- [.calendar, .timeZone, .era, .year, .quarter, .month],
- from: now
- ))!
- let lastOfMonth = calendar.date(
- byAdding: DateComponents(month: 1, day: -1),
- to: firstOfMonth
- )!
- // Loop over the selected date range
- var currentDate = firstOfMonth
- while currentDate <= lastOfMonth {
- // TODO: Update this logic to use your own `Entry` type
- let entry = entries.firstIndex { $0.timestamp.dateOnly == currentDate.dateOnly }
- // Determine properties of the given day
- let firstDayOfMonthWeekday = calendar.component(.weekday, from: firstOfMonth) - calendar.firstWeekday
- let adjustedFirstDayOfMonthWeekday = (firstDayOfMonthWeekday >= 0)
- ? firstDayOfMonthWeekday
- : (firstDayOfMonthWeekday + 7)
- let daysSinceFirstOfMonth = calendar.dateComponents(
- [.day], from: firstOfMonth, to: currentDate
- ).day!
- let weekNumber = (daysSinceFirstOfMonth + adjustedFirstDayOfMonthWeekday) / 7
- let dayOfWeek = calendar.component(.weekday, from: currentDate) - calendar.firstWeekday
- let adjustedDayOfWeek = (dayOfWeek >= 0) ? dayOfWeek : (dayOfWeek + 7)
- // Create heatmap entry
- let heatmapEntry = HeatmapEntry(
- date: currentDate,
- active: entry != nil,
- isToday: currentDate.dateOnly == Date.now.dateOnly,
- isInFuture: currentDate > Date.now,
- gridRow: weekNumber,
- gridCol: adjustedDayOfWeek
- )
- results.append(heatmapEntry)
- // Advance date by one day
- currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
- }
- return results
- }
- var body: some View {
- GeometryReader { geometry in
- let calendar = Calendar.current
- // Calculate the height of a single point on the graph
- let maxWeekdays = Double(currentMonthEntries.map { calendar.component(.weekday, from: $0.date) }.max() ?? 1)
- let dotHeight = geometry.size.height / maxWeekdays
- HStack {
- Spacer()
- Chart {
- ForEach(currentMonthEntries, id: \.date) { entry in
- RectangleMark(
- x: .value(String.empty, entry.gridRow),
- y: .value(String.empty, entry.gridCol),
- width: .fixed(dotHeight),
- height: .fixed(dotHeight)
- )
- .foregroundStyle(entry.color)
- .cornerRadius(2)
- }
- }
- .frame(width: dotHeight * 7, height: dotHeight * 7, alignment: .center)
- .chartXAxis(.hidden)
- .chartYAxis(.hidden)
- Spacer()
- }
- }
- }
- }
- #Preview {
- let entries = [] // TODO: Somehow create your entries here
- return ActivityHeatMap(entries: entries)
- .padding(16)
- .frame(width: 250, height: 150)
- .background(Color.secondarySystemBackground)
- .clipShape(RoundedRectangle(cornerRadius: 8))
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement