How do I specify a custom color for each series on a SwiftUI line chart? The number and desired colors of the series are determined at runtime. The closest fit I've found is chartForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>), but it takes a KeyValuePairs
, which appears to only support compile-time initialization.
struct DataPoint: Identifiable, Hashable {
let series: String
let x: Int
let y: Int
var id: DataPoint { self }
}
let data: [DataPoint] = [
DataPoint(series: "A", x: 1, y: 1),
DataPoint(series: "A", x: 2, y: 2),
DataPoint(series: "B", x: 3, y: 1),
DataPoint(series: "B", x: 4, y: 1),
]
// Hard-coded for this example. in practice, the series names and colors (and number of series) will vary at runtime.
let seriesColors: [String: Color] = [
"A": Color.red,
"B": Color.green
]
struct ContentView: View {
var body: some View {
Chart(data) {
LineMark(
x: .value("X", $0.x),
y: .value("Y", $0.y),
series: .value("Series", $0.series))
}
.chartForegroundStyleScale(seriesColors) // Error: Cannot convert value of type '[String : Color]' to expected argument type 'KeyValuePairs<DataValue, S>'
}
}
Update: Making Color
conform to Plottable
and using foregroundStyle
didn't work. The chart treats all points as if they are from the first series it encounters. In this example, it plots a single brown line instead of a brown line and a purple line. Here's the conformance code:
let colorsBySeries: [String: Color] = ["A": .brown, "B": .purple]
extension Color: Plottable {
public init?(primitivePlottable: String) { self = colorsBySeries[primitivePlottable]! }
public var primitivePlottable: String { colorsBySeries.first { $0.value == self }!.key }
}
This is how I applied the style to LineMark
:
.foregroundStyle(colorsBySeries[$0.series]!)
You can use this method:
nonisolated func chartForegroundStyleScale<Domain, Range>( domain: Domain, range: Range, type: ScaleType? = nil ) -> some View where Domain : ScaleDomain, Range : ScaleRange, Range.VisualValue : ShapeStyle
Example:
Chart(data) {
LineMark(
x: .value("X", $0.x),
y: .value("Y", $0.y)
)
.foregroundStyle(by: .value("Series", $0.series))
}
.chartForegroundStyleScale(
domain: Array(series.keys),
range: Array(series.values)
)
let series = ["A":.green, "B": .red]
let data: [DataPoint] = [
DataPoint(series: "A", x: 1, y: 1),
DataPoint(series: "A", x: 2, y: 2),
DataPoint(series: "B", x: 2, y: 2),
DataPoint(series: "B", x: 1, y: 1)
]