I'm curious, does anyone know how to combine the use of LinePlot
and PointPlot
with per-series styling? I'm getting unpredictable results, and I wonder if I'm doing something wrong.
Here's a small example, taking things only slightly further from a very basic scenario:
struct PlotPoint {
let x: Double
let y: Double
}
// prepopulate a table with the data we want to plot
let cosData: [PlotPoint] = stride(from: -3, to: 3.01, by: 0.5).map { x in
PlotPoint(x: x, y: cos(x))
}
let sinData: [PlotPoint] = stride(from: -3, to: 3.01, by: 0.5).map { x in
PlotPoint(x: x, y: sin(x))
}
struct LinePlotView: View {
var body: some View {
if #available(iOS 18.0, *) {
Chart {
LinePlot(sinData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=sin(x)"))
PointPlot(sinData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=sin(x)"))
.symbol(by: .value("expression", "y=sin(x)"))
LinePlot(cosData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=cos(x)"))
PointPlot(cosData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=cos(x)"))
.symbol(by: .value("expression", "y=cos(x)"))
}
.chartXScale(domain: -3 ... 3)
.chartYScale(domain: -1.5 ... 1.5)
.padding()
} else {
Text("Not available on iOS < 18")
}
}
}
The result is not entirely unreasonable, but if you look closely, you will see that the legend indicates that the symbol for y=cos(x) is a cross, while in the graph it is using a square. In more complicated scenarios, I think multiple symbols are being used at each point, superimposed on each other. What should I do differently for this to work correctly?
LinePlot
s can have symbols too! It's just that they are hollow (not filled with solid color). If you only have these two LinePlot
s
Chart {
LinePlot(cosData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=cos(x)"))
.symbol(by: .value("expression", "y=cos(x)"))
LinePlot(sinData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=sin(x)"))
.symbol(by: .value("expression", "y=sin(x)"))
}
You get:
From my experiments, it seems like the symbols are being applied procedurally, according to the order in which you wrote your ChartContent
s.
In your code, you first have your sin line, then the sin points, and here is where you set a symbol. After that, you have the cos line. Here, you did not set a symbol, so the symbol you previously set (for the sin points) are used. Then you have the cos points which use the cross symbol.
The "squares" that you are seeing are not actually squares - they are the hollow circle symbols for the cos line ("inherited" from the sin points), with the cross symbols for the cos points overlaid on top.
If you don't like the symbols being hollow, you can move all the PointPlot
s where you have set the symbol, to after all the LinePlot
s, so the line plots are not affected.
P.S.
This effect can be exaggerated by adding a size
property to your data, and using symbolSize
.
struct PlotPoint {
let x: Double
let y: Double
let size: CGFloat
}
let cosData: [PlotPoint] = stride(from: -3, to: 3.01, by: 0.5).map { x in
PlotPoint(x: x, y: cos(x), size: 1000)
}
let sinData: [PlotPoint] = stride(from: -3, to: 3.01, by: 0.5).map { x in
PlotPoint(x: x, y: sin(x), size: 1)
}
Chart {
LinePlot(sinData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=sin(x)"))
PointPlot(sinData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=sin(x)"))
.symbol(by: .value("expression", "y=sin(x)"))
.symbolSize(\.size)
LinePlot(cosData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=cos(x)"))
PointPlot(cosData,
x: .value("x", \.x),
y: .value("y", \.y)
)
.foregroundStyle(by: .value("expression", "y=cos(x)"))
.symbol(by: .value("expression", "y=cos(x)"))
}
Symbol size of a PointPlot
can't actually be changed, so .symbolSize(\.size)
has no effect on point plots. Symbol size of LinePlot
s can be changed, and so the cos line gets affected by .symbolSize(\.size)
put on the sin points, simply because the cos line is ordered after the sin points, and it has no set symbol size.