Reputation: 1445
I'm curious, is there a way of overriding the text that's displayed for each series in the legends of Swift Charts?
I have an application where the names of the series are not guaranteed to be unique, and I want the application to display the series as two distinct series even if it gets data with duplicate names. The user will be able to adjust to the situation if they don't like the series names matching.
To illustrate this, consider the following sample code:
private struct PlotPoint {
let x: Double
let y: Double
}
private struct PlotSeries: Identifiable {
let id: Int
let name: String
let points: [PlotPoint]
}
private let data: [PlotSeries] = [
PlotSeries(id: 0, name: "Blue Hulk", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y) }),
PlotSeries(id: 1, name: "Green Hulk", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y/2) })
]
struct LegendOverrideLabel: View {
var body: some View {
Chart {
ForEach(data) { series in
let plot = LinePlot(
series.points,
x: .value("x", \.x),
y: .value("y", \.y)
)
plot
.symbol(by: .value("Series", series.name))
.foregroundStyle(by: .value("Series", series.name))
}
}
.padding()
}
}
Here, we have the series names being given as "Blue Hulk" and "Green Hulk". Everything is working fine, and we get a chart like this:
But then, suppose that the user decides that because of the legend putting a coloured symbol next to the Series name, the "Blue" and "Green" are redundant. So they edit the data so the series name is just "Hulk" in both cases. But because our series are defined by the value we gave (.symbol(by: .value("Series", series.name))
, etc), Swift charts is identifying all the data as being in the same series, and we get this result:
So I would like a way of helping Swift Charts accept the longer unique names as the names of the series, but I would like to intercept those values before display in the legend and remove the colour adjectives at the front, to get a result like this:
I looked at defining my own custom Plottable
type, but that was a dead-end. I wish I could just use a UUID to identify the series (I know, UUID is not Plottable, so the uuidString for it), and provide a mapping to get from UUID to the user-friendly label to be displayed. But I don't see anything like that in the API. Is there another nice way of solving this?
Upvotes: 0
Views: 29
Reputation: 273510
I would include some invisible characters to make the legend labels technically different, but visually the same. For example, you can add \0
characters as a prefix.
To make this more convenient, you can replace the id
and name
in PlotSeries
with a single Plottable
property. You can then include the \0
prefixes in the Plottable
implementation.
private struct PlotSeries: Identifiable {
struct ID: Hashable, Plottable {
let number: Int
let name: String
init(number: Int, name: String) {
self.number = number
self.name = name
}
// remove the \0 prefixes
init?(primitivePlottable: String) {
let prefixCount = primitivePlottable.prefixMatch(of: /\0*/)?.output.count ?? 0
self.init(number: prefixCount, name: String(primitivePlottable.dropFirst(prefixCount)))
}
// add the \0 prefixes
var primitivePlottable: String {
String(repeating: "\0", count: number) + name
}
}
let id: ID
let points: [PlotPoint]
init(number: Int, name: String, points: [PlotPoint]) {
self.id = ID(number: number, name: name)
self.points = points
}
}
Usage:
private let data: [PlotSeries] = [
PlotSeries(number: 0, name: "Hulk", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y) }),
PlotSeries(number: 1, name: "Hulk", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y/2) })
]
struct LegendOverrideLabel: View {
var body: some View {
Chart {
ForEach(data) { series in
let plot = LinePlot(
series.points,
x: .value("x", \.x),
y: .value("y", \.y)
)
plot
.symbol(by: .value("Series", series.id))
.foregroundStyle(by: .value("Series", series.id))
}
}
.padding()
}
}
Upvotes: 1