跳转至

Swift图表教程:入门

2022年11月23日,Swift 5.5,iOS 16,Xcode 14

了解如何使用Swift Charts将数据转换为优雅且易于访问的图形。作者:Vidhur Voora

留下评分/评论

下载材料

学习如何使用Swift Charts将数据转化为优雅的、可访问的图表。

对用户来说,一个有吸引力的、设计良好的图表比成行成列的数据更有用。如果你需要在你的应用程序中把复杂的数据变得简单易懂,那么这个教程就是为你准备的!

Swift Charts是一个灵活的框架,允许你使用你在SwiftUI中已经熟悉的声明式语法来创建图表。开箱即用,它支持动态字体大小、多种屏幕尺寸和可访问性。

在这个框架存在之前,你必须从头开始创建可视化,或者使用第三方软件包。

Swift Charts为你提供了创建漂亮图表的优雅体验。你将为一个名为WeatherChart的启动程序添加功能。你的目标是将历史天气数据的列表转化为吸引人的图表。

在这一过程中,你将:

  • 了解标记和属性--任何Swift图表的构建块。
  • 创建条形图、线形图、面积图和点图。
  • 定制这些图表。
  • 改善图表的可访问性。

你准备好学习如何用美丽的可视化来改进你的应用程序了吗?很好! 你可以直接进入,或者使用导航跳到特定的章节。

开始学习

点击本页顶部或底部的下载材料按钮,下载启动项目。

打开starter文件夹中的WeatherChart项目。你可能记得这个应用程序来自SwiftUI Tutorial for iOS: Creating Charts

构建并运行。

Build and Run the starter version of the Weather Chart app

该应用程序显示了大烟山国家公园内和周围的四个站点的历史天气数据。

  • 北卡罗来纳州的切罗基和田纳西州的加特林堡:这两个城市位于穿过公园的主要道路上。
  • Newfound Gap:与主路相交的缺口。
  • 勒康特山:公园里最高的山之一。

该数据集包含每天的降水、降雪和温度数据。

点击一个地点,显示该地点的基本信息和该地区的地图。注意三个选项卡,显示按月计算的降水量、每日降雪量和温度范围。

如果你有兴趣,你可以查看weather-data.csv中的原始数据。

熟悉Swift图表

花点时间熟悉一下任何Swift图表的构建模块:标记、属性、修改器和数据。

mark是代表数据的图形元素;例如,条形图中的矩形条。

Swift图表默认包括以下标记:

  • BarMark
  • PointMark
  • LineMark
  • AreaMark
  • RuleMark
  • RectangleMark

标记是可扩展的,所以你可以创建自定义标记。

在本教程中,你将使用properties来提供数据,并用modifiers来定制其外观。

Swift图表支持三种类型的数据。

  • Quantitative:表示数值,如温度、降雪的英寸数等。
  • Nominal:数值是离散的类别或群体,如一个城市、一个人的名字等。这种数据类型常常成为标签。
  • Temporal:代表一个时间点或时间间隔,如某一天部分的持续时间。

还有更多的东西要学,但这足以让你开始并进入下一部分,在那里你可以真正建立一些东西。

开发图表

理论够了--是时候开始本教程的实践部分了。从这里到最后,你将开发和改变几个图表。

到本教程结束时,你将有创建标记和修改其属性的实践经验。

创建柱状图

你的第一个任务是为降水数据创建一个条形图。条形图为每个数据点提供一个条形。每个条形图的长度代表一个数值,它可以是水平或垂直方向的。

转到Tabs组,打开PrecipitationTab.swift

你会看到一个标准的SwiftUI List(),它在011的整数中循环,代表一年中的各个月份。它显示每个月的总降水量,单位是英寸。

展开Charts组,打开PrecipitationChart.swift。这目前是一个空的视图。在PrecipitationChart中添加以下变量:

var measurements: [DayInfo]

有了这个,你把天气数据传递给PrecipitationTab的测量。

PrecipitationChart_Previews中的previews内容替换为:

// swiftlint:disable force_unwrapping
PrecipitationChart(
  measurements: WeatherInformation()!.stations[2].measurements)

在这里,你把天气数据传进去,用于预览。

接下来,为PrecipitationChart添加一个辅助方法:

