Browse Source

Algorithm updates (#3638)

* algorithm updates

* Minor code org updates

* Relocated util file

* Removed duplicated file
Jacob Christie 4 years ago
parent
commit
0aa2b76e86

+ 4 - 0
Charts.xcodeproj/project.pbxproj

@@ -27,6 +27,7 @@
 		146EE16342C2BADC92E45BF2 /* LineScatterCandleRadarChartDataSetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9249AD9AEC8C85772365A128 /* LineScatterCandleRadarChartDataSetProtocol.swift */; };
 		17E994DA88777AA1D8CCFC58 /* BarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31AA65EA27776F8C653C7E8 /* BarChartDataSet.swift */; };
 		219192CA6B4895319AB49DCA /* BarLineScatterCandleBubbleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C588E9DF6FFD56D7ADF8E /* BarLineScatterCandleBubbleRenderer.swift */; };
+		221CA2922588FCBC00C2DD1E /* Sequence+KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221CA2912588FCBC00C2DD1E /* Sequence+KeyPath.swift */; };
 		2243BBFD1FF156EC00B49D0B /* EquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2243BBFB1FF156D000B49D0B /* EquatableTests.swift */; };
 		23649EFC635A76022F07FFA6 /* PieChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD02157CF8CEE1189BF681DA /* PieChartDataEntry.swift */; };
 		23FA50B2730D8C7ACA091C4F /* BarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F279974FE650E57A061B09 /* BarChartRenderer.swift */; };
@@ -195,6 +196,7 @@
 		1F3D55A7E6176D52DC957D27 /* XAxisRendererHorizontalBarChart.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XAxisRendererHorizontalBarChart.swift; path = Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift; sourceTree = "<group>"; };
 		2194AA554712E6BA2677F114 /* BubbleChartRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BubbleChartRenderer.swift; path = Source/Charts/Renderers/BubbleChartRenderer.swift; sourceTree = "<group>"; };
 		219BC9CEA037F897E92E45D1 /* ScatterChartDataSetProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScatterChartDataSetProtocol.swift; path = Source/Charts/Data/Interfaces/ScatterChartDataSetProtocol.swift; sourceTree = "<group>"; };
+		221CA2912588FCBC00C2DD1E /* Sequence+KeyPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Sequence+KeyPath.swift"; path = "Source/Charts/Utils/Sequence+KeyPath.swift"; sourceTree = "<group>"; };
 		2243BBFB1FF156D000B49D0B /* EquatableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EquatableTests.swift; path = Tests/Charts/EquatableTests.swift; sourceTree = "<group>"; };
 		23D35CF6F9177D77B6B97AE1 /* XShapeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XShapeRenderer.swift; path = Source/Charts/Renderers/Scatter/XShapeRenderer.swift; sourceTree = "<group>"; };
 		2440DB759AB93B4A928A3F6F /* RadarChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RadarChartView.swift; path = Source/Charts/Charts/RadarChartView.swift; sourceTree = "<group>"; };
@@ -647,6 +649,7 @@
 				C9F3DC252355F791000C3215 /* Platform+Gestures.swift */,
 				C9F3DC242355F791000C3215 /* Platform+Graphics.swift */,
 				C9F3DC282355FA2F000C3215 /* Platform+Touch Handling.swift */,
+				221CA2912588FCBC00C2DD1E /* Sequence+KeyPath.swift */,
 				FF475B9593B9898853814340 /* Transformer.swift */,
 				324C9127B53A8D39C8B49277 /* TransformerHorizontalBarChart.swift */,
 				72EAEBB7CF73E33565FC2896 /* ViewPortHandler.swift */,
@@ -911,6 +914,7 @@
 				B539114951455C35BADAE3F3 /* PieChartDataSet.swift in Sources */,
 				0C52C70C6E6EA09BD7426386 /* RadarChartData.swift in Sources */,
 				C2EFB4EC8C97FA9987F1B50D /* RadarChartDataEntry.swift in Sources */,
