1 contributor
//
// MeasurementChartView.swift
// USB Meter
//
// Created by Bogdan Timofte on 06/05/2020.
// Copyright © 2020 Bogdan Timofte. All rights reserved.
//
import SwiftUI
struct MeasurementChartView: View {
@EnvironmentObject private var measurements: Measurements
@State var displayVoltage: Bool = false
@State var displayCurrent: Bool = false
@State var displayPower: Bool = true
let xLabels: Int = 4
let yLabels: Int = 4
var body: some View {
Group {
//if measurements.power.points.count > 0 {
VStack {
HStack {
Button( action: {
self.displayVoltage.toggle()
if self.displayVoltage {
self.displayPower = false
}
} ) { Text("Voltage") }
.asEnableFeatureButton(state: displayVoltage)
Button( action: {
self.displayCurrent.toggle()
if self.displayCurrent {
self.displayPower = false
}
} ) { Text("Current") }
.asEnableFeatureButton(state: displayCurrent)
Button( action: {
self.displayPower.toggle()
if self.displayPower {
self.displayCurrent = false
self.displayVoltage = false
}
} ) { Text("Power") }
.asEnableFeatureButton(state: displayPower)
}
.padding(.bottom, 5)
if measurements.current.context.isValid {
VStack {
GeometryReader { geometry in
HStack {
Group { // MARK: Left Legend
if self.displayPower {
self.yAxisLabelsView(geometry: geometry, context: self.measurements.power.context, measurementUnit: "W")
.withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .red, opacity: 0.5)
} else if self.displayVoltage {
self.yAxisLabelsView(geometry: geometry, context: self.measurements.voltage.context, measurementUnit: "V")
.withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .green, opacity: 0.5)
}
else if self.displayCurrent {
self.yAxisLabelsView(geometry: geometry, context: self.measurements.current.context, measurementUnit: "A")
.withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .blue, opacity: 0.5)
}
}
ZStack { // MARK: Graph
if self.displayPower {
Chart(strokeColor: .red)
.environmentObject(self.measurements.power)
.opacity(0.5)
} else {
if self.displayVoltage{
Chart(strokeColor: .green)
.environmentObject(self.measurements.voltage)
.opacity(0.5)
}
if self.displayCurrent{
Chart(strokeColor: .blue)
.environmentObject(self.measurements.current)
.opacity(0.5)
}
}
// MARK: Grid
self.horizontalGuides()
self.verticalGuides()
}
.withRoundedRectangleBackground( cornerRadius: 0, foregroundColor: .primary, opacity: 0.06 )
Group { // MARK: Right Legend
self.yAxisLabelsView(geometry: geometry, context: self.measurements.current.context, measurementUnit: "A")
.foregroundColor(self.displayVoltage && self.displayCurrent ? .primary : .clear)
.withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .blue, opacity: 0.5)
}
}
}
xAxisLabelsView(context: self.measurements.current.context)
.padding(.horizontal, 10)
}
}
else {
Text("Nothing to show!")
}
}
.padding(10)
.font(.footnote)
.frame(maxWidth: .greatestFiniteMagnitude)
.withRoundedRectangleBackground( cornerRadius: 15, foregroundColor: .primary, opacity: 0.03 )
.padding()
}
}
// MARK: Cu iteratie nu functioneaza deoarece view-ul din bucla nu este reimprospatat la modificare obiectului observat
fileprivate func xAxisLabelsView(context: ChartContext) -> some View {
var timeFormat: String?
switch context.size.width {
case 0..<3600: timeFormat = "HH:mm:ss"
case 3600...86400: timeFormat = "HH:mm"
default: timeFormat = "E:HH:MM"
}
return HStack {
ForEach (1...xLabels, id: \.self) { i in
Group {
Text( "\(Date(timeIntervalSince1970: context.xAxisLabel(for: i, of: self.yLabels)).format(as: timeFormat!))" )
.fontWeight(.semibold)
if i < self.xLabels {
Spacer()
}
}
}
}
}
fileprivate func yAxisLabelsView(geometry: GeometryProxy, context: ChartContext, measurementUnit: String) -> some View {
return ZStack {
VStack {
Text("\(context.yAxisLabel(for: 4, of: 4).format(fractionDigits: 2))")
.fontWeight(.semibold)
.padding(.top, geometry.size.height*Constants.chartUnderscan/2 )
Spacer()
ForEach (1..<yLabels-1, id: \.self) { i in
Group {
Text("\(context.yAxisLabel(for: self.yLabels-i, of: self.yLabels).format(fractionDigits: 2))")
.fontWeight(.semibold)
Spacer()
}
}
Text("\(context.yAxisLabel(for: 1, of: yLabels).format(fractionDigits: 2))")
.fontWeight(.semibold)
.padding(.bottom, geometry.size.height*Constants.chartUnderscan/2 )
}
VStack {
Text(measurementUnit)
.fontWeight(.bold)
.padding(.top, 5)
Spacer()
}
}
}
fileprivate func horizontalGuides() -> some View {
GeometryReader { geometry in
Path { path in
let pading = geometry.size.height*Constants.chartUnderscan
let height = geometry.size.height - pading
let border = pading/2
for i: CGFloat in stride(from: 0, through: CGFloat(self.yLabels-1), by: 1) {
path.addLine(from: CGPoint(x: 0, y: border + height*i/CGFloat(self.yLabels-1 )), to: CGPoint(x: geometry.size.width, y: border + height*i/CGFloat(self.yLabels-1)))
}
}.stroke(lineWidth: 0.25)
}
}
fileprivate func verticalGuides() -> some View {
GeometryReader { geometry in
Path { path in
for i: CGFloat in stride(from: 1, through: CGFloat(self.xLabels-1), by: 1) {
path.move(to: CGPoint(x: geometry.size.width*i/CGFloat(self.xLabels-1), y: 0) )
path.addLine(to: CGPoint(x: path.currentPoint!.x, y: geometry.size.height) )
}
}.stroke(lineWidth: 0.25)
}
}
}
struct Chart : View {
@EnvironmentObject private var measurement: Measurements.Measurement
var areaChart: Bool = false
var strokeColor: Color = .black
var body : some View {
GeometryReader { geometry in
if self.areaChart {
self.path( geometry: geometry )
.fill(LinearGradient( gradient: .init(colors: [Color.red, Color.green]), startPoint: .init(x: 0.5, y: 0.1), endPoint: .init(x: 0.5, y: 0.9)))
} else {
self.path( geometry: geometry )
.stroke(self.strokeColor, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round))
}
}
}
fileprivate func path(geometry: GeometryProxy) -> Path {
return Path { path in
let firstPoint = measurement.context.placeInRect(point: measurement.points.first!.point())
path.move(to: CGPoint(x: firstPoint.x * geometry.size.width, y: firstPoint.y * geometry.size.height ) )
for item in measurement.points.map({ measurement.context.placeInRect(point: $0.point()) }) {
path.addLine(to: CGPoint(x: item.x * geometry.size.width, y: item.y * geometry.size.height ) )
}
if self.areaChart {
let lastPointX = measurement.context.placeInRect(point: CGPoint(x: measurement.points.last!.point().x, y: measurement.context.origin.y ))
let firstPointX = measurement.context.placeInRect(point: CGPoint(x: measurement.points.first!.point().x, y: measurement.context.origin.y ))
path.addLine(to: CGPoint(x: lastPointX.x * geometry.size.width, y: lastPointX.y * geometry.size.height ) )
path.addLine(to: CGPoint(x: firstPointX.x * geometry.size.width, y: firstPointX.y * geometry.size.height ) )
// MARK: Nu e nevoie. Fill inchide automat calea
// path.closeSubpath()
}
}
}
}