Adds History Graph for channels
This commit is contained in:
21
Pods/SwiftChart/LICENSE.txt
generated
Normal file
21
Pods/SwiftChart/LICENSE.txt
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Giampaolo Bellavite
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
401
Pods/SwiftChart/README.md
generated
Normal file
401
Pods/SwiftChart/README.md
generated
Normal file
@@ -0,0 +1,401 @@
|
||||
SwiftChart
|
||||
===========
|
||||
|
||||
[](http://cocoapods.org/pods/SwiftChart)
|
||||
[](http://cocoapods.org/pods/SwiftChart)
|
||||
[](http://cocoapods.org/pods/SwiftChart)
|
||||
|
||||
A simple line and area charting library for iOS.
|
||||
|
||||
* 📈 Line and area charts
|
||||
* 🌞 Multiple series
|
||||
* 🌒 Partially filled series
|
||||
* 🏊 Works with signed `Double`
|
||||
* 🖖 Touch events
|
||||
|
||||
<p align="center">
|
||||
<img src="https://cloud.githubusercontent.com/assets/120693/11602670/57ef6b26-9adc-11e5-9f95-b226a2491654.png" height="180"><img src="https://cloud.githubusercontent.com/assets/120693/11602672/5c303ac6-9adc-11e5-9006-3275a16b7ec8.png" height="180">
|
||||
<img src="https://cloud.githubusercontent.com/assets/120693/11602674/5ed8a808-9adc-11e5-9e30-f55beacf9a94.png" height="180"><img src="https://cloud.githubusercontent.com/assets/120693/11602678/660d660e-9adc-11e5-8a67-0c3036c20862.gif" height="180">
|
||||
</p>
|
||||
|
||||
---
|
||||
<details>
|
||||
<summary><strong>Table of Content</strong></summary>
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [Getting started](#getting-started)
|
||||
- [Installing SwiftChart via CocoaPods](#installing-swiftchart-via-cocoapods)
|
||||
- [Installing SwiftChart manually](#installing-swiftchart-manually)
|
||||
- [What’s included in SwiftChart](#whats-included-in-swiftchart)
|
||||
- [How to use SwiftChart](#how-to-use-swiftchart)
|
||||
- [Initialize a chart from the Interface Builder](#initialize-a-chart-from-the-interface-builder)
|
||||
- [Initialize a chart programmatically](#initialize-a-chart-programmatically)
|
||||
- [Adding a series to a chart](#adding-a-series-to-a-chart)
|
||||
- [Using partially filled series](#using-partially-filled-series)
|
||||
- [Using different colors above and below zero](#using-different-colors-above-and-below-zero)
|
||||
- [Adding multiple series to a chart](#adding-multiple-series-to-a-chart)
|
||||
- [Configuring touch events](#configuring-touch-events)
|
||||
- [API](#api)
|
||||
- [`Chart` class](#chart-class)
|
||||
- [Chart options](#chart-options)
|
||||
- [Public Methods](#public-methods)
|
||||
- [`ChartSeries` class](#chartseries-class)
|
||||
- [`ChartDelegate` protocol](#chartdelegate-protocol)
|
||||
- [`ChartColors` enum](#chartcolors-enum)
|
||||
- [`ChartPoint` typealias](#chartpoint-typealias)
|
||||
- [`ChartLabelOrientation` enum](#chartlabelorientation-enum)
|
||||
- [Common issues and solutions](#common-issues-and-solutions)
|
||||
- [The chart is not showing](#the-chart-is-not-showing)
|
||||
- [License](#license)
|
||||
|
||||
<!-- /TOC -->
|
||||
</details>
|
||||
|
||||
|
||||
# Getting started
|
||||
|
||||
## Installing SwiftChart via CocoaPods
|
||||
|
||||
SwiftChart is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod "SwiftChart"
|
||||
```
|
||||
|
||||
## Installing SwiftChart manually
|
||||
|
||||
1. Download **SwiftChart.zip** from the [last release](https://github.com/gpbl/SwiftChart/releases/latest) and extract its content in your project's folder.
|
||||
2. From the Xcode project, choose *Add Files to <ProjectName>...* from the *File* menu and add the extracted files.
|
||||
|
||||
## What’s included in SwiftChart
|
||||
|
||||
The library includes:
|
||||
|
||||
- the [Chart](Source/Chart.swift#L40) main class, to initialize and configure the chart’s content, e.g. for adding series or setting up the its appearance
|
||||
- the [ChartSeries](Source/ChartSeries.swift) class, for creating datasets and configure their appearance
|
||||
- the [ChartDelegate](Source/Chart.swift#L10-L32) protocol, which tells other objects about the chart’s touch events
|
||||
- the [ChartColor](Source/ChartColors.swift) struct, containing some predefined colors
|
||||
|
||||
**Example**
|
||||
|
||||
```swift
|
||||
let chart = Chart()
|
||||
let series = ChartSeries([0, 6, 2, 8, 4, 7, 3, 10, 8])
|
||||
series.color = ChartColors.greenColor()
|
||||
chart.add(series)
|
||||
```
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
|
||||
# How to use SwiftChart
|
||||
|
||||
## Initialize a chart from the Interface Builder
|
||||
|
||||
The chart can be initialized from the Interface Builder. Drag a normal View into a View Controller and assign to it the `Chart` Custom Class from the Identity Inspector.
|
||||
|
||||
## Initialize a chart programmatically
|
||||
|
||||
To initialize a chart programmatically, use the `Chart(frame: ...)` initializer, which requires a `frame`:
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
```
|
||||
|
||||
If you prefer to use Autolayout, set the frame to `0` and add the constraints later:
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRectZero)
|
||||
// add constraints now
|
||||
```
|
||||
|
||||
## Adding a series to a chart
|
||||
|
||||
Initialize each series before adding them to the chart. To do so, pass an array to initialize a `ChartSeries` object:
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
let series = ChartSeries([0, 6.5, 2, 8, 4.1, 7, -3.1, 10, 8])
|
||||
chart.add(series)
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="400" src="https://user-images.githubusercontent.com/120693/34648353-b66f352a-f398-11e7-98b9-9d15dcbdd692.png">
|
||||
|
||||
As you can see, as default the values on the x-axis are the progressive indexes of the passed array. You can customize those values by passing an array of `(x: Double, y: Double)` tuples to the series initializer:
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
// Create a new series specifying x and y values
|
||||
let data = [
|
||||
(x: 0, y: 0),
|
||||
(x: 1, y: 3.1),
|
||||
(x: 4, y: 2),
|
||||
(x: 5, y: 4.2),
|
||||
(x: 7, y: 5),
|
||||
(x: 9, y: 9),
|
||||
(x: 10, y: 8)
|
||||
]
|
||||
let series = ChartSeries(data: data)
|
||||
chart.add(series)
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="400" src="https://user-images.githubusercontent.com/120693/34648477-f8a0c48a-f399-11e7-9e36-123171b6413b.png">
|
||||
|
||||
## Using partially filled series
|
||||
|
||||
Use the `chart.xLabels` property to make the x-axis showing more labels than those inferred from the actual data. For example,
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
let data = [
|
||||
(x: 0, y: 0),
|
||||
(x: 3, y: 2.5),
|
||||
(x: 4, y: 2),
|
||||
(x: 5, y: 2.3),
|
||||
(x: 7, y: 3),
|
||||
(x: 8, y: 2.2),
|
||||
(x: 9, y: 2.5)
|
||||
]
|
||||
let series = ChartSeries(data: data)
|
||||
series.area = true
|
||||
|
||||
// Use `xLabels` to add more labels, even if empty
|
||||
chart.xLabels = [0, 3, 6, 9, 12, 15, 18, 21, 24]
|
||||
|
||||
// Format the labels with a unit
|
||||
chart.xLabelsFormatter = { String(Int(round($1))) + "h" }
|
||||
|
||||
chart.add(series)
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="400" src="https://user-images.githubusercontent.com/120693/34648482-28818ee6-f39a-11e7-99d3-0eb0f1402f73.png">
|
||||
|
||||
## Using different colors above and below zero
|
||||
|
||||
The chart displays the series in different colors when below or above the zero-axis:
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
let data: [Double] = [0, -2, -2, 3, -3, 4, 1, 0, -1]
|
||||
|
||||
let series = ChartSeries(data)
|
||||
series.area = true
|
||||
|
||||
chart.add(series)
|
||||
|
||||
// Set minimum and maximum values for y-axis
|
||||
chart.minY = -7
|
||||
chart.maxY = 7
|
||||
|
||||
// Format y-axis, e.g. with units
|
||||
chart.yLabelsFormatter = { String(Int($1)) + "ºC" }
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="410" src="https://user-images.githubusercontent.com/120693/34648596-3f0538be-f39c-11e7-9cb3-ea06c025b09c.png">
|
||||
|
||||
You can customize the zero-axis and the colors with the `colors` options in the `ChartSeries` class.
|
||||
|
||||
```swift
|
||||
series.colors = (
|
||||
above: ChartColors.greenColor(),
|
||||
below: ChartColors.yellowColor(),
|
||||
zeroLevel: -1
|
||||
)
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="410" src="https://user-images.githubusercontent.com/120693/34648597-3f269158-f39c-11e7-90d3-d3dfb120c95d.png">
|
||||
|
||||
|
||||
## Adding multiple series to a chart
|
||||
|
||||
Using the `chart.add(series: ChartSeries)` and `chart.add(series: Array<ChartSeries>)` methods you can add more series. Those will be indentified with a progressive index in the chart’s `series` property.
|
||||
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
|
||||
let series1 = ChartSeries([0, 6, 2, 8, 4, 7, 3, 10, 8])
|
||||
series1.color = ChartColors.yellowColor()
|
||||
series1.area = true
|
||||
|
||||
let series2 = ChartSeries([1, 0, 0.5, 0.2, 0, 1, 0.8, 0.3, 1])
|
||||
series2.color = ChartColors.redColor()
|
||||
series2.area = true
|
||||
|
||||
// A partially filled series
|
||||
let series3 = ChartSeries([9, 8, 10, 8.5, 9.5, 10])
|
||||
series3.color = ChartColors.purpleColor()
|
||||
|
||||
chart.add([series1, series2, series3])
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
<img width="412" alt="screen shot 2018-01-07 at 11 06 55" src="https://user-images.githubusercontent.com/120693/34648532-282fcda8-f39b-11e7-93f3-c502329752b5.png">
|
||||
|
||||
## Configuring touch events
|
||||
|
||||
To make the chart respond to touch events, implement the `ChartDelegate` protocol in your class, e.g. a View Controller, and then set the chart’s `delegate` property:
|
||||
|
||||
```swift
|
||||
class MyViewController: UIViewController, ChartDelegate {
|
||||
override func viewDidLoad() {
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
|
||||
chart.delegate = self
|
||||
}
|
||||
|
||||
// Chart delegate
|
||||
func didTouchChart(chart: Chart, indexes: Array<Int?>, x: Double, left: CGFloat) {
|
||||
// Do something on touch
|
||||
}
|
||||
|
||||
func didFinishTouchingChart(chart: Chart) {
|
||||
// Do something when finished
|
||||
}
|
||||
|
||||
func didEndTouchingChart(chart: Chart) {
|
||||
// Do something when ending touching chart
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `didTouchChart` method passes an array of indexes, one for each series, with an optional `Int` referring to the data’s index:
|
||||
|
||||
```swift
|
||||
func didTouchChart(chart: Chart, indexes: Array<Int?>, x: Double, left: CGFloat) {
|
||||
for (seriesIndex, dataIndex) in enumerate(indexes) {
|
||||
if dataIndex != nil {
|
||||
// The series at `seriesIndex` is that which has been touched
|
||||
let value = chart.valueForSeries(seriesIndex, atIndex: dataIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use `chart.valueForSeries()` to access the value for the touched position.
|
||||
|
||||
The `x: Double` argument refers to the value on the x-axis: it is inferred from the horizontal position of the touch event, and may be not part of the series values.
|
||||
|
||||
The `left: CGFloat` is the x position on the chart’s view, starting from the left side. It may be used to set the position for a label moving above the chart:
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/120693/11602678/660d660e-9adc-11e5-8a67-0c3036c20862.gif" height="200">
|
||||
|
||||
# API
|
||||
|
||||
## `Chart` class
|
||||
|
||||
Use the `Chart` class to initialize and configure the chart’s content, e.g. for adding series or setting up the its appearance.
|
||||
|
||||
**Example**
|
||||
|
||||
```swift
|
||||
let chart = Chart(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
```
|
||||
|
||||
### Chart options
|
||||
|
||||
| Option Name | Description |
|
||||
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `areaAlphaComponent` | Alpha factor for the areas colors (`CGFloat`, default `0.1`) |
|
||||
| `axesColor` | The color of the axes (`UIColor`, default `.gray`) |
|
||||
| `bottomInset` | Height of the area at the bottom of the chart, containing the labels for the x-axis (`CGFloat`, default `20`) |
|
||||
| `delegate` | The delegate to listen to touch events (`ChartDelegate`) |
|
||||
| `highlightLineColor` | The color of the highlight line (`UIColor`, default `gray`) |
|
||||
| `highlightLineWidth` | The width of the highlight line (`CGFloat`, default `0.5`) |
|
||||
| `hideHighlightLineOnTouchEnd` | Hide the highlight line when the touch event ends, e.g. when stop swiping over the chart (`Bool`, default `false`) |
|
||||
| `gridColor` | The color of the grid (`UIColor`, default `.gray`) |
|
||||
| `labelColor` | The color of the labels (`UIColor`, default `.black`) |
|
||||
| `labelFont` | The font used for the labels (`UIFont?`) |
|
||||
| `lineWidth` | The width of the chart's lines (`CGFloat`, default `2`) |
|
||||
| `maxX` | A custom maximum x-value (`Double?`) |
|
||||
| `maxY` | A custom maximum y-value (`Double?`) |
|
||||
| `minX` | A custom minimum x-value (`Double?`) |
|
||||
| `minY` | A custom minimum y-value (`Double?`) |
|
||||
| `showXLabelsAndGrid` | Enable the lines for the labels on the x-axis (`Bool`, default `true`) |
|
||||
| `showYLabelsAndGrid` | Enable the lines for the labels on the y-axis (`Bool`, default `true`) |
|
||||
| `topInset` | Height of the area at the top of the chart, acting a padding to make place for the top y-axis label (`CGFloat`, default `20`) |
|
||||
| `xLabels` | The values to display as labels on the x-axis. You can format these values with the `xLabelFormatter` attribute. As default, it will display the values of the series which has the most data. `[Double]?` |
|
||||
| `xLabelsFormatter` | Function to format the labels on the x-axis (`(Int, Double) -> String`) |
|
||||
| `xLabelsOrientation:` | Set the x-axis labels orientation to `vertical` or `horizontal` (`ChartLabelOrientation`, default `.horizontal`) |
|
||||
| `xLabelsTextAlignment:` | Alignment for the text in the x-labels (`NSTextAlignment`, default `.left`) |
|
||||
| `xLabelsSkipLast:` | Skip the last x-label. Setting this to `false` will make the label overflow the frame width, so use carefully (`Bool`, default `true`) |
|
||||
| `yLabels` | Values to display as labels of the y-axis. If not specified, will display the lowest, the middle and the highest values. |
|
||||
| `yLabelsFormatter` | Function to format the labels on the y-axis (`(Int, Double) -> String`) |
|
||||
| `yLabelsOnRightSide` | Place the y-labels on the right side (`Bool`, default `false`) |
|
||||
|
||||
### Public Methods
|
||||
|
||||
| Method Name | Description |
|
||||
|-------------------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| `add` | Add a series to the chart `(_ series: ChartSeries)` `(_ series: [ChartSeries])` |
|
||||
| `removeSeriesAt` | Remove the series at the specified index `(_ index: Int)` |
|
||||
| `removeAllSeries` | Remove all the series |
|
||||
| `valueForSeries` | Returns the value for the specified series at the given index `(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Double?` |
|
||||
|
||||
## `ChartSeries` class
|
||||
|
||||
Use the `ChartSeries` class to create a chart series and configure its appearance and behavior.
|
||||
|
||||
**Example**
|
||||
|
||||
```swift
|
||||
let data: [Double] = [0, -2, -2, 3, -3, 4, 1, 0, -1]
|
||||
let series = ChartSeries(data)
|
||||
```
|
||||
|
||||
| Option Name | Description |
|
||||
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `area` | Draws an area below the series line (`Bool`, default `false`) |
|
||||
| `line` | When set to `false`, will hide the series line. Useful for drawing only the area with `area=true` (`Bool`, default `true`) |
|
||||
| `color` | The series color. You can use the `ChartColors` struct for some colors shortcuts. (`UIColor`, default `.blueColor()`) |
|
||||
| `colors` | A tuple to specify the color above or below the zero (or the value specified by `zeroLevel`) `(above: UIColor, below: UIColor, zeroLevel: Double)` |
|
||||
|
||||
## `ChartDelegate` protocol
|
||||
|
||||
Use the `ChartDelegate` protocol to tell other objects about the chart’s touch events.
|
||||
|
||||
| Method | Description |
|
||||
|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `didTouchChart` | Tells the delegate that the specified chart has been touched |
|
||||
| `didFinishTouchingChart` | Tells the delegate that the user finished touching the chart. The user will "finish" touching the chart only swiping left/right outside the chart. |
|
||||
| `didEndTouchingChart` | Tells the delegate that the user ended touching the chart. The user will "end" touching the chart whenever the `touchesDidEnd` method is being called. |
|
||||
|
||||
## `ChartColors` enum
|
||||
|
||||
Shorthands for various colors.
|
||||
|
||||
**Example**
|
||||
|
||||
```swift
|
||||
let series = ChartSeries([0, 6, 2, 8, 4, 7, 3, 10, 8])
|
||||
series.color = ChartColors.blueColor()
|
||||
```
|
||||
|
||||
# Common issues and solutions
|
||||
|
||||
If you have issue with this library, please tag your question with `swiftchart` on [Stack Overflow](http://stackoverflow.com/tags/swiftcharts/info).
|
||||
|
||||
## The chart is not showing
|
||||
|
||||
The `Chart` class inherits from `UIView`, so if your chart is not displaying it is likely a problem related to the view's size. Check your view constraints and make sure you initialize it on `viewDidLoad`, when UIKit can calculate the view dimensions.
|
||||
|
||||
Some tips for debugging an hidden chart:
|
||||
|
||||
* start your app and then debug the UI Hierarchy from the Debug navigator
|
||||
* initialize a simple UIView with a colored background instead of the chart to easily see how the view is positioned
|
||||
* try to not to nest the chart in a subview for better debugging
|
||||
|
||||
# License
|
||||
|
||||
SwiftChart is available under the MIT license. See the LICENSE file for more info.
|
||||
849
Pods/SwiftChart/Source/Chart.swift
generated
Normal file
849
Pods/SwiftChart/Source/Chart.swift
generated
Normal file
@@ -0,0 +1,849 @@
|
||||
//
|
||||
// Chart.swift
|
||||
//
|
||||
// Created by Giampaolo Bellavite on 07/11/14.
|
||||
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public protocol ChartDelegate: class {
|
||||
|
||||
/**
|
||||
Tells the delegate that the specified chart has been touched.
|
||||
|
||||
- parameter chart: The chart that has been touched.
|
||||
- parameter indexes: Each element of this array contains the index of the data that has been touched, one for each
|
||||
series. If the series hasn't been touched, its index will be nil.
|
||||
- parameter x: The value on the x-axis that has been touched.
|
||||
- parameter left: The distance from the left side of the chart.
|
||||
|
||||
*/
|
||||
func didTouchChart(_ chart: Chart, indexes: [Int?], x: Double, left: CGFloat)
|
||||
|
||||
/**
|
||||
Tells the delegate that the user finished touching the chart. The user will
|
||||
"finish" touching the chart only swiping left/right outside the chart.
|
||||
|
||||
- parameter chart: The chart that has been touched.
|
||||
|
||||
*/
|
||||
func didFinishTouchingChart(_ chart: Chart)
|
||||
/**
|
||||
Tells the delegate that the user ended touching the chart. The user
|
||||
will "end" touching the chart whenever the touchesDidEnd method is
|
||||
being called.
|
||||
|
||||
- parameter chart: The chart that has been touched.
|
||||
|
||||
*/
|
||||
func didEndTouchingChart(_ chart: Chart)
|
||||
}
|
||||
|
||||
/**
|
||||
Represent the x- and the y-axis values for each point in a chart series.
|
||||
*/
|
||||
typealias ChartPoint = (x: Double, y: Double)
|
||||
|
||||
/**
|
||||
Set the a x-label orientation.
|
||||
*/
|
||||
public enum ChartLabelOrientation {
|
||||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
open class Chart: UIControl {
|
||||
|
||||
// MARK: Options
|
||||
|
||||
@IBInspectable
|
||||
open var identifier: String?
|
||||
|
||||
/**
|
||||
Series to display in the chart.
|
||||
*/
|
||||
open var series: [ChartSeries] = [] {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The values to display as labels on the x-axis. You can format these values with the `xLabelFormatter` attribute.
|
||||
As default, it will display the values of the series which has the most data.
|
||||
*/
|
||||
open var xLabels: [Double]?
|
||||
|
||||
/**
|
||||
Formatter for the labels on the x-axis. `index` represents the `xLabels` index, `value` its value.
|
||||
*/
|
||||
open var xLabelsFormatter = { (labelIndex: Int, labelValue: Double) -> String in
|
||||
String(Int(labelValue))
|
||||
}
|
||||
|
||||
/**
|
||||
Text alignment for the x-labels.
|
||||
*/
|
||||
open var xLabelsTextAlignment: NSTextAlignment = .left
|
||||
|
||||
/**
|
||||
Orientation for the x-labels.
|
||||
*/
|
||||
open var xLabelsOrientation: ChartLabelOrientation = .horizontal
|
||||
|
||||
/**
|
||||
Skip the last x-label. Setting this to false may make the label overflow the frame width.
|
||||
*/
|
||||
open var xLabelsSkipLast: Bool = true
|
||||
|
||||
/**
|
||||
Values to display as labels of the y-axis. If not specified, will display the lowest, the middle and the highest
|
||||
values.
|
||||
*/
|
||||
open var yLabels: [Double]?
|
||||
|
||||
/**
|
||||
Formatter for the labels on the y-axis.
|
||||
*/
|
||||
open var yLabelsFormatter = { (labelIndex: Int, labelValue: Double) -> String in
|
||||
String(Int(labelValue))
|
||||
}
|
||||
|
||||
/**
|
||||
Displays the y-axis labels on the right side of the chart.
|
||||
*/
|
||||
open var yLabelsOnRightSide: Bool = false
|
||||
|
||||
/**
|
||||
Font used for the labels.
|
||||
*/
|
||||
open var labelFont: UIFont? = UIFont.systemFont(ofSize: 12)
|
||||
|
||||
/**
|
||||
The color used for the labels.
|
||||
*/
|
||||
@IBInspectable
|
||||
open var labelColor: UIColor = UIColor.black
|
||||
|
||||
/**
|
||||
Color for the axes.
|
||||
*/
|
||||
@IBInspectable
|
||||
open var axesColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
|
||||
|
||||
/**
|
||||
Color for the grid.
|
||||
*/
|
||||
@IBInspectable
|
||||
open var gridColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
|
||||
/**
|
||||
Enable the lines for the labels on the x-axis
|
||||
*/
|
||||
open var showXLabelsAndGrid: Bool = true
|
||||
/**
|
||||
Enable the lines for the labels on the y-axis
|
||||
*/
|
||||
open var showYLabelsAndGrid: Bool = true
|
||||
|
||||
/**
|
||||
Height of the area at the bottom of the chart, containing the labels for the x-axis.
|
||||
*/
|
||||
open var bottomInset: CGFloat = 20
|
||||
|
||||
/**
|
||||
Height of the area at the top of the chart, acting a padding to make place for the top y-axis label.
|
||||
*/
|
||||
open var topInset: CGFloat = 20
|
||||
|
||||
/**
|
||||
Width of the chart's lines.
|
||||
*/
|
||||
@IBInspectable
|
||||
open var lineWidth: CGFloat = 2
|
||||
|
||||
/**
|
||||
Delegate for listening to Chart touch events.
|
||||
*/
|
||||
weak open var delegate: ChartDelegate?
|
||||
|
||||
/**
|
||||
Custom minimum value for the x-axis.
|
||||
*/
|
||||
open var minX: Double?
|
||||
|
||||
/**
|
||||
Custom minimum value for the y-axis.
|
||||
*/
|
||||
open var minY: Double?
|
||||
|
||||
/**
|
||||
Custom maximum value for the x-axis.
|
||||
*/
|
||||
open var maxX: Double?
|
||||
|
||||
/**
|
||||
Custom maximum value for the y-axis.
|
||||
*/
|
||||
open var maxY: Double?
|
||||
|
||||
/**
|
||||
Color for the highlight line.
|
||||
*/
|
||||
open var highlightLineColor = UIColor.gray
|
||||
|
||||
/**
|
||||
Width for the highlight line.
|
||||
*/
|
||||
open var highlightLineWidth: CGFloat = 0.5
|
||||
|
||||
/**
|
||||
Hide the highlight line when touch event ends, e.g. when stop swiping over the chart
|
||||
*/
|
||||
open var hideHighlightLineOnTouchEnd = false
|
||||
|
||||
/**
|
||||
Alpha component for the area color.
|
||||
*/
|
||||
open var areaAlphaComponent: CGFloat = 0.1
|
||||
|
||||
// MARK: Private variables
|
||||
|
||||
fileprivate var highlightShapeLayer: CAShapeLayer!
|
||||
fileprivate var layerStore: [CAShapeLayer] = []
|
||||
|
||||
fileprivate var drawingHeight: CGFloat!
|
||||
fileprivate var drawingWidth: CGFloat!
|
||||
|
||||
// Minimum and maximum values represented in the chart
|
||||
fileprivate var min: ChartPoint!
|
||||
fileprivate var max: ChartPoint!
|
||||
|
||||
// Represent a set of points corresponding to a segment line on the chart.
|
||||
typealias ChartLineSegment = [ChartPoint]
|
||||
|
||||
// MARK: initializations
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
convenience public init() {
|
||||
self.init(frame: .zero)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
backgroundColor = UIColor.clear
|
||||
contentMode = .redraw // redraw rects on bounds change
|
||||
}
|
||||
|
||||
override open func draw(_ rect: CGRect) {
|
||||
#if TARGET_INTERFACE_BUILDER
|
||||
drawIBPlaceholder()
|
||||
#else
|
||||
drawChart()
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
Adds a chart series.
|
||||
*/
|
||||
open func add(_ series: ChartSeries) {
|
||||
self.series.append(series)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds multiple chart series.
|
||||
*/
|
||||
open func add(_ series: [ChartSeries]) {
|
||||
for s in series {
|
||||
add(s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the series at the specified index.
|
||||
*/
|
||||
open func removeSeriesAt(_ index: Int) {
|
||||
series.remove(at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all the series.
|
||||
*/
|
||||
open func removeAllSeries() {
|
||||
series = []
|
||||
}
|
||||
|
||||
/**
|
||||
Return the value for the specified series at the given index.
|
||||
*/
|
||||
open func valueForSeries(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Double? {
|
||||
if dataIndex == nil { return nil }
|
||||
let series = self.series[seriesIndex] as ChartSeries
|
||||
return series.data[dataIndex!].y
|
||||
}
|
||||
|
||||
fileprivate func drawIBPlaceholder() {
|
||||
let placeholder = UIView(frame: self.frame)
|
||||
placeholder.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1)
|
||||
let label = UILabel()
|
||||
label.text = "Chart"
|
||||
label.font = UIFont.systemFont(ofSize: 28)
|
||||
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
|
||||
label.sizeToFit()
|
||||
label.frame.origin.x += frame.width/2 - (label.frame.width / 2)
|
||||
label.frame.origin.y += frame.height/2 - (label.frame.height / 2)
|
||||
|
||||
placeholder.addSubview(label)
|
||||
addSubview(placeholder)
|
||||
}
|
||||
|
||||
fileprivate func drawChart() {
|
||||
|
||||
drawingHeight = bounds.height - bottomInset - topInset
|
||||
drawingWidth = bounds.width
|
||||
|
||||
let minMax = getMinMax()
|
||||
min = minMax.min
|
||||
max = minMax.max
|
||||
|
||||
highlightShapeLayer = nil
|
||||
|
||||
// Remove things before drawing, e.g. when changing orientation
|
||||
|
||||
for view in self.subviews {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
for layer in layerStore {
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
layerStore.removeAll()
|
||||
|
||||
// Draw content
|
||||
|
||||
for (index, series) in self.series.enumerated() {
|
||||
|
||||
// Separate each line in multiple segments over and below the x axis
|
||||
let segments = Chart.segmentLine(series.data as ChartLineSegment, zeroLevel: series.colors.zeroLevel)
|
||||
|
||||
segments.forEach({ segment in
|
||||
let scaledXValues = scaleValuesOnXAxis( segment.map { $0.x } )
|
||||
let scaledYValues = scaleValuesOnYAxis( segment.map { $0.y } )
|
||||
|
||||
if series.line {
|
||||
drawLine(scaledXValues, yValues: scaledYValues, seriesIndex: index)
|
||||
}
|
||||
if series.area {
|
||||
drawArea(scaledXValues, yValues: scaledYValues, seriesIndex: index)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
drawAxes()
|
||||
|
||||
if showXLabelsAndGrid && (xLabels != nil || series.count > 0) {
|
||||
drawLabelsAndGridOnXAxis()
|
||||
}
|
||||
if showYLabelsAndGrid && (yLabels != nil || series.count > 0) {
|
||||
drawLabelsAndGridOnYAxis()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Scaling
|
||||
|
||||
fileprivate func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
|
||||
// Start with user-provided values
|
||||
|
||||
var min = (x: minX, y: minY)
|
||||
var max = (x: maxX, y: maxY)
|
||||
|
||||
// Check in datasets
|
||||
|
||||
for series in self.series {
|
||||
let xValues = series.data.map { $0.x }
|
||||
let yValues = series.data.map { $0.y }
|
||||
|
||||
let newMinX = xValues.minOrZero()
|
||||
let newMinY = yValues.minOrZero()
|
||||
let newMaxX = xValues.maxOrZero()
|
||||
let newMaxY = yValues.maxOrZero()
|
||||
|
||||
if min.x == nil || newMinX < min.x! { min.x = newMinX }
|
||||
if min.y == nil || newMinY < min.y! { min.y = newMinY }
|
||||
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
|
||||
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
|
||||
}
|
||||
|
||||
// Check in labels
|
||||
|
||||
if let xLabels = self.xLabels {
|
||||
let newMinX = xLabels.minOrZero()
|
||||
let newMaxX = xLabels.maxOrZero()
|
||||
if min.x == nil || newMinX < min.x! { min.x = newMinX }
|
||||
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
|
||||
}
|
||||
|
||||
if let yLabels = self.yLabels {
|
||||
let newMinY = yLabels.minOrZero()
|
||||
let newMaxY = yLabels.maxOrZero()
|
||||
if min.y == nil || newMinY < min.y! { min.y = newMinY }
|
||||
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
|
||||
}
|
||||
|
||||
if min.x == nil { min.x = 0 }
|
||||
if min.y == nil { min.y = 0 }
|
||||
if max.x == nil { max.x = 0 }
|
||||
if max.y == nil { max.y = 0 }
|
||||
|
||||
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
|
||||
}
|
||||
|
||||
fileprivate func scaleValuesOnXAxis(_ values: [Double]) -> [Double] {
|
||||
let width = Double(drawingWidth)
|
||||
|
||||
var factor: Double
|
||||
if max.x - min.x == 0 {
|
||||
factor = 0
|
||||
} else {
|
||||
factor = width / (max.x - min.x)
|
||||
}
|
||||
|
||||
let scaled = values.map { factor * ($0 - self.min.x) }
|
||||
return scaled
|
||||
}
|
||||
|
||||
fileprivate func scaleValuesOnYAxis(_ values: [Double]) -> [Double] {
|
||||
let height = Double(drawingHeight)
|
||||
var factor: Double
|
||||
if max.y - min.y == 0 {
|
||||
factor = 0
|
||||
} else {
|
||||
factor = height / (max.y - min.y)
|
||||
}
|
||||
|
||||
let scaled = values.map { Double(self.topInset) + height - factor * ($0 - self.min.y) }
|
||||
|
||||
return scaled
|
||||
}
|
||||
|
||||
fileprivate func scaleValueOnYAxis(_ value: Double) -> Double {
|
||||
let height = Double(drawingHeight)
|
||||
var factor: Double
|
||||
if max.y - min.y == 0 {
|
||||
factor = 0
|
||||
} else {
|
||||
factor = height / (max.y - min.y)
|
||||
}
|
||||
|
||||
let scaled = Double(self.topInset) + height - factor * (value - min.y)
|
||||
return scaled
|
||||
}
|
||||
|
||||
fileprivate func getZeroValueOnYAxis(zeroLevel: Double) -> Double {
|
||||
if min.y > zeroLevel {
|
||||
return scaleValueOnYAxis(min.y)
|
||||
} else {
|
||||
return scaleValueOnYAxis(zeroLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Drawings
|
||||
|
||||
fileprivate func drawLine(_ xValues: [Double], yValues: [Double], seriesIndex: Int) {
|
||||
// YValues are "reverted" from top to bottom, so 'above' means <= level
|
||||
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
|
||||
let path = CGMutablePath()
|
||||
path.move(to: CGPoint(x: CGFloat(xValues.first!), y: CGFloat(yValues.first!)))
|
||||
for i in 1..<yValues.count {
|
||||
let y = yValues[i]
|
||||
path.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(y)))
|
||||
}
|
||||
|
||||
let lineLayer = CAShapeLayer()
|
||||
lineLayer.frame = self.bounds
|
||||
lineLayer.path = path
|
||||
|
||||
if isAboveZeroLine {
|
||||
lineLayer.strokeColor = series[seriesIndex].colors.above.cgColor
|
||||
} else {
|
||||
lineLayer.strokeColor = series[seriesIndex].colors.below.cgColor
|
||||
}
|
||||
lineLayer.fillColor = nil
|
||||
lineLayer.lineWidth = lineWidth
|
||||
lineLayer.lineJoin = kCALineJoinBevel
|
||||
|
||||
self.layer.addSublayer(lineLayer)
|
||||
|
||||
layerStore.append(lineLayer)
|
||||
}
|
||||
|
||||
fileprivate func drawArea(_ xValues: [Double], yValues: [Double], seriesIndex: Int) {
|
||||
// YValues are "reverted" from top to bottom, so 'above' means <= level
|
||||
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
|
||||
let area = CGMutablePath()
|
||||
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: series[seriesIndex].colors.zeroLevel))
|
||||
|
||||
area.move(to: CGPoint(x: CGFloat(xValues[0]), y: zero))
|
||||
for i in 0..<xValues.count {
|
||||
area.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(yValues[i])))
|
||||
}
|
||||
area.addLine(to: CGPoint(x: CGFloat(xValues.last!), y: zero))
|
||||
let areaLayer = CAShapeLayer()
|
||||
areaLayer.frame = self.bounds
|
||||
areaLayer.path = area
|
||||
areaLayer.strokeColor = nil
|
||||
if isAboveZeroLine {
|
||||
areaLayer.fillColor = series[seriesIndex].colors.above.withAlphaComponent(areaAlphaComponent).cgColor
|
||||
} else {
|
||||
areaLayer.fillColor = series[seriesIndex].colors.below.withAlphaComponent(areaAlphaComponent).cgColor
|
||||
}
|
||||
areaLayer.lineWidth = 0
|
||||
|
||||
self.layer.addSublayer(areaLayer)
|
||||
|
||||
layerStore.append(areaLayer)
|
||||
}
|
||||
|
||||
fileprivate func drawAxes() {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
context.setStrokeColor(axesColor.cgColor)
|
||||
context.setLineWidth(0.5)
|
||||
|
||||
// horizontal axis at the bottom
|
||||
context.move(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
|
||||
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
|
||||
context.strokePath()
|
||||
|
||||
// horizontal axis at the top
|
||||
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
|
||||
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
|
||||
context.strokePath()
|
||||
|
||||
// horizontal axis when y = 0
|
||||
if min.y < 0 && max.y > 0 {
|
||||
let y = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
|
||||
context.move(to: CGPoint(x: CGFloat(0), y: y))
|
||||
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: y))
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
// vertical axis on the left
|
||||
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
|
||||
context.addLine(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
|
||||
context.strokePath()
|
||||
|
||||
// vertical axis on the right
|
||||
context.move(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
|
||||
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
fileprivate func drawLabelsAndGridOnXAxis() {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
context.setStrokeColor(gridColor.cgColor)
|
||||
context.setLineWidth(0.5)
|
||||
|
||||
var labels: [Double]
|
||||
if xLabels == nil {
|
||||
// Use labels from the first series
|
||||
labels = series[0].data.map({ (point: ChartPoint) -> Double in
|
||||
return point.x })
|
||||
} else {
|
||||
labels = xLabels!
|
||||
}
|
||||
|
||||
let scaled = scaleValuesOnXAxis(labels)
|
||||
let padding: CGFloat = 5
|
||||
scaled.enumerated().forEach { (i, value) in
|
||||
let x = CGFloat(value)
|
||||
let isLastLabel = x == drawingWidth
|
||||
|
||||
// Add vertical grid for each label, except axes on the left and right
|
||||
|
||||
if x != 0 && x != drawingWidth {
|
||||
context.move(to: CGPoint(x: x, y: CGFloat(0)))
|
||||
context.addLine(to: CGPoint(x: x, y: bounds.height))
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
if xLabelsSkipLast && isLastLabel {
|
||||
// Do not add label at the most right position
|
||||
return
|
||||
}
|
||||
|
||||
// Add label
|
||||
let label = UILabel(frame: CGRect(x: x, y: drawingHeight, width: 0, height: 0))
|
||||
label.font = labelFont
|
||||
label.text = xLabelsFormatter(i, labels[i])
|
||||
label.textColor = labelColor
|
||||
|
||||
// Set label size
|
||||
label.sizeToFit()
|
||||
// Center label vertically
|
||||
label.frame.origin.y += topInset
|
||||
if xLabelsOrientation == .horizontal {
|
||||
// Add left padding
|
||||
label.frame.origin.y -= (label.frame.height - bottomInset) / 2
|
||||
label.frame.origin.x += padding
|
||||
|
||||
// Set label's text alignment
|
||||
label.frame.size.width = (drawingWidth / CGFloat(labels.count)) - padding * 2
|
||||
label.textAlignment = xLabelsTextAlignment
|
||||
} else {
|
||||
label.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
|
||||
|
||||
// Adjust vertical position according to the label's height
|
||||
label.frame.origin.y += label.frame.size.height / 2
|
||||
|
||||
// Adjust horizontal position as the series line
|
||||
label.frame.origin.x = x
|
||||
if xLabelsTextAlignment == .center {
|
||||
// Align horizontally in series
|
||||
label.frame.origin.x += ((drawingWidth / CGFloat(labels.count)) / 2) - (label.frame.size.width / 2)
|
||||
} else {
|
||||
// Give some space from the vertical line
|
||||
label.frame.origin.x += padding
|
||||
}
|
||||
}
|
||||
self.addSubview(label)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func drawLabelsAndGridOnYAxis() {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
context.setStrokeColor(gridColor.cgColor)
|
||||
context.setLineWidth(0.5)
|
||||
|
||||
var labels: [Double]
|
||||
if yLabels == nil {
|
||||
labels = [(min.y + max.y) / 2, max.y]
|
||||
if yLabelsOnRightSide || min.y != 0 {
|
||||
labels.insert(min.y, at: 0)
|
||||
}
|
||||
} else {
|
||||
labels = yLabels!
|
||||
}
|
||||
|
||||
let scaled = scaleValuesOnYAxis(labels)
|
||||
let padding: CGFloat = 5
|
||||
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
|
||||
|
||||
scaled.enumerated().forEach { (i, value) in
|
||||
|
||||
let y = CGFloat(value)
|
||||
|
||||
// Add horizontal grid for each label, but not over axes
|
||||
if y != drawingHeight + topInset && y != zero {
|
||||
|
||||
context.move(to: CGPoint(x: CGFloat(0), y: y))
|
||||
context.addLine(to: CGPoint(x: self.bounds.width, y: y))
|
||||
if labels[i] != 0 {
|
||||
// Horizontal grid for 0 is not dashed
|
||||
context.setLineDash(phase: CGFloat(0), lengths: [CGFloat(5)])
|
||||
} else {
|
||||
context.setLineDash(phase: CGFloat(0), lengths: [])
|
||||
}
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
let label = UILabel(frame: CGRect(x: padding, y: y, width: 0, height: 0))
|
||||
label.font = labelFont
|
||||
label.text = yLabelsFormatter(i, labels[i])
|
||||
label.textColor = labelColor
|
||||
label.sizeToFit()
|
||||
|
||||
if yLabelsOnRightSide {
|
||||
label.frame.origin.x = drawingWidth
|
||||
label.frame.origin.x -= label.frame.width + padding
|
||||
}
|
||||
|
||||
// Labels should be placed above the horizontal grid
|
||||
label.frame.origin.y -= label.frame.height
|
||||
|
||||
self.addSubview(label)
|
||||
}
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
// MARK: - Touch events
|
||||
|
||||
fileprivate func drawHighlightLineFromLeftPosition(_ left: CGFloat) {
|
||||
if let shapeLayer = highlightShapeLayer {
|
||||
// Use line already created
|
||||
let path = CGMutablePath()
|
||||
|
||||
path.move(to: CGPoint(x: left, y: 0))
|
||||
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
|
||||
shapeLayer.path = path
|
||||
} else {
|
||||
// Create the line
|
||||
let path = CGMutablePath()
|
||||
|
||||
path.move(to: CGPoint(x: left, y: CGFloat(0)))
|
||||
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
|
||||
let shapeLayer = CAShapeLayer()
|
||||
shapeLayer.frame = self.bounds
|
||||
shapeLayer.path = path
|
||||
shapeLayer.strokeColor = highlightLineColor.cgColor
|
||||
shapeLayer.fillColor = nil
|
||||
shapeLayer.lineWidth = highlightLineWidth
|
||||
|
||||
highlightShapeLayer = shapeLayer
|
||||
layer.addSublayer(shapeLayer)
|
||||
layerStore.append(shapeLayer)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTouchEvents(_ touches: Set<UITouch>, event: UIEvent!) {
|
||||
let point = touches.first!
|
||||
let left = point.location(in: self).x
|
||||
let x = valueFromPointAtX(left)
|
||||
|
||||
if left < 0 || left > (drawingWidth as CGFloat) {
|
||||
// Remove highlight line at the end of the touch event
|
||||
if let shapeLayer = highlightShapeLayer {
|
||||
shapeLayer.path = nil
|
||||
}
|
||||
delegate?.didFinishTouchingChart(self)
|
||||
return
|
||||
}
|
||||
|
||||
drawHighlightLineFromLeftPosition(left)
|
||||
|
||||
if delegate == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var indexes: [Int?] = []
|
||||
|
||||
for series in self.series {
|
||||
var index: Int? = nil
|
||||
let xValues = series.data.map({ (point: ChartPoint) -> Double in
|
||||
return point.x })
|
||||
let closest = Chart.findClosestInValues(xValues, forValue: x)
|
||||
if closest.lowestIndex != nil && closest.highestIndex != nil {
|
||||
// Consider valid only values on the right
|
||||
index = closest.lowestIndex
|
||||
}
|
||||
indexes.append(index)
|
||||
}
|
||||
delegate!.didTouchChart(self, indexes: indexes, x: x, left: left)
|
||||
}
|
||||
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
handleTouchEvents(touches, event: event)
|
||||
}
|
||||
|
||||
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
handleTouchEvents(touches, event: event)
|
||||
if self.hideHighlightLineOnTouchEnd {
|
||||
if let shapeLayer = highlightShapeLayer {
|
||||
shapeLayer.path = nil
|
||||
}
|
||||
}
|
||||
delegate?.didEndTouchingChart(self)
|
||||
}
|
||||
|
||||
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
handleTouchEvents(touches, event: event)
|
||||
}
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
fileprivate func valueFromPointAtX(_ x: CGFloat) -> Double {
|
||||
let value = ((max.x-min.x) / Double(drawingWidth)) * Double(x) + min.x
|
||||
return value
|
||||
}
|
||||
|
||||
fileprivate func valueFromPointAtY(_ y: CGFloat) -> Double {
|
||||
let value = ((max.y - min.y) / Double(drawingHeight)) * Double(y) + min.y
|
||||
return -value
|
||||
}
|
||||
|
||||
fileprivate class func findClosestInValues(
|
||||
_ values: [Double],
|
||||
forValue value: Double
|
||||
) -> (
|
||||
lowestValue: Double?,
|
||||
highestValue: Double?,
|
||||
lowestIndex: Int?,
|
||||
highestIndex: Int?
|
||||
) {
|
||||
var lowestValue: Double?, highestValue: Double?, lowestIndex: Int?, highestIndex: Int?
|
||||
|
||||
values.enumerated().forEach { (i, currentValue) in
|
||||
|
||||
if currentValue <= value && (lowestValue == nil || lowestValue! < currentValue) {
|
||||
lowestValue = currentValue
|
||||
lowestIndex = i
|
||||
}
|
||||
if currentValue >= value && (highestValue == nil || highestValue! > currentValue) {
|
||||
highestValue = currentValue
|
||||
highestIndex = i
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
lowestValue: lowestValue,
|
||||
highestValue: highestValue,
|
||||
lowestIndex: lowestIndex,
|
||||
highestIndex: highestIndex
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Segment a line in multiple lines when the line touches the x-axis, i.e. separating
|
||||
positive from negative values.
|
||||
*/
|
||||
fileprivate class func segmentLine(_ line: ChartLineSegment, zeroLevel: Double) -> [ChartLineSegment] {
|
||||
var segments: [ChartLineSegment] = []
|
||||
var segment: ChartLineSegment = []
|
||||
|
||||
line.enumerated().forEach { (i, point) in
|
||||
segment.append(point)
|
||||
if i < line.count - 1 {
|
||||
let nextPoint = line[i+1]
|
||||
if point.y >= zeroLevel && nextPoint.y < zeroLevel || point.y < zeroLevel && nextPoint.y >= zeroLevel {
|
||||
// The segment intersects zeroLevel, close the segment with the intersection point
|
||||
let closingPoint = Chart.intersectionWithLevel(point, and: nextPoint, level: zeroLevel)
|
||||
segment.append(closingPoint)
|
||||
segments.append(segment)
|
||||
// Start a new segment
|
||||
segment = [closingPoint]
|
||||
}
|
||||
} else {
|
||||
// End of the line
|
||||
segments.append(segment)
|
||||
}
|
||||
}
|
||||
return segments
|
||||
}
|
||||
|
||||
/**
|
||||
Return the intersection of a line between two points and 'y = level' line
|
||||
*/
|
||||
fileprivate class func intersectionWithLevel(_ p1: ChartPoint, and p2: ChartPoint, level: Double) -> ChartPoint {
|
||||
let dy1 = level - p1.y
|
||||
let dy2 = level - p2.y
|
||||
return (x: (p2.x * dy1 - p1.x * dy2) / (dy1 - dy2), y: level)
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element == Double {
|
||||
func minOrZero() -> Double {
|
||||
return self.min() ?? 0.0
|
||||
}
|
||||
func maxOrZero() -> Double {
|
||||
return self.max() ?? 0.0
|
||||
}
|
||||
}
|
||||
61
Pods/SwiftChart/Source/ChartColors.swift
generated
Normal file
61
Pods/SwiftChart/Source/ChartColors.swift
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// ChartColors.swift
|
||||
//
|
||||
// Created by Giampaolo Bellavite on 07/11/14.
|
||||
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/**
|
||||
Shorthands for various colors to use in the charts.
|
||||
*/
|
||||
public struct ChartColors {
|
||||
|
||||
static fileprivate func colorFromHex(_ hex: Int) -> UIColor {
|
||||
let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0
|
||||
let green = CGFloat((hex & 0xFF00) >> 8) / 255.0
|
||||
let blue = CGFloat((hex & 0xFF)) / 255.0
|
||||
return UIColor(red: red, green: green, blue: blue, alpha: 1)
|
||||
}
|
||||
|
||||
static public func blueColor() -> UIColor {
|
||||
return colorFromHex(0x4A90E2)
|
||||
}
|
||||
static public func orangeColor() -> UIColor {
|
||||
return colorFromHex(0xF5A623)
|
||||
}
|
||||
static public func greenColor() -> UIColor {
|
||||
return colorFromHex(0x7ED321)
|
||||
}
|
||||
static public func darkGreenColor() -> UIColor {
|
||||
return colorFromHex(0x417505)
|
||||
}
|
||||
static public func redColor() -> UIColor {
|
||||
return colorFromHex(0xFF3200)
|
||||
}
|
||||
static public func darkRedColor() -> UIColor {
|
||||
return colorFromHex(0xD0021B)
|
||||
}
|
||||
static public func purpleColor() -> UIColor {
|
||||
return colorFromHex(0x9013FE)
|
||||
}
|
||||
static public func maroonColor() -> UIColor {
|
||||
return colorFromHex(0x8B572A)
|
||||
}
|
||||
static public func pinkColor() -> UIColor {
|
||||
return colorFromHex(0xBD10E0)
|
||||
}
|
||||
static public func greyColor() -> UIColor {
|
||||
return colorFromHex(0x7f7f7f)
|
||||
}
|
||||
static public func cyanColor() -> UIColor {
|
||||
return colorFromHex(0x50E3C2)
|
||||
}
|
||||
static public func goldColor() -> UIColor {
|
||||
return colorFromHex(0xbcbd22)
|
||||
}
|
||||
static public func yellowColor() -> UIColor {
|
||||
return colorFromHex(0xF8E71C)
|
||||
}
|
||||
}
|
||||
66
Pods/SwiftChart/Source/ChartSeries.swift
generated
Normal file
66
Pods/SwiftChart/Source/ChartSeries.swift
generated
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// ChartSeries.swift
|
||||
//
|
||||
// Created by Giampaolo Bellavite on 07/11/14.
|
||||
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/**
|
||||
The `ChartSeries` class create a chart series and configure its appearance and behavior.
|
||||
*/
|
||||
open class ChartSeries {
|
||||
/**
|
||||
The data used for the chart series.
|
||||
*/
|
||||
open var data: [(x: Double, y: Double)]
|
||||
|
||||
/**
|
||||
When set to `false`, will hide the series line. Useful for drawing only the area with `area=true`.
|
||||
*/
|
||||
open var line: Bool = true
|
||||
|
||||
/**
|
||||
Draws an area below the series line.
|
||||
*/
|
||||
open var area: Bool = false
|
||||
|
||||
/**
|
||||
The series color.
|
||||
*/
|
||||
open var color: UIColor = ChartColors.blueColor() {
|
||||
didSet {
|
||||
colors = (above: color, below: color, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A tuple to specify the color above or below the zero
|
||||
*/
|
||||
open var colors: (
|
||||
above: UIColor,
|
||||
below: UIColor,
|
||||
zeroLevel: Double
|
||||
) = (above: ChartColors.blueColor(), below: ChartColors.redColor(), 0)
|
||||
|
||||
public init(_ data: [Double]) {
|
||||
self.data = []
|
||||
data.enumerated().forEach { (x, y) in
|
||||
let point: (x: Double, y: Double) = (x: Double(x), y: y)
|
||||
self.data.append(point)
|
||||
}
|
||||
}
|
||||
|
||||
public init(data: [(x: Double, y: Double)]) {
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(data: [(x: Int, y: Double)]) {
|
||||
self.data = data.map { (Double($0.x), Double($0.y)) }
|
||||
}
|
||||
|
||||
public init(data: [(x: Float, y: Float)]) {
|
||||
self.data = data.map { (Double($0.x), Double($0.y)) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user