Я рисую диаграмму SwiftUI из структуры, которая выглядит следующим образом:
struct BestWeights: Identifiable
{
let id = UUID()
let date: String
let maxWeight: Int
}
Затем я создаю массив этой структуры, который буду использовать для построения диаграммы:
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
}
Затем я перебираю bestWeights и строю их график:
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)
}
}
}
Результат:
Это не то, чего я хочу. Я не хочу, чтобы 0 отображались на оси Y. Поэтому я добавил проверку, чтобы не добавлять bestWeight, если вес равен 0:
if (bestWeightOfPeriod != 0) {
bestWeights.append(BestWeights(date: formattedMonth, maxWeight: bestWeightOfPeriod))
}
Это именно то, чего я хочу, но это создает еще одну проблему на моей оси X, где пропускаются месяцы. Это идет с февраля прямо по июнь. Я все еще хочу отметить все месяцы. Поскольку дата хранится в моей структуре и пропускает месяцы, имеющие вес 0, диаграмма помечается неправильно.
Как я могу обозначить эти недостающие месяцы?
Мне нужна ось Y второго изображения, но ось X первого изображения.
нет, второй не включает в себя никаких записей с весом 0, поэтому его ось x неправильная, как на первом изображении, да, без этих точек, удаляющих нижнюю часть графика, это то, что я ищу
Итак, вы хотите, чтобы февраль был всего лишь одной точкой, а линия начиналась с июня?
нет, я хочу, чтобы февраль соединился с июнем. Моя единственная проблема со вторым изображением заключается в том, что на оси X в качестве метки отсутствует март, апрель, май.
Фильтрация maxWeight == 0
точек данных идет в правильном направлении. Тебе следует продолжать это делать.
Проблема в том, что вы используете строки для обозначения месяцев. Поскольку вы рисуете строки, диаграмма считает, что это категориальные данные, а не временной ряд.
Сначала замените BestWeights
на Date
:
struct BestWeights: Identifiable
{
let id = UUID()
let date: Date // keep this a Date!
let maxWeight: Int
}
Вот пример набора данных:
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
}
и вот график:
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()
}
}
Обратите внимание: я никогда явно не превращал Date
в String
. Я только что передал стиль формата AxisValueLabel
, и он позаботился о форматировании.
Выход:
Сейчас он работает лучше, но сейчас на втором изображении он работает в феврале, апреле, июне. Марта, мая и июля все еще нет.
Я отредактировал ответ, включив в него ваш код
@johnsmith Вам следует удалить первый модификатор chartXAxis
, куда вы добавили AxisMarks(stroke: StrokeStyle(lineWidth: 0))
. Вам это не нужно — линий сетки нет, потому что я не поставил AxisGridLine
в закрытие конструктора AxisMarks
, где я поставил AxisValueLabel
.
@johnsmith Если вам нужны линии сетки, вы можете добавить AxisGridLine(stroke: StrokeStyle(lineWidth: 1))
после AxisValueLabel
.
Спасибо за отличный ответ. Я уверен, что это поможет многим людям, ресурсы на диаграммах весьма ограничены.
Только один последний вопрос, если кто-то захочет вместо увеличения по месяцам увеличивать по неделям, например, 10/7, 17/7, 24/7 и т. д. Как бы они это сделали, используя ваш ответ? Я не могу найти, как двигаться по неделям?
@johnsmith Ты можешь .stride(by: .day, count: 7)
. Не забудьте также изменить параметр unit:
в x: .value("Month", weight.date, unit: .day)
.
А что бы вы сделали с AxisValueLabel, чтобы он отображал дд/мм по оси X так же, как месяцы?
@johnsmith .dateTime.month(.twoDigits).day()
Пожалуйста, прочитайте документацию.
Это одна и та же ось Y на обоих изображениях, не так ли? Я думаю, вам нужны значения (точки) второго изображения.