Search code examples
iosswiftswiftuiswiftui-charts

How to skip Zero values when plotting a SwiftUI Chart


I am plotting a SwiftUI chart from a struct that looks like this:

struct BestWeights: Identifiable
{
    let id = UUID()
    let date: String
    let maxWeight: Int
}

I am then creating an array of this struct that I will use to plot the chart:

 private var bestWeights: [BestWeights] {           
     let unformattedMonth = DateFormatter().monthSymbols[month - 1]
     let formattedMonth =  String(unformattedMonth.prefix(3))
     bestWeights.append(BestWeights(date: formattedMonth, maxWeight: bestWeightOfPeriod))
    
     //decrementing down one month
     selectedDate = Calendar.current.date(byAdding: .month, value: -1, to: selectedDate) ?? selectedDate
}

Then I am iterating through bestWeights and plotting them:

Chart {
    ForEach(bestWeights) { bestWeight in
                        
        LineMark(x: .value("Date",bestWeight.date), y: .value("MaxWeight", bestWeight.maxWeight))
            .symbol {
                Circle()
                    .fill(.blue)
                    .frame(width: 7, height: 7)
            }                        
    }
}

The Result:

Including 0s

This is not what I want. I don't want the 0's to be drawn on the Y axis. So I then added a check to not append any bestWeight where the weight is 0:

if(bestWeightOfPeriod != 0) {
    bestWeights.append(BestWeights(date: formattedMonth, maxWeight: bestWeightOfPeriod))
}

This is exactly what I want, but it creates another problem in my x-axis, where it's skipping months. It goes from Feb straight to June. I still want to mark all the months. Since the date is stored in my struct and it's skipping the months that have a 0 weight, it's not labeling the chart correctly.

How can I label those missing months?

Skipping 0s

I want the Y axis of the second image but the X axis of the first image.


Solution

  • Filtering out the maxWeight == 0 data points is going in the right direction. You should keep doing that.

    The problem is that you are using strings to represent months. Since you are plotting strings, the chart thinks they are a categorical data, instead of a time series.

    First, change BestWeights into a Date:

    struct BestWeights: Identifiable
    {
        let id = UUID()
        let date: Date // keep this a Date!
        let maxWeight: Int
    }
    

    Here is an example data set:

    func weightsData() -> [BestWeights] {
        let calendar = Calendar.current
        var weights = [BestWeights]()
        for (month, weight) in zip(2...8, [90, 0, 0, 0, 100, 180, 100]) {
            let dateComponents = DateComponents(year: 2023, month: month, day: 1)
            let date = calendar.date(from: dateComponents)!
            weights.append(
                BestWeights(date: date, maxWeight: weight)
            )
        }
        return weights
    }
    

    and here is the chart:

    struct ContentView: View {
        @State var weights = weightsData().filter { $0.maxWeight > 0 }
        
        var body: some View {
            Chart(weights) { weight in
                LineMark(
                    x: .value("Month", weight.date, unit: .month), // note the ".month" argument here!
                    y: .value("Max Weights", weight.maxWeight)
                )
                .symbol {
                    // Also consider using a PointMark here
                    Circle()
                        .fill(.blue)
                        .frame(width: 7, height: 7)
                }
            }
            .chartXAxis {
                // here we add one axis mark per month
                AxisMarks(values: .stride(by: .month)) {
                    // and here we format the date into an abbreviated month format
                    AxisValueLabel(format: .dateTime.month(.abbreviated), centered: true)
                }
            }
            .padding()
        }
    }
    

    Notice that I never explicitly turned a Date into a String. I just passed a format style to AxisValueLabel and it takes care of the formatting.

    Output:

    enter image description here