如何通过点击swiftUI图表中的列来显示注释

问题描述 投票:0回答:1

当我尝试使用

onTapGesture
显示注释时,它出现在图表的中间。

当我单击该列时,它应该在该列的顶部显示我的注释,如下面的屏幕截图所示。我已经尝试了一切,但它总是显示在图表的中间。我该怎么做?

import SwiftUI
import Charts

struct FocusChartView: View {
    var colorStyleTask: Array<Color> = [Color(.black).opacity(0.7), Color(.white)]
    
    let focusStats: [FocusTime] = [
        .init(date: Date.from(year: 2024, month: 1, day: 1), focusCount: 1),
        .init(date: Date.from(year: 2024, month: 2, day: 1), focusCount: 3),
        .init(date: Date.from(year: 2024, month: 3, day: 1), focusCount: 7),
        .init(date: Date.from(year: 2024, month: 4, day: 1), focusCount: 12),
        .init(date: Date.from(year: 2024, month: 5, day: 1), focusCount: 5),
        .init(date: Date.from(year: 2024, month: 6, day: 1), focusCount: 12),
        .init(date: Date.from(year: 2024, month: 7, day: 1), focusCount: 1),]
   
    var body: some View {
        VStack {
            taskCharts
        }
    }
    
    private var taskCharts: some View {
        ZStack {
            Color(.gray)
                .ignoresSafeArea()
            Chart {
                let maxViews = focusStats.map { $0.focusCount }.max() ?? 1
                
                ForEach(focusStats) { viewMonth in
                    let viewCount = Double(viewMonth.focusCount)
                    let scaledHeight = (viewCount / Double(maxViews)) * 90
                    let relativeHeight = max(42.0, scaledHeight)
                    
                    BarMark(
                        x: .value("Month", viewMonth.date, unit: .month),
                        y: .value("Views", relativeHeight)
                    )
                    .foregroundStyle(
                        SwiftUI.LinearGradient(
                            gradient: SwiftUI.Gradient(colors: colorStyleTask),
                            startPoint: .bottom,
                            endPoint: .top
                        )
                    )
                    .clipShape(RoundedRectangle(cornerRadius: 5))
                    .annotation(position: .top) {
                        VStack {
                            Text(viewMonth.date.formattedToMonthAndYear())
                            Text(viewMonth.focusCount.toHoursAndMinutes())
                                .fontWeight(.bold)
                        }
                        .padding(.top, 8)
                        .padding(.horizontal, 12)
                        .background(Color(.white))
                        .cornerRadius(5)
                        .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 3)
                        .offset(y: -3)
                    }
                }
            }
            .frame(height: 151)
            .chartXAxis {
                AxisMarks(values: .stride(by: .month)) { _ in
                    AxisValueLabel(format: .dateTime.month(.abbreviated), centered: true)
                }
            }
            .chartYAxis {
            }
        }
    }
}

#Preview {
    FocusChartView()
}


struct FocusTime: Identifiable {
    let id = UUID()
    let date: Date
    let focusCount: Int
}

extension Int {
    func toHoursAndMinutes() -> String {
        let hours = self / 60
        let minutes = self % 60
        return "\(hours)h \(minutes)m"
    }
}

extension Date {
    static func from(year: Int, month: Int, day: Int) -> Date {
        let components = DateComponents(year: year, month: month, day: day)
        return Calendar.current.date(from: components)!
    }
    
    func formattedToMonthAndYear() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM, yyyy"
        return formatter.string(from: self)
    }
}

enter image description here

swiftui charts annotations popup ontap
1个回答
0
投票

要在点击/单击图表栏时在图表栏顶部显示注释, 使用

var tapedDate
.chartGesture
尝试此方法 如示例代码所示。

struct ContentView: View {
   var body: some View {
       FocusChartView()
   }
}

struct FocusChartView: View {
    var colorStyleTask = [Color(.black).opacity(0.7), Color(.white)]
    
    let focusStats: [FocusTime] = [
        .init(date: Date.from(year: 2024, month: 1, day: 1), focusCount: 1),
        .init(date: Date.from(year: 2024, month: 2, day: 1), focusCount: 3),
        .init(date: Date.from(year: 2024, month: 3, day: 1), focusCount: 7),
        .init(date: Date.from(year: 2024, month: 4, day: 1), focusCount: 12),
        .init(date: Date.from(year: 2024, month: 5, day: 1), focusCount: 5),
        .init(date: Date.from(year: 2024, month: 6, day: 1), focusCount: 12),
        .init(date: Date.from(year: 2024, month: 7, day: 1), focusCount: 1)]
    
    @State private var tapedDate: Date?  // <-- here
    
    var body: some View {
        VStack {
            taskCharts
        }
    }
    
    @ViewBuilder
    private var taskCharts: some View {
        ZStack {
            Color(.gray)
                .ignoresSafeArea()
            
            Chart {
                let maxViews = focusStats.map { $0.focusCount }.max() ?? 1
                
                ForEach(focusStats) { viewMonth in
                    
                    let viewCount = Double(viewMonth.focusCount)
                    let scaledHeight = (viewCount / Double(maxViews)) * 90
                    let relativeHeight = max(42.0, scaledHeight)
                    
                    BarMark(
                        x: .value("Month", viewMonth.date, unit: .month),
                        y: .value("Views", relativeHeight)
                    )
                    .foregroundStyle(
                        SwiftUI.LinearGradient(
                            gradient: SwiftUI.Gradient(colors: colorStyleTask),
                            startPoint: .bottom,
                            endPoint: .top
                        )
                    )
                    .clipShape(RoundedRectangle(cornerRadius: 5))
                    .annotation(position: .top) {
                        if sameMonth(date: tapedDate, tgt: viewMonth.date) {  // <--- here
                            VStack {
                                Text(viewMonth.date.formattedToMonthAndYear())
                                Text(viewMonth.focusCount.toHoursAndMinutes())
                                    .fontWeight(.bold)
                            }
                            .padding(.top, 8)
                            .padding(.horizontal, 12)
                            .background(Color(.white))
                            .cornerRadius(5)
                            .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 3)
                            .offset(y: -3)
                        } else {
                            Text("").frame(height: 55) // <--- here, or adjust the Yscale
                        }
                    }
                }
            }
            .frame(height: 151)
            .chartXAxis {
                AxisMarks(values: .stride(by: .month)) { _ in
                    AxisValueLabel(format: .dateTime.month(.abbreviated), centered: true)
                }
            }
            
            .chartGesture { proxy in  // <-- here
                SpatialTapGesture()
                    .onEnded { item in
                        if let target: (date: Date, temp: Double) = proxy.value(at: item.location, as: (Date, Double).self),
                            let chartData = closestChartData(to: target) {
                            tapedDate = chartData.date
                        }
                    }
            }
            
        }
    }
    
    func closestChartData(to target: (date: Date, temp: Double)) -> FocusTime? {
        var closestChartData: FocusTime?
        for datum in focusStats {
            if sameMonth(date: datum.date, tgt: target.date) {
                closestChartData = datum
                break
            }
        }
        return closestChartData
    }
    
    func sameMonth(date: Date?, tgt: Date) -> Bool {
        if let theDate = date {
            let month = Calendar.current.dateComponents([.month], from: theDate, to: tgt)
            return month.month == 0 ? true : false
        }
        return false
    }
   
}
© www.soinside.com 2019 - 2024. All rights reserved.