Align Chart’s Mark style with Chart Plot Area in SwiftUI 4 and Charts Framework
Apple charts provides easy to use API for data visualization. Charts framework not only makes it easy to draw charts but it also provides an easy to use API for charts customization to fit the need for your app.
Today, we will talk about one of such customization. When setting style for BarMark if we choose to apply linear gradient. Each bar in the BarMark colors its own area to honor the provided style by default but what if we need to show data that’s related in nature and the gradient should be evenly distributed between the bars.
For example, if we want to plot California’s rainfall distribution throughout the year where months in the middle of the year get less rain and winters are wet, coloring each bar with its own gradient will not do the trick.
Graph before gradient distribution:
Graph after gradient distribution:
Let’s say that we want to plot California’s rainfall throughout the year where each month is plotted on x-axis and precipitation is plotted on y-axis.
Let’s set this graph’s foreground color with a gradient.
Here we have light blue color representing light rain and dark blue color representing heavy rain. Although gradients make each bar look good(kinda) but they don’t paint the correct picture. Looking at the gradient color, we can’t tell if July, August and September are light rain months. What we need is the gradient to align based on plot area where months with light rain shouldn’t get the dark blue color. More like this:
Looking at this new graph, we can tell that rain get’s lighter in the middle of the year and picks up during winter time.
This can be done using chart modifier called alignsMarkStylesWithPlotArea(_:) which aligns item’s styles with the chart’s plot area.
It takes boolean parameter to toggle the alignment.
Here is the complete code with data model.
import Charts
import SwiftUI
struct Rainfall: Identifiable {
let id = UUID()
var month: String
var precipitation: Int
}
extension Rainfall {
static var sample: [Rainfall] {
[
.init(month: "Jan", precipitation: 10),
.init(month: "Feb", precipitation: 12),
.init(month: "Mar", precipitation: 8),
.init(month: "Apr", precipitation: 6),
.init(month: "May", precipitation: 4),
.init(month: "Jun", precipitation: 2),
.init(month: "Jul", precipitation: 1),
.init(month: "Aug", precipitation: 1),
.init(month: "Sep", precipitation: 1),
.init(month: "Oct", precipitation: 4),
.init(month: "Nov", precipitation: 6),
.init(month: "Dec", precipitation: 8),
]
}
}
struct CARainChartView: View {
let lightBlue = Color(red: 0.85, green: 0.98, blue: 1.0)
let darkBlue = Color(red: 0.00, green: 0.00, blue: 0.55)
@State private var distributedGradient = false
var body: some View {
NavigationStack {
Chart {
ForEach(Rainfall.sample, id: \.month) { rainFall in
BarMark(
x: .value("Month", String(rainFall.month)),
y: .value("Precipitation", rainFall.precipitation)
)
.annotation {
VStack {
Text("🌧")
Text("\(rainFall.precipitation)\"")
}
.font(.subheadline)
}
}
.foregroundStyle(
.linearGradient(
colors: [lightBlue, darkBlue],
startPoint: .bottom,
endPoint: .top
)
)
.alignsMarkStylesWithPlotArea(distributedGradient)
}
.frame(height: 300)
.padding()
.navigationTitle("DevTechie.com")
.onTapGesture {
withAnimation {
distributedGradient.toggle()
}
}
}
}
}
struct CARainChartView_Previews: PreviewProvider {
static var previews: some View {
CARainChartView()
}
}
Build and run: