Search code examples
swiftuichartsswiftui-charts

SwiftUI Charts Annotation issue


I'm exploring swiftui charts with dates on axis X and floats on axis Y. I'm following some videos and guides but none of them face my problem. The problem is when tapping on bar charts edge cases, when annotation doesn't fit in frame and it starts to move bars which is not correct. Check the gif to see it. Your help is welcome :)

annotation

    struct BarChartView: View {
        struct BarData: Identifiable, Equatable {
            var type: String
            var date: Date
            var count: Float
            var id: Int
        }
        
    var data: [BarData] = [BarData(type: "test1", date: Date(), count: 10.0, id: 1)
//and more]

    @State var activeItem: BarData?
    var body: some View {
        NavigationStack{
            VStack(alignment : .leading, spacing: 15) {
                Text("testing errors")
                VStack{
                    Chart(data) { item in
                        BarMark(
                            x: .value("Date",  item.date, unit: .day),
                            y: .value("Sum", item.count )
                        )
                        if let activeItem,activeItem.id == item.id{
                            RuleMark(x: .value("Day", activeItem.date))
                                .lineStyle(.init(lineWidth: 2, miterLimit: 2, dash: [2], dashPhase: 5))
                            
                                .annotation(position: .top){
                                    VStack(alignment: .leading, spacing: 6){
                                        HStack{
                                            Text("Type")
                                                .font(.caption)
                                                .foregroundColor(.gray)
                                            
                                            Text(activeItem.type)
                                                .font(.title3.bold())
                                        }
                                        HStack{
                                            Text("Sum")
                                                .font(.caption)
                                                .foregroundColor(.gray)
                                            Text("\(activeItem.count, specifier: "%.2f")")
                                                .font(.title3.bold())
                                        }
                                    }
                                    .padding(.horizontal, 10)
                                    .padding(.vertical, 4)
                                    .background(RoundedRectangle(cornerRadius: 6, style: .continuous)
                                        .fill(.black.shadow(.drop(radius: 2))))
                                }
                        }
                    }
                    .chartOverlay { 
// getting activeItem
                    }
                    .chartYScale(domain: 0...50)
                    .frame(height: 250)
                }
                .padding()
                .background{
                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                        .stroke(.gray, lineWidth: 1)
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
            .padding(.top, 10)
            .navigationTitle("Test charts")
        }
    }
}

Solution

  • You can put the annotation on different sides of the vertical line, depending where the active item is, in the array of data.

    A simple implementation is to put it on the right of the line when the active item is on the left side of the chart, and on the left of the line when the active item is on the right side of the chart.

    var annotationPosition: AnnotationPosition {
        guard let activeItem else { return .automatic }
        if let index = data.firstIndex(of: activeItem), index < data.count / 2 {
            return .topTrailing
        } else {
            return .topLeading
        }
    }
    
    .annotation(position: annotationPosition) { ... }
    

    You can also add make the annotations for the data in the middle (wherever "middle" is) have a .top position.


    Alternatively, add some padding to the bars, so that there is enough space for the annotations, and the bars don't need to be shifted to fit the annotation.

    .chartXScale(range: .plotDimension(padding: 30))