+				221CA2922588FCBC00C2DD1E /* Sequence+KeyPath.swift in Sources */,
 				E3B28EA1E21279DF3889BCE8 /* RadarChartDataSet.swift in Sources */,
 				9A26C8DB1F87B01700367599 /* DataApproximator+N.swift in Sources */,
 				2B791E64E7C4523B1A63F72A /* ScatterChartData.swift in Sources */,

+ 2 - 5
Source/Charts/Charts/ChartViewBase.swift

@@ -62,12 +62,9 @@ open class ChartViewBase: NSUIView, ChartDataProvider, AnimatorDelegate
             // calculate how many digits are needed
             setupDefaultFormatter(min: data.yMin, max: data.yMax)
 
-            for set in data
+            for set in data where set.valueFormatter is DefaultValueFormatter
             {
-                if set.valueFormatter is DefaultValueFormatter
-                {
-                    set.valueFormatter = defaultValueFormatter
-                }
+                set.valueFormatter = defaultValueFormatter
             }
 
             // let the chart know there is new data

+ 6 - 13
Source/Charts/Components/AxisBase.swift

@@ -133,19 +133,12 @@ open class AxisBase: ComponentBase
     
     @objc open func getLongestLabel() -> String
     {
-        var longest = ""
-        
-        for i in entries.indices
-        {
-            let text = getFormattedLabel(i)
-            
-            if longest.count < text.count
-            {
-                longest = text
-            }
-        }
-        
-        return longest
+        let longest = entries.indices
+            .lazy
+            .map(getFormattedLabel(_:))
+            .max(by: \.count)
+
+        return longest ?? ""
     }
     
     /// - Returns: The formatted label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set).

+ 0 - 1
Source/Charts/Data/Implementations/Standard/BarChartData.swift

@@ -91,7 +91,6 @@ open class BarChartData: BarLineScatterCandleBubbleChartData
             {
                 fromX += diff
             }
-
         }
         
         notifyDataChanged()

+ 5 - 13
Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift

@@ -112,6 +112,8 @@ open class BarChartDataEntry: ChartDataEntry
         return _positiveSum
     }
 
