Issue #943
In this tutorial, we’ll learn how to use Swift Charts to visualize ranking data.
We use default AxisMarks
and AxisMarks
to let Swift Charts interpolate x and y grid lines. For y axis, I want to have finer grain control over the grid, so I specify an arrayLiteral
Note that for rank, the lower value the better ranking, so we make use of chartYScale
with reversed automatic domain to flip from 0 -> 100 to 100 -> 0
Finally, to handle hover, we place an invisible view Color.clear
with onContinuousHover
. Note that the x axis value are in Date
values, so we need to cast to Date
type
struct ContentView: View {
@State private var selectedDate: Date?
var body: some View {
Chart {
ForEach(items) { item in
lineMark(for: item)
}
}
.chartXAxisLabel("Last 30 days")
.chartXAxis {
AxisMarks()
}
.chartYAxisLabel("Rank")
.chartYAxis {
AxisMarks(values: .init(arrayLiteral: 1, 25, 50, 75, 100))
}
.chartYScale(domain: .automatic(reversed: true))
.chartLegend(spacing: 16)
.chartOverlay { proxy in
Color.clear
.onContinuousHover { phase in
switch phase {
case let .active(location):
selectedDate = proxy.value(atX: location.x, as: Date.self)
case .ended:
selectedDate = nil
}
}
}
}
}
For our mark, we can move it to another method marked with @ChartContentBuilder
. As we get hovered date value, we can find the item for that and place an annotation
@ChartContentBuilder
private func lineMark(for item: RankTrends.DailyItem) -> some ChartContent {
LineMark(
x: .value("Date", item.date),
y: .value("Rank", item.rank)
)
.foregroundStyle(.purple.gradient)
if let selectedDate,
let item = items.first(where: { $0.date.isSameDay(date: selectedDate) }) {
RectangleMark(
x: .value("Date", selectedDate)
)
.foregroundStyle(Color.separator.opacity(0.1))
.annotation {
Text("\(item.rank)")
.font(.callout)
}
}
}