wire LSV peak detection into iOS AppState and LSVView with markers, labels, and toggle

This commit is contained in:
jess 2026-04-02 18:16:25 -07:00
parent 324c8a7f5a
commit 491befa8c6
2 changed files with 58 additions and 0 deletions

View File

@ -37,6 +37,8 @@ final class AppState {
// LSV // LSV
var lsvPoints: [LsvPoint] = [] var lsvPoints: [LsvPoint] = []
var lsvPeaks: [LsvPeak] = []
var lsvManualPeaks: Bool = false
var lsvTotal: UInt16 = 0 var lsvTotal: UInt16 = 0
var lsvStartV: String = "0" var lsvStartV: String = "0"
var lsvStopV: String = "500" var lsvStopV: String = "500"
@ -163,6 +165,9 @@ final class AppState {
case .lsvEnd: case .lsvEnd:
saveLsv() saveLsv()
if !lsvManualPeaks {
lsvPeaks = detectLsvPeaks(lsvPoints)
}
status = "LSV complete: \(lsvPoints.count) points" status = "LSV complete: \(lsvPoints.count) points"
case .ampStart(let vHold): case .ampStart(let vHold):

View File

@ -7,6 +7,7 @@ struct LSVView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
controlsRow controlsRow
peakLabels
Divider() Divider()
GeometryReader { geo in GeometryReader { geo in
if geo.size.width > 700 { if geo.size.width > 700 {
@ -42,6 +43,16 @@ struct LSVView: View {
Button("Start LSV") { state.startLSV() } Button("Start LSV") { state.startLSV() }
.buttonStyle(ActionButtonStyle(color: .green)) .buttonStyle(ActionButtonStyle(color: .green))
Button(state.lsvManualPeaks ? "Manual" : "Auto") {
state.lsvManualPeaks.toggle()
if state.lsvManualPeaks {
state.lsvPeaks.removeAll()
} else {
state.lsvPeaks = detectLsvPeaks(state.lsvPoints)
}
}
.font(.caption)
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical, 8) .padding(.vertical, 8)
@ -86,6 +97,18 @@ struct LSVView: View {
.foregroundStyle(Color.yellow) .foregroundStyle(Color.yellow)
.symbolSize(16) .symbolSize(16)
} }
ForEach(Array(state.lsvPeaks.enumerated()), id: \.offset) { _, peak in
PointMark(
x: .value("V", Double(peak.vMv)),
y: .value("I", Double(peak.iUa))
)
.foregroundStyle(peakColor(peak.kind))
.symbolSize(100)
.symbol(.diamond)
RuleMark(x: .value("V", Double(peak.vMv)))
.foregroundStyle(peakColor(peak.kind).opacity(0.3))
.lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 4]))
}
} }
.chartXAxisLabel("V (mV)") .chartXAxisLabel("V (mV)")
.chartYAxisLabel("I (uA)", position: .leading) .chartYAxisLabel("I (uA)", position: .leading)
@ -115,6 +138,36 @@ struct LSVView: View {
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
} }
@ViewBuilder
private var peakLabels: some View {
if !state.lsvPeaks.isEmpty {
HStack(spacing: 12) {
ForEach(Array(state.lsvPeaks.enumerated()), id: \.offset) { _, peak in
let label: String = {
switch peak.kind {
case .freeCl: return String(format: "Free: %.0fmV %.2fuA", peak.vMv, peak.iUa)
case .totalCl: return String(format: "Total: %.0fmV %.2fuA", peak.vMv, peak.iUa)
case .crossover: return String(format: "X-over: %.0fmV", peak.vMv)
}
}()
Text(label)
.font(.caption)
.foregroundStyle(peakColor(peak.kind))
}
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
}
}
private func peakColor(_ kind: PeakKind) -> Color {
switch kind {
case .freeCl: .green
case .totalCl: .orange
case .crossover: .purple
}
}
// MARK: - Table // MARK: - Table
private var lsvTable: some View { private var lsvTable: some View {