+    var stackSize: Int { yValues?.count ?? 1}
+
     @objc open func calcPosNegSum()
     {
         (_negativeSum, _positiveSum) = _yVals?.reduce(into: (0,0)) { (result, y) in
@@ -175,7 +177,7 @@ open class BarChartDataEntry: ChartDataEntry
         get { return self._yVals }
         set
         {
-            self.y = BarChartDataEntry.calcSum(values: newValue)
+            self.y = BarChartDataEntry.calcSum(values: newValue ?? [])
             self._yVals = newValue
             calcPosNegSum()
             calcRanges()
@@ -205,18 +207,8 @@ open class BarChartDataEntry: ChartDataEntry
     /// - Parameters:
     ///   - vals:
     /// - Returns:
-    private static func calcSum(values: [Double]?) -> Double
+    private static func calcSum(values: [Double]) -> Double
     {
-        guard let values = values
-            else { return 0.0 }
-        
-        var sum = 0.0
-        
-        for f in values
-        {
-            sum += f
-        }
-        
-        return sum
+        values.reduce(into: 0, +=)
     }
 }

+ 6 - 7
Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift

@@ -48,18 +48,17 @@ open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, BarChartData
     /// stacks. All values belonging to a stack are calculated separately.
     private func calcEntryCountIncludingStacks(entries: [BarChartDataEntry])
     {
-        _entryCountStacks = 0
-        
-        entries.forEach { _entryCountStacks += $0.yValues?.count ?? 1 }
+        _entryCountStacks = entries.lazy
+            .map(\.stackSize)
+            .reduce(into: 0, +=)
     }
     
     /// calculates the maximum stacksize that occurs in the Entries array of this DataSet
     private func calcStackSize(entries: [BarChartDataEntry])
     {
-        for e in entries where (e.yValues?.count ?? 0) > _stackSize
-        {
-            _stackSize = e.yValues!.count
-        }
+        _stackSize = entries.lazy
+            .map(\.stackSize)
+            .max() ?? 1
     }
     
     open override func calcMinMax(entry e: ChartDataEntry)

+ 1 - 0
Source/Charts/Highlight/BarHighlighter.swift

@@ -98,6 +98,7 @@ open class BarHighlighter: ChartHighlighter
     @objc open func getClosestStackIndex(ranges: [Range]?, value: Double) -> Int
     {
         guard let ranges = ranges else { return 0 }
+        
         if let stackIndex = ranges.firstIndex(where: { $0.contains(value) }) {
             return stackIndex
         } else {

+ 5 - 13
Source/Charts/Highlight/RadarHighlighter.swift

@@ -22,20 +22,12 @@ open class RadarHighlighter: PieRadarHighlighter
         let highlights = getHighlights(forIndex: index)
         
         let distanceToCenter = Double(chart.distanceToCenter(x: x, y: y) / chart.factor)
-        
-        var closest: Highlight?
-        var distance = Double.greatestFiniteMagnitude
-        
-        for high in highlights
-        {
-            let cdistance = abs(high.y - distanceToCenter)
-            if cdistance < distance
-            {
-                closest = high
-                distance = cdistance
-            }
+
+        func closestToCenter(lhs: Highlight, rhs: Highlight) -> Bool {
+            abs(lhs.y - distanceToCenter) < abs(rhs.y - distanceToCenter)
         }
-        
+
+        let closest = highlights.min(by: closestToCenter(lhs:rhs:))
         return closest
     }
     

+ 17 - 16
Source/Charts/Renderers/BarChartRenderer.swift

@@ -391,12 +391,13 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
             // Create and append the corresponding accessibility element to accessibilityOrderedElements
             if let chart = dataProvider as? BarChartView
             {
-                let element = createAccessibleElement(withIndex: j,
-                                                      container: chart,
-                                                      dataSet: dataSet,
-                                                      dataSetIndex: index,
-                                                      stackSize: stackSize)
-                { (element) in
+                let element = createAccessibleElement(
+                    withIndex: j,
+                    container: chart,
+                    dataSet: dataSet,
+                    dataSetIndex: index,
+                    stackSize: stackSize
+                ) { (element) in
                     element.accessibilityFrame = barRect
                 }
 
@@ -536,8 +537,9 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
                     // if we have stacks
                     
                     var bufferIndex = 0
-                    
-                    for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX))
+                    let lastIndex = ceil(Double(dataSet.entryCount) * animator.phaseX)
+
+                    for index in 0 ..< Int(lastIndex)
                     {
                         guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue }
                         
@@ -548,7 +550,7 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
                         let x = rect.origin.x + rect.size.width / 2.0
                         
                         // we still draw stacked bars, but there is one non-stacked in between
-                        if let vals = vals
+                        if let values = vals
                         {
                             // draw stack values
                             var transformed = [CGPoint]()
@@ -556,11 +558,10 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
                             var posY = 0.0
                             var negY = -e.negativeSum
 
-                            for k in vals.indices
+                            for value in values
                             {
-                                let value = vals[k]
-                                var y: Double
-
+                                let y: Double
+                                
                                 if value == 0.0 && (posY == 0.0 || negY == 0.0)
                                 {
                                     // Take care of the situation of a 0.0 value, which overlaps a non-zero bar
@@ -582,9 +583,9 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
 
                             trans.pointValuesToPixel(&transformed)
 
-                            for (val, transformed) in zip(vals, transformed)
+                            for (value, transformed) in zip(values, transformed)
                             {
-                                let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0
+                                let drawBelow = (value == 0.0 && negY == 0.0 && posY > 0.0) || value < 0.0
                                 let y = transformed.y + (drawBelow ? negOffset : posOffset)
 
                                 guard viewPortHandler.isInBoundsRight(x) else { break }
@@ -597,7 +598,7 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
                                     drawValue(
                                         context: context,
                                         value: formatter.stringForValue(
-                                            val,
+                                            value,
                                             entry: e,
                                             dataSetIndex: dataSetIndex,
                                             viewPortHandler: viewPortHandler),

+ 2 - 1
Source/Charts/Renderers/BubbleChartRenderer.swift

@@ -45,7 +45,8 @@ open class BubbleChartRenderer: BarLineScatterCandleBubbleRenderer
             accessibleChartElements.append(element)
         }
 
-        for case let (i, set) as (Int, BubbleChartDataSetProtocol) in bubbleData.enumerated() where set.isVisible
+        let sets = bubbleData.dataSets as! [BubbleChartDataSet]
+        for case let (i, set) in zip(sets.indices, sets) where set.isVisible
         {
             drawDataSet(context: context, dataSet: set, dataSetIndex: i)
         }

+ 5 - 13
Source/Charts/Renderers/CombinedChartRenderer.swift

@@ -59,35 +59,30 @@ open class CombinedChartRenderer: NSObject, DataRenderer
                 {
                     _renderers.append(BarChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler))
                 }
-                break
-                
+
             case .line:
                 if chart.lineData !== nil
                 {
                     _renderers.append(LineChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler))
                 }
-                break
-                
+
             case .candle:
                 if chart.candleData !== nil
                 {
                     _renderers.append(CandleStickChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler))
                 }
-                break
-                
+
             case .scatter:
                 if chart.scatterData !== nil
                 {
                     _renderers.append(ScatterChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler))
                 }
-                break
-                
+
             case .bubble:
                 if chart.bubbleData !== nil
                 {
                     _renderers.append(BubbleChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler))
                 }
-                break
             }
         }
 
@@ -190,10 +185,7 @@ open class CombinedChartRenderer: NSObject, DataRenderer
     /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines.
     open var drawOrder: [CombinedChartView.DrawOrder]
     {
-        get
-        {
-            return _drawOrder
-        }
+        get { _drawOrder }
         set
         {
             if !newValue.isEmpty

+ 33 - 37
Source/Charts/Renderers/LineChartRenderer.swift

@@ -32,18 +32,14 @@ open class LineChartRenderer: LineRadarRenderer
     open override func drawData(context: CGContext)
     {
         guard let lineData = dataProvider?.lineData else { return }
-        
-        for i in lineData.indices
-        {
-            guard let set = lineData[i] as? LineChartDataSetProtocol else
-            {
-                fatalError("Datasets for LineChartRenderer must conform to LineChartDataSetProtocol")
-            }
 
-            guard set.isVisible else { continue }
+        let sets = lineData.dataSets as? [LineChartDataSet]
+        assert(sets != nil, "Datasets for LineChartRenderer must conform to ILineChartDataSet")
 
-            drawDataSet(context: context, dataSet: set)
-        }
+        let drawDataSet = { self.drawDataSet(context: context, dataSet: $0) }
+        sets!.lazy
+            .filter(\.isVisible)
+            .forEach(drawDataSet)
     }
     
     @objc open func drawDataSet(context: CGContext, dataSet: LineChartDataSetProtocol)
@@ -269,7 +265,7 @@ open class LineChartRenderer: LineRadarRenderer
     {
         guard
             let dataProvider = dataProvider
-            else { return }
+        else { return }
         
         if bounds.range <= 0
         {
@@ -373,7 +369,7 @@ open class LineChartRenderer: LineRadarRenderer
                 {
                     break
                 }
-            
+
                 // Determine the start and end coordinates of the line, and make sure they differ.
                 guard
                     let firstCoordinate = _lineSegments.first,
@@ -381,9 +377,9 @@ open class LineChartRenderer: LineRadarRenderer
                     firstCoordinate != lastCoordinate else { continue }
                 
                 // make sure the lines don't do shitty things outside bounds
-            if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) ||
-                !viewPortHandler.isInBoundsTop(max(firstCoordinate.y, lastCoordinate.y)) ||
-                !viewPortHandler.isInBoundsBottom(min(firstCoordinate.y, lastCoordinate.y))
+                if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) ||
+                    !viewPortHandler.isInBoundsTop(max(firstCoordinate.y, lastCoordinate.y)) ||
+                    !viewPortHandler.isInBoundsBottom(min(firstCoordinate.y, lastCoordinate.y))
                 {
                     continue
                 }
@@ -523,7 +519,7 @@ open class LineChartRenderer: LineRadarRenderer
         guard
             let dataProvider = dataProvider,
             let lineData = dataProvider.lineData
-            else { return }
+        else { return }
 
         if isDrawingValuesAllowed(dataProvider: dataProvider)
         {
@@ -534,9 +530,9 @@ open class LineChartRenderer: LineRadarRenderer
             for i in lineData.indices
             {
                 guard let
-                    dataSet = lineData[i] as? LineChartDataSetProtocol,
-                    shouldDrawValues(forDataSet: dataSet)
-                    else { continue }
+                        dataSet = lineData[i] as? LineChartDataSetProtocol,
+                      shouldDrawValues(forDataSet: dataSet)
+                else { continue }
                 
                 let valueFont = dataSet.valueFont
                 
@@ -595,7 +591,7 @@ open class LineChartRenderer: LineRadarRenderer
                     {
                         context.drawImage(icon,
                                           atCenter: CGPoint(x: pt.x + iconsOffset.x,
-                                                          y: pt.y + iconsOffset.y),
+                                                            y: pt.y + iconsOffset.y),
                                           size: icon.size)
                     }
                 }
@@ -613,7 +609,7 @@ open class LineChartRenderer: LineRadarRenderer
         guard
             let dataProvider = dataProvider,
             let lineData = dataProvider.lineData
-            else { return }
+        else { return }
         
         let phaseY = animator.phaseY
         
@@ -756,7 +752,7 @@ open class LineChartRenderer: LineRadarRenderer
         guard
             let dataProvider = dataProvider,
             let lineData = dataProvider.lineData
-            else { return }
+        else { return }
         
         let chartXMax = dataProvider.chartXMax
         
@@ -765,8 +761,8 @@ open class LineChartRenderer: LineRadarRenderer
         for high in indices
         {
             guard let set = lineData[high.dataSetIndex] as? LineChartDataSetProtocol,
-                set.isHighlightEnabled
-                else { continue }
+                  set.isHighlightEnabled
+            else { continue }
             
             guard let e = set.entryForXValue(high.x, closestToY: high.y) else { continue }
             
@@ -835,7 +831,7 @@ open class LineChartRenderer: LineRadarRenderer
                     return
                 }
                 components += [r, g, b, a]
-        }
+            }
         let gradientLocations: [CGFloat] = gradientPositions.reversed()
             .map { (position) in
                 let location = CGPoint(x: boundingBox.minX, y: position)
@@ -843,14 +839,14 @@ open class LineChartRenderer: LineRadarRenderer
                 let normalizedLocation = (location.y - boundingBox.minY)
                     / (boundingBox.maxY - boundingBox.minY)
                 return normalizedLocation.clamped(to: 0...1)
-        }
+            }
 
         let baseColorSpace = CGColorSpaceCreateDeviceRGB()
         guard let gradient = CGGradient(
-            colorSpace: baseColorSpace,
-            colorComponents: gradientColorComponents,
-            locations: gradientLocations,
-            count: gradientLocations.count) else {
+                colorSpace: baseColorSpace,
+                colorComponents: gradientColorComponents,
+                locations: gradientLocations,
+                count: gradientLocations.count) else {
             return
         }
 
