Swift Charts:如何仅显示数组中值的值和标签?

Dan*_*iel 5 swiftui swiftui-charts

我有一个图表,其中包含在 x 轴上跨越多天的WeatherKit.HourWeather对象。但是,我想排除夜间时间。看起来我可以使用 ChartXScale 修改器来做到这一点,如下所示:

let myDataSeperatedByHours = arrayWithAllDates.filter { ... }.sorted(...) // Array of WeatherKit.HourWeather objects filtered by isDaylight = true and sorted by date
let allDaytimeDates = myDataSeperatedByHours.map { $0.date } //only the Date objects

Chart {
      ForEach(myDataSeperatedByHours, id: \.date) { hourData in
           LineMark(
                x: .value("hour", hourData.date),
                y: .value("value", hourData.value)
           )
      }
                       

}
.chartXAxis {
        AxisMarks(position: .bottom, values: allDaytimeDates) { axisValue in
            if let date = axisValue.as(Date.self) {
                AxisValueLabel(
                    "\(Self.shortTimeFormatter.calendar.component(.hour, from: date))"
                )
            }
        }
    }
.chartXScale(domain: allDaytimeDates, type: .category)
Run Code Online (Sandbox Code Playgroud)

然而,图表仍然显示没有值的部分。(夜间)我希望晚上的时候把所有东西都移走。我在下图中将其标记为绿色。也许我必须使用两个相邻的图表。每天一张,但我不敢相信只用一张图表是不可能做到的。

在此输入图像描述

我创建了一个示例应用程序,您可以在此处下载和测试: https: //github.com/Iomegan/DateChartIssue

Sou*_*unt 6

根据域参数的图表比例修改器文档

图表中 x 轴的可能数据值。您可以使用 ClosedRange 定义域以表示数字或日期值(例如,0 ... 500),并使用数组定义域以表示分类值(例如,[“A”、“B”、“C”])

对于日期类型值,该函数似乎需要一个范围,但由于您指定了一个数组,因此该方法调用会陷入困境。

您可以提供修改推断域的自动缩放域,而不是直接提供域。要将域设置为您计算的allDaytimeDates使用量:

.chartXScale(domain: .automatic(dataType: Date.self) { dates in
    dates = allDaytimeDates
})
Run Code Online (Sandbox Code Playgroud)

更新1

您可以尝试多种方法来忽略 X 轴上的夜间日期刻度。更简单且不推荐的方法是在线条标记中提供 X 轴值作为 aString而不是Date

指定 X 轴值的问题是,Date您只能提供轴刻度的范围,并且您现在不能选择多个范围作为轴的刻度,同样,您不能指定刻度来忽略某些范围或值(即夜间)。通过将 X 轴值指定为字符串,您将能够忽略夜间值:

LineMark(
    x: .value("hour", "\(hourData.date)"),
    y: .value("value", hourData.value)
)
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点是从该图中获得的温度变化是错误的,因为所有数据点都将被平等地分开,无论其日期值如何。

首选方法是手动调整第二天数据点的 X 轴位置。对于您的场景,您可以创建一个DayHourWeather具有自定义 X 位置值的类型:

struct DayHourWeather: Plottable {
    let position: TimeInterval // Manually calculated X-axis position
    let date: Date
    let temp: Double
    let series: String // The day this data belongs to

    var primitivePlottable: TimeInterval { position }
    init?(primitivePlottable: TimeInterval) { nil }

    init(position: TimeInterval, date: Date, temp: Double, series: String) {
        self.position = position
        self.date = date
        self.temp = temp
        self.series = series
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以自定义position数据,将白天的图移得更近,而忽略夜间值。DayHourWeather然后你可以从你的 s创建s HourWeather

/// assumes `hourWeathers` are filtered containing only day time data and already sorted
func getDayHourWeathers(from hourWeathers: [HourWeather]) -> [DayHourWeather] {
    let padding: TimeInterval = 10000 // distance between lat day's last data point and next day's first data point
    var translation: TimeInterval = 0 // The negetive translation required on X-axis for certain day
    var series: Int = 0 // Current day series
    var result: [DayHourWeather] = []
    result.reserveCapacity(hourWeathers.count)
    for (index, hourWeather) in hourWeathers.enumerated() {
        defer {
            result.append(
                .init(
                    position: hourWeather.date.timeIntervalSince1970 - translation,
                    date: hourWeather.date,
                    temp: hourWeather.temp,
                    series: "Day \(series + 1)"
                )
            )
        }

        guard
            index > 0,
            case let lastWeather = hourWeathers[index - 1],
            !Calendar.current.isDate(lastWeather.date, inSameDayAs: hourWeather.date)
        else { continue }

        // move next day graph to left occupying previous day's night scale
        translation = hourWeather.date.timeIntervalSince1970 - (result.last!.position + padding)
        series += 1
    }
    return result
}
Run Code Online (Sandbox Code Playgroud)

现在要绘制图表,您可以使用新创建的DayHourWeather值:

var body: some View {
    let dayWeathers = getDayHourWeathers(from: myDataSeperatedByHours)

    Chart {
        ForEach(dayWeathers, id: \.date) { hourData in
            LineMark(
                x: .value("hour", hourData.position), // custom X-axis position calculated
                y: .value("value", hourData.temp)
            )
            .foregroundStyle(by: .value("Day", hourData.series))
        }
    }
    .chartXScale(domain: dayWeathers.first!.position...dayWeathers.last!.position) // provide scale range for calculated custom X-axis positions
}
Run Code Online (Sandbox Code Playgroud)

请注意,通过上述更改,您的 X 轴标记将显示您的自定义 X 轴位置。要将其更改回您想要显示的实际日期标签,您可以指定自定义 X 轴标签:


.chartXAxis {
    AxisMarks(position: .bottom, values: dayWeathers) {
        AxisValueLabel(
            "\(Self.shortTimeFormatter.calendar.component(.hour, from: dayWeathers[$0.index].date))"
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

valuesfor 的参数仅AxisMarks接受项目数组Plottable,这就是需要确认DayHourWeatherto的原因。Plottable经过上述更改后,获得的图表将类似于以下内容:

最终情节

请注意,我为每天的数据创建了不同的系列。虽然您可以将它们组合成一个系列,但我建议您不要这样做,因为您要删除部分 X 轴刻度,因此生成的图表会误导查看者。