Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added DataApproximator+N extension #2848

Merged
merged 2 commits into from
Jan 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Charts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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 @@ -252,6 +253,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 @@ -530,6 +532,7 @@
isa = PBXGroup;
children = (
93EF9709CF635BEE70D1ABC5 /* DataApproximator.swift */,
9A26C8DA1F87B01700367599 /* DataApproximator+N.swift */,
);
name = Filters;
sourceTree = "<group>";
Expand Down Expand Up @@ -852,6 +855,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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The algorithm shouldn't be overridden. @objc public static func reduceWithDouglasPeukerN(_ points: [CGPoint], resultCount: Int) -> [CGPoint]

Copy link
Contributor Author

@666tos 666tos Dec 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but in current code there is
@objc open class func reduceWithDouglasPeuker(_ points: [CGPoint], tolerance: CGFloat) -> [CGPoint]

Copy link
Collaborator

@jjatie jjatie Dec 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be changed too. This is fine then. I will change to static in a separate PR.

{
// 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
}
}