@@ -880,10 +876,10 @@ open class LineChartRenderer: LineRadarRenderer
     /// i.e. in case of a stacked chart, this returns each stack, not the combined bar.
     /// Note that it is marked internal to support subclass modification in the HorizontalBarChart.
     private func createAccessibleElement(withIndex idx: Int,
-                                          container: LineChartView,
-                                          dataSet: LineChartDataSetProtocol,
-                                          dataSetIndex: Int,
-                                          modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement
+                                         container: LineChartView,
+                                         dataSet: LineChartDataSetProtocol,
+                                         dataSetIndex: Int,
+                                         modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement
     {
         let element = NSUIAccessibilityElement(accessibilityContainer: container)
         let xAxis = container.xAxis
@@ -897,9 +893,9 @@ open class LineChartRenderer: LineRadarRenderer
         let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)"
 
         let elementValueText = dataSet.valueFormatter.stringForValue(e.y,
-                                                                      entry: e,
-                                                                      dataSetIndex: dataSetIndex,
-                                                                      viewPortHandler: viewPortHandler)
+                                                                     entry: e,
+                                                                     dataSetIndex: dataSetIndex,
+                                                                     viewPortHandler: viewPortHandler)
 
         let dataSetCount = dataProvider.lineData?.dataSetCount ?? -1
         let doesContainMultipleDataSets = dataSetCount > 1

