Reputation: 1504
I have two data sets and I want to show them in the same chart.
Both data sets are time based (x axis) and with same time range. They have different units of measure and scaling (y axis).
Is it possible to show both data sets in the same chart, but with two different y axis definitions?
I have tried to overlay two independent charts but then the two charts are not aligned as I configure one with Y axis to the left and the other with Y axis to the right.
Any ideas how to make this happen?
Upvotes: 1
Views: 3615
Reputation: 12165
Here is an approach overlaying two separate Chart
s in a ZStack
. By this you don't have to renormalise any values.
To make the charts align they both use the same double Y axes (with respective dummy values). Also the foreground color of the 2nd X axis is set to .clear
to avoid overlaying type.
ZStack {
Chart {
ForEach(dataKosten, id: \.0) { item in
LineMark(
x: .value("Monat", item.0, unit: .month),
y: .value("Kosten", item.1)
)
}
.lineStyle(StrokeStyle.init(lineWidth: 3))
.foregroundStyle(.primary)
}
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { _ in
AxisTick()
AxisValueLabel(format: .dateTime.month(.wide), centered: true)
}
}
.chartYScale(domain: 0...250_000)
.chartYAxis {
AxisMarks(values: .stride(by: 25_000)) { _ in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .currency(code: "eur"), centered: false)
.foregroundStyle(Color.accentColor)
}
// dummy axis to align both charts
AxisMarks(position: .leading, values: [50]) { _ in
AxisGridLine()
AxisTick()
AxisValueLabel(centered: false)
.foregroundStyle(.clear)
}
}
Chart {
ForEach(dataFTE, id: \.0) { item in
BarMark(
x: .value("Monat", item.0, unit: .month),
y: .value("FTE", item.1)
)
}
.foregroundStyle(Color.secondary)
}
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { _ in
AxisTick()
AxisValueLabel(format: .dateTime.month(.wide), centered: true)
.foregroundStyle(.clear)
}
}
.chartYScale(domain: 0...50) // important, so the value of dummy axis is not used for scaling
.chartYAxis {
AxisMarks(position: .leading, values: Array(stride(from: 0, through: 50, by: 5))) { _ in
AxisGridLine()
AxisTick()
AxisValueLabel(centered: false)
}
// dummy axis to align both charts
AxisMarks(values: [100_000]) { _ in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .currency(code: "eur"), centered: false)
.foregroundStyle(.clear)
}
}
}
Upvotes: 1
Reputation: 423
Here is an example showing two different data sets on the same axis. You need to scale the data yourself though, so in this example I've done so manually with the adjustments to the pressure values, but you could also do this programatically.
Example here at github.com/jknlsn/MultipleDataSetSwiftChartsExample with lollipop detail popover, and slightly simplified example below.
import Charts
import SwiftUI
import WeatherKit
struct HourWeatherStruct {
var date: Date
var pressure: Double
var temperature: Double
var windSpeed: Double
}
let hours: [HourWeatherStruct] = [
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600),
pressure: 1015.0,
temperature: 18.2,
windSpeed: 6.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 2),
pressure: 1015.3,
temperature: 18.2,
windSpeed: 8.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 3),
pressure: 1015.9,
temperature: 18.2,
windSpeed: 9.4),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 4),
pressure: 1016.3,
temperature: 18.2,
windSpeed: 5.2),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 5),
pressure: 1016.3,
temperature: 18.2,
windSpeed: 12.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 6),
pressure: 1016.3,
temperature: 18.2,
windSpeed: 11.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 7),
pressure: 1017.3,
temperature: 18.2,
windSpeed: 10.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 8),
pressure: 1018.3,
temperature: 18.2,
windSpeed: 11.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 9),
pressure: 1018.3,
temperature: 18.2,
windSpeed: 9.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 10),
pressure: 1018.3,
temperature: 18.2,
windSpeed: 8.1),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 11),
pressure: 1017.3,
temperature: 18.2,
windSpeed: 19.9),
HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 12),
pressure: 1018.3,
temperature: 18.2,
windSpeed: 7.1),
]
struct InteractiveLollipopChartMinimal: View {
var body: some View {
Chart {
ForEach(hours, id: \.date) {
LineMark(
x: .value("Date", $0.date, unit: .hour),
y: .value("Wind Speed", $0.windSpeed)
)
.foregroundStyle(by: .value("Value", "Wind"))
LineMark(
x: .value("Date", $0.date, unit: .hour),
y: .value("Pressure", ($0.pressure - 1014) * 4)
)
.foregroundStyle(by: .value("Value", "Pressure"))
}
.lineStyle(StrokeStyle(lineWidth: 4.0))
.interpolationMethod(.catmullRom)
}
.chartForegroundStyleScale([
"Pressure": .purple,
"Wind": .teal
])
.chartXAxis {
AxisMarks(position: .bottom, values: .stride(by: .hour, count: 2)) {
_ in
AxisTick()
AxisGridLine()
AxisValueLabel(format: .dateTime.hour(), centered: true)
}
}
.chartYAxis {
AxisMarks(position: .leading, values: Array(stride(from: 0, through: 24, by: 4))){
axis in
AxisTick()
AxisGridLine()
AxisValueLabel("\(1014 + (axis.index * 1))", centered: false)
}
AxisMarks(position: .trailing, values: Array(stride(from: 0, through: 24, by: 4))){
axis in
AxisTick()
AxisGridLine()
AxisValueLabel("\(axis.index * 4)", centered: false)
}
}
}
}
struct InteractiveLollipopMinimal: View {
var body: some View {
List {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("Windspeed and Pressure")
.font(.callout)
.foregroundStyle(.secondary)
Text("\(hours.first?.date ?? Date(), format: .dateTime)")
.font(.title2.bold())
}
InteractiveLollipopChartMinimal()
.frame(height: 200)
}
.listRowSeparator(.hidden)
}
.listStyle(.plain)
.navigationBarTitle("Interactive Lollipop", displayMode: .inline)
}
}
Upvotes: 4