func sumPrecipitation(_ month: Int) -> Double {
  self.measurements.filter {
    Calendar.current.component(.month, from: $0.date) == month + 1
  }
  .reduce(0) { $0 + $1.precipitation }
}

这个简短的代码块做了很多事情:

  • sumPrecipitation(_:)需要一个Int来代表月份。
  • filter获得该月的测量值,然后调整整数,该整数被作为零指数传入 - 这将其调整为1
  • reduce对这些测量值的降水值进行合计。

接下来,在import SwiftUI下面添加以下内容:

import Charts

在这里,你导入了Charts框架。

添加条形图

body的内容替换为:

// 1
Chart {
  // 2
  ForEach(0..<12, id: \.self) { month in
    // 3
    let precipitationValue = sumPrecipitation(month)
    let monthName = DateUtils.monthAbbreviationFromInt(month)
    // 4
    BarMark(
      // 5
      x: .value("Month", monthName),
      // 6
      y: .value("Precipitation", precipitationValue)
    )
  }
}

下面是里面发生的事情:

  1. 通过添加一个Chart结构开始创建一个图表。然后在其主体内声明标记并设置相应的属性。
  2. 添加一个ForEach循环,为每个月生成一个柱状图。
  3. 使用两个实用的方法来:
  4. 获取该月的降水数据之和。
  5. 通过向DateUtilsmonthAbbreviationFromInt(_:)传递月份编号,获得月份的缩写名称。
  6. 为图表创建一个BarMark,以显示条形图--标记表示视觉元素。
  7. 将月份的名称设置为x参数。.value修改器的第一个参数是数值的描述。第二个参数是实际值本身。
  8. 将每月降水量数据的总和设为数值--每条的高度由y参数控制。

把你的注意力转向预览窗口。现在条形图应该显示每个月的降水数据。

Vertical Bar Chart in Xcode Preview Canvas

注意到Swift Charts是如何优雅地使用缩写的月份名称作为X轴上每个条形图的标签的。y轴也根据所提供的降雨量数据被设置为一个适当的范围。

相当不错! 拍拍自己的背,请自己吃块糖,因为你提高了标准......有了标准! :]

整理柱状图

有一个更好、更简洁的方法来写上面的代码! 当ForEach是图表主体内唯一的内容时,你可以将其中的数据移到图表初始化器中。

Chart{}主体中移除ForEach,将数据移入图表初始化器,如下所示。

Chart(0..<12, id: \.self) { month in
  let precipitationValue = sumPrecipitation(month)
  let monthName = DateUtils.monthAbbreviationFromInt(month)
  BarMark(
    x: .value("Month", monthName),
    y: .value("Precipitation", precipitationValue)
  )
}

再次检查预览。柱状图的外观没有变化,而且代码更干净。

Vertical Bar Chart in the Xcode preview canvas

不过那张图看起来是不是有点拥挤?它可以看起来更好。

值得庆幸的是,你可以对此进行调整,这正是你在下一节要做的事情。

改成水平条形图

制作一个水平条形图--而不是垂直条形图--就像交换轴一样简单。

更新BarMark的值,如下所示:

BarMark(
  x: .value("Precipitation", precipitationValue),
  y: .value("Month", monthName)
)

这里,你把xy的值对调了。再次检查预览。

Horizontal Bar Chart in the Xcode preview canvas

好了!你会看到,图表已经转位,不再显得拥挤。你会看到,图表被移位了,不再显得拥挤了。

所以,图表是存在的,但它并不突出,也没有指定每个条形图的数值和轴的单位。你的下一个任务是定制该图表,使其更容易阅读,信息量更大。

自定义柱状图

默认情况下,柱状图的颜色是蓝色,这对一个关于水的图表来说不是一个糟糕的选择。但是你是来学习的,所以要继续学习如何改变它。

BarMark()中添加以下内容:

.foregroundStyle(.mint)

这将酒吧的颜色设置为薄荷色。

Bar Chart with Mint Style

花点时间看一下图表--你能准确地说出某月的降雨量吗?没有指示,这就是你接下来要解决的问题。

.foregroundStyle(.mint)下面添加以下内容:

.annotation {
  Text(String(format: "%.2f", precipitationValue))
    .font(.caption)
}

你用Text来注释每个BarMark。该值被设置为每个月的降水量之和。

Bar Chart with Annotations

刷新Canvas中的预览。现在你的图表明确地显示了这些值。

