前言
在这篇文章中,我们将建立一个条形图,比较基督城区域自然漫步的持续时间。我们将运用本年推出的新的Swift Charts
框架,并将看到怎么绘制默许不符合 Plottable
协议的类型的数据,如 Measurement<UnitDuration>
。
定义图表的数据
让我们先定义一下要在图表中展现的数据。
我们声清楚一个包括标题和步行时间(小时)的 Walk
结构体。我们运用 Foundation
框架中的丈量类型Measurement和单位类型UnitDuration来标明每次步行的时间。
struct Walk {
let title: String
let duration: Measurement<UnitDuration>
}
我们在数组 works
中存储要在图表中闪现的数据。
let walks = [
Walk(
title: "Taylors Mistake to Sumner Beach Coastal Walk",
duration: Measurement(value: 3.1, unit: .hours)
),
Walk(
title: "Bottle Lake Forest",
duration: Measurement(value: 2, unit: .hours)
),
Walk(
title: "Old Halswell Quarry Loop",
duration: Measurement(value: 0.5, unit: .hours)
),
...
]
在图表中运用丈量值
尝试直接在图表中运用丈量值
让我们定义一个 Chart
,并将 walks
数组作为数据参数传递给它。因为我们知道我们的walk
标题是仅有的,所以我们可以直接运用它们作为 id
,但你也可以将你的数据模型改为 Identifiable
。
Chart(walks, id: \.title) { walk in
BarMark(
x: .value("Duration", walk.duration),
y: .value("Walk", walk.title)
)
}
注意,因为 Measurement<UnitDuration>
没有遵守 Plottable
协议,我们会得到一个差错:「Initializer ‘init(x:y:width:height:stacking:)’ requires that ‘Measurement’ conform to ‘Plottable’」
BarkMark
的初始化器期望收到一个用于 x 和 y 的 PlottableValue
参数。而且 PlottableValue
的值类型有必要符合 Plottable
协议。
我们有几个挑选来处理这个差错。我们可以提取丈量值的 value
,它是一个 Double
类型,它是默许符合 Plottable
的,我们可以扩展具有 Plottable
一致性的 Measurement<UnitDuration>
,或许我们可以定义一个包装了丈量的类型并使其符合 Plottable
协议。
假如我们简略地从丈量值中提取,我们就会失去上下文,不知道用什么单位来创建丈量值。这意味着,我们将无法正确格局化图表的标签来向用户标明单位。尽管我们可以记住我们在创建丈量时运用了小时 hours
,但这并不抱负。例如,我们可以决议以后改动数据模型,以分钟为单位存储持续时间,或许数据或许来自其他地方,所以手动重构单位并不是一个完美的处理计划。
用 Plottable
的一致性来扩展 Measurement<UnitDuration>
是可行的,但根据 Swift 中关于外部类型的追溯一致性的警告 (Warning for Retroactive Conformances of External Types),假如 Swift Charts 在未来添加了这种一致性,它或许会被损坏。
我们将研讨怎么定义我们自己的类型来包装 measurement
,并为我们的自定义类型添加 Plottable
的一致性。
规划一个包装器类型
规划一个符合 Plottable 标准的包装器类型
我们将定义一个自定义的 PlottableMeasurement
类型,并使其成为通用的,所以它可以包容任何类型的单位的丈量类型。
struct PlottableMeasurement<UnitType: Unit> {
var measurement: Measurement<UnitType>
}
然后,我们将为 PlottableMeasurement
添加 Plottable
的一致性,其单位为 UnitDuration
类型。我们可以在将来添加对其他单位的支撑。
extension PlottableMeasurement: Plottable where UnitType == UnitDuration {
var primitivePlottable: Double {
self.measurement.converted(to: .minutes).value
}
init?(primitivePlottable: Double) {
self.init(
measurement: Measurement(
value: primitivePlottable,
unit: .minutes
)
)
}
}
Plottable
协议有两个要求: primitivePlottable
特点有必要回来原始类型之一,如 Double
、String
或 Date
,以及一个可失败的初始化器,从原始 plottable
类型创建一个值。
我决议将丈量值转化为分钟,但你可以挑选合适你需求的任何其他单位。只是在与原始值转化时要运用相同的单位,这一点很重要。
我们现在可以更新我们的图表,以运用我们的自定义 Plottable
类型。
Chart(walks, id: \.title) { walk in
BarMark(
x: .value(
"Duration",
PlottableMeasurement(measurement: walk.duration)
),
y: .value("Walk", walk.title)
)
}
它可以工作,但X轴上的标签没有格局化,没有向用户闪现丈量单位。我们接下来要处理这个问题。
闪现格局化标签
闪现带有丈量单位的格局化标签
为了定制X轴上的标签,我们将运用chartXAxis(content:)
修改器,并用传递给我们的值重构x轴的符号。
Chart(walks, id: \.title) { ... }
.chartXAxis {
AxisMarks { value in
AxisGridLine()
AxisValueLabel("""
\(value.as(PlottableMeasurement.self)!
.measurement
.converted(to: .hours),
format: .measurement(
width: .narrow,
numberFormatStyle: .number.precision(
.fractionLength(0))
)
)
""")
}
}
我们首要添加网格线,然后重构给定值的标签。
AxisValueLabel
在初始化器中承受一个LocalizedStringKey
,它可以经过插值丈量和指定其格局风格来构建。
我们收到的值是运用我们在 Plottable
一致性中定义的初始化器创建的,所以在我们的案例中,丈量值是以分钟为单位供给的。但我信任关于这个特定的图表,运用小时会更好。我们可以很容易地将丈量值转化为插值内部所需的单位。在这里,我们承认该值是 PlottableMeasurement
类型的,所以我们可以强制解包类型转化。
我挑选了缩小的格局和小数点后零位数作为数字款式,但你可以根据你的具体图表调整这些设置。
最后的结果是在X轴上闪现以小时为单位的格局化持续时间。
你可以从我们的 GitHub repo 中取得这篇文章中运用的项目的无缺 示例代码。
本文正在参加「金石计划 . 分割6万现金大奖」