├── .gitignore ├── APIv4.md ├── Classes ├── Drawing │ ├── BarDrawingLayer.swift │ ├── DotDrawingLayer.swift │ ├── FillDrawingLayer.swift │ ├── GradientDrawingLayer.swift │ ├── LineDrawingLayer.swift │ ├── ReferenceLineDrawingView.swift │ └── ScrollableGraphViewDrawingLayer.swift ├── Plots │ ├── Animation │ │ ├── GraphPoint.swift │ │ └── GraphPointAnimation.swift │ ├── BarPlot.swift │ ├── DotPlot.swift │ ├── LinePlot.swift │ └── Plot.swift ├── Protocols │ ├── ScrollableGraphViewDataSource.swift │ └── ScrollableGraphViewDrawingDelegate.swift ├── Reference │ ├── LabelPool.swift │ └── ReferenceLines.swift └── ScrollableGraphView.swift ├── GraphView ├── GraphView.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── GraphView │ ├── AppDelegate.swift │ ├── Info.plist │ └── UIColor+colorFromHex.swift ├── GraphViewCode │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-40.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-72.png │ │ │ ├── Icon-72@2x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ ├── Icon-Small-50.png │ │ │ ├── Icon-Small-50@2x.png │ │ │ ├── Icon-Small.png │ │ │ ├── Icon-Small@2x.png │ │ │ ├── Icon-Small@3x.png │ │ │ ├── Icon.png │ │ │ ├── Icon@2x.png │ │ │ ├── NotificationIcon@2x.png │ │ │ ├── NotificationIcon@3x.png │ │ │ ├── NotificationIcon~ipad.png │ │ │ ├── NotificationIcon~ipad@2x.png │ │ │ └── ios-marketing.png │ │ └── Contents.json │ ├── Examples.swift │ ├── GraphType.swift │ ├── LaunchImageCode.png │ ├── LaunchScreen.storyboard │ ├── Main.storyboard │ └── ViewController.swift └── GraphViewIB │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon.png │ │ ├── Icon@2x.png │ │ ├── NotificationIcon@2x.png │ │ ├── NotificationIcon@3x.png │ │ ├── NotificationIcon~ipad.png │ │ ├── NotificationIcon~ipad@2x.png │ │ └── ios-marketing.png │ └── Contents.json │ ├── LaunchImageIB.png │ ├── LaunchScreen.storyboard │ ├── Main.storyboard │ └── ViewController.swift ├── LICENSE ├── README.md ├── ScrollableGraphView.podspec ├── carthage └── ScrollableGraphView │ ├── ScrollableGraphView.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── ScrollableGraphView.xcscheme │ └── ScrollableGraphView │ ├── Info.plist │ └── ScrollableGraphView.h ├── examples.md └── readme_images ├── IMG_5814_small.jpg ├── adapting.gif ├── animating.gif ├── customising.gif ├── dynamic-reload.gif ├── gallery-v4 ├── bar-dark.png ├── dot.png ├── line-dark-smooth.png ├── line-pink-straight.png ├── multi1.png ├── multi2.png └── simple.png ├── gallery ├── bar.png ├── dark.png ├── default.png ├── dot.png ├── pink_margins.png └── pink_mountain.png ├── init_anim_high_fps.gif ├── more_scrolling.gif ├── scrolling.gif ├── simple.png └── spacing.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | # macOS 26 | 27 | *.DS_Store 28 | -------------------------------------------------------------------------------- /APIv4.md: -------------------------------------------------------------------------------- 1 | # ScrollableGraphView Major API Changes in v4 2 | 3 | # Overview 4 | 5 | The older versions of the graph have multiple issues: 6 | 7 | - Setting data is a one time event that happens before the graph is shown, making it hard to update the data if it happens to change. Currently the entire graph has to be recreated. 8 | - Multiple plots on a single graph are unsupported. 9 | - Reference lines have limited customisation options and user specified locations for reference lines is lacking and clumsy. 10 | - Approximately 60 settings all specified as public properties on a single class, `ScrollableGraphView`. This makes both maintenance and adding new features difficult to achieve. 11 | 12 | The new API aims to resolve these issues by: 13 | 14 | - Refactoring the current monolithic graph into multiple files and appropriate classes. 15 | - Using common delegate based patterns as a more robust way of providing the graph with data for multiple plots. 16 | - Reworking the way reference lines are specified and added to the graph. 17 | - Providing a new method of configuring the graph, separating the code for the appearance of the graph and the data for the graph. 18 | 19 | ## Contents 20 | 21 | - [API - ScrollableGraphView](#api---scrollablegraphview-class) 22 | - [API - ScrollableGraphViewDataSource](#api---scrollablegraphviewdatasource-protocol) 23 | - [API - ReferenceLines](#api---referencelines-class) 24 | - [Example Usage](#example-usage) 25 | - [Creating a Graph and Configuring it Programmatically](#creating-a-graph-and-configuring-it-programmatically) 26 | - [List of New Protocols and Types](#list-of-new-protocols-and-types) 27 | 28 | # API - ScrollableGraphView Class 29 | 30 | ## Creating a Graph 31 | 32 | ```swift 33 | init(frame: CGRect, dataSource: ScrollableGraphViewDataSource) 34 | ``` 35 | 36 | Returns a graph instance. The data source for the graph is an object which conforms to the `GraphViewDataSource` protocol. 37 | 38 | ## Adding 39 | 40 | ```swift 41 | func addPlot(plot: Plot) 42 | ``` 43 | 44 | Adds a plot to the graph. Can be called multiple times to add multiple plots. The `identifier` for the plot is passed to the `dataSource` delegate when requesting data. 45 | 46 | ## Adding Reference Lines to the Graph 47 | 48 | ```swift 49 | func addReferenceLines(referenceLines: ReferenceLines) 50 | ``` 51 | 52 | Adds an instance of ReferenceLines to the graph. Multiple calls will override the previous reference lines. 53 | 54 | ## Giving the Graph Data 55 | 56 | ```swift 57 | var dataSource: ScrollableGraphViewDataSource 58 | ``` 59 | 60 | The data source delegate which provides the graph data. This object must conform to the `GraphViewDataSource` protocol by implementing the following three methods. 61 | 62 | ```swift 63 | func reload() 64 | ``` 65 | 66 | Causes the graph to recall the delegate functions, to refetch the data. 67 | 68 | # API - ScrollableGraphViewDataSource Protocol 69 | 70 | ```swift 71 | func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double 72 | ``` 73 | 74 | Provides the y-axis value for a given x-axis index. 75 | 76 | ```swift 77 | func label(atIndex pointIndex: Int) -> String 78 | ``` 79 | 80 | Provides the label that will appear on the x-axis below the point. 81 | 82 | ```swift 83 | func numberOfPoints(forPlot plot: Plot) -> Int 84 | ``` 85 | 86 | Provides the number of points for each each plot. 87 | 88 | # API - ReferenceLines Class 89 | 90 | ## New Customisation Options for Reference Lines 91 | 92 | ```swift 93 | var positionType: ReferenceLinePositionType 94 | ``` 95 | 96 | Specifies whether references lines are positioned used percentages or absolute values. 97 | 98 | ```swift 99 | var relativePositions: [Double] 100 | ``` 101 | 102 | An array of positions specified in percentages where the reference lines will be placed. For example, a value of `[0, 0.5, 0.8, 0.9, 1]` would render 5 reference lines at 0%, 50%, 80%, 90% and 100% of the `rangeMax`. 103 | 104 | ```swift 105 | var absolutePositions: [Double] 106 | ``` 107 | 108 | An array of positions specified in absolute values where the reference lines will be rendered. 109 | 110 | ## Example Usage 111 | 112 | ### Creating a Graph and Configuring it Programmatically 113 | 114 | ```ViewController.swift``` 115 | 116 | ```swift 117 | class ViewController: UIViewController, ScrollableGraphViewDataSource { 118 | 119 | // Class members and init... 120 | 121 | var linePlotData: [Double] = // data for line plot 122 | var barPlotData: [Double] = // data for bar plot 123 | var xAxisLabels: [String] = // the labels along the x axis 124 | 125 | override func viewDidLoad() { 126 | super.viewDidLoad() 127 | 128 | let graph = ScrollableGraphView(frame: self.view.frame, dataSource: self) 129 | 130 | // Graph Configuration 131 | // ################### 132 | 133 | graph.backgroundColor = UIColor.white 134 | graph.shouldAnimateOnStartup = true 135 | 136 | // Reference Lines 137 | // ############### 138 | 139 | let referenceLines = ReferenceLines() 140 | referenceLines.positionType = .relative 141 | referenceLines.relativePositions = [0, 0.5, 0.8, 0.9, 1] 142 | 143 | graph.addReferenceLines(referenceLines: referenceLines) 144 | 145 | // Adding Plots 146 | // ############ 147 | 148 | let linePlot = LinePlot(identifier: "linePlot") 149 | linePlot.lineWidth = 5 150 | linePlot.color = UIColor.black 151 | 152 | let barPlot = BarPlot(identifier: "barPlot") 153 | barPlot.barWidth = 20 154 | barPlot.barFillColor = UIColor.black 155 | barPlot.barOutlineColor = UIColor.gray 156 | 157 | graph.addPlot(plot: linePlot) 158 | graph.addPlot(plot: barPlot) 159 | 160 | self.view.addSubview(graph) 161 | } 162 | 163 | // Other class methods... 164 | 165 | // Implementation for ScrollableGraphViewDataSource protocol 166 | func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double { 167 | switch(plot.name) { 168 | case "linePlot": 169 | return linePlotData[pointIndex] 170 | break 171 | case "barPlot": 172 | return barPlotData[pointIndex] 173 | break 174 | } 175 | } 176 | 177 | func label(atIndex pointIndex: Int) -> String { 178 | return xAxisLabels[pointIndex] 179 | } 180 | 181 | func numberOfPoints() -> Int { 182 | return numberOfPointsInGraph 183 | } 184 | } 185 | ``` 186 | 187 | # List of New Protocols and Types 188 | 189 | ## New Protocols 190 | 191 | `ScrollableGraphViewDataSource` 192 | 193 | ## New Types 194 | 195 | `Plot` 196 | 197 | `ReferenceLines` 198 | -------------------------------------------------------------------------------- /Classes/Drawing/BarDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | // MARK: Drawing the bars 5 | internal class BarDrawingLayer: ScrollableGraphViewDrawingLayer { 6 | 7 | private var barPath = UIBezierPath() 8 | private var barWidth: CGFloat = 4 9 | private var shouldRoundCorners = false 10 | 11 | init(frame: CGRect, barWidth: CGFloat, barColor: UIColor, barLineWidth: CGFloat, barLineColor: UIColor, shouldRoundCorners: Bool) { 12 | super.init(viewportWidth: frame.size.width, viewportHeight: frame.size.height) 13 | 14 | self.barWidth = barWidth 15 | self.lineWidth = barLineWidth 16 | self.strokeColor = barLineColor.cgColor 17 | self.fillColor = barColor.cgColor 18 | self.shouldRoundCorners = shouldRoundCorners 19 | 20 | self.lineJoin = lineJoin 21 | self.lineCap = lineCap 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | private func createBarPath(centre: CGPoint) -> UIBezierPath { 29 | 30 | let barWidthOffset: CGFloat = self.barWidth / 2 31 | 32 | let origin = CGPoint(x: centre.x - barWidthOffset, y: centre.y) 33 | let size = CGSize(width: barWidth, height: zeroYPosition - centre.y) 34 | let rect = CGRect(origin: origin, size: size) 35 | 36 | let barPath: UIBezierPath = { 37 | if shouldRoundCorners { 38 | return UIBezierPath(roundedRect: rect, cornerRadius: barWidthOffset) 39 | } else { 40 | return UIBezierPath(rect: rect) 41 | } 42 | }() 43 | 44 | return barPath 45 | } 46 | 47 | private func createPath () -> UIBezierPath { 48 | 49 | barPath.removeAllPoints() 50 | 51 | // We can only move forward if we can get the data we need from the delegate. 52 | guard let 53 | activePointsInterval = self.owner?.graphViewDrawingDelegate?.intervalForActivePoints() 54 | else { 55 | return barPath 56 | } 57 | 58 | for i in activePointsInterval { 59 | 60 | var location = CGPoint.zero 61 | 62 | if let pointLocation = owner?.graphPoint(forIndex: i).location { 63 | location = pointLocation 64 | } 65 | 66 | let pointPath = createBarPath(centre: location) 67 | barPath.append(pointPath) 68 | } 69 | 70 | return barPath 71 | } 72 | 73 | override func updatePath() { 74 | 75 | self.path = createPath ().cgPath 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Classes/Drawing/DotDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class DotDrawingLayer: ScrollableGraphViewDrawingLayer { 5 | 6 | private var dataPointPath = UIBezierPath() 7 | private var dataPointSize: CGFloat = 5 8 | private var dataPointType: ScrollableGraphViewDataPointType = .circle 9 | 10 | private var customDataPointPath: ((_ centre: CGPoint) -> UIBezierPath)? 11 | 12 | init(frame: CGRect, fillColor: UIColor, dataPointType: ScrollableGraphViewDataPointType, dataPointSize: CGFloat, customDataPointPath: ((_ centre: CGPoint) -> UIBezierPath)? = nil) { 13 | 14 | self.dataPointType = dataPointType 15 | self.dataPointSize = dataPointSize 16 | self.customDataPointPath = customDataPointPath 17 | 18 | super.init(viewportWidth: frame.size.width, viewportHeight: frame.size.height) 19 | 20 | self.fillColor = fillColor.cgColor 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | private func createDataPointPath() -> UIBezierPath { 28 | 29 | dataPointPath.removeAllPoints() 30 | 31 | // We can only move forward if we can get the data we need from the delegate. 32 | guard let 33 | activePointsInterval = self.owner?.graphViewDrawingDelegate?.intervalForActivePoints() 34 | else { 35 | return dataPointPath 36 | } 37 | 38 | let pointPathCreator = getPointPathCreator() 39 | 40 | for i in activePointsInterval { 41 | 42 | var location = CGPoint.zero 43 | 44 | if let pointLocation = owner?.graphPoint(forIndex: i).location { 45 | location = pointLocation 46 | } 47 | 48 | let pointPath = pointPathCreator(location) 49 | dataPointPath.append(pointPath) 50 | } 51 | 52 | return dataPointPath 53 | } 54 | 55 | private func createCircleDataPoint(centre: CGPoint) -> UIBezierPath { 56 | return UIBezierPath(arcCenter: centre, radius: dataPointSize, startAngle: 0, endAngle: CGFloat(2.0 * Double.pi), clockwise: true) 57 | } 58 | 59 | private func createSquareDataPoint(centre: CGPoint) -> UIBezierPath { 60 | 61 | let squarePath = UIBezierPath() 62 | 63 | squarePath.move(to: centre) 64 | 65 | let topLeft = CGPoint(x: centre.x - dataPointSize, y: centre.y - dataPointSize) 66 | let topRight = CGPoint(x: centre.x + dataPointSize, y: centre.y - dataPointSize) 67 | let bottomLeft = CGPoint(x: centre.x - dataPointSize, y: centre.y + dataPointSize) 68 | let bottomRight = CGPoint(x: centre.x + dataPointSize, y: centre.y + dataPointSize) 69 | 70 | squarePath.move(to: topLeft) 71 | squarePath.addLine(to: topRight) 72 | squarePath.addLine(to: bottomRight) 73 | squarePath.addLine(to: bottomLeft) 74 | squarePath.addLine(to: topLeft) 75 | 76 | return squarePath 77 | } 78 | 79 | private func getPointPathCreator() -> (_ centre: CGPoint) -> UIBezierPath { 80 | switch(self.dataPointType) { 81 | case .circle: 82 | return createCircleDataPoint 83 | case .square: 84 | return createSquareDataPoint 85 | case .custom: 86 | if let customCreator = self.customDataPointPath { 87 | return customCreator 88 | } 89 | else { 90 | // We don't have a custom path, so just return the default. 91 | fallthrough 92 | } 93 | default: 94 | return createCircleDataPoint 95 | } 96 | } 97 | 98 | override func updatePath() { 99 | self.path = createDataPointPath().cgPath 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Classes/Drawing/FillDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class FillDrawingLayer : ScrollableGraphViewDrawingLayer { 5 | 6 | // Fills are only used with lineplots and we need 7 | // to know what the line looks like. 8 | private var lineDrawingLayer: LineDrawingLayer 9 | 10 | init(frame: CGRect, fillColor: UIColor, lineDrawingLayer: LineDrawingLayer) { 11 | 12 | self.lineDrawingLayer = lineDrawingLayer 13 | super.init(viewportWidth: frame.size.width, viewportHeight: frame.size.height) 14 | self.fillColor = fillColor.cgColor 15 | } 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | override func updatePath() { 22 | self.path = lineDrawingLayer.createLinePath().cgPath 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Classes/Drawing/GradientDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class GradientDrawingLayer : ScrollableGraphViewDrawingLayer { 5 | 6 | private var startColor: UIColor 7 | private var endColor: UIColor 8 | private var gradientType: ScrollableGraphViewGradientType 9 | 10 | // Gradient fills are only used with lineplots and we need 11 | // to know what the line looks like. 12 | private var lineDrawingLayer: LineDrawingLayer 13 | 14 | lazy private var gradientMask: CAShapeLayer = ({ 15 | let mask = CAShapeLayer() 16 | 17 | mask.frame = CGRect(x: 0, y: 0, width: self.viewportWidth, height: self.viewportHeight) 18 | mask.fillRule = CAShapeLayerFillRule.evenOdd 19 | mask.lineJoin = self.lineJoin 20 | 21 | return mask 22 | })() 23 | 24 | init(frame: CGRect, startColor: UIColor, endColor: UIColor, gradientType: ScrollableGraphViewGradientType, lineJoin: String = convertFromCAShapeLayerLineJoin(CAShapeLayerLineJoin.round), lineDrawingLayer: LineDrawingLayer) { 25 | self.startColor = startColor 26 | self.endColor = endColor 27 | self.gradientType = gradientType 28 | //self.lineJoin = lineJoin 29 | 30 | self.lineDrawingLayer = lineDrawingLayer 31 | 32 | super.init(viewportWidth: frame.size.width, viewportHeight: frame.size.height) 33 | 34 | addMaskLayer() 35 | self.setNeedsDisplay() 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | private func addMaskLayer() { 43 | self.mask = gradientMask 44 | } 45 | 46 | override func updatePath() { 47 | gradientMask.path = lineDrawingLayer.createLinePath().cgPath 48 | } 49 | 50 | override func draw(in ctx: CGContext) { 51 | 52 | let colors = [startColor.cgColor, endColor.cgColor] 53 | let colorSpace = CGColorSpaceCreateDeviceRGB() 54 | let locations: [CGFloat] = [0.0, 1.0] 55 | let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) 56 | 57 | let displacement = ((viewportWidth / viewportHeight) / 2.5) * self.bounds.height 58 | let topCentre = CGPoint(x: offset + self.bounds.width / 2, y: -displacement) 59 | let bottomCentre = CGPoint(x: offset + self.bounds.width / 2, y: self.bounds.height) 60 | let startRadius: CGFloat = 0 61 | let endRadius: CGFloat = self.bounds.width 62 | 63 | switch(gradientType) { 64 | case .linear: 65 | ctx.drawLinearGradient(gradient!, start: topCentre, end: bottomCentre, options: .drawsAfterEndLocation) 66 | case .radial: 67 | ctx.drawRadialGradient(gradient!, startCenter: topCentre, startRadius: startRadius, endCenter: topCentre, endRadius: endRadius, options: .drawsAfterEndLocation) 68 | } 69 | } 70 | } 71 | 72 | // Helper function inserted by Swift 4.2 migrator. 73 | fileprivate func convertFromCAShapeLayerLineJoin(_ input: CAShapeLayerLineJoin) -> String { 74 | return input.rawValue 75 | } 76 | -------------------------------------------------------------------------------- /Classes/Drawing/LineDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class LineDrawingLayer : ScrollableGraphViewDrawingLayer { 5 | 6 | private var currentLinePath = UIBezierPath() 7 | 8 | private var lineStyle: ScrollableGraphViewLineStyle 9 | private var shouldFill: Bool 10 | private var lineCurviness: CGFloat 11 | 12 | init(frame: CGRect, lineWidth: CGFloat, lineColor: UIColor, lineStyle: ScrollableGraphViewLineStyle, lineJoin: String, lineCap: String, shouldFill: Bool, lineCurviness: CGFloat) { 13 | 14 | self.lineStyle = lineStyle 15 | self.shouldFill = shouldFill 16 | self.lineCurviness = lineCurviness 17 | 18 | super.init(viewportWidth: frame.size.width, viewportHeight: frame.size.height) 19 | 20 | self.lineWidth = lineWidth 21 | self.strokeColor = lineColor.cgColor 22 | 23 | self.lineJoin = convertToCAShapeLayerLineJoin(lineJoin) 24 | self.lineCap = convertToCAShapeLayerLineCap(lineCap) 25 | 26 | // Setup 27 | self.fillColor = UIColor.clear.cgColor // This is handled by the fill drawing layer. 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | internal func createLinePath() -> UIBezierPath { 35 | 36 | guard let owner = owner else { 37 | return UIBezierPath() 38 | } 39 | 40 | // Can't really do anything without the delegate. 41 | guard let delegate = self.owner?.graphViewDrawingDelegate else { 42 | return currentLinePath 43 | } 44 | 45 | currentLinePath.removeAllPoints() 46 | 47 | let pathSegmentAdder = lineStyle == .straight ? addStraightLineSegment : addCurvedLineSegment 48 | 49 | let activePointsInterval = delegate.intervalForActivePoints() 50 | 51 | let pointPadding = delegate.paddingForPoints() 52 | 53 | let min = delegate.rangeForActivePoints().min 54 | zeroYPosition = delegate.calculatePosition(atIndex: 0, value: min).y 55 | 56 | let viewport = delegate.currentViewport() 57 | let viewportWidth = viewport.width 58 | let viewportHeight = viewport.height 59 | 60 | // Connect the line to the starting edge if we are filling it. 61 | if(shouldFill) { 62 | // Add a line from the base of the graph to the first data point. 63 | let firstDataPoint = owner.graphPoint(forIndex: activePointsInterval.lowerBound) 64 | 65 | let viewportLeftZero = CGPoint(x: firstDataPoint.location.x - (pointPadding.leftmostPointPadding), y: zeroYPosition) 66 | let leftFarEdgeTop = CGPoint(x: firstDataPoint.location.x - (pointPadding.leftmostPointPadding + viewportWidth), y: zeroYPosition) 67 | let leftFarEdgeBottom = CGPoint(x: firstDataPoint.location.x - (pointPadding.leftmostPointPadding + viewportWidth), y: viewportHeight) 68 | 69 | currentLinePath.move(to: leftFarEdgeBottom) 70 | pathSegmentAdder(leftFarEdgeBottom, leftFarEdgeTop, currentLinePath) 71 | pathSegmentAdder(leftFarEdgeTop, viewportLeftZero, currentLinePath) 72 | pathSegmentAdder(viewportLeftZero, CGPoint(x: firstDataPoint.location.x, y: firstDataPoint.location.y), currentLinePath) 73 | } 74 | else { 75 | let firstDataPoint = owner.graphPoint(forIndex: activePointsInterval.lowerBound) 76 | currentLinePath.move(to: firstDataPoint.location) 77 | } 78 | 79 | // Connect each point on the graph with a segment. 80 | for i in activePointsInterval.lowerBound ..< activePointsInterval.upperBound - 1 { 81 | 82 | let startPoint = owner.graphPoint(forIndex: i).location 83 | let endPoint = owner.graphPoint(forIndex: i+1).location 84 | 85 | pathSegmentAdder(startPoint, endPoint, currentLinePath) 86 | } 87 | 88 | // Connect the line to the ending edge if we are filling it. 89 | if(shouldFill) { 90 | // Add a line from the last data point to the base of the graph. 91 | let lastDataPoint = owner.graphPoint(forIndex: activePointsInterval.upperBound - 1).location 92 | 93 | let viewportRightZero = CGPoint(x: lastDataPoint.x + (pointPadding.rightmostPointPadding), y: zeroYPosition) 94 | let rightFarEdgeTop = CGPoint(x: lastDataPoint.x + (pointPadding.rightmostPointPadding + viewportWidth), y: zeroYPosition) 95 | let rightFarEdgeBottom = CGPoint(x: lastDataPoint.x + (pointPadding.rightmostPointPadding + viewportWidth), y: viewportHeight) 96 | 97 | pathSegmentAdder(lastDataPoint, viewportRightZero, currentLinePath) 98 | pathSegmentAdder(viewportRightZero, rightFarEdgeTop, currentLinePath) 99 | pathSegmentAdder(rightFarEdgeTop, rightFarEdgeBottom, currentLinePath) 100 | } 101 | 102 | return currentLinePath 103 | } 104 | 105 | private func addStraightLineSegment(startPoint: CGPoint, endPoint: CGPoint, inPath path: UIBezierPath) { 106 | path.addLine(to: endPoint) 107 | } 108 | 109 | private func addCurvedLineSegment(startPoint: CGPoint, endPoint: CGPoint, inPath path: UIBezierPath) { 110 | // calculate control points 111 | let difference = endPoint.x - startPoint.x 112 | 113 | var x = startPoint.x + (difference * lineCurviness) 114 | var y = startPoint.y 115 | let controlPointOne = CGPoint(x: x, y: y) 116 | 117 | x = endPoint.x - (difference * lineCurviness) 118 | y = endPoint.y 119 | let controlPointTwo = CGPoint(x: x, y: y) 120 | 121 | // add curve from start to end 122 | currentLinePath.addCurve(to: endPoint, controlPoint1: controlPointOne, controlPoint2: controlPointTwo) 123 | } 124 | 125 | override func updatePath() { 126 | self.path = createLinePath().cgPath 127 | } 128 | } 129 | 130 | // Helper function inserted by Swift 4.2 migrator. 131 | fileprivate func convertToCAShapeLayerLineJoin(_ input: String) -> CAShapeLayerLineJoin { 132 | return CAShapeLayerLineJoin(rawValue: input) 133 | } 134 | 135 | // Helper function inserted by Swift 4.2 migrator. 136 | fileprivate func convertToCAShapeLayerLineCap(_ input: String) -> CAShapeLayerLineCap { 137 | return CAShapeLayerLineCap(rawValue: input) 138 | } 139 | -------------------------------------------------------------------------------- /Classes/Drawing/ReferenceLineDrawingView.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class ReferenceLineDrawingView : UIView { 5 | 6 | var settings: ReferenceLines = ReferenceLines() 7 | 8 | // PRIVATE PROPERTIES 9 | // ################## 10 | 11 | private var labelMargin: CGFloat = 4 12 | private var leftLabelInset: CGFloat = 10 13 | private var rightLabelInset: CGFloat = 10 14 | 15 | // Store information about the ScrollableGraphView 16 | private var currentRange: (min: Double, max: Double) = (0,100) 17 | private var topMargin: CGFloat = 10 18 | private var bottomMargin: CGFloat = 10 19 | 20 | private var lineWidth: CGFloat { 21 | get { 22 | return self.bounds.width 23 | } 24 | } 25 | 26 | private var units: String { 27 | get { 28 | if let units = self.settings.referenceLineUnits { 29 | return " \(units)" 30 | } else { 31 | return "" 32 | } 33 | } 34 | } 35 | 36 | // Layers 37 | private var labels = [UILabel]() 38 | private let referenceLineLayer = CAShapeLayer() 39 | private let referenceLinePath = UIBezierPath() 40 | 41 | init(frame: CGRect, topMargin: CGFloat, bottomMargin: CGFloat, referenceLineColor: UIColor, referenceLineThickness: CGFloat, referenceLineSettings: ReferenceLines) { 42 | super.init(frame: frame) 43 | 44 | self.topMargin = topMargin 45 | self.bottomMargin = bottomMargin 46 | 47 | // The reference line layer draws the reference lines and we handle the labels elsewhere. 48 | self.referenceLineLayer.frame = self.frame 49 | self.referenceLineLayer.strokeColor = referenceLineColor.cgColor 50 | self.referenceLineLayer.lineWidth = referenceLineThickness 51 | 52 | self.settings = referenceLineSettings 53 | 54 | self.layer.addSublayer(referenceLineLayer) 55 | } 56 | 57 | required init?(coder aDecoder: NSCoder) { 58 | fatalError("init(coder:) has not been implemented") 59 | } 60 | 61 | private func createLabel(at position: CGPoint, withText text: String) -> UILabel { 62 | let frame = CGRect(x: position.x, y: position.y, width: 0, height: 0) 63 | let label = UILabel(frame: frame) 64 | 65 | return label 66 | } 67 | 68 | private func createReferenceLinesPath() -> UIBezierPath { 69 | 70 | referenceLinePath.removeAllPoints() 71 | for label in labels { 72 | label.removeFromSuperview() 73 | } 74 | labels.removeAll() 75 | 76 | if(self.settings.includeMinMax) { 77 | let maxLineStart = CGPoint(x: 0, y: topMargin) 78 | let maxLineEnd = CGPoint(x: lineWidth, y: topMargin) 79 | 80 | let minLineStart = CGPoint(x: 0, y: self.bounds.height - bottomMargin) 81 | let minLineEnd = CGPoint(x: lineWidth, y: self.bounds.height - bottomMargin) 82 | 83 | let numberFormatter = referenceNumberFormatter() 84 | 85 | let maxString = numberFormatter.string(from: self.currentRange.max as NSNumber)! + units 86 | let minString = numberFormatter.string(from: self.currentRange.min as NSNumber)! + units 87 | 88 | addLine(withTag: maxString, from: maxLineStart, to: maxLineEnd, in: referenceLinePath) 89 | addLine(withTag: minString, from: minLineStart, to: minLineEnd, in: referenceLinePath) 90 | } 91 | 92 | let initialRect = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y + topMargin, width: self.bounds.size.width, height: self.bounds.size.height - (topMargin + bottomMargin)) 93 | 94 | switch(settings.positionType) { 95 | case .relative: 96 | createReferenceLines(in: initialRect, atRelativePositions: self.settings.relativePositions, forPath: referenceLinePath) 97 | case .absolute: 98 | createReferenceLines(in: initialRect, atAbsolutePositions: self.settings.absolutePositions, forPath: referenceLinePath) 99 | } 100 | 101 | return referenceLinePath 102 | } 103 | 104 | private func referenceNumberFormatter() -> NumberFormatter { 105 | let numberFormatter = NumberFormatter() 106 | numberFormatter.numberStyle = self.settings.referenceLineNumberStyle 107 | numberFormatter.minimumFractionDigits = self.settings.referenceLineNumberOfDecimalPlaces 108 | numberFormatter.maximumFractionDigits = self.settings.referenceLineNumberOfDecimalPlaces 109 | 110 | return numberFormatter 111 | } 112 | 113 | private func createReferenceLines(in rect: CGRect, atRelativePositions relativePositions: [Double], forPath path: UIBezierPath) { 114 | 115 | let height = rect.size.height 116 | var relativePositions = relativePositions 117 | 118 | // If we are including the min and max already need to make sure we don't redraw them. 119 | if(self.settings.includeMinMax) { 120 | relativePositions = relativePositions.filter({ (x:Double) -> Bool in 121 | return (x != 0 && x != 1) 122 | }) 123 | } 124 | 125 | for relativePosition in relativePositions { 126 | 127 | let yPosition = height * CGFloat(1 - relativePosition) 128 | 129 | let lineStart = CGPoint(x: 0, y: rect.origin.y + yPosition) 130 | let lineEnd = CGPoint(x: lineStart.x + lineWidth, y: lineStart.y) 131 | 132 | createReferenceLineFrom(from: lineStart, to: lineEnd, in: path) 133 | } 134 | } 135 | 136 | private func createReferenceLines(in rect: CGRect, atAbsolutePositions absolutePositions: [Double], forPath path: UIBezierPath) { 137 | 138 | for absolutePosition in absolutePositions { 139 | 140 | let yPosition = calculateYPositionForYAxisValue(value: absolutePosition) 141 | 142 | // don't need to add rect.origin.y to yPosition like we do for relativePositions, 143 | // as we calculate the position for the y axis value in the previous line, 144 | // this already takes into account margins, etc. 145 | let lineStart = CGPoint(x: 0, y: yPosition) 146 | let lineEnd = CGPoint(x: lineStart.x + lineWidth, y: lineStart.y) 147 | 148 | createReferenceLineFrom(from: lineStart, to: lineEnd, in: path) 149 | } 150 | } 151 | 152 | private func createReferenceLineFrom(from lineStart: CGPoint, to lineEnd: CGPoint, in path: UIBezierPath) { 153 | if(self.settings.shouldAddLabelsToIntermediateReferenceLines) { 154 | 155 | let value = calculateYAxisValue(for: lineStart) 156 | let numberFormatter = referenceNumberFormatter() 157 | var valueString = numberFormatter.string(from: value as NSNumber)! 158 | 159 | if(self.settings.shouldAddUnitsToIntermediateReferenceLineLabels) { 160 | valueString += " \(units)" 161 | } 162 | 163 | addLine(withTag: valueString, from: lineStart, to: lineEnd, in: path) 164 | 165 | } else { 166 | addLine(from: lineStart, to: lineEnd, in: path) 167 | } 168 | } 169 | 170 | private func addLine(withTag tag: String, from: CGPoint, to: CGPoint, in path: UIBezierPath) { 171 | 172 | let boundingSize = self.boundingSize(forText: tag) 173 | let leftLabel = createLabel(withText: tag) 174 | let rightLabel = createLabel(withText: tag) 175 | 176 | // Left label gap. 177 | leftLabel.frame = CGRect( 178 | origin: CGPoint(x: from.x + leftLabelInset, y: from.y - (boundingSize.height / 2)), 179 | size: boundingSize) 180 | 181 | let leftLabelStart = CGPoint(x: leftLabel.frame.origin.x - labelMargin, y: to.y) 182 | let leftLabelEnd = CGPoint(x: (leftLabel.frame.origin.x + leftLabel.frame.size.width) + labelMargin, y: to.y) 183 | 184 | // Right label gap. 185 | rightLabel.frame = CGRect( 186 | origin: CGPoint(x: (from.x + self.frame.width) - rightLabelInset - boundingSize.width, y: from.y - (boundingSize.height / 2)), 187 | size: boundingSize) 188 | 189 | let rightLabelStart = CGPoint(x: rightLabel.frame.origin.x - labelMargin, y: to.y) 190 | let rightLabelEnd = CGPoint(x: (rightLabel.frame.origin.x + rightLabel.frame.size.width) + labelMargin, y: to.y) 191 | 192 | // Add the lines and tags depending on the settings for where we want them. 193 | var gaps = [(start: CGFloat, end: CGFloat)]() 194 | 195 | switch(self.settings.referenceLinePosition) { 196 | 197 | case .left: 198 | gaps.append((start: leftLabelStart.x, end: leftLabelEnd.x)) 199 | self.addSubview(leftLabel) 200 | self.labels.append(leftLabel) 201 | 202 | case .right: 203 | gaps.append((start: rightLabelStart.x, end: rightLabelEnd.x)) 204 | self.addSubview(rightLabel) 205 | self.labels.append(rightLabel) 206 | 207 | case .both: 208 | gaps.append((start: leftLabelStart.x, end: leftLabelEnd.x)) 209 | gaps.append((start: rightLabelStart.x, end: rightLabelEnd.x)) 210 | self.addSubview(leftLabel) 211 | self.addSubview(rightLabel) 212 | self.labels.append(leftLabel) 213 | self.labels.append(rightLabel) 214 | } 215 | 216 | addLine(from: from, to: to, withGaps: gaps, in: path) 217 | } 218 | 219 | private func addLine(from: CGPoint, to: CGPoint, withGaps gaps: [(start: CGFloat, end: CGFloat)], in path: UIBezierPath) { 220 | 221 | // If there are no gaps, just add a single line. 222 | if(gaps.count <= 0) { 223 | addLine(from: from, to: to, in: path) 224 | } 225 | // If there is only 1 gap, it's just two lines. 226 | else if (gaps.count == 1) { 227 | 228 | let gapLeft = CGPoint(x: gaps.first!.start, y: from.y) 229 | let gapRight = CGPoint(x: gaps.first!.end, y: from.y) 230 | 231 | addLine(from: from, to: gapLeft, in: path) 232 | addLine(from: gapRight, to: to, in: path) 233 | } 234 | // If there are many gaps, we have a series of intermediate lines. 235 | else { 236 | 237 | let firstGap = gaps.first! 238 | let lastGap = gaps.last! 239 | 240 | let firstGapLeft = CGPoint(x: firstGap.start, y: from.y) 241 | let lastGapRight = CGPoint(x: lastGap.end, y: to.y) 242 | 243 | // Add the first line to the start of the first gap 244 | addLine(from: from, to: firstGapLeft, in: path) 245 | 246 | // Add lines between all intermediate gaps 247 | for i in 0 ..< gaps.count - 1 { 248 | 249 | let startGapEnd = gaps[i].end 250 | let endGapStart = gaps[i + 1].start 251 | 252 | let lineStart = CGPoint(x: startGapEnd, y: from.y) 253 | let lineEnd = CGPoint(x: endGapStart, y: from.y) 254 | 255 | addLine(from: lineStart, to: lineEnd, in: path) 256 | } 257 | 258 | // Add the final line to the end 259 | addLine(from: lastGapRight, to: to, in: path) 260 | } 261 | } 262 | 263 | private func addLine(from: CGPoint, to: CGPoint, in path: UIBezierPath) { 264 | path.move(to: from) 265 | path.addLine(to: to) 266 | } 267 | 268 | private func boundingSize(forText text: String) -> CGSize { 269 | return (text as NSString).size(withAttributes: 270 | [NSAttributedString.Key.font:self.settings.referenceLineLabelFont]) 271 | } 272 | 273 | private func calculateYAxisValue(for point: CGPoint) -> Double { 274 | 275 | let graphHeight = self.frame.size.height - (topMargin + bottomMargin) 276 | 277 | // value = the corresponding value on the graph for any y co-ordinate in the view 278 | // y - t y = the y co-ordinate in the view for which we want to know the corresponding value on the graph 279 | // value = --------- * (min - max) + max t = the top margin 280 | // h h = the height of the graph space without margins 281 | // min = the range's current mininum 282 | // max = the range's current maximum 283 | 284 | var value = (((point.y - topMargin) / (graphHeight)) * CGFloat((self.currentRange.min - self.currentRange.max))) + CGFloat(self.currentRange.max) 285 | 286 | // Sometimes results in "negative zero" 287 | if(value == 0) { 288 | value = 0 289 | } 290 | 291 | return Double(value) 292 | } 293 | 294 | private func calculateYPositionForYAxisValue(value: Double) -> CGFloat { 295 | 296 | // Just an algebraic re-arrangement of calculateYAxisValue 297 | let graphHeight = self.frame.size.height - (topMargin + bottomMargin) 298 | var y = ((CGFloat(value - self.currentRange.max) / CGFloat(self.currentRange.min - self.currentRange.max)) * graphHeight) + topMargin 299 | 300 | if (y == 0) { 301 | y = 0 302 | } 303 | 304 | return y 305 | } 306 | 307 | private func createLabel(withText text: String) -> UILabel { 308 | let label = UILabel() 309 | 310 | label.text = text 311 | label.textColor = self.settings.referenceLineLabelColor 312 | label.font = self.settings.referenceLineLabelFont 313 | 314 | return label 315 | } 316 | 317 | // Public functions to update the reference lines with any changes to the range and viewport (phone rotation, etc). 318 | // When the range changes, need to update the max for the new range, then update all the labels that are showing for the axis and redraw the reference lines. 319 | func set(range: (min: Double, max: Double)) { 320 | self.currentRange = range 321 | self.referenceLineLayer.path = createReferenceLinesPath().cgPath 322 | } 323 | 324 | func set(viewportWidth: CGFloat, viewportHeight: CGFloat) { 325 | self.frame.size.width = viewportWidth 326 | self.frame.size.height = viewportHeight 327 | self.referenceLineLayer.path = createReferenceLinesPath().cgPath 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Classes/Drawing/ScrollableGraphViewDrawingLayer.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class ScrollableGraphViewDrawingLayer : CAShapeLayer { 5 | 6 | var offset: CGFloat = 0 { 7 | didSet { 8 | offsetDidChange() 9 | } 10 | } 11 | 12 | var viewportWidth: CGFloat = 0 13 | var viewportHeight: CGFloat = 0 14 | var zeroYPosition: CGFloat = 0 15 | 16 | weak var owner: Plot? 17 | 18 | var active = true 19 | 20 | init(viewportWidth: CGFloat, viewportHeight: CGFloat, offset: CGFloat = 0) { 21 | super.init() 22 | 23 | self.viewportWidth = viewportWidth 24 | self.viewportHeight = viewportHeight 25 | 26 | self.frame = CGRect(origin: CGPoint(x: offset, y: 0), size: CGSize(width: self.viewportWidth, height: self.viewportHeight)) 27 | 28 | setup() 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | private func setup() { 36 | // Get rid of any animations. 37 | self.actions = ["position" : NSNull(), "bounds" : NSNull()] 38 | } 39 | 40 | private func offsetDidChange() { 41 | self.frame.origin.x = offset 42 | self.bounds.origin.x = offset 43 | } 44 | 45 | func updatePath() { 46 | fatalError("updatePath needs to be implemented by the subclass") 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Classes/Plots/Animation/GraphPoint.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | internal class GraphPoint { 4 | 5 | var location = CGPoint(x: 0, y: 0) 6 | var currentlyAnimatingToPosition = false 7 | 8 | var x: CGFloat { 9 | get { 10 | return location.x 11 | } 12 | set { 13 | location.x = newValue 14 | } 15 | } 16 | 17 | var y: CGFloat { 18 | get { 19 | return location.y 20 | } 21 | set { 22 | location.y = newValue 23 | } 24 | } 25 | 26 | init(position: CGPoint = CGPoint.zero) { 27 | x = position.x 28 | y = position.y 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Classes/Plots/Animation/GraphPointAnimation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | internal class GraphPointAnimation : Equatable { 4 | 5 | // Public Properties 6 | var animationEasing = Easings.easeOutQuad 7 | var duration: Double = 1 8 | var delay: Double = 0 9 | private(set) var finished = false 10 | private(set) var animationKey: String 11 | 12 | // Private State 13 | private var startingPoint: CGPoint 14 | private var endingPoint: CGPoint 15 | 16 | private var elapsedTime: Double = 0 17 | private var graphPoint: GraphPoint? 18 | private var multiplier: Double = 1 19 | 20 | static private var animationsCreated = 0 21 | 22 | init(fromPoint: CGPoint, toPoint: CGPoint, forGraphPoint graphPoint: GraphPoint, forKey key: String = "animation\(animationsCreated)") { 23 | self.startingPoint = fromPoint 24 | self.endingPoint = toPoint 25 | self.animationKey = key 26 | self.graphPoint = graphPoint 27 | self.graphPoint?.currentlyAnimatingToPosition = true 28 | 29 | GraphPointAnimation.animationsCreated += 1 30 | } 31 | 32 | func update(withTimestamp dt: Double) { 33 | 34 | if(!finished) { 35 | 36 | if elapsedTime > delay { 37 | 38 | let animationElapsedTime = elapsedTime - delay 39 | 40 | let changeInX = endingPoint.x - startingPoint.x 41 | let changeInY = endingPoint.y - startingPoint.y 42 | 43 | // t is in the range of 0 to 1, indicates how far through the animation it is. 44 | let t = animationElapsedTime / duration 45 | let interpolation = animationEasing(t) 46 | 47 | let x = startingPoint.x + changeInX * CGFloat(interpolation) 48 | let y = startingPoint.y + changeInY * CGFloat(interpolation) 49 | 50 | if(animationElapsedTime >= duration) { 51 | animationDidFinish() 52 | } 53 | 54 | graphPoint?.x = CGFloat(x) 55 | graphPoint?.y = CGFloat(y) 56 | 57 | elapsedTime += dt * multiplier 58 | } 59 | // Keep going until we are passed the delay 60 | else { 61 | elapsedTime += dt * multiplier 62 | } 63 | } 64 | } 65 | 66 | func animationDidFinish() { 67 | self.graphPoint?.currentlyAnimatingToPosition = false 68 | self.finished = true 69 | } 70 | 71 | static func ==(lhs: GraphPointAnimation, rhs: GraphPointAnimation) -> Bool { 72 | return lhs.animationKey == rhs.animationKey 73 | } 74 | } 75 | 76 | // Simplified easing functions from: http://www.joshondesign.com/2013/03/01/improvedEasingEquations 77 | internal struct Easings { 78 | 79 | static let easeInQuad = { (t:Double) -> Double in return t*t; } 80 | static let easeOutQuad = { (t:Double) -> Double in return 1 - Easings.easeInQuad(1-t); } 81 | 82 | static let easeOutElastic = { (t: Double) -> Double in 83 | var p = 0.3; 84 | return pow(2,-10*t) * sin((t-p/4)*(2*Double.pi)/p) + 1; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Classes/Plots/BarPlot.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | open class BarPlot : Plot { 5 | 6 | // Customisation 7 | // ############# 8 | 9 | /// The width of an individual bar on the graph. 10 | open var barWidth: CGFloat = 25; 11 | /// The actual colour of the bar. 12 | open var barColor: UIColor = UIColor.gray 13 | /// The width of the outline of the bar 14 | open var barLineWidth: CGFloat = 1 15 | /// The colour of the bar outline 16 | open var barLineColor: UIColor = UIColor.darkGray 17 | /// Whether the bars should be drawn with rounded corners 18 | open var shouldRoundBarCorners: Bool = false 19 | 20 | // Private State 21 | // ############# 22 | 23 | private var barLayer: BarDrawingLayer? 24 | 25 | public init(identifier: String) { 26 | super.init() 27 | self.identifier = identifier 28 | } 29 | 30 | override func layers(forViewport viewport: CGRect) -> [ScrollableGraphViewDrawingLayer?] { 31 | createLayers(viewport: viewport) 32 | return [barLayer] 33 | } 34 | 35 | private func createLayers(viewport: CGRect) { 36 | barLayer = BarDrawingLayer( 37 | frame: viewport, 38 | barWidth: barWidth, 39 | barColor: barColor, 40 | barLineWidth: barLineWidth, 41 | barLineColor: barLineColor, 42 | shouldRoundCorners: shouldRoundBarCorners) 43 | 44 | barLayer?.owner = self 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Classes/Plots/DotPlot.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | open class DotPlot : Plot { 5 | 6 | // Customisation 7 | // ############# 8 | 9 | /// The shape to draw for each data point. 10 | open var dataPointType = ScrollableGraphViewDataPointType.circle 11 | /// The size of the shape to draw for each data point. 12 | open var dataPointSize: CGFloat = 5 13 | /// The colour with which to fill the shape. 14 | open var dataPointFillColor: UIColor = UIColor.black 15 | /// If dataPointType is set to .Custom then you,can provide a closure to create any kind of shape you would like to be displayed instead of just a circle or square. The closure takes a CGPoint which is the centre of the shape and it should return a complete UIBezierPath. 16 | open var customDataPointPath: ((_ centre: CGPoint) -> UIBezierPath)? 17 | 18 | // Private State 19 | // ############# 20 | 21 | private var dataPointLayer: DotDrawingLayer? 22 | 23 | public init(identifier: String) { 24 | super.init() 25 | self.identifier = identifier 26 | } 27 | 28 | override func layers(forViewport viewport: CGRect) -> [ScrollableGraphViewDrawingLayer?] { 29 | createLayers(viewport: viewport) 30 | return [dataPointLayer] 31 | } 32 | 33 | private func createLayers(viewport: CGRect) { 34 | dataPointLayer = DotDrawingLayer( 35 | frame: viewport, 36 | fillColor: dataPointFillColor, 37 | dataPointType: dataPointType, 38 | dataPointSize: dataPointSize, 39 | customDataPointPath: customDataPointPath) 40 | 41 | dataPointLayer?.owner = self 42 | } 43 | } 44 | 45 | @objc public enum ScrollableGraphViewDataPointType : Int { 46 | case circle 47 | case square 48 | case custom 49 | } 50 | -------------------------------------------------------------------------------- /Classes/Plots/LinePlot.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | open class LinePlot : Plot { 5 | 6 | // Public settings for the LinePlot 7 | // ################################ 8 | 9 | /// Specifies how thick the graph of the line is. In points. 10 | open var lineWidth: CGFloat = 2 11 | 12 | /// The color of the graph line. UIColor. 13 | open var lineColor: UIColor = UIColor.black 14 | 15 | /// Whether the line is straight or curved. 16 | open var lineStyle_: Int { 17 | get { return lineStyle.rawValue } 18 | set { 19 | if let enumValue = ScrollableGraphViewLineStyle(rawValue: newValue) { 20 | lineStyle = enumValue 21 | } 22 | } 23 | } 24 | 25 | /// Whether or not the line should be rendered using bezier curves are straight lines. 26 | open var lineStyle = ScrollableGraphViewLineStyle.straight 27 | 28 | /// How each segment in the line should connect. Takes any of the Core Animation LineJoin values. 29 | open var lineJoin: String = convertFromCAShapeLayerLineJoin(CAShapeLayerLineJoin.round) 30 | 31 | /// The line caps. Takes any of the Core Animation LineCap values. 32 | open var lineCap: String = convertFromCAShapeLayerLineCap(CAShapeLayerLineCap.round) 33 | open var lineCurviness: CGFloat = 0.5 34 | 35 | 36 | // Fill Settings 37 | // ############# 38 | 39 | /// Specifies whether or not the plotted graph should be filled with a colour or gradient. 40 | open var shouldFill: Bool = false 41 | 42 | var fillType_: Int { 43 | get { return fillType.rawValue } 44 | set { 45 | if let enumValue = ScrollableGraphViewFillType(rawValue: newValue) { 46 | fillType = enumValue 47 | } 48 | } 49 | } 50 | 51 | /// Specifies whether to fill the graph with a solid colour or gradient. 52 | open var fillType = ScrollableGraphViewFillType.solid 53 | 54 | /// If fillType is set to .Solid then this colour will be used to fill the graph. 55 | open var fillColor: UIColor = UIColor.black 56 | 57 | /// If fillType is set to .Gradient then this will be the starting colour for the gradient. 58 | open var fillGradientStartColor: UIColor = UIColor.white 59 | 60 | /// If fillType is set to .Gradient, then this will be the ending colour for the gradient. 61 | open var fillGradientEndColor: UIColor = UIColor.black 62 | 63 | open var fillGradientType_: Int { 64 | get { return fillGradientType.rawValue } 65 | set { 66 | if let enumValue = ScrollableGraphViewGradientType(rawValue: newValue) { 67 | fillGradientType = enumValue 68 | } 69 | } 70 | } 71 | 72 | /// If fillType is set to .Gradient, then this defines whether the gradient is rendered as a linear gradient or radial gradient. 73 | open var fillGradientType = ScrollableGraphViewGradientType.linear 74 | 75 | // Private State 76 | // ############# 77 | 78 | private var lineLayer: LineDrawingLayer? 79 | private var fillLayer: FillDrawingLayer? 80 | private var gradientLayer: GradientDrawingLayer? 81 | 82 | public init(identifier: String) { 83 | super.init() 84 | self.identifier = identifier 85 | } 86 | 87 | override func layers(forViewport viewport: CGRect) -> [ScrollableGraphViewDrawingLayer?] { 88 | createLayers(viewport: viewport) 89 | return [lineLayer, fillLayer, gradientLayer] 90 | } 91 | 92 | private func createLayers(viewport: CGRect) { 93 | 94 | // Create the line drawing layer. 95 | lineLayer = LineDrawingLayer(frame: viewport, lineWidth: lineWidth, lineColor: lineColor, lineStyle: lineStyle, lineJoin: lineJoin, lineCap: lineCap, shouldFill: shouldFill, lineCurviness: lineCurviness) 96 | 97 | // Depending on whether we want to fill with solid or gradient, create the layer accordingly. 98 | 99 | // Gradient and Fills 100 | switch (self.fillType) { 101 | 102 | case .solid: 103 | if(shouldFill) { 104 | // Setup fill 105 | fillLayer = FillDrawingLayer(frame: viewport, fillColor: fillColor, lineDrawingLayer: lineLayer!) 106 | } 107 | 108 | case .gradient: 109 | if(shouldFill) { 110 | gradientLayer = GradientDrawingLayer(frame: viewport, startColor: fillGradientStartColor, endColor: fillGradientEndColor, gradientType: fillGradientType, lineDrawingLayer: lineLayer!) 111 | } 112 | } 113 | 114 | lineLayer?.owner = self 115 | fillLayer?.owner = self 116 | gradientLayer?.owner = self 117 | } 118 | } 119 | 120 | @objc public enum ScrollableGraphViewLineStyle : Int { 121 | case straight 122 | case smooth 123 | } 124 | 125 | @objc public enum ScrollableGraphViewFillType : Int { 126 | case solid 127 | case gradient 128 | } 129 | 130 | @objc public enum ScrollableGraphViewGradientType : Int { 131 | case linear 132 | case radial 133 | } 134 | 135 | // Helper function inserted by Swift 4.2 migrator. 136 | fileprivate func convertFromCAShapeLayerLineJoin(_ input: CAShapeLayerLineJoin) -> String { 137 | return input.rawValue 138 | } 139 | 140 | // Helper function inserted by Swift 4.2 migrator. 141 | fileprivate func convertFromCAShapeLayerLineCap(_ input: CAShapeLayerLineCap) -> String { 142 | return input.rawValue 143 | } 144 | -------------------------------------------------------------------------------- /Classes/Plots/Plot.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | open class Plot { 5 | 6 | // The id for this plot. Used when determining which data to give it in the dataSource 7 | open var identifier: String! 8 | 9 | weak var graphViewDrawingDelegate: ScrollableGraphViewDrawingDelegate! = nil 10 | 11 | // Animation Settings 12 | // ################## 13 | 14 | /// How long the animation should take. Affects both the startup animation and the animation when the range of the y-axis adapts to onscreen points. 15 | open var animationDuration: Double = 1.5 16 | 17 | open var adaptAnimationType_: Int { 18 | get { return adaptAnimationType.rawValue } 19 | set { 20 | if let enumValue = ScrollableGraphViewAnimationType(rawValue: newValue) { 21 | adaptAnimationType = enumValue 22 | } 23 | } 24 | } 25 | /// The animation style. 26 | open var adaptAnimationType = ScrollableGraphViewAnimationType.easeOut 27 | /// If adaptAnimationType is set to .Custom, then this is the easing function you would like applied for the animation. 28 | open var customAnimationEasingFunction: ((_ t: Double) -> Double)? 29 | 30 | // Private Animation State 31 | // ####################### 32 | 33 | private var currentAnimations = [GraphPointAnimation]() 34 | private var displayLink: CADisplayLink! 35 | private var previousTimestamp: CFTimeInterval = 0 36 | private var currentTimestamp: CFTimeInterval = 0 37 | 38 | private var graphPoints = [GraphPoint]() 39 | 40 | deinit { 41 | displayLink?.invalidate() 42 | } 43 | 44 | // MARK: Different plot types should implement: 45 | // ############################################ 46 | 47 | func layers(forViewport viewport: CGRect) -> [ScrollableGraphViewDrawingLayer?] { 48 | return [] 49 | } 50 | 51 | // MARK: Plot Animation 52 | // #################### 53 | 54 | // Animation update loop for co-domain changes. 55 | @objc private func animationUpdate() { 56 | let dt = timeSinceLastFrame() 57 | 58 | for animation in currentAnimations { 59 | 60 | animation.update(withTimestamp: dt) 61 | 62 | if animation.finished { 63 | dequeue(animation: animation) 64 | } 65 | } 66 | 67 | graphViewDrawingDelegate.updatePaths() 68 | } 69 | 70 | private func animate(point: GraphPoint, to position: CGPoint, withDelay delay: Double = 0) { 71 | let currentPoint = CGPoint(x: point.x, y: point.y) 72 | let animation = GraphPointAnimation(fromPoint: currentPoint, toPoint: position, forGraphPoint: point) 73 | animation.animationEasing = getAnimationEasing() 74 | animation.duration = animationDuration 75 | animation.delay = delay 76 | enqueue(animation: animation) 77 | } 78 | 79 | private func getAnimationEasing() -> (Double) -> Double { 80 | switch(self.adaptAnimationType) { 81 | case .elastic: 82 | return Easings.easeOutElastic 83 | case .easeOut: 84 | return Easings.easeOutQuad 85 | case .custom: 86 | if let customEasing = customAnimationEasingFunction { 87 | return customEasing 88 | } 89 | else { 90 | fallthrough 91 | } 92 | default: 93 | return Easings.easeOutQuad 94 | } 95 | } 96 | 97 | private func enqueue(animation: GraphPointAnimation) { 98 | if (currentAnimations.count == 0) { 99 | // Need to kick off the loop. 100 | displayLink.isPaused = false 101 | } 102 | currentAnimations.append(animation) 103 | } 104 | 105 | private func dequeue(animation: GraphPointAnimation) { 106 | if let index = currentAnimations.index(of: animation) { 107 | currentAnimations.remove(at: index) 108 | } 109 | 110 | if(currentAnimations.count == 0) { 111 | // Stop animation loop. 112 | displayLink.isPaused = true 113 | } 114 | } 115 | 116 | internal func dequeueAllAnimations() { 117 | 118 | for animation in currentAnimations { 119 | animation.animationDidFinish() 120 | } 121 | 122 | currentAnimations.removeAll() 123 | displayLink.isPaused = true 124 | } 125 | 126 | private func timeSinceLastFrame() -> Double { 127 | if previousTimestamp == 0 { 128 | previousTimestamp = displayLink.timestamp 129 | } else { 130 | previousTimestamp = currentTimestamp 131 | } 132 | 133 | currentTimestamp = displayLink.timestamp 134 | 135 | var dt = currentTimestamp - previousTimestamp 136 | 137 | if dt > 0.032 { 138 | dt = 0.032 139 | } 140 | 141 | return dt 142 | } 143 | 144 | internal func startAnimations(forPoints pointsToAnimate: CountableRange, withData data: [Double], withStaggerValue stagger: Double) { 145 | 146 | animatePlotPointPositions(forPoints: pointsToAnimate, withData: data, withDelay: stagger) 147 | } 148 | 149 | internal func createPlotPoints(numberOfPoints: Int, range: (min: Double, max: Double)) { 150 | for i in 0 ..< numberOfPoints { 151 | 152 | let value = range.min 153 | 154 | let position = graphViewDrawingDelegate.calculatePosition(atIndex: i, value: value) 155 | let point = GraphPoint(position: position) 156 | graphPoints.append(point) 157 | } 158 | } 159 | 160 | // When active interval changes, need to set the position for any NEWLY ACTIVATED points, otherwise 161 | // they will come on screen at the incorrect position. 162 | // Needs to be called when the active interval has changed and during initial setup. 163 | internal func setPlotPointPositions(forNewlyActivatedPoints newPoints: CountableRange, withData data: [Double]) { 164 | 165 | for i in newPoints.startIndex ..< newPoints.endIndex { 166 | // e.g. 167 | // indices: 10...20 168 | // data positions: 0...10 = // 0 to (end - start) 169 | let dataPosition = i - newPoints.startIndex 170 | 171 | let value = data[dataPosition] 172 | 173 | let newPosition = graphViewDrawingDelegate.calculatePosition(atIndex: i, value: value) 174 | graphPoints[i].x = newPosition.x 175 | graphPoints[i].y = newPosition.y 176 | } 177 | } 178 | 179 | // Same as a above, but can take an array with the indicies of the activated points rather than a range. 180 | internal func setPlotPointPositions(forNewlyActivatedPoints activatedPoints: [Int], withData data: [Double]) { 181 | 182 | var index = 0 183 | for activatedPointIndex in activatedPoints { 184 | 185 | let dataPosition = index 186 | let value = data[dataPosition] 187 | 188 | let newPosition = graphViewDrawingDelegate.calculatePosition(atIndex: activatedPointIndex, value: value) 189 | graphPoints[activatedPointIndex].x = newPosition.x 190 | graphPoints[activatedPointIndex].y = newPosition.y 191 | 192 | index += 1 193 | } 194 | } 195 | 196 | // When the range changes, we need to set the position for any VISIBLE points, either animating or setting directly 197 | // depending on the settings. 198 | // Needs to be called when the range has changed. 199 | internal func animatePlotPointPositions(forPoints pointsToAnimate: CountableRange, withData data: [Double], withDelay delay: Double) { 200 | // For any visible points, kickoff the animation to their new position after the axis' min/max has changed. 201 | var dataIndex = 0 202 | for pointIndex in pointsToAnimate { 203 | let newPosition = graphViewDrawingDelegate.calculatePosition(atIndex: pointIndex, value: data[dataIndex]) 204 | let point = graphPoints[pointIndex] 205 | animate(point: point, to: newPosition, withDelay: Double(dataIndex) * delay) 206 | dataIndex += 1 207 | } 208 | } 209 | 210 | internal func setup() { 211 | displayLink = CADisplayLink(target: self, selector: #selector(animationUpdate)) 212 | displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 213 | displayLink.isPaused = true 214 | } 215 | 216 | internal func reset() { 217 | currentAnimations.removeAll() 218 | graphPoints.removeAll() 219 | displayLink?.invalidate() 220 | previousTimestamp = 0 221 | currentTimestamp = 0 222 | } 223 | 224 | internal func invalidate() { 225 | currentAnimations.removeAll() 226 | graphPoints.removeAll() 227 | displayLink?.invalidate() 228 | } 229 | 230 | internal func graphPoint(forIndex index: Int) -> GraphPoint { 231 | return graphPoints[index] 232 | } 233 | } 234 | 235 | @objc public enum ScrollableGraphViewAnimationType : Int { 236 | case easeOut 237 | case elastic 238 | case custom 239 | } 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /Classes/Protocols/ScrollableGraphViewDataSource.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | public protocol ScrollableGraphViewDataSource : class { 5 | func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double 6 | func label(atIndex pointIndex: Int) -> String 7 | func numberOfPoints() -> Int // This now forces the same number of points in each plot. 8 | } 9 | -------------------------------------------------------------------------------- /Classes/Protocols/ScrollableGraphViewDrawingDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | // Delegate definition that provides the data required by the drawing layers. 5 | internal protocol ScrollableGraphViewDrawingDelegate : class { 6 | func intervalForActivePoints() -> CountableRange 7 | func rangeForActivePoints() -> (min: Double, max: Double) 8 | func paddingForPoints() -> (leftmostPointPadding: CGFloat, rightmostPointPadding: CGFloat) 9 | func calculatePosition(atIndex index: Int, value: Double) -> CGPoint 10 | func currentViewport() -> CGRect 11 | func updatePaths() 12 | } 13 | -------------------------------------------------------------------------------- /Classes/Reference/LabelPool.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | internal class LabelPool { 5 | 6 | var labels = [UILabel]() 7 | var relations = [Int : Int]() 8 | var unused = [Int]() 9 | 10 | func deactivateLabel(forPointIndex pointIndex: Int){ 11 | 12 | if let unusedLabelIndex = relations[pointIndex] { 13 | unused.append(unusedLabelIndex) 14 | } 15 | relations[pointIndex] = nil 16 | } 17 | 18 | @discardableResult 19 | func activateLabel(forPointIndex pointIndex: Int) -> UILabel { 20 | var label: UILabel 21 | 22 | if(unused.count >= 1) { 23 | let unusedLabelIndex = unused.first! 24 | unused.removeFirst() 25 | 26 | label = labels[unusedLabelIndex] 27 | relations[pointIndex] = unusedLabelIndex 28 | } 29 | else { 30 | label = UILabel() 31 | labels.append(label) 32 | let newLabelIndex = labels.index(of: label)! 33 | relations[pointIndex] = newLabelIndex 34 | } 35 | 36 | return label 37 | } 38 | 39 | var activeLabels: [UILabel] { 40 | get { 41 | 42 | var currentlyActive = [UILabel]() 43 | let numberOfLabels = labels.count 44 | 45 | for i in 0 ..< numberOfLabels { 46 | if(!unused.contains(i)) { 47 | currentlyActive.append(labels[i]) 48 | } 49 | } 50 | return currentlyActive 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Reference/ReferenceLines.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | // Currently just a simple data structure to hold the settings for the reference lines. 5 | open class ReferenceLines { 6 | 7 | // Reference Lines 8 | // ############### 9 | 10 | /// Whether or not to show the y-axis reference lines and labels. 11 | @IBInspectable open var shouldShowReferenceLines: Bool = true 12 | /// The colour for the reference lines. 13 | @IBInspectable open var referenceLineColor: UIColor = UIColor.black 14 | /// The thickness of the reference lines. 15 | @IBInspectable open var referenceLineThickness: CGFloat = 0.5 16 | 17 | @IBInspectable var referenceLinePosition_: Int { 18 | get { return referenceLinePosition.rawValue } 19 | set { 20 | if let enumValue = ScrollableGraphViewReferenceLinePosition(rawValue: newValue) { 21 | referenceLinePosition = enumValue 22 | } 23 | } 24 | } 25 | /// Where the labels should be displayed on the reference lines. 26 | open var referenceLinePosition = ScrollableGraphViewReferenceLinePosition.left 27 | 28 | @IBInspectable open var positionType = ReferenceLinePositioningType.relative 29 | @IBInspectable open var relativePositions: [Double] = [0.25, 0.5, 0.75] 30 | @IBInspectable open var absolutePositions: [Double] = [25, 50, 75] 31 | @IBInspectable open var includeMinMax: Bool = true 32 | 33 | /// Whether or not to add labels to the intermediate reference lines. 34 | @IBInspectable open var shouldAddLabelsToIntermediateReferenceLines: Bool = true 35 | /// Whether or not to add units specified by the referenceLineUnits variable to the labels on the intermediate reference lines. 36 | @IBInspectable open var shouldAddUnitsToIntermediateReferenceLineLabels: Bool = false 37 | 38 | // Reference Line Labels 39 | // ##################### 40 | 41 | /// The font to be used for the reference line labels. 42 | open var referenceLineLabelFont = UIFont.systemFont(ofSize: 8) 43 | /// The colour of the reference line labels. 44 | @IBInspectable open var referenceLineLabelColor: UIColor = UIColor.black 45 | 46 | /// Whether or not to show the units on the reference lines. 47 | @IBInspectable open var shouldShowReferenceLineUnits: Bool = true 48 | /// The units that the y-axis is in. This string is used for labels on the reference lines. 49 | @IBInspectable open var referenceLineUnits: String? 50 | /// The number of decimal places that should be shown on the reference line labels. 51 | @IBInspectable open var referenceLineNumberOfDecimalPlaces: Int = 0 52 | /// The NSNumberFormatterStyle that reference lines should use to display 53 | @IBInspectable open var referenceLineNumberStyle: NumberFormatter.Style = .none 54 | 55 | // Data Point Labels // TODO: Refactor these into their own settings and allow for more label options (positioning) 56 | // ################################################################################################################ 57 | 58 | /// Whether or not to show the labels on the x-axis for each point. 59 | @IBInspectable open var shouldShowLabels: Bool = true 60 | /// How far from the "minimum" reference line the data point labels should be rendered. 61 | @IBInspectable open var dataPointLabelTopMargin: CGFloat = 10 62 | /// How far from the bottom of the view the data point labels should be rendered. 63 | @IBInspectable open var dataPointLabelBottomMargin: CGFloat = 0 64 | /// The font for the data point labels. 65 | @IBInspectable open var dataPointLabelColor: UIColor = UIColor.black 66 | /// The colour for the data point labels. 67 | open var dataPointLabelFont: UIFont? = UIFont.systemFont(ofSize: 10) 68 | /// Used to force the graph to show every n-th dataPoint label 69 | @IBInspectable open var dataPointLabelsSparsity: Int = 1 70 | 71 | public init() { 72 | // Need this for external frameworks. 73 | } 74 | } 75 | 76 | 77 | @objc public enum ScrollableGraphViewReferenceLinePosition : Int { 78 | case left 79 | case right 80 | case both 81 | } 82 | 83 | @objc public enum ReferenceLinePositioningType : Int { 84 | case relative 85 | case absolute 86 | } 87 | 88 | @objc public enum ScrollableGraphViewReferenceLineType : Int { 89 | case cover 90 | } 91 | -------------------------------------------------------------------------------- /GraphView/GraphView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GraphView/GraphView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GraphView/GraphView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GraphView 4 | // 5 | // Created by Phillip on 2/16/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /GraphView/GraphView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UIStatusBarStyle 36 | UIStatusBarStyleDefault 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /GraphView/GraphView/UIColor+colorFromHex.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | // An extension to UIColor which adds a class function that returns a UIColor from the provided hex string. The colours are cached. 5 | // Parameters: hexString:String, the hex code for the color you want. Leading "#" is optional. Must be 6 hex digts long. (8 bits per color) 6 | // Usage: let someColor = UIColor.colorFromHex("#2d34aa") 7 | extension UIColor { 8 | 9 | // Convert a hex string to a UIColor object. 10 | class func colorFromHex(hexString:String) -> UIColor { 11 | 12 | func clean(hexString: String) -> String { 13 | 14 | var cleanedHexString = String() 15 | 16 | // Remove the leading "#" 17 | if(hexString[hexString.startIndex] == "#") { 18 | let index = hexString.index(hexString.startIndex, offsetBy: 1) 19 | cleanedHexString = String(hexString[index...]) 20 | } 21 | 22 | // TODO: Other cleanup. Allow for a "short" hex string, i.e., "#fff" 23 | 24 | return cleanedHexString 25 | } 26 | 27 | let cleanedHexString = clean(hexString: hexString) 28 | 29 | // If we can get a cached version of the colour, get out early. 30 | if let cachedColor = UIColor.getColorFromCache(hexString: cleanedHexString) { 31 | return cachedColor 32 | } 33 | 34 | // Else create the color, store it in the cache and return. 35 | let scanner = Scanner(string: cleanedHexString) 36 | 37 | var value:UInt32 = 0 38 | 39 | // We have the hex value, grab the red, green, blue and alpha values. 40 | // Have to pass value by reference, scanner modifies this directly as the result of scanning the hex string. The return value is the success or fail. 41 | if(scanner.scanHexInt32(&value)){ 42 | 43 | // intValue = 01010101 11110111 11101010 // binary 44 | // intValue = 55 F7 EA // hexadecimal 45 | 46 | // r 47 | // 00000000 00000000 01010101 intValue >> 16 48 | // & 00000000 00000000 11111111 mask 49 | // ========================== 50 | // = 00000000 00000000 01010101 red 51 | 52 | // r g 53 | // 00000000 01010101 11110111 intValue >> 8 54 | // & 00000000 00000000 11111111 mask 55 | // ========================== 56 | // = 00000000 00000000 11110111 green 57 | 58 | // r g b 59 | // 01010101 11110111 11101010 intValue 60 | // & 00000000 00000000 11111111 mask 61 | // ========================== 62 | // = 00000000 00000000 11101010 blue 63 | 64 | let intValue = UInt32(value) 65 | let mask:UInt32 = 0xFF 66 | 67 | let red = intValue >> 16 & mask 68 | let green = intValue >> 8 & mask 69 | let blue = intValue & mask 70 | 71 | // red, green, blue and alpha are currently between 0 and 255 72 | // We want to normalise these values between 0 and 1 to use with UIColor. 73 | let colors:[UInt32] = [red, green, blue] 74 | let normalised = normalise(colors: colors) 75 | 76 | let newColor = UIColor(red: normalised[0], green: normalised[1], blue: normalised[2], alpha: 1) 77 | UIColor.storeColorInCache(hexString: cleanedHexString, color: newColor) 78 | 79 | return newColor 80 | 81 | } 82 | // We couldn't get a value from a valid hex string. 83 | else { 84 | print("Error: Couldn't convert the hex string to a number, returning UIColor.whiteColor() instead.") 85 | return UIColor.white 86 | } 87 | } 88 | 89 | // Takes an array of colours in the range of 0-255 and returns a value between 0 and 1. 90 | private class func normalise(colors: [UInt32]) -> [CGFloat]{ 91 | var normalisedVersions = [CGFloat]() 92 | 93 | for color in colors{ 94 | normalisedVersions.append(CGFloat(color % 256) / 255) 95 | } 96 | 97 | return normalisedVersions 98 | } 99 | 100 | // Caching 101 | // Store any colours we've gotten before. Colours don't change. 102 | private static var hexColorCache = [String : UIColor]() 103 | 104 | private class func getColorFromCache(hexString: String) -> UIColor? { 105 | guard let color = UIColor.hexColorCache[hexString] else { 106 | return nil 107 | } 108 | 109 | return color 110 | } 111 | 112 | private class func storeColorInCache(hexString: String, color: UIColor) { 113 | 114 | if UIColor.hexColorCache.keys.contains(hexString) { 115 | return // No work to do if it is already there. 116 | } 117 | 118 | UIColor.hexColorCache[hexString] = color 119 | } 120 | 121 | private class func clearColorCache() { 122 | UIColor.hexColorCache.removeAll() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "images" : [ 7 | { 8 | "filename" : "Icon-40.png", 9 | "size" : "40x40", 10 | "idiom" : "ipad", 11 | "scale" : "1x" 12 | }, 13 | { 14 | "filename" : "Icon-40@2x.png", 15 | "size" : "40x40", 16 | "idiom" : "ipad", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "filename" : "Icon-60@2x.png", 21 | "size" : "60x60", 22 | "idiom" : "iphone", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "filename" : "Icon-72.png", 27 | "size" : "72x72", 28 | "idiom" : "ipad", 29 | "scale" : "1x" 30 | }, 31 | { 32 | "filename" : "Icon-72@2x.png", 33 | "size" : "72x72", 34 | "idiom" : "ipad", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "filename" : "Icon-76.png", 39 | "size" : "76x76", 40 | "idiom" : "ipad", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "filename" : "Icon-76@2x.png", 45 | "size" : "76x76", 46 | "idiom" : "ipad", 47 | "scale" : "2x" 48 | }, 49 | { 50 | "filename" : "Icon-Small-50.png", 51 | "size" : "50x50", 52 | "idiom" : "ipad", 53 | "scale" : "1x" 54 | }, 55 | { 56 | "filename" : "Icon-Small-50@2x.png", 57 | "size" : "50x50", 58 | "idiom" : "ipad", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "filename" : "Icon-Small.png", 63 | "size" : "29x29", 64 | "idiom" : "iphone", 65 | "scale" : "1x" 66 | }, 67 | { 68 | "filename" : "Icon-Small@2x.png", 69 | "size" : "29x29", 70 | "idiom" : "iphone", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "filename" : "Icon.png", 75 | "size" : "57x57", 76 | "idiom" : "iphone", 77 | "scale" : "1x" 78 | }, 79 | { 80 | "filename" : "Icon@2x.png", 81 | "size" : "57x57", 82 | "idiom" : "iphone", 83 | "scale" : "2x" 84 | }, 85 | { 86 | "filename" : "Icon-Small@3x.png", 87 | "size" : "29x29", 88 | "idiom" : "iphone", 89 | "scale" : "3x" 90 | }, 91 | { 92 | "filename" : "Icon-40@3x.png", 93 | "size" : "40x40", 94 | "idiom" : "iphone", 95 | "scale" : "3x" 96 | }, 97 | { 98 | "filename" : "Icon-60@3x.png", 99 | "size" : "60x60", 100 | "idiom" : "iphone", 101 | "scale" : "3x" 102 | }, 103 | { 104 | "filename" : "Icon-40@2x.png", 105 | "size" : "40x40", 106 | "idiom" : "iphone", 107 | "scale" : "2x" 108 | }, 109 | { 110 | "filename" : "Icon-Small.png", 111 | "size" : "29x29", 112 | "idiom" : "ipad", 113 | "scale" : "1x" 114 | }, 115 | { 116 | "filename" : "Icon-Small@2x.png", 117 | "size" : "29x29", 118 | "idiom" : "ipad", 119 | "scale" : "2x" 120 | }, 121 | { 122 | "filename" : "Icon-83.5@2x.png", 123 | "size" : "83.5x83.5", 124 | "idiom" : "ipad", 125 | "scale" : "2x" 126 | }, 127 | { 128 | "filename" : "NotificationIcon@2x.png", 129 | "size" : "20x20", 130 | "idiom" : "iphone", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "filename" : "NotificationIcon@3x.png", 135 | "size" : "20x20", 136 | "idiom" : "iphone", 137 | "scale" : "3x" 138 | }, 139 | { 140 | "filename" : "NotificationIcon~ipad.png", 141 | "size" : "20x20", 142 | "idiom" : "ipad", 143 | "scale" : "1x" 144 | }, 145 | { 146 | "filename" : "NotificationIcon~ipad@2x.png", 147 | "size" : "20x20", 148 | "idiom" : "ipad", 149 | "scale" : "2x" 150 | }, 151 | { 152 | "filename" : "ios-marketing.png", 153 | "size" : "1024x1024", 154 | "idiom" : "ios-marketing", 155 | "scale" : "1x" 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/Assets.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Examples.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Examples.swift 3 | // GraphView 4 | // 5 | // Created by Kelly Roach on 8/18/18. 6 | // 7 | 8 | import UIKit 9 | 10 | class Examples : ScrollableGraphViewDataSource { 11 | // MARK: Data Properties 12 | 13 | private var numberOfDataItems = 29 14 | 15 | // Data for graphs with a single plot 16 | private lazy var simpleLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 17 | private lazy var darkLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 50, shouldIncludeOutliers: true) 18 | private lazy var dotPlotData: [Double] = self.generateRandomData(self.numberOfDataItems, variance: 4, from: 25) 19 | private lazy var barPlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 20 | private lazy var pinkLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 21 | 22 | // Data for graphs with multiple plots 23 | private lazy var blueLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 50) 24 | private lazy var orangeLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems, max: 40, shouldIncludeOutliers: false) 25 | 26 | // Labels for the x-axis 27 | 28 | private lazy var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems, text: "FEB") 29 | 30 | // MARK: ScrollableGraphViewDataSource protocol 31 | // ######################################################### 32 | 33 | // You would usually only have a couple of cases here, one for each 34 | // plot you want to display on the graph. However as this is showing 35 | // off many graphs with different plots, we are using one big switch 36 | // statement. 37 | func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double { 38 | 39 | switch(plot.identifier) { 40 | 41 | // Data for the graphs with a single plot 42 | case "simple": 43 | return simpleLinePlotData[pointIndex] 44 | case "darkLine": 45 | return darkLinePlotData[pointIndex] 46 | case "darkLineDot": 47 | return darkLinePlotData[pointIndex] 48 | case "bar": 49 | return barPlotData[pointIndex] 50 | case "dot": 51 | return dotPlotData[pointIndex] 52 | case "pinkLine": 53 | return pinkLinePlotData[pointIndex] 54 | 55 | // Data for MULTI graphs 56 | case "multiBlue": 57 | return blueLinePlotData[pointIndex] 58 | case "multiBlueDot": 59 | return blueLinePlotData[pointIndex] 60 | case "multiOrange": 61 | return orangeLinePlotData[pointIndex] 62 | case "multiOrangeSquare": 63 | return orangeLinePlotData[pointIndex] 64 | 65 | default: 66 | return 0 67 | } 68 | } 69 | 70 | func label(atIndex pointIndex: Int) -> String { 71 | // Ensure that you have a label to return for the index 72 | return xAxisLabels[pointIndex] 73 | } 74 | 75 | func numberOfPoints() -> Int { 76 | return numberOfDataItems 77 | } 78 | 79 | // MARK: Example Graphs 80 | // ################################## 81 | 82 | // The simplest kind of graph 83 | // A single line plot, with no range adaption when scrolling 84 | // No animations 85 | // min: 0 86 | // max: 100 87 | func createSimpleGraph(_ frame: CGRect) -> ScrollableGraphView { 88 | 89 | // Compose the graph view by creating a graph, then adding any plots 90 | // and reference lines before adding the graph to the view hierarchy. 91 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 92 | 93 | let linePlot = LinePlot(identifier: "simple") // Identifier should be unique for each plot. 94 | let referenceLines = ReferenceLines() 95 | 96 | graphView.addPlot(plot: linePlot) 97 | graphView.addReferenceLines(referenceLines: referenceLines) 98 | 99 | return graphView 100 | } 101 | 102 | // Multi plot v1 103 | // min: 0 104 | // max: determined from active points 105 | // The max reference line will be the max of all visible points 106 | // Reference lines are placed relatively, at 0%, 20%, 40%, 60%, 80% and 100% of the max 107 | func createMultiPlotGraphOne(_ frame: CGRect) -> ScrollableGraphView { 108 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 109 | 110 | // Setup the first plot. 111 | let blueLinePlot = LinePlot(identifier: "multiBlue") 112 | 113 | blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc") 114 | blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 115 | 116 | // dots on the line 117 | let blueDotPlot = DotPlot(identifier: "multiBlueDot") 118 | blueDotPlot.dataPointType = ScrollableGraphViewDataPointType.circle 119 | blueDotPlot.dataPointSize = 5 120 | blueDotPlot.dataPointFillColor = UIColor.colorFromHex(hexString: "#16aafc") 121 | 122 | blueDotPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 123 | 124 | // Setup the second plot. 125 | let orangeLinePlot = LinePlot(identifier: "multiOrange") 126 | 127 | orangeLinePlot.lineColor = UIColor.colorFromHex(hexString: "#ff7d78") 128 | orangeLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 129 | 130 | // squares on the line 131 | let orangeSquarePlot = DotPlot(identifier: "multiOrangeSquare") 132 | orangeSquarePlot.dataPointType = ScrollableGraphViewDataPointType.square 133 | orangeSquarePlot.dataPointSize = 5 134 | orangeSquarePlot.dataPointFillColor = UIColor.colorFromHex(hexString: "#ff7d78") 135 | 136 | orangeSquarePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 137 | 138 | // Setup the reference lines. 139 | let referenceLines = ReferenceLines() 140 | 141 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 142 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2) 143 | referenceLines.referenceLineLabelColor = UIColor.white 144 | referenceLines.relativePositions = [0, 0.2, 0.4, 0.6, 0.8, 1] 145 | 146 | referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(1) 147 | 148 | // Setup the graph 149 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 150 | 151 | graphView.dataPointSpacing = 80 152 | 153 | graphView.shouldAnimateOnStartup = true 154 | graphView.shouldAdaptRange = true 155 | graphView.shouldRangeAlwaysStartAtZero = true 156 | 157 | // Add everything to the graph. 158 | graphView.addReferenceLines(referenceLines: referenceLines) 159 | graphView.addPlot(plot: blueLinePlot) 160 | graphView.addPlot(plot: blueDotPlot) 161 | graphView.addPlot(plot: orangeLinePlot) 162 | graphView.addPlot(plot: orangeSquarePlot) 163 | 164 | return graphView 165 | } 166 | 167 | // Multi plot v2 168 | // min: 0 169 | // max: determined from active points 170 | // The max reference line will be the max of all visible points 171 | func createMultiPlotGraphTwo(_ frame: CGRect) -> ScrollableGraphView { 172 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 173 | 174 | // Setup the line plot. 175 | let blueLinePlot = LinePlot(identifier: "multiBlue") 176 | 177 | blueLinePlot.lineWidth = 1 178 | blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc") 179 | blueLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 180 | 181 | blueLinePlot.shouldFill = true 182 | blueLinePlot.fillType = ScrollableGraphViewFillType.solid 183 | blueLinePlot.fillColor = UIColor.colorFromHex(hexString: "#16aafc").withAlphaComponent(0.5) 184 | 185 | blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 186 | 187 | // Setup the second line plot. 188 | let orangeLinePlot = LinePlot(identifier: "multiOrange") 189 | 190 | orangeLinePlot.lineWidth = 1 191 | orangeLinePlot.lineColor = UIColor.colorFromHex(hexString: "#ff7d78") 192 | orangeLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 193 | 194 | orangeLinePlot.shouldFill = true 195 | orangeLinePlot.fillType = ScrollableGraphViewFillType.solid 196 | orangeLinePlot.fillColor = UIColor.colorFromHex(hexString: "#ff7d78").withAlphaComponent(0.5) 197 | 198 | orangeLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 199 | 200 | // Setup the reference lines. 201 | let referenceLines = ReferenceLines() 202 | 203 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 204 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2) 205 | referenceLines.referenceLineLabelColor = UIColor.white 206 | 207 | referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(1) 208 | 209 | // Setup the graph 210 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 211 | 212 | graphView.dataPointSpacing = 80 213 | graphView.shouldAnimateOnStartup = true 214 | graphView.shouldAdaptRange = true 215 | 216 | graphView.shouldRangeAlwaysStartAtZero = true 217 | 218 | // Add everything to the graph. 219 | graphView.addReferenceLines(referenceLines: referenceLines) 220 | graphView.addPlot(plot: blueLinePlot) 221 | graphView.addPlot(plot: orangeLinePlot) 222 | 223 | return graphView 224 | } 225 | 226 | // Reference lines are positioned absolutely. will appear at specified values on y axis 227 | func createDarkGraph(_ frame: CGRect) -> ScrollableGraphView { 228 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 229 | 230 | // Setup the line plot. 231 | let linePlot = LinePlot(identifier: "darkLine") 232 | 233 | linePlot.lineWidth = 1 234 | linePlot.lineColor = UIColor.colorFromHex(hexString: "#777777") 235 | linePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 236 | 237 | linePlot.shouldFill = true 238 | linePlot.fillType = ScrollableGraphViewFillType.gradient 239 | linePlot.fillGradientType = ScrollableGraphViewGradientType.linear 240 | linePlot.fillGradientStartColor = UIColor.colorFromHex(hexString: "#555555") 241 | linePlot.fillGradientEndColor = UIColor.colorFromHex(hexString: "#444444") 242 | 243 | linePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 244 | 245 | let dotPlot = DotPlot(identifier: "darkLineDot") // Add dots as well. 246 | dotPlot.dataPointSize = 2 247 | dotPlot.dataPointFillColor = UIColor.white 248 | 249 | dotPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 250 | 251 | // Setup the reference lines. 252 | let referenceLines = ReferenceLines() 253 | 254 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 255 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2) 256 | referenceLines.referenceLineLabelColor = UIColor.white 257 | 258 | referenceLines.positionType = .absolute 259 | // Reference lines will be shown at these values on the y-axis. 260 | referenceLines.absolutePositions = [10, 20, 25, 30] 261 | referenceLines.includeMinMax = false 262 | 263 | referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(0.5) 264 | 265 | // Setup the graph 266 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 267 | graphView.dataPointSpacing = 80 268 | 269 | graphView.shouldAnimateOnStartup = true 270 | graphView.shouldAdaptRange = true 271 | graphView.shouldRangeAlwaysStartAtZero = true 272 | 273 | graphView.rangeMax = 50 274 | 275 | // Add everything to the graph. 276 | graphView.addReferenceLines(referenceLines: referenceLines) 277 | graphView.addPlot(plot: linePlot) 278 | graphView.addPlot(plot: dotPlot) 279 | 280 | return graphView 281 | } 282 | 283 | // min: 0 284 | // max: 100 285 | // Will not adapt min and max reference lines to range of visible points 286 | func createBarGraph(_ frame: CGRect) -> ScrollableGraphView { 287 | 288 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 289 | 290 | // Setup the plot 291 | let barPlot = BarPlot(identifier: "bar") 292 | 293 | barPlot.barWidth = 25 294 | barPlot.barLineWidth = 1 295 | barPlot.barLineColor = UIColor.colorFromHex(hexString: "#777777") 296 | barPlot.barColor = UIColor.colorFromHex(hexString: "#555555") 297 | 298 | barPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 299 | barPlot.animationDuration = 1.5 300 | 301 | // Setup the reference lines 302 | let referenceLines = ReferenceLines() 303 | 304 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 305 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2) 306 | referenceLines.referenceLineLabelColor = UIColor.white 307 | 308 | referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(0.5) 309 | 310 | // Setup the graph 311 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 312 | 313 | graphView.shouldAnimateOnStartup = true 314 | 315 | graphView.rangeMax = 100 316 | graphView.rangeMin = 0 317 | 318 | // Add everything 319 | graphView.addPlot(plot: barPlot) 320 | graphView.addReferenceLines(referenceLines: referenceLines) 321 | return graphView 322 | } 323 | 324 | // min: 0 325 | // max 50 326 | // Will not adapt min and max reference lines to range of visible points 327 | // no animations 328 | func createDotGraph(_ frame: CGRect) -> ScrollableGraphView { 329 | 330 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 331 | 332 | // Setup the plot 333 | let plot = DotPlot(identifier: "dot") 334 | 335 | plot.dataPointSize = 5 336 | plot.dataPointFillColor = UIColor.white 337 | 338 | // Setup the reference lines 339 | let referenceLines = ReferenceLines() 340 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 10) 341 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.5) 342 | referenceLines.referenceLineLabelColor = UIColor.white 343 | referenceLines.referenceLinePosition = ScrollableGraphViewReferenceLinePosition.both 344 | 345 | referenceLines.shouldShowLabels = false 346 | 347 | // Setup the graph 348 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#00BFFF") 349 | graphView.shouldAdaptRange = false 350 | graphView.shouldAnimateOnAdapt = false 351 | graphView.shouldAnimateOnStartup = false 352 | 353 | graphView.dataPointSpacing = 25 354 | graphView.rangeMax = 50 355 | graphView.rangeMin = 0 356 | 357 | // Add everything 358 | graphView.addPlot(plot: plot) 359 | graphView.addReferenceLines(referenceLines: referenceLines) 360 | return graphView 361 | } 362 | 363 | // min: min of visible points 364 | // max: max of visible points 365 | // Will adapt min and max reference lines to range of visible points 366 | func createPinkGraph(_ frame: CGRect) -> ScrollableGraphView { 367 | 368 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 369 | 370 | // Setup the plot 371 | let linePlot = LinePlot(identifier: "pinkLine") 372 | 373 | linePlot.lineColor = UIColor.clear 374 | linePlot.shouldFill = true 375 | linePlot.fillColor = UIColor.colorFromHex(hexString: "#FF0080") 376 | 377 | // Setup the reference lines 378 | let referenceLines = ReferenceLines() 379 | 380 | referenceLines.referenceLineThickness = 1 381 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 10) 382 | referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.5) 383 | referenceLines.referenceLineLabelColor = UIColor.white 384 | referenceLines.referenceLinePosition = ScrollableGraphViewReferenceLinePosition.both 385 | 386 | referenceLines.dataPointLabelFont = UIFont.boldSystemFont(ofSize: 10) 387 | referenceLines.dataPointLabelColor = UIColor.white 388 | referenceLines.dataPointLabelsSparsity = 3 389 | 390 | // Setup the graph 391 | graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#222222") 392 | 393 | graphView.dataPointSpacing = 60 394 | graphView.shouldAdaptRange = true 395 | 396 | // Add everything 397 | graphView.addPlot(plot: linePlot) 398 | graphView.addReferenceLines(referenceLines: referenceLines) 399 | return graphView 400 | } 401 | 402 | // 403 | func createBlueOrangeGraph(_ frame: CGRect) -> ScrollableGraphView { 404 | let graphView = ScrollableGraphView(frame: frame, dataSource: self) 405 | // Setup the first line plot. 406 | let blueLinePlot = LinePlot(identifier: "multiBlue") 407 | 408 | blueLinePlot.lineWidth = 5 409 | blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc") 410 | blueLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 411 | 412 | blueLinePlot.shouldFill = false 413 | blueLinePlot.fillType = ScrollableGraphViewFillType.solid 414 | blueLinePlot.fillColor = UIColor.colorFromHex(hexString: "#16aafc").withAlphaComponent(0.5) 415 | 416 | blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 417 | 418 | // Setup the second line plot. 419 | let orangeLinePlot = LinePlot(identifier: "multiOrange") 420 | 421 | orangeLinePlot.lineWidth = 5 422 | orangeLinePlot.lineColor = UIColor.colorFromHex(hexString: "#ff7d78") 423 | orangeLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 424 | 425 | orangeLinePlot.shouldFill = false 426 | orangeLinePlot.fillType = ScrollableGraphViewFillType.solid 427 | orangeLinePlot.fillColor = UIColor.colorFromHex(hexString: "#ff7d78").withAlphaComponent(0.5) 428 | 429 | orangeLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 430 | 431 | // Customise the reference lines. 432 | let referenceLines = ReferenceLines() 433 | 434 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 435 | referenceLines.referenceLineColor = UIColor.black.withAlphaComponent(0.2) 436 | referenceLines.referenceLineLabelColor = UIColor.black 437 | 438 | referenceLines.dataPointLabelColor = UIColor.black.withAlphaComponent(1) 439 | 440 | // All other graph customisation is done in Interface Builder, 441 | // e.g, the background colour would be set in interface builder rather than in code. 442 | // graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 443 | 444 | // Add everything to the graph. 445 | graphView.addReferenceLines(referenceLines: referenceLines) 446 | graphView.addPlot(plot: blueLinePlot) 447 | graphView.addPlot(plot: orangeLinePlot) 448 | return graphView 449 | } 450 | 451 | // MARK: Data Generation 452 | 453 | func reload() { 454 | // Currently changing the number of data items is not supported. 455 | // It is only possible to change the the actual values of the data before reloading. 456 | // numberOfDataItems = 30 457 | 458 | // data for graphs with a single plot 459 | simpleLinePlotData = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 460 | darkLinePlotData = self.generateRandomData(self.numberOfDataItems, max: 50, shouldIncludeOutliers: true) 461 | dotPlotData = self.generateRandomData(self.numberOfDataItems, variance: 4, from: 25) 462 | barPlotData = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 463 | pinkLinePlotData = self.generateRandomData(self.numberOfDataItems, max: 100, shouldIncludeOutliers: false) 464 | 465 | // data for graphs with multiple plots 466 | blueLinePlotData = self.generateRandomData(self.numberOfDataItems, max: 50) 467 | orangeLinePlotData = self.generateRandomData(self.numberOfDataItems, max: 40, shouldIncludeOutliers: false) 468 | 469 | // update labels 470 | xAxisLabels = self.generateSequentialLabels(self.numberOfDataItems, text: "MAR") 471 | } 472 | 473 | private func generateRandomData(_ numberOfItems: Int, max: Double, shouldIncludeOutliers: Bool = true) -> [Double] { 474 | var data = [Double]() 475 | for _ in 0 ..< numberOfItems { 476 | var randomNumber = Double(arc4random()).truncatingRemainder(dividingBy: max) 477 | 478 | if(shouldIncludeOutliers) { 479 | if(arc4random() % 100 < 10) { 480 | randomNumber *= 3 481 | } 482 | } 483 | 484 | data.append(randomNumber) 485 | } 486 | return data 487 | } 488 | 489 | private func generateRandomData(_ numberOfItems: Int, variance: Double, from: Double) -> [Double] { 490 | 491 | var data = [Double]() 492 | for _ in 0 ..< numberOfItems { 493 | 494 | let randomVariance = Double(arc4random()).truncatingRemainder(dividingBy: variance) 495 | var randomNumber = from 496 | 497 | if(arc4random() % 100 < 50) { 498 | randomNumber += randomVariance 499 | } 500 | else { 501 | randomNumber -= randomVariance 502 | } 503 | 504 | data.append(randomNumber) 505 | } 506 | return data 507 | } 508 | 509 | private func generateSequentialLabels(_ numberOfItems: Int, text: String) -> [String] { 510 | var labels = [String]() 511 | for i in 0 ..< numberOfItems { 512 | labels.append("\(text) \(i+1)") 513 | } 514 | return labels 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /GraphView/GraphViewCode/GraphType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GraphType.swift 3 | // GraphView 4 | // 5 | // Created by Kelly Roach on 8/18/18. 6 | // 7 | 8 | // The type of the current graph we are showing. 9 | enum GraphType { 10 | case simple 11 | case multiOne 12 | case multiTwo 13 | case dark 14 | case bar 15 | case dot 16 | case pink 17 | case blueOrange 18 | 19 | mutating func next() { 20 | switch(self) { 21 | case .simple: 22 | self = GraphType.multiOne 23 | case .multiOne: 24 | self = GraphType.multiTwo 25 | case .multiTwo: 26 | self = GraphType.dark 27 | case .dark: 28 | self = GraphType.bar 29 | case .bar: 30 | self = GraphType.dot 31 | case .dot: 32 | self = GraphType.pink 33 | case .pink: 34 | self = GraphType.blueOrange 35 | case .blueOrange: 36 | self = GraphType.simple 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GraphView/GraphViewCode/LaunchImageCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewCode/LaunchImageCode.png -------------------------------------------------------------------------------- /GraphView/GraphViewCode/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /GraphView/GraphViewCode/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /GraphView/GraphViewCode/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Simple example usage of ScrollableGraphView.swift 3 | // ################################################# 4 | // 5 | 6 | import UIKit 7 | 8 | class ViewController: UIViewController { 9 | // MARK: Properties 10 | 11 | var examples: Examples! 12 | var graphView: ScrollableGraphView! 13 | var currentGraphType = GraphType.multiOne 14 | var graphConstraints = [NSLayoutConstraint]() 15 | 16 | var label = UILabel() 17 | var reloadLabel = UILabel() 18 | 19 | override var prefersStatusBarHidden : Bool { 20 | return true 21 | } 22 | 23 | // MARK: Init 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | examples = Examples() 29 | graphView = examples.createMultiPlotGraphOne(self.view.frame) 30 | 31 | addReloadLabel(withText: "RELOAD") 32 | addLabel(withText: "MULTI 1") 33 | 34 | self.view.insertSubview(graphView, belowSubview: reloadLabel) 35 | 36 | setupConstraints() 37 | } 38 | 39 | // MARK: Constraints 40 | 41 | private func setupConstraints() { 42 | 43 | self.graphView.translatesAutoresizingMaskIntoConstraints = false 44 | graphConstraints.removeAll() 45 | 46 | let topConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0) 47 | let rightConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutConstraint.Attribute.right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: 0) 48 | let bottomConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0) 49 | let leftConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 0) 50 | 51 | //let heightConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 0) 52 | 53 | graphConstraints.append(topConstraint) 54 | graphConstraints.append(bottomConstraint) 55 | graphConstraints.append(leftConstraint) 56 | graphConstraints.append(rightConstraint) 57 | 58 | //graphConstraints.append(heightConstraint) 59 | 60 | self.view.addConstraints(graphConstraints) 61 | } 62 | 63 | // Adding and updating the graph switching label in the top right corner of the screen. 64 | private func addLabel(withText text: String) { 65 | 66 | label.removeFromSuperview() 67 | label = createLabel(withText: text) 68 | label.isUserInteractionEnabled = true 69 | 70 | let rightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutConstraint.Attribute.right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: -20) 71 | 72 | let topConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 20) 73 | 74 | let heightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40) 75 | let widthConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: label.frame.width * 1.5) 76 | 77 | let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(didTap)) 78 | label.addGestureRecognizer(tapGestureRecogniser) 79 | 80 | self.view.insertSubview(label, aboveSubview: reloadLabel) 81 | self.view.addConstraints([rightConstraint, topConstraint, heightConstraint, widthConstraint]) 82 | } 83 | 84 | private func addReloadLabel(withText text: String) { 85 | 86 | reloadLabel.removeFromSuperview() 87 | reloadLabel = createLabel(withText: text) 88 | reloadLabel.isUserInteractionEnabled = true 89 | 90 | let leftConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 20) 91 | 92 | let topConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 20) 93 | 94 | let heightConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40) 95 | let widthConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: reloadLabel.frame.width * 1.5) 96 | 97 | let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(reloadDidTap)) 98 | reloadLabel.addGestureRecognizer(tapGestureRecogniser) 99 | 100 | self.view.insertSubview(reloadLabel, aboveSubview: graphView) 101 | self.view.addConstraints([leftConstraint, topConstraint, heightConstraint, widthConstraint]) 102 | } 103 | 104 | private func createLabel(withText text: String) -> UILabel { 105 | let label = UILabel() 106 | 107 | label.backgroundColor = UIColor.black.withAlphaComponent(0.5) 108 | 109 | label.text = text 110 | label.textColor = UIColor.white 111 | label.textAlignment = NSTextAlignment.center 112 | label.font = UIFont.boldSystemFont(ofSize: 14) 113 | 114 | label.layer.cornerRadius = 2 115 | label.clipsToBounds = true 116 | 117 | 118 | label.translatesAutoresizingMaskIntoConstraints = false 119 | label.sizeToFit() 120 | 121 | return label 122 | } 123 | 124 | // MARK: Button Taps 125 | 126 | @objc func didTap(_ gesture: UITapGestureRecognizer) { 127 | 128 | currentGraphType.next() 129 | 130 | self.view.removeConstraints(graphConstraints) 131 | graphView.removeFromSuperview() 132 | 133 | switch(currentGraphType) { 134 | 135 | case .simple: // Show simple graph, no adapting, single line. 136 | graphView = examples.createSimpleGraph(self.view.frame) 137 | addReloadLabel(withText: "RELOAD") 138 | addLabel(withText: "SIMPLE") 139 | case .multiOne: // Show graph with multiple plots, with adapting and using dot plots to decorate the line 140 | graphView = examples.createMultiPlotGraphOne(self.view.frame) 141 | addReloadLabel(withText: "RELOAD") 142 | addLabel(withText: "MULTI 1") 143 | case .multiTwo: 144 | graphView = examples.createMultiPlotGraphTwo(self.view.frame) 145 | addReloadLabel(withText: "RELOAD") 146 | addLabel(withText: "MULTI 2") 147 | case .dark: 148 | graphView = examples.createDarkGraph(self.view.frame) 149 | addReloadLabel(withText: "RELOAD") 150 | addLabel(withText: "DARK") 151 | case .dot: 152 | graphView = examples.createDotGraph(self.view.frame) 153 | addReloadLabel(withText: "RELOAD") 154 | addLabel(withText: "DOT") 155 | case .bar: 156 | graphView = examples.createBarGraph(self.view.frame) 157 | addReloadLabel(withText: "RELOAD") 158 | addLabel(withText: "BAR") 159 | case .pink: 160 | graphView = examples.createPinkGraph(self.view.frame) 161 | addReloadLabel(withText: "RELOAD") 162 | addLabel(withText: "PINK") 163 | case .blueOrange: 164 | graphView = examples.createBlueOrangeGraph(self.view.frame) 165 | addReloadLabel(withText: "RELOAD") 166 | addLabel(withText: "BLUE ORANGE") 167 | } 168 | 169 | self.view.insertSubview(graphView, belowSubview: reloadLabel) 170 | 171 | setupConstraints() 172 | } 173 | 174 | @objc func reloadDidTap(_ gesture: UITapGestureRecognizer) { 175 | examples.reload() 176 | graphView.reload() 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "images" : [ 7 | { 8 | "filename" : "Icon-40.png", 9 | "size" : "40x40", 10 | "idiom" : "ipad", 11 | "scale" : "1x" 12 | }, 13 | { 14 | "filename" : "Icon-40@2x.png", 15 | "size" : "40x40", 16 | "idiom" : "ipad", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "filename" : "Icon-60@2x.png", 21 | "size" : "60x60", 22 | "idiom" : "iphone", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "filename" : "Icon-72.png", 27 | "size" : "72x72", 28 | "idiom" : "ipad", 29 | "scale" : "1x" 30 | }, 31 | { 32 | "filename" : "Icon-72@2x.png", 33 | "size" : "72x72", 34 | "idiom" : "ipad", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "filename" : "Icon-76.png", 39 | "size" : "76x76", 40 | "idiom" : "ipad", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "filename" : "Icon-76@2x.png", 45 | "size" : "76x76", 46 | "idiom" : "ipad", 47 | "scale" : "2x" 48 | }, 49 | { 50 | "filename" : "Icon-Small-50.png", 51 | "size" : "50x50", 52 | "idiom" : "ipad", 53 | "scale" : "1x" 54 | }, 55 | { 56 | "filename" : "Icon-Small-50@2x.png", 57 | "size" : "50x50", 58 | "idiom" : "ipad", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "filename" : "Icon-Small.png", 63 | "size" : "29x29", 64 | "idiom" : "iphone", 65 | "scale" : "1x" 66 | }, 67 | { 68 | "filename" : "Icon-Small@2x.png", 69 | "size" : "29x29", 70 | "idiom" : "iphone", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "filename" : "Icon.png", 75 | "size" : "57x57", 76 | "idiom" : "iphone", 77 | "scale" : "1x" 78 | }, 79 | { 80 | "filename" : "Icon@2x.png", 81 | "size" : "57x57", 82 | "idiom" : "iphone", 83 | "scale" : "2x" 84 | }, 85 | { 86 | "filename" : "Icon-Small@3x.png", 87 | "size" : "29x29", 88 | "idiom" : "iphone", 89 | "scale" : "3x" 90 | }, 91 | { 92 | "filename" : "Icon-40@3x.png", 93 | "size" : "40x40", 94 | "idiom" : "iphone", 95 | "scale" : "3x" 96 | }, 97 | { 98 | "filename" : "Icon-60@3x.png", 99 | "size" : "60x60", 100 | "idiom" : "iphone", 101 | "scale" : "3x" 102 | }, 103 | { 104 | "filename" : "Icon-40@2x.png", 105 | "size" : "40x40", 106 | "idiom" : "iphone", 107 | "scale" : "2x" 108 | }, 109 | { 110 | "filename" : "Icon-Small.png", 111 | "size" : "29x29", 112 | "idiom" : "ipad", 113 | "scale" : "1x" 114 | }, 115 | { 116 | "filename" : "Icon-Small@2x.png", 117 | "size" : "29x29", 118 | "idiom" : "ipad", 119 | "scale" : "2x" 120 | }, 121 | { 122 | "filename" : "Icon-83.5@2x.png", 123 | "size" : "83.5x83.5", 124 | "idiom" : "ipad", 125 | "scale" : "2x" 126 | }, 127 | { 128 | "filename" : "NotificationIcon@2x.png", 129 | "size" : "20x20", 130 | "idiom" : "iphone", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "filename" : "NotificationIcon@3x.png", 135 | "size" : "20x20", 136 | "idiom" : "iphone", 137 | "scale" : "3x" 138 | }, 139 | { 140 | "filename" : "NotificationIcon~ipad.png", 141 | "size" : "20x20", 142 | "idiom" : "ipad", 143 | "scale" : "1x" 144 | }, 145 | { 146 | "filename" : "NotificationIcon~ipad@2x.png", 147 | "size" : "20x20", 148 | "idiom" : "ipad", 149 | "scale" : "2x" 150 | }, 151 | { 152 | "filename" : "ios-marketing.png", 153 | "size" : "1024x1024", 154 | "idiom" : "ios-marketing", 155 | "scale" : "1x" 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/Assets.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /GraphView/GraphViewIB/LaunchImageIB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/GraphView/GraphViewIB/LaunchImageIB.png -------------------------------------------------------------------------------- /GraphView/GraphViewIB/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /GraphView/GraphViewIB/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /GraphView/GraphViewIB/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // GraphViewIB 4 | // 5 | 6 | import UIKit 7 | 8 | class ViewController: UIViewController, ScrollableGraphViewDataSource { 9 | // MARK: Properties 10 | 11 | @IBOutlet var graphView: ScrollableGraphView! 12 | @IBOutlet var reloadButton: UIButton! 13 | 14 | var numberOfItems = 29 15 | lazy var plotOneData: [Double] = self.generateRandomData(self.numberOfItems, max: 100, shouldIncludeOutliers: true) 16 | lazy var plotTwoData: [Double] = self.generateRandomData(self.numberOfItems, max: 80, shouldIncludeOutliers: false) 17 | override var prefersStatusBarHidden : Bool { 18 | return true 19 | } 20 | 21 | // MARK: Init 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | graphView.dataSource = self 26 | setupGraph(graphView: graphView) 27 | } 28 | 29 | // MARK: Button Clicks 30 | 31 | @IBAction func reloadDidClick(_ sender: Any) { 32 | plotOneData = self.generateRandomData(self.numberOfItems, max: 100, shouldIncludeOutliers: true) 33 | plotTwoData = self.generateRandomData(self.numberOfItems, max: 80, shouldIncludeOutliers: false) 34 | graphView.reload() 35 | } 36 | 37 | // MARK: ScrollableGraphViewDataSource 38 | 39 | func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double { 40 | switch(plot.identifier) { 41 | case "one": 42 | return plotOneData[pointIndex] 43 | case "two": 44 | return plotTwoData[pointIndex] 45 | default: 46 | return 0 47 | } 48 | } 49 | 50 | func label(atIndex pointIndex: Int) -> String { 51 | return "FEB \(pointIndex+1)" 52 | } 53 | 54 | func numberOfPoints() -> Int { 55 | return numberOfItems 56 | } 57 | 58 | // MARK: Helper Functions 59 | 60 | // When using Interface Builder, only add the plots and reference lines in code. 61 | func setupGraph(graphView: ScrollableGraphView) { 62 | 63 | // Setup the first line plot. 64 | let blueLinePlot = LinePlot(identifier: "one") 65 | 66 | blueLinePlot.lineWidth = 5 67 | blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc") 68 | blueLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 69 | 70 | blueLinePlot.shouldFill = false 71 | blueLinePlot.fillType = ScrollableGraphViewFillType.solid 72 | blueLinePlot.fillColor = UIColor.colorFromHex(hexString: "#16aafc").withAlphaComponent(0.5) 73 | 74 | blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 75 | 76 | // Setup the second line plot. 77 | let orangeLinePlot = LinePlot(identifier: "two") 78 | 79 | orangeLinePlot.lineWidth = 5 80 | orangeLinePlot.lineColor = UIColor.colorFromHex(hexString: "#ff7d78") 81 | orangeLinePlot.lineStyle = ScrollableGraphViewLineStyle.smooth 82 | 83 | orangeLinePlot.shouldFill = false 84 | orangeLinePlot.fillType = ScrollableGraphViewFillType.solid 85 | orangeLinePlot.fillColor = UIColor.colorFromHex(hexString: "#ff7d78").withAlphaComponent(0.5) 86 | 87 | orangeLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic 88 | 89 | // Customise the reference lines. 90 | let referenceLines = ReferenceLines() 91 | 92 | referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8) 93 | referenceLines.referenceLineColor = UIColor.black.withAlphaComponent(0.2) 94 | referenceLines.referenceLineLabelColor = UIColor.black 95 | 96 | referenceLines.dataPointLabelColor = UIColor.black.withAlphaComponent(1) 97 | 98 | // All other graph customisation is done in Interface Builder, 99 | // e.g, the background colour would be set in interface builder rather than in code. 100 | // graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333") 101 | 102 | // Add everything to the graph. 103 | graphView.addReferenceLines(referenceLines: referenceLines) 104 | graphView.addPlot(plot: blueLinePlot) 105 | graphView.addPlot(plot: orangeLinePlot) 106 | } 107 | 108 | private func generateRandomData(_ numberOfItems: Int, max: Double, shouldIncludeOutliers: Bool = true) -> [Double] { 109 | var data = [Double]() 110 | for _ in 0 ..< numberOfItems { 111 | var randomNumber = Double(arc4random()).truncatingRemainder(dividingBy: max) 112 | 113 | if(shouldIncludeOutliers) { 114 | if(arc4random() % 100 < 10) { 115 | randomNumber *= 3 116 | } 117 | } 118 | 119 | data.append(randomNumber) 120 | } 121 | return data 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Phillip 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ScrollableGraphView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ScrollableGraphView" 3 | s.version = "4.0.6" 4 | s.summary = "Scrollable graph view for iOS" 5 | s.description = "An adaptive scrollable graph view for iOS to visualise simple discrete datasets. Written in Swift." 6 | s.homepage = "https://github.com/philackm/Scrollable-GraphView" 7 | s.license = 'MIT' 8 | s.author = { "philackm" => "philackm@icloud.com" } 9 | s.source = { :git => "https://github.com/philackm/ScrollableGraphView.git", :tag => s.version.to_s } 10 | s.platform = :ios, '8.0' 11 | s.requires_arc = true 12 | s.swift_version = "4.2" 13 | 14 | # If more than one source file: https://guides.cocoapods.org/syntax/podspec.html#source_files 15 | s.source_files = 'Classes/**/*.swift' 16 | 17 | end 18 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 298BFD2B1F121BA10062F5C8 /* ScrollableGraphView.h in Headers */ = {isa = PBXBuildFile; fileRef = 298BFD291F121BA10062F5C8 /* ScrollableGraphView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 298BFD491F121BB00062F5C8 /* BarDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD331F121BB00062F5C8 /* BarDrawingLayer.swift */; }; 12 | 298BFD4A1F121BB00062F5C8 /* DotDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD341F121BB00062F5C8 /* DotDrawingLayer.swift */; }; 13 | 298BFD4B1F121BB00062F5C8 /* FillDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD351F121BB00062F5C8 /* FillDrawingLayer.swift */; }; 14 | 298BFD4C1F121BB00062F5C8 /* GradientDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD361F121BB00062F5C8 /* GradientDrawingLayer.swift */; }; 15 | 298BFD4D1F121BB00062F5C8 /* LineDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD371F121BB00062F5C8 /* LineDrawingLayer.swift */; }; 16 | 298BFD4E1F121BB00062F5C8 /* ReferenceLineDrawingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD381F121BB00062F5C8 /* ReferenceLineDrawingView.swift */; }; 17 | 298BFD4F1F121BB00062F5C8 /* ScrollableGraphViewDrawingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD391F121BB00062F5C8 /* ScrollableGraphViewDrawingLayer.swift */; }; 18 | 298BFD501F121BB00062F5C8 /* GraphPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD3C1F121BB00062F5C8 /* GraphPoint.swift */; }; 19 | 298BFD511F121BB00062F5C8 /* GraphPointAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD3D1F121BB00062F5C8 /* GraphPointAnimation.swift */; }; 20 | 298BFD521F121BB00062F5C8 /* BarPlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD3E1F121BB00062F5C8 /* BarPlot.swift */; }; 21 | 298BFD531F121BB00062F5C8 /* DotPlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD3F1F121BB00062F5C8 /* DotPlot.swift */; }; 22 | 298BFD541F121BB00062F5C8 /* LinePlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD401F121BB00062F5C8 /* LinePlot.swift */; }; 23 | 298BFD551F121BB00062F5C8 /* Plot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD411F121BB00062F5C8 /* Plot.swift */; }; 24 | 298BFD561F121BB00062F5C8 /* ScrollableGraphViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD431F121BB00062F5C8 /* ScrollableGraphViewDataSource.swift */; }; 25 | 298BFD571F121BB00062F5C8 /* ScrollableGraphViewDrawingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD441F121BB00062F5C8 /* ScrollableGraphViewDrawingDelegate.swift */; }; 26 | 298BFD581F121BB00062F5C8 /* LabelPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD461F121BB00062F5C8 /* LabelPool.swift */; }; 27 | 298BFD591F121BB00062F5C8 /* ReferenceLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD471F121BB00062F5C8 /* ReferenceLines.swift */; }; 28 | 298BFD5A1F121BB00062F5C8 /* ScrollableGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BFD481F121BB00062F5C8 /* ScrollableGraphView.swift */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 298BFD261F121BA10062F5C8 /* ScrollableGraphView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ScrollableGraphView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 298BFD291F121BA10062F5C8 /* ScrollableGraphView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScrollableGraphView.h; sourceTree = ""; }; 34 | 298BFD2A1F121BA10062F5C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 298BFD331F121BB00062F5C8 /* BarDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarDrawingLayer.swift; sourceTree = ""; }; 36 | 298BFD341F121BB00062F5C8 /* DotDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotDrawingLayer.swift; sourceTree = ""; }; 37 | 298BFD351F121BB00062F5C8 /* FillDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FillDrawingLayer.swift; sourceTree = ""; }; 38 | 298BFD361F121BB00062F5C8 /* GradientDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientDrawingLayer.swift; sourceTree = ""; }; 39 | 298BFD371F121BB00062F5C8 /* LineDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineDrawingLayer.swift; sourceTree = ""; }; 40 | 298BFD381F121BB00062F5C8 /* ReferenceLineDrawingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceLineDrawingView.swift; sourceTree = ""; }; 41 | 298BFD391F121BB00062F5C8 /* ScrollableGraphViewDrawingLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollableGraphViewDrawingLayer.swift; sourceTree = ""; }; 42 | 298BFD3C1F121BB00062F5C8 /* GraphPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphPoint.swift; sourceTree = ""; }; 43 | 298BFD3D1F121BB00062F5C8 /* GraphPointAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphPointAnimation.swift; sourceTree = ""; }; 44 | 298BFD3E1F121BB00062F5C8 /* BarPlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarPlot.swift; sourceTree = ""; }; 45 | 298BFD3F1F121BB00062F5C8 /* DotPlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotPlot.swift; sourceTree = ""; }; 46 | 298BFD401F121BB00062F5C8 /* LinePlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinePlot.swift; sourceTree = ""; }; 47 | 298BFD411F121BB00062F5C8 /* Plot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Plot.swift; sourceTree = ""; }; 48 | 298BFD431F121BB00062F5C8 /* ScrollableGraphViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollableGraphViewDataSource.swift; sourceTree = ""; }; 49 | 298BFD441F121BB00062F5C8 /* ScrollableGraphViewDrawingDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollableGraphViewDrawingDelegate.swift; sourceTree = ""; }; 50 | 298BFD461F121BB00062F5C8 /* LabelPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelPool.swift; sourceTree = ""; }; 51 | 298BFD471F121BB00062F5C8 /* ReferenceLines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceLines.swift; sourceTree = ""; }; 52 | 298BFD481F121BB00062F5C8 /* ScrollableGraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollableGraphView.swift; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 298BFD221F121BA10062F5C8 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 298BFD1C1F121BA10062F5C8 = { 67 | isa = PBXGroup; 68 | children = ( 69 | 298BFD311F121BB00062F5C8 /* Classes */, 70 | 298BFD281F121BA10062F5C8 /* ScrollableGraphView */, 71 | 298BFD271F121BA10062F5C8 /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 298BFD271F121BA10062F5C8 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 298BFD261F121BA10062F5C8 /* ScrollableGraphView.framework */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | 298BFD281F121BA10062F5C8 /* ScrollableGraphView */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 298BFD291F121BA10062F5C8 /* ScrollableGraphView.h */, 87 | 298BFD2A1F121BA10062F5C8 /* Info.plist */, 88 | ); 89 | path = ScrollableGraphView; 90 | sourceTree = ""; 91 | }; 92 | 298BFD311F121BB00062F5C8 /* Classes */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 298BFD321F121BB00062F5C8 /* Drawing */, 96 | 298BFD3A1F121BB00062F5C8 /* Plots */, 97 | 298BFD421F121BB00062F5C8 /* Protocols */, 98 | 298BFD451F121BB00062F5C8 /* Reference */, 99 | 298BFD481F121BB00062F5C8 /* ScrollableGraphView.swift */, 100 | ); 101 | name = Classes; 102 | path = ../../Classes; 103 | sourceTree = ""; 104 | }; 105 | 298BFD321F121BB00062F5C8 /* Drawing */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 298BFD331F121BB00062F5C8 /* BarDrawingLayer.swift */, 109 | 298BFD341F121BB00062F5C8 /* DotDrawingLayer.swift */, 110 | 298BFD351F121BB00062F5C8 /* FillDrawingLayer.swift */, 111 | 298BFD361F121BB00062F5C8 /* GradientDrawingLayer.swift */, 112 | 298BFD371F121BB00062F5C8 /* LineDrawingLayer.swift */, 113 | 298BFD381F121BB00062F5C8 /* ReferenceLineDrawingView.swift */, 114 | 298BFD391F121BB00062F5C8 /* ScrollableGraphViewDrawingLayer.swift */, 115 | ); 116 | path = Drawing; 117 | sourceTree = ""; 118 | }; 119 | 298BFD3A1F121BB00062F5C8 /* Plots */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 298BFD3B1F121BB00062F5C8 /* Animation */, 123 | 298BFD3E1F121BB00062F5C8 /* BarPlot.swift */, 124 | 298BFD3F1F121BB00062F5C8 /* DotPlot.swift */, 125 | 298BFD401F121BB00062F5C8 /* LinePlot.swift */, 126 | 298BFD411F121BB00062F5C8 /* Plot.swift */, 127 | ); 128 | path = Plots; 129 | sourceTree = ""; 130 | }; 131 | 298BFD3B1F121BB00062F5C8 /* Animation */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 298BFD3C1F121BB00062F5C8 /* GraphPoint.swift */, 135 | 298BFD3D1F121BB00062F5C8 /* GraphPointAnimation.swift */, 136 | ); 137 | path = Animation; 138 | sourceTree = ""; 139 | }; 140 | 298BFD421F121BB00062F5C8 /* Protocols */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 298BFD431F121BB00062F5C8 /* ScrollableGraphViewDataSource.swift */, 144 | 298BFD441F121BB00062F5C8 /* ScrollableGraphViewDrawingDelegate.swift */, 145 | ); 146 | path = Protocols; 147 | sourceTree = ""; 148 | }; 149 | 298BFD451F121BB00062F5C8 /* Reference */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 298BFD461F121BB00062F5C8 /* LabelPool.swift */, 153 | 298BFD471F121BB00062F5C8 /* ReferenceLines.swift */, 154 | ); 155 | path = Reference; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXHeadersBuildPhase section */ 161 | 298BFD231F121BA10062F5C8 /* Headers */ = { 162 | isa = PBXHeadersBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | 298BFD2B1F121BA10062F5C8 /* ScrollableGraphView.h in Headers */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXHeadersBuildPhase section */ 170 | 171 | /* Begin PBXNativeTarget section */ 172 | 298BFD251F121BA10062F5C8 /* ScrollableGraphView */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = 298BFD2E1F121BA10062F5C8 /* Build configuration list for PBXNativeTarget "ScrollableGraphView" */; 175 | buildPhases = ( 176 | 298BFD211F121BA10062F5C8 /* Sources */, 177 | 298BFD221F121BA10062F5C8 /* Frameworks */, 178 | 298BFD231F121BA10062F5C8 /* Headers */, 179 | 298BFD241F121BA10062F5C8 /* Resources */, 180 | ); 181 | buildRules = ( 182 | ); 183 | dependencies = ( 184 | ); 185 | name = ScrollableGraphView; 186 | productName = ScrollableGraphView; 187 | productReference = 298BFD261F121BA10062F5C8 /* ScrollableGraphView.framework */; 188 | productType = "com.apple.product-type.framework"; 189 | }; 190 | /* End PBXNativeTarget section */ 191 | 192 | /* Begin PBXProject section */ 193 | 298BFD1D1F121BA10062F5C8 /* Project object */ = { 194 | isa = PBXProject; 195 | attributes = { 196 | LastUpgradeCheck = 0930; 197 | ORGANIZATIONNAME = SGV; 198 | TargetAttributes = { 199 | 298BFD251F121BA10062F5C8 = { 200 | CreatedOnToolsVersion = 8.3.3; 201 | ProvisioningStyle = Automatic; 202 | }; 203 | }; 204 | }; 205 | buildConfigurationList = 298BFD201F121BA10062F5C8 /* Build configuration list for PBXProject "ScrollableGraphView" */; 206 | compatibilityVersion = "Xcode 3.2"; 207 | developmentRegion = English; 208 | hasScannedForEncodings = 0; 209 | knownRegions = ( 210 | en, 211 | ); 212 | mainGroup = 298BFD1C1F121BA10062F5C8; 213 | productRefGroup = 298BFD271F121BA10062F5C8 /* Products */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | 298BFD251F121BA10062F5C8 /* ScrollableGraphView */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 298BFD241F121BA10062F5C8 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXResourcesBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | 298BFD211F121BA10062F5C8 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 298BFD551F121BB00062F5C8 /* Plot.swift in Sources */, 238 | 298BFD561F121BB00062F5C8 /* ScrollableGraphViewDataSource.swift in Sources */, 239 | 298BFD541F121BB00062F5C8 /* LinePlot.swift in Sources */, 240 | 298BFD571F121BB00062F5C8 /* ScrollableGraphViewDrawingDelegate.swift in Sources */, 241 | 298BFD581F121BB00062F5C8 /* LabelPool.swift in Sources */, 242 | 298BFD4C1F121BB00062F5C8 /* GradientDrawingLayer.swift in Sources */, 243 | 298BFD4A1F121BB00062F5C8 /* DotDrawingLayer.swift in Sources */, 244 | 298BFD521F121BB00062F5C8 /* BarPlot.swift in Sources */, 245 | 298BFD4B1F121BB00062F5C8 /* FillDrawingLayer.swift in Sources */, 246 | 298BFD4D1F121BB00062F5C8 /* LineDrawingLayer.swift in Sources */, 247 | 298BFD491F121BB00062F5C8 /* BarDrawingLayer.swift in Sources */, 248 | 298BFD4F1F121BB00062F5C8 /* ScrollableGraphViewDrawingLayer.swift in Sources */, 249 | 298BFD4E1F121BB00062F5C8 /* ReferenceLineDrawingView.swift in Sources */, 250 | 298BFD591F121BB00062F5C8 /* ReferenceLines.swift in Sources */, 251 | 298BFD511F121BB00062F5C8 /* GraphPointAnimation.swift in Sources */, 252 | 298BFD531F121BB00062F5C8 /* DotPlot.swift in Sources */, 253 | 298BFD5A1F121BB00062F5C8 /* ScrollableGraphView.swift in Sources */, 254 | 298BFD501F121BB00062F5C8 /* GraphPoint.swift in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXSourcesBuildPhase section */ 259 | 260 | /* Begin XCBuildConfiguration section */ 261 | 298BFD2C1F121BA10062F5C8 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 272 | CLANG_WARN_BOOL_CONVERSION = YES; 273 | CLANG_WARN_COMMA = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INFINITE_RECURSION = YES; 281 | CLANG_WARN_INT_CONVERSION = YES; 282 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 284 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 287 | CLANG_WARN_STRICT_PROTOTYPES = YES; 288 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 289 | CLANG_WARN_UNREACHABLE_CODE = YES; 290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 291 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 292 | COPY_PHASE_STRIP = NO; 293 | CURRENT_PROJECT_VERSION = 1; 294 | DEBUG_INFORMATION_FORMAT = dwarf; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 312 | MTL_ENABLE_DEBUG_INFO = YES; 313 | ONLY_ACTIVE_ARCH = YES; 314 | SDKROOT = iphoneos; 315 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 316 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 317 | TARGETED_DEVICE_FAMILY = "1,2"; 318 | VERSIONING_SYSTEM = "apple-generic"; 319 | VERSION_INFO_PREFIX = ""; 320 | }; 321 | name = Debug; 322 | }; 323 | 298BFD2D1F121BA10062F5C8 /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_ANALYZER_NONNULL = YES; 328 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_COMMA = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 339 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INFINITE_RECURSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 345 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 346 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 349 | CLANG_WARN_STRICT_PROTOTYPES = YES; 350 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 351 | CLANG_WARN_UNREACHABLE_CODE = YES; 352 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 354 | COPY_PHASE_STRIP = NO; 355 | CURRENT_PROJECT_VERSION = 1; 356 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 357 | ENABLE_NS_ASSERTIONS = NO; 358 | ENABLE_STRICT_OBJC_MSGSEND = YES; 359 | GCC_C_LANGUAGE_STANDARD = gnu99; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 368 | MTL_ENABLE_DEBUG_INFO = NO; 369 | SDKROOT = iphoneos; 370 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 371 | TARGETED_DEVICE_FAMILY = "1,2"; 372 | VALIDATE_PRODUCT = YES; 373 | VERSIONING_SYSTEM = "apple-generic"; 374 | VERSION_INFO_PREFIX = ""; 375 | }; 376 | name = Release; 377 | }; 378 | 298BFD2F1F121BA10062F5C8 /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | CODE_SIGN_IDENTITY = ""; 382 | DEFINES_MODULE = YES; 383 | DYLIB_COMPATIBILITY_VERSION = 1; 384 | DYLIB_CURRENT_VERSION = 1; 385 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 386 | INFOPLIST_FILE = ScrollableGraphView/Info.plist; 387 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 389 | PRODUCT_BUNDLE_IDENTIFIER = SGV.ScrollableGraphView; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | SKIP_INSTALL = YES; 392 | SWIFT_VERSION = 4.2; 393 | }; 394 | name = Debug; 395 | }; 396 | 298BFD301F121BA10062F5C8 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | CODE_SIGN_IDENTITY = ""; 400 | DEFINES_MODULE = YES; 401 | DYLIB_COMPATIBILITY_VERSION = 1; 402 | DYLIB_CURRENT_VERSION = 1; 403 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 404 | INFOPLIST_FILE = ScrollableGraphView/Info.plist; 405 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 407 | PRODUCT_BUNDLE_IDENTIFIER = SGV.ScrollableGraphView; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | SKIP_INSTALL = YES; 410 | SWIFT_VERSION = 4.2; 411 | }; 412 | name = Release; 413 | }; 414 | /* End XCBuildConfiguration section */ 415 | 416 | /* Begin XCConfigurationList section */ 417 | 298BFD201F121BA10062F5C8 /* Build configuration list for PBXProject "ScrollableGraphView" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | 298BFD2C1F121BA10062F5C8 /* Debug */, 421 | 298BFD2D1F121BA10062F5C8 /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | 298BFD2E1F121BA10062F5C8 /* Build configuration list for PBXNativeTarget "ScrollableGraphView" */ = { 427 | isa = XCConfigurationList; 428 | buildConfigurations = ( 429 | 298BFD2F1F121BA10062F5C8 /* Debug */, 430 | 298BFD301F121BA10062F5C8 /* Release */, 431 | ); 432 | defaultConfigurationIsVisible = 0; 433 | defaultConfigurationName = Release; 434 | }; 435 | /* End XCConfigurationList section */ 436 | }; 437 | rootObject = 298BFD1D1F121BA10062F5C8 /* Project object */; 438 | } 439 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView.xcodeproj/xcshareddata/xcschemes/ScrollableGraphView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /carthage/ScrollableGraphView/ScrollableGraphView/ScrollableGraphView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollableGraphView.h 3 | // ScrollableGraphView 4 | // 5 | 6 | #import 7 | 8 | //! Project version number for ScrollableGraphView. 9 | FOUNDATION_EXPORT double ScrollableGraphViewVersionNumber; 10 | 11 | //! Project version string for ScrollableGraphView. 12 | FOUNDATION_EXPORT const unsigned char ScrollableGraphViewVersionString[]; 13 | 14 | // In this header, you should import all the public headers of your framework using statements like #import 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples.md: -------------------------------------------------------------------------------- 1 | # Applications Using This Component 2 | 3 | - ApplicationName - http://url.to.app -------------------------------------------------------------------------------- /readme_images/IMG_5814_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/IMG_5814_small.jpg -------------------------------------------------------------------------------- /readme_images/adapting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/adapting.gif -------------------------------------------------------------------------------- /readme_images/animating.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/animating.gif -------------------------------------------------------------------------------- /readme_images/customising.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/customising.gif -------------------------------------------------------------------------------- /readme_images/dynamic-reload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/dynamic-reload.gif -------------------------------------------------------------------------------- /readme_images/gallery-v4/bar-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/bar-dark.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/dot.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/line-dark-smooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/line-dark-smooth.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/line-pink-straight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/line-pink-straight.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/multi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/multi1.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/multi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/multi2.png -------------------------------------------------------------------------------- /readme_images/gallery-v4/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery-v4/simple.png -------------------------------------------------------------------------------- /readme_images/gallery/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/bar.png -------------------------------------------------------------------------------- /readme_images/gallery/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/dark.png -------------------------------------------------------------------------------- /readme_images/gallery/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/default.png -------------------------------------------------------------------------------- /readme_images/gallery/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/dot.png -------------------------------------------------------------------------------- /readme_images/gallery/pink_margins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/pink_margins.png -------------------------------------------------------------------------------- /readme_images/gallery/pink_mountain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/gallery/pink_mountain.png -------------------------------------------------------------------------------- /readme_images/init_anim_high_fps.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/init_anim_high_fps.gif -------------------------------------------------------------------------------- /readme_images/more_scrolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/more_scrolling.gif -------------------------------------------------------------------------------- /readme_images/scrolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/scrolling.gif -------------------------------------------------------------------------------- /readme_images/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/simple.png -------------------------------------------------------------------------------- /readme_images/spacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philackm/ScrollableGraphView/aa0948b2fce02b1607c4b6855387e114ffcc447a/readme_images/spacing.png --------------------------------------------------------------------------------