Xcode中使用Variants功能

Xcode的预览Canvas的底部有一个网格图标--它是两行三个方框的。点击它可以激活变体功能。

您可以使用此功能在不同的颜色方案、方向和字体大小中预览您的SwiftUI视图,以便您可以做出适当的调整。

单击grid icon并选择Color Scheme Variants

Using Color Scheme Variants

颜色方案的变体允许你在浅色和深色模式下预览你的图表。

再次点击grid icon,并选择Orientation Variants来检查你的图表在纵向和横向的方向。

Showing Orientation Variants

再次,点击grid icon,选择Dynamic Type Variants

Showing Dynamic Type Variants

使用动态类型变体,你可以用不同的字体比例预览图表。点击一个动态类型的变体,可以放大该变体并仔细检查。

现在你知道了:

  • 更多关于你可以创建的变体类型。
  • Swift Charts提供了对黑暗模式、方向和动态类型的支持,开箱即用。
  • 它还支持开箱即用的辅助功能,你可以为VoiceOver定制内容。

再仔细看一下这个图表。

Annotation overlapping the month name

你可能已经注意到,在降水极少的月份,文字重叠了。在查看动态类型的变体时,这一点尤其明显。

修复注释

在这一节中,你将解决文字重叠的问题,并为坐标轴添加一个标签,使图表的目的明确。

.annotation{}有3个可选参数,位置、对齐方式和间距:

  • 使用position来放置注释在项目的上方、下方、上方或末端。
  • 使用alignment来控制相对于被注释项目的对齐方式。
  • 最后,使用spacing来指定项目和注释之间的距离。

将注释代码改为:

.annotation(position: .trailing) {
  Text(String(format: "%.2f in", precipitationValue))
    .font(.caption)
}

你使用position.trailing来把注释放在条形图之后。你还添加了"in"来表示测量的单位。

另一种显示单位的方法是用.chartXAxisLabel(_:position:alignment:spacing:)在图表的x轴上添加一个标签。与注释类似,你也可以提供一个可选的位置、对齐方式和间距。

Chart{}下面添加以下内容:

.chartXAxisLabel("Inches", position: .leading)

这将标签设置为"Inches"并沿y轴居中。spacing:的默认值是.center.。看一下预览,确认标签是否显示。

Precipitation chart with a labeled axis

接下来,你将通过定制VoiceOver的内容使你的图表更加无障碍。

支持可访问性

Chart{}中添加以下修改器,在.annotation{}下面:

.accessibilityLabel(DateUtils.monthFromInt(month))
.accessibilityValue("Precipitation \(precipitationValue)")

这将月份名称设置为可访问性标签,并将该月的降水值设置为可访问性值。

现在,柱状图已经准备好迎接它的黄金时间了!

把它放在一起

打开PrecipitationTab.swift,将body的内容替换为:

VStack {
  Text("Precipitation for 2018")
  PrecipitationChart(measurements: self.station.measurements)
}

在这里,你用一个新造的、闪亮的图表取代了枯燥的降水数据列表! 建立和运行。

Viewing Precipitation Chart

现在你已经准备好启用VoiceOver

Note

花点时间为VoiceOver创建一个快捷方式,方法是浏览Settings ▸ Accessibility ▸ Accessibility Shortcut并选择VoiceOver。这让你可以选择通过三击电源按钮打开或关闭VoiceOver

你只能在一个物理设备上测试VoiceOver。你可能认为你可以使用Xcode Accessibility Inspector与模拟器。然而,检查器不会读出.accessibilityValue。总是在真实的硬件上进行测试。

通过三连击电源按钮激活VoiceOver

Accessibility In Precipitation Chart

你应该听到VoiceOver把每个条形标记读成月份名称和相应的降水值。

添加一个点图

点状图对于以简洁的方式显示定量数据非常有用。

大烟山是美国东部海拔最高的地区之一,它的降雪量比你想象的要少。

雪的稀少意味着每个月的数据可能都不存在。

要想自己检查这一点,请运行应用程序并点击Cherokee站。选择Snowfall标签并检查数据。

点状图是可视化该数据的一个好选择。

在项目导航器中找到Charts组,打开SnowfallChart.swift

import SwiftUI下面添加以下内容:

import Charts

同样,你只需导入Charts框架。

SnowfallChart中添加以下变量:

var measurements: [DayInfo]

