Created
February 13, 2023 22:13
-
-
Save ppeelen/95366a0f6a229ed104aff1738b9a3f41 to your computer and use it in GitHub Desktop.
The end result of the tutorial for multi data type in Swift Chart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import PlaygroundSupport | |
import Charts | |
// MARK: Chart implementation | |
struct ChartView: View { | |
@Binding var consumption: [Consumption] | |
var body: some View { | |
VStack(alignment: .leading) { | |
let strideBy: Double = 6 | |
let costs = consumption.map { $0.cost } | |
let costMin = costs.min()! | |
let costMax = costs.max()! | |
let consumptions = consumption.map { $0.consumption } | |
let consumptionMin = consumptions.min()! | |
let consumptionMax = consumptions.max()! | |
Text("Cost") | |
.font(.footnote) | |
Chart(consumption) { item in | |
LineMark( | |
x: .value("Hour", item.from), | |
y: .value("Price", item.cost / costMax) | |
) | |
.interpolationMethod(.catmullRom) | |
.foregroundStyle(.green) | |
.lineStyle(StrokeStyle(lineWidth: 3)) | |
.foregroundStyle(by: .value("Value", "Cost")) | |
LineMark( | |
x: .value("Hour", item.from), | |
y: .value("Consumption", item.consumption / consumptionMax) | |
) | |
.interpolationMethod(.catmullRom) | |
.foregroundStyle(.blue) | |
.lineStyle(StrokeStyle(lineWidth: 3)) | |
.foregroundStyle(by: .value("Value", "Consumption")) | |
} | |
.chartForegroundStyleScale([ | |
"Consumption": .blue, | |
"Cost": .green, | |
]) | |
.chartXAxis { | |
AxisMarks(values: .stride(by: .hour, count: 4)) { _ in | |
AxisValueLabel(format: .dateTime.hour(.twoDigits(amPM: .abbreviated))) | |
} | |
} | |
.chartYAxis { | |
let defaultStride = Array(stride(from: 0, to: 1, by: 1.0/strideBy)) | |
let costsStride = Array(stride(from: costMin, | |
through: costMax, | |
by: (costMax - costMin)/strideBy)) | |
AxisMarks(position: .trailing, values: defaultStride) { axis in | |
AxisGridLine() | |
let value = costsStride[axis.index] | |
AxisValueLabel("\(String(format: "%.2F", value)) kr", centered: false) | |
} | |
let consumptionStride = Array(stride(from: consumptionMin, | |
through: consumptionMax, | |
by: (consumptionMax - consumptionMin)/strideBy)) | |
AxisMarks(position: .leading, values: defaultStride) { axis in | |
AxisGridLine() | |
let value = consumptionStride[axis.index] | |
AxisValueLabel("\(String(format: "%.2F", value)) kWh", centered: false) | |
} | |
} | |
.padding(.bottom, 20) | |
} | |
} | |
} | |
struct MainView: View { | |
@State private var consumption: [Consumption] | |
init(consumption: [Consumption]) { | |
self.consumption = consumption | |
} | |
var body: some View { | |
VStack { | |
ChartView(consumption: $consumption) | |
.frame(minWidth: 450, minHeight: 250) | |
.padding() | |
} | |
.environment(\.colorScheme, .dark) | |
.background(.black) | |
} | |
} | |
// MARK: - Setting up chart data | |
let data = """ | |
[{"id":"1","from":"2023-02-12T00:00:00.000+01:00","to":"2023-02-12T01:00:00.000+01:00","cost":2.768758875,"consumption":2.817,"consumptionUnit":"kWh","currency":"SEK"},{"id":"2","from":"2023-02-12T01:00:00.000+01:00","to":"2023-02-12T02:00:00.000+01:00","cost":49.00516435,"consumption":10.794,"consumptionUnit":"kWh","currency":"SEK"},{"id":"3","from":"2023-02-12T02:00:00.000+01:00","to":"2023-02-12T03:00:00.000+01:00","cost":47.8345572125,"consumption":9.967,"consumptionUnit":"kWh","currency":"SEK"},{"id":"4","from":"2023-02-12T03:00:00.000+01:00","to":"2023-02-12T04:00:00.000+01:00","cost":48.62424185,"consumption":10.026,"consumptionUnit":"kWh","currency":"SEK"},{"id":"5","from":"2023-02-12T04:00:00.000+01:00","to":"2023-02-12T05:00:00.000+01:00","cost":46.695987275,"consumption":10.619,"consumptionUnit":"kWh","currency":"SEK"},{"id":"6","from":"2023-02-12T05:00:00.000+01:00","to":"2023-02-12T06:00:00.000+01:00","cost":50.915838875,"consumption":11.201,"consumptionUnit":"kWh","currency":"SEK"},{"id":"7","from":"2023-02-12T06:00:00.000+01:00","to":"2023-02-12T07:00:00.000+01:00","cost":52.9999920625,"consumption":11.317,"consumptionUnit":"kWh","currency":"SEK"},{"id":"8","from":"2023-02-12T07:00:00.000+01:00","to":"2023-02-12T08:00:00.000+01:00","cost":42.7862852,"consumption":10.698,"consumptionUnit":"kWh","currency":"SEK"},{"id":"9","from":"2023-02-12T08:00:00.000+01:00","to":"2023-02-12T09:00:00.000+01:00","cost":37.242389225,"consumption":9.377,"consumptionUnit":"kWh","currency":"SEK"},{"id":"10","from":"2023-02-12T09:00:00.000+01:00","to":"2023-02-12T10:00:00.000+01:00","cost":32.778883225,"consumption":8.171,"consumptionUnit":"kWh","currency":"SEK"},{"id":"11","from":"2023-02-12T10:00:00.000+01:00","to":"2023-02-12T11:00:00.000+01:00","cost":21.0670651,"consumption":4.436,"consumptionUnit":"kWh","currency":"SEK"},{"id":"12","from":"2023-02-12T11:00:00.000+01:00","to":"2023-02-12T12:00:00.000+01:00","cost":0.98445655,"consumption":2.156,"consumptionUnit":"kWh","currency":"SEK"},{"id":"13","from":"2023-02-12T12:00:00.000+01:00","to":"2023-02-12T13:00:00.000+01:00","cost":0.68222785,"consumption":1.523,"consumptionUnit":"kWh","currency":"SEK"},{"id":"14","from":"2023-02-12T13:00:00.000+01:00","to":"2023-02-12T14:00:00.000+01:00","cost":0.5496218,"consumption":1.288,"consumptionUnit":"kWh","currency":"SEK"},{"id":"15","from":"2023-02-12T14:00:00.000+01:00","to":"2023-02-12T15:00:00.000+01:00","cost":0.5298075,"consumption":1.269,"consumptionUnit":"kWh","currency":"SEK"},{"id":"16","from":"2023-02-12T15:00:00.000+01:00","to":"2023-02-12T16:00:00.000+01:00","cost":0.677347125,"consumption":1.61,"consumptionUnit":"kWh","currency":"SEK"},{"id":"17","from":"2023-02-12T16:00:00.000+01:00","to":"2023-02-12T17:00:00.000+01:00","cost":1.1193823,"consumption":2.488,"consumptionUnit":"kWh","currency":"SEK"},{"id":"18","from":"2023-02-12T17:00:00.000+01:00","to":"2023-02-12T18:00:00.000+01:00","cost":1.155690375,"consumption":2.445,"consumptionUnit":"kWh","currency":"SEK"},{"id":"19","from":"2023-02-12T18:00:00.000+01:00","to":"2023-02-12T19:00:00.000+01:00","cost":1.15172305,"consumption":2.468,"consumptionUnit":"kWh","currency":"SEK"},{"id":"20","from":"2023-02-12T19:00:00.000+01:00","to":"2023-02-12T20:00:00.000+01:00","cost":0.81017805,"consumption":1.803,"consumptionUnit":"kWh","currency":"SEK"},{"id":"21","from":"2023-02-12T20:00:00.000+01:00","to":"2023-02-12T21:00:00.000+01:00","cost":0.7772117375,"consumption":1.837,"consumptionUnit":"kWh","currency":"SEK"},{"id":"22","from":"2023-02-12T21:00:00.000+01:00","to":"2023-02-12T22:00:00.000+01:00","cost":0.6472297625,"consumption":1.567,"consumptionUnit":"kWh","currency":"SEK"},{"id":"23","from":"2023-02-12T22:00:00.000+01:00","to":"2023-02-12T23:00:00.000+01:00","cost":0.5633025,"consumption":1.416,"consumptionUnit":"kWh","currency":"SEK"},{"id":"24","from":"2023-02-12T23:00:00.000+01:00","to":"2023-02-13T00:00:00.000+01:00","cost":0.4434144,"consumption":1.292,"consumptionUnit":"kWh","currency":"SEK"}] | |
""".data(using: .utf8)! | |
let decoder = JSONDecoder() | |
decoder.dateDecodingStrategyFormatters = [ DateFormatter.json ] | |
let consumption = try decoder.decode([Consumption].self, from: data) | |
// MARK: - View | |
let view = MainView(consumption: consumption) | |
PlaygroundPage.current.setLiveView(view) | |
// MARK: - Helpers | |
struct Consumption: Codable, Identifiable { | |
let id: String | |
let from: Date | |
let to: Date | |
let cost: Double | |
let consumption: Double | |
let consumptionUnit: String | |
let currency: String | |
} | |
extension DateFormatter { | |
static let json: DateFormatter = { | |
var dateFormatter = DateFormatter() | |
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS+01:00" | |
return dateFormatter | |
}() | |
} | |
extension JSONDecoder { | |
var dateDecodingStrategyFormatters: [DateFormatter]? { | |
@available(*, unavailable, message: "This variable is meant to be set only") | |
get { return nil } | |
set { | |
guard let formatters = newValue else { return } | |
self.dateDecodingStrategy = .custom { decoder in | |
let container = try decoder.singleValueContainer() | |
let dateString = try container.decode(String.self) | |
for formatter in formatters { | |
if let date = formatter.date(from: dateString) { | |
return date | |
} | |
} | |
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)") | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment