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: "" } } ) } }