I have a Swift Chart. It has two sets of data plotted. A BarMark
using yStart
and yEnd
to specify minimum/maximum values and a LineMark
to specify a related value.
I want the LineMark
plot to be "on top" of the BarMark
plot, but it always plots the other way around, with the result that the bars obscure part of the line.
How can I change this?
Here is a simple reproduction of my problem
struct DataItem: Identifiable {
var id: Int {
return month
}
let month: Int
let min: Int
let max: Int
let mid: Int
static var data:[DataItem] = {
var items = [DataItem]()
for i in 1...12 {
let min = Int.random(in: 1...50)
let max = Int.random(in: 51...100)
let mid = (max-min)/2+min
items.append(DataItem(month: i, min: min, max: max, mid:mid))
print(max,min,mid)
}
return items
}()
}
import SwiftUI
import Charts
struct ChartView: View {
var data: [DataItem]
var body: some View {
Chart(data) { dataItem in
BarMark(x: .value("Month", dataItem.month), yStart: .value("Min", dataItem.min), yEnd: .value("Max", dataItem.max))
LineMark(x: .value("Month",dataItem.month), y:.value("mid", dataItem.mid))
.symbol(.circle)
.foregroundStyle(.green)
}
}
}
#Preview {
ChartView(data: DataItem.data)
}
In the image, you can see that the first LineMark
appears on top of the first BarMark
but the rest are behind. Why is this and how can I change it?
It turns out the answer is a subtle but significant change to the way that the marks are added to the Chart
.
Using the form in my question
Chart(data) {
BarMark()
LineMark()
}
results in a chart with effectively a single plot with data represented in two different ways.
If I change it to
Chart {
ForEach(data) {
BarMark()
}
ForEach(data) {
LineMark()
}
}
Then I have one chart with two separate plots of two separate data series and the layer order follows the order in which those plots are specified.
Here is the working code:
struct ChartView: View {
var data: [DataItem]
var body: some View {
Chart {
ForEach(data) { dataItem in
BarMark(x: .value("Month", dataItem.month), yStart: .value("Min", dataItem.min), yEnd: .value("Max", dataItem.max))
}
ForEach(data) { dataItem in
LineMark(x: .value("Month",dataItem.month), y:.value("mid", dataItem.mid))
.symbol(.circle)
.foregroundStyle(.green)
}
}
}
}