EIS-BLE-S3/cue-ios/CueIOS/Views/AmpView.swift

143 lines
5.3 KiB
Swift

import SwiftUI
import Charts
struct AmpView: View {
@Bindable var state: AppState
var body: some View {
VStack(spacing: 0) {
controlsRow
Divider()
GeometryReader { geo in
if geo.size.width > 700 {
HSplitLayout(ratio: 0.55) {
amperogramPlot
} trailing: {
ampTable
}
} else {
ScrollView {
VStack(spacing: 12) {
amperogramPlot.frame(height: 350)
ampTable.frame(height: 300)
}
.padding()
}
}
}
}
}
// MARK: - Controls
private var controlsRow: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
LabeledField("V hold mV", text: $state.ampVHold, width: 80)
LabeledField("Interval ms", text: $state.ampInterval, width: 80)
LabeledField("Duration s", text: $state.ampDuration, width: 80)
LabeledPicker("RTIA", selection: $state.ampRtia, items: LpRtia.allCases) { $0.label }
.frame(width: 120)
if state.ampRunning {
Button("Stop") { state.stopAmp() }
.buttonStyle(ActionButtonStyle(color: .red))
} else {
Button("Start Amp") { state.startAmp() }
.buttonStyle(ActionButtonStyle(color: .green))
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
}
// MARK: - Plot
private var amperogramPlot: some View {
Group {
if state.ampPoints.isEmpty {
Text("No data")
.foregroundStyle(Color(white: 0.4))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(0.3))
} else {
let ampColor = Color(red: 0.6, green: 0.6, blue: 1.0)
PlotContainer(title: "") {
Chart {
if let ref = state.ampRef {
ForEach(Array(ref.enumerated()), id: \.offset) { _, pt in
LineMark(
x: .value("t", Double(pt.tMs)),
y: .value("I", Double(pt.iUa))
)
.foregroundStyle(Color.gray.opacity(0.5))
.lineStyle(StrokeStyle(lineWidth: 1.5))
}
}
ForEach(Array(state.ampPoints.enumerated()), id: \.offset) { _, pt in
LineMark(
x: .value("t", Double(pt.tMs)),
y: .value("I", Double(pt.iUa))
)
.foregroundStyle(ampColor)
.lineStyle(StrokeStyle(lineWidth: 2))
}
ForEach(Array(state.ampPoints.enumerated()), id: \.offset) { _, pt in
PointMark(
x: .value("t", Double(pt.tMs)),
y: .value("I", Double(pt.iUa))
)
.foregroundStyle(ampColor)
.symbolSize(16)
}
}
.chartXAxisLabel("t (ms)")
.chartYAxisLabel("I (uA)", position: .leading)
.chartXAxis {
AxisMarks { _ in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray.opacity(0.3))
AxisValueLabel()
.font(.caption2)
.foregroundStyle(.secondary)
}
}
.chartYAxis {
AxisMarks(position: .leading) { _ in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray.opacity(0.3))
AxisValueLabel()
.font(.caption2)
.foregroundStyle(ampColor)
}
}
.padding(8)
}
}
}
.background(Color.black.opacity(0.3))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
// MARK: - Table
private var ampTable: some View {
MeasurementTable(
columns: [
MeasurementColumn(header: "t (ms)", width: 80, alignment: .trailing),
MeasurementColumn(header: "I (uA)", width: 90, alignment: .trailing),
],
rows: state.ampPoints,
cellText: { pt, col in
switch col {
case 0: String(format: "%.1f", pt.tMs)
case 1: String(format: "%.3f", pt.iUa)
default: ""
}
}
)
}
}