mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
EnvironmentMetrics chart scale improvements
This commit is contained in:
parent
95b006b442
commit
27ed32eb19
4 changed files with 84 additions and 12 deletions
|
|
@ -17,7 +17,7 @@
|
|||
2344A2B12D68DFF800170A77 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C49D8F2C471AEA0024FBD1 /* Constants.swift */; };
|
||||
2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */; };
|
||||
2373AE152D0A24930086C749 /* MetricsSeriesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */; };
|
||||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */; };
|
||||
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */; };
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */; };
|
||||
|
|
@ -282,7 +282,7 @@
|
|||
2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnviornmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = "<group>"; };
|
||||
251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = "<group>"; };
|
||||
251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -600,7 +600,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */,
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */,
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */,
|
||||
231B3F262D0885240069A07D /* MetricsColumnDetail.swift */,
|
||||
);
|
||||
path = "Metrics Columns";
|
||||
|
|
@ -1405,7 +1405,7 @@
|
|||
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */,
|
||||
251926902C3CB44900249DF5 /* ClientHistoryButton.swift in Sources */,
|
||||
DDD5BB102C285FB3007E03CA /* AppLogFilter.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */,
|
||||
DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */,
|
||||
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */,
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -38,12 +38,18 @@ class MetricsChartSeries: ObservableObject {
|
|||
// Possibly converted to the proper units
|
||||
let valueClosure: (TelemetryEntity) -> Float?
|
||||
|
||||
// Used for scaling the Y-axis
|
||||
let initialYAxisRange: ClosedRange<Float>?
|
||||
let minumumYAxisSpan: Float?
|
||||
|
||||
// Main initializer
|
||||
init<Value, ChartBody: ChartContent, ForegroundStyle: ShapeStyle>(
|
||||
id: String,
|
||||
keyPath: KeyPath<TelemetryEntity, Value>,
|
||||
name: String,
|
||||
abbreviatedName: String,
|
||||
initialYAxisRange: ClosedRange<Float>? = nil,
|
||||
minumumYAxisSpan: Float? = nil,
|
||||
conversion: ((Value) -> Value)? = nil,
|
||||
visible: Bool = true,
|
||||
foregroundStyle: @escaping ((ClosedRange<Float>?) -> ForegroundStyle?) = { _ in nil },
|
||||
|
|
@ -54,6 +60,8 @@ class MetricsChartSeries: ObservableObject {
|
|||
self.id = id
|
||||
self.name = name
|
||||
self.abbreviatedName = abbreviatedName
|
||||
self.initialYAxisRange = initialYAxisRange
|
||||
self.minumumYAxisSpan = minumumYAxisSpan
|
||||
self.visible = visible
|
||||
|
||||
// By saving these closures, MetricsChartSeries can be type agnostic
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplaceableCollection {
|
||||
|
||||
@Published var series: [MetricsChartSeries]
|
||||
|
|
@ -38,25 +39,86 @@ class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplacea
|
|||
return nil
|
||||
}
|
||||
|
||||
// Calculates the chartRange based on the series configuration and data provided
|
||||
// Besides checkign the range of the data, this function also obeys some series-level
|
||||
// configuraiton, such as:
|
||||
// 1. starting with a desired fixed range
|
||||
// 2. obeying a minimum span
|
||||
func chartRange(forData data: [TelemetryEntity]) -> ClosedRange<Float> {
|
||||
var lower: Float?
|
||||
var upper: Float?
|
||||
var globalLower: Float = .infinity
|
||||
var globalUpper: Float = -.infinity
|
||||
|
||||
// Keep track of the range of each series
|
||||
var range: [MetricsChartSeries: ClosedRange<Float>] = [:]
|
||||
|
||||
// Determine if there is an initial fixed range.
|
||||
// The range might exapand past this initial range if the data goes beyond.
|
||||
for aSeries in self.visible {
|
||||
if let thisRange = aSeries.initialYAxisRange {
|
||||
range[aSeries] = thisRange
|
||||
if thisRange.upperBound > globalUpper {globalUpper = thisRange.upperBound}
|
||||
if thisRange.lowerBound < globalLower {globalLower = thisRange.lowerBound}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all the data. It would be easier to iterate
|
||||
// the series then the data, but this way we only iterate the data once
|
||||
for te in data {
|
||||
for aSeries in self.visible {
|
||||
var seriesUpper = range[aSeries]?.upperBound ?? -.infinity
|
||||
var seriesLower = range[aSeries]?.lowerBound ?? .infinity
|
||||
|
||||
if let value = aSeries.valueFor(te) {
|
||||
if value > (upper ?? -.infinity) {upper = value}
|
||||
if value < (lower ?? .infinity) {lower = value}
|
||||
// Update the global bounds
|
||||
if value > globalUpper {globalUpper = value}
|
||||
if value < globalLower {globalLower = value}
|
||||
|
||||
// Update the series bounds if necessary
|
||||
if value > seriesUpper || value < seriesLower {
|
||||
if value > seriesUpper {
|
||||
seriesUpper = value
|
||||
}
|
||||
if value < seriesLower {
|
||||
seriesLower = value
|
||||
}
|
||||
if seriesUpper.isFinite && seriesLower.isFinite {
|
||||
range[aSeries] = seriesLower...seriesUpper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data or nil
|
||||
guard let lower, let upper else {
|
||||
|
||||
// Go through each series one last time to obey the minimum span
|
||||
for aSeries in self.visible {
|
||||
if let minimumSpan = aSeries.minumumYAxisSpan,
|
||||
let currentRange = range[aSeries] {
|
||||
let currentSpan = currentRange.upperBound - currentRange.lowerBound
|
||||
//Logger.data.info("Updated \(aSeries.id) to \(range[aSeries] ?? 0...0) span=\(currentSpan)")
|
||||
if currentSpan < minimumSpan {
|
||||
// Calculate the center of the range
|
||||
let centerOfRange = currentRange.lowerBound + (currentSpan / 2)
|
||||
let newLower = centerOfRange - (minimumSpan / 2.0)
|
||||
let newUpper = centerOfRange + (minimumSpan / 2.0)
|
||||
|
||||
if newUpper > globalUpper {
|
||||
globalUpper = newUpper
|
||||
}
|
||||
if newLower < globalLower {
|
||||
globalLower = newLower
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data
|
||||
if !globalLower.isFinite || !globalUpper.isFinite {
|
||||
return 0.0...100.0
|
||||
}
|
||||
return lower...upper
|
||||
return globalLower...globalUpper
|
||||
}
|
||||
|
||||
|
||||
// Collection conformance
|
||||
typealias Index = Int
|
||||
typealias Element = MetricsChartSeries
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ extension MetricsSeriesList {
|
|||
keyPath: \.temperature,
|
||||
name: "Temperature",
|
||||
abbreviatedName: "Temp",
|
||||
minumumYAxisSpan: 50.0,
|
||||
conversion: { t in t.map { Float($0.localeTemperature()) } },
|
||||
foregroundStyle: { chartRange in
|
||||
let locale = NSLocale.current as NSLocale
|
||||
|
|
@ -60,6 +61,7 @@ extension MetricsSeriesList {
|
|||
keyPath: \.relativeHumidity,
|
||||
name: "Relative Humidity",
|
||||
abbreviatedName: "Hum",
|
||||
initialYAxisRange: 0.0...100.0,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple],
|
||||
Loading…
Add table
Add a link
Reference in a new issue