这将保存测量结果。

还是在同一个文件中,将previews的内容替换为:

// swiftlint:disable force_unwrapping
SnowfallChart(
  measurements: WeatherInformation()!.stations[2].measurements)

在这里,你传递给预览的测量值,以便显示。

接下来,将body的内容替换为:

// 1
Chart(measurements) { dayInfo in
  // 2
  PointMark(
    x: .value("Day", dayInfo.date),
    y: .value("Inches", dayInfo.snowfall)
  )
}

这段代码做了几件事。

  1. 通过添加一个Chart来创建一个图表。
  2. 通过添加一个PointMark来创建一个点图。

  3. 将降雪的日期设置为xvalue

  4. 将当天的总降雪量设置为yvalue

为了将其付诸实施,打开SnowfallTab.swift,并将body的内容替换为以下内容:

VStack {
  Text("Snowfall for 2018")
  SnowfallChart(measurements: measurementsWithSnowfall)
}
.padding()

一张图表胜过一千个数据点!

建立和运行。

Point chart showing snowfall with default scales

点选一个气象站并选择Snowfall标签。只用了几行代码,就添加了一个点状图来可视化降雪数据--干得好!这就是我们的工作。

现在,比较各城市之间的降雪数据。你会注意到Y轴的刻度根据相应站点的降雪数据而动态变化。

这很准确,但是当比例尺发生变化时,就很难进行心理比较了。你可以为所有站点设置一个固定的Y轴刻度。

自定义点图

再次打开SnowfallChart.swift,在Chart{}中添加以下内容:

.chartYScale(domain: 0...10)

你刚刚把y-axis的刻度设置为总是从0开始,到10结束。

接下来,你将自定义这个图表的背景颜色。

就在.chartYScale(domain: 0...10)下面添加:

.chartPlotStyle { plotArea in
  plotArea.background(.blue.opacity(0.2))
}

这里,你通过使用.chartPlotStyle将绘图区的背景改为blue,不透明度为0.2

charPlotStyle{}下面添加:

.chartYAxisLabel("Inches")

这将在Y轴上添加一个标签,指定测量单位。

建立并运行。

Showing snowfall data with Point Chart and a scaled axis

花点时间来比较不同气象站之间的降雪数据。

注意每个图表的Y轴刻度都是一样的,背景颜色是蓝色。做到这些只用了几行代码!

接下来,你将学习如何创建一个折线图并结合不同的标记。

添加一个线形图

在你到目前为止建立的所有图表中,这个图表将是最漂亮的。

请看一下你正在处理的数据:

  1. 运行WeatherChart,然后选择一个气象站。
  2. 点击Temperatures,查看显示一年内每日最高和最低温度的列表。

List showing raw temperature data

这个名单并不方便用户使用。当你滚动时,很难说它是如何变化的。

温度读数在折线图中看起来很好,因为它们随时间波动。当你的眼睛追踪线条时,你几乎可以感觉到温度的变化。

你可以分别显示最高和最低温度,但这将使你更难比较每个月的情况。

但是,如果你首先计算平均温度,你可以只把一组数据输入每个月的图表,并显示一条线。

在接下来的几个步骤中,你将建立一个线图,并排显示多个月份的数据,并以清晰的坐标轴表示每个星期和温度读数。

计算和创建折线图

在项目导航器中,找到并展开Charts组。打开MonthlyTemperatureChart.swift

与你之前建立的图表类似,在import SwiftUI后添加以下内容:

import Charts

MonthlyTemperatureChart中添加以下变量:

var measurements: [DayInfo]

MonthlyTemperatureChart_Previewspreviews的内容替换为:

// swiftlint:disable force_unwrapping
MonthlyTemperatureChart(
  measurements: WeatherInformation()!.stations[2].measurements)

MonthlyTemperatureChart中添加以下实用方法:

func measurementsByMonth(_ month: Int) -> [DayInfo] {
  return self.measurements.filter {
    Calendar.current.component(.month, from: $0.date) == month + 1
  }
}

你要告诉你的新方法measurementsByMonth(_:)来返回指定月份的每日天气信息数组。

接下来,在MonthlyTemperatureChart中添加以下内容:

