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

Selectable Nodes Delegate #69

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
136 changes: 132 additions & 4 deletions Classes/ScrollableGraphView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import UIKit

// MARK: PointSelectedDelegate

@objc public protocol PointSelectedProtocol {
func pointWasSelectedAt(label:String, value:Double, location: CGPoint)
}

// MARK: - ScrollableGraphView
@IBDesignable
@objc open class ScrollableGraphView: UIScrollView, UIScrollViewDelegate, ScrollableGraphViewDrawingDelegate {
Expand Down Expand Up @@ -70,7 +76,7 @@ import UIKit
open var fillType = ScrollableGraphViewFillType.solid
/// If fillType is set to .Solid then this colour will be used to fill the graph.
@IBInspectable open var fillColor: UIColor = UIColor.black
/// If fillType is set to .Gradient then this will be the starting colour for the gradient.
/// If fillType is set to .Gradient then this will be the starting colour for the gradient.
@IBInspectable open var fillGradientStartColor: UIColor = UIColor.white
/// If fillType is set to .Gradient, then this will be the ending colour for the gradient.
@IBInspectable open var fillGradientEndColor: UIColor = UIColor.black
Expand Down Expand Up @@ -224,6 +230,9 @@ import UIKit
open var dataPointLabelFont: UIFont? = UIFont.systemFont(ofSize: 10)
/// Used to force the graph to show every n-th dataPoint label
@IBInspectable open var dataPointLabelsSparsity: Int = 1

/// Message will be sent to the delegate on point touched.
open weak var pointSelectedDelegate: PointSelectedProtocol?

// MARK: - Private State
// #####################
Expand Down Expand Up @@ -581,9 +590,7 @@ import UIKit
private func updateOffsetWidths() {
drawingView.frame.origin.x = offsetWidth
drawingView.bounds.origin.x = offsetWidth

gradientLayer?.offset = offsetWidth

referenceLineView?.frame.origin.x = offsetWidth
}

Expand All @@ -602,6 +609,123 @@ import UIKit
self.contentSize.height = viewportHeight
}

private let distanceSensitivityFromTouchPoint : CGFloat = 15

open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)

//make sure touch exists, make sure delegate is set
guard let firstTouch = touches.first, let _ = pointSelectedDelegate else { return }
let locationOfTouchPoint = firstTouch.location(in: self)

// instead of looping over all points which is inefficent
// get | --- (touch area) --- |
// get the upper point and the lower point
// if it's within the a limit range from either the upper or bottom point then select that point.
let leftDataPointIndex = Int(floor((locationOfTouchPoint.x - 50) / 80))
let rightDataPointIndex = leftDataPointIndex + 1
let lastIndexInGraphPoints = graphPoints.count - 1

// user clicks left of left most point
if leftDataPointIndex < 0 {

// right point only
let rightPoint = graphPoints[rightDataPointIndex].location

if shouldDrawBarLayer {
let barWidthToLeftOfX = rightPoint.x - (barWidth / 2)
if locationOfTouchPoint.y < rightPoint.y &&
barWidthToLeftOfX < locationOfTouchPoint.x {
triggerDelegateWith(index: rightDataPointIndex)
}

} else {
// right point only
let distanceBetweenUpperPointAndTouch = distance(a: rightPoint, b: locationOfTouchPoint)
if distanceBetweenUpperPointAndTouch < distanceSensitivityFromTouchPoint {
triggerDelegateWith(index: rightDataPointIndex)
}
}
// user clicks right of last point
} else if rightDataPointIndex > lastIndexInGraphPoints {

// left point only
let leftPoint = graphPoints[leftDataPointIndex].location

if shouldDrawBarLayer {
let barWidthToRightOfX = locationOfTouchPoint.x + (barWidth / 2)
if locationOfTouchPoint.y < leftPoint.y &&
barWidthToRightOfX > locationOfTouchPoint.x {
triggerDelegateWith(index: leftDataPointIndex)
}

} else {
let distanceBetweenLowerPointAndTouch = distance(a: leftPoint, b: locationOfTouchPoint)

if distanceBetweenLowerPointAndTouch < distanceSensitivityFromTouchPoint {
triggerDelegateWith(index: leftDataPointIndex)
}
}

// user clicks neither exception case
} else {
// left point
let leftPoint = graphPoints[leftDataPointIndex].location

// right point
let rightPoint = graphPoints[rightDataPointIndex].location

// hit detection should check the left and the right bar if user tapped there.
if shouldDrawBarLayer {

// check if the left bar was tapped
let barWidthToRightOfX = leftPoint.x + (barWidth / 2)
if locationOfTouchPoint.y > leftPoint.y &&
barWidthToRightOfX > locationOfTouchPoint.x {
triggerDelegateWith(index: leftDataPointIndex)
}

// check if the right bar was tapped
let barWidthToLeftOfX = rightPoint.x - (barWidth / 2)
if locationOfTouchPoint.y > rightPoint.y &&
barWidthToLeftOfX < locationOfTouchPoint.x {
triggerDelegateWith(index: rightDataPointIndex)
}

} else {

let distanceBetweenLeftPointAndTouch = distance(a: leftPoint, b: locationOfTouchPoint)
let distanceBetweenRightPointAndTouch = distance(a: rightPoint, b: locationOfTouchPoint)

// user pressed closer to the right point
if distanceBetweenRightPointAndTouch < distanceSensitivityFromTouchPoint {
triggerDelegateWith(index: rightDataPointIndex)

// user pressed closer to the left point
} else if distanceBetweenLeftPointAndTouch < distanceSensitivityFromTouchPoint {
triggerDelegateWith(index: leftDataPointIndex)

}
}
}
}

private func triggerDelegateWith(index: Int) {

guard 0 <= index && index <= (labels.count - 1) else { return }
guard 0 <= index && index <= (data.count - 1) else { return }
guard let pointDelegate = pointSelectedDelegate else { return }

pointDelegate.pointWasSelectedAt(label: labels[index], value: data[index], location: graphPoints[index].location)

}

private func distance(a: CGPoint, b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
}

// MARK: - Public Methods
// ######################

Expand Down Expand Up @@ -1155,7 +1279,7 @@ private class LabelPool {


// MARK: - GraphPoints and Animation Classes
private class GraphPoint {
private class GraphPoint : CustomStringConvertible {

var location = CGPoint(x: 0, y: 0)
var currentlyAnimatingToPosition = false
Expand All @@ -1182,6 +1306,10 @@ private class GraphPoint {
x = position.x
y = position.y
}

var description: String{
return "POINT: x:\(x) y:\(y)\n"
}
}

private class GraphPointAnimation : Equatable {
Expand Down
8 changes: 6 additions & 2 deletions graphview_example_ib/GraphView/GraphViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@

import UIKit

class GraphViewController: UIViewController {
class GraphViewController: UIViewController, PointSelectedProtocol {
@IBOutlet var graphView: ScrollableGraphView?
var data: [Double]?
var labels: [String]?

override func viewDidLoad() {
super.viewDidLoad()

graphView?.pointSelectedDelegate = self
guard let data = data, let labels = labels else {
return
}
graphView?.set(data: data, withLabels: labels)
}

func pointWasSelectedAt(label: String, value: Double, location: CGPoint) {
print("Point selected x:\(label) y:\(value) point:\(location)\n")
}

}