Procházet zdrojové kódy

added DataApproximator+N extension (#2848)

* added DataApproximator+N extension

* fixed PR notes
Nikita Ivaniushchenko před 7 roky
rodič
revize
f05ed02f2b

+ 4 - 0
Charts.xcodeproj/project.pbxproj

@@ -99,6 +99,7 @@
 		967EE2EDDE3337C5C4337C59 /* IndexAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10DD0A02E3CF611BD11EBA9B /* IndexAxisValueFormatter.swift */; };
 		967EE2EDDE3337C5C4337C59 /* IndexAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10DD0A02E3CF611BD11EBA9B /* IndexAxisValueFormatter.swift */; };
 		97E033CC0ABEF0F448DAFA8E /* DataApproximator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */; };
 		97E033CC0ABEF0F448DAFA8E /* DataApproximator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */; };
 		98E2EEF45E8933E4AD182D58 /* ChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFAD7920F76360ADB3B5F5 /* ChartViewBase.swift */; };
 		98E2EEF45E8933E4AD182D58 /* ChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFAD7920F76360ADB3B5F5 /* ChartViewBase.swift */; };
+		9A26C8DB1F87B01700367599 /* DataApproximator+N.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A26C8DA1F87B01700367599 /* DataApproximator+N.swift */; };
 		9C91C151608E2D6E19B1EAD1 /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F099502DA50C56204E7B744 /* Range.swift */; };
 		9C91C151608E2D6E19B1EAD1 /* Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F099502DA50C56204E7B744 /* Range.swift */; };
 		9F760570BCECB0BF5727AF90 /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C6D3723C4E001B119CA0C8 /* BarLineChartViewBase.swift */; };
 		9F760570BCECB0BF5727AF90 /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C6D3723C4E001B119CA0C8 /* BarLineChartViewBase.swift */; };
 		A40ACF0CCE96EEE104B0463D /* IValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA8AA30C377D54D22A577A /* IValueFormatter.swift */; };
 		A40ACF0CCE96EEE104B0463D /* IValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA8AA30C377D54D22A577A /* IValueFormatter.swift */; };
@@ -255,6 +256,7 @@
 		9249AD9AEC8C85772365A128 /* ILineScatterCandleRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ILineScatterCandleRadarChartDataSet.swift; path = Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift; sourceTree = "<group>"; };
 		9249AD9AEC8C85772365A128 /* ILineScatterCandleRadarChartDataSet.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ILineScatterCandleRadarChartDataSet.swift; path = Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift; sourceTree = "<group>"; };
 		93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataApproximator.swift; path = Source/Charts/Filters/DataApproximator.swift; sourceTree = "<group>"; };
 		93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataApproximator.swift; path = Source/Charts/Filters/DataApproximator.swift; sourceTree = "<group>"; };
 		998F2BFE318471AFC05B50AC /* IHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IHighlighter.swift; path = Source/Charts/Highlight/IHighlighter.swift; sourceTree = "<group>"; };
 		998F2BFE318471AFC05B50AC /* IHighlighter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IHighlighter.swift; path = Source/Charts/Highlight/IHighlighter.swift; sourceTree = "<group>"; };
+		9A26C8DA1F87B01700367599 /* DataApproximator+N.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "DataApproximator+N.swift"; path = "Source/Charts/Filters/DataApproximator+N.swift"; sourceTree = "<group>"; };
 		9D7184C8A5A60A3522AB9B05 /* BarChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartDataProvider.swift; path = Source/Charts/Interfaces/BarChartDataProvider.swift; sourceTree = "<group>"; };
 		9D7184C8A5A60A3522AB9B05 /* BarChartDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarChartDataProvider.swift; path = Source/Charts/Interfaces/BarChartDataProvider.swift; sourceTree = "<group>"; };
 		9DCD13D558BA177D5952AD66 /* PieChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartView.swift; path = Source/Charts/Charts/PieChartView.swift; sourceTree = "<group>"; };
 		9DCD13D558BA177D5952AD66 /* PieChartView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PieChartView.swift; path = Source/Charts/Charts/PieChartView.swift; sourceTree = "<group>"; };
 		9E7C673B9ED4340F550A9283 /* LegendEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LegendEntry.swift; path = Source/Charts/Components/LegendEntry.swift; sourceTree = "<group>"; };
 		9E7C673B9ED4340F550A9283 /* LegendEntry.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LegendEntry.swift; path = Source/Charts/Components/LegendEntry.swift; sourceTree = "<group>"; };
@@ -536,6 +538,7 @@
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
 				93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */,
 				93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */,
+				9A26C8DA1F87B01700367599 /* DataApproximator+N.swift */,
 			);
 			);
 			name = Filters;
 			name = Filters;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -858,6 +861,7 @@
 				0C52C70C6E6EA09BD7426386 /* RadarChartData.swift in Sources */,
 				0C52C70C6E6EA09BD7426386 /* RadarChartData.swift in Sources */,
 				C2EFB4EC8C97FA9987F1B50D /* RadarChartDataEntry.swift in Sources */,
 				C2EFB4EC8C97FA9987F1B50D /* RadarChartDataEntry.swift in Sources */,
 				E3B28EA1E21279DF3889BCE8 /* RadarChartDataSet.swift in Sources */,
 				E3B28EA1E21279DF3889BCE8 /* RadarChartDataSet.swift in Sources */,
+				9A26C8DB1F87B01700367599 /* DataApproximator+N.swift in Sources */,
 				2B791E64E7C4523B1A63F72A /* ScatterChartData.swift in Sources */,
 				2B791E64E7C4523B1A63F72A /* ScatterChartData.swift in Sources */,
 				EB56849433A76B08606B73EB /* ScatterChartDataSet.swift in Sources */,
 				EB56849433A76B08606B73EB /* ScatterChartDataSet.swift in Sources */,
 				C3F0DDB7F0A922F0BB7EDB8A /* IBarChartDataSet.swift in Sources */,
 				C3F0DDB7F0A922F0BB7EDB8A /* IBarChartDataSet.swift in Sources */,

+ 152 - 0
Source/Charts/Filters/DataApproximator+N.swift

@@ -0,0 +1,152 @@
+//
+//  DataApproximator+N.swift
+//  Charts
+//
+//  Created by M Ivaniushchenko on 9/6/17.
+//  Licensed under Apache License 2.0
+//
+//  https://github.com/danielgindi/Charts
+//
+
+import Foundation
+
+extension CGPoint {
+    fileprivate func distanceToLine(from linePoint1: CGPoint, to linePoint2: CGPoint) -> CGFloat {
+        let dx = linePoint2.x - linePoint1.x
+        let dy = linePoint2.y - linePoint1.y
+        
+        let dividend = fabs(dy * self.x - dx * self.y - linePoint1.x * linePoint2.y + linePoint2.x * linePoint1.y)
+        let divisor = sqrt(dx * dx + dy * dy)
+        
+        return dividend / divisor
+    }
+}
+
+private struct LineAlt {
+    let start: Int
+    let end: Int
+    
+    var distance: CGFloat = 0
+    var index: Int = 0
+    
+    init(start: Int, end: Int, points: [CGPoint]) {
+        self.start = start
+        self.end = end
+        
+        let startPoint = points[start]
+        let endPoint = points[end]
+        
+        guard (end > start + 1) else {
+            return
+        }
+        
+        for i in start + 1 ..< end {
+            let currentPoint = points[i]
+            
+            let distance = currentPoint.distanceToLine(from: startPoint, to: endPoint)
+            
+            if distance > self.distance {
+                self.index = i
+                self.distance = distance
+            }
+        }
+    }
+}
+
+extension LineAlt: Comparable {
+    static func ==(lhs: LineAlt, rhs: LineAlt) -> Bool {
+        return (lhs.start == rhs.start) && (lhs.end == rhs.end) && (lhs.index == rhs.index)
+    }
+    
+    static func <(lhs: LineAlt, rhs: LineAlt) -> Bool {
+        return lhs.distance < rhs.distance
+    }
+}
+
+
+extension DataApproximator {
+    /// uses the douglas peuker algorithm to reduce the given arraylist of entries to given number of points
+    /// More algorithm details here - http://psimpl.sourceforge.net/douglas-peucker.html
+    @objc open class func reduceWithDouglasPeukerN(_ points: [CGPoint], resultCount: Int) -> [CGPoint]
+    {
+        // if a shape has 2 or less points it cannot be reduced
+        if resultCount <= 2 || resultCount >= points.count
+        {
+            return points
+        }
+        var keep = [Bool](repeating: false, count: points.count)
+        
+        // first and last always stay
+        keep[0] = true
+        keep[points.count - 1] = true
+        var currentStoredPoints = 2
+        
+        var queue = [LineAlt]()
+        let line = LineAlt(start: 0, end: points.count - 1, points: points)
+        queue.append(line)
+        
+        repeat {
+            let line = queue.popLast()!
+            
+            // store the key
+            keep[line.index] = true
+            
+            // check point count tolerance
+            currentStoredPoints += 1
+            
+            if (currentStoredPoints == resultCount) {
+                break;
+            }
+            
+            // split the polyline at the key and recurse
+            let left = LineAlt(start: line.start, end: line.index, points: points)
+            if (left.index > 0) {
+                self.insertLine(left, into: &queue)
+            }
+            
+            let right = LineAlt(start: line.index, end: line.end, points: points)
+            if (right.index > 0) {
+                self.insertLine(right, into: &queue)
+            }
+            
+        } while !queue.isEmpty
+        
+        // create a new array with series, only take the kept ones
+        let reducedEntries = points.enumerated().flatMap { (index: Int, point: CGPoint) -> CGPoint? in
+            return keep[index] ? point : nil
+        }
+        
+        return reducedEntries
+    }
+    
+    // Keeps array sorted
+    private static func insertLine(_ line: LineAlt, into array: inout [LineAlt]) {
+        let insertionIndex = self.insertionIndex(for: line, into: &array)
+        array.insert(line, at: insertionIndex)
+    }
+    
+    private static func insertionIndex(for line: LineAlt, into array: inout [LineAlt]) -> Int {
+        var indices = array.indices
+        
+        while !indices.isEmpty {
+            let midIndex = indices.lowerBound.advanced(by: indices.count / 2)
+            let midLine = array[midIndex]
+            
+            if midLine == line {
+                return midIndex
+            }
+            else if (line < midLine) {
+                // perform search in left half
+                indices = indices.lowerBound..<midIndex
+            }
+            else {
+                // perform search in right half
+                indices = (midIndex + 1)..<indices.upperBound
+            }
+        }
+        
+        return indices.lowerBound
+    }
+}
+
+