I am working on a SwiftUI app where I need to display a bar chart using the Charts framework. My chart displays a series of TokenHolder objects, and I want to assign a unique color to each BarMark in the chart.I want to color each bar differently based on the label generated in generateAlphabetLabels function. However, I'm not sure how to apply different colors to each BarMark in the Chart view. I tried using .chartForegroundStyleScale, but it doesn't seem to work as expected. How can I assign a unique color to each bar in the chart based on its label or any other unique property of the TokenHolder?
Here is my CryptoChartView struct:
import SwiftUI
import Charts
struct CryptoChartView: View {
let cryptoData: [TokenHolder]
func generateAlphabetLabels(count: Int) -> [String] {
let letters = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
var labels = [String]()
for i in 0..<count {
let prefixIndex = i / letters.count
let suffixIndex = i % letters.count
let prefix = prefixIndex > 0 ? String(letters[prefixIndex - 1]) : ""
let suffix = String(letters[suffixIndex])
labels.append(prefix + suffix)
}
return labels
}
var body: some View {
let labels = generateAlphabetLabels(count: cryptoData.count)
Chart {
ForEach(Array(zip(labels, cryptoData)), id: \.1.id) { (label, holder) in
BarMark(
x: .value("Wallet", "\(label)"),
y: .value("Amount", Double(holder.amount) ?? 0)
)
}
}
.chartScrollableAxes(.horizontal)
.chartYAxis {
AxisMarks(values: .automatic) { value in
AxisGridLine(centered: true)
AxisTick()
AxisValueLabel() {
if let intValue = value.as(Int.self) {
Text(String.formatAmount(String(intValue)))
}
}
}
}
}
}
static let sampleData = [
TokenHolder(amount: "500000000000.000000000000000000", walletAddress: "0x1111111111111111111111111111111111111111", usdAmount: "75000000.00"),
TokenHolder(amount: "450000000000.000000000000000000", walletAddress: "0x2222222222222222222222222222222222222222", usdAmount: "67500000.00"),
TokenHolder(amount: "400000000000.000000000000000000", walletAddress: "0x3333333333333333333333333333333333333333", usdAmount: "60000000.00"),
TokenHolder(amount: "350000000000.000000000000000000", walletAddress: "0x4444444444444444444444444444444444444444", usdAmount: "52500000.00"),
TokenHolder(amount: "300000000000.000000000000000000", walletAddress: "0x5555555555555555555555555555555555555555", usdAmount: "45000000.00"),
TokenHolder(amount: "250000000000.000000000000000000", walletAddress: "0x6666666666666666666666666666666666666666", usdAmount: "37500000.00"),
TokenHolder(amount: "200000000000.000000000000000000", walletAddress: "0x7777777777777777777777777777777777777777", usdAmount: "30000000.00"),
TokenHolder(amount: "150000000000.000000000000000000", walletAddress: "0x8888888888888888888888888888888888888888", usdAmount: "22500000.00"),
I don't know if it's the best solution, but it works.
You can add each color to your TokenHolder item e.g.
struct TokenHolder: Identifiable, Codable {
var id: String { walletAddress }
let amount: String
let walletAddress: String
let usdAmount: String
let color: Color
}
and add some essential staff to make it codable, identifiable and comparable to works well with charts e.g.
extension TokenHolder: Comparable {
static func < (lhs: TokenHolder, rhs: TokenHolder) -> Bool {
lhs.amount < rhs.amount
}
static func == (lhs: TokenHolder, rhs: TokenHolder) -> Bool {
lhs.amount == rhs.amount
}
}
extension Color: Codable {
enum CodingKeys: String, CodingKey {
case red, green, blue
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let r = try container.decode(Double.self, forKey: .red)
let g = try container.decode(Double.self, forKey: .green)
let b = try container.decode(Double.self, forKey: .blue)
self.init(red: r, green: g, blue: b)
}
public func encode(to encoder: Encoder) throws {
guard let colorComponents = self.colorComponents else {
return
}
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(colorComponents.red, forKey: .red)
try container.encode(colorComponents.green, forKey: .green)
try container.encode(colorComponents.blue, forKey: .blue)
}
}
fileprivate extension Color {
var colorComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
guard UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) else {
return nil
}
return (r, g, b, a)
}
}
And after that u can add to your BarMark
modifier .foregroundStyle(holder.color)
.