+ 7 - 11
Source/Charts/Renderers/ScatterChartRenderer.swift

@@ -41,17 +41,13 @@ open class ScatterChartRenderer: LineScatterCandleRadarRenderer
         // TODO: Due to the potential complexity of data presented in Scatter charts, a more usable way
         // for VO accessibility would be to use axis based traversal rather than by dataset.
         // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views)
-        for i in scatterData.indices
-        {
-            guard let set = scatterData[i] as? ScatterChartDataSetProtocol else
-            {
-                fatalError("Datasets for ScatterChartRenderer must conform to ScatterChartDataSetProtocol")
-            }
-
-            guard set.isVisible else { continue }
-
-            drawDataSet(context: context, dataSet: set)
-        }
+        let sets = scatterData.dataSets as? [ScatterChartDataSet]
+        assert(sets != nil, "Datasets for ScatterChartRenderer must conform to IScatterChartDataSet")
+        
+        let drawDataSet = { self.drawDataSet(context: context, dataSet: $0) }
+        sets!.lazy
+            .filter(\.isVisible)
+            .forEach(drawDataSet)
     }
     
     private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2)

+ 4 - 4
Source/Charts/Utils/Platform+Accessibility.swift

@@ -16,12 +16,12 @@ import UIKit
 
 internal func accessibilityPostLayoutChangedNotification(withElement element: Any? = nil)
 {
-    UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: element)
+    UIAccessibility.post(notification: .layoutChanged, argument: element)
 }
 
 internal func accessibilityPostScreenChangedNotification(withElement element: Any? = nil)
 {
-    UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: element)
+    UIAccessibility.post(notification: .screenChanged, argument: element)
 }
 
 /// A simple abstraction over UIAccessibilityElement and NSAccessibilityElement.
