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:
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?
I want the Y axis of the second image but the X axis of the first image.
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: