Skip to content

Commit

Permalink
added DataApproximator+N extension (ChartsOrg#2848)
Browse files Browse the repository at this point in the history
* added DataApproximator+N extension

* fixed PR notes
  • Loading branch information
666tos authored and philipengberg committed Jan 19, 2018
1 parent d4d18a8 commit b6bde08
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Charts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
967EE2EDDE3337C5C4337C59 /* IndexAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10DD0A02E3CF611BD11EBA9B /* IndexAxisValueFormatter.swift */; };
97E033CC0ABEF0F448DAFA8E /* DataApproximator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EF9709CF635BEE70D1ABC5 /* DataApproximator.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 */; };
9F760570BCECB0BF5727AF90 /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C6D3723C4E001B119CA0C8 /* BarLineChartViewBase.swift */; };
A40ACF0CCE96EEE104B0463D /* IValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA8AA30C377D54D22A577A /* IValueFormatter.swift */; };
Expand Down Expand Up @@ -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>"; };
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>"; };
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>"; };
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>"; };
Expand Down Expand Up @@ -536,6 +538,7 @@
isa = PBXGroup;
children = (
93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */,
9A26C8DA1F87B01700367599 /* DataApproximator+N.swift */,
);
name = Filters;
sourceTree = "<group>";
Expand Down Expand Up @@ -858,6 +861,7 @@
0C52C70C6E6EA09BD7426386 /* RadarChartData.swift in Sources */,
C2EFB4EC8C97FA9987F1B50D /* RadarChartDataEntry.swift in Sources */,
E3B28EA1E21279DF3889BCE8 /* RadarChartDataSet.swift in Sources */,
9A26C8DB1F87B01700367599 /* DataApproximator+N.swift in Sources */,
2B791E64E7C4523B1A63F72A /* ScatterChartData.swift in Sources */,
EB56849433A76B08606B73EB /* ScatterChartDataSet.swift in Sources */,
C3F0DDB7F0A922F0BB7EDB8A /* IBarChartDataSet.swift in Sources */,
Expand Down
152 changes: 152 additions & 0 deletions Source/Charts/Filters/DataApproximator+N.swift
Original file line number Diff line number Diff line change
@@ -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
}
}


0 comments on commit b6bde08

Please sign in to comment.