// 1
var monthlyAvgTemperatureView: some View {
  // 2
  List(0..<12) { month in
    // 3
    VStack {
      // 4
      Chart(measurementsByMonth(month)) { dayInfo in
        // 5
        LineMark(
          x: .value("Day", dayInfo.date),
          y: .value("Temperature", dayInfo.temp(type: .avg))
        )
        // 6
        .foregroundStyle(.orange)
        // 7
        .interpolationMethod(.catmullRom)
      }

      Text(Calendar.current.monthSymbols[month])
    }
    .frame(height: 150)
  }
  .listStyle(.plain)
}

在这个计算变量中,有很多很酷的事情发生:

  1. 你定义了monthlyAvgTemperatureView,它将填充月度温度视图。
  2. 你添加了一个List来显示月度温度图表。
  3. 在列表里面,VStack显示温度图表和它下面的月份名称。
  4. Chart获得相应月份的天气信息。
  5. 你使用LineMark来创建一个线图。对于月内的每一天,你都要添加一个LineMarkx-axis表示当天,y-axis表示当天的平均温度。
  6. 使用.foregroundStyle.将折线图的颜色设置为橙色。
  7. 为了平滑渲染的线条,你使用.interpolationMethod并调用Catmull-Rom样条来插值数据点。

显示折线图

现在,用以下内容替换body的内容:

monthlyAvgTemperatureView

你刚刚把你的花哨的新计算变量设置为body内容。

在预览窗口中检查你的工作。

Line chart showing monthly temperature data

现在这很干净! 你的线形图优雅地显示了每个月的平均温度。干得好!

自定义折线图

还是在MonthlyTemperatureChart.swift中,在monthlyAvgTemperatureView的实现中找到Chart{}。添加以下内容:

// 1
.chartForegroundStyleScale([
  TemperatureTypes.avg.rawValue: .orange
])
// 2
.chartXAxisLabel("Weeks", alignment: .center)
.chartYAxisLabel("ºF")
// 3
.chartXAxis {
  AxisMarks(values: .automatic(minimumStride: 7)) { _ in
    AxisGridLine()
    AxisTick()
    AxisValueLabel(
      format: .dateTime.week(.weekOfMonth)
    )
  }
}
// 4
.chartYAxis {
  AxisMarks( preset: .extended, position: .leading)
}

