Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a dynamic input to .chartForegroundStyleScale

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?

like image 293
Fleet Phil Avatar asked Feb 24 '26 03:02

Fleet Phil


2 Answers

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
    }
}

like image 110
carloe Avatar answered Feb 27 '26 02:02

carloe


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...

like image 41
Fleet Phil Avatar answered Feb 27 '26 03:02

Fleet Phil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!