diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 01c0c90f..d0857465 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -14631,6 +14631,9 @@ }, "Mininum time between detection broadcasts. Default is 45 seconds." : { + }, + "Minute" : { + }, "mode" : { "localizations" : { diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 51a68a49..d62d62c0 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -24,6 +24,7 @@ struct DeviceMetricsLog: View { @ObservedObject var node: NodeInfoEntity @State private var sortOrder = [KeyPathComparator(\TelemetryEntity.time, order: .reverse)] @State private var selection: TelemetryEntity.ID? + @State private var chartSelection: Date? var body: some View { VStack { @@ -35,60 +36,79 @@ struct DeviceMetricsLog: View { .sorted { $0.time! < $1.time! } if chartData.count > 0 { GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - Chart { - ForEach(chartData, id: \.self) { point in - Plot { - LineMark( - x: .value("x", point.time!), - y: .value("y", point.batteryLevel) - ) + if #available(iOS 17.0, macOS 14.0, *) { + Chart { + ForEach(chartData, id: \.self) { point in + Plot { + LineMark( + x: .value("x", point.time!), + y: .value("y", point.batteryLevel) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") + .foregroundStyle(batteryChartColor) + .interpolationMethod(.linear) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.channelUtilization) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") + .foregroundStyle(channelUtilizationChartColor) + if let chartSelection { + RuleMark(x: .value("Minute", chartSelection, unit: .minute)) + .foregroundStyle(.tertiary.opacity(0.5)) +// .annotation( +// position: .automatic, +// overflowResolution: .init(x: .fit, y: .disabled) +// ) { +// ZStack { +// Text("\(getTelemetry(for: chartSelection))") +// } +// .padding() +// .background { +// RoundedRectangle(cornerRadius: 4) +// .foregroundStyle(Color.accentColor.opacity(0.2)) +// } +// } + } + RuleMark(y: .value("Network Status Orange", 25)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.orange) + RuleMark(y: .value("Network Status Red", 50)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.red) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.airUtilTx) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") + .foregroundStyle(airtimeChartColor) } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") - .foregroundStyle(batteryChartColor) - .interpolationMethod(.linear) - - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.channelUtilization) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") - .foregroundStyle(channelUtilizationChartColor) - - RuleMark(y: .value("Network Status Orange", 25)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.orange) - RuleMark(y: .value("Network Status Red", 50)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.red) - - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.airUtilTx) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") - .foregroundStyle(airtimeChartColor) } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + .chartXAxis(.automatic) + .chartXSelection(value: $chartSelection) + .chartYScale(domain: 0...100) + .chartForegroundStyleScale([ + idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, + "Channel Utilization": channelUtilizationChartColor, + "Airtime": airtimeChartColor + ]) + .chartLegend(position: .automatic, alignment: .bottom) + } else { + // Fallback on earlier versions } - .chartXAxis(content: { - AxisMarks(position: .top) - }) - .chartXAxis(.automatic) - .chartYScale(domain: 0...100) - .chartForegroundStyleScale([ - idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, - "Channel Utilization": channelUtilizationChartColor, - "Airtime": airtimeChartColor - ]) - .chartLegend(position: .automatic, alignment: .bottom) } .frame(minHeight: 240) } @@ -191,6 +211,12 @@ struct DeviceMetricsLog: View { .padding(.bottom) .padding(.trailing) } + .onChange(of: selection) { newSelection in + guard let metrics = deviceMetrics.first(where: { $0.id == newSelection }) else { + return + } + chartSelection = metrics.time + } } else { if #available (iOS 17, *) { ContentUnavailableView("No Device Metrics", systemImage: "slash.circle")