Reputation: 461
In Swift Charts the signature for chartForegroundStyleScale
to set the ShapeStyle for each data series is:
func chartForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) -> some View where DataValue : Plottable, S : ShapeStyle
The KeyValuePairs
initialiser (init(dictionaryLiteral: (Key, Value)...)
) only takes a variadic parameter so any attempt to initialise a foreground style from an array (in my case <String, Color>
) results in the error:
Cannot pass array of type '[(String, Color)]' as variadic arguments of type '(String, Color)'
In my application the names of the chart series are set dynamically from the data so although I can generate a [String : Color]
dictionary or an array of (String, Color)
tuples I can't see that it's possible to pass either of these into chartForegroundStyleScale
? Unless I'm missing something this seems like a odd limitation in Swift charts that the series names need to be hard coded for this modifier?
Upvotes: 12
Views: 2901
Reputation: 1009
Finally found it. You can use chartForegroundStyleScale(domain:mapping:)
. With this you pass in the "domain", all values your key can have and a callback that maps a key to a style.
This is preferable to the upvoted answer, in cases where you may not have all data types in the chart at each x value. For instance, a chart that has many stacked bar values that don't have each value at each x position.
Upvotes: 1
Reputation: 21
You can also write the function to the .chartForegroundStyleScale(range:)
itself:
.chartForegroundStyleScale({
if something {
["MyString": myColor]
} else {
[:]
}
}())
Upvotes: -1
Reputation: 1540
You could also pass an array of colors to .chartForegroundStyleScale(range:)
. As long as you add the colors to the array in the same order you add your graph marks it should work fine.
Not incredibly elegant either, but this approach works with an arbitrary number or entries.
struct GraphItem: Identifiable {
var id = UUID()
var label: String
var value: Double
var color: Color
}
struct ContentView: View {
let data = [
GraphItem(label: "Apples", value: 2, color: .red),
GraphItem(label: "Pears", value: 3, color: .yellow),
GraphItem(label: "Melons", value: 5, color: .green)
]
var body: some View {
Chart {
ForEach(data, id: \.label) { item in
BarMark(
x: .value("Count", item.value),
y: .value("Fruit", item.label)
)
.foregroundStyle(by: .value("Fruit", item.label))
}
}
.chartForegroundStyleScale(range: graphColors(for: data))
}
func graphColors(for input: [GraphItem]) -> [Color] {
var returnColors = [Color]()
for item in input {
returnColors.append(item.color)
}
return returnColors
}
}
Upvotes: 13
Reputation: 461
OK I've found an approach that works as long as an arbitrary limitation to the number of entries is acceptable (example below with max size of 4:
func keyValuePairs<S, T>(_ from: [(S, T)]) -> KeyValuePairs<S, T> {
switch from.count {
case 1: return [ from[0].0 : from[0].1 ]
case 2: return [ from[0].0 : from[0].1, from[1].0 : from[1].1 ]
case 3: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1 ]
default: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1, from[3].0 : from[3].1 ]
}
In my case I know that there won't be more than 20 mappings so this func can just be extended to accommodate that number. Not ideal, but it works...
Upvotes: 2