@@ -33,7 +33,7 @@ open class NSUIAccessibilityElement: UIAccessibilityElement
     {
         didSet
         {
-            accessibilityTraits = isHeader ? UIAccessibilityTraits.header : UIAccessibilityTraits.none
+            accessibilityTraits = isHeader ? .header : .none
         }
     }
 
@@ -41,7 +41,7 @@ open class NSUIAccessibilityElement: UIAccessibilityElement
         {
         didSet
         {
-            accessibilityTraits = isSelected ? UIAccessibilityTraits.selected : UIAccessibilityTraits.none
+            accessibilityTraits = isSelected ? .selected : .none
         }
     }
 

+ 19 - 0
Source/Charts/Utils/Sequence+KeyPath.swift

@@ -0,0 +1,19 @@
+//
+//  Sequence+KeyPath.swift
+//  Charts
+//
+//  Created by Jacob Christie on 2020-12-15.
+//
+
+extension Sequence {
+    func max<T>(
+        by keyPath: KeyPath<Element, T>,
+        areInIncreasingOrder: (T, T) -> Bool
+    ) -> Element? {
+        self.max { areInIncreasingOrder($0[keyPath: keyPath], $1[keyPath: keyPath]) }
+    }
+
+    func max<T: Comparable>(by keyPath: KeyPath<Element, T>) -> Element? {
+        max(by: keyPath, areInIncreasingOrder: <)
+    }
+}