以下是你在这里做的事情:

  1. 添加一个.chartForegroundStyleScale修改器来定义平均值如何映射到前景风格,并在折线图下面添加一个图例。
  2. x-axisy-axis做一个标签,并指定x轴的对齐方式,使其不与图例重叠。
  3. .chartXAxis修改x-axis,以显示当月的星期,而不是默认的。设置X轴上的视觉标记以显示周数:
  4. 设置AxisMarks最小跨度为7,因为每个星期由7天组成。
  5. 使用AxisGridLine来显示一条横跨绘图区的线。
  6. 使用`AxisTick'来绘制刻度线。
  7. 设置AxisValueLabel为每月的星期数。
  8. .chartYAxisAxisMarks调整Y轴,使其与图表的前缘而不是默认的后缘相接。

你有更多的选项来定制图表。例如,你也可以为坐标轴使用不同的字体或前景样式。

完成折线图的制作

打开TemperatureTab.swift。用以下内容替换body的内容:

VStack {
  Text("Temperature for 2018")
  MonthlyTemperatureChart(measurements: self.station.measurements)
}

你刚刚插入了你新创建的MonthlyTemperatureChart,并传入了天气测量数据。

建立并运行。

Line Chart showing monthly temperature data

选择一个气象站,并导航到温度标签,玩玩你花哨的新线图,显示每星期和每月的平均温度。

现在你的大脑可以快速阅读和比较差异。恭喜你。:]

但你的工作还没有完全完成。

在下一节中,你将结合不同的标记来创建一个更有意义的图表。

在折线图中组合标记

在本节中,你将向自己说明如何使用RectangleMarkAreaMark来显示低、高和平均温度,以及添加一个下钻功能,使用户可以看到每一天的细节。

Charts组中找到并打开WeeklyTemperatureChart.swift

将整个文件的内容替换为以下内容:

import SwiftUI
// 1
import Charts

struct WeeklyTemperatureChart: View {
  // 2
  var measurements: [DayInfo]

  // 3
  var month: Int

  // 4
  let colorForAverageTemperature: Color = .red
  let colorForLowestTemperature: Color = .blue.opacity(0.3)
  let colorForHighestTemperature: Color = .yellow.opacity(0.4)

  var body: some View {
    // 5
    weeklyTemperatureView
  }

  var weeklyTemperatureView: some View {
    // TODO: Chart will be added here
  }
}

struct WeeklyTemperatureChart_Previews: PreviewProvider {
  static var previews: some View {
    // swiftlint:disable force_unwrapping
    // 6
    WeeklyTemperatureChart(
      measurements: WeatherInformation()!.stations[2].measurements, month: 1)
  }
}

这里有一个细分:

  1. 导入Charts框架。
  2. measurements存储天气数据。
  3. month存储你想查看的每日温度数据的月号。
  4. 颜色分别代表平均、最低和最高温度。
  5. 创建weeklyTemperatureView计算变量来保存图表的内容。你将在视图body中使用它。
  6. 为预览传入天气数据。

WeeklyTemperatureChart添加以下实用方法:

// 1
func measurementsByMonth(_ month: Int) -> [DayInfo] {
  return self.measurements
    .filter {
      Calendar.current.component(.month, from: $0.date) == month + 1
    }
}

// 2
func measurementsBy(month: Int, week: Int) -> [DayInfo] {
  return self.measurementsByMonth(month)
    .filter {
      let day = Calendar.current.component(.day, from: $0.date)
      if week == 1 {
        return day <= 7
      } else if week == 2 {
        return (day > 7 && day <= 14)
      } else if week == 3 {
        return (day > 14 && day <= 21)
      } else if week == 4 {
        return (day > 21 && day <= 28)
      } else {
        return day > 28
      }
    }
}

以下是这些新方法的作用:

  1. measurementsByMonth(_:)返回指定月份的每日天气信息阵列。
  2. measurementsBy(month:week:)返回一个数组,其中包含指定月份的每周的天气信息 - 你需要这个数组来显示每一周的图表。

添加钻取功能

你需要提供一个选项,在两种类型的图表之间切换。

WeeklyTemperatureChart中添加以下内容:

enum TemperatureChartType {
  case bar
  case line
}

你添加了TemperatureChartType来决定显示温度数据的图表类型。

接下来,在TemperatureChartType下面添加以下内容:

@State var chartType: TemperatureChartType = .bar

chartType保存当前选择的温度图表类型,以便查看。

添加图表类型选择器

weeklyTemperatureView中的// TODO: Chart will be added here替换为:

return VStack {
  // 1
  Picker("Chart Type", selection: $chartType.animation(.easeInOut)) {
    Text("Bar").tag(TemperatureChartType.bar)
    Text("Line").tag(TemperatureChartType.line)
  }
  .pickerStyle(.segmented)

  // 2
  List(1..<6) { week in
    VStack {
      // TODO: Add chart here
    }
    .frame(
      height: 200.0
    )
  }
  .listStyle(.plain)
}

有了这个,你就增加了。

  1. 一个Picker,可以选择条形图或线形图。选择被保存在chartType中。
  2. 一个List来显示每周的温度数据,在其中创建一个VStack作为该月每一周的列表项。你很快就会给它添加一个图表。

添加多个标记

// TODO: Add chart here替换为:

// 1
Chart(measurementsBy(month: month, week: week)) { dayInfo in
  switch chartType {
    // 2
  case .bar:
    BarMark(
      x: .value("Day", dayInfo.date),
      yStart: .value("Low", dayInfo.temp(type: .low)),
      yEnd: .value("High", dayInfo.temp(type: .high)),
      width: 10
    )
    .foregroundStyle(
      Gradient(
        colors: [
          colorForHighestTemperature,
          colorForLowestTemperature
        ]
      )
    )

    // 3
  case .line:
    LineMark(
      x: .value("Day", dayInfo.date),
      y: .value("Temperature", dayInfo.temp(type: .avg))
    )
    .foregroundStyle(colorForAverageTemperature)
    .symbol(.circle)
    .interpolationMethod(.catmullRom)
  }
}
// 4
.chartXAxis {
  AxisMarks(values: .stride(by: .day))
}
.chartYAxisLabel("ºF")
.chartForegroundStyleScale([
  TemperatureTypes.avg.rawValue: colorForAverageTemperature,
  TemperatureTypes.low.rawValue: colorForLowestTemperature,
  TemperatureTypes.high.rawValue: colorForHighestTemperature
])
这段代码是你的图表逻辑的主要部分,它创建了两种不同的图表风格来显示相同的数据!在这里,我们将逐节解释。

下面是一个逐节的解释:

  1. 你创建了一个Chart,并将给定月份的一周中每一天的天气测量数据传递给它。
  2. 接下来,你为条形可视化添加一个BarMark,并将日期设置为X轴的值,你还可以:

  3. 为y轴提供一个范围,用yStart表示最低温度,yEnd表示当天的最高温度。

  4. 通过设置width来控制标记的宽度。
  5. 设置一个漂亮的Gradient颜色,使最低温度到最高温度的范围可视化。

  6. LineMark显示当天的平均温度,类似于月度温度图。

Note

你可以用.symbol(.circle)来指定图表对每个点应使用的符号类型。

  1. 通过以下方式定制x-axis
  2. AxisMark的跨度设置为一天。
  3. 添加ºF作为Y轴的单位标签。
  4. 通过向.chartForegroundStyleScale传递一个KeyValue对数组,为图表添加图例。每一对代表图表上的一个测量值,以及它在图例中应该使用的颜色--图表的颜色不受此影响。 注意在BarMark中,温度是一个从高到低的范围。而在LineMark中,它只是平均温度。

你能在一个视觉中显示高、低和平均吗?是的,你可以,接下来你会这样做。:]

可视化多个数据点

case .bar:的末尾添加以下内容,就在case .line上面:

RectangleMark(
  x: .value("Day", dayInfo.date),
  y: .value("Temperature", dayInfo.temp(type: .avg)),
  width: 5,
  height: 5
)
.foregroundStyle(colorForAverageTemperature)

你可以将多个标记结合起来,以提供更好的可视化数据!

这里你创建了一个RectangleMark来显示当天的平均温度。

RectangleMark结合的BarMark现在显示当天的最高、最低和平均温度。

case .line:下面添加.interpolationMethod(.catmullRom)

AreaMark(
  x: .value("Day", dayInfo.date),
  yStart: .value("Low", dayInfo.temp(type: .low)),
  yEnd: .value("High", dayInfo.temp(type: .high))
)
.foregroundStyle(
  Gradient(
    colors: [
      colorForHighestTemperature,
      colorForLowestTemperature
    ]
  )
)

这增加了一个AreaMark来显示当天的最低和最高温度。LineMarkAreaMark相结合,以不同的可视化方式展示了每天的高、低和平均温度。

最后一步。你已经完成了图表,但仍然需要启用下钻体验,以便用户可以在月度和周度图表之间自由浏览。

打开MonthlyTemperatureChart.swift,将body的内容替换为以下内容:

NavigationView {
  monthlyAvgTemperatureView
}
.navigationTitle("Monthly Temperature")

这一小段代码将monthlyAvgTemperatureView嵌入到一个NavigationView中,并为导航设置了一个标题。

最后,在monthlyAvgTemperatureView中,将VStack包围在List中的NavigationLink中,如下所示:

List(0..<12) { month in
  let destination = WeeklyTemperatureChart(
    measurements: measurements, month: month)
  NavigationLink(destination: destination) {
    // VStack code
  }
}

在这里,你让每个VStack表现为一个导航链接,以呈现相关的细节。

构建和运行。

Bar and Line charts showing weekly temperature data

选择一个气象站,点击Temperature标签,然后从月度温度视图中选择一个图表。

使用选取器在BarLine之间切换,看看组合标记的作用。

哇,这是一个相当大的成就! 现在,我们可以优雅而轻松地查看一段时间内的温度,了解当时的天气情况。

接下来去哪?

使用本教程顶部或底部的下载材料按钮下载项目的完成版。

在本教程中,你已经学会了如何:

  • 创建不同类型的图表,如条形图、线图和点图。
  • 创建和自定义标记、单位标签和它们的属性。
  • 自定义图表样式、颜色、坐标轴样式和位置,以及整个绘图区域。
  • 合并标记以更好地显示数据。
  • 从相同的数据建立多种风格的图表,并使用户能够在它们之间进行切换。
  • 启用下钻功能,使用户可以在摘要数据和详细的可视化之间跳跃。

要了解更多关于图表的信息,请查看这些WWDC视频:

我希望你喜欢这个教程。如果你有任何问题或意见,请加入下面的论坛讨论。