I am trying to use Swift Charts to draw the below graph. I am having trouble figuring out how to change the color of the line based on it's Y value. In the graph below, different ranges of Y have different colors assigned to them and the line reflects that.
I tried to set the line color using the foreground of the LineMark, but it seems to only take the first color assignment and ignore any others set on it.
Chart {
ForEach(plotItemSeries) { plotItem in
LineMark(x: .value("XVAL", plotItem.xVal),
y: .value("YVAL", plotItem.yVal))
.foregroundStyle(plotItem.zoneColor) // <== color assigned base on y-val
}
}
I tried using a foreground scale but it just broke it up into multiple series like you would expect.
Chart {
ForEach(plotItemSeries) { plotItem in
LineMark(x: .value("XVAL", plotItem.xVal),
y: .value("YVAL", plotItem.yVal))
.foregroundStyle(by: .value("Zone", plotItem.zoneColorName)) // <== "gray", "blue", etc..
}
}
.chartForegroundStyleScale(["gray": Color("grayCustom"),
"green": Color("greenCustom"),
"blue": Color("blueCustom"),
"orange": Color("orangeCustom"),
"red": Color("redCustom")])
Any ideas how to set the color of the line depending on it's y value?
It turns out .linearGradient(_ gradient: Gradient, startPoint: UnitPoint, endPoint: UnitPoint) -> LinearGradient
is the correct approach for this.
The Gradient.Stops are mapped to a UnitPoint which maps to the bounding box of the line drawn so the color boundary y-vals have to be converted to the unit point value which maps to the y-val of the color boundary.
For a simple example, if we want to draw the line blue below y-val of 5 and green above, and the line is: (0,0) to (10,10) then the unit point value that corresponds to the transition point is 0.5.
To get a crisp color transition we just define the gradient so that the portion where the color changes is too small to render:
let stops = [
Gradient.Stop(color: .red, location: 0.0),
Gradient.Stop(color: .red, location: 0.5),
Gradient.Stop(color: .green, location: 0.50001),
Gradient.Stop(color: .green, location: 1.0)
]
Thus the correct approach looks something like:
Chart {
ForEach(plotItemSeries) { plotItem in
LineMark(x: .value("XVAL", plotItem.xVal),
y: .value("YVAL", plotItem.yVal))
.foregroundStyle(.linearGradient(Gradient(stops: stops),
startPoint: .bottom,
endPoint: .top))
}
}
The resulting graph line (using the original data) looks close to the original graph we were trying to match: Graph With SwiftCharts