How do I obtain colour imagery, or more generally, styling information for each legend entry to construct a custom legend in a chart in Swift Charts?
I have a chart here, with a legend positioned on the right, but using the content:
argument of chartLegend
does not pass any info into the closure to use. I would like to wrap the legend into a scroll view, so when there are too many entries, the chart will appear correctly on the screen, and the user can scroll through the legend below the chart.
Chart(points, id: \.self) { point in
LineMark(
x: .value("time/s", point.timestamp),
y: .value("potential/mV", point.potential)
)
.foregroundStyle(by: .value("Electrode", point.electrode.symbol))
}
.chartLegend(position: .bottom)
// ...
Here is the chart with too many legend entries interfering with the chart sizing, resulting in cropping:
And here is the chart with only a few entries so that the chart is sized correctly, with no cropping, and the legend has text to discern between the electrodes they represent:
Any help is much appreciated.
I'm not sure what you originally tried, but this works in Swift 5.7. Overriding the legend with .chartLegend() removes the legend colors, so I added func colorFor(symbol:) to cycle through the default legend colors. Note: "symbols" is an array of the electrode names, used in the legend.
import SwiftUI
import Charts
struct ContentView: View {
let (symbols, points) = Point.sampleData()
let colors = [Color.blue, .green, .orange, .purple, .red, .cyan, .yellow]
var body: some View {
Chart(points, id: \.self) { point in
LineMark(
x: .value("time/s", point.timestamp),
y: .value("potential/mV", point.potential)
)
.foregroundStyle(by: .value("Electrode", point.electrode.symbol))
}
.chartLegend(position: .bottom) {
ScrollView(.horizontal) {
HStack {
ForEach(symbols, id: \.self) { symbol in
HStack {
BasicChartSymbolShape.circle
.foregroundColor(colorFor(symbol: symbol))
.frame(width: 8, height: 8)
Text(symbol)
.foregroundColor(.gray)
.font(.caption)
}
}
}
.padding()
}
}
.chartXScale(domain: 0...0.5)
.chartYAxis {
AxisMarks(position: .leading)
}
.padding()
}
func colorFor(symbol: String) -> Color {
let symbolIndex = symbols.firstIndex(of: symbol) ?? 0
return colors[symbolIndex % colors.count] // wrap-around colors
}
}
Here's the code I used to generate sample data, in case you were wondering.
import Foundation
struct Point: Hashable {
var timestamp: Double
var potential: Double
var electrode: Electrode
struct Electrode: Hashable {
var symbol: String
}
static func sampleData() -> ([String], [Point]) {
let numElectrodes = 20
let numTimes = 100
let maxTime = 0.5
let amplitude = 80.0
let frequency = 2.0 // Hz
let bias = 30.0
var symbols = [String]()
var points = [Point]()
for e in 0..<numElectrodes {
let phase = Double.random(in: 0...10)
let symbol = "C\(e + 1)"
symbols.append(symbol)
for t in 0..<numTimes {
let time = maxTime * Double(t) / Double(numTimes)
let potential = amplitude * sin(2 * Double.pi * frequency * time - phase) + bias
points.append(Point(timestamp: time, potential: potential, electrode: .init(symbol: symbol)))
}
}
return (symbols, points)
}
static func == (lhs: Point, rhs: Point) -> Bool {
lhs.electrode == rhs.electrode && lhs.timestamp == rhs.timestamp
}
}