| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- //
- // ChartAxisValuesStaticGenerator.swift
- // LoopUI
- //
- // Created by Nathaniel Hamming on 2020-09-08.
- // Copyright © 2020 LoopKit Authors. All rights reserved.
- //
- import SwiftCharts
- import UIKit
- extension ChartAxisValuesStaticGenerator {
- // This is the same as SwiftChart ChartAxisValuesStaticGenerator.generateAxisValuesWithChartPoints(...) with the exception that the `currentMultiple` is calculated linearly instead of quadratically
- static func generateYAxisValuesUsingLinearSegmentStep(chartPoints: [ChartPoint],
- minSegmentCount: Double,
- maxSegmentCount: Double,
- multiple: Double,
- axisValueGenerator: ChartAxisValueStaticGenerator,
- addPaddingSegmentIfEdge: Bool) -> [ChartAxisValue]
- {
- precondition(multiple > 0, "Invalid multiple: \(multiple)")
-
- let sortedChartPoints = chartPoints.sorted {(obj1, obj2) in
- return obj1.y.scalar < obj2.y.scalar
- }
-
- if let firstChartPoint = sortedChartPoints.first, let lastChartPoint = sortedChartPoints.last {
- let first = firstChartPoint.y.scalar
- let lastPar = lastChartPoint.y.scalar
-
- guard lastPar >=~ first else {fatalError("Invalid range generating axis values")}
-
- let last = lastPar =~ first ? lastPar + 1 : lastPar
-
- /// The first axis value will be less than or equal to the first scalar value, aligned with the desired multiple
- var firstValue = first - (first.truncatingRemainder(dividingBy: multiple))
- /// The last axis value will be greater than or equal to the last scalar value, aligned with the desired multiple
- let remainder = last.truncatingRemainder(dividingBy: multiple)
- var lastValue = remainder == 0 ? last : last + (multiple - remainder)
- var segmentSize = multiple
-
- /// If there should be a padding segment added when a scalar value falls on the first or last axis value, adjust the first and last axis values
- if firstValue =~ first && addPaddingSegmentIfEdge {
- firstValue = firstValue - segmentSize
- }
-
- // do not allow the first label to be displayed as -0
- while firstValue < 0 && firstValue.rounded() == -0 {
- firstValue = firstValue - segmentSize
- }
-
- if lastValue =~ last && addPaddingSegmentIfEdge {
- lastValue = lastValue + segmentSize
- }
-
- let distance = lastValue - firstValue
- var currentMultiple = multiple
- var segmentCount = distance / currentMultiple
- var potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)
- /// Find the optimal number of segments and segment width
- /// If the number of segments is greater than desired, make each segment wider
- /// ensure no label of -0 will be displayed on the axis
- while segmentCount > maxSegmentCount ||
- !potentialSegmentValues.filter({ $0 < 0 && $0.rounded() == -0 }).isEmpty
- {
- currentMultiple += multiple
- segmentCount = distance / currentMultiple
- potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)
- }
- segmentCount = ceil(segmentCount)
-
- /// Increase the number of segments until there are enough as desired
- while segmentCount < minSegmentCount {
- segmentCount += 1
- }
- segmentSize = currentMultiple
-
- /// Generate axis values from the first value, segment size and number of segments
- let offset = firstValue
- return (0...Int(segmentCount)).map {segment in
- var scalar = offset + (Double(segment) * segmentSize)
- // a value that could be displayed as 0 should truly be 0 to have the zero-line drawn correctly.
- if scalar != 0,
- scalar.rounded() == 0
- {
- scalar = 0
- }
- return axisValueGenerator(scalar)
- }
- } else {
- print("Trying to generate Y axis without datapoints, returning empty array")
- return []
- }
- }
- }
- fileprivate func =~ (a: Double, b: Double) -> Bool {
- return fabs(a - b) < Double.ulpOfOne
- }
- fileprivate func >=~ (a: Double, b: Double) -> Bool {
- return a =~ b || a > b
- }
|