iOS: add LSV sweep and voltammogram to chlorine tab
This commit is contained in:
parent
73899beaa5
commit
34f8bda191
|
|
@ -7,11 +7,13 @@ struct ChlorineView: View {
|
|||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
controlsRow
|
||||
clPeakLabels
|
||||
Divider()
|
||||
GeometryReader { geo in
|
||||
if geo.size.width > 700 {
|
||||
HSplitLayout(ratio: 0.55) {
|
||||
VStack(spacing: 4) {
|
||||
voltammogramPlot
|
||||
resultBanner
|
||||
chlorinePlot
|
||||
}
|
||||
|
|
@ -21,8 +23,9 @@ struct ChlorineView: View {
|
|||
} else {
|
||||
ScrollView {
|
||||
VStack(spacing: 12) {
|
||||
voltammogramPlot.frame(height: 250)
|
||||
resultBanner
|
||||
chlorinePlot.frame(height: 350)
|
||||
chlorinePlot.frame(height: 250)
|
||||
clTable.frame(height: 300)
|
||||
}
|
||||
.padding()
|
||||
|
|
@ -37,6 +40,21 @@ struct ChlorineView: View {
|
|||
private var controlsRow: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
Button("Start LSV") { state.startLSV() }
|
||||
.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)
|
||||
|
||||
Divider().frame(height: 24)
|
||||
|
||||
LabeledField("Cond mV", text: $state.clCondV, width: 70)
|
||||
LabeledField("Cond ms", text: $state.clCondT, width: 70)
|
||||
LabeledField("Free mV", text: $state.clFreeV, width: 70)
|
||||
|
|
@ -88,7 +106,102 @@ struct ChlorineView: View {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Plot
|
||||
// MARK: - Peak labels
|
||||
|
||||
@ViewBuilder
|
||||
private var clPeakLabels: 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(clPeakColor(peak.kind))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private func clPeakColor(_ kind: PeakKind) -> Color {
|
||||
switch kind {
|
||||
case .freeCl: .green
|
||||
case .totalCl: .orange
|
||||
case .crossover: .purple
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Voltammogram
|
||||
|
||||
private var voltammogramPlot: some View {
|
||||
Group {
|
||||
if state.lsvPoints.isEmpty {
|
||||
Text("No LSV data")
|
||||
.foregroundStyle(Color(white: 0.4))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black.opacity(0.3))
|
||||
} else {
|
||||
PlotContainer(title: "") {
|
||||
Chart {
|
||||
if let ref = state.lsvRef {
|
||||
ForEach(Array(ref.enumerated()), id: \.offset) { _, pt in
|
||||
LineMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
|
||||
.foregroundStyle(Color.gray.opacity(0.5))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1.5))
|
||||
}
|
||||
}
|
||||
ForEach(Array(state.lsvPoints.enumerated()), id: \.offset) { _, pt in
|
||||
LineMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
|
||||
.foregroundStyle(Color.yellow)
|
||||
.lineStyle(StrokeStyle(lineWidth: 2))
|
||||
}
|
||||
ForEach(Array(state.lsvPoints.enumerated()), id: \.offset) { _, pt in
|
||||
PointMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
|
||||
.foregroundStyle(Color.yellow)
|
||||
.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(clPeakColor(peak.kind))
|
||||
.symbolSize(100)
|
||||
.symbol(.diamond)
|
||||
RuleMark(x: .value("V", Double(peak.vMv)))
|
||||
.foregroundStyle(clPeakColor(peak.kind).opacity(0.3))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 4]))
|
||||
}
|
||||
}
|
||||
.chartXAxisLabel("V (mV)")
|
||||
.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(Color.yellow)
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.black.opacity(0.3))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
|
||||
// MARK: - Chlorine plot
|
||||
|
||||
private var chlorinePlot: some View {
|
||||
Group {
|
||||
|
|
|
|||
Loading…
Reference in New Issue